Search code examples
clinuxassemblyx86ptrace

Writing the simplest assembly debugger


Let's say I have the following assembly code which I'd like to single-step through:

.globl _start
_start:
    nop
    mov $60, %eax
    syscall

What would be the simplest way I could attach a ptrace to this to run this with single-stepping? I usually do this in gdb but curious how to manually do this in the crudest way possible (with no error handling or anything except the above case) to see what occurs behind the scenes. Any language is fine (assembly might be the best though).


Solution

  • For simplicity, I added an int3 which triggers a breakpoint trap. In real usage, you'd want to trace the exec call and put a software or hardware breakpoint at the entry address you parsed out of the ELF header. I have assembled the target program into a.out and it looks like:

    00000000004000d4 <_start>:
      4000d4:   cc                      int3   
      4000d5:   90                      nop
      4000d6:   b8 3c 00 00 00          mov    $0x3c,%eax
      4000db:   0f 05                   syscall 
    

    A simple program demonstrating single stepping:

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    #include <sys/ptrace.h>
    #include <sys/user.h>
    
    int main() {
        int pid;
        int status;
        if ((pid = fork()) == 0) {
            ptrace(PTRACE_TRACEME, 0, NULL, NULL);
            execl("./a.out", "a.out", NULL);
        }
        printf("child: %d\n", pid);
        waitpid(pid, &status, __WALL);
        ptrace(PTRACE_CONT, pid, NULL, NULL);
        while(1) {
            unsigned long rip;
            waitpid(pid, &status, __WALL);
            if (WIFEXITED(status)) return 0;
            rip = ptrace(PTRACE_PEEKUSER, pid, 16*8, 0);    // RIP is the 16th register in the PEEKUSER layout
            printf("RIP: %016lx opcode: %02x\n", rip, (unsigned char)ptrace(PTRACE_PEEKTEXT, pid, rip, NULL));
            ptrace(PTRACE_SINGLESTEP, pid, NULL, NULL);
        }
    }
    

    Sample output:

    $ ./singlestep 
    child: 31254
    RIP: 00000000004000d5 opcode: 90
    RIP: 00000000004000d6 opcode: b8
    RIP: 00000000004000db opcode: 0f