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).
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