proc,service,terminal: read defer list

Adds -defer flag to the stack command that decorates the stack traces
by associating each stack frame with its deferred calls.

Reworks proc.next to use this feature instead of using proc.DeferPC,
laying the groundwork to implement #1240.
This commit is contained in:
aarzilli 2018-07-06 09:37:31 +02:00 committed by Derek Parker
parent 932aad9e3d
commit 8f1fc63da8
20 changed files with 388 additions and 117 deletions

32
_fixtures/deferstack.go Normal file

@ -0,0 +1,32 @@
package main
import "runtime"
func f1() {
}
func f2() {
}
func f3() {
}
func call1() {
defer f2()
defer f1()
call2()
}
func call2() {
defer f3()
defer f2()
call3()
}
func call3() {
runtime.Breakpoint()
}
func main() {
call1()
}

@ -185,7 +185,7 @@ func TestCore(t *testing.T) {
var panickingStack []proc.Stackframe var panickingStack []proc.Stackframe
for _, g := range gs { for _, g := range gs {
t.Logf("Goroutine %d", g.ID) t.Logf("Goroutine %d", g.ID)
stack, err := g.Stacktrace(10) stack, err := g.Stacktrace(10, false)
if err != nil { if err != nil {
t.Errorf("Stacktrace() on goroutine %v = %v", g, err) t.Errorf("Stacktrace() on goroutine %v = %v", g, err)
} }
@ -329,7 +329,7 @@ func TestCoreWithEmptyString(t *testing.T) {
var mainFrame *proc.Stackframe var mainFrame *proc.Stackframe
mainSearch: mainSearch:
for _, g := range gs { for _, g := range gs {
stack, err := g.Stacktrace(10) stack, err := g.Stacktrace(10, false)
assertNoError(err, t, "Stacktrace()") assertNoError(err, t, "Stacktrace()")
for _, frame := range stack { for _, frame := range stack {
if frame.Current.Fn != nil && frame.Current.Fn.Name == "main.main" { if frame.Current.Fn != nil && frame.Current.Fn.Name == "main.main" {

@ -380,17 +380,14 @@ func StepOut(dbp Process) error {
var deferpc uint64 = 0 var deferpc uint64 = 0
if filepath.Ext(topframe.Current.File) == ".go" { if filepath.Ext(topframe.Current.File) == ".go" {
if selg != nil { if topframe.TopmostDefer != nil && topframe.TopmostDefer.DeferredPC != 0 {
deferPCEntry := selg.DeferPC() deferfn := dbp.BinInfo().PCToFunc(topframe.TopmostDefer.DeferredPC)
if deferPCEntry != 0 {
deferfn := dbp.BinInfo().PCToFunc(deferPCEntry)
deferpc, err = FirstPCAfterPrologue(dbp, deferfn, false) deferpc, err = FirstPCAfterPrologue(dbp, deferfn, false)
if err != nil { if err != nil {
return err return err
} }
} }
} }
}
if deferpc != 0 && deferpc != topframe.Current.PC { if deferpc != 0 && deferpc != topframe.Current.PC {
bp, err := dbp.SetBreakpoint(deferpc, NextDeferBreakpoint, sameGCond) bp, err := dbp.SetBreakpoint(deferpc, NextDeferBreakpoint, sameGCond)
@ -554,7 +551,7 @@ func ConvertEvalScope(dbp Process, gid, frame int) (*EvalScope, error) {
thread = g.Thread thread = g.Thread
} }
locs, err := g.Stacktrace(frame + 1) locs, err := g.Stacktrace(frame+1, false)
if err != nil { if err != nil {
return nil, err return nil, err
} }

@ -920,7 +920,7 @@ func TestStacktraceGoroutine(t *testing.T) {
mainCount := 0 mainCount := 0
for i, g := range gs { for i, g := range gs {
locations, err := g.Stacktrace(40) locations, err := g.Stacktrace(40, false)
if err != nil { if err != nil {
// On windows we do not have frame information for goroutines doing system calls. // On windows we do not have frame information for goroutines doing system calls.
t.Logf("Could not retrieve goroutine stack for goid=%d: %v", g.ID, err) t.Logf("Could not retrieve goroutine stack for goid=%d: %v", g.ID, err)
@ -1237,13 +1237,13 @@ func TestFrameEvaluation(t *testing.T) {
found := make([]bool, 10) found := make([]bool, 10)
for _, g := range gs { for _, g := range gs {
frame := -1 frame := -1
frames, err := g.Stacktrace(10) frames, err := g.Stacktrace(10, false)
if err != nil { if err != nil {
t.Logf("could not stacktrace goroutine %d: %v\n", g.ID, err) t.Logf("could not stacktrace goroutine %d: %v\n", g.ID, err)
continue continue
} }
t.Logf("Goroutine %d", g.ID) t.Logf("Goroutine %d", g.ID)
logStacktrace(t, frames) logStacktrace(t, p.BinInfo(), frames)
for i := range frames { for i := range frames {
if frames[i].Call.Fn != nil && frames[i].Call.Fn.Name == "main.agoroutine" { if frames[i].Call.Fn != nil && frames[i].Call.Fn.Name == "main.agoroutine" {
frame = i frame = i
@ -1957,7 +1957,7 @@ func TestNextParked(t *testing.T) {
if g.Thread != nil { if g.Thread != nil {
continue continue
} }
frames, _ := g.Stacktrace(5) frames, _ := g.Stacktrace(5, false)
for _, frame := range frames { for _, frame := range frames {
// line 11 is the line where wg.Done is called // line 11 is the line where wg.Done is called
if frame.Current.Fn != nil && frame.Current.Fn.Name == "main.sayhi" && frame.Current.Line < 11 { if frame.Current.Fn != nil && frame.Current.Fn.Name == "main.sayhi" && frame.Current.Line < 11 {
@ -2010,7 +2010,7 @@ func TestStepParked(t *testing.T) {
} }
t.Logf("Parked g is: %v\n", parkedg) t.Logf("Parked g is: %v\n", parkedg)
frames, _ := parkedg.Stacktrace(20) frames, _ := parkedg.Stacktrace(20, false)
for _, frame := range frames { for _, frame := range frames {
name := "" name := ""
if frame.Call.Fn != nil { if frame.Call.Fn != nil {
@ -2714,7 +2714,7 @@ func TestStacktraceWithBarriers(t *testing.T) {
goid, _ := constant.Int64Val(goidVar.Value) goid, _ := constant.Int64Val(goidVar.Value)
if g := getg(int(goid), gs); g != nil { if g := getg(int(goid), gs); g != nil {
stack, err := g.Stacktrace(50) stack, err := g.Stacktrace(50, false)
assertNoError(err, t, fmt.Sprintf("Stacktrace(goroutine = %d)", goid)) assertNoError(err, t, fmt.Sprintf("Stacktrace(goroutine = %d)", goid))
for _, frame := range stack { for _, frame := range stack {
if frame.Current.Fn != nil && frame.Current.Fn.Name == "main.bottomUpTree" { if frame.Current.Fn != nil && frame.Current.Fn.Name == "main.bottomUpTree" {
@ -2740,7 +2740,7 @@ func TestStacktraceWithBarriers(t *testing.T) {
for _, goid := range stackBarrierGoids { for _, goid := range stackBarrierGoids {
g := getg(goid, gs) g := getg(goid, gs)
stack, err := g.Stacktrace(200) stack, err := g.Stacktrace(200, false)
assertNoError(err, t, "Stacktrace()") assertNoError(err, t, "Stacktrace()")
// Check that either main.main or main.main.func1 appear in the // Check that either main.main or main.main.func1 appear in the
@ -3142,7 +3142,7 @@ func TestIssue844(t *testing.T) {
}) })
} }
func logStacktrace(t *testing.T, frames []proc.Stackframe) { func logStacktrace(t *testing.T, bi *proc.BinaryInfo, frames []proc.Stackframe) {
for j := range frames { for j := range frames {
name := "?" name := "?"
if frames[j].Current.Fn != nil { if frames[j].Current.Fn != nil {
@ -3150,6 +3150,23 @@ func logStacktrace(t *testing.T, frames []proc.Stackframe) {
} }
t.Logf("\t%#x %#x %#x %s at %s:%d\n", frames[j].Call.PC, frames[j].FrameOffset(), frames[j].FramePointerOffset(), name, filepath.Base(frames[j].Call.File), frames[j].Call.Line) t.Logf("\t%#x %#x %#x %s at %s:%d\n", frames[j].Call.PC, frames[j].FrameOffset(), frames[j].FramePointerOffset(), name, filepath.Base(frames[j].Call.File), frames[j].Call.Line)
if frames[j].TopmostDefer != nil {
f, l, fn := bi.PCToLine(frames[j].TopmostDefer.DeferredPC)
fnname := ""
if fn != nil {
fnname = fn.Name
}
t.Logf("\t\ttopmost defer: %#x %s at %s:%d\n", frames[j].TopmostDefer.DeferredPC, fnname, f, l)
}
for deferIdx, _defer := range frames[j].Defers {
f, l, fn := bi.PCToLine(_defer.DeferredPC)
fnname := ""
if fn != nil {
fnname = fn.Name
}
t.Logf("\t\t%d defer: %#x %s at %s:%d\n", deferIdx, _defer.DeferredPC, fnname, f, l)
}
} }
} }
@ -3260,11 +3277,11 @@ func TestCgoStacktrace(t *testing.T) {
} }
} }
frames, err := g.Stacktrace(100) frames, err := g.Stacktrace(100, false)
assertNoError(err, t, fmt.Sprintf("Stacktrace at iteration step %d", itidx)) assertNoError(err, t, fmt.Sprintf("Stacktrace at iteration step %d", itidx))
t.Logf("iteration step %d", itidx) t.Logf("iteration step %d", itidx)
logStacktrace(t, frames) logStacktrace(t, p.BinInfo(), frames)
m := stacktraceCheck(t, tc, frames) m := stacktraceCheck(t, tc, frames)
mismatch := (m == nil) mismatch := (m == nil)
@ -3301,7 +3318,7 @@ func TestCgoStacktrace(t *testing.T) {
if frames[j].Current.File != threadFrames[j].Current.File || frames[j].Current.Line != threadFrames[j].Current.Line { if frames[j].Current.File != threadFrames[j].Current.File || frames[j].Current.Line != threadFrames[j].Current.Line {
t.Logf("stack mismatch between goroutine stacktrace and thread stacktrace") t.Logf("stack mismatch between goroutine stacktrace and thread stacktrace")
t.Logf("thread stacktrace:") t.Logf("thread stacktrace:")
logStacktrace(t, threadFrames) logStacktrace(t, p.BinInfo(), threadFrames)
mismatch = true mismatch = true
break break
} }
@ -3348,9 +3365,9 @@ func TestSystemstackStacktrace(t *testing.T) {
assertNoError(proc.Continue(p), t, "second continue") assertNoError(proc.Continue(p), t, "second continue")
g, err := proc.GetG(p.CurrentThread()) g, err := proc.GetG(p.CurrentThread())
assertNoError(err, t, "GetG") assertNoError(err, t, "GetG")
frames, err := g.Stacktrace(100) frames, err := g.Stacktrace(100, false)
assertNoError(err, t, "stacktrace") assertNoError(err, t, "stacktrace")
logStacktrace(t, frames) logStacktrace(t, p.BinInfo(), frames)
m := stacktraceCheck(t, []string{"!runtime.startpanic_m", "runtime.gopanic", "main.main"}, frames) m := stacktraceCheck(t, []string{"!runtime.startpanic_m", "runtime.gopanic", "main.main"}, frames)
if m == nil { if m == nil {
t.Fatal("see previous loglines") t.Fatal("see previous loglines")
@ -3383,9 +3400,9 @@ func TestSystemstackOnRuntimeNewstack(t *testing.T) {
break break
} }
} }
frames, err := g.Stacktrace(100) frames, err := g.Stacktrace(100, false)
assertNoError(err, t, "stacktrace") assertNoError(err, t, "stacktrace")
logStacktrace(t, frames) logStacktrace(t, p.BinInfo(), frames)
m := stacktraceCheck(t, []string{"!runtime.newstack", "main.main"}, frames) m := stacktraceCheck(t, []string{"!runtime.newstack", "main.main"}, frames)
if m == nil { if m == nil {
t.Fatal("see previous loglines") t.Fatal("see previous loglines")
@ -3400,7 +3417,7 @@ func TestIssue1034(t *testing.T) {
_, err := setFunctionBreakpoint(p, "main.main") _, err := setFunctionBreakpoint(p, "main.main")
assertNoError(err, t, "setFunctionBreakpoint()") assertNoError(err, t, "setFunctionBreakpoint()")
assertNoError(proc.Continue(p), t, "Continue()") assertNoError(proc.Continue(p), t, "Continue()")
frames, err := p.SelectedGoroutine().Stacktrace(10) frames, err := p.SelectedGoroutine().Stacktrace(10, false)
assertNoError(err, t, "Stacktrace") assertNoError(err, t, "Stacktrace")
scope := proc.FrameToScope(p.BinInfo(), p.CurrentThread(), nil, frames[2:]...) scope := proc.FrameToScope(p.BinInfo(), p.CurrentThread(), nil, frames[2:]...)
args, _ := scope.FunctionArguments(normalLoadConfig) args, _ := scope.FunctionArguments(normalLoadConfig)
@ -3873,3 +3890,62 @@ func TestIssue1264(t *testing.T) {
assertLineNumber(p, t, 8, "after continue") assertLineNumber(p, t, 8, "after continue")
}) })
} }
func TestReadDefer(t *testing.T) {
withTestProcess("deferstack", t, func(p proc.Process, fixture protest.Fixture) {
assertNoError(proc.Continue(p), t, "Continue")
frames, err := p.SelectedGoroutine().Stacktrace(10, true)
assertNoError(err, t, "Stacktrace")
logStacktrace(t, p.BinInfo(), frames)
examples := []struct {
frameIdx int
topmostDefer string
defers []string
}{
// main.call3 (defers nothing, topmost defer main.f2)
{0, "main.f2", []string{}},
// main.call2 (defers main.f2, main.f3, topmost defer main.f2)
{1, "main.f2", []string{"main.f2", "main.f3"}},
// main.call1 (defers main.f1, main.f2, topmost defer main.f1)
{2, "main.f1", []string{"main.f1", "main.f2"}},
// main.main (defers nothing)
{3, "", []string{}}}
defercheck := func(d *proc.Defer, deferName, tgt string, frameIdx int) {
if d == nil {
t.Fatalf("expected %q as %s of frame %d, got nothing", tgt, deferName, frameIdx)
}
if d.Unreadable != nil {
t.Fatalf("expected %q as %s of frame %d, got unreadable defer: %v", tgt, deferName, frameIdx, d.Unreadable)
}
_, _, dfn := p.BinInfo().PCToLine(d.DeferredPC)
if dfn == nil {
t.Fatalf("expected %q as %s of frame %d, got %#x", tgt, deferName, frameIdx, d.DeferredPC)
}
if dfn.Name != tgt {
t.Fatalf("expected %q as %s of frame %d, got %q", tgt, deferName, frameIdx, dfn.Name)
}
}
for _, example := range examples {
frame := &frames[example.frameIdx]
if example.topmostDefer != "" {
defercheck(frame.TopmostDefer, "topmost defer", example.topmostDefer, example.frameIdx)
}
if len(example.defers) != len(frames[example.frameIdx].Defers) {
t.Fatalf("expected %d defers for %d, got %v", len(example.defers), example.frameIdx, frame.Defers)
}
for deferIdx := range example.defers {
defercheck(frame.Defers[deferIdx], fmt.Sprintf("defer %d", deferIdx), example.defers[deferIdx], example.frameIdx)
}
}
})
}

@ -4,6 +4,7 @@ import (
"debug/dwarf" "debug/dwarf"
"errors" "errors"
"fmt" "fmt"
"go/constant"
"strings" "strings"
"github.com/derekparker/delve/pkg/dwarf/frame" "github.com/derekparker/delve/pkg/dwarf/frame"
@ -57,6 +58,15 @@ type Stackframe struct {
// pkg/proc. // pkg/proc.
// Use this value to determine active lexical scopes for the stackframe. // Use this value to determine active lexical scopes for the stackframe.
lastpc uint64 lastpc uint64
// TopmostDefer is the defer that would be at the top of the stack when a
// panic unwind would get to this call frame, in other words it's the first
// deferred function that will be called if the runtime unwinds past this
// call frame.
TopmostDefer *Defer
// Defers is the list of functions deferred by this stack frame (so far).
Defers []*Defer
} }
// FrameOffset returns the address of the stack frame, absolute for system // FrameOffset returns the address of the stack frame, absolute for system
@ -91,7 +101,7 @@ func ThreadStacktrace(thread Thread, depth int) ([]Stackframe, error) {
it := newStackIterator(thread.BinInfo(), thread, thread.BinInfo().Arch.RegistersToDwarfRegisters(regs), 0, nil, -1, nil) it := newStackIterator(thread.BinInfo(), thread, thread.BinInfo().Arch.RegistersToDwarfRegisters(regs), 0, nil, -1, nil)
return it.stacktrace(depth) return it.stacktrace(depth)
} }
return g.Stacktrace(depth) return g.Stacktrace(depth, false)
} }
func (g *G) stackIterator() (*stackIterator, error) { func (g *G) stackIterator() (*stackIterator, error) {
@ -112,12 +122,19 @@ func (g *G) stackIterator() (*stackIterator, error) {
// Stacktrace returns the stack trace for a goroutine. // Stacktrace returns the stack trace for a goroutine.
// Note the locations in the array are return addresses not call addresses. // Note the locations in the array are return addresses not call addresses.
func (g *G) Stacktrace(depth int) ([]Stackframe, error) { func (g *G) Stacktrace(depth int, readDefers bool) ([]Stackframe, error) {
it, err := g.stackIterator() it, err := g.stackIterator()
if err != nil { if err != nil {
return nil, err return nil, err
} }
return it.stacktrace(depth) frames, err := it.stacktrace(depth)
if err != nil {
return nil, err
}
if readDefers {
g.readDefers(frames)
}
return frames, nil
} }
// NullAddrError is an error for a null address. // NullAddrError is an error for a null address.
@ -568,3 +585,101 @@ func (it *stackIterator) readRegisterAt(regnum uint64, addr uint64) (*op.DwarfRe
} }
return op.DwarfRegisterFromBytes(buf), nil return op.DwarfRegisterFromBytes(buf), nil
} }
// Defer represents one deferred call
type Defer struct {
DeferredPC uint64 // Value of field _defer.fn.fn, the deferred function
DeferPC uint64 // PC address of instruction that added this defer
SP uint64 // Value of SP register when this function was deferred (this field gets adjusted when the stack is moved to match the new stack space)
link *Defer // Next deferred function
variable *Variable
Unreadable error
}
// readDefers decorates the frames with the function deferred at each stack frame.
func (g *G) readDefers(frames []Stackframe) {
curdefer := g.Defer()
i := 0
// scan simultaneously frames and the curdefer linked list, assigning
// defers to their associated frames.
for {
if curdefer == nil || i >= len(frames) {
return
}
if curdefer.Unreadable != nil {
// Current defer is unreadable, stick it into the first available frame
// (so that it can be reported to the user) and exit
frames[i].Defers = append(frames[i].Defers, curdefer)
return
}
if frames[i].Err != nil {
return
}
if frames[i].TopmostDefer == nil {
frames[i].TopmostDefer = curdefer
}
if frames[i].SystemStack || curdefer.SP >= uint64(frames[i].Regs.CFA) {
// frames[i].Regs.CFA is the value that SP had before the function of
// frames[i] was called.
// This means that when curdefer.SP == frames[i].Regs.CFA then curdefer
// was added by the previous frame.
//
// curdefer.SP < frames[i].Regs.CFA means curdefer was added by a
// function further down the stack.
//
// SystemStack frames live on a different physical stack and can't be
// compared with deferred frames.
i++
} else {
frames[i].Defers = append(frames[i].Defers, curdefer)
curdefer = curdefer.Next()
}
}
}
func (d *Defer) load() {
d.variable.loadValue(LoadConfig{false, 1, 0, 0, -1})
if d.variable.Unreadable != nil {
d.Unreadable = d.variable.Unreadable
return
}
fnvar := d.variable.fieldVariable("fn").maybeDereference()
if fnvar.Addr != 0 {
fnvar = fnvar.loadFieldNamed("fn")
if fnvar.Unreadable == nil {
d.DeferredPC, _ = constant.Uint64Val(fnvar.Value)
}
}
d.DeferPC, _ = constant.Uint64Val(d.variable.fieldVariable("pc").Value)
d.SP, _ = constant.Uint64Val(d.variable.fieldVariable("sp").Value)
linkvar := d.variable.fieldVariable("link").maybeDereference()
if linkvar.Addr != 0 {
d.link = &Defer{variable: linkvar}
}
}
// spDecreasedErr is used when (*Defer).Next detects a corrupted linked
// list, specifically when after followin a link pointer the value of SP
// decreases rather than increasing or staying the same (the defer list is a
// FIFO list, nodes further down the list have been added by function calls
// further down the call stack and therefore the SP should always increase).
var spDecreasedErr = errors.New("corrupted defer list: SP decreased")
// Next returns the next defer in the linked list
func (d *Defer) Next() *Defer {
if d.link == nil {
return nil
}
d.link.load()
if d.link.SP < d.SP {
d.link.Unreadable = spDecreasedErr
}
return d.link
}

@ -79,7 +79,7 @@ func topframe(g *G, thread Thread) (Stackframe, Stackframe, error) {
} }
frames, err = ThreadStacktrace(thread, 1) frames, err = ThreadStacktrace(thread, 1)
} else { } else {
frames, err = g.Stacktrace(1) frames, err = g.Stacktrace(1, true)
} }
if err != nil { if err != nil {
return Stackframe{}, Stackframe{}, err return Stackframe{}, Stackframe{}, err
@ -224,17 +224,14 @@ func next(dbp Process, stepInto, inlinedStepOut bool) error {
// Set breakpoint on the most recently deferred function (if any) // Set breakpoint on the most recently deferred function (if any)
var deferpc uint64 = 0 var deferpc uint64 = 0
if selg != nil { if topframe.TopmostDefer != nil && topframe.TopmostDefer.DeferredPC != 0 {
deferPCEntry := selg.DeferPC() deferfn := dbp.BinInfo().PCToFunc(topframe.TopmostDefer.DeferredPC)
if deferPCEntry != 0 {
deferfn := dbp.BinInfo().PCToFunc(deferPCEntry)
var err error var err error
deferpc, err = FirstPCAfterPrologue(dbp, deferfn, false) deferpc, err = FirstPCAfterPrologue(dbp, deferfn, false)
if err != nil { if err != nil {
return err return err
} }
} }
}
if deferpc != 0 && deferpc != topframe.Current.PC { if deferpc != 0 && deferpc != topframe.Current.PC {
bp, err := dbp.SetBreakpoint(deferpc, NextDeferBreakpoint, sameGCond) bp, err := dbp.SetBreakpoint(deferpc, NextDeferBreakpoint, sameGCond)
if err != nil { if err != nil {

@ -489,29 +489,18 @@ func (v *Variable) fieldVariable(name string) *Variable {
return nil return nil
} }
// PC of entry to top-most deferred function. // Defer returns the top-most defer of the goroutine.
func (g *G) DeferPC() uint64 { func (g *G) Defer() *Defer {
if g.variable.Unreadable != nil { if g.variable.Unreadable != nil {
return 0 return nil
} }
d := g.variable.fieldVariable("_defer").maybeDereference() dvar := g.variable.fieldVariable("_defer").maybeDereference()
if d.Addr == 0 { if dvar.Addr == 0 {
return 0 return nil
} }
d.loadValue(LoadConfig{false, 1, 64, 0, -1}) d := &Defer{variable: dvar}
if d.Unreadable != nil { d.load()
return 0 return d
}
fnvar := d.fieldVariable("fn").maybeDereference()
if fnvar.Addr == 0 {
return 0
}
fnvar.loadValue(LoadConfig{false, 1, 64, 0, -1})
if fnvar.Unreadable != nil {
return 0
}
deferPC, _ := constant.Int64Val(fnvar.fieldVariable("fn").Value)
return uint64(deferPC)
} }
// From $GOROOT/src/runtime/traceback.go:597 // From $GOROOT/src/runtime/traceback.go:597

@ -503,7 +503,7 @@ func threads(t *Term, ctx callContext, args string) error {
if th.Function != nil { if th.Function != nil {
fmt.Printf("%sThread %d at %#v %s:%d %s\n", fmt.Printf("%sThread %d at %#v %s:%d %s\n",
prefix, th.ID, th.PC, ShortenFilePath(th.File), prefix, th.ID, th.PC, ShortenFilePath(th.File),
th.Line, th.Function.Name) th.Line, th.Function.Name())
} else { } else {
fmt.Printf("%sThread %s\n", prefix, formatThread(th)) fmt.Printf("%sThread %s\n", prefix, formatThread(th))
} }
@ -593,7 +593,7 @@ func goroutines(t *Term, ctx callContext, argstr string) error {
} }
fmt.Printf("%sGoroutine %s\n", prefix, formatGoroutine(g, fgl)) fmt.Printf("%sGoroutine %s\n", prefix, formatGoroutine(g, fgl))
if bPrintStack { if bPrintStack {
stack, err := t.client.Stacktrace(g.ID, 10, nil) stack, err := t.client.Stacktrace(g.ID, 10, false, nil)
if err != nil { if err != nil {
return err return err
} }
@ -682,7 +682,7 @@ func (c *Commands) frameCommand(t *Term, ctx callContext, argstr string, directi
if frame < 0 { if frame < 0 {
return fmt.Errorf("Invalid frame %d", frame) return fmt.Errorf("Invalid frame %d", frame)
} }
stack, err := t.client.Stacktrace(ctx.Scope.GoroutineID, frame, nil) stack, err := t.client.Stacktrace(ctx.Scope.GoroutineID, frame, false, nil)
if err != nil { if err != nil {
return err return err
} }
@ -731,11 +731,7 @@ const (
) )
func formatLocation(loc api.Location) string { func formatLocation(loc api.Location) string {
fname := "" return fmt.Sprintf("%s:%d %s (%#v)", ShortenFilePath(loc.File), loc.Line, loc.Function.Name(), loc.PC)
if loc.Function != nil {
fname = loc.Function.Name
}
return fmt.Sprintf("%s:%d %s (%#v)", ShortenFilePath(loc.File), loc.Line, fname, loc.PC)
} }
func formatGoroutine(g *api.Goroutine, fgl formatGoroutineLoc) string { func formatGoroutine(g *api.Goroutine, fgl formatGoroutineLoc) string {
@ -1320,7 +1316,7 @@ func stackCommand(t *Term, ctx callContext, args string) error {
if sa.full { if sa.full {
cfg = &ShortLoadConfig cfg = &ShortLoadConfig
} }
stack, err := t.client.Stacktrace(ctx.Scope.GoroutineID, sa.depth, cfg) stack, err := t.client.Stacktrace(ctx.Scope.GoroutineID, sa.depth, sa.readDefers, cfg)
if err != nil { if err != nil {
return err return err
} }
@ -1332,6 +1328,7 @@ type stackArgs struct {
depth int depth int
full bool full bool
offsets bool offsets bool
readDefers bool
} }
func parseStackArgs(argstr string) (stackArgs, error) { func parseStackArgs(argstr string) (stackArgs, error) {
@ -1347,6 +1344,8 @@ func parseStackArgs(argstr string) (stackArgs, error) {
r.full = true r.full = true
case "-offsets": case "-offsets":
r.offsets = true r.offsets = true
case "-defer":
r.readDefers = true
default: default:
n, err := strconv.Atoi(args[i]) n, err := strconv.Atoi(args[i])
if err != nil { if err != nil {
@ -1373,7 +1372,7 @@ func listCommand(t *Term, ctx callContext, args string) error {
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():
locs, err := t.client.Stacktrace(ctx.Scope.GoroutineID, ctx.Scope.Frame, nil) locs, err := t.client.Stacktrace(ctx.Scope.GoroutineID, ctx.Scope.Frame, false, nil)
if err != nil { if err != nil {
return err return err
} }
@ -1487,6 +1486,15 @@ func printStack(stack []api.Stackframe, ind string, offsets bool) {
if len(stack) == 0 { if len(stack) == 0 {
return return
} }
extranl := offsets
for i := range stack {
if extranl {
break
}
extranl = extranl || (len(stack[i].Defers) > 0) || (len(stack[i].Arguments) > 0) || (len(stack[i].Locals) > 0)
}
d := digits(len(stack) - 1) d := digits(len(stack) - 1)
fmtstr := "%s%" + strconv.Itoa(d) + "d 0x%016x in %s\n" fmtstr := "%s%" + strconv.Itoa(d) + "d 0x%016x in %s\n"
s := ind + strings.Repeat(" ", d+2+len(ind)) s := ind + strings.Repeat(" ", d+2+len(ind))
@ -1496,23 +1504,35 @@ func printStack(stack []api.Stackframe, ind string, offsets bool) {
fmt.Printf("%serror: %s\n", s, stack[i].Err) fmt.Printf("%serror: %s\n", s, stack[i].Err)
continue continue
} }
name := "(nil)" fmt.Printf(fmtstr, ind, i, stack[i].PC, stack[i].Function.Name())
if stack[i].Function != nil {
name = stack[i].Function.Name
}
fmt.Printf(fmtstr, ind, i, stack[i].PC, name)
fmt.Printf("%sat %s:%d\n", s, ShortenFilePath(stack[i].File), stack[i].Line) fmt.Printf("%sat %s:%d\n", s, ShortenFilePath(stack[i].File), stack[i].Line)
if offsets { if offsets {
fmt.Printf("%sframe: %+#x frame pointer %+#x\n", s, stack[i].FrameOffset, stack[i].FramePointerOffset) fmt.Printf("%sframe: %+#x frame pointer %+#x\n", s, stack[i].FrameOffset, stack[i].FramePointerOffset)
} }
for j, d := range stack[i].Defers {
deferHeader := fmt.Sprintf("%s defer %d: ", s, j)
s2 := strings.Repeat(" ", len(deferHeader))
if d.Unreadable != "" {
fmt.Printf("%s(unreadable defer: %s)\n", deferHeader, d.Unreadable)
continue
}
fmt.Printf("%s%#016x in %s\n", deferHeader, d.DeferredLoc.PC, d.DeferredLoc.Function.Name())
fmt.Printf("%sat %s:%d\n", s2, d.DeferredLoc.File, d.DeferredLoc.Line)
fmt.Printf("%sdeferred by %s at %s:%d\n", s2, d.DeferLoc.Function.Name(), d.DeferLoc.File, d.DeferLoc.Line)
}
for j := range stack[i].Arguments { for j := range stack[i].Arguments {
fmt.Printf("%s %s = %s\n", s, stack[i].Arguments[j].Name, stack[i].Arguments[j].SinglelineString()) fmt.Printf("%s %s = %s\n", s, stack[i].Arguments[j].Name, stack[i].Arguments[j].SinglelineString())
} }
for j := range stack[i].Locals { for j := range stack[i].Locals {
fmt.Printf("%s %s = %s\n", s, stack[i].Locals[j].Name, stack[i].Locals[j].SinglelineString()) fmt.Printf("%s %s = %s\n", s, stack[i].Locals[j].Name, stack[i].Locals[j].SinglelineString())
} }
if extranl {
fmt.Println()
}
} }
} }
@ -1563,7 +1583,7 @@ func printcontext(t *Term, state *api.DebuggerState) error {
} }
func printcontextLocation(loc api.Location) { func printcontextLocation(loc api.Location) {
fmt.Printf("> %s() %s:%d (PC: %#v)\n", loc.Function.Name, ShortenFilePath(loc.File), loc.Line, loc.PC) 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 { if loc.Function != nil && loc.Function.Optimized {
fmt.Println(optimizedFunctionWarning) fmt.Println(optimizedFunctionWarning)
} }
@ -1607,7 +1627,7 @@ func printcontextThread(t *Term, th *api.Thread) {
if hitCount, ok := th.Breakpoint.HitCount[strconv.Itoa(th.GoroutineID)]; ok { if hitCount, ok := th.Breakpoint.HitCount[strconv.Itoa(th.GoroutineID)]; ok {
fmt.Printf("> %s%s(%s) %s:%d (hits goroutine(%d):%d total:%d) (PC: %#v)\n", fmt.Printf("> %s%s(%s) %s:%d (hits goroutine(%d):%d total:%d) (PC: %#v)\n",
bpname, bpname,
fn.Name, fn.Name(),
args, args,
ShortenFilePath(th.File), ShortenFilePath(th.File),
th.Line, th.Line,
@ -1618,7 +1638,7 @@ func printcontextThread(t *Term, th *api.Thread) {
} else { } else {
fmt.Printf("> %s%s(%s) %s:%d (hits total:%d) (PC: %#v)\n", fmt.Printf("> %s%s(%s) %s:%d (hits total:%d) (PC: %#v)\n",
bpname, bpname,
fn.Name, fn.Name(),
args, args,
ShortenFilePath(th.File), ShortenFilePath(th.File),
th.Line, th.Line,
@ -1829,11 +1849,7 @@ func checkpoint(t *Term, ctx callContext, args string) error {
if state.SelectedGoroutine != nil { if state.SelectedGoroutine != nil {
loc = state.SelectedGoroutine.CurrentLoc loc = state.SelectedGoroutine.CurrentLoc
} }
fname := "???" args = fmt.Sprintf("%s() %s:%d (%#x)", loc.Function.Name(), loc.File, loc.Line, loc.PC)
if loc.Function != nil {
fname = loc.Function.Name
}
args = fmt.Sprintf("%s() %s:%d (%#x)", fname, loc.File, loc.Line, loc.PC)
} }
cpid, err := t.client.Checkpoint(args) cpid, err := t.client.Checkpoint(args)

@ -224,7 +224,7 @@ func TestExecuteFile(t *testing.T) {
func TestIssue354(t *testing.T) { func TestIssue354(t *testing.T) {
printStack([]api.Stackframe{}, "", false) printStack([]api.Stackframe{}, "", false)
printStack([]api.Stackframe{{api.Location{PC: 0, File: "irrelevant.go", Line: 10, Function: nil}, nil, nil, 0, 0, ""}}, "", false) printStack([]api.Stackframe{{api.Location{PC: 0, File: "irrelevant.go", Line: 10, Function: nil}, nil, nil, 0, 0, nil, ""}}, "", false)
} }
func TestIssue411(t *testing.T) { func TestIssue411(t *testing.T) {

@ -14,7 +14,7 @@ func DisasmPrint(dv api.AsmInstructions, out io.Writer) {
bw := bufio.NewWriter(out) bw := bufio.NewWriter(out)
defer bw.Flush() defer bw.Flush()
if len(dv) > 0 && dv[0].Loc.Function != nil { if len(dv) > 0 && dv[0].Loc.Function != nil {
fmt.Fprintf(bw, "TEXT %s(SB) %s\n", dv[0].Loc.Function.Name, dv[0].Loc.File) fmt.Fprintf(bw, "TEXT %s(SB) %s\n", dv[0].Loc.Function.Name(), dv[0].Loc.File)
} }
tw := tabwriter.NewWriter(bw, 1, 8, 1, '\t', 0) tw := tabwriter.NewWriter(bw, 1, 8, 1, '\t', 0)
defer tw.Flush() defer tw.Flush()

@ -215,7 +215,7 @@ func ConvertFunction(fn *proc.Function) *Function {
// those fields is not documented their value was replaced with 0 when // those fields is not documented their value was replaced with 0 when
// gosym.Func was replaced by debug_info entries. // gosym.Func was replaced by debug_info entries.
return &Function{ return &Function{
Name: fn.Name, Name_: fn.Name,
Type: 0, Type: 0,
Value: fn.Entry, Value: fn.Entry,
GoType: 0, GoType: 0,

@ -129,9 +129,18 @@ type Stackframe struct {
FrameOffset int64 FrameOffset int64
FramePointerOffset int64 FramePointerOffset int64
Defers []Defer
Err string Err string
} }
type Defer struct {
DeferredLoc Location // deferred function
DeferLoc Location // location of the defer statement
SP uint64 // value of SP when the function was deferred
Unreadable string
}
func (frame *Stackframe) Var(name string) *Variable { func (frame *Stackframe) Var(name string) *Variable {
for i := range frame.Locals { for i := range frame.Locals {
if frame.Locals[i].Name == name { if frame.Locals[i].Name == name {
@ -149,7 +158,7 @@ func (frame *Stackframe) Var(name string) *Variable {
// Function represents thread-scoped function information. // Function represents thread-scoped function information.
type Function struct { type Function struct {
// Name is the function name. // Name is the function name.
Name string `json:"name"` Name_ string `json:"name"`
Value uint64 `json:"value"` Value uint64 `json:"value"`
Type byte `json:"type"` Type byte `json:"type"`
GoType uint64 `json:"goType"` GoType uint64 `json:"goType"`
@ -157,6 +166,13 @@ type Function struct {
Optimized bool `json:"optimized"` Optimized bool `json:"optimized"`
} }
func (fn *Function) Name() string {
if fn == nil {
return "???"
}
return fn.Name_
}
// VariableFlags is the type of the Flags field of Variable. // VariableFlags is the type of the Flags field of Variable.
type VariableFlags uint16 type VariableFlags uint16

@ -98,7 +98,7 @@ type Client interface {
ListGoroutines() ([]*api.Goroutine, error) ListGoroutines() ([]*api.Goroutine, error)
// Returns stacktrace // Returns stacktrace
Stacktrace(int, int, *api.LoadConfig) ([]api.Stackframe, error) Stacktrace(goroutineID int, depth int, readDefers bool, cfg *api.LoadConfig) ([]api.Stackframe, error)
// Returns whether we attached to a running process or not // Returns whether we attached to a running process or not
AttachedToExistingProcess() bool AttachedToExistingProcess() bool

@ -895,7 +895,7 @@ func (d *Debugger) Goroutines() ([]*api.Goroutine, error) {
// Stacktrace returns a list of Stackframes for the given goroutine. The // Stacktrace returns a list of Stackframes for the given goroutine. The
// length of the returned list will be min(stack_len, depth). // length of the returned list will be min(stack_len, depth).
// If 'full' is true, then local vars, function args, etc will be returned as well. // If 'full' is true, then local vars, function args, etc will be returned as well.
func (d *Debugger) Stacktrace(goroutineID, depth int, cfg *proc.LoadConfig) ([]api.Stackframe, error) { func (d *Debugger) Stacktrace(goroutineID, depth int, readDefers bool, cfg *proc.LoadConfig) ([]api.Stackframe, error) {
d.processMutex.Lock() d.processMutex.Lock()
defer d.processMutex.Unlock() defer d.processMutex.Unlock()
@ -913,7 +913,7 @@ func (d *Debugger) Stacktrace(goroutineID, depth int, cfg *proc.LoadConfig) ([]a
if g == nil { if g == nil {
rawlocs, err = proc.ThreadStacktrace(d.target.CurrentThread(), depth) rawlocs, err = proc.ThreadStacktrace(d.target.CurrentThread(), depth)
} else { } else {
rawlocs, err = g.Stacktrace(depth) rawlocs, err = g.Stacktrace(depth, readDefers)
} }
if err != nil { if err != nil {
return nil, err return nil, err
@ -930,6 +930,8 @@ func (d *Debugger) convertStacktrace(rawlocs []proc.Stackframe, cfg *proc.LoadCo
FrameOffset: rawlocs[i].FrameOffset(), FrameOffset: rawlocs[i].FrameOffset(),
FramePointerOffset: rawlocs[i].FramePointerOffset(), FramePointerOffset: rawlocs[i].FramePointerOffset(),
Defers: d.convertDefers(rawlocs[i].Defers),
} }
if rawlocs[i].Err != nil { if rawlocs[i].Err != nil {
frame.Err = rawlocs[i].Err.Error() frame.Err = rawlocs[i].Err.Error()
@ -955,6 +957,36 @@ func (d *Debugger) convertStacktrace(rawlocs []proc.Stackframe, cfg *proc.LoadCo
return locations, nil return locations, nil
} }
func (d *Debugger) convertDefers(defers []*proc.Defer) []api.Defer {
r := make([]api.Defer, len(defers))
for i := range defers {
ddf, ddl, ddfn := d.target.BinInfo().PCToLine(defers[i].DeferredPC)
drf, drl, drfn := d.target.BinInfo().PCToLine(defers[i].DeferPC)
r[i] = api.Defer{
DeferredLoc: api.ConvertLocation(proc.Location{
PC: defers[i].DeferredPC,
File: ddf,
Line: ddl,
Fn: ddfn,
}),
DeferLoc: api.ConvertLocation(proc.Location{
PC: defers[i].DeferPC,
File: drf,
Line: drl,
Fn: drfn,
}),
SP: defers[i].SP,
}
if defers[i].Unreadable != nil {
r[i].Unreadable = defers[i].Unreadable.Error()
}
}
return r
}
// FindLocation will find the location specified by 'locStr'. // FindLocation will find the location specified by 'locStr'.
func (d *Debugger) FindLocation(scope api.EvalScope, locStr string) ([]api.Location, error) { func (d *Debugger) FindLocation(scope api.EvalScope, locStr string) ([]api.Location, error) {
d.processMutex.Lock() d.processMutex.Lock()

@ -316,7 +316,7 @@ func (ale AmbiguousLocationError) Error() string {
var candidates []string var candidates []string
if ale.CandidatesLocation != nil { if ale.CandidatesLocation != nil {
for i := range ale.CandidatesLocation { for i := range ale.CandidatesLocation {
candidates = append(candidates, ale.CandidatesLocation[i].Function.Name) candidates = append(candidates, ale.CandidatesLocation[i].Function.Name())
} }
} else { } else {

@ -87,7 +87,7 @@ func (s *RPCServer) StacktraceGoroutine(args *StacktraceGoroutineArgs, locations
if args.Full { if args.Full {
loadcfg = &defaultLoadConfig loadcfg = &defaultLoadConfig
} }
locs, err := s.debugger.Stacktrace(args.Id, args.Depth, loadcfg) locs, err := s.debugger.Stacktrace(args.Id, args.Depth, false, loadcfg)
if err != nil { if err != nil {
return err return err
} }

@ -295,9 +295,9 @@ func (c *RPCClient) ListGoroutines() ([]*api.Goroutine, error) {
return out.Goroutines, err return out.Goroutines, err
} }
func (c *RPCClient) Stacktrace(goroutineId, depth int, cfg *api.LoadConfig) ([]api.Stackframe, error) { func (c *RPCClient) Stacktrace(goroutineId, depth int, readDefers bool, cfg *api.LoadConfig) ([]api.Stackframe, error) {
var out StacktraceOut var out StacktraceOut
err := c.call("Stacktrace", StacktraceIn{goroutineId, depth, false, cfg}, &out) err := c.call("Stacktrace", StacktraceIn{goroutineId, depth, false, readDefers, cfg}, &out)
return out.Locations, err return out.Locations, err
} }

@ -155,6 +155,7 @@ type StacktraceIn struct {
Id int Id int
Depth int Depth int
Full bool Full bool
Defers bool // read deferred functions
Cfg *api.LoadConfig Cfg *api.LoadConfig
} }
@ -171,7 +172,7 @@ func (s *RPCServer) Stacktrace(arg StacktraceIn, out *StacktraceOut) error {
if cfg == nil && arg.Full { if cfg == nil && arg.Full {
cfg = &api.LoadConfig{true, 1, 64, 64, -1} cfg = &api.LoadConfig{true, 1, 64, 64, -1}
} }
locs, err := s.debugger.Stacktrace(arg.Id, arg.Depth, api.LoadConfigToProc(cfg)) locs, err := s.debugger.Stacktrace(arg.Id, arg.Depth, arg.Defers, api.LoadConfigToProc(cfg))
if err != nil { if err != nil {
return err return err
} }

@ -732,7 +732,7 @@ func Test1ClientServer_FullStacktrace(t *testing.T) {
if frame.Function == nil { if frame.Function == nil {
continue continue
} }
if frame.Function.Name != "main.agoroutine" { if frame.Function.Name() != "main.agoroutine" {
continue continue
} }
t.Logf("frame %d: %v", i, frame) t.Logf("frame %d: %v", i, frame)
@ -887,7 +887,7 @@ func Test1Disasm(t *testing.T) {
// look for static call to afunction() on line 29 // look for static call to afunction() on line 29
found := false found := false
for i := range d3 { for i := range d3 {
if d3[i].Loc.Line == 29 && strings.HasPrefix(d3[i].Text, "call") && d3[i].DestLoc != nil && d3[i].DestLoc.Function != nil && d3[i].DestLoc.Function.Name == "main.afunction" { if d3[i].Loc.Line == 29 && strings.HasPrefix(d3[i].Text, "call") && d3[i].DestLoc != nil && d3[i].DestLoc.Function != nil && d3[i].DestLoc.Function.Name() == "main.afunction" {
found = true found = true
break break
} }
@ -937,7 +937,7 @@ func Test1Disasm(t *testing.T) {
if curinstr.DestLoc == nil || curinstr.DestLoc.Function == nil { if curinstr.DestLoc == nil || curinstr.DestLoc.Function == nil {
t.Fatalf("Call instruction does not have destination: %v", curinstr) t.Fatalf("Call instruction does not have destination: %v", curinstr)
} }
if curinstr.DestLoc.Function.Name != "main.afunction" { if curinstr.DestLoc.Function.Name() != "main.afunction" {
t.Fatalf("Call instruction destination not main.afunction: %v", curinstr) t.Fatalf("Call instruction destination not main.afunction: %v", curinstr)
} }
break break

@ -798,13 +798,13 @@ func TestClientServer_FullStacktrace(t *testing.T) {
assertNoError(err, t, "GoroutinesInfo()") assertNoError(err, t, "GoroutinesInfo()")
found := make([]bool, 10) found := make([]bool, 10)
for _, g := range gs { for _, g := range gs {
frames, err := c.Stacktrace(g.ID, 10, &normalLoadConfig) frames, err := c.Stacktrace(g.ID, 10, false, &normalLoadConfig)
assertNoError(err, t, fmt.Sprintf("Stacktrace(%d)", g.ID)) assertNoError(err, t, fmt.Sprintf("Stacktrace(%d)", g.ID))
for i, frame := range frames { for i, frame := range frames {
if frame.Function == nil { if frame.Function == nil {
continue continue
} }
if frame.Function.Name != "main.agoroutine" { if frame.Function.Name() != "main.agoroutine" {
continue continue
} }
t.Logf("frame %d: %v", i, frame) t.Logf("frame %d: %v", i, frame)
@ -832,7 +832,7 @@ func TestClientServer_FullStacktrace(t *testing.T) {
t.Fatalf("Continue(): %v\n", state.Err) t.Fatalf("Continue(): %v\n", state.Err)
} }
frames, err := c.Stacktrace(-1, 10, &normalLoadConfig) frames, err := c.Stacktrace(-1, 10, false, &normalLoadConfig)
assertNoError(err, t, "Stacktrace") assertNoError(err, t, "Stacktrace")
cur := 3 cur := 3
@ -911,7 +911,7 @@ func TestIssue355(t *testing.T) {
assertError(err, t, "ListRegisters()") assertError(err, t, "ListRegisters()")
_, err = c.ListGoroutines() _, err = c.ListGoroutines()
assertError(err, t, "ListGoroutines()") assertError(err, t, "ListGoroutines()")
_, err = c.Stacktrace(gid, 10, &normalLoadConfig) _, err = c.Stacktrace(gid, 10, false, &normalLoadConfig)
assertError(err, t, "Stacktrace()") assertError(err, t, "Stacktrace()")
_, err = c.FindLocation(api.EvalScope{gid, 0}, "+1") _, err = c.FindLocation(api.EvalScope{gid, 0}, "+1")
assertError(err, t, "FindLocation()") assertError(err, t, "FindLocation()")
@ -964,7 +964,7 @@ func TestDisasm(t *testing.T) {
// look for static call to afunction() on line 29 // look for static call to afunction() on line 29
found := false found := false
for i := range d3 { for i := range d3 {
if d3[i].Loc.Line == 29 && strings.HasPrefix(d3[i].Text, "call") && d3[i].DestLoc != nil && d3[i].DestLoc.Function != nil && d3[i].DestLoc.Function.Name == "main.afunction" { if d3[i].Loc.Line == 29 && strings.HasPrefix(d3[i].Text, "call") && d3[i].DestLoc != nil && d3[i].DestLoc.Function != nil && d3[i].DestLoc.Function.Name() == "main.afunction" {
found = true found = true
break break
} }
@ -1014,7 +1014,7 @@ func TestDisasm(t *testing.T) {
if curinstr.DestLoc == nil || curinstr.DestLoc.Function == nil { if curinstr.DestLoc == nil || curinstr.DestLoc.Function == nil {
t.Fatalf("Call instruction does not have destination: %v", curinstr) t.Fatalf("Call instruction does not have destination: %v", curinstr)
} }
if curinstr.DestLoc.Function.Name != "main.afunction" { if curinstr.DestLoc.Function.Name() != "main.afunction" {
t.Fatalf("Call instruction destination not main.afunction: %v", curinstr) t.Fatalf("Call instruction destination not main.afunction: %v", curinstr)
} }
break break
@ -1034,7 +1034,7 @@ func TestNegativeStackDepthBug(t *testing.T) {
ch := c.Continue() ch := c.Continue()
state := <-ch state := <-ch
assertNoError(state.Err, t, "Continue()") assertNoError(state.Err, t, "Continue()")
_, err = c.Stacktrace(-1, -2, &normalLoadConfig) _, err = c.Stacktrace(-1, -2, false, &normalLoadConfig)
assertError(err, t, "Stacktrace()") assertError(err, t, "Stacktrace()")
}) })
} }
@ -1493,7 +1493,7 @@ func TestAcceptMulticlient(t *testing.T) {
client2 := rpc2.NewClient(listener.Addr().String()) client2 := rpc2.NewClient(listener.Addr().String())
state := <-client2.Continue() state := <-client2.Continue()
if state.CurrentThread.Function.Name != "main.main" { if state.CurrentThread.Function.Name() != "main.main" {
t.Fatalf("bad state after continue: %v\n", state) t.Fatalf("bad state after continue: %v\n", state)
} }
client2.Detach(true) client2.Detach(true)
@ -1516,12 +1516,12 @@ func TestClientServerFunctionCall(t *testing.T) {
c.SetReturnValuesLoadConfig(&normalLoadConfig) c.SetReturnValuesLoadConfig(&normalLoadConfig)
state := <-c.Continue() state := <-c.Continue()
assertNoError(state.Err, t, "Continue()") assertNoError(state.Err, t, "Continue()")
beforeCallFn := state.CurrentThread.Function.Name beforeCallFn := state.CurrentThread.Function.Name()
state, err := c.Call("call1(one, two)") state, err := c.Call("call1(one, two)")
assertNoError(err, t, "Call()") assertNoError(err, t, "Call()")
t.Logf("returned to %q", state.CurrentThread.Function.Name) t.Logf("returned to %q", state.CurrentThread.Function.Name())
if state.CurrentThread.Function.Name != beforeCallFn { if state.CurrentThread.Function.Name() != beforeCallFn {
t.Fatalf("did not return to the calling function %q %q", beforeCallFn, state.CurrentThread.Function.Name) t.Fatalf("did not return to the calling function %q %q", beforeCallFn, state.CurrentThread.Function.Name())
} }
if state.CurrentThread.ReturnValues == nil { if state.CurrentThread.ReturnValues == nil {
t.Fatal("no return values on return from call") t.Fatal("no return values on return from call")