Skip to content

Instantly share code, notes, and snippets.

@jorendorff
Created August 18, 2014 19:29
Show Gist options
  • Save jorendorff/cd18d77bd1cf1d2b40af to your computer and use it in GitHub Desktop.
Save jorendorff/cd18d77bd1cf1d2b40af to your computer and use it in GitHub Desktop.
// 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