#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <linux/user.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

void setbp(pid_t pid, void *addr, long* orig)
{
        union {
                long word;
                unsigned char bytes[4];
        }u;
        *orig = u.word = ptrace(PTRACE_PEEKTEXT, pid, addr, NULL);
        u.bytes[0] = 0xcc; // 0xcc is INT3
        ptrace(PTRACE_POKETEXT, pid, addr, u.word);
        printf("set breakpoint (0x%lx) at 0x%lx.\n", 
               u.word, (unsigned long)addr);
}

void unsetbp(pid_t pid, void *addr, long orig)
{
        printf("remove breakpoint at 0x%lx.\n", 
               (unsigned long)addr);
        ptrace(PTRACE_POKETEXT, pid, addr, orig);

        struct user_regs_struct regs;
        ptrace(PTRACE_GETREGS, pid, 0, &regs);

#if __WORDSIZE == 64
        regs.rip = (long)addr;
#else
        regs.eip = (long)addr;
#endif
        ptrace(PTRACE_SETREGS, pid, 0, &regs);

}

void breakpoint(pid_t pid, void* addr)
{
        long orig;

        while(1) {
                setbp(pid, addr, &orig);

                printf("executing...\n");
                ptrace(PTRACE_CONT, pid, 0, 0);

                wait(NULL);
                printf("breakpoint hit. press return to continue\n");
                getchar();
                unsetbp(pid, addr, orig);
                
                // single step to next instruction, so we can set
                // breakpoint again
                ptrace(PTRACE_SINGLESTEP, pid, 0, 0);
                wait(NULL);
        }
        
}

int main(int ac, char *av[])
{
        if(ac == 2) { // test loop, executed by child process.
                int i = 0;
                while(1) {
                        printf ("debugee: %d\n", i++);
                bp_addr:
                        sleep(2);
                }
                return 0;
        }

        int pid;
        switch(pid=fork()) {
        case -1: perror("fork"); break;
        case 0: 
                ptrace(PTRACE_TRACEME, 0, 0, 0);
                // exec myself, but with one argument
                execlp(av[0], av[0], "loop", NULL);
                break;
        default: 
                wait(NULL);
                ptrace(PTRACE_SETOPTIONS, pid, 0, PTRACE_O_TRACEEXEC);
                breakpoint(pid, &&bp_addr); 
                break;
        }
        return 0;
}
