HP OpenVMS Systems

C++ Programming Language
Content starts here HP C++

HP C++
User's Guide for OpenVMS Systems


Previous Contents Index


Chapter 8
Using the OpenVMS Debugger

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.

8.1 Debugging C++ Programs

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.

8.1.1 Compiling and Linking in Preparation for Debugging

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.

8.1.2 Debugger Support

Additionally, compilation with normal (full) optimization will have the following noticeable effects on OpenVMS Alpha systems:

  • Stepping by line will generally seem to bounce forward and back, due to the effects of code scheduling. The general drift will definitely be forward, but initial experience indicates that the effect will be very close to stepping by instruction.
  • Variables that are "split" (so that they are allocated in more than one location during different parts of their lifetimes) are not described at all.
    Although not handled quite like normal split variables, formal parameters that are passed in registers share many of the same problems as split variables. Even with the /NOOPTIMIZE qualifier, such a parameter often will be copied immediately to a "permanent home" (either on the stack or in some other register) during the routine prolog. The debugger symbol table description of such parameters encodes this permanent home location and not the physical register in which the parameter is passed. The end-of-prolog location is recorded in the debugger symbol tables and will be used as the preferred breakpoint location when a breakpoint is set in the context of an appropriately set module (so that symbol table information is available to the debugger).

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.

Debugger Command-Line Options

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.

8.1.3 Starting and Ending a Debugging Session

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.

8.1.4 Features Basic to Debugging C++ Programs

This section describes features essential for debugging C++ programs.

8.1.4.1 Determining Language Mode

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> 

8.1.4.2 Built-In Operators

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.

8.1.4.3 Constructs in Language and Address Expressions

The supported constructs in language and address expressions for C++ are as follows:

Symbol Construct
[ ] Subscripting
. (period) Structure component selection
-> Pointer dereferencing

8.1.4.4 Data Types

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

8.2 Using the OpenVMS Debugger with C++ Data

This section describes how to use the OpenVMS Debugger with C++ data.

8.2.1 Nonstatic Data Members

This section describes how to refer to data members that are not declared static.

8.2.1.1 Noninherited Data Members

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 

8.2.1.2 Inherited Data Members

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.

8.2.2 Reference Objects and Reference Members

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 

8.2.3 Pointers to Members

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).

8.2.4 Referencing Entities by Type

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.

8.3 Using the OpenVMS Debugger with C++ Functions

This section describes how to reference the various kinds of functions and function arguments.

8.3.1 Referring to Overloaded Functions

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) 

8.3.2 Referring to Destructors

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' 

8.3.3 Referring to Conversions

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)& 

8.3.4 Referring to User-Defined Operators

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++'() 

8.3.5 Referring to Function Arguments

In OpenVMS Debugger referencing, you use this , *this , and this->m as follows:

  • All nonstatic member functions have a pointer parameter available named this . For example:


    DBG> examine this 
    
  • Use *this to examine the prefix object that a member function is invoked against. For example:


    DBG> examine *this 
    
  • Use the this parameter to refer to a data member m of the prefix argument to a member function. For example:


    DBG> examine this->m 
    

8.3.6 Calling C++ Member Functions from the Debugger

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