Watchpoints normally work by configuring debugging functionality embedded in the CPU itself (hardware watchpoints, that is).
Essentially you load the address range to watch into special registers.
You need specialised code for each CPU architecture.
Here's what gdbserver does to set watchpoints on ARM CPUs:
https://github.com/facebookarchive/binutils/blob/a535268b59862077d95f34f1572ac0bce0b428c7/gdb/gdbserver/linux-arm-low.c#L552
Note the call to update_registers_callback()
- gdbserver has to use ptrace
functionality to update the registers in the context of the tracee. If you want the process to watch itself, it can access those registers directly.
Here's how your process would notice its own watchpoint being hit:
it receives a SIGTRAP signal, with detail info indicating the watchpoint.
https://github.com/facebookarchive/binutils/blob/a535268b59862077d95f34f1572ac0bce0b428c7/gdb/gdbserver/linux-arm-low.c#L632
In gdbserver context, the notification again has to be passed over ptrace
. In your own process, you could examine the signal info directly in a handler for SIGTRAP. Code excerpt (for ARM):
/* This must be a hardware breakpoint. */
if (siginfo.si_signo != SIGTRAP
|| (siginfo.si_code & 0xffff) != 0x0004 /* TRAP_HWBKPT */)
return 0;
/* If we are in a positive slot then we're looking at a breakpoint and not
a watchpoint. */
if (siginfo.si_errno >= 0)
return 0;
/* Cache stopped data address for use by arm_stopped_data_address. */
lwp->arch_private->stopped_data_address
= (CORE_ADDR) (uintptr_t) siginfo.si_addr;
On further reading it turns out that the hardware breakpoint "registers" accessed by the tracer by means of ptrace
are not directly the real thing, but actually an abstraction implemented in the Linux kernel.
There's an "hw-breakpoint" framework involved, which abstracts from the CPU specifics. Found this overview:
https://www.kernel.org/doc/ols/2009/ols2009-pages-149-158.pdf
While this gives hope that it could in fact be possible to use the ptrace()
syscall, applied by a process to itself, to install hardware watchpoints in a fairly portable manner, it doesn't work out - see below.
You'd need this ptrace()
request code (which is not documented on the usual man page):
PTRACE_SETHBPREGS
Upon further experimentation:
- a process can't
ptrace()
itself (permission denied)
- a process can call
clone()
, and then one thread can successfully ptrace(PTRACE_SEIZE)
the other
- at this point, you could as well just
fork()
, and have the tracer exec()
gdb to do the watching
- neither x86 nor x86_64 seem to implement PTRACE_SETHBPREGS (as of kernel 4.15)
- aarch64 does, but I only managed to raise some bus errors so far
So I guess using ptrace()
can be ruled out as an option to set hardware watchpoints in any portable way.