|
|
@@ -0,0 +1,178 @@ |
|
|
// 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; |
|
|
} |