Vladimir Prus


Wednesday, May 31, 2006

Debugger stories: Watchpoints

One of my faviourite debugger changes in KDevelop 3.4 is proper watchpoint handling. Before explaining it, some introduction is due.

Say you're debugging and see that the 'foo' field of 'pointer_to_some_data' is completely bogus. You are sure that it was valid some time ago, like when its containing object was constructed, so the question is where the corruption happened. That's exactly what watchpoints are for. You set breakpoint at a code where 'foo' is known to be valid, and then ask the debugger to stop whenever the value of 'foo' changes. The debugger in turn writes the address of 'foo' to a special processor register, and processor will call back the operating system, and then debugger, when 'foo' changes.

Except that GDB does not work this way by default. If you say:

watch pointer_to_some_data->foo
there are two interpretations. First is to stop when memory location referred to by pointer_to_some_data->foo is modified. Second is to stop when the value of the expression pointer_to_some_data->foo changes, which can happen also if pointer_to_some_data changes. Obviously, when debugging memory corruption, you care about memory address, and pointer_to_some_data is just a way to specify the memory address. Alas, by default GDB uses the second interpretation, so to set watchpoint on address you should use:
print &(pointer_to_some_data->foo)
watch *$

But the problem is not just that you'll get false hits when pointer_to_some_data changes. The thing is that if that variable is a local one, or a function parameter, then GDB will immediately remove watchpoint when you exit the containing function. So, for KDevelop user it will be like that: you pick a local variable in a variables widget, you expand it, right-click on some member, select "Toggle watchpoint", and continue. The watchpoint you've just added immediately goes away.

KDevelop 3.4 solves this problem in a radical way. All watchpoints are address watchpoints. For any expression you enter, address is computed and watchpoint is set on address. Expression without address (rvalue) can't be watched and you'll get an error message if adding watchpoints for rvalue. Additionally, when the application exits, all watchpoints are disabled, because data addresses can well be different on the next run. When user decides to enable a watchpoint, the address of expression is evaluated again, and a new watchpoints is set to that address.

Hopefully this will make watchpoints more usable for the ordinary programmer.

1 comment:

Anonymous said...

Thanks! gdb was driving us nuts trying to break on a change to an address.