Using GNU's GDB Debugger

Breakpoints And Watchpoints

By Peter Jay Salzman


Previous: Initialization, Listing, And RunningNext: Stepping And Resuming



Breakpoints, Watchpoints, and Catchpoints

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:

  1. A breakpoint stops your program whenever a particular point in the program is reached. We will discuss breakpoints momentarily.
  2. A watchpoint stops your program whenever the value of a variable or expression changes. We'll discuss watchpoints later on in this chapter.
  3. A catchpoint stops your program whenever a particular event occurs. We won't discuss catchpoints until I get a chance to write about them.


Breakpoints

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:

  1. What does "stopping at line 5" mean?

    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.

  2. Why did gdb stop here?

    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 codeoptimized code
    Breakpoint at line Program pauses at lineBreakpoint set at lineProgram 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.



Breaking

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.:

  1. By function name.
  2. By line number.
  3. By filename and line number.
  4. By address.

By Function Name

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) 

By Line Number

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) 

By Filename And Line Number

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) 

By Address

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.

Breakpoint Numbers

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.

Removing Breakpoints

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.

Exercises

  1. If you've been following along with the tutorial, you shouldn't have any breakpoints set since we deleted them all with clear. Set three breakpoints wherever you like by the methods of your choice. Before you do, guess what their identifiers will be.
  2. Use delete, not clear, to remove only the last breakpoint you set. This will leave you with two remaining breakpoints.
  3. You should have two breakpoints left. delete with no arguments removes all breakpoints. Try it out, then quit GDB.

Enabling, Disabling, And Ignoring

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.

Exercises

  1. The disable command permanently disabled a breakpoint until you explicitly enable it with enable. However, it's possible to temporarily disable a breakpoint. Use GDB's help utility to read about the ignore command, which disables a breakpoint "for n crossings".
  2. Personally, I don't use ignore a whole lot. It seems like conditional breaking makes ignore not very useful, but you should still know of its existence. Hopefully you have GDB still open. Use ignore to disable breakpoint 3 (the one at line 12) for 3 crossings. Verify that it works.

Listing Breakpoints

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:

  1. The Num field gives the identifier.
  2. The Type field gives the type of breakpoint. There are different types of breakpoints, like hardware watchpoints, which we'll cover shortly.
  3. The Disp field (short for disposition) describes what will happen to the breakpoint the next time it's activated (the next time it pauses execution). keep indicates nothing will happen to the breakpoint, however, it's possible to disable or even remove a breakpoint the next time it's reached. These situations are identified by the Disp field.

 



back   Back: Initialization, Listing, And Runningup  Up to the TOCNext: Stepping And Resuming   next
email  Email comments and corrections
printable  Printable version