I want to set a watchpoint (break on hardware write) temporarily in my C++ program to find memory corruption.
I've seen all the ways to do it manually through gdb, but I would like to actually set the watchpoint via some method in my code so I don't have to break into gdb, find out the address, set the watchpoint and then continue.
Something like:
#define SET_WATCHPOINT(addr) asm ("set break on hardware write %addr")
5.1 How do I set a write watchpoint for a variable? [top] [toc] Use the watch command. The argument to the watch command is an expression that is evaluated. This implies that the variabel you want to set a watchpoint on must be in the current scope.
Setting watchpoints. You can use a watchpoint to stop execution whenever the value of an expression changes, without having to predict a particular place where this may happen. Depending on your system, watchpoints may be implemented in software or hardware.
Setting breakpoints A breakpoint is like a stop sign in your code -- whenever gdb gets to a breakpoint it halts execution of your program and allows you to examine it. To set breakpoints, type "break [filename]:[linenumber]".
Set hardware watchpoint from child process.
#include <signal.h> #include <syscall.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <stddef.h> #include <sys/ptrace.h> #include <sys/types.h> #include <sys/wait.h> #include <linux/user.h> enum { DR7_BREAK_ON_EXEC = 0, DR7_BREAK_ON_WRITE = 1, DR7_BREAK_ON_RW = 3, }; enum { DR7_LEN_1 = 0, DR7_LEN_2 = 1, DR7_LEN_4 = 3, }; typedef struct { char l0:1; char g0:1; char l1:1; char g1:1; char l2:1; char g2:1; char l3:1; char g3:1; char le:1; char ge:1; char pad1:3; char gd:1; char pad2:2; char rw0:2; char len0:2; char rw1:2; char len1:2; char rw2:2; char len2:2; char rw3:2; char len3:2; } dr7_t; typedef void sighandler_t(int, siginfo_t*, void*); int watchpoint(void* addr, sighandler_t handler) { pid_t child; pid_t parent = getpid(); struct sigaction trap_action; int child_stat = 0; sigaction(SIGTRAP, NULL, &trap_action); trap_action.sa_sigaction = handler; trap_action.sa_flags = SA_SIGINFO | SA_RESTART | SA_NODEFER; sigaction(SIGTRAP, &trap_action, NULL); if ((child = fork()) == 0) { int retval = EXIT_SUCCESS; dr7_t dr7 = {0}; dr7.l0 = 1; dr7.rw0 = DR7_BREAK_ON_WRITE; dr7.len0 = DR7_LEN_4; if (ptrace(PTRACE_ATTACH, parent, NULL, NULL)) { exit(EXIT_FAILURE); } sleep(1); if (ptrace(PTRACE_POKEUSER, parent, offsetof(struct user, u_debugreg[0]), addr)) { retval = EXIT_FAILURE; } if (ptrace(PTRACE_POKEUSER, parent, offsetof(struct user, u_debugreg[7]), dr7)) { retval = EXIT_FAILURE; } if (ptrace(PTRACE_DETACH, parent, NULL, NULL)) { retval = EXIT_FAILURE; } exit(retval); } waitpid(child, &child_stat, 0); if (WEXITSTATUS(child_stat)) { printf("child exit !0\n"); return 1; } return 0; } int var; void trap(int sig, siginfo_t* info, void* context) { printf("new value: %d\n", var); } int main(int argc, char * argv[]) { int i; printf("init value: %d\n", var); watchpoint(&var, trap); for (i = 0; i < 100; i++) { var++; sleep(1); } return 0; }
Based on user512106's great answer, I coded up a little "library" that someone might find useful:
It's on github at https://github.com/whh8b/hwbp_lib. I wish I could have commented directly on his answer, but I don't have enough rep yet.
Based on feedback from the community, I am going to copy/paste the relevant code here:
#include <stdio.h> #include <stddef.h> #include <signal.h> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> #include <sys/ptrace.h> #include <sys/user.h> #include <sys/prctl.h> #include <stdint.h> #include <errno.h> #include <stdbool.h> extern int errno; enum { BREAK_EXEC = 0x0, BREAK_WRITE = 0x1, BREAK_READWRITE = 0x3, }; enum { BREAK_ONE = 0x0, BREAK_TWO = 0x1, BREAK_FOUR = 0x3, BREAK_EIGHT = 0x2, }; #define ENABLE_BREAKPOINT(x) (0x1<<(x*2)) #define ENABLE_BREAK_EXEC(x) (BREAK_EXEC<<(16+(x*4))) #define ENABLE_BREAK_WRITE(x) (BREAK_WRITE<<(16+(x*4))) #define ENABLE_BREAK_READWRITE(x) (BREAK_READWRITE<<(16+(x*4))) /* * This function fork()s a child that will use * ptrace to set a hardware breakpoint for * memory r/w at _addr_. When the breakpoint is * hit, then _handler_ is invoked in a signal- * handling context. */ bool install_breakpoint(void *addr, int bpno, void (*handler)(int)) { pid_t child = 0; uint32_t enable_breakpoint = ENABLE_BREAKPOINT(bpno); uint32_t enable_breakwrite = ENABLE_BREAK_WRITE(bpno); pid_t parent = getpid(); int child_status = 0; if (!(child = fork())) { int parent_status = 0; if (ptrace(PTRACE_ATTACH, parent, NULL, NULL)) _exit(1); while (!WIFSTOPPED(parent_status)) waitpid(parent, &parent_status, 0); /* * set the breakpoint address. */ if (ptrace(PTRACE_POKEUSER, parent, offsetof(struct user, u_debugreg[bpno]), addr)) _exit(1); /* * set parameters for when the breakpoint should be triggered. */ if (ptrace(PTRACE_POKEUSER, parent, offsetof(struct user, u_debugreg[7]), enable_breakwrite | enable_breakpoint)) _exit(1); if (ptrace(PTRACE_DETACH, parent, NULL, NULL)) _exit(1); _exit(0); } waitpid(child, &child_status, 0); signal(SIGTRAP, handler); if (WIFEXITED(child_status) && !WEXITSTATUS(child_status)) return true; return false; } /* * This function will disable a breakpoint by * invoking install_breakpoint is a 0x0 _addr_ * and no handler function. See comments above * for implementation details. */ bool disable_breakpoint(int bpno) { return install_breakpoint(0x0, bpno, NULL); } /* * Example of how to use this /library/. */ int handled = 0; void handle(int s) { handled = 1; return; } int main(int argc, char **argv) { int a = 0; if (!install_breakpoint(&a, 0, handle)) printf("failed to set the breakpoint!\n"); a = 1; printf("handled: %d\n", handled); if (!disable_breakpoint(0)) printf("failed to disable the breakpoint!\n"); return 1; }
I hope that this helps someone!
Will
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With