Skip to content

Commit

Permalink
proc,service,terminal: read defer list
Browse files Browse the repository at this point in the history
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 go-delve#1240.
  • Loading branch information
aarzilli authored and derekparker committed Jul 24, 2018
1 parent 932aad9 commit 8f1fc63
Show file tree
Hide file tree
Showing 20 changed files with 388 additions and 117 deletions.
32 changes: 32 additions & 0 deletions _fixtures/deferstack.go
Original file line number Diff line number Diff line change
@@ -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()
}
4 changes: 2 additions & 2 deletions pkg/proc/core/core_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ func TestCore(t *testing.T) {
var panickingStack []proc.Stackframe
for _, g := range gs {
t.Logf("Goroutine %d", g.ID)
stack, err := g.Stacktrace(10)
stack, err := g.Stacktrace(10, false)
if err != nil {
t.Errorf("Stacktrace() on goroutine %v = %v", g, err)
}
Expand Down Expand Up @@ -329,7 +329,7 @@ func TestCoreWithEmptyString(t *testing.T) {
var mainFrame *proc.Stackframe
mainSearch:
for _, g := range gs {
stack, err := g.Stacktrace(10)
stack, err := g.Stacktrace(10, false)
assertNoError(err, t, "Stacktrace()")
for _, frame := range stack {
if frame.Current.Fn != nil && frame.Current.Fn.Name == "main.main" {
Expand Down
15 changes: 6 additions & 9 deletions pkg/proc/proc.go
Original file line number Diff line number Diff line change
Expand Up @@ -380,14 +380,11 @@ func StepOut(dbp Process) error {

var deferpc uint64 = 0
if filepath.Ext(topframe.Current.File) == ".go" {
if selg != nil {
deferPCEntry := selg.DeferPC()
if deferPCEntry != 0 {
deferfn := dbp.BinInfo().PCToFunc(deferPCEntry)
deferpc, err = FirstPCAfterPrologue(dbp, deferfn, false)
if err != nil {
return err
}
if topframe.TopmostDefer != nil && topframe.TopmostDefer.DeferredPC != 0 {
deferfn := dbp.BinInfo().PCToFunc(topframe.TopmostDefer.DeferredPC)
deferpc, err = FirstPCAfterPrologue(dbp, deferfn, false)
if err != nil {
return err
}
}
}
Expand Down Expand Up @@ -554,7 +551,7 @@ func ConvertEvalScope(dbp Process, gid, frame int) (*EvalScope, error) {
thread = g.Thread
}

locs, err := g.Stacktrace(frame + 1)
locs, err := g.Stacktrace(frame+1, false)
if err != nil {
return nil, err
}
Expand Down
108 changes: 92 additions & 16 deletions pkg/proc/proc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -920,7 +920,7 @@ func TestStacktraceGoroutine(t *testing.T) {
mainCount := 0

for i, g := range gs {
locations, err := g.Stacktrace(40)
locations, err := g.Stacktrace(40, false)
if err != nil {
// 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)
Expand Down Expand Up @@ -1237,13 +1237,13 @@ func TestFrameEvaluation(t *testing.T) {
found := make([]bool, 10)
for _, g := range gs {
frame := -1
frames, err := g.Stacktrace(10)
frames, err := g.Stacktrace(10, false)
if err != nil {
t.Logf("could not stacktrace goroutine %d: %v\n", g.ID, err)
continue
}
t.Logf("Goroutine %d", g.ID)
logStacktrace(t, frames)
logStacktrace(t, p.BinInfo(), frames)
for i := range frames {
if frames[i].Call.Fn != nil && frames[i].Call.Fn.Name == "main.agoroutine" {
frame = i
Expand Down Expand Up @@ -1957,7 +1957,7 @@ func TestNextParked(t *testing.T) {
if g.Thread != nil {
continue
}
frames, _ := g.Stacktrace(5)
frames, _ := g.Stacktrace(5, false)
for _, frame := range frames {
// 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 {
Expand Down Expand Up @@ -2010,7 +2010,7 @@ func TestStepParked(t *testing.T) {
}

t.Logf("Parked g is: %v\n", parkedg)
frames, _ := parkedg.Stacktrace(20)
frames, _ := parkedg.Stacktrace(20, false)
for _, frame := range frames {
name := ""
if frame.Call.Fn != nil {
Expand Down Expand Up @@ -2714,7 +2714,7 @@ func TestStacktraceWithBarriers(t *testing.T) {
goid, _ := constant.Int64Val(goidVar.Value)

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))
for _, frame := range stack {
if frame.Current.Fn != nil && frame.Current.Fn.Name == "main.bottomUpTree" {
Expand All @@ -2740,7 +2740,7 @@ func TestStacktraceWithBarriers(t *testing.T) {
for _, goid := range stackBarrierGoids {
g := getg(goid, gs)

stack, err := g.Stacktrace(200)
stack, err := g.Stacktrace(200, false)
assertNoError(err, t, "Stacktrace()")

// Check that either main.main or main.main.func1 appear in the
Expand Down Expand Up @@ -3142,14 +3142,31 @@ 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 {
name := "?"
if frames[j].Current.Fn != nil {
name = frames[j].Current.Fn.Name
}

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)

}
}
}

Expand Down Expand Up @@ -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))

t.Logf("iteration step %d", itidx)
logStacktrace(t, frames)
logStacktrace(t, p.BinInfo(), frames)

m := stacktraceCheck(t, tc, frames)
mismatch := (m == nil)
Expand Down Expand Up @@ -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 {
t.Logf("stack mismatch between goroutine stacktrace and thread stacktrace")
t.Logf("thread stacktrace:")
logStacktrace(t, threadFrames)
logStacktrace(t, p.BinInfo(), threadFrames)
mismatch = true
break
}
Expand Down Expand Up @@ -3348,9 +3365,9 @@ func TestSystemstackStacktrace(t *testing.T) {
assertNoError(proc.Continue(p), t, "second continue")
g, err := proc.GetG(p.CurrentThread())
assertNoError(err, t, "GetG")
frames, err := g.Stacktrace(100)
frames, err := g.Stacktrace(100, false)
assertNoError(err, t, "stacktrace")
logStacktrace(t, frames)
logStacktrace(t, p.BinInfo(), frames)
m := stacktraceCheck(t, []string{"!runtime.startpanic_m", "runtime.gopanic", "main.main"}, frames)
if m == nil {
t.Fatal("see previous loglines")
Expand Down Expand Up @@ -3383,9 +3400,9 @@ func TestSystemstackOnRuntimeNewstack(t *testing.T) {
break
}
}
frames, err := g.Stacktrace(100)
frames, err := g.Stacktrace(100, false)
assertNoError(err, t, "stacktrace")
logStacktrace(t, frames)
logStacktrace(t, p.BinInfo(), frames)
m := stacktraceCheck(t, []string{"!runtime.newstack", "main.main"}, frames)
if m == nil {
t.Fatal("see previous loglines")
Expand All @@ -3400,7 +3417,7 @@ func TestIssue1034(t *testing.T) {
_, err := setFunctionBreakpoint(p, "main.main")
assertNoError(err, t, "setFunctionBreakpoint()")
assertNoError(proc.Continue(p), t, "Continue()")
frames, err := p.SelectedGoroutine().Stacktrace(10)
frames, err := p.SelectedGoroutine().Stacktrace(10, false)
assertNoError(err, t, "Stacktrace")
scope := proc.FrameToScope(p.BinInfo(), p.CurrentThread(), nil, frames[2:]...)
args, _ := scope.FunctionArguments(normalLoadConfig)
Expand Down Expand Up @@ -3873,3 +3890,62 @@ func TestIssue1264(t *testing.T) {
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)
}
}
})
}
Loading

0 comments on commit 8f1fc63

Please sign in to comment.