Rational Developer for System z, Version 7.6

Debugging a C++ program in full-screen mode

The descriptions of basic debugging tasks for C++ refer to the following C++ program.

Example: sample C++ program for debugging

Refer to the following topics for more information related to the material discussed in this topic.

Example: sample C++ program for debugging

The program below is used in various topics to demonstrate debugging tasks.

This program is a simple calculator that reads its input from a character buffer. If integers are read, they are pushed on a stack. If one of the operators (+ - * /) is read, the top two elements are popped off the stack, the operation is performed on them, and the result is pushed on the stack. The = operator writes out the value of the top element of the stack to a buffer.

CALC.HPP

/*----- FILE CALC.HPP ------------------------------------------------*/
/*                                                                    */
/* Header file for CALC.CPP PUSHPOP.CPP READTOKN.CPP                  */
/* a simple calculator                                                */
/*--------------------------------------------------------------------*/
typedef enum toks {
  T_INTEGER,
  T_PLUS,
  T_TIMES,
  T_MINUS,
  T_DIVIDE,
  T_EQUALS,
  T_STOP
} Token;
extern "C" Token read_token(char buf[]);
class IntLink {
  private:
    int i;
    IntLink * next;
  public:
    IntLink();
    ~IntLink();
    int get_i();
    void set_i(int j);
    IntLink * get_next();
    void set_next(IntLink * d);
};
class IntStack {
  private:
    IntLink * top;
  public:
    IntStack();
    ~IntStack();
    void push(int);
    int pop();
};

CALC.CPP

/*----- FILE CALC.CPP ------------------------------------------------*/
/*                                                                    */
/* A simple calculator that does operations on integers that          */
/* are pushed and popped on a stack                                   */
/*--------------------------------------------------------------------*/
#include <stdio.h>
#include <stdlib.h>
#include "calc.hpp"
IntStack stack;
int main()
{
  Token tok;
  char word[100];
  char buf_out[100];
  int num, num2;
  for(;;)
  {
    tok=read_token(word);
    switch(tok)
    {
      case T_STOP:
        break;
      case T_INTEGER:
        num = atoi(word);
        stack.push(num);      /*  CALC1  statement */
        break;
      case T_PLUS:
        stack.push(stack.pop()+stack.pop());
        break;
      case T_MINUS:
        num = stack.pop();
        stack.push(num-stack.pop());
        break;
      case T_TIMES:
        stack.push(stack.pop()*stack.pop() );
        break;
      case T_DIVIDE:
        num2 = stack.pop();
        num = stack.pop();
        stack.push(num/num2);      /*  CALC2  statement */
        break;
      case T_EQUALS:
        num = stack.pop();
        sprintf(buf_out,"= %d ",num);
        stack.push(num);
        break;
    }
    if (tok==T_STOP)
      break;
  }
  return 0;
}

PUSHPOP.CPP

/*----- FILE: PUSHPOP.CPP --------------------------------------------*/
/*                                                                    */
/* Push and pop functions for a stack of integers                     */
/*--------------------------------------------------------------------*/
#include <stdio.h>
#include <stdlib.h>
#include "calc.hpp"
/*--------------------------------------------------------------------*/
/* input:  num - value to push on the stack                           */
/* action: get a link to hold the pushed value, push link on stack    */
/*--------------------------------------------------------------------*/
void IntStack::push(int num) {
  IntLink * ptr;
  ptr = new IntLink;
  ptr->set_i(num);
  ptr->set_next(top);
  top = ptr;
}
/*--------------------------------------------------------------------*/
/* return: int value popped from stack (0 if stack is empty)          */
/* action: pops top element from stack and get return value from it   */
/*--------------------------------------------------------------------*/
int IntStack::pop() {
  IntLink * ptr;
  int num;
  ptr = top;
  num = ptr->get_i();
  top = ptr->get_next();
  delete ptr;
  return num;
}
IntStack::IntStack() {
  top = 0;
}
IntStack::~IntStack() {
  while(top)
    pop();
}
IntLink::IntLink() {  /* constructor leaves elements unassigned */
}
IntLink::~IntLink() {
}
void IntLink::set_i(int j) {
  i = j;
}
int IntLink::get_i() {
  return i;
}
void IntLink::set_next(IntLink * p) {
  next = p;
}
IntLink * IntLink::get_next() {
  return next;
}

READTOKN.CPP

/*----- FILE READTOKN.CPP --------------------------------------------*/
/*                                                                    */
/* A function to read input and tokenize it for a simple calculator   */
/*--------------------------------------------------------------------*/
#include <ctype.h>
#include <stdio.h>
#include "calc.hpp"
/*--------------------------------------------------------------------*/
/* action: get next input char, update index for next call            */
/* return: next input char                                            */
/*--------------------------------------------------------------------*/
static char nextchar(void)
{
  /*    input  action
   *    -----  ------
   *    2      push 2 on stack
   *    18     push 18
   *    +      pop 2, pop 18, add, push result (20)
   *    =      output value on the top of the stack (20)
   *    5      push 5
   *    /      pop 5, pop 20, divide, push result (4)
   *    =      output value on the top of the stack (4)
   */
  char * buf_in  = "2 18 + = 5 / = ";
  static int index;     /* starts at 0 */
  char ret;
  ret = buf_in[index];
  ++index;
  return ret;
}
/*--------------------------------------------------------------------*/
/* output: buf - null terminated token                                */
/* return: token type                                                 */
/* action: reads chars through nextchar() and tokenizes them          */
/*--------------------------------------------------------------------*/
extern "C"
Token read_token(char buf[])
{
  int i;
  char c;
  /* skip leading white space */
  for( c=nextchar();
       isspace(c);
       c=nextchar())
    ;
  buf[0] = c;  /* get ready to return single char e.g. "+" */
  buf[1] = 0;
  switch(c)
  {
    case '+' : return T_PLUS;
    case '-' : return T_MINUS;
    case '*' : return T_TIMES;
    case '/' : return T_DIVIDE;
    case '=' : return T_EQUALS;
    default:
      i = 0;
      while (isdigit(c)) {
        buf[i++] = c;
        c = nextchar();
      }
      buf[i] = 0;
      if (i==0)
        return T_STOP;
      else
        return T_INTEGER;
  }
}

Refer to the following topics for more information related to the material discussed in this topic.

Halting when certain functions are called in C++

This topic describes how to halt just before or just after a routine is called by using the AT CALL or AT ENTRY commands. The Example: sample C++ program for debugging is used to describe these commands. Before you use either of these commands, you must do the following tasks:

When you use either of these commands, include the C++ signature along with the function name.

To facilitate entering the breakpoint, you can display PUSHPOP.CPP in the Source window by typing over the name of the file on the top line of the Source window. This makes PUSHPOP.CPP your currently qualified program. You can then enter the following command:

LIST NAMES

Debug Tool displays the names of all the blocks and variables for the currently qualified program. Debug Tool displays information similar to the following example in the Log window:

There are no session names.
The following names are known in block CALC ::> "USERID.MFISTART.CPP(PUSHPOP)"
IntStack::~IntStack()
IntStack::IntStack()
IntLink::get_i()
IntLink::get_next()
IntLink::~IntLink()
IntLink::set_i(int)
IntLink::set_next(IntLink*)
IntLink::IntLink()

Now you can save some keystrokes by inserting the command next to the block name.

To halt just before IntStack::push(int) is called, insert AT CALL next to the function signature and, by pressing Enter, the entire command is placed on the command line. Now, with AT CALL IntStack::push(int) on the command line, you can enter the following command:

AT CALL IntStack::push(int)

To halt just after IntStack::push(int) is called, enter the following command, which is the same way as the AT CALL command:

AT ENTRY IntStack::push(int) ;

To halt just after IntStack::push(int) is called and only when num equals 16, enter the following command:

AT ENTRY IntStack::push(int) WHEN num=16;

Modifying the value of a C++ variable

To list the contents of a single variable, move the cursor to the variable name and press PF4 (LIST). The value is displayed in the Log window. This is equivalent to entering LIST TITLED variable on the command line.

Example: sample C++ program for debugging

Run the CALC program and step into the first call of function IntStack::push(int) until just after the IntLink has been allocated. Enter the Debug Tool command:

LIST TITLED num

Debug Tool displays the following in the Log window:

LIST TITLED num;
num = 2

To modify the value of num to 22, type over the num = 2 line with num = 22, press Enter to put it on the command line, and press Enter again to issue the command.

You can enter most C++ expressions on the command line.

To view the attributes of variable ptr in IntStack::push(int), issue the Debug Tool command:

DESCRIBE ATTRIBUTES *ptr;

The result in the Log window is:

ATTRIBUTES for * ptr
Its address is 0BA25EB8 and its length is 8
  class IntLink
    signed int i
    struct IntLink *next

So for most classes, structures, and unions, this can act as a browser.

You can list all the values of the data members of the class object pointed to by ptr with the command:

LIST *ptr ;

with results in the Log window similar to:

LIST * ptr ; * ptr.i = 0 * ptr.next = 0x00000000

You can change the value of data member of a class object by issuing the assignment as a command, as in this example:

(* ptr).i = 33 ;

Refer to the following topics for more information related to the material discussed in this topic.

Halting on a line in C++ only if a condition is true

Often a particular part of your program works fine for the first few thousand times, but fails under certain conditions. You don't want to set a simple line breakpoint because you will have to keep entering GO.

Example: sample C++ program for debugging

For example, in main you want to stop in T_DIVIDE only if the divisor is 0 (before the exception occurs). Set the breakpoint like this:

AT 40 { if(num2 != 0) GO; }

Line 40 is the statement labeled  CALC2 . The command causes Debug Tool to stop at line 40. If the value of num is not 0, the program will continue. Debug Tool stops on line 40 only if num2 is 0.

Viewing and modifying data members of the this pointer in C++

If you step into a class method, for example, one for class IntLink, the command:

LIST TITLED ;

responds with a list that includes this. With the command:

DESCRIBE ATTRIBUTES *this ;

you will see the types of the data elements pointed to by the this pointer. With the command:

LIST *this ;

you will list the data member of the object pointed to and see something like:

 LIST * this ;
(* this).i = 4
(* this).next =  0x0

in the Log window. To modify element i, enter either the command:

i = 2001;

or, if you have ambiguity (for example, you also have an auto variable named i), enter:

(* this).i = 2001 ;

Debugging C++ when only a few parts are compiled with TEST

Example: sample C++ program for debugging

Suppose you want to set a breakpoint at entry to function IntStack::push(int) in the file PUSHPOP.CPP. PUSHPOP.CPP has been compiled with TEST but the other files have not. Debug Tool comes up with an empty Source window. To display the compile units, enter the command:

LIST NAMES CUS

The LIST NAMES CUS command displays a list of all the compile units that are known to Debug Tool.

Depending on the compiler you are using, or if USERID.MFISTART.CPP(PUSHPOP) is fetched later on by the application, this compile unit might or might not be known to Debug Tool, and the PDS member PUSHPOP might or might not be displayed. If it is displayed, enter:

SET QUALIFY CU "USERID.MFISTART.CPP(PUSHPOP)"
AT ENTRY IntStack::push(int) ;
GO ;

or

AT ENTRY "USERID.MFISTART.CPP(PUSHPOP)":>push
GO

If it is not displayed, you need to set an appearance breakpoint as follows:

AT APPEARANCE "USERID.MFISTART.CPP(PUSHPOP)" ;
GO ;

You can also combine the breakpoints as follows:

AT APPEARANCE "USERID.MFISTART.CPP(PUSHPOP)" AT ENTRY push; GO;

The only purpose of this appearance breakpoint is to gain control the first time a function in the PUSHPOP compile unit is run. When that happens you can, for example, set a breakpoint at entry to IntStack::push(int) as follows:

AT ENTRY IntStack::push(int) ;

Capturing C++ output to stdout

To redirect stdout to the Log window, issue the following command:

SET INTERCEPT ON FILE stdout ;

With this SET command, you will not only capture stdout from your program, but also from interactive function calls. For example, you can interactively use cout on the command line to display a null terminated string by entering:

cout << sptr ;

You might find this easier than using LIST STORAGE.

For CICS® only, SET INTERCEPT is not supported.

Capturing C++ input to stdin

To redirect stdin input so that you can enter it from the command prompt, do the following steps

  1. Enter the following command: SET INTERCEPT ON FILE stdin ;
  2. When Debug Tool encounters a C++ statement such as scanf, the following message is displayed in the Log window:
    EQA1290I The program is waiting for input from stdin           
    EQA1292I Use the INPUT command to enter up to a maximum of 1000
             characters for the intercepted variable-format file.  
  3. Enter the INPUT command to enter the input data.

Refer to the following topics for more information related to the material discussed in this topic.

Calling a C++ function from Debug Tool

You can start a library function (such as strlen) or one of the programs functions interactively by calling it on the command line. You can also start C linkage functions such as read_token. However, you cannot call C++ linkage functions interactively. The functions must comply with the following requirements:

Example: sample C++ program for debugging

In the example below, we call read_token interactively.

AT CALL read_token;
GO;
read_token(word);

The calculator produces different results than before because of the additional token removed from input.

Displaying raw storage in C++

A char * variable ptr can point to a piece of storage that contains printable characters. To display the first 20 characters, enter;

LIST STORAGE(*ptr,20)

If the string is null terminated, you can also use an interactive function call on the command line as shown in this example:

puts(ptr) ;

You can also display storage based on offset. For example, to display 10 bytes at an offset of 2 from location 20CD0, use the following command:

LIST STORAGE(0x20CD0,2,10);

Refer to the following topics for more information related to the material discussed in this topic.

Debugging a C++ DLL

Example: sample C++ program for debugging

Build PUSHPOP.CPP as a DLL, exporting IntStack::push(int) and IntStack::pop(). Build CALC.CPP and READTOKN.CPP as the program that imports IntStack::push(int) and IntStack::pop() from the DLL named PUSHPOP. When the application CALC starts, the DLL PUSHPOP is not known to Debug Tool. Use the AT APPEARANCE breakpoint, as shown in the following example, to gain control in the DLL the first time code in that compile unit appears.

AT APPEARANCE "USERID.MFISTART.CPP(PUSHPOP)" ;
GO ;

The only purpose of this appearance breakpoint is to gain control the first time a function in the PUSHPOP compile unit is run. When this happens, you can set breakpoints in PUSHPOP.

Getting a function traceback in C++

Often when you get close to a programming error, you want to know how you got into that situation, especially what the traceback of calling functions is. To get this information, issue the command:

LIST CALLS ;

For example, if you run the CALC example with the following commands:

AT ENTRY read_token ;
GO ;
LIST CALLS ;

the Log window contains something like:

At ENTRY in C function "USERID.MFISTART.CPP(READTOKN)" :> read_token.
From LINE 18 in C function "USERID.MFISTART.CPP(CALC)" :> main :> %BLOCK2.

which shows the traceback of callers.

Tracing the run-time path for C++ code compiled with TEST

To trace a program showing the entry and exit of that program without requiring any changes to it, place the following Debug Tool commands, shown in the example below, in a file and USE them when Debug Tool initially displays your program. Assume you have a data set that contains USERID.DTUSE(TRACE) and contains the following Debug Tool commands:

int indent;
indent = 0;
SET INTERCEPT ON FILE stdout;
AT ENTRY * { \
  ++indent; \
  if (indent < 0) indent = 0; \
  printf("%*.s>%s\n", indent, " ", %block); \
  GO; \
}
AT EXIT * {\
  if (indent < 0) indent = 0; \
  printf("%*.s<%s\n", indent, " ", %block); \
  --indent; \
  GO; \
}

You can use this file as the source of commands to Debug Tool by entering the following command:

USE USERID.DTUSE(TRACE)

The trace of running the program listed below after executing the USE file is displayed in the Log window:

int foo(int i, int j) {
  return i+j;
}
int main(void) {
  return foo(1,2);
}

The following trace in the Log window is displayed after running the sample program, using the USE file as a source of input for Debug Tool commands:

>main
 >foo(int,int)
 <foo(int,int)
<main

If you do not want to create the USE file, you can enter the commands through the command line, and the same effect will be achieved.

Finding unexpected storage overwrite errors in C++

During program run time, some storage might unexpectedly change its value and you would like to find out when and where this happened. Consider this simple example where function set_i changes more than the caller expects it to change.

struct s { int i; int j;};
struct s a = { 0, 0 };

/* function sets only field i */
void set_i(struct s * p, int k)
{
  p->i = k;
  p->j = k;   /* error, it unexpectedly sets field j also */
}
main() {
  set_i(&a,123);
}

Find the address of a with the command:

LIST &(a.j) ;

Suppose the result is 0x7042A04. To set a breakpoint that watches for a change in storage values, starting at that address for the next 4 bytes, issue the command:

AT CHANGE %STORAGE(0x7042A04,4)

When the program is run, Debug Tool will halt if the value in this storage changes.

Finding uninitialized storage errors in C++

To help find your uninitialized storage errors, run your program with the Language Environment® TEST run-time and STORAGE options. In the following example:

TEST STORAGE(FD,FB,F9)

the first subparameter of STORAGE is the fill byte for storage allocated from the heap. For example, storage allocated through operator new is filled with the byte 0xFD. If you see this byte repeated throughout storage, it is likely uninitialized heap storage.

The second subparameter of STORAGE is the fill byte for storage allocated from the heap but then freed. For example, storage freed by the operator delete might be filled with the byte 0xFB. If you see this byte repeated throughout storage, it is likely storage that was allocated on the heap, but has been freed.

The third subparameter of STORAGE is the fill byte for auto storage variables in a new stack frame. If you see this byte repeated throughout storage, you probably have uninitialized auto storage.

The values chosen in the example are odd and large, to maximize early problem detection. For example, if you attempt to branch to an odd address, you will get an exception immediately.

As an example of uninitialized heap storage, run program CALC, with the STORAGE run-time option as STORAGE(FD,FB,F9), to the line labeled PUSHPOP2 and issue the command:

LIST *ptr ;

You will see the byte fill for uninitialized heap storage as the following example shows:

 LIST * ptr ;
(* ptr).next = 0xFDFDFDFD
(* ptr).i = -33686019

Refer to the following topics for more information related to the material discussed in this topic.

Halting before calling a NULL C++ function

Calling an undefined function or calling a function through a function pointer that points to NULL is a severe error. To halt just before such a call is run, set this breakpoint:

AT CALL 0

When Debug Tool stops at this breakpoint, you can bypass the call by entering the GO BYPASS command. This command allows you to continue your debug session without raising a condition.


Terms of use | Feedback

This information center is powered by Eclipse technology. (http://www.eclipse.org)