The debugger that comes with FreeBSD is called gdb (GNU debugger). You start it up by typing
% gdb progname
although most people prefer to run it inside Emacs. You can
do this by:
M-x gdb RET progname RET
Using a debugger allows you to run the program under more controlled circumstances.
Typically, you can step through the program a line at a time, inspect the value of
variables, change them, tell the debugger to run up to a certain point and then stop, and
so on. You can even attach to a program that is already running, or load a core file to
investigate why the program crashed. It is even possible to debug the kernel, though that
is a little trickier than the user applications we will be discussing in this
section.
gdb has quite good on-line help, as well as a set of info
pages, so this section will concentrate on a few of the basic commands.
Finally, if you find its text-based command-prompt style off-putting, there is a
graphical front-end for it (xxgdb) in the ports collection.
This section is intended to be an introduction to using gdb
and does not cover specialized topics such as debugging the kernel.
You will need to have compiled the program with the -g
option to get the most out of using gdb. It will work without,
but you will only see the name of the function you are in, instead of the source code. If
you see a line like:
... (no debugging symbols found) ...
when gdb starts up, you will know that the program was not
compiled with the -g option.
At the gdb prompt, type break
main. This will tell the debugger to skip over the preliminary set-up code in the
program and start at the beginning of your code. Now type run to start the program--it will start at the beginning of the
set-up code and then get stopped by the debugger when it calls main()
. (If you have ever wondered where main()
gets called from, now you know!).
You can now step through the program, a line at a time, by pressing n. If you get to a function call, you can step into it by pressing
s. Once you are in a function call, you can return from stepping
into a function call by pressing f. You can also use up and down to take a quick look at the
caller.
Here is a simple example of how to spot a mistake in a program with gdb. This is our program (with a deliberate mistake):
#include <stdio.h>
int bazz(int anint);
main() {
int i;
printf("This is my program\n");
bazz(i);
return 0;
}
int bazz(int anint) {
printf("You gave me %d\n", anint);
return anint;
}
This program sets i to be 5 and
passes it to a function bazz()
which prints out the number
we gave it.
When we compile and run the program we get
% cc -g -o temp temp.c
% ./temp
This is my program
anint = 4231
That was not what we expected! Time to see what is going on!
% gdb temp
GDB is free software and you are welcome to 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.
GDB 4.13 (i386-unknown-freebsd), Copyright 1994 Free Software Foundation, Inc.
(gdb) break main Skip the set-up code
Breakpoint 1 at 0x160f: file temp.c, line 9. gdb puts breakpoint at main()
(gdb) run Run as far as main()
Starting program: /home/james/tmp/temp Program starts running
Breakpoint 1, main () at temp.c:9 gdb stops at main()
(gdb) n Go to next line
This is my program Program prints out
(gdb) s step into bazz()
bazz (anint=4231) at temp.c:17 gdb displays stack frame
(gdb)
Hang on a minute! How did anint get to be 4231? Did we not we set it to be 5 in
main()
? Let's move up to main()
and have a look.
(gdb) up Move up call stack
#1 0x1625 in main () at temp.c:11 gdb displays stack frame
(gdb) p i Show us the value of i
$1 = 4231 gdb displays 4231
Oh dear! Looking at the code, we forgot to initialize i. We
meant to put
...
main() {
int i;
i = 5;
printf("This is my program\n");
...
but we left the i=5; line out. As we did not initialize
i, it had whatever number happened to be in that area of memory
when the program ran, which in this case happened to be 4231.
Note: gdb displays the stack frame every time we go
into or out of a function, even if we are using up and down to move around the call stack. This shows the name of the
function and the values of its arguments, which helps us keep track of where we are and
what is going on. (The stack is a storage area where the program stores information about
the arguments passed to functions and where to go when it returns from a function
call).
A core file is basically a file which contains the complete state of the process when
it crashed. In “the good old days”, programmers had to print out hex listings
of core files and sweat over machine code manuals, but now life is a bit easier.
Incidentally, under FreeBSD and other 4.4BSD systems, a core file is called progname.core instead of just core, to make it clearer which program a core file belongs to.
To examine a core file, start up gdb in the usual way.
Instead of typing break or run,
type
(gdb) core progname.core
If you are not in the same directory as the core file, you will have to do dir /path/to/core/file first.
You should see something like this:
% gdb a.out
GDB is free software and you are welcome to 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.
GDB 4.13 (i386-unknown-freebsd), Copyright 1994 Free Software Foundation, Inc.
(gdb) core a.out.core
Core was generated by `a.out'.
Program terminated with signal 11, Segmentation fault.
Cannot access memory at address 0x7020796d.
#0 0x164a in bazz (anint=0x5) at temp.c:17
(gdb)
In this case, the program was called a.out, so the core file
is called a.out.core. We can see that the program crashed due
to trying to access an area in memory that was not available to it in a function called
bazz
.
Sometimes it is useful to be able to see how a function was called, as the problem
could have occurred a long way up the call stack in a complex program. The bt command causes gdb to print out a
back-trace of the call stack:
(gdb) bt
#0 0x164a in bazz (anint=0x5) at temp.c:17
#1 0xefbfd888 in end ()
#2 0x162c in main () at temp.c:11
(gdb)
The end()
function is called when a program crashes; in
this case, the bazz()
function was called from main()
.
One of the neatest features about gdb is that it can attach
to a program that is already running. Of course, that assumes you have sufficient
permissions to do so. A common problem is when you are stepping through a program that
forks, and you want to trace the child, but the debugger will only let you trace the
parent.
What you do is start up another gdb, use ps to find the process ID for the child, and do
(gdb) attach pid
in gdb, and then debug as usual.
“That is all very well,” you are probably thinking, “but by the time
I have done that, the child process will be over the hill and far away”. Fear not,
gentle reader, here is how to do it (courtesy of the gdb info
pages):
...
if ((pid = fork()) < 0) /* _Always_ check this */
error();
else if (pid == 0) { /* child */
int PauseMode = 1;
while (PauseMode)
sleep(10); /* Wait until someone attaches to us */
...
} else { /* parent */
...
Now all you have to do is attach to the child, set PauseMode
to 0, and wait for the sleep()
call to return!