Skip to content

Instantly share code, notes, and snippets.

@ksurent
Last active February 2, 2020 22:36
Show Gist options
  • Save ksurent/35c673edd1eb8dae9a3db4f7e0368742 to your computer and use it in GitHub Desktop.
Save ksurent/35c673edd1eb8dae9a3db4f7e0368742 to your computer and use it in GitHub Desktop.
A toy strace clone for my p2p study group
package main
// heavily inspired by https://blog.nelhage.com/2010/08/write-yourself-an-strace-in-70-lines-of-code/
import (
"errors"
"fmt"
"log"
"os"
"strconv"
"syscall"
)
func main() {
if len(os.Args) != 2 {
log.Fatal("Need PID")
}
pid, err := strconv.Atoi(os.Args[1])
if err != nil {
log.Fatal("pid: ", err)
}
if err := syscall.PtraceAttach(pid); err != nil {
log.Fatal("PTRACE_ATTACH: ", err)
}
defer func() {
if err := syscall.PtraceDetach(pid); err != nil {
log.Fatal("PTRACE_DETACH: ", err)
}
log.Println("Detached")
}()
log.Println("Attached")
if err := syscall.PtraceSetOptions(pid, syscall.PTRACE_O_TRACESYSGOOD); err != nil {
log.Fatal("PTRACE_SETOPTIONS: ", err)
}
log.Println("PTRACE_O_TRACESYSGOOD set")
var (
ws syscall.WaitStatus
ru syscall.Rusage
)
log.Println("Waiting for SIGSTOP")
for {
if _, err := syscall.Wait4(pid, &ws, 0, &ru); err != nil {
log.Fatal("wait4: ", err)
}
if ws.Stopped() && ws.StopSignal() == syscall.SIGSTOP {
log.Println("Stopped")
break
}
}
// at this point we know that the tracee has stopped
var regs syscall.PtraceRegs
for {
// keep executing until tracee enters a syscall
if err := waitForSyscall(pid); err != nil {
log.Println(err)
break
}
// tracee has entered the syscall and got suspended
if err := syscall.PtraceGetRegs(pid, &regs); err != nil {
log.Println("PTRACE_GETREGS: ", err)
break
}
n := regs.Orig_rax
name, arity := syscallNameAndArity(n)
args := syscallArgs(regs, arity)
// keep executing until tracee is about to leave the syscall
if err := waitForSyscall(pid); err != nil {
log.Println(err)
break
}
// tracee is just about to leave the syscall and execution is again
// suspended
if err := syscall.PtraceGetRegs(pid, &regs); err != nil {
log.Println("PTRACE_GETREGS: ", err)
break
}
fmt.Printf("%s(%v) = %d\n", name, args, regs.Rax)
}
}
var errExited = errors.New("tracee exited")
func waitForSyscall(pid int) error {
var (
ws syscall.WaitStatus
ru syscall.Rusage
)
for {
if err := syscall.PtraceSyscall(pid, 0); err != nil {
return fmt.Errorf("PTRACE_SYSCALL: %s", err)
}
if _, err := syscall.Wait4(pid, &ws, 0, &ru); err != nil {
return fmt.Errorf("wait4: %s", err)
}
//log.Println("Stopped via ", ws.StopSignal()&^0x80)
// ignore SIGTRAPs not sent by ptrace (PTRACE_O_TRACESYSGOOD)
if ws.Stopped() && ws.StopSignal()&0x80 == 0x80 {
return nil
} else if ws.Exited() {
return errExited
}
}
}
func syscallNameAndArity(scn uint64) (string, int) {
switch scn {
case syscall.SYS_READ:
return "read", 3
case syscall.SYS_WRITE:
return "write", 3
case syscall.SYS_OPEN:
return "open", 3
case syscall.SYS_CLOSE:
return "close", 1
case syscall.SYS_SELECT:
return "select", 5
case syscall.SYS_FSTAT:
return "fstat", 2
default:
return "unknown", -1
}
}
func syscallArgs(regs syscall.PtraceRegs, arity int) []uint64 {
if arity < 0 {
return []uint64{}
}
args := make([]uint64, arity)
for i := 0; i < arity; i++ {
args[i] = getSyscallArg(regs, i)
}
return args
}
func getSyscallArg(regs syscall.PtraceRegs, idx int) uint64 {
// x86_64 calling convention:
// https://en.wikipedia.org/wiki/X86_calling_conventions#System_V_AMD64_ABI
switch idx {
case 0:
return regs.Rdi
case 1:
return regs.Rsi
case 2:
return regs.Rdx
case 3:
return regs.R10
case 4:
return regs.R8
case 5:
return regs.R9
}
return 42 // oops
}
@ksurent
Copy link
Author

ksurent commented Feb 2, 2020

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment