Skip to content

Instantly share code, notes, and snippets.

@d4em0n
Forked from jorendorff/watchpoint.cpp
Created July 30, 2018 08:47
Show Gist options
  • Select an option

  • Save d4em0n/8876ef16036ef8e1eb4a371eb4ed1821 to your computer and use it in GitHub Desktop.

Select an option

Save d4em0n/8876ef16036ef8e1eb4a371eb4ed1821 to your computer and use it in GitHub Desktop.

Revisions

  1. @jorendorff jorendorff created this gist Aug 18, 2014.
    178 changes: 178 additions & 0 deletions watchpoint.cpp
    Original file line number Diff line number Diff line change
    @@ -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;
    }