DIGITAL C++
Using DIGITAL C++ for DIGITAL UNIX Systems


Previous Contents Index


Chapter 9
Using the Ladebug Debugger

A debugger helps you find run-time errors by letting you observe and interactively manipulate execution step by step, until you discover where the program functions incorrectly. The language of the source program you are currently debugging determines the format you use to enter and display data. The current language also determines the format used for features, such as comment characters, operators, and operator precedence, which have language-specific settings. If you have modules written in another language, you can switch from one language to another during your debugging session.

This chapter discusses debugging programs using the command line interface to the Ladebug Debugger. A graphical user interface is also available through DEC FUSE, which supports all of the Ladebug commands mentioned in this chapter. DEC FUSE is the DIGITAL integrated software development environment for DIGITAL UNIX systems. Besides the Ladebug commands described in this chapter, DEC FUSE provides the programmer with a class browser, call graph browser, and cross-referencing capability to assist in debugging DIGITAL C++ programs. For more information, see the DEC FUSE Debugger Manual.

9.1 Debugging C++ Programs

The Ladebug debugger is a source-level debugger that debugs C++ programs compiled with the DIGITAL C++ compiler. For general information on how to use the Ladebug debugger, see the DIGITAL UNIX Ladebug Debugger Manual and the Ladebug(1) reference page.

When debugging C++ code, the debugger supports the following features:

The debugger interprets C++ names and expressions using the language rules described in The Annotated C++ Reference Manual. C++ is a distinct language, rather than a superset of C. Where the semantics of C and C++ differ, the debugger provides the interpretation appropriate for the language of the program being debugged.

To make the debugger more useful, the debugger relaxes some standard C++ name visibility rules. For example, you can reference both public and private class members.

9.2 Debugging Programs Containing C and C++ Code

The debugger lets you debug mixed-language programs. Program flow of control across functions written in different languages is transparent.

The debugger automatically identifies the language of the current function or code segment based on information embedded in the executable file. If program execution is suspended in a C function, the current language is C. If the program executes a C++ function, the current language becomes C++.

The current language determines the valid expression syntax for the debugger. When the current language is C, printing an expression such as S::foo causes an error, because scope resolution operators and class types are not valid in C expressions. The current language of the debugger also affects the semantics used to evaluate an expression. For example, in C, all character constants are of type int. In C++, single character constants are of type char. In C, sizeof('a')= sizeof(int). In C++, sizeof('a')= 1.

The debugger sets the variable $lang to the language of the current function or code segment. By manually setting the debugger variable $lang, you can force the debugger to interpret expressions used in commands by the rules and semantics of a particular language. Example 9-1 shows how to switch between C++ and C modes while debugging.

Example 9-1 Switching Between C++ and C Debugging Modes

(Ladebug) print $lang
"C++" 
(Ladebug) print sizeof('a')
1 
(Ladebug) set $lang = "C" 
(Ladebug) print sizeof('a')
4 
(Ladebug) print sizeof(int)
4 
(Ladebug) 

When the debugger reaches the end of your program, the $lang variable is set to the language of the final function of your program, rather than the language of the _exit routine. The _exit routine is written in machine code and is the last function executed by every C or C++ program.

9.3 Setting the Class Scope

The debugger maintains the concept of a current context in which to perform lookup of program variable names. The current context includes a file scope and either a function scope or a class scope. The debugger automatically updates the current context when program execution suspends.

The class command lets you set the scope to a class in the program you are debugging. The syntax for the class command is as follows:
class class_name

Explicitly setting the debugger's current context to a class allows visibility into a class to set a breakpoint in a member function, to print static data members, or to examine any data member's type. After the class scope is set, you can set breakpoints in the class's member functions and examine data without explicitly mentioning the class name. If you do not want to affect the current context, you can use the scope resolution operator (::) to access a class whose members are not currently visible.

There can be only one current context. If you set a class scope, you invalidate the current function scope. The opposite is also true: if you set a function scope, you invalidate the current class scope.

To display the current class scope (if one exists), enter the class command with no argument.

Example 9-2 shows the use of the class command to set the class scope to S to make member function foo visible so a breakpoint can be set in foo.

Example 9-2 Setting the Class Scope

(Ladebug) stop in main; run
[#1: stop in main ] 
[1] stopped at [int main(void):26 0x120000744] 
     26      int result = s.bar(); 
(Ladebug) stop in foo
Symbol foo not visible in current scope. 
foo has no valid breakpoint address 
Warning: Breakpoint not set 
(Ladebug)  class S
class S  { 
  int i; 
  int j; 
  S (void); 
  ~S (void); 
  int foo (void); 
  virtual int bar (void); 
} 
(Ladebug) stop in foo
[#2: stop in foo (void) ] 
(Ladebug) 

9.4 Displaying Class Information

The whatis and print commands display information about a class. Use the whatis command to display static information about the classes. Use the print command to view dynamic information about class objects.

The whatis command displays the class type declaration, including the data members, member functions, constructors, destructors, static data members, and static member functions. For classes that are derived from other classes, the data members and member functions inherited from the base class are not displayed. However, any member functions that are redefined from the base class are displayed.

The whatis command used on a class name displays all class information, including constructors. To use this command on a constructor only, use the following syntax:
whatis class_name::class_name

Constructors and destructors of nested classes must be accessed using one of the following syntaxes:
class class_name::(type signature)
class class_name~::(type signature)

The print command lets you display the value of data members and static members.

Information regarding the public, private, or protected status of class members is not provided because the debugger relaxes these rules to be more helpful to you.

The type signatures of member functions, constructors, and destructors are displayed in a form that is appropriate for later use in resolving references to overloaded functions.

Example 9-3 shows the whatis and print commands in conjunction with a class.

Example 9-3 Displaying Class Information

(Ladebug) list 1, 9
      1 class S { 
      2 public: 
      3      int i; 
      4      int j; 
      5      S() { i = 1; j = 2; } 
      6      ~S() { } 
      7      int foo (); 
      8      virtual int bar(); 
      9 };     
(Ladebug) whatis S
class S  { 
  int i; 
  int j; 
  S (void); 
  ~S (void); 
  int foo (void); 
  virtual int bar (void); 
} S 
(Ladebug) whatis S :: bar
int bar (void) 
(Ladebug) stop in S :: foo
[#2: stop in S :: foo ] 
(Ladebug) run
[2] stopped at [int S::foo(void):13 0x120000648]       
     13      return i; 
(Ladebug) print S :: i
1 
(Ladebug) 

9.5 Displaying Object Information

The print and whatis commands also display information on instances of classes (objects). Use the whatis command to display the class type of an object. Use the print command to display the current value of an object. You can print an object's contents all at once using the following syntax:
print object

You can also display individual object members using the member access operators, period (.) and right arrow (->) in a print command. Static data members are treated as members of objects, so you can reference them by object name. If the debugger stops in a member function of an object, you can access other members of that same object using the this pointer implicitly or explicitly.

You can use the scope resolution operator (::) to reference global variables, to reference hidden members in base classes, to explicitly reference a member that is inherited, or to otherwise name a member hidden by the current context.

When you are in the context of a nested class, you must use the scope resolution operator to access members of the enclosing class.

Example 9-4 shows how to use the print and whatis commands to display object information.

Example 9-4 Displaying Object Information

(Ladebug) whatis s
class S  { 
  int i; 
  int j; 
  S (void); 
  ~S (void); 
  int foo (void); 
  virtual int bar (void); 
} s 
(Ladebug) stop in S::foo; run
[#1: stop in s.foo ] 
[1] stopped at [int S::foo(void):13 0x120000638]       
     35      return i; 
(Ladebug) print *this
class { 
        i = 1; 
        j = 2; 
    } 
(Ladebug) print i, j
1 2 
(Ladebug) print this->i, this->j
1 2 
(Ladebug) 

9.6 Displaying Virtual and Inherited Class Information

When you use the print command to display information on an instance of a derived class, the debugger displays both the new class members as well as the members inherited from a base class. Base class member information is nested within the inherited class information.

Pointers to members of a class are not supported. Example 9-5 shows the format the debugger uses to print information on derived classes.

Example 9-5 Printing Information on a Derived Class

(Ladebug) list 1, 23
      1 class S { 
      2 public: 
      3      int i; 
      4      S() {i=1;}; 
      5      ~S() {i=0;}; 
      6      int foo(); 
      7 }; 
      8 
      9 S::foo() 
     10 { 
     11    i = i + 1; 
     12    return i; 
     13 } 
     14 
     15 class T : public S { 
     16 public: 
     17      int j; 
     18      T() {j=2;}; 
     19      ~T() {i=0;}; 
     20 }; 
     21 
     22 S a; 
     23 T b; 
(Ladebug) stop in main ; run
[#1: stop in main(void) ] 
[1] stopped at [main(void):27 0x1200007f4] 
     27    a.i = 1; 
(Ladebug) print b
class { 
        S = class { 
            i = 1; 
        }; 
        j = 2; 
    } 
(Ladebug) whatis b
class T : S { 
  int j; 
  T (void); 
  ~T (void); 
} b 
(Ladebug) whatis S
class S  { 
  int i; 
  S (void); 
  ~S (void); 
  int foo (void); 
} S 
(Ladebug) 

The whatis b command in this example displays the class type of the object b. Descriptions of derived classes obtained with the whatis command do not include members inherited from base classes. Class T inherits the public members of class S; in this case, integer variable i and member function foo.

If you have two members in an object with the same name but different base class types (multiple inheritance), you can refer to the members using the following syntax:

object.class::member

This syntax is more effective than using the object.member and object->member syntaxes, which can be ambiguous. In all cases, the Ladebug debugger uses the C++ language rules as defined in The Annotated C++ Reference Manual to determine which member you are specifying.

Example 9-6 shows a case where the expanded syntax is necessary. In this example, two base classes, B and C inherit the public members of base class V. Derived class D inherits the public members of both class B and class C.

Example 9-6 Resolving References to Objects of Multiple Inherited Classes

(Ladebug) whatis D
class D : B, C { 
  D (void); 
  ~D (void); 
  void g (void); 
} D 
(Ladebug) whatis C
class C : virtual V { 
  int ambig; 
  C (void); 
  ~C (void); 
} C 
(Ladebug) whatis B
class B : virtual V { 
  int x; 
  int ambig; 
  B (void); 
  ~B (void); 
  int f (void); 
} B 
(Ladebug) whatis V
class V  { 
  int v; 
  int x; 
  V (void); 
  ~V (void); 
  int f (void); 
} V 
(Ladebug) stop in main; run
[#1: stop in main(void) ] 
[1] stopped at [main(void):59 0x120001024] 
     59   D dinst; 
(Ladebug) next
stopped at [main(void):60 0x120001030] 
     60   V vinst; 
(Ladebug) [Return]
stopped at [main(void):62 0x120001038] 
     62   printf("%d\en", dinst; 
(Ladebug) print dinst.ambig
Ambiguous reference 
Selecting 'ambig' failed! 
Error: no value for dinst.ambig 
(Ladebug) print dinst.B::ambig
2 
(Ladebug) 

Trying to examine an inlined member function that is not called results in the following error:


Member function has been inlined. 

Ladebug will report this error regardless of the settings of the -noinline_auto compilation switches. As a workaround, include a call to the given member function somewhere in your program. (The call does not need to be executed.)

If a program is not compiled with the -g option, a breakpoint set on an inline member function may confuse the debugger.

9.7 Modifying Class and Object Data Members

When debugging C++ code, the debugger lets you modify a class's static data members and object data members as you would modify any other program variable by using the debugger's assign command. See the DIGITAL UNIX Ladebug Debugger Manual for details on how to use the assign command to modify variable values. The following assignments are allowed by the debugger even though they are prohibited in the C++ language:

9.8 Member Functions on the Stack Trace

The implicit this pointer, which is a part of all nonstatic member functions, is displayed as the address on the stack trace. The class type of the object is also given.

Sometimes the debugger does not see class type names with internal linkage. When this happens, the debugger gives the following error message:


Name is overloaded. 

The stack trace in Figure 9-1 displays a member function foo of an object declared with class type S.

Figure 9-1 A Stack Trace Displaying a Member Function


9.9 Resolving Ambiguous References to Overloaded Functions

In most cases, the debugger works with one specific function at a time. In the case of overloaded function names, you must specify the desired overloaded function. There are two ways to resolve references to overloaded function names. Both ways are under the control of the debugger variable $overloadmenu. The default setting of this debugger variable is 0.

One way to specify the desired function name is to choose the correct reference from a selection menu. To enable menu selection of overloaded names, set the $overloadmenu variable to 1. If you use this method, whenever you specify a function name that is overloaded, a menu will appear with all the possible functions; you must select from this menu. In Example 9-7, a breakpoint is set in foo, which is overloaded.

Example 9-7 Resolving Overloaded Functions by Selection Menu

(Ladebug) set $overloadmenu = 1
(Ladebug) class S
class S  { 
  int i; 
  float f; 
  double d; 
  char c; 
  S (void); 
  ~S (void); 
  int foo (void); 
  int foo ( S); 
  int foo ( S *); 
  int foo (int&); 
  int foo (const int&); 
  int foo (float*&); 
  int foo (int, float, char *, unsigned short, long&, const char*&); 
  int foo (const double *); 
  int foo (char); 
  void foo (short); 
  void foo (unsigned); 
  void foo (long); 
  void foo (int, float, char *, unsigned short, long&, char*&); 
  void foo (double); 
  void foo (char *); 
  void foo (const char *); 
} 
(Ladebug) stop in foo
Enter the number of the overloaded function you want 
---------------------------------------------------- 
     1 int foo (void) 
     2 void foo (const char *) 
     3 void foo (char *) 
     4 void foo (double) 
     5 void foo (int, float, char *, unsigned short, long&, char*&) 
     6 void foo (long) 
     7 void foo (unsigned) 
     8 void foo (short) 
     9 int foo (char) 
    10 int foo (const double *) 
    11 int foo (int, float, char *, unsigned short, long&, const char*&) 
    12 int foo (float*&) 
    13 int foo (const int&) 
    14 int foo (int&) 
    15 int foo ( S *) 
    16 int foo ( S) 
    17 None of the above 
---------------------------------------------------- 
10
[#1: stop in int S::foo(const double*) ] 
(Ladebug) 

The other way to resolve ambiguous overloaded function names is to enter the function name with its full type signature. If you prefer this method, set the $overloadmenu variable to 0. To see the possible type signatures for the overloaded function, first display all the declarations of an overloaded function by using one of the following syntax lines:
whatis function
whatis class_name::function

You cannot select a version of an overloaded function that has a type signature containing ellipsis points. Pointers to functions with type signatures that contain parameter list or ellipsis arguments are not supported.

Use one of the displayed function type signatures to refer to the desired version of the overloaded function. If a function has no parameter, include the parameter void as the function's type signature. In Example 9-8 the function context is set to foo(), which is overloaded.

Example 9-8 Resolving Overloaded Functions by Type Signature

(Ladebug) print $overloadmenu
0 
(Ladebug) class S
class S  { 
  int i; 
  float f; 
  double d; 
  char c; 
  S (void); 
  ~S (void); 
  int foo (void); 
  int foo ( S); 
  int foo ( S *); 
  int foo (int&); 
  int foo (const int&); 
  int foo (float*&); 
  int foo (int, float, char *, unsigned short, long&, const char*&); 
  int foo (const double *); 
  int foo (char); 
  void foo (short); 
  void foo (unsigned); 
  void foo (long); 
  void foo (int, float, char *, unsigned short, long&, char*&); 
  void foo (double); 
  void foo (char *); 
  void foo (const char *); 
} 
(Ladebug) whatis foo
Overloaded Function. Functions are: 
int S::foo(void) 
int S::foo(S) 
int S::foo(S*) 
int S::foo(int&) 
int S::foo(const int&) 
int S::foo(float*&) 
int S::foo(int, float, char*, unsigned short, long&, const char*&) 
int S::foo(const double*) 
int S::foo(char) 
void S::foo(short) 
void S::foo(unsigned) 
void S::foo(long) 
void S::foo(int, float, char*, unsigned short, long&, char*&) 
void S::foo(double) 
void S::foo(char*) 
void S::foo(const char*) 
(Ladebug) func foo
Error: foo is overloaded 
(Ladebug) func foo(double)
S::foo(double) in c++over.C line No. 156: 
    156     printf ("void S::foo (double d = %f)\n", d); 
(Ladebug) 


Previous Next Contents Index