Last active
February 2, 2020 22:36
-
-
Save ksurent/35c673edd1eb8dae9a3db4f7e0368742 to your computer and use it in GitHub Desktop.
A toy strace clone for my p2p study group
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, ®s); 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, ®s); 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 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
But of course things are way more complicated if we start looking closely: https://www.linuxplumbersconf.org/event/2/contributions/78/attachments/63/74/lpc_2018-what_could_be_done_in_the_kernel_to_make_strace_happy.pdf.