Previous: Debugging With Your Brain | Next: Breakpoints And Watchpoints |
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:
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.
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.
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.
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)
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.
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)
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):
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.
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.
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.
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.
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.
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.
Back: Debugging With Your Brain | Up to the TOC | Next: Breakpoints And Watchpoints |
Email comments and corrections | ||
Printable version |