Issue 5-14, April 5, 2000

Be Engineering Insights: Watch Your Data!

By John Dance

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 short kNumHexChars = 16;
const char* kGreeting = "Hello World...\n";
const char* kGoodbye = "Goodbye for now\n";

int main()
{
    // line 10
    int oneHexNumber;
    char hexChars[kNumHexChars];
    int anotherHexNumber;

    // line 15
    oneHexNumber = 0xff;
    anotherHexNumber = 0x100;
    char* hello = new char[strlen(kGreeting)];
    char* goodbye = new char[strlen(kGoodbye)];

    // line 20
    strcpy(hello, kGreeting);
    strcpy(goodbye, kGoodbye);
    cerr << hello;

    // line 25
    for (int i = 0; i <= kNumHexChars; i++) {
        hexChars[i] = (i < 10) ? ('0' + i) : ('A' + (i-10));
    }

    // line 30
    cerr << "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 < kNumberHexChars. I overstepped the array and trampled on 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:

  1. While the x86 architecture itself supports four hardware breakpoints, bdb is limited to three watchpoints.

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

  3. 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 Window->Set Watchpoint.

Creative Commons License
Legal Notice
This work is licensed under a Creative Commons Attribution-Non commercial-No Derivative Works 3.0 License.