CMPSC 311, Introduction to Systems Programming

GDB



Some references
A test program



(Excerpts from Harvard Univ., CS161, Operating Systems, GDB handout; h/t Prof. Margo Seltzer et al.  We added a few bullets.)

GDB is a debugger.  Its purpose is to help you analyze the behavior of your program, and thus help you diagnose bugs or mistakes.

GDB allows you to examine what is happening inside a program while it is running.  It lets you execute programs in a controlled manner and view and set the values of variables.

With GDB you can do the following things:

In your previous programming experience, you may have managed without using a debugger.  You might have been able to find the mistakes in your programs by printing things on the screen or simply reading through your code.  Beware, however, that OS/161 [the operating system developed in the CS161 course] is a large and complex body of code, much more so than you may have worked on in the past.  To make matters worse, much of it was written by someone other than you.  A debugger is an essential tool in this environment.

We would not lie if we said that there has never been a student in CS161 who has survived this class without using GDB.  You should, therefore, take the time to learn GDB and make it your best friend (or rather your second best friend; your best friend should be your partner).

This guide will explain to you how to get started debugging OS/161, describe the most common GDB commands, and suggest some helpful debugging techniques.

Debugging tips

Tip #1: Check your beliefs about the program

So how do you actually approach debugging?  When you have a bug in a program, it means that you have a particular belief about how your program should behave, and somewhere in the program this belief is violated.  For example, you may believe that a certain variable should always be 0 when you start a "for" loop, or a particular pointer can never be NULL in a certain "if statement".  To check such beliefs, set a breakpoint in the debugger at a line where you can check the validity of your belief.  And when your program hits the breakpoint, ask the debugger to display the value of the variable in question.

Tip #2: Narrow down your search

If you have a situation where a variable does not have the value you expect, and you want to find a place where it is modified, instead of walking through the entire program line by line, you can check the value of the variable at several points in the program and narrow down the location of the misbehaving code.

Tip #3: Walk through your code

Steve Maguire (the author of Writing Solid Code) recommends using the debugger to step through every new line of code you write, at least once, in order to understand exactly what your code is doing.  It helps you visually verify that your program is behaving more or less as intended.  With judicious use, the step, next and finish commands can help you trace through complex code quickly and make it possible to examine key data structures as they are built.

Tip #4: Use good tools

Using GDB with a visual front-end can be very helpful.  For example, using GDB inside the emacs editor puts you in a split-window mode, where in one of the windows you run your GDB session, and in the other window the GDB moves an arrow through the lines of your source file as they are executed.  To use GDB through emacs do the following:

  1. Start emacs.
  2. Type the "meta" key followed by an "x".
  3. At the prompt type "gdb". Emacs will display the message:
        Run gdb (like this): gdb

Tip #5: Beware of printfs!

A lot of programmers like to find mistakes in their programs by inserting "printf" statements that display the values of the variables.  If you decide to resort to this technique, you have to keep in mind two things:  First, because adding printfs requires a recompile, printf debugging may take longer overall than using a debugger.

More subtly, if you are debugging a multi-threaded program, such as an OS kernel, the order in which the instructions are executed depends on how your threads are scheduled, and some bugs may or may not manifest themselves under a particular execution scenario.  Because printf outputs to the console, and the console is a serial device that isn't extraordinarily fast, an extra call to printf may alter the timing and scheduling considerably.  This can make bugs hide or appear to come and go, which makes your debugging job much more difficult.

...  (Tips 6 and 7 are specific to assembly languages and OS/161)

Tip #8: Other tricks and caveats

If you have a void * in GDB and you know what type it actually is, you can cast it when printing, using the usual C expression syntax.

(end of excerpts from Harvard)




Some output from GDB on Linux, using a server with AMD processors


% gdb
GNU gdb Red Hat Linux (6.3.0.0-1.162.el4rh)
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 "i386-redhat-linux-gnu".

(gdb) help
List of classes of commands:

aliases -- Aliases of other commands
breakpoints -- Making program stop at certain points
data -- Examining data
files -- Specifying and examining files
internals -- Maintenance commands
obscure -- Obscure features
running -- Running the program
stack -- Examining the stack
status -- Status inquiries
support -- Support facilities
tracepoints -- Tracing of program execution without stopping the program
user-defined -- User-defined commands

Type "help" followed by a class name for a list of commands in that class.
Type "help" followed by command name for full documentation.
Command name abbreviations are allowed if unambiguous.

(gdb) quit


% make gdbdemo
gcc -g -Wall -Wextra -o h1.x h1.c
h1.c:22: warning: unused parameter 'argc'
% h1.x
Segmentation fault

% h1.x 1
   1  f1
main out

% h1.x 2
   2  f1
main out

% h1.x 3
   3  f2 ***
main out

% gdb h1.x
GNU gdb Red Hat Linux (6.3.0.0-1.162.el4rh)
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 "i386-redhat-linux-gnu"...Using host libthread_db library "/lib/tls/libthread_db.so.1".

(gdb) run
Starting program: .../h1.x

Program received signal SIGSEGV, Segmentation fault.
0x0071addc in ____strtol_l_internal () from /lib/tls/libc.so.6
(gdb) bt
#0  0x0071addc in ____strtol_l_internal () from /lib/tls/libc.so.6
#1  0x0071ab6f in __strtol_internal () from /lib/tls/libc.so.6
#2  0x00718136 in atoi () from /lib/tls/libc.so.6
#3  0x0804840b in main (argc=1, argv=0xbfe6b4e4) at h1.c:27
(gdb) l h1.c:27
22    int main(/*@unused@*/ int argc, char *argv[])    /*@globals n@*/ /*@modifies n@*/
23    {
24      void (*func)(void) = f1;
25      uintptr_t A[2];
26   
27      A[n = atoi(argv[1])] = (uintptr_t) f2;
28   
29      (*func)();
30   
31      printf("main out\n");
(gdb) b 26
Breakpoint 1 at 0x80483fb: file h1.c, line 26.
(gdb) c
Continuing.

Program terminated with signal SIGSEGV, Segmentation fault.
The program no longer exists.
(gdb) disp n
(gdb) r
Starting program: .../h1.x

Breakpoint 1, main (argc=1, argv=0xbff1ed74) at h1.c:27
27      A[n = atoi(argv[1])] = (uintptr_t) f2;
1: n = 0

(gdb) p A
$1 = {134518080, 8495092}
(gdb) p /x A
$2 = {0x8049540, 0x819ff4}
(gdb) quit
The program is running.  Exit anyway? (y or n) y


% gdb h1.x
...

(gdb) r
Starting program: .../h1.x

Program received signal SIGSEGV, Segmentation fault.
0x0071addc in ____strtol_l_internal () from /lib/tls/libc.so.6

(gdb) bt
#0  0x0071addc in ____strtol_l_internal () from /lib/tls/libc.so.6
#1  0x0071ab6f in __strtol_internal () from /lib/tls/libc.so.6
#2  0x00718136 in atoi () from /lib/tls/libc.so.6
#3  0x0804840b in main (argc=1, argv=0xbff8cd64) at h1.c:27

(gdb) l h1.c:27
22    int main(/*@unused@*/ int argc, char *argv[])    /*@globals n@*/ /*@modifies n@*/
23    {
24      void (*func)(void) = f1;
25      uintptr_t A[2];
26   
27      A[n = atoi(argv[1])] = (uintptr_t) f2;
28   
29      (*func)();
30   
31      printf("main out\n");

(gdb) b 23
Breakpoint 1 at 0x80483d8: file h1.c, line 23.

(gdb) disp argc
No symbol "argc" in current context.

(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: .../h1.x

Breakpoint 1, main (argc=1, argv=0xbfe8f0b4) at h1.c:23
23    {

(gdb) disp n
1: n = 0

(gdb) disp argc
2: argc = 1

(gdb) disp argv
3: argv = (char **) 0xbfea7024

(gdb) disp argv[]
A syntax error in expression, near `]'.

(gdb) disp argv[0]
4: argv[0] = 0xbff5372c ".../h1.x"

(gdb) disp argv[1]
5: argv[1] = 0x0

(gdb) disp f1
6: f1 = {void (void)} 0x804839c <f1>

(gdb) disp f2
7: f2 = {void (void)} 0x80483ba <f2>

(gdb) disp func
8: func = (void (*)(void)) 0x6ebca0 <_rtld_local_ro>

(gdb) disp /x A
9: /x A = {0x8049540, 0x819ff4}

(gdb) n
24      void (*func)(void) = f1;
9: /x A = {0x8049540, 0x819ff4}
8: func = (void (*)(void)) 0x6ebca0 <_rtld_local_ro>
7: f2 = {void (void)} 0x80483ba <f2>
6: f1 = {void (void)} 0x804839c <f1>
5: argv[1] = 0x0
4: argv[0] = 0xbff5372c ".../h1.x"
3: argv = (char **) 0xbfea7024
2: argc = 1
1: n = 0
(gdb) n
27      A[n = atoi(argv[1])] = (uintptr_t) f2;
9: /x A = {0x8049540, 0x819ff4}
8: func = (void (*)(void)) 0x804839c <f1>
7: f2 = {void (void)} 0x80483ba <f2>
6: f1 = {void (void)} 0x804839c <f1>
5: argv[1] = 0x0
4: argv[0] = 0xbff5372c ".../h1.x"
3: argv = (char **) 0xbfea7024
2: argc = 1
1: n = 0

(gdb) n

Program received signal SIGSEGV, Segmentation fault.
0x0071addc in ____strtol_l_internal () from /lib/tls/libc.so.6
7: f2 = {void (void)} 0x80483ba <f2>
6: f1 = {void (void)} 0x804839c <f1>
1: n = 0

(gdb) n
Single stepping until exit from function ____strtol_l_internal,
which has no line number information.

Program terminated with signal SIGSEGV, Segmentation fault.
The program no longer exists.


(gdb) r 3
Starting program: .../h1.x 3

Breakpoint 1, main (argc=2, argv=0xbfe33094) at h1.c:23
23    {
9: /x A = {0x8049540, 0x819ff4}
8: func = (void (*)(void)) 0x6ebca0 <_rtld_local_ro>
7: f2 = {void (void)} 0x80483ba <f2>
6: f1 = {void (void)} 0x804839c <f1>
5: argv[1] = 0xbff19768 "3"
4: argv[0] = 0xbff1972a ".../h1.x"
3: argv = (char **) 0xbfe33094
2: argc = 2
1: n = 0

(gdb) n
24      void (*func)(void) = f1;
9: /x A = {0x8049540, 0x819ff4}
8: func = (void (*)(void)) 0x6ebca0 <_rtld_local_ro>
7: f2 = {void (void)} 0x80483ba <f2>
6: f1 = {void (void)} 0x804839c <f1>
5: argv[1] = 0xbff19768 "3"
4: argv[0] = 0xbff1972a ".../h1.x"
3: argv = (char **) 0xbfe33094
2: argc = 2
1: n = 0

(gdb) n
27      A[n = atoi(argv[1])] = (uintptr_t) f2;
9: /x A = {0x8049540, 0x819ff4}
8: func = (void (*)(void)) 0x804839c <f1>
7: f2 = {void (void)} 0x80483ba <f2>
6: f1 = {void (void)} 0x804839c <f1>
5: argv[1] = 0xbff19768 "3"
4: argv[0] = 0xbff1972a ".../h1.x"
3: argv = (char **) 0xbfe33094
2: argc = 2
1: n = 0

(gdb) n
29      (*func)();
9: /x A = {0x8049540, 0x819ff4}
8: func = (void (*)(void)) 0x80483ba <f2>
7: f2 = {void (void)} 0x80483ba <f2>
6: f1 = {void (void)} 0x804839c <f1>
5: argv[1] = 0xbff19768 "3"
4: argv[0] = 0xbff1972a ".../h1.x"
3: argv = (char **) 0xbfe33094
2: argc = 2
1: n = 3

(gdb) n
   3  f2 ***
31      printf("main out\n");
9: /x A = {0x8049540, 0x819ff4}
8: func = (void (*)(void)) 0x80483ba <f2>
7: f2 = {void (void)} 0x80483ba <f2>
6: f1 = {void (void)} 0x804839c <f1>
5: argv[1] = 0xbff19768 "3"
4: argv[0] = 0xbff1972a ".../h1.x"
3: argv = (char **) 0xbfe33094
2: argc = 2
1: n = 3

(gdb) where
#0  main (argc=2, argv=0xbfe33094) at h1.c:31
(gdb) info frame
Stack level 0, frame at 0xbfe33010:
 eip = 0x8048425 in main (h1.c:31); saved eip 0x703df3
 source language c.
 Arglist at 0xbfe33008, args: argc=2, argv=0xbfe33094
 Locals at 0xbfe33008, Previous frame's sp is 0xbfe33010
 Saved registers:
  ebp at 0xbfe33008, eip at 0xbfe3300c
(gdb) p f1
$1 = {void (void)} 0x804839c <f1>

(gdb) p f2
$2 = {void (void)} 0x80483ba <f2>

(gdb) p /x A
$8 = {0x8049540, 0x819ff4}

(gdb) p &A
$2 = (uintptr_t (*)[2]) 0xbffbae88

(gdb) p &func
$3 = (void (**)(void)) 0xbffbae94

(gdb) p &A[0]
$4 = (uintptr_t *) 0xbffbae88

(gdb) p &A[1]
$5 = (uintptr_t *) 0xbffbae8c

(gdb) p &A[2]
$6 = (uintptr_t *) 0xbffbae90

(gdb) p &A[3]
$7 = (uintptr_t *) 0xbffbae94

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

%



More stuff, not illustrated



Last revised, 27 Mar. 2013