diff --git a/proctl/proctl.go b/proctl/proctl.go new file mode 100644 index 00000000..827d1db4 --- /dev/null +++ b/proctl/proctl.go @@ -0,0 +1,495 @@ +// Package proctl provides functions for attaching to and manipulating +// a process during the debug session. +package proctl + +import ( + "debug/gosym" + "fmt" + "os" + "os/exec" + "sync" + "syscall" + + "github.com/derekparker/delve/dwarf/frame" +) + +// Represents a single breakpoint. Stores information on the break +// point including the byte of data that originally was stored at that +// address. +type BreakPoint struct { + FunctionName string + File string + Line int + Addr uint64 + OriginalData []byte + temp bool +} + +type BreakPointExistsError struct { + file string + line int + addr uintptr +} + +// ProcessStatus is the result of parsing the data from +// the /proc//stats psuedo file. +type ProcessStatus struct { + pid int + comm string + state rune + ppid int +} + +const ( + STATUS_SLEEPING = 'S' + STATUS_RUNNING = 'R' + STATUS_TRACE_STOP = 't' +) + +func (bpe BreakPointExistsError) Error() string { + return fmt.Sprintf("Breakpoint exists at %s:%d at %x", bpe.file, bpe.line, bpe.addr) +} + +func Attach(pid int) (*DebuggedProcess, error) { + dbp, err := newDebugProcess(pid, true) + if err != nil { + return nil, err + } + // Attach to all currently active threads. + allm, err := dbp.CurrentThread.AllM() + if err != nil { + return nil, err + } + for _, m := range allm { + if m.procid == 0 { + continue + } + _, err := dbp.AttachThread(m.procid) + if err != nil { + return nil, err + } + } + return dbp, nil +} + +func Launch(cmd []string) (*DebuggedProcess, error) { + proc := exec.Command(cmd[0]) + proc.Args = cmd + proc.Stdout = os.Stdout + proc.Stderr = os.Stderr + proc.SysProcAttr = &syscall.SysProcAttr{Ptrace: true} + + if err := proc.Start(); err != nil { + return nil, err + } + + _, _, err := wait(proc.Process.Pid, 0) + if err != nil { + return nil, fmt.Errorf("waiting for target execve failed: %s", err) + } + + return newDebugProcess(proc.Process.Pid, false) +} + +// Returns a new DebuggedProcess struct with sensible defaults. +func newDebugProcess(pid int, attach bool) (*DebuggedProcess, error) { + dbp := DebuggedProcess{ + Pid: pid, + Threads: make(map[int]*ThreadContext), + BreakPoints: make(map[uint64]*BreakPoint), + } + + if attach { + thread, err := dbp.AttachThread(pid) + if err != nil { + return nil, err + } + dbp.CurrentThread = thread + } else { + thread, err := dbp.addThread(pid) + if err != nil { + return nil, err + } + dbp.CurrentThread = thread + } + + proc, err := os.FindProcess(pid) + if err != nil { + return nil, err + } + + dbp.Process = proc + err = dbp.LoadInformation() + if err != nil { + return nil, err + } + + return &dbp, nil +} + +func (dbp *DebuggedProcess) AttachThread(tid int) (*ThreadContext, error) { + if thread, ok := dbp.Threads[tid]; ok { + return thread, nil + } + + err := syscall.PtraceAttach(tid) + if err != nil && err != syscall.EPERM { + // Do not return err if err == EPERM, + // we may already be tracing this thread due to + // PTRACE_O_TRACECLONE. We will surely blow up later + // if we truly don't have permissions. + return nil, fmt.Errorf("could not attach to new thread %d %s", tid, err) + } + + pid, status, err := wait(tid, 0) + if err != nil { + return nil, err + } + + if status.Exited() { + return nil, fmt.Errorf("thread already exited %d", pid) + } + + return dbp.addThread(tid) +} + +// Sets a breakpoint in the current thread. +func (dbp *DebuggedProcess) Break(addr uintptr) (*BreakPoint, error) { + return dbp.CurrentThread.Break(addr) +} + +// Clears a breakpoint in the current thread. +func (dbp *DebuggedProcess) Clear(pc uint64) (*BreakPoint, error) { + return dbp.CurrentThread.Clear(pc) +} + +// Returns the status of the current main thread context. +func (dbp *DebuggedProcess) Status() *syscall.WaitStatus { + return dbp.CurrentThread.Status +} + +// Loop through all threads, printing their information +// to the console. +func (dbp *DebuggedProcess) PrintThreadInfo() error { + for _, th := range dbp.Threads { + if err := th.PrintInfo(); err != nil { + return err + } + } + return nil +} + +// Finds the executable from /proc//exe and then +// uses that to parse the following information: +// * Dwarf .debug_frame section +// * Dwarf .debug_line section +// * Go symbol table. +func (dbp *DebuggedProcess) LoadInformation() error { + var ( + wg sync.WaitGroup + err error + ) + + err = dbp.findExecutable() + if err != nil { + return err + } + + wg.Add(2) + go dbp.parseDebugFrame(&wg) + go dbp.obtainGoSymbols(&wg) + + wg.Wait() + + return nil +} + +// Steps through process. +func (dbp *DebuggedProcess) Step() (err error) { + var ( + th *ThreadContext + ok bool + ) + + allm, err := dbp.CurrentThread.AllM() + if err != nil { + return err + } + + for _, m := range allm { + th, ok = dbp.Threads[m.procid] + if !ok { + th = dbp.Threads[dbp.Pid] + } + + if m.blocked == 0 { + err := th.Step() + if err != nil { + return err + } + } + + } + + return nil +} + +// Step over function calls. +func (dbp *DebuggedProcess) Next() error { + var ( + th *ThreadContext + ok bool + ) + + allm, err := dbp.CurrentThread.AllM() + if err != nil { + return err + } + + for _, m := range allm { + th, ok = dbp.Threads[m.procid] + if !ok { + th = dbp.Threads[dbp.Pid] + } + + if m.blocked == 1 { + // Continue any blocked M so that the + // scheduler can continue to do its' + // job correctly. + err := th.Continue() + if err != nil { + return err + } + continue + } + + err := th.Next() + if err != nil && err != syscall.ESRCH { + return err + } + } + return stopTheWorld(dbp) +} + +// Resume process. +func (dbp *DebuggedProcess) Continue() error { + for _, thread := range dbp.Threads { + err := thread.Continue() + if err != nil { + return err + } + } + + wpid, _, err := trapWait(dbp, -1, 0) + if err != nil { + return err + } + return handleBreakPoint(dbp, wpid) +} + +// Obtains register values from what Delve considers to be the current +// thread of the traced process. +func (dbp *DebuggedProcess) Registers() (*syscall.PtraceRegs, error) { + return dbp.CurrentThread.Registers() +} + +type InvalidAddressError struct { + address uintptr +} + +func (iae InvalidAddressError) Error() string { + return fmt.Sprintf("Invalid address %#v\n", iae.address) +} + +func (dbp *DebuggedProcess) CurrentPC() (uint64, error) { + return dbp.CurrentThread.CurrentPC() +} + +// Returns the value of the named symbol. +func (dbp *DebuggedProcess) EvalSymbol(name string) (*Variable, error) { + return dbp.CurrentThread.EvalSymbol(name) +} + +func (dbp *DebuggedProcess) parseDebugFrame(wg *sync.WaitGroup) { + defer wg.Done() + + debugFrame, err := dbp.Executable.Section(".debug_frame").Data() + if err != nil { + fmt.Println("could not get .debug_frame section", err) + os.Exit(1) + } + + dbp.FrameEntries = frame.Parse(debugFrame) +} + +func (dbp *DebuggedProcess) obtainGoSymbols(wg *sync.WaitGroup) { + defer wg.Done() + + var ( + symdat []byte + pclndat []byte + err error + ) + + if sec := dbp.Executable.Section(".gosymtab"); sec != nil { + symdat, err = sec.Data() + if err != nil { + fmt.Println("could not get .gosymtab section", err) + os.Exit(1) + } + } + + if sec := dbp.Executable.Section(".gopclntab"); sec != nil { + pclndat, err = sec.Data() + if err != nil { + fmt.Println("could not get .gopclntab section", err) + os.Exit(1) + } + } + + pcln := gosym.NewLineTable(pclndat, dbp.Executable.Section(".text").Addr) + tab, err := gosym.NewTable(symdat, pcln) + if err != nil { + fmt.Println("could not get initialize line table", err) + os.Exit(1) + } + + dbp.GoSymTable = tab +} + +type ProcessExitedError struct { + pid int +} + +func (pe ProcessExitedError) Error() string { + return fmt.Sprintf("process %d has exited", pe.pid) +} + +func trapWait(dbp *DebuggedProcess, pid int, options int) (int, *syscall.WaitStatus, error) { + for { + wpid, status, err := wait(pid, 0) + if err != nil { + return -1, nil, fmt.Errorf("wait err %s %d", err, pid) + } + if wpid == 0 { + continue + } + if th, ok := dbp.Threads[wpid]; ok { + th.Status = status + } + if status.Exited() && wpid == dbp.Pid { + return -1, status, ProcessExitedError{wpid} + } + if status.StopSignal() == syscall.SIGTRAP && status.TrapCause() == syscall.PTRACE_EVENT_CLONE { + err = addNewThread(dbp, wpid) + if err != nil { + return -1, nil, err + } + continue + } + if status.StopSignal() == syscall.SIGTRAP { + return wpid, status, nil + } + } +} + +func handleBreakPoint(dbp *DebuggedProcess, pid int) error { + thread := dbp.Threads[pid] + if pid != dbp.CurrentThread.Id { + fmt.Printf("thread context changed from %d to %d\n", dbp.CurrentThread.Id, pid) + dbp.CurrentThread = thread + } + + pc, err := thread.CurrentPC() + if err != nil { + return fmt.Errorf("could not get current pc %s", err) + } + + // Check to see if we hit a runtime.breakpoint + fn := dbp.GoSymTable.PCToFunc(pc) + if fn != nil && fn.Name == "runtime.breakpoint" { + // step twice to get back to user code + for i := 0; i < 2; i++ { + err = thread.Step() + if err != nil { + return err + } + } + stopTheWorld(dbp) + return nil + } + + // Check to see if we have hit a user set breakpoint. + if bp, ok := dbp.BreakPoints[pc-1]; ok { + if !bp.temp { + stopTheWorld(dbp) + } + return nil + } + + return fmt.Errorf("did not hit recognized breakpoint") +} + +// Ensure execution of every traced thread is halted. +func stopTheWorld(dbp *DebuggedProcess) error { + // Loop through all threads and ensure that we + // stop the rest of them, so that by the time + // we return control to the user, all threads + // are inactive. We send SIGSTOP and ensure all + // threads are in in signal-delivery-stop mode. + for _, th := range dbp.Threads { + ps, err := parseProcessStatus(th.Id) + if err != nil { + return err + } + + if ps.state == STATUS_TRACE_STOP { + continue + } + + err = syscall.Tgkill(dbp.Pid, th.Id, syscall.SIGSTOP) + if err != nil { + return err + } + + pid, _, err := wait(th.Id, 0) + if err != nil { + return fmt.Errorf("wait err %s %d", err, pid) + } + } + + return nil +} + +func addNewThread(dbp *DebuggedProcess, pid int) error { + // A traced thread has cloned a new thread, grab the pid and + // add it to our list of traced threads. + msg, err := syscall.PtraceGetEventMsg(pid) + if err != nil { + return fmt.Errorf("could not get event message: %s", err) + } + fmt.Println("new thread spawned", msg) + + _, err = dbp.addThread(int(msg)) + if err != nil { + return err + } + + err = syscall.PtraceCont(int(msg), 0) + if err != nil { + return fmt.Errorf("could not continue new thread %d %s", msg, err) + } + + err = syscall.PtraceCont(pid, 0) + if err != nil { + return fmt.Errorf("could not continue stopped thread %d %s", pid, err) + } + + return nil +} + +func wait(pid, options int) (int, *syscall.WaitStatus, error) { + var status syscall.WaitStatus + wpid, err := syscall.Wait4(pid, &status, syscall.WALL|options, nil) + return wpid, &status, err +} diff --git a/proctl/proctl_linux_amd64.go b/proctl/proctl_linux_amd64.go index ba012233..416b8a70 100644 --- a/proctl/proctl_linux_amd64.go +++ b/proctl/proctl_linux_amd64.go @@ -1,13 +1,9 @@ -// Package proctl provides functions for attaching to and manipulating -// a process during the debug session. package proctl import ( "debug/gosym" "fmt" "os" - "os/exec" - "sync" "syscall" "github.com/derekparker/delve/dwarf/frame" @@ -28,160 +24,6 @@ type DebuggedProcess struct { CurrentThread *ThreadContext } -// Represents a single breakpoint. Stores information on the break -// point including the byte of data that originally was stored at that -// address. -type BreakPoint struct { - FunctionName string - File string - Line int - Addr uint64 - OriginalData []byte - temp bool -} - -type BreakPointExistsError struct { - file string - line int - addr uintptr -} - -// ProcessStatus is the result of parsing the data from -// the /proc//stats psuedo file. -type ProcessStatus struct { - pid int - comm string - state rune - ppid int -} - -const ( - STATUS_SLEEPING = 'S' - STATUS_RUNNING = 'R' - STATUS_TRACE_STOP = 't' -) - -func parseProcessStatus(pid int) (*ProcessStatus, error) { - var ps ProcessStatus - - f, err := os.Open(fmt.Sprintf("/proc/%d/stat", pid)) - if err != nil { - return nil, err - } - defer f.Close() - - fmt.Fscanf(f, "%d %s %c %d", &ps.pid, &ps.comm, &ps.state, &ps.ppid) - - return &ps, nil -} - -func (bpe BreakPointExistsError) Error() string { - return fmt.Sprintf("Breakpoint exists at %s:%d at %x", bpe.file, bpe.line, bpe.addr) -} - -func Attach(pid int) (*DebuggedProcess, error) { - dbp, err := newDebugProcess(pid, true) - if err != nil { - return nil, err - } - // Attach to all currently active threads. - allm, err := dbp.CurrentThread.AllM() - if err != nil { - return nil, err - } - for _, m := range allm { - if m.procid == 0 { - continue - } - _, err := dbp.AttachThread(m.procid) - if err != nil { - return nil, err - } - } - return dbp, nil -} - -func Launch(cmd []string) (*DebuggedProcess, error) { - proc := exec.Command(cmd[0]) - proc.Args = cmd - proc.Stdout = os.Stdout - proc.Stderr = os.Stderr - proc.SysProcAttr = &syscall.SysProcAttr{Ptrace: true} - - if err := proc.Start(); err != nil { - return nil, err - } - - _, _, err := wait(proc.Process.Pid, 0) - if err != nil { - return nil, fmt.Errorf("waiting for target execve failed: %s", err) - } - - return newDebugProcess(proc.Process.Pid, false) -} - -// Returns a new DebuggedProcess struct with sensible defaults. -func newDebugProcess(pid int, attach bool) (*DebuggedProcess, error) { - dbp := DebuggedProcess{ - Pid: pid, - Threads: make(map[int]*ThreadContext), - BreakPoints: make(map[uint64]*BreakPoint), - } - - if attach { - thread, err := dbp.AttachThread(pid) - if err != nil { - return nil, err - } - dbp.CurrentThread = thread - } else { - thread, err := dbp.addThread(pid) - if err != nil { - return nil, err - } - dbp.CurrentThread = thread - } - - proc, err := os.FindProcess(pid) - if err != nil { - return nil, err - } - - dbp.Process = proc - err = dbp.LoadInformation() - if err != nil { - return nil, err - } - - return &dbp, nil -} - -func (dbp *DebuggedProcess) AttachThread(tid int) (*ThreadContext, error) { - if thread, ok := dbp.Threads[tid]; ok { - return thread, nil - } - - err := syscall.PtraceAttach(tid) - if err != nil && err != syscall.EPERM { - // Do not return err if err == EPERM, - // we may already be tracing this thread due to - // PTRACE_O_TRACECLONE. We will surely blow up later - // if we truly don't have permissions. - return nil, fmt.Errorf("could not attach to new thread %d %s", tid, err) - } - - pid, status, err := wait(tid, 0) - if err != nil { - return nil, err - } - - if status.Exited() { - return nil, fmt.Errorf("thread already exited %d", pid) - } - - return dbp.addThread(tid) -} - func (dbp *DebuggedProcess) addThread(tid int) (*ThreadContext, error) { err := syscall.PtraceSetOptions(tid, syscall.PTRACE_O_TRACECLONE) if err == syscall.ESRCH { @@ -205,161 +47,18 @@ func (dbp *DebuggedProcess) addThread(tid int) (*ThreadContext, error) { return dbp.Threads[tid], nil } -// Sets a breakpoint in the current thread. -func (dbp *DebuggedProcess) Break(addr uintptr) (*BreakPoint, error) { - return dbp.CurrentThread.Break(addr) -} +func parseProcessStatus(pid int) (*ProcessStatus, error) { + var ps ProcessStatus -// Clears a breakpoint in the current thread. -func (dbp *DebuggedProcess) Clear(pc uint64) (*BreakPoint, error) { - return dbp.CurrentThread.Clear(pc) -} - -// Returns the status of the current main thread context. -func (dbp *DebuggedProcess) Status() *syscall.WaitStatus { - return dbp.CurrentThread.Status -} - -// Loop through all threads, printing their information -// to the console. -func (dbp *DebuggedProcess) PrintThreadInfo() error { - for _, th := range dbp.Threads { - if err := th.PrintInfo(); err != nil { - return err - } - } - return nil -} - -// Finds the executable from /proc//exe and then -// uses that to parse the following information: -// * Dwarf .debug_frame section -// * Dwarf .debug_line section -// * Go symbol table. -func (dbp *DebuggedProcess) LoadInformation() error { - var ( - wg sync.WaitGroup - err error - ) - - err = dbp.findExecutable() + f, err := os.Open(fmt.Sprintf("/proc/%d/stat", pid)) if err != nil { - return err + return nil, err } + defer f.Close() - wg.Add(2) - go dbp.parseDebugFrame(&wg) - go dbp.obtainGoSymbols(&wg) + fmt.Fscanf(f, "%d %s %c %d", &ps.pid, &ps.comm, &ps.state, &ps.ppid) - wg.Wait() - - return nil -} - -// Steps through process. -func (dbp *DebuggedProcess) Step() (err error) { - var ( - th *ThreadContext - ok bool - ) - - allm, err := dbp.CurrentThread.AllM() - if err != nil { - return err - } - - for _, m := range allm { - th, ok = dbp.Threads[m.procid] - if !ok { - th = dbp.Threads[dbp.Pid] - } - - if m.blocked == 0 { - err := th.Step() - if err != nil { - return err - } - } - - } - - return nil -} - -// Step over function calls. -func (dbp *DebuggedProcess) Next() error { - var ( - th *ThreadContext - ok bool - ) - - allm, err := dbp.CurrentThread.AllM() - if err != nil { - return err - } - - for _, m := range allm { - th, ok = dbp.Threads[m.procid] - if !ok { - th = dbp.Threads[dbp.Pid] - } - - if m.blocked == 1 { - // Continue any blocked M so that the - // scheduler can continue to do its' - // job correctly. - err := th.Continue() - if err != nil { - return err - } - continue - } - - err := th.Next() - if err != nil && err != syscall.ESRCH { - return err - } - } - return stopTheWorld(dbp) -} - -// Resume process. -func (dbp *DebuggedProcess) Continue() error { - for _, thread := range dbp.Threads { - err := thread.Continue() - if err != nil { - return err - } - } - - wpid, _, err := trapWait(dbp, -1, 0) - if err != nil { - return err - } - return handleBreakPoint(dbp, wpid) -} - -// Obtains register values from what Delve considers to be the current -// thread of the traced process. -func (dbp *DebuggedProcess) Registers() (*syscall.PtraceRegs, error) { - return dbp.CurrentThread.Registers() -} - -type InvalidAddressError struct { - address uintptr -} - -func (iae InvalidAddressError) Error() string { - return fmt.Sprintf("Invalid address %#v\n", iae.address) -} - -func (dbp *DebuggedProcess) CurrentPC() (uint64, error) { - return dbp.CurrentThread.CurrentPC() -} - -// Returns the value of the named symbol. -func (dbp *DebuggedProcess) EvalSymbol(name string) (*Variable, error) { - return dbp.CurrentThread.EvalSymbol(name) + return &ps, nil } func (dbp *DebuggedProcess) findExecutable() error { @@ -379,187 +78,3 @@ func (dbp *DebuggedProcess) findExecutable() error { return nil } - -func (dbp *DebuggedProcess) parseDebugFrame(wg *sync.WaitGroup) { - defer wg.Done() - - debugFrame, err := dbp.Executable.Section(".debug_frame").Data() - if err != nil { - fmt.Println("could not get .debug_frame section", err) - os.Exit(1) - } - - dbp.FrameEntries = frame.Parse(debugFrame) -} - -func (dbp *DebuggedProcess) obtainGoSymbols(wg *sync.WaitGroup) { - defer wg.Done() - - var ( - symdat []byte - pclndat []byte - err error - ) - - if sec := dbp.Executable.Section(".gosymtab"); sec != nil { - symdat, err = sec.Data() - if err != nil { - fmt.Println("could not get .gosymtab section", err) - os.Exit(1) - } - } - - if sec := dbp.Executable.Section(".gopclntab"); sec != nil { - pclndat, err = sec.Data() - if err != nil { - fmt.Println("could not get .gopclntab section", err) - os.Exit(1) - } - } - - pcln := gosym.NewLineTable(pclndat, dbp.Executable.Section(".text").Addr) - tab, err := gosym.NewTable(symdat, pcln) - if err != nil { - fmt.Println("could not get initialize line table", err) - os.Exit(1) - } - - dbp.GoSymTable = tab -} - -type ProcessExitedError struct { - pid int -} - -func (pe ProcessExitedError) Error() string { - return fmt.Sprintf("process %d has exited", pe.pid) -} - -func trapWait(dbp *DebuggedProcess, pid int, options int) (int, *syscall.WaitStatus, error) { - for { - wpid, status, err := wait(pid, 0) - if err != nil { - return -1, nil, fmt.Errorf("wait err %s %d", err, pid) - } - if wpid == 0 { - continue - } - if th, ok := dbp.Threads[wpid]; ok { - th.Status = status - } - if status.Exited() && wpid == dbp.Pid { - return -1, status, ProcessExitedError{wpid} - } - if status.StopSignal() == syscall.SIGTRAP && status.TrapCause() == syscall.PTRACE_EVENT_CLONE { - err = addNewThread(dbp, wpid) - if err != nil { - return -1, nil, err - } - continue - } - if status.StopSignal() == syscall.SIGTRAP { - return wpid, status, nil - } - } -} - -func handleBreakPoint(dbp *DebuggedProcess, pid int) error { - thread := dbp.Threads[pid] - if pid != dbp.CurrentThread.Id { - fmt.Printf("thread context changed from %d to %d\n", dbp.CurrentThread.Id, pid) - dbp.CurrentThread = thread - } - - pc, err := thread.CurrentPC() - if err != nil { - return fmt.Errorf("could not get current pc %s", err) - } - - // Check to see if we hit a runtime.breakpoint - fn := dbp.GoSymTable.PCToFunc(pc) - if fn != nil && fn.Name == "runtime.breakpoint" { - // step twice to get back to user code - for i := 0; i < 2; i++ { - err = thread.Step() - if err != nil { - return err - } - } - stopTheWorld(dbp) - return nil - } - - // Check to see if we have hit a user set breakpoint. - if bp, ok := dbp.BreakPoints[pc-1]; ok { - if !bp.temp { - stopTheWorld(dbp) - } - return nil - } - - return fmt.Errorf("did not hit recognized breakpoint") -} - -// Ensure execution of every traced thread is halted. -func stopTheWorld(dbp *DebuggedProcess) error { - // Loop through all threads and ensure that we - // stop the rest of them, so that by the time - // we return control to the user, all threads - // are inactive. We send SIGSTOP and ensure all - // threads are in in signal-delivery-stop mode. - for _, th := range dbp.Threads { - ps, err := parseProcessStatus(th.Id) - if err != nil { - return err - } - - if ps.state == STATUS_TRACE_STOP { - continue - } - - err = syscall.Tgkill(dbp.Pid, th.Id, syscall.SIGSTOP) - if err != nil { - return err - } - - pid, _, err := wait(th.Id, 0) - if err != nil { - return fmt.Errorf("wait err %s %d", err, pid) - } - } - - return nil -} - -func addNewThread(dbp *DebuggedProcess, pid int) error { - // A traced thread has cloned a new thread, grab the pid and - // add it to our list of traced threads. - msg, err := syscall.PtraceGetEventMsg(pid) - if err != nil { - return fmt.Errorf("could not get event message: %s", err) - } - fmt.Println("new thread spawned", msg) - - _, err = dbp.addThread(int(msg)) - if err != nil { - return err - } - - err = syscall.PtraceCont(int(msg), 0) - if err != nil { - return fmt.Errorf("could not continue new thread %d %s", msg, err) - } - - err = syscall.PtraceCont(pid, 0) - if err != nil { - return fmt.Errorf("could not continue stopped thread %d %s", pid, err) - } - - return nil -} - -func wait(pid, options int) (int, *syscall.WaitStatus, error) { - var status syscall.WaitStatus - wpid, err := syscall.Wait4(pid, &status, syscall.WALL|options, nil) - return wpid, &status, err -}