Watchpoints - more than watch and continue

Written by Dr Greg Law | Last updated 15th Feb 2021

Hello! Welcome to gdbWatchPoint where we're looking at GDB tips and tricks to make your debugging life easier.

This epsisode of gdbWatchPoint is, somewhat confusingly on watchpoints, which I'm sure most of you will have used, if perhaps in their simplest form. But there's more to watchpoints than simply watch and continue.

A simple program

Let's start at the begining to make sure everyone is up to speed. Here's a very simple little program:

include <stdio.h>
struct st
  int a;
  int b;
struct st foo = {1,2};
struct st bar = {3,4};

  struct st *p= &foo;
  printf("p->a is %d\n", p->a);
  p = &bar;
  printf("p->b is %d\n", p->a);
  return 0;

This has a structure, a couple of instances of the structure and maintains a pointer to that structure. We can then compile in the normal way and load into GDB:

gcc -g watch.c
gdb a.out

Debugging in GDB

In GDB, start debugging with:

(gdb) start


Now let's watch the pointer, p:

(gdb) watch p
Hardware watchpoint 2: p

We'll immediately continue and, because we're watching for changes in p we hit the first line which modifes p: 

(gdb) c


...where we can see that it was set to 0 (zero) but it's new a pointer to foo. If you continue again, we're stopped when p changes (because we're watching it) and p is now a pointer to bar.

UDB Time Travel Debugger
Find and fix bugs in minutes  - even C/C++ concurrency issues
Learn more »

Watching pointers

I'm now going to restart the program by running start in gdb, and then list all watchpoints by running info break. (Do this for yourself of follow along in the video.)

I want to watch not just p but also p->a. I can't do this when I've initially started the program in GDB because p is null, which GDB can't reference. But if I do n (for next) then I can watch p->a:


If I then continue with c then the watchpoint does trigger because even though a within foo hasn't changed, p is now pointing to bar and so p->a has changed from 1 to 3:


Interestingly, the watchpoint won't change when the memory is written, it will change when the memory is changed.

Software watchpoints

You'll have noticed the "Hardware watchpoint" in the output from GDB. Watchpoints preceed hardware support and GDB has a notion of software watchpoints as well.

If we start the program again with start. If I then do:

(gdb) watch foo
Hardware watchpoint 6: foo

... you can see that GDB watches that with a hardware watchpoint, but if this structure was a big array or it was misaligned then GDB would use a software watchpoint.

An easy way to always get a software whatpoint is to watch a register because you can't use hardware watchpoints with registers on x86.

watch $sp

If you do this, you will notice the difference - this is "watchpoint" not "hardware watchpoint":


Watchpoint (that is, software watchpoint) is the default; but hardware watchpoint is almost always what you want.

If I now continue the program, GDB is going to single step one instruction at a time, then GDB is going to observe this and ask "has the watch data changed?". In some situations that might be what you want, but if you're watching data and you go into this single step mode, it probably isn't because it's super slow - 10,000s times slower... 

Location based watchpoints

Let's look now at location based watchpoints. Often with C and C++ programs, I'm looking for memory corruption errors, so some kind of pointer error. What variables are in scope and what they're set to at the time aren't really what I care about. What I care about is a location in memory and who's overwriting it.

Again, let's start gdb. You might also need to delete your watchpoints with delete.

Start, do next and watch:

(gdb) start
(gdb) n
(gdb) watch -l p->a

If I then continue, the watchpoint isn't hit because that doesn't get changed. Let's modify the program to show what happens if it does get changed. Update the code so that after the p = &bar; line your code reads as follows:

p = &bar;

Recompile this, launch GDB again, and let's debug again:

(gdb) start
(gdb) n
(gdb) watch -l foo->a
(gdb) c

When I continue (the last line), GDB stops on line 19:


Event though p->a has not been aftected by that line, we were watching the location and so GDB did stop. (In the video I show how you can do this with older versions of GDB which don't support this way of doing location based watchpoints.)

How all this works

To see how this works, you can look at the assembly using:

(gdb) disas


The hardware will stop the program after the instruction that writes to the memory has completed. The highlighted line above is the write which increments foo.a and the line after is where we stop.

Read watchpoints

Another feature of watchpoints is read watchpoints which are triggered when the location is read (rather than written). To set this for p I would do:

(gdb) rwatch p

(Not all hardware supports this. x86 does.)

That's it for watchpoints. Hope you have good fun with them!

UDB Time Travel Debugger
Step backwards in your program's execution to view registers and memory
Learn more »

Don't miss my next GDB tutorial: sign up to my gdbWatchPoint mailing list below.

Get tutorials straight to your inbox
Become a GDB Power User. Get Greg's debugging tips directly in your inbox every 2 weeks.
    Find and fix bugs in minutes with reverse debugging - fix test failures, concurrency defects in C/C++

Find and fix bugs in minutes with Time Travel Debugging

Learn more