Debugging with GDB

From Gentoo Wiki
Jump to:navigation Jump to:search

Introduction

GDB (sys-devel/gdb) is used to find run time errors that normally involve memory corruption. In order to debug a program, one has to emerge the program with FEATURES="nostrip" to prevent the stripping of debug symbols. The debug information is stripped by default to safe space. Here, bad_code is the program we will debug with gdb.

CODE Comparison of Filesizes
## (debug symbols stripped)
3140 bytes
## (debug symbols enabled)
6374 bytes
## (-ggdb flag enabled)
19552 bytes

Adding -ggdb to the system's CFLAGS and CXXFLAGS adds more debugging information than is generally included. This is how /etc/portage/make.conf might look with the newly added flags.

FILE /etc/portage/make.confmake.conf settings
CFLAGS="-O1 -Wall -g -pipe -ggdb"
CXXFLAGS="${CFLAGS}"

-Wall prints all compiler warnings, and -g produces debugging information in the operating system's native format which also works with gdb. See GCC Debugging Options for details.

Lastly, add debug to the specific package's USE flags. This can be done with the package.use file:

user $echo "category/package debug" >> /etc/portage/package.use
Note
The directory /etc/portage does not exist by default so it may need to be created. If the package already has USE flags set in package.use, then manually modify them in a favorite text editor.

Then we re-emerge the package with the modifications we've done so far as shown below.

root #FEATURES="nostrip" emerge package

Now that debug symbols are set it is time to begin debugging the program.

If you need a little more help while debugging, such as being able to see the code within GDB TUI (GDB Text User Interface), install the dev-util/debugedit package and include FEATURES="installsources". You also need to specify the source code folder:

user $gdb --tui -d /usr/src/debug/app-cdr/my_app/my_app/src my_app

Although not really needed for meaningful backtraces, this significantly helps when actively coding.

Running the program with GDB

Let's say we have a program here called "bad_code". Some person claims that the program crashes and provides an example. You go ahead and test it out:

user $/bad_code `perl -e 'print "A"x100'`
Segmentation fault

It seems this person was right. Since the program is obviously broken, there is now a bug at hand. Now, it is time to use gdb to help solve this matter. First we run gdb with the --args option, then give it the full program with arguments like shown:

user $gdb --args ./bad_code `perl -e 'print "A"x100'`
GNU gdb 6.3
Copyright 2004 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"...Using host libthread_db library "/lib/libthread_db.so.1".
Note
One can also debug with core dumps. These core files contain the same information that the program would produce when run with gdb. In order to debug with a core file with bad_code, you would run gdb ./bad_code core where core is the name of the core file.

You should see a prompt that says "(gdb)" and waits for input. First, run the program with run:

(gdb)run
Starting program: /home/chris/bad_code

Program received signal SIGSEGV, Segmentation fault.
0xb7ec6dc0 in strcpy () from /lib/libc.so.6

Here we see the program starting, as well as a notification of SIGSEGV, or Segmentation Fault. This is GDB telling us that the program has crashed. It also gives the last run function it could trace when the program crashes. However, this is not too useful, as there could be multiple strcpy's in the program, making it hard for developers to find which one is causing the issue. In order to help them out, we do what's called a backtrace. A backtrace runs backwards through all the functions that occurred upon program execution, to the function at fault. Functions that return (without causing a crash) will not show up on the backtrace. To get a backtrace at the (gdb) prompt type in bt. Something similar to the following message should be returned:

(gdb)bt
#0  0xb7ec6dc0 in strcpy () from /lib/libc.so.6
#1  0x0804838c in run_it ()
#2  0x080483ba in main ()

You can notice the trace pattern clearly. main() is called first, followed by run_it(), and somewhere in run_it() lies the strcpy() at fault. Things such as this help developers narrow down problems. There are a few exceptions to the output. First off is forgetting to enable debug symbols with FEATURES="nostrip". With debug symbols stripped, the output looks something like this:

(gdb)bt
#0  0xb7e2cdc0 in strcpy () from /lib/libc.so.6
#1  0x0804838c in ?? ()
#2  0xbfd19510 in ?? ()
#3  0x00000000 in ?? ()
#4  0x00000000 in ?? ()
#5  0xb7eef148 in libgcc_s_personality () from /lib/libc.so.6
#6  0x080482ed in ?? ()
#7  0x080495b0 in ?? ()
#8  0xbfd19528 in ?? ()
#9  0xb7dd73b8 in __guard_setup () from /lib/libc.so.6
#10 0xb7dd742d in __guard_setup () from /lib/libc.so.6
#11 0x00000006 in ?? ()
#12 0xbfd19548 in ?? ()
#13 0x080483ba in ?? ()
#14 0x00000000 in ?? ()
#15 0x00000000 in ?? ()
#16 0xb7deebcc in __new_exitfn () from /lib/libc.so.6
#17 0x00000000 in ?? ()
#18 0xbfd19560 in ?? ()
#19 0xb7ef017c in nullserv () from /lib/libc.so.6
#20 0xb7dd6f37 in __libc_start_main () from /lib/libc.so.6
#21 0x00000001 in ?? ()
#22 0xbfd195d4 in ?? ()
#23 0xbfd195dc in ?? ()
#24 0x08048201 in ?? ()

This backtrace contains a large number of ?? (question) marks. This is because without debug symbols, gdb does not know how the program was run. Hence, it is crucial that debug symbols are not stripped. Let's see what the output looks like with -ggdb flag enabled:

(gdb)bt
#0  0xb7e4bdc0 in strcpy () from /lib/libc.so.6
#1  0x0804838c in run_it (input=0x0) at bad_code.c:7
#2  0x080483ba in main (argc=1, argv=0xbfd3a434) at bad_code.c:12

Here we see that a lot more information is available for developers. Not only is function information displayed, but even the exact line numbers of the source files. This method is the most preferred if you can spare the extra space.

The backtrace can be saved to a file by copying and pasting from the terminal. Now that we're done with gdb, we can quit.

(gdb)quit
The program is running. Exit anyway? (y or n) y

This ends the walk-through of gdb. However, there are other types of errors such as improper file access that can cause a program to fail during run time. We can find those using strace.