Every successful programmer carries around a bag of tips and techniques. Today, I'll offer a few on the watchpoint.
Ever had the value of a variable go bad on you? Sometime between the initialization or critical assignment of that variable and a subsequent use, the value has changed. So—what technique do you use to figure out how or when it changed?
If you're dealing with a big body of code you didn't write (or wrote a few months ago), you probably first search through the code to find all the uses of that item: where it's used, and especially where it's set. Another technique is to print out the value of that variable at various points in your program. Many programmers perform a binary search by narrowing the printouts until they find the area where the data changed. Both methods can be useful, but they can also waste a lot of time.
A watchpoint is a debugger monitor that you can put on a data address, to act as a "data breakpoint." As soon as the value at that address changes, the debugger stops the running program and reports where it changed. If the hardware doesn't support watchpoints, the debugger must resort to tricks like single stepping the program and checking the value after each step. As you can imagine, this really slows down program execution. However, if the hardware supports watchpoints, the debugger just has to set up the appropriate state and instruct the hardware to do the watching. Then the program can run happily at full speed. This is how bdb works on the x86 architecture.
Here's an example that explains how watchpoints work, based on a piece of nonsense code with a bug in it.
#include <stdio.h> #include <iostream.h> const shortkNumHexChars
= 16; const char*kGreeting
= "Hello World...\n"; const char*kGoodbye
= "Goodbye for now\n"; int main() { // line 10 intoneHexNumber
; charhexChars
[kNumHexChars
]; intanotherHexNumber
; // line 15oneHexNumber
= 0xff;anotherHexNumber
= 0x100; char*hello
= new char[strlen(kGreeting
)]; char*goodbye
= new char[strlen(kGoodbye
)]; // line 20strcpy
(hello
,kGreeting
);strcpy
(goodbye
,kGoodbye
);cerr
<<hello
; // line 25 for (inti
= 0;i
<=kNumHexChars
;i
++) {hexChars
[i
] = (i
< 10) ? ('0' +i
) : ('A' + (i
-10)); } // line 30cerr
<< "Our two numbers are " <<oneHexNumber
<< " and " <<anotherHexNumber
<< "\n";cerr
<<goodbye
; return 0; }
If I run this program, the output is
Hello World... Our two numbers are 71 and 256 Goodbye for now
Wait a minute! The first number should be 255, not 71. What happened to it? Sometime between when its value was assigned (line 16) and when it was used (line 31) the value has changed.
Of course, this example is trivial, but there can often be hundreds or thousands of lines executed between the initialization or assignment and a subsequent usage. Even if this program were thousands of lines long, solving this problem is trivial with a watchpoint. Just use bdb to step to line 17 in the program and select "oneHexNumber" in the variables view. Then choose Data->Add Watchpoint, and continue running the program.
bdb stops with an alert "Watchpoint Hit: Watchpoint 1 at address
0xfd001bf0 written". The pc
in bdb
is at line 29. I look at the state of
the program and find that the variable i
is 16. My loop test should have
been
.
I overstepped the array and trampled on
i
< kNumberHexChars
oneHexNumber
. Now I fix the program and continue on a happy man.
(Actually I don't in this case because there are two other types of bugs
lurking in this code waiting to bite me. Four lines of code must be
changed. Can you find them? Watchpoints don't help in this case—so
what does?
Some things to remember about watchpoints and their current implementation in bdb:
While the x86 architecture itself supports four hardware breakpoints, bdb is limited to three watchpoints.
The range of watchpoints is currently 4 bytes (long word aligned), so if you're watching a char, you'll be stopped for changes to the memory addresses surrounding that char also. Just use the address given in the alert and the memory viewer to find out if your char was changed in these cases.
You can set watchpoints on variables in the variable view, or on addresses in the register window, or on memory locations in the memory viewer, or on manually entered addresses in
-> .