Fred's Home Page

Main
About me
Crumble
csh is bad
Debian
FredCam
Guppy
Hardware
Help out
Java-glossary
Job-control
KRoC/Linux (old)
Lambda
Life-stuff
Links
Linux
Mesh
Misc
Music
occam
occam upgrades
occam tutorial
OpenUPSd
Pictures
Programming
Projects (PhD)
Publications
Quick gdb
RAPP
RMoX
Software
UNIX crashcourse
UWG
UWGBuilder
WG / WGBuilder
XDM-Choose
XHPD

A Brief Guide to Using GDB (the GNU debugger)

[ about | compiling | running | debugging | breakpoints ]

About this document

This document provides a brief guide to using the `gdb' debugger, for use with C and C++ programs compiled using `gcc' and `g++'. The full documentation for gdb can be found here, on the GNU site. Documentation (in the form of manuals as HTML, TeXinfo and PostScript) for most GNU software can be found here.

This document is largely intended for helping with ``my C program crashes and I have no idea why'' scenarios, as are often experienced by new C programmers. Often, errors appear in the use of pointers, which are a somewhat alien concept to many Java programmers.


Compiling for debugging

Before using a debugger, it is necessary to make sure that the program to be debugged has been compiled appropriately. For the combination of `gcc' and `gdb' (which are of interest here), this involves using the ``-ggdb'' command-line option to `gcc' (just using the ``-g'' option will often suffice however).

For software with a `Makefile', this flag can be added by editing the ``CFLAGS'' definition in the Makefile -- it will mostly be obvious. Generally speaking, the flags ``-Wall -ggdb'' are a good plan. Then simply ``make'' the program (with possibly a ``make clean'' first).

For compiling a single source file manually, do it in the usual command-line style with the extra arguments:

    bash$ gcc -Wall -ggdb -o ./myprog ./myprog.c

Running the debugger

Once compiled with debugging, a program can be run by invoking the debugger with the executable name:

    bash$ gdb ./myprog

GDB will print some information before presenting the debugging prompt:

    (gdb) 

If the program has previously crashed and left a `core' file behind, this can be used to find out what happened, simply by passing the core file on the command-line too:

    bash$ gdb ./myprog ./core

Note that `core' must have been generated by this version of `myprog', the debugger will complain if this is not the case.


Debugging code

As an example, consider the following (slightly faulty) program (myprog.c):

    #include <stdio.h>
    
    int main (int argc, char **argv)
    {
        int i;
    
        printf ("Hello, world!\n");
    
        /* print first characters of command-line arguments */
        for (i=0; i<=argc; i++) {
            printf ("%c", argv[i][0]);
        }
        printf ("\n");
    
        return 0;
    }

This is compiled using the following:

    bash$ gcc -Wall -ggdb -o ./myprog ./myprog.c

When run, this program prints the hello message then segfaults:

    bash$ ./myprog
    Hello, world!
    Segmentation fault

Thus to find what the problem is, the debugger is used. For my Linux (debian/unstable) box, gdb prints:

    bash$ gdb ./myprog

    GNU gdb 5.3-debian
    Copyright 2002 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-linux"...

    (gdb) 

Once that's loaded, the program can be run from within the debugger using the `(r) run' command. Any arguments that would normally be passed to the program are given here. GDB will run the program until it terminates, receives a stopping signal (``segmentation fault'' (SIGSEGV) is one of these), or until a breakpoint is reached. For example:

    (gdb) r some arguments 42
    Starting program: /storage/home/frmb2/toys/c/myprog some arguments 42
    Hello, world!

    Program received signal SIGSEGV, Segmentation fault.
    0x0804836c in main (argc=4, argv=0xbffffcb4) at myprog.c:12
    12                      printf ("%c", argv[i][0]);
    (gdb) 

This shows where the program crashed -- in this case on line 12 of `myprog.c', and gdb helpfully shows the offending line of code.

Sometimes a program will crash in code outside that being debugged. For example, if the `free()' library-call is called with a NULL pointer. To figure out where it was called from, the `(bt) backtrace' command is used. For `myprog', this is not terribly exciting, since only `main()' is involved. But:

    (gdb) bt
    #0  0x0804836c in main (argc=4, argv=0xbffffcb4) at myprog.c:12
    (gdb) 

This shows the functions called (`stack frames'), with 0 being the frame where the program crashed, moving upwards to the last, that will be `main()'. In the above example, there is only one stack frame to show. By default, frame 0 is active when the debugger resumes after a crash. If the code of interest is not frame 0, then the correct frame must be selected. This is done using the `(f) frame' command, for example:

    (gdb) f 0
    #0  0x0804836c in main (argc=4, argv=0xbffffcb4) at myprog.c:12
    12                      printf ("%c", argv[i][0]);

Once some offending code has been located, variables can be inspected in an attempt to determine what went wrong. For this code, the only reasonable explanation is that an attempt to access ``argv[i][0]'' is causing the segmentation fault. The `(p) print' command is used to inspect the values of expressions, and should be approached incrementally, for example:

    (gdb) p argv
    $1 = (char **) 0xbffffcb4
    (gdb) p i    
    $2 = 4
    (gdb) p argv[i]
    $3 = 0x0
    (gdb) 

and thus the error is found -- ``argv[i]'' is NULL (zero), so attempting to access its elements will cause a segmentation fault (by NULL dereference in this case). The cause of the error is something else, and may require close inspection of the code to figure out what the real problem is. A functional, but not entirely clean solution, would be to modify the loop to check for NULL cases:

    for (i=0; i<=argc; i++) {
        if (argv[i] != NULL) {
            printf ("%c", argv[i][0]);
        }
    }

However, this is not the real problem. The loop itself is wrong, since on the last iteration `i' is `argc'; and `argv[argc]' is not a valid command-line argument. The last valid argument is `argv[argc-1]'. Fixing the loop is trivial:

    for (i=0; i<argc; i++) {
        printf ("%c", argv[i][0]);
    }

After editing and re-compiling, the program works as expected:

    bash$ ./myprog foo bar co
    Hello, world!
    .fbc

Setting breakpoints and stepping

Sometimes it is desirable to stop a program at a specific point, before it crashes. This is particularly useful for investigating suspect code, since breakpoints can be moved around to help track down the bug. The `(b) break' command is used to set a breakpoint. For the fairly boring example above, we might want to stop execution when the `for()' loop on line 11 is reached. Thus:

    (gdb) b myprog.c:11
    Breakpoint 1 at 0x8048348: file myprog.c, line 11.
    (gdb) 

Additional breakpoints can be set without restriction. To delete a breakpoint, the `(d) delete' command is used, giving a breakpoint number as an optional argument. Without arguments, this command will delete all breakpoints (but will ask first). To show the current breakpoints, the command `(i b) info breakpoints' is used. For example:

    (gdb) i b
    Num Type           Disp Enb Address    What
    1   breakpoint     keep y   0x08048348 in main at myprog.c:11

The program is run as normal from within the debugger, but will stop if it reaches a breakpoint (before terminating or receiving a signal). GDB reports the breakpoint number, filename and line-number where the break occured. For example:

    (gdb) r foo bar co
    Starting program: /storage/home/frmb2/toys/c/myprog foo bar co
    Hello, world!

    Breakpoint 1, main (argc=4, argv=0xbffffcc4) at myprog.c:11
    11              for (i=0; i<argc; i++) {
    (gdb) 

Variables and the backtrace can be inspected in the normal way. To continue after a breakpoint, or a crash, the commands `(s) step' and `(c) continue' are generally useful (there are additional ones too). The `step' command attempts to step to the next line of source code, going into function calls for which it has debugging information. The `continue' command resumes execution proper, until the program reaches another (or possibly the same) breakpoint, terminates or receives a signal.

Last modified: 2003-10-29 01:32:27.000000000 +0000 by Fred Barnes [ls] [plain]
Page generated: Sun Apr 28 11:39:34 2013
Valid XHTML 1.0! Valid CSS!