Using GDB


Let s now dive into GDB and look at its capabilities for debugging C applications. We ll look at some of the most common methods for debugging with GDB using breakpoints. We ll demonstrate using command-line GDB, though GUI versions exist.

One important point to note before we jump in is that the program being debugged uses the same terminal input and output as GDB. This is suitable for our purposes here.

Note  

It is possible to redirect stdin and stdout for the program s I/O. We can redirect the output on the command line when we start the GDB session. We can also specify a new terminal for the program s stdin using the tty shell command.

To demonstrate GDB, we ll use the source shown in Listing 25.1. This source represents a very simple stack implementation that provides math operators.

Listing 25.1: Example Source for the GDB Debugging Session (on the CD-ROM at ./source/ch25/testapp.c )
start example
  1  :       #include <stdio.h>  2  :       #include <assert.h>  3  :  4  :       #define MAX_STACK_ELEMS         10  5  :  6  :       #define OP_ADD                  0  7  :       #define OP_SUBTRACT             1  8  :       #define OP_MULTIPLY             2  9  :       #define OP_DIVIDE               3  10  :  11  :  12  :       typedef struct {  13  :         int stack[MAX_STACK_ELEMS];  14  :         int index;  15  :       } STACK_T;  16  :  17  :  18  :       void initStack(STACK_T *stack)  19  :       {  20  :         assert(stack);  21  :         stack->index =  0;   22  :       }  23  :  24  :  25  :       void push(STACK_T *stack, int elem)  26  :       {  27  :         assert(stack);  28  :         assert(stack->index < MAX_STACK_ELEMS);  29  :  30  :         stack->stack[stack->index++] = elem;  31  :         return;  32  :       }  33  :  34  :  35  :       int pop(STACK_T *stack)  36  :       {  37  :         assert(stack);  38  :         assert(stack->index >   );  39  :  40  :         return(stack->stack[stack->index]);  41  :       }  42  :  43  :  44  :       void operator(STACK_T *stack, int op)  45  :       {  46  :         int a, b;  47  :  48  :         assert(stack);  49  :         assert(stack->index >   );  50  :  51  :         a = pop(stack); b = pop(stack);  52  :  53  :         switch(op) {  54  :  55  :           case OP_ADD:  56  :             push(stack, (a+b)); break;  57  :  58  :           case OP_SUBTRACT:  59  :             push(stack, (a-b)); break;  60  :  61  :           case OP_MULTIPLY:  62  :             push(stack, (a*b)); break;  63  :  64  :           case OP_DIVIDE:  65  :             push(stack, (a/b)); break;  66  :  67  :           default:  68  :             assert(   ); break;  69  :  70  :         }  71  :  72  :       }  73  :  74  :  75  :       int main()  76  :       {  77  :         STACK_T stack;  78  :  79  :         initStack(&stack);  80  :  81  :         push(&stack,  2  );  82  :         push(&stack,  5  );  83  :         push(&stack,  2  );  84  :         push(&stack,  3  );  85  :         push(&stack,  5  );  86  :         push(&stack,  3  );  87  :         push(&stack,  6  );  88  :  89  :         operator(&stack, OP_ADD);  90  :         operator(&stack, OP_SUBTRACT);  91  :         operator(&stack, OP_MULTIPLY);  92  :         operator(&stack, OP_DIVIDE);  93  :         operator(&stack, OP_ADD);  94  :         operator(&stack, OP_SUBTRACT);  95  :  96  :         printf("Result is %d\n", pop(&stack));  97  :         return 0;  98  :       } 
end example
 

We compile our source with the -g flag to include debugging information for GDB, as:

 # gcc -g -Wall -o testapp testapp.c     # 

Starting GDB

To debug a program with GDB, we simply execute GDB with our program name as the first argument. We can also start GDB and then load our program using the load command. Here we start GDB with our application:

 # gdb testapp     GNU gdb Red Hat Linux (5.3post-0.20021129.18rh)     Copyright 2003 Free Software Foundation, Inc.     GDB is free software, covered by the GNU General Public      License, and you are welcome to change it and/or distribute      copies of it under certain conditions.     Type show copying to see the conditions.     There is absolutely no warranty for GDB.  Type show warranty      for details.     This GDB was configured as i386-redhat-linux-gnu...     (gdb) 

The (gdb) is the regular prompt for GDB and indicates that it is available for commands. We could start our application using the run command, but we ll look at a few other commands first.

Looking at Source

Once we start GDB, our application is not yet running, but instead is just loaded into GDB. Using the list command, we can view the source of our application, as demonstrated below:

 (gdb) list  70  }  71   72  }  73   74   75  int main()  76  {  77  STACK_T stack;  78   79  initStack(&stack);           (gdb) 

The main is our entry point, so list here shows this entry. We can also specify the lines of interest with list as:

 (gdb) list 75,85  75  int main()  76  {  77  STACK_T stack;  78   79  initStack(&stack);  80   81  push(&stack, 2);  82  push(&stack, 5);  83  push(&stack, 2);  84  push(&stack, 3);  85  push(&stack, 5);           (gdb) 

Using the list command with no arguments will always list the source with the current line centered in the list.

Using Breakpoints

The primary strategy for debugging with GDB is the use of breakpoints to stop the running program and allow inspection of the internal data. A breakpoint can be set in a variety of ways, but the most common is specifying a function name. Here, we tell GDB to break at our main program:

 (gdb) break main     Breakpoint 1 at 0x804855b: file testapp.c, line 79.     (gdb) run     Starting program: /home/mtj/gnulinux/ch25/testapp          Breakpoint 1, main () at testapp.c:79     79        initStack(&stack);     (gdb) 

Once we give the break command, GDB tells us our breakpoint number (since we may set multiple) and the address, filename, and line number of the breakpoint. We then start our application using the run command, which results in hitting our previously set breakpoint. Once the breakpoint is hit, GDB shows the line that will

be executed next . Note that this statement, line 79, is the first executable statement of our application.

Note  

Recall that in all C applications, the main function is the user entry point to the application, but various other work goes on behind the scenes to start and end the program. Therefore, when we break at the main function, we break at our user entry point, but not the true start of the application.

We can view the available breakpoints using the info command:

 (gdb) info breakpoints     Num Type           Disp Enb Address    What     1   breakpoint     keep y   0x0804855b in main at testapp.c:79             breakpoint already hit 1 time     (gdb) 

We see our single breakpoint and an indication from GDB that this breakpoint has been hit.

If our breakpoint is now of no use, we remove it using the clear command:

 (gdb) clear 79     Deleted breakpoint 1     (gdb) 

Other methods for setting breakpoints are shown in Table 26.1.

Table 25.1: Available Methods for Setting Breakpoints

Command

Breakpoint Method

break function

Set a breakpoint at a function

break file:function

Set a breakpoint at a function

break line

Set a breakpoint at a line number

break file:line

Set a breakpoint at a line number

break address

Set a breakpoint at a physical address

One final interesting breakpoint method is the conditional breakpoint. Consider the following command:

 (gdb) break operator if op = 2     Breakpoint 2 at 0x8048445: file testapp.c, line 48. 

This tells GDB to break at the operator function if the op argument is equal to two ( OP_MULTIPLY ). This can be very useful if you re looking for a given condition rather than having to break at each call and check the variable.

Stepping Through the Source

When we left our debugging session, we had hit a breakpoint on our main function. Let s now step forward through the source. We have a few different possibilities, depending upon what we want to achieve (Table 26.2 lists these). To execute the next line of code, we can use the step command. This will also step into a function (if a function call is the next line to execute). If we d prefer to step over a function, we could use the next command, which executes the next line and, if a function, simply performs it and sets the next line to execute to the line after the function. The cont command (short for continue) simply starts the program running.

Table 25.2: Methods for Stepping Through the Source

Command (shortcut)

Operation

next ( n )

Execute next line, step over functions

step ( s )

Execute next line, step into functions

cont ( c )

Continue execution

We can also provide a count after the next and step commands, which performs the command the number of times specified as the argument. For example, issuing the command step 5 will perform the step command five times.

We illustrate the next and step commands within our debugging session as follows :

 Breakpoint 1, main () at testapp.c:79     79        initStack(&stack);     (gdb) s     initStack (stack=0xbfffde60) at testapp.c:20     20        assert(stack);     (gdb) s     21        stack->index = 0;     (gdb) s     22      }     (gdb) s     main () at testapp.c:81     81        push(&stack, 2);     (gdb) n     82        push(&stack, 5);     (gdb) 

In this last debugging fragment, we step into the initStack function. GDB then lets us know where we are (the function name and stack address). We step through the lines of initStack and upon returning, GDB let s us know again that we re back in the main function. We then use the next command to perform the push function with a value of 2.

Inspecting Data

GDB makes it easy to inspect the data within a running program. Continuing from our debugging session, let s now look at our stack structure. We do this with the display command:

 (gdb) display stack     1: stack = {stack = {2, 0, 1107383313, 134513378,          1108545272, 1108544020, -1073750392, 134513265,          1108544020, 1073792624}, index = 1}     (gdb) 

If we simply display the stack variable, we see the aggregate components of the structure (first the array itself, then the index variable). Note that many of the stack elements are unusually large numbers , but this is only because the structure was not initialized . We can inspect specific elements of the stack variable, also using the display command:

 (gdb) display stack.index     2: stack.index = 1     (gdb) 

If we were dealing with an object reference (a pointer to the structure), we could deal with it as we would in C. For example, in this next example, we step into the push function to illustrate dealing with an object reference:

 (gdb) s     push (stack=0xbffffae0, elem=5) at testapp.c:27     27        assert(stack);     (gdb) display stack->index     3: stack->index = 1     (gdb) display stack->stack[0]     4: stack->stack[0] = 2     (gdb) 

One important consideration is the issue of static data. Static data names may be used numerous times in an application (bad coding policy, but it happens). To display a specific instance of static data, we can reference both the variable and file, such as display ˜file2.c ::variable .

The print command (or its shortcut, p ) can also be used to display data.

Changing Data

It s also possible to change the data in an operating program. We use the set command to change data, illustrated as:

 (gdb) set stack->stack[9] = 999     (gdb) p *stack      = {stack = {2, 0, 1107383313, 134513378,          1108545272, 1108544020, -1073743096, 134513265,          1108544020, 999}, index = 1}     (gdb) 

Here we see that we ve modified the last element of our stack array and then printed it back out to monitor the change.

Examining the Stack

The backtrace command (or bt for short) can be used to inspect the stack. This can tell us the current active function trace and the parameters passed. We re currently in the push function in our debugging session; let s look at the stack backtrace:

 (gdb) bt     #0  push (stack=0xbffffae0, elem=5) at testapp.c:27     #1  0x08048589 in main () at testapp.c:82     #2  0x42015504 in __libc_start_main () from /lib/tls/libc.so.6     (gdb) 

At the top is the current stack frame. We re in the push function, with a stack reference and an element of 5. The second frame is the function that called push , in this case, the main function. Note here that main was called by a function __libc_start_main . This function provides the initialization for glibc .

Stopping the Program

It s also possible to stop a debugging session using Ctrl+C. If the program is stopped in a function for which no debugging information is available (it wasn t compiled with -g ), then only assembly will be displayed (since source debugging information is not available).




GNU/Linux Application Programming
GNU/Linux Application Programming (Programming Series)
ISBN: 1584505680
EAN: 2147483647
Year: 2006
Pages: 203
Authors: M. Tim Jones

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net