GDB Tutorial
All programmers make errors. As time progresses and you get more comfortable writing code, the number and severity of errors you make will decrease. Nevertheless, debugging is an inevitable part of a programmer's life. Most of the time, a few simple, well placed printf statements will suffice in pin-pointing the problem. Sometimes, however, a programmer is going to need more help. To this end, a debugger is an essential tool to learn for any programmer. In this course, we'll use gdb, the "GNU" debugger.
compiling programs to run with gdb:
In order for your compiled program to be compatible with gdb, it must be compiled with the -g option, which tells the compiler to embed debugging information in the executable for the debugger to use. So, to try to compile a file called crash.cpp :-), the following command would be used:
g++ -g -ocrash crash.cpp
(the case for gcc is exactly analogous)
Starting gdb:
You can start debugging a program by specifying it as a command line parameter. To start gdb for the crash program compiled above, I'd say:
$gdb crash
GNU gdb 5.3-22mdk (Mandrake Linux)
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 "i586-mandrake-linux-gnu"...
(gdb)
Before we run the program under gdb, take a look at the source for the not-so-well written
crash.cpp:
1 #include <stdio.h>
2 #include <string.h>
3 #include <stdlib.h>
4
5 char * buf;
6
7 int sumFrom1(int num)
8 {
9 int i,sum=0;
10 for(i=1;i<=num;i++)
11 sum+=i;
12
13 if(sum==5050) {
14 printf("5050 chance it'll crash??\n");
15 sprintf(buf,"5050");
16 }
17 printf("sum=%d",sum);
18 return sum;
19 }
20
21 void printSum()
22 {
23 char line[10];
24 printf("enter a number:\n");
25 gets(line);
26 sprintf(buf,"input=%d,sum=%d",line,sumFrom1(atoi(line)));
27 printf("%s\n",buf);
28 }
29
30 int main(void)
31 {
32 printSum();
33 return 0;
34 }
35
The program just reads a number n from the standard input, caculates the sum from 1 to n, and prints the result. Cut and paste the above code, compile it, and run it. You should see the following:
$ crash
enter a number:
30
Segmentation fault
Whoa! what happened?? (can you spot from the source what the problem might be?). The "Segmentation fault" indicates illegal memory access of some kind. Unfortunately, this is likely a message that you will become intimately familiar with if you aren't already (Under MS-Windows, it's "This Program has performed an illegal operation and will be shutdown"). At any rate, let's try to get some more information using the debugger (let's pick up from the point after you loaded gdb with crash):
run
(gdb) run
Starting program: /home/xman/bin/crash
enter a number:
30
Program received signal SIGSEGV, Segmentation fault.
0x4016d834 in _IO_str_overflow () from /lib/i686/libc.so.6
Current language: auto; currently c
backtrace
Ok...so it crashed. To get a little more insight, let's use the "backtrace" command, which tells gdb to display the stack frame (the list of all the function calls that lead up to the crash):
(gdb) backtrace
#0 0x4016d834 in _IO_str_overflow () from /lib/i686/libc.so.6
#1 0x4016c5e8 in _IO_default_xsputn () from /lib/i686/libc.so.6
#2 0x40144797 in vfprintf () from /lib/i686/libc.so.6
#3 0x40161a3c in vsprintf () from /lib/i686/libc.so.6
#4 0x4014ef2d in sprintf () from /lib/i686/libc.so.6
#5 0x080484e2 in printSum() () at crash.cpp:26
#6 0x08048513 in main () at crash.cpp:32
#7 0x401127f7 in __libc_start_main () from /lib/i686/libc.so.6
(gdb)
From this, we can discern that main() called printsum() which then called sprintf(). sprintf() went on to call a bunch of lower level functions, which eventually led to the crash. Everything from sprintf() down is outside of our control, so let's take a look at what we passed to sprintf. The output above says this happened at line 26 of crash.cpp. Let's take a look at that line:
26 sprintf(buf,"input=%d,sum=%d",line,sumFrom1(atoi(line)));
Break Points
We'd be interested to take a look at the values of variables at that point in the code, to see what exactly the problem might be. To do this we would set a
break point, and restart the program. When the execution reaches the break point, it will pause, allowing us to take a look at the state of things. To set a break point in gdb, type
break file:line#. So in our case, this is:
(gdb) break crash.cpp:26
Breakpoint 1 at 0x80484b5: file crash.cpp, line 26.
(gdb)
Let's re-run the program:
(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/xman/bin/crash
enter a number:
30
Breakpoint 1, printSum() () at crash.cpp:26
26 sprintf(buf,"input=%d,sum=%d",line,sumFrom1(atoi(line)));
Current language: auto; currently c++
(gdb)
Let's take a look at what the variables are. You can do this using the
print command:
(gdb) print line
$1 = "30\0\0\0\0\0\0¨õ"
(gdb)
So line has the character values '3' followed by '0' and then a null terminator '\0', and then junk. This is what we'd inputed to the system, so let's take a look at the other variable:
(gdb) print buf
$2 = 0x0
(gdb)
By now the error should be painfully obvious. We're trying to copy a bunch of stuff into a buffer pointed to by buf, but we haven't allocated any memory for it! buf is the null pointer, so when sprintf tries to place stuff into the "buffer", a segmentation fault (seg fault) is generated. Note that we were lucky in this case: because buf is a global, it was automatically initialized to 0 (null pointer). If it were not, it might contain an aribtrary value like 0x38fd2301 and then it's no longer obvious that the spot it points to in memory is invalid. Bugs due to uninitialized variables are a real pain to track down.
While we're on this topic, please read about some common
pitfalls to C programming.
Conditional break points:
Sometimes you want a break point to take effect only when some condition is true. For example, I might want to break at line 10 of crash.cpp only if num is 100:
(gdb) break crash.cpp:10
Breakpoint 1 at 0x8048429: file crash.cpp, line 10.
(gdb) condition 1 num==100
(gdb) run
Starting program: /home/xman/bin/crash
enter a number:
30
Program received signal SIGSEGV, Segmentation fault.
0x4016d834 in _IO_str_overflow () from /lib/i686/libc.so.6
(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/xman/bin/crash
enter a number:
100
Breakpoint 1, sumFrom1(int) (num=100) at crash.cpp:10
10 for(i=1;i<=num;i++)
(gdb) continue
Continuing.
5050 chance it'll crash??
Program received signal SIGSEGV, Segmentation fault.
0x4016d834 in _IO_str_overflow () from /lib/i686/libc.so.6
Notice the program didn't break on the first run, but did the second time when num==100. Note also you can resume execution with the continue command.
Stepping through individual statements:
Once your program has paused, you can watch the execution of your program line by line using step (s), next (n), until (u). next steps over function calls as 1 statement, whereas step goes into the function being called. until is like next, except that if you are at the end of a loop, until will continue execution until the loop is exited:
enter a number:
100
Breakpoint 1, sumFrom1(int) (num=100) at crash.cpp:10
10 for(i=1;i<=num;i++)
(gdb) n
11 sum+=i;
(gdb) n
10 for(i=1;i<=num;i++)
(gdb) n
11 sum+=i;
(gdb) n
10 for(i=1;i<=num;i++)
(gdb) n
11 sum+=i;
(gdb) u
10 for(i=1;i<=num;i++)
(gdb) u
13 if(sum==5050) {
(gdb)
Here once a break point has been reached, we start stepping through each line of execution with next (n). However, because num==100 in this case, this can get tiresome. Note how the until (u) was useful in getting out of the loop.
Other commands of interest:
list linenumber
will print out some lines from the source code around linenumber. If you give it the argument function it will print out lines from the beginning of that function. Just list without any arguments will print out the lines just after the lines that you printed out with the previous list command.
delete
delete will delete all breakpoints that you have set.
delete number will delete breakpoint numbered number.
clear function will delete the breakpoint set at that function. Similarly for linenumber, filename:function, and filename:linenumber.
x memory address will examine the contents of a memory address:
(gdb) print &num
$1 = (int *) 0xbffff580
(gdb) x 0xbffff580
0xbffff580: 0x00000064
(gdb)