Previous: Initialization, Listing, And Running | Next: Stepping And Resuming |
So far you know how to list source code and run a program from within gdb. But you already knew how to do that without gdb. What else does gdb give us? To do anything really useful with gdb, you need to set breakpoints which temporarily pause your program's execution so you can do useful debugging work like inspecting variables and watching the program's execution in an atomic line-by-line fashion. This right here is the magic of a symbolic debugger.
Breakpoints come in three flavors:
A breakpoint stops your program whenever a particular place in the program is reached. Here are some examples of what a breakpoint does:
All those requests have one thing in common: they ask gdb to stop based on reaching some location within the program. That's what a breakpoint does. There are two things I'd like to mention before we start:
When gdb stops at "line 5", this means that gdb is currently waiting "between" lines 4 and 5. Line 5 hasn't executed yet. Keep this in mind! You can execute line 5 with the next command, but line 5 has not happened yet.
Sometimes you may be surprised at where gdb stops. You may have specified a breakpoint at line 5 of the source code, but gdb could stop at line 7, for instance. This can happen for 2 reasons. First, if you compile a program with optimization set, some lines of source code may be optimized out of existence; they exist in your source code, but not in the executable. Secondly, not every line of source code gets compiled into machine code instruction. See the section on "until" (FIXME: when I write it). Consider the code below:
1 #include <stdio.h> 2 3 int main( void ) 4 { 5 int i; 6 i = 3; 7 8 return 0; 9 }
Inserting a breakpoint at line X makes your program pause at line Y...
unoptimized code | optimized code | ||
Breakpoint at line | Program pauses at line | Breakpoint set at line | Program pauses at line |
1--4, main() | 4 | 1--4, main() | 4 |
5, 6 | 6 | 5--9 | 9 |
7, 8 | 8 | ||
9 | 9 |
Each breakpoint, watchpoint, and catchpoint you set is assigned a number starting with 1. You use this number to refer to that breakpoint. To see the list of all breakpoints and watchpoints you've set, type info breakpoints (which can be abbreviated by i b. I show a sample resulting output:
(gdb) info breakpoints Num Type Disp Enb Address What 1 breakpoint keep y 0x080483f6 in main at try5.c:4 breakpoint already hit 1 time 2 breakpoint keep n 0x0804841a in display at try5.c:14 breakpoint already hit 1 time 3 hw watchpoint keep y i
According to the output, there are two breakpoints, one at line 4 and the other at line 14 of the source code. They are assigned to numbers 1 and 2 respectively. There is also a watchpoint set: the program will halt whenever the variable i (local to display()) changes value.
In addition to being assigned a number, each breakpoint and watchpoint can be enabled or disabled. A program's execution won't stop at a disabled breakpoint or watchpoint. By default, when you create a new breakpoint or watchpoint, it's enabled. To disable the breakpoint or watchpoint assigned to number n, type:
disable n
To re-enable this breakpoint or watchpoint, type:
enable n
If you look at the sample output of info breakpoints above, you'll see that breakpoint 2 has been disabled.
Compile and run the fgets program, which is a multi-filed program (the files are fgets.c, fgets.h, and main.c:
$ gcc -c -g -W -Wall fgets.c main.c $ gcc -o fgets fgets.o main.o
Note that the compiler generated a warning. That's because we used -W -Wall which instructs gcc to tell us when it sees what it thinks might be a common programming error. The best way to debug your program is to not put the bugs in the program to begin with. You should always use these gcc bug finding options. Let me be blunt here, and I hope I don't offend anyone. It's stupid not to use -W -Wall when you compile code. Plain and simple. Stupid. With a capital S. Most people don't use them, even people who are clearly better programmers than me. That's because even smart people can do dumb things. Don't you be dumb. Always use -W -Wall.
The program is a password guessing program. Take a moment to look through the code to see how it works. The program is
ultra-simple so we can focus on learning GDB rather than trying to figure out complicated code like linked lists and whatnot.
You should be able to deduce how the program works (and what the password is) in under a few seconds. Now run the code and
notice it simply doesn't work. We'll first concentrate on learning how to set breakpoints, and then we'll debug the program.
Setting Basic Breakpoints
There are four major ways to set a breakpoint, in roughly the order that I personally use them.:
We've already seen the most common way of setting a brekpoint: with the function name.
$ gdb fgets Using host libthread_db library "/lib/tls/libthread_db.so.1". (gdb) break main Breakpoint 1 at 0x8048464: file main.c, line 6. (gdb)
The "break main" command sets a breakpoint at the top of main(), which happens to be line 6 of main.c. If we now run the program, the program will stop at line 6. Recall from the previous discussion that this means that GDB will be sitting between lines 5 and 6. Line 6 will not have executed until we issue the step command:
(gdb) run Starting program: code/fgets/fgets Breakpoint 1, main () at main.c:6 6 char *word = "password"; (gdb)
A second way of setting breakpoints is with a line number. The line number refers to the file GDB is currently in. Right now, we're in main.c, so line numbers are with respect to that file for now. Let's set a breakpoint at line 9, where the printf() statement is.
(gdb) break 9 Breakpoint 2 at 0x804846b: file main.c, line 9. (gdb)
GDB has a continue command which we haven't seen yet. Once GDB pauses due to a breakpoint, the continue command will resume execution. Use continue to make sure that GDB pauses at line 9:
(gdb) continue Continuing. Breakpoint 2, main () at main.c:9 9 printf("I'm thinking of a word. Let's see if you can guess it.\n"); (gdb)
A third way of setting breakpoints is with a filename and line number, separated with a colon. Let's set a breakpoint at line 10 of fgets.c:
(gdb) break fgets.c:10 Breakpoint 3 at 0x80483fd: file fgets.c, line 10. (gdb)
A fourth way of setting breakpoints is with a memory address within the process's VM space. I'll find the address of TakeGuess() and set a breakpoint at that address:
(gdb) print TakeGuess $1 = {int (const char *)} 0x80483f4 <TakeGuess> (gdb) break *0x80483f4 Breakpoint 4 at 0x80483f4: file fgets.c, line 7.
You might have noticed that each breakpoint is given an integer identifier. For example, we've set 4 breakpoints already, and the last one we set (by address) was assigned the number 4. If you haven't noticed this, go back and take a look. Various operations can be performed on a breakpoint, like removing them. You can perform an operation on a particular breakpoint by referring to its integer identifier.
Just as you can set breakpoints, you can also remove them. There are numerous ways to remove a breakpoint:
So let's use clear to remove the four breakpoints the way we set them; kind of like "undoing" what we did:
(gdb) clear *0x80483f4 Deleted breakpoint 4 (gdb) clear fgets.c:10 Deleted breakpoint 3 (gdb) clear 9 Deleted breakpoint 2 (gdb) clear main Deleted breakpoint 1 (gdb)
The delete command deletes breakpoints by identifier, as opposed to clear which removes breakpoints based on their location. In fact, delete n deletes the breakpoint with identifier n. We investigate this command more fully in the exercises.
Once set, there are only two ways to get rid of a breakpoint: remove it or quit GDB. GDB will continually break at the breakpoint. However, you'll sometimes find it useful to temporarily disable a breakpoint, that is, you do not want GDB to break at the breakpoint, but you want to keep the breakpoint there in case you need to debug that section of code again.
Breakpoints can be enabled and disabled. Simply put, your program will pause at an enabled breakpoint, but it will not pause at a disabled breakpoint.
You can enable or disable breakpoints using the enable and disable commands which take an argument of the breakpoint identifier for the breakpoint you want to enable or disable. Let's take a look at this using the fgets program that we previously used. Start a debugging session of fgets and place two breakpoints at lines 6, 9, and 12 of main.c:
$ gdb fgets (gdb) break 6 Breakpoint 1 at 0x8048464: file main.c, line 6. (gdb) break 9 Breakpoint 2 at 0x804846b: file main.c, line 9. (gdb) break 12 Breakpoint 3 at 0x8048477: file main.c, line 12.
Disable breakpoint 2, run the program, and use continue to verify that breakpoint 2 does not pause execution.
(gdb) disable 2 (gdb) run Starting program: code/fgets/fgets Breakpoint 1, main () at main.c:6 6 char *word = "password"; (gdb) continue Continuing. I'm thinking of a word. Let's see if you can guess it. Breakpoint 3, main () at main.c:12 12 while ( KeepGoing )
Confirmed, breakpoint 2 is disabled. Finally, enable breakpoint 2 and rerun the program. Use continue to verify that breakpoint 2 now pauses execution:
(gdb) enable 2 (gdb) run The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /www/p/linux/gdb/code/fgets/fgets Breakpoint 1, main () at main.c:6 6 char *word = "password"; (gdb) continue Continuing. Breakpoint 2, main () at main.c:9 9 printf("I'm thinking of a word. Let's see if you can guess it.\n");
Confirmed, once enabled, breakpoint 2 again pauses execution.
So far, we've seen three commands that take a breakpoint's identifier as an argument: delete, enable, and disable. There are many others, which we'll cover later. The point is, breakpoint identifiers are useful, and you'll find yourself using them quite a bit. But how do you remember the identifiers for your breakpoints, or even where your breakpoints were set to begin with? There's a command, info breakpoints which lists all your breakpoints, their identifiers, and lots more information. Hopefully, GDB is still open from the previous subsection, so check it out:
(gdb) info breakpoints Num Type Disp Enb Address What 1 breakpoint keep y 0x08048464 in main at main.c:6 breakpoint already hit 1 time 2 breakpoint keep y 0x0804846b in main at main.c:9 breakpoint already hit 1 time 3 breakpoint keep y 0x08048477 in main at main.c:12
This is a very important command, and I find myself using it all the time. It should be completely self explanatory except for a couple of things:
![]() | ![]() | Next: Stepping And Resuming ![]() |
![]() | ||
![]() |