Next Up Previous Contents
Next: 2.5.4 Intermixing Fortran and C
Up: 2.5 Code topics
Previous: 2.5.2 Optimization
[ID index][Keyword index]

2.5.3 Debugging

One approach to debugging is the brute-force method: simply scatter tracing statements through your code and watch them fill your screen. There are some types of debugging for which this is ideal -- for example, graphing a trace of intermediate values may reveal that an algorithm is showing signs of instability -- but it can be very cumbersome.

Better, in many cases, is to use a debugger. A debugger allows you to roam through your code, stopping in troublesome functions, examining data, and stepping through your code line-by-line. There is a great deal you can do with a debugger, but they are not often the easiest tools to master. However, there is a good deal you can do armed only with experience of the foothills of the debugger's learning curve.

I will describe the Sun debugger dbx, here. The use of the GNU debugger, gdb, and the dbx debugger on Digital Unix, are broadly similar.

First, compile crash.c, listed in Appendix A.5, in the usual way (cc -o crash crash.c), run it, and watch it crash when it tries to dereference a zero pointer. This leaves a core file in the current directory[Note 9]. You can obtain rudimentary post-mortem information from this core file with dbx, as follows:


% dbx crash core
[...]
program terminated by signal SEGV (no mapping at the fault address)
(dbx) where                                                                  
=>[1] bananas(0x0, 0x1, 0xef691338, 0x10074, 0x2, 0xeffff8d0), at 0x10870
[2] main(0x1, 0xeffff94c, 0xeffff954, 0x20800, 0x1, 0x0), at 0x108fc
(dbx) quit
The where command to dbx shows where the program was when it crashed (the [...] shows where I have omitted some unenlightening chatter from the debugger).

The other information dbx gives you is less useful -- the debugger doesn't know enough about your program to be more helpful. You can, however, tell the compiler to pass on more information to the debugger when it processes your code; do this with the -g flag to the compiler, as in cc -g -o crash crash.c. If you compile several modules to produce your executable, you'll need to give this option to the compilation of each module you wish to debug. You'll also have to give this option at the link stage on SunOS4, but not on Solaris or for gdb on Linux.

If we run crash again now, and look at the core file, we see that the debugger can provide us with more information.


% dbx crash core
[...]
program terminated by signal SEGV (no mapping at the fault address)
Current function is bananas
12           printf ("banana split: %d\n", *zp);
(dbx) where                                                                  
=>[1] bananas(i = 0), line 12 in "crash.c"
[2] main(), line 22 in "crash.c"
(dbx) print i
i = 0
(dbx) print buf
buf = "banana number 0
"
(dbx) print *zp
dbx: reference through nil pointer
(dbx) print zp
zp = (nil)
(dbx) quit
Note that we can examine the values of variables in scope at the point where execution stopped, and that dbx knows enough about C (or Fortran) to know the type of variables it is asked to print. Printing the value of the pointer zp shows us why our program has crashed.

The debugger is not only useful for such post-mortem diagnosis. It is also (for some people, primarily) used for investigating a program's behaviour when it is running normally, albeit with bugs we wish to track down. For example, start the debugger and tell it to stop when it enters the function bananas:


% dbx crash
[...]
(dbx) stop in bananas
(2) stop in bananas
You can set multiple breakpoints (so called) not only in functions, but also at line numbers, or linenumbers within files (using (dbx) stop at crash.c:12). Then tell the program to start running:

(dbx) run           
Running: crash 
(process id 29707)
Entering the bananas function: 1
stopped in bananas at line 6 in file "crash.c"
6       sprintf (buf, "banana number %d\n", i);    
(dbx) next
stopped in bananas at line 7 in file "crash.c"
7       if (i != 0)
(dbx) print i
i = 1
Note that, as well as output from the debugger itself, we also see output from the running program. If the program required input from the user, it would receive it as normal. The program stops at the first line of code within the bananas function. We can move one line forward in the code, verify that the parameter i has its expected value, then tell the program to resume execution.

(dbx) cont 
banana number 1
No bananas left!
stopped in bananas at line 6 in file "crash.c"
6       sprintf (buf, "banana number %d\n", i);    
(dbx) print i
i = 0
(dbx) cont 
signal SEGV (no mapping at the fault address) in bananas 
at line 12 in file "crash.c"
12           printf ("banana split: %d\n", *zp);
(dbx) quit

The Solaris debugger can be used through a GUI: give the command debugger to start this up. The Digital GUI debugger is ladebug. There is a GUI interface to gdb, called xxgdb.

In general, you can't debug optimized code (that is, code compiled with the option -O), because the optimizer may rearrange lines or remove redundant variables. However, both dbx and gdb do have support for debugging code with mild optimization, though there are some things, such as stepping from line to line, you might not be able to do.

This introduction has merely scratched the surface of what you can do with the debugger, by showing you the bare minimum of commands you need to navigate around your running program. There is a great deal more information available in Sun's debugging manual [sundebug], or in gdb's info pages (info gdb).


Next Up Previous Contents
Next: 2.5.4 Intermixing Fortran and C
Up: 2.5 Code topics
Previous: 2.5.2 Optimization
[ID index][Keyword index]
Theory and Modelling Resources Cookbook
Starlink Cookbook 13
Norman Gray
2 December 2001. Release 2-5. Last updated 10 March 2003