terminal: make printcontext use SelectedGoroutine

printcontext should use SelectedGoroutine instead of trusting that the
goroutine running on current thread matches the SelectedGoroutine.

When the user switches to a parked goroutine CurrentThread and
SelectedGoroutine will diverge.

Almost all calls to printcontext are safe, they happen after a continue
command returns when SelectedGoroutine and CurrentThread always agree,
but the calls in frameCommand and listCommand are wrong.

Additionally we should stop reporting an error when the debugger is
stopped on an unknown PC address.
This commit is contained in:
aarzilli 2018-04-08 12:41:47 +02:00 committed by Derek Parker
parent 4e177bb99a
commit 4f70ff0a77
3 changed files with 76 additions and 10 deletions

@ -128,6 +128,9 @@ func Continue(dbp Process) error {
} }
loc, err := curthread.Location() loc, err := curthread.Location()
if err != nil || loc.Fn == nil || (loc.Fn.Name != "runtime.breakpoint" && loc.Fn.Name != "runtime.Breakpoint") { if err != nil || loc.Fn == nil || (loc.Fn.Name != "runtime.breakpoint" && loc.Fn.Name != "runtime.Breakpoint") {
if g := dbp.SelectedGoroutine(); g != nil {
g.CurrentLoc = *loc
}
break break
} }
} }

@ -73,10 +73,10 @@ func (c command) match(cmdstr string) bool {
// Commands represents the commands for Delve terminal process. // Commands represents the commands for Delve terminal process.
type Commands struct { type Commands struct {
cmds []command cmds []command
lastCmd cmdfunc lastCmd cmdfunc
client service.Client client service.Client
frame int // Current frame as set by frame/up/down commands. frame int // Current frame as set by frame/up/down commands.
} }
var ( var (
@ -1302,6 +1302,9 @@ func listCommand(t *Term, ctx callContext, args string) error {
return err return err
} }
printcontext(t, state) printcontext(t, state)
if state.SelectedGoroutine != nil {
return printfile(t, state.SelectedGoroutine.CurrentLoc.File, state.SelectedGoroutine.CurrentLoc.Line, true)
}
return printfile(t, state.CurrentThread.File, state.CurrentThread.Line, true) return printfile(t, state.CurrentThread.File, state.CurrentThread.Line, true)
case len(args) == 0 && ctx.scoped(): case len(args) == 0 && ctx.scoped():
@ -1462,13 +1465,30 @@ func printcontext(t *Term, state *api.DebuggerState) error {
fmt.Println("No current thread available") fmt.Println("No current thread available")
return nil return nil
} }
if len(state.CurrentThread.File) == 0 {
var th *api.Thread
if state.SelectedGoroutine == nil {
th = state.CurrentThread
} else {
for i := range state.Threads {
if state.Threads[i].ID == state.SelectedGoroutine.ThreadID {
th = state.Threads[i]
break
}
}
if th == nil {
printcontextLocation(state.SelectedGoroutine.CurrentLoc)
return nil
}
}
if th.File == "" {
fmt.Printf("Stopped at: 0x%x\n", state.CurrentThread.PC) fmt.Printf("Stopped at: 0x%x\n", state.CurrentThread.PC)
t.Println("=>", "no source available") t.Println("=>", "no source available")
return nil return nil
} }
printcontextThread(t, state.CurrentThread) printcontextThread(t, th)
if state.When != "" { if state.When != "" {
fmt.Println(state.When) fmt.Println(state.When)
@ -1477,14 +1497,19 @@ func printcontext(t *Term, state *api.DebuggerState) error {
return nil return nil
} }
func printcontextLocation(loc api.Location) {
fmt.Printf("> %s() %s:%d (PC: %#v)\n", loc.Function.Name, ShortenFilePath(loc.File), loc.Line, loc.PC)
if loc.Function != nil && loc.Function.Optimized {
fmt.Println(optimizedFunctionWarning)
}
return
}
func printcontextThread(t *Term, th *api.Thread) { func printcontextThread(t *Term, th *api.Thread) {
fn := th.Function fn := th.Function
if th.Breakpoint == nil { if th.Breakpoint == nil {
fmt.Printf("> %s() %s:%d (PC: %#v)\n", fn.Name, ShortenFilePath(th.File), th.Line, th.PC) printcontextLocation(api.Location{PC: th.PC, File: th.File, Line: th.Line, Function: th.Function})
if th.Function != nil && th.Function.Optimized {
fmt.Println(optimizedFunctionWarning)
}
return return
} }
@ -1561,6 +1586,9 @@ func printcontextThread(t *Term, th *api.Thread) {
} }
func printfile(t *Term, filename string, line int, showArrow bool) error { func printfile(t *Term, filename string, line int, showArrow bool) error {
if filename == "" {
return nil
}
file, err := os.Open(t.substitutePath(filename)) file, err := os.Open(t.substitutePath(filename))
if err != nil { if err != nil {
return err return err

@ -748,3 +748,38 @@ func TestIssue1090(t *testing.T) {
} }
}) })
} }
func TestPrintContextParkedGoroutine(t *testing.T) {
withTestTerminal("goroutinestackprog", t, func(term *FakeTerminal) {
term.MustExec("break stacktraceme")
term.MustExec("continue")
// pick a goroutine that isn't running on a thread
gid := ""
gout := strings.Split(term.MustExec("goroutines"), "\n")
t.Logf("goroutines -> %q", gout)
for _, gline := range gout {
if !strings.Contains(gline, "thread ") && strings.Contains(gline, "agoroutine") {
if dash := strings.Index(gline, " - "); dash > 0 {
gid = gline[len(" Goroutine "):dash]
break
}
}
}
t.Logf("picked %q", gid)
term.MustExec(fmt.Sprintf("goroutine %s", gid))
frameout := strings.Split(term.MustExec("frame 0"), "\n")
t.Logf("frame 0 -> %q", frameout)
if strings.Contains(frameout[0], "stacktraceme") {
t.Fatal("bad output for `frame 0` command on a parked goorutine")
}
listout := strings.Split(term.MustExec("list"), "\n")
t.Logf("list -> %q", listout)
if strings.Contains(listout[0], "stacktraceme") {
t.Fatal("bad output for list command on a parked goroutine")
}
})
}