I've come up with two answers, one of which actually worked for me.
Use the right tool for the job. gdb is for debugging program flow. valgrind seems much better suited for debugging memory/buffer errors. Running the program with valgrind, I found the bug(s) pretty quickly.
There is actually a way to do this in gdb in theory. In practice it would only be fast enough if
*A
changed relatively rarely. Not the 10k+ times it was changing in this particular program.
Here it is:
set $A = (void ***) &whateveritis
set $B = (void **) 0
set $WPN = 2
set $WP = 0
watch *$A
commands
silent
set $B = *$A
if $WP != 0
delete $WP
set $WP = 0
end
if $B != (void *) 0
watch *$B
commands
silent
if *$B == magicalcorruptedvalue
where
else
continue
end
end
set $WP = $WPN++
end
continue
end
This sets a watchpoint on A
, deletes whatever previous watch existed on the previous *A
(which will eventually be set to B
), then sets the watch for the current *A
. The watch always is looking for magicalcorruptedvalue, although in my case any change of B
at all while it was stored in *A
would have been an error, so I omitted that whole part.
Note that $WPN is the number of the next breakpoint. Beware of it changing due to temporary implicit breakpoints like that created via start
.
This should work, but note that in my case the program was so bogged down that it never made it to the corrupt section, and so the watchpoint for B
was never tripped. YMMV.
Back to the real world where nobody ever does anything this complex with gdb. The lessons here are: (1) Learn what tools are out there. Valgrind is amazing, and I'm totally unsure how any C programmer, myself included, has ever coped without it. (2) Think carefully about the problem domain and its relationship to the tool you're trying to use. If the tool doesn't fit, use or write something else. Don't let language fool you: just because you're "debugging" doesn't mean you should do it with a "debugger."