Created
August 18, 2014 19:29
-
-
Save jorendorff/cd18d77bd1cf1d2b40af to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 <errno.h> | |
#include <signal.h> | |
#include <stddef.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <sys/prctl.h> | |
#include <sys/ptrace.h> | |
#include <sys/types.h> | |
#include <sys/user.h> | |
#include <sys/wait.h> | |
#include <syscall.h> | |
#include <unistd.h> | |
#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; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment