HP OpenVMS SystemsC++ Programming Language |
HP C++
|
Previous | Contents | Index |
A debugger helps you find run-time errors by letting you observe and interactively manipulate program execution step by step, until you discover where the program functions incorrectly. The OpenVMS Debugger is symbolic, meaning that you can refer to symbolic names for the memory addresses allocated to variables, routines, labels, and so on. You need not use virtual addresses.
The language of the source program you are currently debugging determines the format you use to enter and display data. The language also determines the format used for features, such as comment characters, operators, and operator precedence, which have language-specific settings. However, if you have modules written in another language, you can switch from one language to another during your debugging session.
The OpenVMS Debugger supports the language constructs of C++ and other debugger-supported programming languages. This section describes features specific to debugging C++ programs. For more information on the OpenVMS Debugger, see the OpenVMS Debugger Manual.
To use the debugger, compile and link your program with the /DEBUG qualifier on both commands. On the compiler command, the /DEBUG qualifier writes into the object module the debug symbol records declared in the program source file. These records make the names of variables and other declared symbols accessible to debugger commands. If your program has several compilation units, make sure you use the /DEBUG qualifier to compile each unit you want to debug.
On OpenVMS I64 systems, specifying /DEBUG gives you /DEBUG=(TRACEBACK,SYMBOLS=BRIEF), which omits debug information for unused labels and unused types, even when /NOOPTIMIZE is specified. This feature results in much smaller object files. To include unused labels and types, specify the SYMBOLS=NOBRIEF keyword explicitly (/DEBUG=(SYMBOLS=NOBRIEF)).
On OpenVMS Alpha systems, specifying /DEBUG gives you /DEBUG=(TRACEBACK,SYMBOLS), which effectively gives you /DEBUG=(TRACEBACK,SYMBOLS=NOBRIEF).
Additionally, use the /NOOPTIMIZE qualifier with the compiler command. Optimized code can reduce program size and increase execution speed, but can also create inconsistencies in memory content that adversely affects debugging. Use the default /OPTIMIZE qualifier only with programs that have been completely debugged.
Additionally, compilation with normal (full) optimization will have the following noticeable effects on OpenVMS Alpha systems:
On the linker command, the /DEBUG qualifier incorporates into the executable image all the symbol information contained in the object modules. Using the /DEBUG qualifier on the linker command also starts the debugger at run time.
The compiler provides a set of debugger options that you can specify to the /DEBUG qualifier on the compiler command line. These options determine the kind of information that the compiler places in the object module for use by the OpenVMS Debugger. These debugger options include using traceback records and using the debugger symbol table. For more information, see the /DEBUG qualifier in Appendix A.
When you enter the DCL run command and specify your executable image file, the OpenVMS Debugger takes control. The debugger displays a message indicating its version, the programming language the source code is written in, and the name of the image file. When the DBG> prompt appears, you can enter debugger commands.
To execute the program, enter the debugger go command. Execution proceeds until the debugger pauses or stops the program (for example, to prompt you for user input, to signal an error, or to inform you that your program completed successfully).
To interrupt the debugging session in progress, press Ctrl/C. The DBG> prompt displays and you can again enter debugger commands.
To end a debugging session, enter the debugger exit command or press Ctrl/Z.
This section describes features essential for debugging C++ programs.
The OpenVMS Debugger is in C++ language mode when invoked against a main program or routine written in C++. If you are debugging an application with modules written in some language other than C++, you may switch back to C++ language mode by using the command set language c_plus_plus .
You can use the show language command to determine the language mode set. For example:
DBG> show language language: C_PLUS_PLUS DBG> |
This section describes the built-in operators that you can use in debugger commands. The operators in C++ language expressions are as follows:
Symbol | Function | Kind |
---|---|---|
* | Indirection | Prefix |
& | Address of | Prefix |
sizeof | size of | Prefix |
-- | Unary minus (negation) | Prefix |
+ | Addition | Infix |
-- | Subtraction | Infix |
* | Multiplication | Infix |
/ | Division | Infix |
% | Remainder | Infix |
<< | Left shift | Infix |
>> | Right shift | Infix |
== | Equal to | Infix |
!= | Not equal to | Infix |
> | Greater than | Infix |
>= | Greater than or equal to | Infix |
< | Less than | Infix |
<= | Less than or equal to | Infix |
~ (tilde) | Bit-wise NOT | Prefix |
& | Bit-wise AND | Infix |
| | Bit-wise OR | Infix |
^ | Bit-wise exclusive OR | Infix |
! | Logical NOT | Prefix |
&& | Logical AND | Infix |
|| | Logical OR | Infix |
Because the exclamation point (!) is an operator, it cannot be used in C++ programs as a comment delimiter. However, to permit debugger log files to be used as debugger input, the debugger still recognizes the exclamation point as a comment delimiter if it is the first nonspace character on a line. In C++ language mode, the debugger accepts a forward slash immediately followed by an asterisk (/*) as the comment delimiter. The comment continues to the end of the current line. A matching asterisk immediately followed by a slash (*/) is neither needed nor recognized.
The debugger accepts the asterisk (*) prefix as an indirection operator in both C++ language expressions and debugger address expressions. In address expressions, the asterisk prefix is synonymous to the period (.) prefix or the at sign (@) prefix when the language is set to C++.
To prevent unintended modifications to the program being debugged, the debugger does not support any of the assignment operators in C++ (or any other language). Thus, such operators as =, +=, --=, ++, and -- are not recognized. To alter the contents of a memory location, you must do so with an explicit deposit command.
The supported constructs in language and address expressions for C++ are as follows:
Symbol | Construct |
---|---|
[ ] | Subscripting |
. (period) | Structure component selection |
-> | Pointer dereferencing |
Predefined data types supported in the debugger are as follows:
C++ Data Type | OpenVMS Data Type Name |
---|---|
int , long | Longword Integer |
unsigned int , unsigned long | Longword Unsigned |
long long | Quadword Integer |
unsigned long long | Quadword Unsigned |
short int | Word Integer |
unsigned short int | Word Unsigned |
char | Byte Integer |
unsigned char | Byte Unsigned |
float | F_Floating (Alpha default), S_Floating (I64 default) |
double | G_Floating (Alpha default), T_Floating (I64 default), D_Floating |
enum | None |
struct | None |
union | None |
class | None |
Pointer type | None |
Array type | None |
Uppercase letters in parentheses represent standard data type mnemonics in the OpenVMS common language environment. For more information, see OpenVMS Programming Interfaces: Calling a System Routine.
Supported data types specific to OpenVMS systems are as follows:
C++ Data Type | OpenVMS Data Type Name |
---|---|
__int16 | Word Integer |
unsigned __int16 | Word Unsigned |
__int32 | Longword Integer |
unsigned __int32 | Longword Unsigned |
__int64 | Quadword Integer |
unsigned __int64 | Quadword Unsigned |
This section describes how to use the OpenVMS Debugger with C++ data.
This section describes how to refer to data members that are not declared static.
To refer to a nonstatic data member that is defined directly in a C++ class (or a struct or union ), use its name just as with a C language struct or union member. The following example shows the correct use of a nonstatic data member reference:
DBG> examine x.m, p->m |
Currently, debugger support distinguishes nonstatic data members inherited from various base classes by prefixing their names with a sequence of significant base class names on the inheritance path to the member, and then the class that the member is declared in. A base class on a path from an object to a member is significant if the base class in question is derived from using multiple inheritance. Thus, a base class is significant if it is mentioned in a base list containing more than one base specifier.
This notation generates the minimum number of base class prefixes necessary to describe the inheritance path to a base class, because it involves naming only those base classes where one must choose where to proceed next when traversing the path. When no multiple inheritance is involved, the reference has the following syntax:
CLASS::member |
Specify the sequence of significant base classes in the order from the object's most derived significant class, to the significant base class closest to the object.
Because the debugger understands the concept of reference objects and reference members to objects, you can examine a reference object or reference member directly, without dereferencing it as you would for a pointer. To access the values of objects declared with a reference, use the name of the object.
For example, consider the following code:
class C { public: int &ref_mem; C(int &arg) : ref_mem(arg) {} }; main() { auto int obj = 5; auto int &ref_obj = obj; auto C c(obj); obj = 23; } ... |
The following sequence shows the correct way to use the debugger to examine the members:
break at R8_2_3\main\%LINE 13 13: } DBG> exam obj, ref_obj R8_2_3\main\obj: 23 R8_2_3\main\ref_obj: 23 DBG> exam c R8_2_3\main\c: class C ref_mem: 23 DBG> exam c.ref_mem R8_2_3\main\c.ref_mem: 23 |
For Alpha systems compiled with /MODEL=ANSI and for I64 systems, a pointer to member is an offset into a structure.
Consider the following example:
struct A { int mem0; }; struct B { int mem1; int mem2; }; struct C : public A, public B { int mem3; int mem4; }; /* pointer to member initalized with pointer to member * address of the same class. */ int C::*pmc = &C::mem2; /* pointer to member initialized with pointer to member * address of one of the * base classses. An implicit * conversion occurs. */ int C::*pmbc = &B::mem2; extern "C" printf (const char *,...); main() { C *cinst = new C; cinst->*pmc = 7; printf("cinst pointer to member value is %d\n",cinst->mem2); } |
If you compile this program with the /NOOPTIMIZE/DEBUG qualifiers, from the last line in the program, you can use the pointer to member to display the following information:
DBG> set radix hex DBG> exam *cinst *EX8_2_4\main\cinst: struct C inherit A mem0: 00000000 inherit B mem1: 00000000 mem2: 0000000A mem3: 00000000 mem4: 00000000 DBG> set radix hex DBG> exa pmc EX8_2_4\pmc: 00000008 DBG> exam pmbc EX8_2_4\pmbc: 00000008 DBG> exam cinst EX8_2_4\main\cinst: 0000000080000090 DBG> exam 080000090+8 0000000080000098: 00000007 |
For the preceding sample program, the above debug sequence examines the pointer to member ( pmc or pmbc ) to obtain an offset into the structure, and adds this value to the address of the object ( *cinst ). In our example, this is *cinst + the value of pmc .
For Alpha systems compiled with the default object model (/MODEL=ARM), a pointer to member involves executing a piece of function-like code, called a thunk.
The argument to this function is the address of the base class containing the member. This address is obtained by adding the offset of the start of the base class to the address of the object. This offset adjustment is needed when the pointer to member refers to a multiply inherited base class.
A sample debug sequence for the previous program example follows:
DBG> set radix hex DBG> sho sym /full C type C struct (C, 2 components), size: 20 bytes inherits: A, size: 4 bytes, offset: 0000000000000000 bytes B, size: 8 bytes, offset: 0000000000000004 bytes contains the following members: mem3 : longword integer, size: 4 bytes, offset: 000000000000000C bytes mem4 : longword integer, size: 4 bytes, offset: 0000000000000010 bytes DBG> exam pmc EX8_2_4\pmc: 000100D8 DBG> exam cinst EX8_2_4\main\cinst: 006706F0 DBG> call 000100D8(006706F0+4) value returned is 006706F8 DBG> exam 006706F8 00000000006706F8: 00000007 |
This debug sequence first obtains the offset to the start of the nested class containing the member pointed to with show sym /full . In this case, the offset is 4.
It then examines the pointer to member ( pmc ) and determines the address of the object ( cinst ). In our case, pmc = 100D8 (thunk) and cinst = 6706F0.
Then it calls the thunk, passing the address of the object plus the offset: CALL 000100D8(006706F0+4). This call to the thunk returns the address of the member.
Finally, it examines the member (006706F8).
To examine and display the value of an object or member by type, use the command examine/type . Similarly, you can modify the value of an expression to be deposited to a type you specify by using the command deposit/type . With the /type qualifier, the syntax for these commands is as follows:
deposit/type=(name) examine/type=(name) |
The type denoted by name must be the name of a variable or data type declared in the program. The /type qualifier is particularly useful for referencing C++ objects that have been declared with more than one type.
This section describes how to reference the various kinds of functions and function arguments.
You can use the debug SHOW SYMBOL command to see all the overloaded names for a given function. You can set breakpoints on an overloaded function by specifying either the object name and function name followed by the argument types, or by specifying the class name and function name followed by the arguments.
For example, consider the following sample program:
extern "C" {int printf(const char *,...);} class base{ public: base(){}; base(int){}; ~base(){}; void base_f1() {printf("called base_f1()\n");} void base_f2() {printf("called base_f2()\n");} void base_f2(int) {printf("called base_f2(int)\n");} void base_f2(char c) {printf("call base_f2(char)\n");} }; int main() { base b; base b1(1); b.base_f1(); b.base_f2(10); b.base_f2(); b.base_f2('c'); } |
The following debug sequence for the previous sample program shows how to set breakpoints on overloaded symbols and how to list these functions:
DBG> s stepped to EX8_3_1\main\%LINE 20 20: base b1(1); DBG> set break base::base_f1 DBG> set break base::base_f2 %DEBUG-I-NOTUNQOVR, symbol 'base::base_f2' is overloaded overloaded name base::base_f2 instance base::base_f2(char) instance base::base_f2(int) instance base::base_f2() %DEBUG-E-REENTER, reenter the command using a more precise pathname DBG> set break base::base_f2(char) |
The C++ I64 debugger supports the following format for setting a break on a destructor:
DBG> set break stack::~stack() DBG> set break stack::~stack(int) |
Older (Alpha) debuggers require use of the %name syntax:
DBG> set break stack::%name'~stack' |
The set of atomic types are drawn from the following set of names:
void char signed_char unsigned_char signed_short unsigned_short int signed_int unsigned_int signed_long unsigned_long float double long_double |
Pointer types are named (type)* . Reference types are named (type)& . The types struct , union , class , and enum are named by their tags, and the qualifiers const and volatile precede their types with a space in between. For example:
DBG> set break C::int, C::(const S)& |
The following operators can be overloaded by user-defined functions:
+ - * / % ^ & | ~ ! = < > += -= *= /= %= ^= &= |= << >> >>= <<= == != <= >= && | ++ -- ->* , -> [] () delete new |
The following example shows the correct use of user-defined function references:
DBG> set break stack::%name'operator++'() |
In OpenVMS Debugger referencing, you use this , *this , and this->m as follows:
DBG> examine this |
DBG> examine *this |
DBG> examine this->m |
When calling C++ member functions from the debugger, you cannot make the call using the same syntax that you would use in a C++ source file. You must call the class-qualified member function name with the object as the first argument.
For example:
extern "C" void printf(const char *,...); class C12 { int i; int j; public: static int sum; public: C12() : i(1), j(2) {} void method(); static int get_sum() { printf("called static function get_sum()\n"); return sum; } }; void C12::method() { i = i + j; printf("C12::method called: i=%d, j=%d\n",i,j); } int C12::sum = 0; main() { C12 cinst; cinst.method(); C12::get_sum(); printf("End of example.\n"); } |
When you compile this example with /DEBUG/NOOPT, you can call the member function with the following command:
DBG> call C12::method(cinst) |
Be aware that when a nonstatic member function is called, the compiler passes an implicit first parameter, the "this" pointer. But, when using the debugger's call instruction, you must explicitly pass this hidden first argument:
//Call the nonstatic member function: DBG> call cinst.method(cinst) C12::method called: i=3 j=2 value returned is 28 // notice that the following call confuses debug: DBG> call cinst.method() %DEBUG-E-MISOPEMIS, misplaced operator or missing operand at 'end of expression' |
However, when calling a static member function, there is no implict this pointer and there function may be called using the class name or the object name:
// Call the static function: DBG> call C12::get_sum called static function get_sum() value returned is 0 DBG> call cinst.get_sum called static function get_sum() value returned is 0 |
Previous | Next | Contents | Index |