< August 2006 >
   1 2 3 4 5
6 7 8 9101112
Wed, 16 Aug 2006:

The first lines of this was written around seven months ago, when I was itching to get a proper article published with the stuff I've learned over the last four years. Working on the dotgnu unroller, APC and debugging random bits of code had turned me into a gdb power user. Sure, there's lots I don't know about gdb and most of what I talk about is stuff you can read right off the man page. But, even these I had to learn the hard way and I wanted to get this out to the world in one compact vehicle. Here's the part1 or the so-called duh! component of the four-part tutorial.

Segfaults: No, this is not for developers. Any and everytime I've reported a segfault to a developer, the first thing they ask for is a backtrace. To put it simply, a backtrace is a log of all the calls leading up to the segfault and is invaluable when dealing with most segfaults, be it bad arguments, corner case inputs or plain stupidity (of the user, developer or even his counterpart on the server side). Getting a backtrace out is the step #1 for any bug report. But before that, you've got to figure out how to run the errant program inside gdb.

bash$ gdb ./sig11

(gdb) run argument1=0 argument2=1 file1 file2 ...

That starts off the program in gdb. Now, you can continue to make the program crash by doing whatever you were doing before.

Program received signal SIGSEGV, Segmentation fault.
0x0000003c2fe6f200 in strlen () from /lib64/tls/libc.so.6

To get a backtrace out of it is just the matter of a single command bt

(gdb) bt
#0  0x0000003c2fe6f200 in strlen () from /lib64/tls/libc.so.6
#1  0x000000000040054d in foo (a=0x0) at test.c:5
#2  0x000000000040058a in main () at test.c:9

But often, distros like ubuntu and fedora, ship binaries without any debug info, to save on install space. If you are debugging something real, better look for a package that says -debuginfo. Or if you were building your own programs, compile them with the -g flag (gcc -g test.c).

There are variants of backtraces. You can provide backtrace with a count or even can ask it to be more verbose about the induvidual frames (every method call occupies a frame, unless it is a recursive tail call ... *blink* *blink*). For example, I could run bt full to print out the locals in the ancestor functions directly.

(gdb) bt full
#0  0x0000003c2fe6f200 in strlen () from /lib64/tls/libc.so.6
No symbol table info available.
#1  0x000000000040054d in foo (a=0x0) at test.c:5
No locals.
#2  0x000000000040059f in main () at test.c:11
        xyz = 0x4006b3 "abc"
        template = "abc"

Frames: Frames need more exploring in most cases. The recent debug symbol version, embed the entire code in the function. The key commands to play around with are frame or f for short and the list command. Both of these combined, empowers the user to see the code that went into the binary.

(gdb) f 2
#2  0x000000000040059f in main () at test.c:11
11              foo(NULL);

You can move up and down the frame using the up and down commands.

Now, just seeing the obvious culprit might not have been enough. You might want to see code around the actual call to decide the error condition. That's where list really kicks in.

(gdb) list
6       }
7       int main()
8       {
9               const char * xyz = "abc";
10              const char template[] = "abc";
11              foo(NULL);
12      }

Actually, you can list arbitrary lines of code too. This is just too good for debugging a huge file of code. For example, if I wanted to see what's at line 1 of the file.

(gdb) list test.c:1
1       #include &glt;stdio.h>
3       int foo(char * a)
4       {

Frame info: The frame info commands let you look at the arguments and local variables quickly. Unfortunately, if your code modifies the arguments along the way, you'll just see the new value, especially on AMD64 and other places where there are enough registers to pass around arguments.

(gdb) info locals
xyz = 0x4006b3 "abc"
template = "abc"
(gdb) info args
argc = 1
argv = (char **) 0x7fbffff858

Print: these are my favourite type of commands. Mainly because they print out information based on the inferred types and dump full structures. For example, here's a tidbit of print commands used on some ZendEngine2 (php) code.

(gdb) p h
$2 = (zend_file_handle *) 0x2a9a433540
(gdb) p *h
$1 = {type = 0 '\0', filename = 0x0, opened_path = 0x0, 
      handle = {fd = 2, fp = 0x2, stream = {
      handle = 0x2, reader = 0x2a9a433548 <executor_globals+8>, closer = 0, 
      fteller = 0, interactive = 1}}, free_filename = 104 'h'}

Now, this tells me way more than the average printf does. Especially, if I don't know what I'm looking for. But sometimes, you want some "processed" data out. Maybe the hex version of something or someone's mangled the name with a NUL prepend. So there's still a printf around.

(gdb) p src
$1 = (zend_function *) 0x7fbffff260
(gdb) printf "0x%02x\n", src->common.function_name[0]
(gdb) printf "%s\n", src->common.function_name+1

These are the basic commands I use to start off inspecting a coredump or other misbehaving programs. But there are a lot more you can do if you are debugging a live program. My next tutorial is about attaching a debugger to a program and the extra stuff you can do with the process. Breaking, watching and the works.

Watch this space.

"The debugger is akin to giving the _rabbits_ a bazooka. The poor wolf doesn't need any sharper teeth."
                      -- Linus Torvalds, about NT's new debugger

posted at: 18:11 | path: /tutorials | permalink | Tags: , ,