I am attempting to write a program that would print all of the syscalls a program makes. I am having trouble extending this code to work with multi-processing scripts. I started off with code from https://github.com/lizrice/strace-from-scratch, and now I would like to trace offspring processes as well.
I tried adding the options PTRACE_O_TRACEVFORK | PTRACE_O_TRACEFORK | PTRACE_O_TRACECLONE, but this causes the process to hang for some reason. I do not know why. If I do not specify these options, then the process runs to completion, but of course children are not traced.
package main
import (
"fmt"
seccomp "github.com/seccomp/libseccomp-golang"
"golang.org/x/sys/unix"
"log"
"os"
"os/exec"
"runtime"
)
func init() {
runtime.LockOSThread()
}
func main() {
var err error
if len(os.Args) < 2 {
log.Fatalf("usage: ./trace-files program [arg]...")
}
cmd := exec.Command(os.Args[1], os.Args[2:]...)
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.SysProcAttr = &unix.SysProcAttr{
Ptrace: true,
}
if err = cmd.Start(); err != nil {
log.Fatalf("error starting command: %s\n", err)
}
if err = cmd.Wait(); err != nil {
// We expect "trace/breakpoint trap" here.
fmt.Printf("Wait returned: %s\n", err)
}
pid := cmd.Process.Pid
exit := true
var regs unix.PtraceRegs
var status unix.WaitStatus
// TODO: Setting these options causes the multiprocessing Python script to hang.
ptraceOptions := unix.PTRACE_O_TRACEVFORK | unix.PTRACE_O_TRACEFORK | unix.PTRACE_O_TRACECLONE
if err = unix.PtraceSetOptions(pid, ptraceOptions); err != nil {
log.Fatalf("error setting ptrace options: %s", err)
}
fmt.Println("pid\tsyscall")
for {
if exit {
err = unix.PtraceGetRegs(pid, ®s)
if err != nil {
break
}
name, err := seccomp.ScmpSyscall(regs.Orig_rax).GetName()
if err != nil {
fmt.Printf("error getting syscall name for orig_rax %d\n", regs.Orig_rax)
}
fmt.Printf("%d\t%s\n", pid, name)
}
if err = unix.PtraceSyscall(pid, 0); err != nil {
log.Fatalf("error calling ptrace syscall: %s\n", err)
}
// TODO: is it OK to overwrite pid here?
pid, err = unix.Wait4(pid, &status, 0, nil)
if err != nil {
log.Fatalf("error calling wait")
}
exit = !exit
}
}
For testing purposes, I have written a Python script that uses multiprocessing and prints the process IDs that it spawned.
import multiprocessing
import os
def fun(x):
return os.getpid()
if __name__ == "__main__":
print("PYTHON: starting multiprocessing pool")
with multiprocessing.Pool() as pool:
processes = pool.map(fun, range(1000000))
print("PYTHON: ended multiprocessing pool")
processes = map(str, set(processes))
print("PYTHON: process IDs: ", ", ".join(processes))
When I run the Go code above on a single process program, like ls, then things seems to work fine.
go run . ls
But when I run the Go code on the Python script, then the output hangs (but only if I supply the ptrace options I mentioned above).
go run . python script.py
My end goal for this program is to get a list of all of the files a program uses. I will inspect /proc/PID/maps for each syscall for that part, but first I would like to know how to trace multi-process programs. I tried looking through the documentation and code for strace, but that confused me further...