diff --git a/client/cli/cli.go b/client/cli/cli.go index 1a5fe786..d1304bb0 100644 --- a/client/cli/cli.go +++ b/client/cli/cli.go @@ -5,6 +5,7 @@ import ( "io" "os" "os/exec" + "os/signal" "strings" "syscall" @@ -47,6 +48,16 @@ func Run(run bool, pid int, args []string) { } } + ch := make(chan os.Signal) + signal.Notify(ch, syscall.SIGINT) + go func() { + for _ = range ch { + if dbp.Running() { + dbp.RequestManualStop() + } + } + }() + cmds := command.DebugCommands() goreadline.LoadHistoryFromFile(historyFile) fmt.Println("Type 'help' for list of commands.") diff --git a/proctl/proctl.go b/proctl/proctl.go index 61de75e6..5e5edc16 100644 --- a/proctl/proctl.go +++ b/proctl/proctl.go @@ -28,6 +28,8 @@ type DebuggedProcess struct { BreakPoints map[uint64]*BreakPoint Threads map[int]*ThreadContext CurrentThread *ThreadContext + running bool + halt bool } // Represents a single breakpoint. Stores information on the break @@ -49,6 +51,16 @@ type BreakPointExistsError struct { addr uint64 } +func (bpe BreakPointExistsError) Error() string { + return fmt.Sprintf("Breakpoint exists at %s:%d at %x", bpe.file, bpe.line, bpe.addr) +} + +type ManualStopError struct{} + +func (mse ManualStopError) Error() string { + return "Manual stop requested" +} + // ProcessStatus is the result of parsing the data from // the /proc//stats psuedo file. type ProcessStatus struct { @@ -68,10 +80,6 @@ var ( breakpointIDCounter = 0 ) -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 { @@ -175,6 +183,10 @@ func (dbp *DebuggedProcess) AttachThread(tid int) (*ThreadContext, error) { return dbp.addThread(tid) } +func (dbp *DebuggedProcess) Running() bool { + return dbp.running +} + // Find a location by string (file+line, function, breakpoint id, addr) func (dbp *DebuggedProcess) FindLocation(str string) (uint64, error) { // File + Line @@ -222,6 +234,18 @@ func (dbp *DebuggedProcess) FindLocation(str string) (uint64, error) { } } +func (dbp *DebuggedProcess) RequestManualStop() { + dbp.halt = true + for _, th := range dbp.Threads { + ps, _ := parseProcessStatus(th.Id) + if ps.state == STATUS_TRACE_STOP { + continue + } + syscall.Tgkill(dbp.Pid, th.Id, syscall.SIGSTOP) + } + dbp.running = false +} + // Sets a breakpoint in the current thread. func (dbp *DebuggedProcess) Break(addr uint64) (*BreakPoint, error) { return dbp.CurrentThread.Break(addr) @@ -278,22 +302,25 @@ func (dbp *DebuggedProcess) Step() (err error) { 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 + fn := func() error { + 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 } - return nil + return dbp.run(fn) } // Step over function calls. @@ -308,29 +335,32 @@ func (dbp *DebuggedProcess) Next() error { return err } - for _, m := range allm { - th, ok = dbp.Threads[m.procid] - if !ok { - th = dbp.Threads[dbp.Pid] - } + fn := func() error { + 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 { + 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 } - continue - } - - err := th.Next() - if err != nil && err != syscall.ESRCH { - return err } + return stopTheWorld(dbp) } - return stopTheWorld(dbp) + return dbp.run(fn) } // Resume process. @@ -342,11 +372,14 @@ func (dbp *DebuggedProcess) Continue() error { } } - wpid, _, err := trapWait(dbp, -1, 0) - if err != nil { - return err + fn := func() error { + wpid, _, err := trapWait(dbp, -1) + if err != nil { + return err + } + return handleBreakPoint(dbp, wpid) } - return handleBreakPoint(dbp, wpid) + return dbp.run(fn) } // Obtains register values from what Delve considers to be the current @@ -377,6 +410,18 @@ func (dbp *DebuggedProcess) DwarfReader() *reader.Reader { return reader.New(dbp.Dwarf) } +func (dbp *DebuggedProcess) run(fn func() error) error { + dbp.running = true + dbp.halt = false + defer func() { dbp.running = false }() + if err := fn(); err != nil { + if _, ok := err.(ManualStopError); !ok { + return err + } + } + return nil +} + type ProcessExitedError struct { pid int } @@ -385,7 +430,7 @@ 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) { +func trapWait(dbp *DebuggedProcess, pid int) (int, *syscall.WaitStatus, error) { for { wpid, status, err := wait(pid, 0) if err != nil { @@ -410,6 +455,9 @@ func trapWait(dbp *DebuggedProcess, pid int, options int) (int, *syscall.WaitSta if status.StopSignal() == syscall.SIGTRAP { return wpid, status, nil } + if status.StopSignal() == syscall.SIGSTOP && dbp.halt { + return -1, nil, ManualStopError{} + } } } diff --git a/proctl/threads.go b/proctl/threads.go index 325a8b04..7bde3406 100644 --- a/proctl/threads.go +++ b/proctl/threads.go @@ -264,7 +264,7 @@ func (thread *ThreadContext) continueToReturnAddress(pc uint64, fde *frame.Frame // change the goroutine context on us, we there is // no guarantee that waiting on this tid will ever // return. - wpid, _, err := trapWait(thread.Process, -1, 0) + wpid, _, err := trapWait(thread.Process, -1) if err != nil { return err }