// This source code comes from: // http://stackoverflow.com/questions/8941711/is-is-possible-to-set-a-gdb-watchpoint-programatically // with additional tricks from: // https://code.google.com/p/google-breakpad/source/browse/trunk/src/client/linux/handler/exception_handler.cc?r=1361 #include #include #include #include #include #include #include #include #include #include #include #include #include #define printf_stderr printf 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 sigactionhandler_t(int, siginfo_t*, void*); bool set_watchpoint(void* addr, sigactionhandler_t handler) { pid_t child = fork(); if (child == -1) { printf_stderr("set_watchpoint -> fork -> error %d (%s)\n", errno, strerror(errno)); return false; } if (child == 0) { // Child process. // Attach to the parent process for debugging. // // On some kernels, the parent must explicitly enable ptrace, but it // needs to know the pid of the child process before it can do this. // Therefore there is a race condition between the prctl() call below // and the first PTRACE_ATTACH here that could cause ptrace to fail. // If it does, keep trying for up to a few seconds. pid_t parent = getppid(); const int WAIT_LIMIT = 1000; const int WAIT_INCREMENT_USEC = 10 * 1000; // 1/100 of a second int i; for (i = 0; i < WAIT_LIMIT; i++) { if (ptrace(PTRACE_ATTACH, parent, NULL, NULL) == 0) break; usleep(WAIT_INCREMENT_USEC); } if (i == WAIT_LIMIT) exit(EXIT_FAILURE); errno = 0; // The design of PTRACE_ATTACH is such that the parent likely hasn't // stopped yet. But a minimal sleep is often sufficient to make the // first PTRACE_POKEUSER call below succeed. usleep(1); // Start configuring debug registers. If the first call doesn't // succeed, most likely the parent just hasn't stopped yet, so wait a // few milliseconds and try again. for (i = 0; i < WAIT_LIMIT; i++) { if (ptrace(PTRACE_POKEUSER, parent, offsetof(struct user, u_debugreg[0]), addr) == 0) break; usleep(WAIT_INCREMENT_USEC); } if (i == WAIT_LIMIT) exit(EXIT_FAILURE); errno = 0; printf("Attached! Waited %d msec\n", 10 * i); dr7_t dr7 = {0}; dr7.l0 = 1; dr7.rw0 = DR7_BREAK_ON_WRITE; dr7.len0 = DR7_LEN_4; if (ptrace(PTRACE_POKEUSER, parent, offsetof(struct user, u_debugreg[7]), dr7)) exit(EXIT_FAILURE); if (ptrace(PTRACE_DETACH, parent, NULL, NULL)) exit(EXIT_FAILURE); exit(EXIT_SUCCESS); } // Parent process. struct sigaction trap_action; if (sigaction(SIGTRAP, NULL, &trap_action) == -1) { printf_stderr("set_watchpoint -> sigaction get -> error %d (%s)\n", errno, strerror(errno)); return false; } trap_action.sa_sigaction = handler; trap_action.sa_flags = SA_SIGINFO | SA_RESTART | SA_NODEFER; if (sigaction(SIGTRAP, &trap_action, NULL) == -1) { printf_stderr("set_watchpoint -> sigaction set -> error %d (%s)\n", errno, strerror(errno)); return false; } // Ignore errors here. prctl(PR_SET_PTRACER, child, 0, 0, 0); errno = 0; int child_stat = 0; if (waitpid(child, &child_stat, 0) == -1) { printf_stderr("set_watchpoint -> waitpid -> error %d (%s)", errno, strerror(errno)); return false; } if (WEXITSTATUS(child_stat)) { printf_stderr("set_watchpoint -> child exit code %d\n", child_stat); errno = ECHILD; return false; } return true; } 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); if (!set_watchpoint(&var, trap)) { perror("watchpoint"); return 1; } for (i = 0; i < 4; i++) { var++; usleep(100000); } return 0; }