C Debugging Quickstart

Basics

In the following sample sessions, user input is red, comments are green, while computer output is blue. The sessions will use the program hello.c. In order to use source-level debugging, your C program must be compiled with the -g flag. This will instruct the compiler and linker to leave symbol table information in the executable, which the debugger can then use to associate addresses with source code lines and C data structures.

The easiest way to do this is by setting the CFLAGS environment. I highly recommend to put the line

export CFLAGS="-g -Wall"
into your .profile. There is, in general, no reason not to use these flags.

You can then compile and run your programs normally:

% make hello
cc -Wall -g hello.c -o hello
hello.c:19: warning: return type of `main' is not `int'
% ./hello
In main
Entering divide
zsh: floating point exception ./hello
%

Post-Mortem Debugging

You can use the debugger either post-mortem, i.e., after a program has crashed, or at run time. A program that crashes will produce a core file, which is simply the process image written to a file. The debugger can examine this image. Example:
% gdb hello core
GNU gdb 19990928
Copyright 1998 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 "i686-pc-linux-gnu"...
Core was generated by `./hello'.
Program terminated with signal 8, Floating point exception.
Reading symbols from /lib/libc.so.6...done.
Reading symbols from /lib/ld-linux.so.2...done.
#0 0x804843c in divide (i=1, j=0) at hello.c:8
8 k = i/j;
(gdb) p i print variable i
$1 = 1
(gdb) p j
$2 = 0
(gdb) bt "back trace" (show execution stack)
#0 0x804843c in divide (i=1, j=0) at hello.c:8
#1 0x80484c9 in main () at hello.c:32
(gdb)
... and now we know what happened. What we do not necessarily know is how this could happen. This is where run-time debugging is handy, as you can trace the execution of the program.

Run-Time Debugging

Gdb allows you to turn a post-mortem debugging session into a run-time debugging session, you can simply use the r ("run") command. Alternatively, you can start up the debugger without requiring a core file:
% gdb hello
GNU gdb 19990928
Copyright 1998 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 "i686-pc-linux-gnu"...
(gdb)
Here we can set breakpoints using the b command, where execution will stop and control is given back to the debugger. Breakpoints can be set on functions (stop at the entry to the function), on function exit, or on particular line numbers.

We can use the commands n ("next") and s ("step") to step through the program. The difference between the two is that n will step over function calls, while s will step into functions. The c ("continue") command simply continues normal execution of the program, until it hits the next breakpoint, or causes an exception.

The p command can show arbitrary C data structures.

Example:

(gdb) b main
Breakpoint 1 at 0x804846a: file hello.c, line 24.
(gdb) r
Starting program: /export/zuse/2/gernot/tmp/hello

Breakpoint 1, main () at hello.c:24
24 printf ("In main\n");
(gdb) n
In main
25 r.count = 5;
(gdb) p r
$1 = {count = 2147477592, next = 0x2aae2e68} r isn't inilialised yet...
(gdb) n
26 p = malloc(sizeof(struct rec));
(gdb) p r
$2 = {count = 5, next = 0x2aae2e68}
(gdb) p p
$3 = (struct rec *) 0x8049658 neither is p
(gdb) n
27 p->count = 10;
(gdb) p p
$4 = (struct rec *) 0x8049678
(gdb) p *p
$5 = {count = 0, next = 0x0} now p is, but *p isn't
(gdb) n
28 p->next = &r;
(gdb) p *p
$6 = {count = 10, next = 0x0} still the same
(gdb) n
29 r.next = p;
(gdb) p *p
$7 = {count = 10, next = 0x7fffe838}
(gdb) p *p->next
$8 = {count = 5, next = 0x2aae2e68}
(gdb) p *p->next->next
$9 = {count = -762985847, next = 0x2c71274}
(gdb) n
30 i = 1;
(gdb) p *p->next->next
$10 = {count = 10, next = 0x7fffe838}
(gdb) n
31 j = 0;
(gdb) n
32 k = divide (i, j); /* Ouch! */
(gdb) s
divide (i=1, j=0) at hello.c:7
7 printf ("Entering divide\n");
(gdb) bt
#0 divide (i=1, j=0) at hello.c:7
#1 0x80484c9 in main () at hello.c:32
(gdb) n
Entering divide
8 k = i/j;
(gdb) n

Program received signal SIGFPE, Arithmetic exception.
0x804843c in divide (i=1, j=0) at hello.c:8
8 k = i/j;
(gdb) bt
#0 0x804843c in divide (i=1, j=0) at hello.c:8
#1 0x80484c9 in main () at hello.c:32
(gdb) ^DThe program is running. Exit anyway? (y or n) y
%

What next?

That should give you the basic idea. More details can be found from
man gdb

Also, ddd gives you an nice point-and-click interface to gdb.