Using GNU's GDB Debugger: Initialization, Listing, And Running

By Peter Jay Salzman


Previous: Debugging With Your BrainNext: Breakpoints And Watchpoints



Where Are We Now?

In the last chapter we learned about an executing process's memory layout which is divided into segments. One important segment is the call stack (or stack), which is a collection of stack frames (or frames). There is one frame for each function call, and the frame holds three important things:

  1. The local variables for the function.
  2. The current address pointer within the function.
  3. The arguments passed to the function.

When a function is called, a new frame is allocated and added to the stack. When the function returns, its frame is returned back to unused stack memory and execution resumes at the address pointed to by the previous function's current address pointer. We can ask GDB to tell us what the stack looks like with the backtrace command. We can also find out which frame GDB's context is in using the frame command. Lastly, we can change GDB's context to the n'th frame using the frame n command.

Executables don't contain references to object (function and variable) names or source code line numbers. It would be painful to debug a program without these things, so to debug a program, we generate an augmented symbol table using gcc's -g option.

Lastly, we briefly learned how to make GDB pause execution using the break command and execute one line of source code using the step command. We'll have much more to say about these commands shortly.



Where Are We Going To Go?

In this chapter, we'll investigate the list command which (surprisingly) lists lines of source code. We'll take an in-depth look at GDB's initialization file .gdbinit. Lastly, we'll look at GDB's run command which executes a program from within GDB.



Basic Listing of Source Code

Download derivative, a program that calculates numerical derivatives, to follow along with the discussion: derivative.tar.bz2. Take a moment to familiarize yourself with the code. Note the use of groovy function pointers.

You can list source code with GDB's list command, abbreviated by l. Run GDB on the executable and use the list command:

$ gdb driver
(gdb) list
12    }
13
14
15
16    int main(int argc, char *argv[])
17    {
18        double x, dx, ans;
19        double Forw, ForwDelta, Cent, CentDelta, Extr, ExtrDelta;
20
21        if (argc != 1) {

By default, GDB always lists 10 lines of source code. When you first issue list, GDB lists 10 lines of source code centred on main(). Subsequent use of list gives the next 10 lines of source code. Try it:

(gdb) list
22        printf("You must supply a value for the derivative location!\n");
23        return EXIT_FAILURE;
24    }
25
26    x   = atol(argv[1]);
27    ans = sin(log(x)) / x;
28
29    printf("%23s%10s%10s%11s%10s%11s\n", "Forward", "error", "Central",
30        "error", "Extrap", "error");
31
(gdb) 

Use list three more times, and you'll see:

     ... output suppressed

45            printf("dx=%e: %.5e %.4f  %.5e %.4f  %.5e %.4f\n",
46                dx, Forw, ForwDelta, Cent, CentDelta, Extr, ExtrDelta);
47        }
48  
49        return 0;
50    }
(gdb) list
Line number 51 out of range; driver.c has 50 lines.
(gdb) 

The second time we used list, only 9 lines were printed, since we reached the end of the file. The final list didn't print any lines. That's because list always prints 10 lines of code after the previously listed lines. There were simply no more lines of code to list.

"list -" works like list, except in reverse. It lists the 10 lines previous to the last listed lines. Since line 50 was the last listed line, list -should print lines 41 through 50:

(gdb) list -
41
42                Extr      = ExtrapolatedDiff(x, dx, &f);
43                ExtrDelta = fabs(Extr - ans);
44
45                printf("dx=%e: %.5e %.4f  %.5e %.4f  %.5e %.4f\n",
46                    dx, Forw, ForwDelta, Cent, CentDelta, Extr, ExtrDelta);
47            }
48
49        return 0;
50    }
(gdb)

If you give list a line number, GDB lists 10 lines centered on that line number:

(gdb) list 13
8
9      double f(double x)
10     {
11          return cos(log(x));
12     }
13
14
15
16     int main(int argc, char *argv[])
17     {
(gdb)

I'm going to suppress the output to conserve space, however I strongly encourage you to follow along with my examples by performing the operations in GDB yourself. Try to imagine what the output looks like before you actually perform the operation.

Other listing operations you'll find useful:

starting with some line number(gdb) list 5,
ending with some line number (gdb) list ,28
between two numbers: (gdb) list 21,25
by function name: (gdb) list f
functions in the other file: (gdb) list CentralDiff
by filename and line number: (gdb) list derivative.c:12
filename and function name: (gdb) list derivative.c:ForwardDiff

list has a "memory" of what file was list used to print source code. We started out by listing lines from driver.c. We then switched to derivative.c by telling GDB to list CentralDiff(). So now, list is in the "context" of derivative.c. Therefore, if we use list by itself again, it'll list lines lines from derivative.c.

(gdb) list
11    }
12
13
14
15     double ExtrapolatedDiff( double x, double dx, double (*f)(double) )
16     {
17         double term1 = 8.0 * ( f(x + dx/4.0) - f(x - dx/4.0) );
18         double term2 = ( f(x + dx/2.0) - f(x - dx/2.0) );
19
20         return (term1 - term2) / (3.0*dx);

But what if we wanted to start listing lines from driver.c again? How do we go back to that file? We simply list anything that lives in driver.c, like a function or line number. All these commands will reset list's command context from derivative.c back to driver.c:

   list main
   list f
   list driver.c:main
   list driver.c:f
   list driver.c:20

And so forth. The rules aren't complicated; you'll get the hang of them after debugging a few multi-file programs.

Listing By Memory Address (advanced)

Every function begins at some memory address. You can find this address with the print function (which we'll cover later). For instance, we'll find the address for main():

(gdb) print *main
$1 = {int (int, char **)} 0x8048647 <main>
(gdb)

So main() lives at 0x8048647. We can use list using memory locations as well; the syntax is very C'ish:

(gdb) list *0x8048647
0x8048647 is in main (driver.c:17).
12     }
13
14
15
16     int main(int argc, char *argv[])
17     {
18          double x, dx, ans;
19          double Forw, ForwDelta, Cent, CentDelta, Extr, ExtrDelta;
20
21          if (argc != 1) {
(gdb)

It stands to reason that 0x8048690 is also somewhere inside of main(). Let's find out:

(gdb) list *0x8048690
0x8048690 is in main (driver.c:26).
21          if (argc != 1) {
22               printf("You must supply a value for the derivative location!\n");
23               return EXIT_FAILURE;
24          }
25
26          x   = atol(argv[1]);
27          ans = sin(log(x)) / x;
28
29          printf("%23s%10s%10s%11s%10s%11s\n", "Forward", "error", "Central",
30               "error", "Extrap", "error");
(gdb)

Exercises

  1. Using list and print *, figure out how many machine instructions are used for this line of code:
    18          double x, dx, ans;
    19          double Forw, ForwDelta, Cent, CentDelta, Extr, ExtrDelta;
    
    Think about this for a second; you'll learn a bit about compilers and machine instructions.

Setting The List Size

GDB lists code in increments of 10 lines. Maybe that's too much. Or maybe that's too little. You can tell GDB to change the listing size with the set command and listsize variable:

(gdb) set listsize 5
(gdb) list main
15
16     int main(int argc, char *argv[])
17     {
18          double x, dx, ans;
19          double Forw, ForwDelta, Cent, CentDelta, Extr, ExtrDelta;
(gdb)

Exercises

  1. There's actually a lot of things you can set. Issue help set from GDB's prompt. I'm not expecting you to read it all---I just want you to marvel at how big the list is!


The .gdbinit File

Upon startup, GDB reads and executes an initialization file named .gdbinit. It can contain any command (eg set and break), and more. For example, "set listsize" and "set prompt" can go into .gdbinit. There are two locations where GDB will look for this file (in order):

  1. In your home directory
  2. In the current directory

You can put commands to be executed for all your programming projects in $HOME/.gdbinit and project-specific commands in $PWD/.gdbinit.

You can comment your .gdbinit files with bash's "#". And blank lines, of course, are ignored.

Exercises

  1. When you invoke GDB, it prints a copyright notice. Using GDB's man page, figure out how to prevent GDB from printing this notice. Using your shell's alias feature, make an alias for "gdb" that invokes GDB, but surpresses the copyright notice. I use this alias myself.
  2. Figure out how to reset GDB's prompt from (gdb) to something that tickles your fancy. Google would be a great way of figuring this out. GDB's help utility would also be useful (hint: you want to "set" the prompt to something else). Modify .gdbinit so that GDB uses your chosen prompt on startup.
  3. You can even use terminal escape codes to put color in your GDB prompt! If you don't know about terminal color escape codes, you can read about them here. One caveat: You have to use the octal code \033 for the escape character. So for example, bold blue would be \033[01;34m. And then don't forget to turn the blue off, otherwise everything will be blue. I'll let you figure out how to do that yourself! Thanks to Jeff Terrell for pointing this out to me!

A Caveat for Colored GDB Prompts

Thanks Eric Rannaud

According to the GDB documentation binutils-gdb/readline/doc/rltech.texi that comes with the GDB source code:

Applications may indicate that the prompt contains characters that take up no physical screen space when displayed by bracketing a sequence of such characters with the special markers @code{RL_PROMPT_START_IGNORE} and @code{RL_PROMPT_END_IGNORE} (declared in @file{readline.h}. This may be used to embed terminal-specific escape sequences in prompts.
PROMPT_START_IGNORE is defined to \001
PROMPT_END_IGNORE is defined to \002

Without these characters, at least with xterm, the cursor is not always in the right place when using backspace, as gdb includes the terminal escape codes in its computation of the visible length of the prompt, which is wrong. With a simple colored "(gdb) " prompt, gdb thinks its length is 17, instead of 6.

The fix is simple:

set prompt \001\033[1;32m\002(gdb)\001\033[0m\002\040

The \040 is just a space -- better to escape it than to have to copy and paste a trailing blank space.

gdbinit on MS Windows

Thanks Ted Alves

The environment variable HOME, used by .gdbinit, is not normally defined in Windows. You must set/define it yourself: (you must be logged in as an Administrator to change the system variables) right-click My Computer, left-click Properties, left-click the Advanced tab, left-click the Environment Variables button. Now add New: HOME: "(your chosen path) c:\documents and settings\username" (without the double-quotes) and save it by clicking OK. Type "set" after rebooting to verify that it's there. You can also type "set HOME=(your chosen path)" to set it before rebooting, but this method isn't permanent.

Windows Explorer will not accept (create) a file named ".gdbinit" but Windows itself has no problem with it. Create a file named something like: gdb.init in your %HOME% directory, then go to command-line and type "move gdb.init .gdbinit". This will create the file and Explorer will now work with it. You might want to copy this (empty) file to your intended working directory(ies) before you edit in your commands for the HOME file.



Running A Program In GDB

Let's properly introduce the run command. Download and compile arguments.tar.bz2.

The run command with no arguments runs your program without command line arguments. If you want to give the program arguments, use the run command with whatever arguments you want to pass to the program:

   $ gdb arguments
   (gdb) run 1 2
   Starting program: try2 1 2
   Argument 0: arguments
   Argument 1: 1
   Argument 2: 2
   
   Program exited normally.
   (gdb)

Nothing could be simpler. From now on, whenever you use run again, it'll automatically use the arguments you just used (ie, "1 2"):

   (gdb) run
   Starting program: arguments 1 2
   Argument 0: arguments
   Argument 1: 1
   Argument 2: 2
   
   Program exited normally.
   (gdb)

until you tell it to use different arguments:

   (gdb) run testing one two three
   Starting program: arguments testing one two three
   Argument 0: testing
   Argument 1: one
   Argument 2: two
   Argument 3: three
   
   Program exited normally.
   (gdb)

Suppose you want to run the program without command line arguments? How do you get run to stop automatically passing them? There's a "set args" command. If you give this command without any parameters, run will no longer automatically pass command line arguments to the program:

   (gdb) set args
   (gdb) run
   Starting program: arguments 
   Argument 0: try2
   
   Program exited normally.
   (gdb)

If you do give an argument to set args, those arguments will be passed to the program the next time you use run, just as if you had given those arguments directly to run.

There's one more use for set args. If you intend on passing the same arguments to a program every time you begin a debugging session, you can put it in your .gdbinit file. This will make run pass your arguments to the program without you having to specify them every time you start GDB on a given project.



Restarting A Program In GDB

Sometimes you'll want to re-start a program in GDB from the beginning. One reason why you'd want to do this is if you find that the breakpoint you set is too late in the program execution and you want to set the breakpoint earlier. There are three ways of restarting a program in GDB.

  1. Quit GDB and start over.
  2. Use the kill command to stop the program, and run to restart it.
  3. Use the GDB command run. GDB will tell you the program is already running and ask if you want to re-run the program from the beginning.

The last two options will leave everything intact: breakpoints, watchpoints, commands, convenience variables, etc. However, if you don't mind starting fresh with nothing saved from your previous debugging session, quitting GDB is certainly an option.

You might be wondering why there's a kill command when you can either quit GDB with quit or re-run the program with run. The kill command seems kind of superfluous. There are some reasons why you'd use this command, and you can read about them here. Thanks to Suresh Babu for pointing out that the kill command may also be useful when you are remote debugging or if a process is debugged via the attach command. That said, I've never used kill myself.