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.
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.H
/*----- FILE CALC.H --------------------------------------------------*/
/* */
/* Header file for CALC.C PUSHPOP.C READTOKN.C */
/* a simple calculator */
/*--------------------------------------------------------------------*/
typedef enum toks {
T_INTEGER,
T_PLUS,
T_TIMES,
T_MINUS,
T_DIVIDE,
T_EQUALS,
T_STOP
} Token;
Token read_token(char buf[]);
typedef struct int_link {
struct int_link * next;
int i;
} IntLink;
typedef struct int_stack {
IntLink * top;
} IntStack;
extern void push(IntStack *, int);
extern int pop(IntStack *);
CALC.C
/*----- FILE CALC.C --------------------------------------------------*/
/* */
/* A simple calculator that does operations on integers that */
/* are pushed and popped on a stack */
/*--------------------------------------------------------------------*/
#include <stdio.h>
#include <stdlib.h>
#include "calc.h"
IntStack stack = { 0 };
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);
push(&stack,num); /* CALC1 statement */
break;
case T_PLUS:
push(&stack, pop(&stack)+pop(&stack) );
break;
case T_MINUS:
num = pop(&stack);
push(&stack, num-pop(&stack));
break;
case T_TIMES:
push(&stack, pop(&stack)*pop(&stack));
break;
case T_DIVIDE:
num2 = pop(&stack);
num = pop(&stack);
push(&stack, num/num2); /* CALC2 statement */
break;
case T_EQUALS:
num = pop(&stack);
sprintf(buf_out,"= %d ",num);
push(&stack,num);
break;
}
if (tok==T_STOP)
break;
}
return 0;
}
PUSHPOP.C
/*----- FILE PUSHPOP.C -----------------------------------------------*/
/* */
/* A push and pop function for a stack of integers */
/*--------------------------------------------------------------------*/
#include <stdlib.h>
#include "calc.h"
/*--------------------------------------------------------------------*/
/* input: stk - stack of integers */
/* num - value to push on the stack */
/* action: get a link to hold the pushed value, push link on stack */
/* */
extern void push(IntStack * stk, int num)
{
IntLink * ptr;
ptr = (IntLink *) malloc( sizeof(IntLink)); /* PUSHPOP1 */
ptr->i = num; /* PUSHPOP2 statement */
ptr->next = stk->top;
stk->top = ptr;
}
/*--------------------------------------------------------------------*/
/* return: int value popped from stack */
/* action: pops top element from stack and gets return value from it */
/*--------------------------------------------------------------------*/
extern int pop(IntStack * stk)
{
IntLink * ptr;
int num;
ptr = stk->top;
num = ptr->i;
stk->top = ptr->next;
free(ptr);
return num;
}
READTOKN.C
/*----- FILE READTOKN.C ----------------------------------------------*/
/* */
/* A function to read input and tokenize it for a simple calculator */
/*--------------------------------------------------------------------*/
#include <ctype.h>
#include <stdio.h>
#include "calc.h"
/*--------------------------------------------------------------------*/
/* 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 */
/*--------------------------------------------------------------------*/
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.
This topic describes how to halt just before or just after a routine is called by using the AT CALL and AT ENTRY commands. The Example: sample C program for debugging is used to describe these commands.
To use the AT CALL command, you must compile the calling program with the TEST compiler option.
To halt just before read_token is called, enter the following command:
AT CALL read_token ;
To use the AT ENTRY command, you must compile the called program with the TEST compiler option.
To halt just after read_token is called, enter the following command:
AT ENTRY read_token ;
To halt just after push is called and only when num equals 16, enter the following command:
AT ENTRY push WHEN num=16;
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 above to the statement labeled CALC1 , move the cursor over num and press PF4 (LIST). The following appears in the Log window:
LIST ( 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.
Now step into the call to push() by pressing PF2 (STEP) and step until the statement labeled PUSHPOP2 is reached. To view the attributes of variable ptr, issue the Debug Tool command:
DESCRIBE ATTRIBUTES *ptr;
The result in the Log window is similar to the following:
ATTRIBUTES for * ptr
Its address is 0BB6E010 and its length is 8
struct int_link
struct int_link *next;
int i;You can use this action to browse structures and unions.
You can list all the values of the members of the structure pointed to by ptr with the command:
LIST *ptr ;
with results in the Log window appearing similar to the following:
LIST * ptr ; (* ptr).next = 0x00000000 (* ptr).i = 0
You can change the value of a structure member by issuing the assignment as a command as in the following example:
(* ptr).i = 33 ;
Often a particular part of your program works fine for the first few thousand times, but fails afterward because a specific condition is present. Setting a simple line breakpoint is an inefficient way to debug the program because you need to execute the GO command a thousand times to reach the specific condition. You can instruct Debug Tool to continue executing a program until a specific condition is present.
Example: sample C program for debugging
For example, in the main procedure of the program above, you want to stop at 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 num2 is not 0, the program continues. You can enter Debug Tool commands to change the value of num2 to a nonzero value.
Example: sample C program for debugging
Suppose you want to set a breakpoint at entry to the function push() in the file PUSHPOP.C. PUSHPOP.C 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.C(PUSHPOP)" is fetched later on by the application, this compile unit might not be known to Debug Tool. If it is displayed, enter:
SET QUALIFY CU "USERID.MFISTART.C(PUSHPOP)" AT ENTRY push; GO ;
or
AT ENTRY "USERID.MFISTART.C(PUSHPOP)":>push GO;
If it is not displayed, set an appearance breakpoint as follows:
AT APPEARANCE "USERID.MFISTART.C(PUSHPOP)" ; GO ;
The only purpose for this appearance breakpoint is to gain control the first time a function in the PUSHPOP compile unit is run. When that happens, you can set breakpoints at entry to push():
AT ENTRY push;
You can also combine the breakpoints as follows:
AT APPEARANCE "USERID.MFISTART.C(PUSHPOP)" AT ENTRY push; GO;
To redirect stdout to the Log window, issue the following command:
SET INTERCEPT ON FILE stdout ;
With this SET command, you will capture not only stdout from your program, but also from interactive function calls. For example, you can interactively call printf on the command line to display a null-terminated string by entering:
printf(sptr);
You might find this easier than using LIST STORAGE.
To redirect stdin input so that you can enter it from the command prompt, do the following steps
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. Refer to the following topics for more information related to the material discussed in this topic.
You can start a library function (such as strlen) or one of the program functions interactively by calling it on the command line. The functions must comply with the following requirements:
Example: sample C program for debugging
Below, we call push() interactively to push one more value on the stack just before a value is popped off.
AT CALL pop ; GO ; push(77); GO ;
The calculator produces different results than before because of the additional value pushed on the stack.
A char * variable ptr can point to a piece of storage containing 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 in:
puts(ptr) ;
Refer to the following topics for more information related to the material discussed in this topic.
Example: sample C program for debugging
Build PUSHPOP.C as a DLL, exporting push() and pop(). Build CALC.C and READTOKN.C as the program that imports push() and pop() from the DLL named PUSHPOP. When the application CALC starts the DLL, PUSHPOP will not be known to Debug Tool. Use the AT APPEARANCE breakpoint to gain control in the DLL the first time code in that compile unit appears, as shown in the following example:
AT APPEARANCE "USERID.MFISTART.C(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.
Often when you get close to a programming error, you want to know how you got into that situation, and especially what the traceback of calling functions is. To get this information, issue the command:
LIST CALLS ;
Example: sample C program for debugging
For example, if you run the CALC example with the commands:
AT ENTRY read_token ; GO ; LIST CALLS ;
the Log window will contain something like:
At ENTRY in C function CALC ::> "USERID.MFISTART.C(READTOKN)" :> read_token. From LINE 18 in C function CALC ::> "USERID.MFISTART.C(CALC)" :> main :> %BLOCK2.
which shows the traceback of callers.
To trace a program showing the entry and exit points without requiring any changes to the program, place the following Debug Tool commands in a file and USE them when Debug Tool initially displays your program. Assuming you have a data set USERID.DTUSE(TRACE) that 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 will be 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, with the USE file as a source of input for Debug Tool commands:
>main >foo <foo <main
If you do not want to create the USE file, you can enter the commands through the command line, and the same effect is achieved.
During program run time, some storage might unexpectedly change its value and you want to find out when and where this happens. Consider this 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.
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 malloc() is filled with the byte 0xFD. If you see this byte repeated through 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 calling free() might be filled with the byte 0xFB. If you see this byte repeated through 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 through storage, it is likely 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.
Example: sample C program for debugging
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
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 allows you to continue your debug session without raising a condition.