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()
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
}
}

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