program: trace multi-threaded programs. LGTM=r R=r https://golang.org/cl/86370043
diff --git a/program/client/client.go b/program/client/client.go index bc585b1..57ce552 100644 --- a/program/client/client.go +++ b/program/client/client.go
@@ -162,10 +162,8 @@ panic("unimplemented") } -func (p *Program) Run(start bool) (program.Status, error) { - req := proxyrpc.RunRequest{ - Start: start, - } +func (p *Program) Run() (program.Status, error) { + req := proxyrpc.RunRequest{} var resp proxyrpc.RunResponse err := p.client.Call("Server.Run", &req, &resp) if err != nil {
diff --git a/program/program.go b/program/program.go index bc71091..2bed9dd 100644 --- a/program/program.go +++ b/program/program.go
@@ -34,11 +34,9 @@ // and execs a new instance of the target binary file // (which may have changed underfoot). // Breakpoints and open files are re-established. - // The flag specifies whether to run the program (true) - // or stop it before it executes any instructions (false). // The call hangs until the program stops executing, // at which point it returns the program status. - Run(start bool) (Status, error) + Run() (Status, error) // Stop stops execution of the current process but // does not kill it.
diff --git a/program/proxyrpc/proxyrpc.go b/program/proxyrpc/proxyrpc.go index eeef340..e201f1f 100644 --- a/program/proxyrpc/proxyrpc.go +++ b/program/proxyrpc/proxyrpc.go
@@ -52,7 +52,6 @@ } type RunRequest struct { - Start bool } type RunResponse struct {
diff --git a/program/server/ptrace.go b/program/server/ptrace.go index 0072e7f..0f25e80 100644 --- a/program/server/ptrace.go +++ b/program/server/ptrace.go
@@ -4,9 +4,6 @@ package server -// TODO: syscall.PTRACE_O_TRACECLONE shenanigans to trace multi-threaded -// programs. - import ( "fmt" "os" @@ -50,13 +47,6 @@ return <-s.ec } -func (s *Server) ptraceSetRegs(pid int, regs *syscall.PtraceRegs) (err error) { - s.fc <- func() error { - return syscall.PtraceSetRegs(pid, regs) - } - return <-s.ec -} - func (s *Server) ptracePeek(pid int, addr uintptr, out []byte) (err error) { s.fc <- func() error { n, err := syscall.PtracePeekText(pid, addr, out) @@ -85,6 +75,20 @@ return <-s.ec } +func (s *Server) ptraceSetOptions(pid int, options int) (err error) { + s.fc <- func() error { + return syscall.PtraceSetOptions(pid, options) + } + return <-s.ec +} + +func (s *Server) ptraceSetRegs(pid int, regs *syscall.PtraceRegs) (err error) { + s.fc <- func() error { + return syscall.PtraceSetRegs(pid, regs) + } + return <-s.ec +} + func (s *Server) ptraceSingleStep(pid int) (err error) { s.fc <- func() error { return syscall.PtraceSingleStep(pid) @@ -92,12 +96,11 @@ return <-s.ec } -func (s *Server) wait() (err error) { - var status syscall.WaitStatus +func (s *Server) wait(pid int) (wpid int, status syscall.WaitStatus, err error) { s.fc <- func() error { - _, err1 := syscall.Wait4(-1, &status, 0, nil) + var err1 error + wpid, err1 = syscall.Wait4(pid, &status, syscall.WALL, nil) return err1 } - // TODO: do something with status. - return <-s.ec + return wpid, status, <-s.ec }
diff --git a/program/server/server.go b/program/server/server.go index fb6a503..c9a425c 100644 --- a/program/server/server.go +++ b/program/server/server.go
@@ -40,6 +40,9 @@ ec chan error proc *os.Process + procIsUp bool + stoppedPid int + stoppedRegs syscall.PtraceRegs breakpoints map[uint64]breakpoint files []*file // Index == file descriptor. } @@ -183,6 +186,9 @@ if s.proc != nil { s.proc.Kill() s.proc = nil + s.procIsUp = false + s.stoppedPid = 0 + s.stoppedRegs = syscall.PtraceRegs{} } p, err := s.startProcess(s.executable, nil, &os.ProcAttr{ Files: []*os.File{ @@ -191,17 +197,15 @@ os.Stderr, }, Sys: &syscall.SysProcAttr{ - Ptrace: !req.Start, + Pdeathsig: syscall.SIGKILL, + Ptrace: true, }, }) if err != nil { return err } s.proc = p - - if !req.Start { - // TODO: wait until /proc/{s.proc.Pid}/status says "State: t (tracing stop)". - } + s.stoppedPid = p.Pid return nil } @@ -209,29 +213,41 @@ s.mu.Lock() defer s.mu.Unlock() - regs := syscall.PtraceRegs{} - err := s.ptraceGetRegs(s.proc.Pid, ®s) - if err != nil { - return err + if s.proc == nil { + return fmt.Errorf("Resume: Run did not successfully start a process") } - if _, ok := s.breakpoints[regs.Rip]; ok { - err = s.ptraceSingleStep(s.proc.Pid) + + if !s.procIsUp { + s.procIsUp = true + _, err := s.waitForTrap(s.stoppedPid) + if err != nil { + return err + } + err = s.ptraceSetOptions(s.stoppedPid, syscall.PTRACE_O_TRACECLONE) + if err != nil { + return fmt.Errorf("ptraceSetOptions: %v", err) + } + } else if _, ok := s.breakpoints[s.stoppedRegs.Rip]; ok { + err := s.ptraceSingleStep(s.stoppedPid) if err != nil { return fmt.Errorf("ptraceSingleStep: %v", err) } + _, err = s.waitForTrap(s.stoppedPid) + if err != nil { + return err + } } - err = s.setBreakpoints() + err := s.setBreakpoints() if err != nil { return err } - - err = s.ptraceCont(s.proc.Pid, 0) + err = s.ptraceCont(s.stoppedPid, 0) if err != nil { - return err + return fmt.Errorf("ptraceCont: %v", err) } - err = s.wait() + s.stoppedPid, err = s.waitForTrap(-1) if err != nil { return err } @@ -241,22 +257,39 @@ return err } - err = s.ptraceGetRegs(s.proc.Pid, ®s) + err = s.ptraceGetRegs(s.stoppedPid, &s.stoppedRegs) if err != nil { - return err + return fmt.Errorf("ptraceGetRegs: %v", err) } - regs.Rip -= uint64(s.arch.BreakpointSize) - err = s.ptraceSetRegs(s.proc.Pid, ®s) + s.stoppedRegs.Rip -= uint64(s.arch.BreakpointSize) + + err = s.ptraceSetRegs(s.stoppedPid, &s.stoppedRegs) if err != nil { return fmt.Errorf("ptraceSetRegs: %v", err) } - resp.Status.PC = regs.Rip - resp.Status.SP = regs.Rsp + resp.Status.PC = s.stoppedRegs.Rip + resp.Status.SP = s.stoppedRegs.Rsp return nil } +func (s *Server) waitForTrap(pid int) (wpid int, err error) { + for { + wpid, status, err := s.wait(pid) + if err != nil { + return 0, fmt.Errorf("wait: %v", err) + } + if status.StopSignal() == syscall.SIGTRAP && status.TrapCause() != syscall.PTRACE_EVENT_CLONE { + return wpid, nil + } + err = s.ptraceCont(wpid, 0) // TODO: non-zero when wait catches other signals? + if err != nil { + return 0, fmt.Errorf("ptraceCont: %v", err) + } + } +} + func (s *Server) Breakpoint(req *proxyrpc.BreakpointRequest, resp *proxyrpc.BreakpointResponse) (err error) { s.mu.Lock() defer s.mu.Unlock() @@ -275,7 +308,7 @@ return fmt.Errorf("breakpoint already set at %#x (TODO)", pc) } - err = s.ptracePeek(s.proc.Pid, uintptr(pc), bp.origInstr[:s.arch.BreakpointSize]) + err = s.ptracePeek(s.stoppedPid, uintptr(pc), bp.origInstr[:s.arch.BreakpointSize]) if err != nil { return fmt.Errorf("ptracePeek: %v", err) } @@ -288,7 +321,7 @@ func (s *Server) setBreakpoints() error { for pc := range s.breakpoints { - err := s.ptracePoke(s.proc.Pid, uintptr(pc), s.arch.BreakpointInstr[:s.arch.BreakpointSize]) + err := s.ptracePoke(s.stoppedPid, uintptr(pc), s.arch.BreakpointInstr[:s.arch.BreakpointSize]) if err != nil { return fmt.Errorf("setBreakpoints: %v", err) } @@ -298,7 +331,7 @@ func (s *Server) liftBreakpoints() error { for pc, breakpoint := range s.breakpoints { - err := s.ptracePoke(s.proc.Pid, uintptr(pc), breakpoint.origInstr[:s.arch.BreakpointSize]) + err := s.ptracePoke(s.stoppedPid, uintptr(pc), breakpoint.origInstr[:s.arch.BreakpointSize]) if err != nil { return fmt.Errorf("liftBreakpoints: %v", err) } @@ -381,7 +414,7 @@ // TODO: we're assuming we're at a function's entry point (LowPC). regs := syscall.PtraceRegs{} - err := s.ptraceGetRegs(s.proc.Pid, ®s) + err := s.ptraceGetRegs(s.stoppedPid, ®s) if err != nil { return err } @@ -432,7 +465,7 @@ if location == 0 { return fmt.Errorf("no location for FormalParameter") } - err = s.ptracePeek(s.proc.Pid, location, buf[:s.arch.IntSize]) + err = s.ptracePeek(s.stoppedPid, location, buf[:s.arch.IntSize]) if err != nil { return err }