From bcd63efbd62fc89d317558c7f162e591a66697ac Mon Sep 17 00:00:00 2001 From: aarzilli Date: Fri, 24 May 2024 08:46:57 +0200 Subject: [PATCH] proc: initial stepping with range-over-func support Initial support for stepping in functions that use the new range-over-func statement in go1.23. Does not support: - inlining - viewing variables of the enclosing function from a range-over-func body closure - the correct way to find the enclosing function from a range-over-func body closure (but it should work most of the time). Updates #3733 --- _fixtures/rangeoverfunc.go | 328 ++++++++++++++++++++ pkg/proc/bininfo.go | 133 ++++++-- pkg/proc/proc_test.go | 608 +++++++++++++++++++++++++++++++------ pkg/proc/stack.go | 184 +++++++++-- pkg/proc/target_exec.go | 103 ++++++- pkg/proc/variables.go | 4 +- 6 files changed, 1204 insertions(+), 156 deletions(-) create mode 100644 _fixtures/rangeoverfunc.go diff --git a/_fixtures/rangeoverfunc.go b/_fixtures/rangeoverfunc.go new file mode 100644 index 0000000000..9ef7522333 --- /dev/null +++ b/_fixtures/rangeoverfunc.go @@ -0,0 +1,328 @@ +package main + +// The tests here are adapted from $GOROOT/src/cmd/compile/internal/rangefunc/rangefunc_test.go + +import ( + "fmt" +) + +/* + + + +THESE +LINES +INTENTIONALLY +LEFT +BLANK + + + + +*/ + +func TestTrickyIterAll() { + trickItAll := TrickyIterator{} + i := 0 + for _, x := range trickItAll.iterAll([]int{30, 7, 8, 9, 10}) { + i += x + if i >= 36 { + break + } + } + + fmt.Println("Got i = ", i) +} + +func TestTrickyIterAll2() { + trickItAll := TrickyIterator{} + i := 0 + for _, x := range trickItAll.iterAll([]int{42, 47}) { + i += x + } + fmt.Println(i) +} + +func TestBreak1() { + var result []int + for _, x := range OfSliceIndex([]int{-1, -2, -4}) { + if x == -4 { + break + } + for _, y := range OfSliceIndex([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) { + if y == 3 { + break + } + result = append(result, y) + } + result = append(result, x) + } + fmt.Println(result) +} + +func TestBreak2() { + var result []int +outer: + for _, x := range OfSliceIndex([]int{-1, -2, -4}) { + for _, y := range OfSliceIndex([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) { + if y == 3 { + break + } + if x == -4 { + break outer + } + result = append(result, y) + } + result = append(result, x) + } + fmt.Println(result) +} + +func TestMultiCont0() { + var result []int + +W: + for _, w := range OfSliceIndex([]int{1000, 2000}) { + result = append(result, w) + if w == 2000 { + break + } + for _, x := range OfSliceIndex([]int{100, 200, 300, 400}) { + for _, y := range OfSliceIndex([]int{10, 20, 30, 40}) { + result = append(result, y) + for _, z := range OfSliceIndex([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) { + if z&1 == 1 { + continue + } + result = append(result, z) + if z >= 4 { + continue W // modified to be multilevel + } + } + result = append(result, -y) // should never be executed + } + result = append(result, x) + } + } + fmt.Println(result) +} + +func TestPanickyIterator1() { + var result []int + defer func() { + r := recover() + fmt.Println("Recovering", r) + }() + for _, z := range PanickyOfSliceIndex([]int{1, 2, 3, 4}) { + result = append(result, z) + if z == 4 { + break + } + } + fmt.Println(result) +} + +func TestPanickyIterator2() { + var result []int + defer func() { + r := recover() + fmt.Println("Recovering ", r) + }() + for _, x := range OfSliceIndex([]int{100, 200}) { + result = append(result, x) + Y: + // swallows panics and iterates to end BUT `break Y` disables the body, so--> 10, 1, 2 + for _, y := range VeryBadOfSliceIndex([]int{10, 20}) { + result = append(result, y) + + // converts early exit into a panic --> 1, 2 + for k, z := range PanickyOfSliceIndex([]int{1, 2}) { // iterator panics + result = append(result, z) + if k == 1 { + break Y + } + } + } + } +} + +func TestPanickyIteratorWithNewDefer() { + var result []int + defer func() { + r := recover() + fmt.Println("Recovering ", r) + }() + for _, x := range OfSliceIndex([]int{100, 200}) { + result = append(result, x) + Y: + // swallows panics and iterates to end BUT `break Y` disables the body, so--> 10, 1, 2 + for _, y := range VeryBadOfSliceIndex([]int{10, 20}) { + defer func() { // This defer will be set on TestPanickyIteratorWithNewDefer from TestPanickyIteratorWithNewDefer-range2 + fmt.Println("y loop defer") + }() + result = append(result, y) + + // converts early exit into a panic --> 1, 2 + for k, z := range PanickyOfSliceIndex([]int{1, 2}) { // iterator panics + result = append(result, z) + if k == 1 { + break Y + } + } + } + } +} + +func TestLongReturnWrapper() { + TestLongReturn() + fmt.Println("returned") +} + +func TestLongReturn() { + for _, x := range OfSliceIndex([]int{1, 2, 3}) { + for _, y := range OfSliceIndex([]int{10, 20, 30}) { + if y == 10 { + return + } + } + fmt.Println(x) + } +} + +func TestGotoA1() { + result := []int{} + for _, x := range OfSliceIndex([]int{-1, -4, -5}) { + result = append(result, x) + if x == -4 { + break + } + for _, y := range OfSliceIndex([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) { + if y == 3 { + goto A + } + result = append(result, y) + } + A: + result = append(result, x) + } + fmt.Println(result) +} + +func TestGotoB1() { + result := []int{} + for _, x := range OfSliceIndex([]int{-1, -2, -3, -4, -5}) { + result = append(result, x) + if x == -4 { + break + } + for _, y := range OfSliceIndex([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) { + if y == 3 { + goto B + } + result = append(result, y) + } + result = append(result, x) + } +B: + result = append(result, 999) + fmt.Println(result) +} + +func main() { + TestTrickyIterAll() + TestTrickyIterAll2() + TestBreak1() + TestBreak2() + TestMultiCont0() + TestPanickyIterator1() + TestPanickyIterator2() + TestPanickyIteratorWithNewDefer() + TestLongReturnWrapper() + TestGotoA1() + TestGotoB1() +} + +type Seq[T any] func(yield func(T) bool) +type Seq2[T1, T2 any] func(yield func(T1, T2) bool) + +type TrickyIterator struct { + yield func(int, int) bool +} + +func (ti *TrickyIterator) iterEcho(s []int) Seq2[int, int] { + return func(yield func(int, int) bool) { + for i, v := range s { + if !yield(i, v) { + ti.yield = yield + return + } + if ti.yield != nil && !ti.yield(i, v) { + return + } + } + ti.yield = yield + return + } +} + +func (ti *TrickyIterator) iterAll(s []int) Seq2[int, int] { + return func(yield func(int, int) bool) { + ti.yield = yield // Save yield for future abuse + for i, v := range s { + if !yield(i, v) { + return + } + } + return + } +} + +func (ti *TrickyIterator) iterZero(s []int) Seq2[int, int] { + return func(yield func(int, int) bool) { + ti.yield = yield // Save yield for future abuse + // Don't call it at all, maybe it won't escape + return + } +} + +// OfSliceIndex returns a Seq2 over the elements of s. It is equivalent +// to range s. +func OfSliceIndex[T any, S ~[]T](s S) Seq2[int, T] { + return func(yield func(int, T) bool) { + for i, v := range s { + if !yield(i, v) { + return + } + } + return + } +} + +// PanickyOfSliceIndex iterates the slice but panics if it exits the loop early +func PanickyOfSliceIndex[T any, S ~[]T](s S) Seq2[int, T] { + return func(yield func(int, T) bool) { + for i, v := range s { + if !yield(i, v) { + panic(fmt.Errorf("Panicky iterator panicking")) + } + } + return + } +} + +// VeryBadOfSliceIndex is "very bad" because it ignores the return value from yield +// and just keeps on iterating, and also wraps that call in a defer-recover so it can +// keep on trying after the first panic. +func VeryBadOfSliceIndex[T any, S ~[]T](s S) Seq2[int, T] { + return func(yield func(int, T) bool) { + for i, v := range s { + func() { + defer func() { + recover() + }() + yield(i, v) + }() + } + return + } +} diff --git a/pkg/proc/bininfo.go b/pkg/proc/bininfo.go index 68adb1654a..759d6269c4 100644 --- a/pkg/proc/bininfo.go +++ b/pkg/proc/bininfo.go @@ -501,9 +501,24 @@ type Function struct { trampoline bool // DW_AT_trampoline attribute set to true // InlinedCalls lists all inlined calls to this function - InlinedCalls []InlinedCall + InlinedCalls []InlinedCall + rangeParentNameCache int // see rangeParentName + // extraCache contains informations about this function that is only needed for + // some operations and is expensive to compute or store for every function. + extraCache *functionExtra +} + +type functionExtra struct { // closureStructType is the cached struct type for closures for this function - closureStructTypeCached *godwarf.StructType + closureStructType *godwarf.StructType + + // rangeParent is set when this function is a range-over-func body closure + // and points to the function that the closure was generated from. + rangeParent *Function + // rangeBodies is the list of range-over-func body closures for this + // function. Only one between rangeParent and rangeBodies should be set at + // any given time. + rangeBodies []*Function } // instRange returns the indexes in fn.Name of the type parameter @@ -640,43 +655,99 @@ func (fn *Function) privateRuntime() bool { return len(name) > n && name[:n] == "runtime." && !('A' <= name[n] && name[n] <= 'Z') } -func (fn *Function) closureStructType(bi *BinaryInfo) *godwarf.StructType { - if fn.closureStructTypeCached != nil { - return fn.closureStructTypeCached +func rangeParentName(fnname string) int { + const rangeSuffix = "-range" + ridx := strings.Index(fnname, rangeSuffix) + if ridx <= 0 { + return -1 } - dwarfTree, err := fn.cu.image.getDwarfTree(fn.offset) - if err != nil { - return nil + ok := true + for i := ridx + len(rangeSuffix); i < len(fnname); i++ { + if fnname[i] < '0' || fnname[i] > '9' { + ok = false + break + } } - st := &godwarf.StructType{ - Kind: "struct", + if !ok { + return -1 } - vars := reader.Variables(dwarfTree, 0, 0, reader.VariablesNoDeclLineCheck|reader.VariablesSkipInlinedSubroutines) - for _, v := range vars { - off, ok := v.Val(godwarf.AttrGoClosureOffset).(int64) - if ok { - n, _ := v.Val(dwarf.AttrName).(string) - typ, err := v.Type(fn.cu.image.dwarf, fn.cu.image.index, fn.cu.image.typeCache) - if err == nil { - sz := typ.Common().ByteSize - st.Field = append(st.Field, &godwarf.StructField{ - Name: n, - Type: typ, - ByteOffset: off, - ByteSize: sz, - BitOffset: off * 8, - BitSize: sz * 8, - }) + return ridx +} + +// rangeParentName, if this function is a range-over-func body closure +// returns the name of the parent function, otherwise returns "" +func (fn *Function) rangeParentName() string { + if fn.rangeParentNameCache == 0 { + ridx := rangeParentName(fn.Name) + fn.rangeParentNameCache = ridx + } + if fn.rangeParentNameCache < 0 { + return "" + } + return fn.Name[:fn.rangeParentNameCache] +} + +// extra loads informations about fn that is expensive to compute and we +// only need for a minority of the functions. +func (fn *Function) extra(bi *BinaryInfo) *functionExtra { + if fn.extraCache != nil { + return fn.extraCache + } + + fn.extraCache = &functionExtra{} + + // Calculate closureStructType + { + dwarfTree, err := fn.cu.image.getDwarfTree(fn.offset) + if err != nil { + return nil + } + st := &godwarf.StructType{ + Kind: "struct", + } + vars := reader.Variables(dwarfTree, 0, 0, reader.VariablesNoDeclLineCheck|reader.VariablesSkipInlinedSubroutines) + for _, v := range vars { + off, ok := v.Val(godwarf.AttrGoClosureOffset).(int64) + if ok { + n, _ := v.Val(dwarf.AttrName).(string) + typ, err := v.Type(fn.cu.image.dwarf, fn.cu.image.index, fn.cu.image.typeCache) + if err == nil { + sz := typ.Common().ByteSize + st.Field = append(st.Field, &godwarf.StructField{ + Name: n, + Type: typ, + ByteOffset: off, + ByteSize: sz, + BitOffset: off * 8, + BitSize: sz * 8, + }) + } } } + + if len(st.Field) > 0 { + lf := st.Field[len(st.Field)-1] + st.ByteSize = lf.ByteOffset + lf.Type.Common().ByteSize + } + fn.extraCache.closureStructType = st + } + + // Find rangeParent for this function (if it is a range-over-func body closure) + if rangeParentName := fn.rangeParentName(); rangeParentName != "" { + fn.extraCache.rangeParent = bi.lookupOneFunc(rangeParentName) } - if len(st.Field) > 0 { - lf := st.Field[len(st.Field)-1] - st.ByteSize = lf.ByteOffset + lf.Type.Common().ByteSize + // Find range-over-func bodies of this function + if fn.extraCache.rangeParent == nil { + for i := range bi.Functions { + fn2 := &bi.Functions[i] + if strings.HasPrefix(fn2.Name, fn.Name) && fn2.rangeParentName() == fn.Name { + fn.extraCache.rangeBodies = append(fn.extraCache.rangeBodies, fn2) + } + } } - fn.closureStructTypeCached = st - return st + + return fn.extraCache } type constantsMap map[dwarfRef]*constantType diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go index ef780617f6..2e317208ae 100644 --- a/pkg/proc/proc_test.go +++ b/pkg/proc/proc_test.go @@ -488,118 +488,126 @@ func testseq2(t *testing.T, program string, initialLocation string, testcases [] func testseq2Args(wd string, args []string, buildFlags protest.BuildFlags, t *testing.T, program string, initialLocation string, testcases []seqTest) { protest.AllowRecording(t) + t.Helper() withTestProcessArgs(program, t, wd, args, buildFlags, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + checkBreakpointClear := true var bp *proc.Breakpoint if initialLocation != "" { bp = setFunctionBreakpoint(p, t, initialLocation) } else if testcases[0].cf == contContinue { bp = setFileBreakpoint(p, t, fixture.Source, testcases[0].pos.(int)) + } else if testcases[0].cf == contNothing { + // Do nothing + checkBreakpointClear = false } else { panic("testseq2 can not set initial breakpoint") } if traceTestseq2 { t.Logf("initial breakpoint %v", bp) } - regs, err := p.CurrentThread().Registers() - assertNoError(err, t, "Registers") - f, ln := currentLineNumber(p, t) - for i, tc := range testcases { - switch tc.cf { - case contNext: - if traceTestseq2 { - t.Log("next") - } - assertNoError(grp.Next(), t, "Next() returned an error") - case contStep: - if traceTestseq2 { - t.Log("step") - } - assertNoError(grp.Step(), t, "Step() returned an error") - case contStepout: - if traceTestseq2 { - t.Log("stepout") - } - assertNoError(grp.StepOut(), t, "StepOut() returned an error") - case contContinue: - if traceTestseq2 { - t.Log("continue") - } - assertNoError(grp.Continue(), t, "Continue() returned an error") - if i == 0 { - if traceTestseq2 { - t.Log("clearing initial breakpoint") - } - err := p.ClearBreakpoint(bp.Addr) - assertNoError(err, t, "ClearBreakpoint() returned an error") - } - case contReverseNext: - if traceTestseq2 { - t.Log("reverse-next") - } - assertNoError(grp.ChangeDirection(proc.Backward), t, "direction switch") - assertNoError(grp.Next(), t, "reverse Next() returned an error") - assertNoError(grp.ChangeDirection(proc.Forward), t, "direction switch") - case contReverseStep: - if traceTestseq2 { - t.Log("reverse-step") - } - assertNoError(grp.ChangeDirection(proc.Backward), t, "direction switch") - assertNoError(grp.Step(), t, "reverse Step() returned an error") - assertNoError(grp.ChangeDirection(proc.Forward), t, "direction switch") - case contReverseStepout: - if traceTestseq2 { - t.Log("reverse-stepout") - } - assertNoError(grp.ChangeDirection(proc.Backward), t, "direction switch") - assertNoError(grp.StepOut(), t, "reverse StepOut() returned an error") - assertNoError(grp.ChangeDirection(proc.Forward), t, "direction switch") - case contContinueToBreakpoint: - bp := setFileBreakpoint(p, t, fixture.Source, tc.pos.(int)) + testseq2intl(t, fixture, grp, p, bp, testcases) + + if countBreakpoints(p) != 0 && checkBreakpointClear { + t.Fatal("Not all breakpoints were cleaned up", len(p.Breakpoints().M)) + } + }) +} + +func testseq2intl(t *testing.T, fixture protest.Fixture, grp *proc.TargetGroup, p *proc.Target, bp *proc.Breakpoint, testcases []seqTest) { + f, ln := currentLineNumber(p, t) + for i, tc := range testcases { + switch tc.cf { + case contNext: + if traceTestseq2 { + t.Log("next") + } + assertNoError(grp.Next(), t, "Next() returned an error") + case contStep: + if traceTestseq2 { + t.Log("step") + } + assertNoError(grp.Step(), t, "Step() returned an error") + case contStepout: + if traceTestseq2 { + t.Log("stepout") + } + assertNoError(grp.StepOut(), t, "StepOut() returned an error") + case contContinue: + if traceTestseq2 { + t.Log("continue") + } + assertNoError(grp.Continue(), t, "Continue() returned an error") + if i == 0 { if traceTestseq2 { - t.Log("continue") + t.Log("clearing initial breakpoint") } - assertNoError(grp.Continue(), t, "Continue() returned an error") err := p.ClearBreakpoint(bp.Addr) assertNoError(err, t, "ClearBreakpoint() returned an error") - case contNothing: - // do nothing } - - if err := p.CurrentThread().Breakpoint().CondError; err != nil { - t.Logf("breakpoint condition error: %v", err) + case contReverseNext: + if traceTestseq2 { + t.Log("reverse-next") } - - f, ln = currentLineNumber(p, t) - regs, _ = p.CurrentThread().Registers() - pc := regs.PC() - + assertNoError(grp.ChangeDirection(proc.Backward), t, "direction switch") + assertNoError(grp.Next(), t, "reverse Next() returned an error") + assertNoError(grp.ChangeDirection(proc.Forward), t, "direction switch") + case contReverseStep: if traceTestseq2 { - t.Logf("at %#x %s:%d", pc, f, ln) - fmt.Printf("at %#x %s:%d\n", pc, f, ln) + t.Log("reverse-step") } - switch pos := tc.pos.(type) { - case int: - if pos >= 0 && ln != pos { - t.Fatalf("Program did not continue to correct next location expected %d was %s:%d (%#x) (testcase %d)", pos, filepath.Base(f), ln, pc, i) - } - case string: - v := strings.Split(pos, ":") - tgtln, _ := strconv.Atoi(v[1]) - if !strings.HasSuffix(f, v[0]) || (ln != tgtln) { - t.Fatalf("Program did not continue to correct next location, expected %s was %s:%d (%#x) (testcase %d)", pos, filepath.Base(f), ln, pc, i) - } - case func(*proc.Target): - pos(p) - default: - panic(fmt.Errorf("unexpected type %T", pos)) + assertNoError(grp.ChangeDirection(proc.Backward), t, "direction switch") + assertNoError(grp.Step(), t, "reverse Step() returned an error") + assertNoError(grp.ChangeDirection(proc.Forward), t, "direction switch") + case contReverseStepout: + if traceTestseq2 { + t.Log("reverse-stepout") } + assertNoError(grp.ChangeDirection(proc.Backward), t, "direction switch") + assertNoError(grp.StepOut(), t, "reverse StepOut() returned an error") + assertNoError(grp.ChangeDirection(proc.Forward), t, "direction switch") + case contContinueToBreakpoint: + bp := setFileBreakpoint(p, t, fixture.Source, tc.pos.(int)) + if traceTestseq2 { + t.Log("continue") + } + assertNoError(grp.Continue(), t, "Continue() returned an error") + err := p.ClearBreakpoint(bp.Addr) + assertNoError(err, t, "ClearBreakpoint() returned an error") + case contNothing: + // do nothing } - if countBreakpoints(p) != 0 { - t.Fatal("Not all breakpoints were cleaned up", len(p.Breakpoints().M)) + if err := p.CurrentThread().Breakpoint().CondError; err != nil { + t.Logf("breakpoint condition error: %v", err) } - }) + + f, ln = currentLineNumber(p, t) + regs, _ := p.CurrentThread().Registers() + pc := regs.PC() + _, _, fn := p.BinInfo().PCToLine(pc) + + if traceTestseq2 { + t.Logf("at %#x (%s) %s:%d", pc, fn.Name, f, ln) + //fmt.Printf("at %#x %s:%d\n", pc, f, ln) + } + switch pos := tc.pos.(type) { + case int: + if pos >= 0 && ln != pos { + t.Fatalf("Program did not continue to correct next location expected %d was %s:%d (%#x) (testcase %d)", pos, filepath.Base(f), ln, pc, i) + } + case string: + v := strings.Split(pos, ":") + tgtln, _ := strconv.Atoi(v[1]) + if !strings.HasSuffix(f, v[0]) || (ln != tgtln) { + t.Fatalf("Program did not continue to correct next location, expected %s was %s:%d (%#x) (testcase %d)", pos, filepath.Base(f), ln, pc, i) + } + case func(*proc.Target): + pos(p) + default: + panic(fmt.Errorf("unexpected type %T", pos)) + } + } } func TestNextGeneral(t *testing.T) { @@ -6246,3 +6254,429 @@ func TestStepIntoGoroutine(t *testing.T) { }}, }) } + +func TestRangeOverFuncNext(t *testing.T) { + if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 23) { + t.Skip("N/A") + } + + funcBreak := func(t *testing.T, fnname string) seqTest { + return seqTest{ + contNothing, + func(p *proc.Target) { + setFunctionBreakpoint(p, t, fnname) + }} + } + + notAtEntryPoint := func(t *testing.T) seqTest { + return seqTest{contNothing, func(p *proc.Target) { + pc := currentPC(p, t) + fn := p.BinInfo().PCToFunc(pc) + if pc == fn.Entry { + t.Fatalf("current PC is entry point") + } + }} + } + + nx := func(n int) seqTest { + return seqTest{contNext, n} + } + + withTestProcessArgs("rangeoverfunc", t, ".", []string{}, 0, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + + t.Run("TestTrickyIterAll1", func(t *testing.T) { + testseq2intl(t, fixture, grp, p, nil, []seqTest{ + funcBreak(t, "main.TestTrickyIterAll"), + {contContinue, 24}, // TestTrickyIterAll + nx(25), + nx(26), + nx(27), // for _, x := range ... + nx(27), // for _, x := range ... (TODO: this probably shouldn't be here but it's also very hard to skip stopping here a second time) + nx(28), // i += x + nx(29), // if i >= 36 { + nx(32), + nx(27), // for _, x := range ... + notAtEntryPoint(t), + nx(28), // i += x + nx(29), // if i >= 36 { + nx(30), // break + nx(32), + nx(34), // fmt.Println + }) + }) + + t.Run("TestTrickyIterAll2", func(t *testing.T) { + testseq2intl(t, fixture, grp, p, nil, []seqTest{ + funcBreak(t, "main.TestTrickyIterAll2"), + {contContinue, 37}, // TestTrickyIterAll2 + nx(38), + nx(39), + nx(40), // for _, x := range... + nx(40), + nx(41), + nx(42), + nx(40), + notAtEntryPoint(t), + nx(41), + nx(42), + nx(42), // different function from the one above... + nx(43), + }) + }) + + t.Run("TestBreak1", func(t *testing.T) { + testseq2intl(t, fixture, grp, p, nil, []seqTest{ + funcBreak(t, "main.TestBreak1"), + {contContinue, 46}, // TestBreak1 + nx(47), + nx(48), // for _, x := range... (x == -1) + nx(48), + nx(49), // if x == -4 + + nx(52), // for _, y := range... (y == 1) + nx(52), + nx(53), // if y == 3 + nx(56), // result = append(result, y) + nx(57), + nx(52), // for _, y := range... (y == 2) + notAtEntryPoint(t), + nx(53), // if y == 3 + nx(56), // result = append(result, y) + nx(57), + nx(52), // for _, y := range... (y == 3) + nx(53), // if y == 3 + nx(54), // break + nx(57), + nx(58), // result = append(result, x) + nx(59), + + nx(48), // for _, x := range... (x == -2) + nx(49), // if x == -4 + nx(52), // for _, y := range... (y == 1) + nx(52), + nx(53), // if y == 3 + nx(56), // result = append(result, y) + nx(57), + nx(52), // for _, y := range... (y == 2) + notAtEntryPoint(t), + nx(53), // if y == 3 + nx(56), // result = append(result, y) + nx(57), + nx(52), // for _, y := range... (y == 3) + nx(53), // if y == 3 + nx(54), // break + nx(57), + nx(58), // result = append(result, x) + nx(59), + + nx(48), // for _, x := range... (x == -4) + nx(49), // if x == -4 + nx(50), // break + nx(59), + nx(60), + nx(61), + }) + }) + + t.Run("TestBreak2", func(t *testing.T) { + testseq2intl(t, fixture, grp, p, nil, []seqTest{ + funcBreak(t, "main.TestBreak2"), + + {contContinue, 63}, // TestBreak2 + nx(64), + nx(65), + + nx(66), // for _, x := range (x == -1) + nx(66), + nx(67), // for _, y := range (y == 1) + nx(67), + nx(68), // if y == 3 + nx(71), // if x == -4 + nx(74), // result = append(result, y) + nx(75), + + nx(67), // for _, y := range (y == 2) + nx(68), // if y == 3 + nx(71), // if x == -4 + nx(74), // result = append(result, y) + nx(75), + + nx(67), // for _, y := range (y == 3) + nx(68), // if y == 3 + nx(69), // break + nx(75), + nx(76), // result = append(result, x) + nx(77), + + nx(66), // for _, x := range (x == -2) + nx(67), // for _, y := range (y == 1) + nx(67), + nx(68), // if y == 3 + nx(71), // if x == -4 + nx(74), // result = append(result, y) + nx(75), + + nx(67), // for _, y := range (y == 2) + nx(68), // if y == 3 + nx(71), // if x == -4 + nx(74), // result = append(result, y) + nx(75), + + nx(67), // for _, y := range (y == 3) + nx(68), // if y == 3 + nx(69), // break + nx(75), + nx(76), // result = append(result, x) + nx(77), + + nx(66), // for _, x := range (x == -4) + nx(67), // for _, y := range (y == 1) + nx(67), + nx(68), // if y == 3 + nx(71), // if x == -4 + nx(72), // break outer + nx(75), + nx(77), + nx(78), + nx(79), + }) + }) + + t.Run("TestMultiCont0", func(t *testing.T) { + testseq2intl(t, fixture, grp, p, nil, []seqTest{ + funcBreak(t, "main.TestMultiCont0"), + {contContinue, 81}, + nx(82), + nx(84), + nx(85), // for _, w := range (w == 1000) + nx(85), + nx(86), // result = append(result, w) + nx(87), // if w == 2000 + nx(90), // for _, x := range (x == 100) + nx(90), + nx(91), // for _, y := range (y == 10) + nx(91), + nx(92), // result = append(result, y) + + nx(93), // for _, z := range (z == 1) + nx(93), + nx(94), // if z&1 == 1 + nx(95), // continue + + nx(93), // for _, z := range (z == 2) + nx(94), // if z&1 == 1 + nx(97), // result = append(result, z) + nx(98), // if z >= 4 { + nx(101), + + nx(93), // for _, z := range (z == 3) + nx(94), // if z&1 == 1 + nx(95), // continue + + nx(93), // for _, z := range (z == 4) + nx(94), // if z&1 == 1 + nx(97), // result = append(result, z) + nx(98), // if z >= 4 { + nx(99), // continue W + nx(101), + nx(103), + nx(105), + + nx(85), // for _, w := range (w == 2000) + nx(86), // result = append(result, w) + nx(87), // if w == 2000 + nx(88), // break + nx(106), + nx(107), // fmt.Println + }) + }) + + t.Run("TestPanickyIterator1", func(t *testing.T) { + testseq2intl(t, fixture, grp, p, nil, []seqTest{ + funcBreak(t, "main.TestPanickyIterator1"), + {contContinue, 110}, + nx(111), + nx(112), + nx(116), // for _, z := range (z == 1) + nx(116), + nx(117), // result = append(result, z) + nx(118), // if z == 4 + nx(121), + + nx(116), // for _, z := range (z == 2) + nx(117), // result = append(result, z) + nx(118), // if z == 4 + nx(121), + + nx(116), // for _, z := range (z == 3) + nx(117), // result = append(result, z) + nx(118), // if z == 4 + nx(121), + + nx(116), // for _, z := range (z == 4) + nx(117), // result = append(result, z) + nx(118), // if z == 4 + nx(119), // break + + nx(112), // defer func() + nx(113), // r := recover() + nx(114), // fmt.Println + }) + }) + + t.Run("TestPanickyIterator2", func(t *testing.T) { + testseq2intl(t, fixture, grp, p, nil, []seqTest{ + funcBreak(t, "main.TestPanickyIterator2"), + {contContinue, 125}, + nx(126), + nx(127), + nx(131), // for _, x := range (x == 100) + nx(131), + nx(132), + nx(133), + nx(135), // for _, y := range (y == 10) + nx(135), + nx(136), // result = append(result, y) + nx(139), // for k, z := range (k == 0, z == 1) + nx(139), + nx(140), // result = append(result, z) + nx(141), // if k == 1 + nx(144), + + nx(139), // for k, z := range (k == 1, z == 2) + nx(140), // result = append(result, z) + nx(141), // if k == 1 + nx(142), // break Y + nx(135), + nx(145), + nx(127), // defer func() + nx(128), // r := recover() + nx(129), // fmt.Println + }) + }) + + t.Run("TestPanickyIteratorWithNewDefer", func(t *testing.T) { + testseq2intl(t, fixture, grp, p, nil, []seqTest{ + funcBreak(t, "main.TestPanickyIteratorWithNewDefer"), + {contContinue, 149}, + nx(150), + nx(151), + nx(155), // for _, x := range (x == 100) + nx(155), + nx(156), + nx(157), + nx(159), // for _, y := range (y == 10) + nx(159), + nx(160), + nx(163), // result = append(result, y) + nx(166), // for k, z := range (k == 0, z == 1) + nx(166), + nx(167), // result = append(result, z) + nx(168), // if k == 1 + nx(171), + + nx(166), // for k, z := range (k == 0, z == 1) + nx(167), // result = append(result, z) + nx(168), // if k == 1 + nx(169), // break Y + nx(159), + nx(172), + nx(160), // defer func() + nx(161), // fmt.Println + }) + }) + + t.Run("TestLongReturn", func(t *testing.T) { + testseq2intl(t, fixture, grp, p, nil, []seqTest{ + funcBreak(t, "main.TestLongReturn"), + {contContinue, 181}, + nx(182), // for _, x := range (x == 1) + nx(182), + nx(183), // for _, y := range (y == 10) + nx(183), + nx(184), // if y == 10 + nx(185), // return + nx(187), + nx(189), + nx(178), // into TestLongReturnWrapper, fmt.Println + }) + }) + + t.Run("TestGotoA1", func(t *testing.T) { + testseq2intl(t, fixture, grp, p, nil, []seqTest{ + funcBreak(t, "main.TestGotoA1"), + {contContinue, 192}, + nx(193), + nx(194), // for _, x := range (x == -1) + nx(194), + nx(195), // result = append(result, x) + nx(196), // if x == -4 + nx(199), // for _, y := range (y == 1) + nx(199), + nx(200), // if y == 3 + nx(203), // result = append(result, y) + nx(204), + + nx(199), // for _, y := range (y == 2) + nx(200), // if y == 3 + nx(203), // result = append(result, y) + nx(204), + + nx(199), // for _, y := range (y == 3) + nx(200), // if y == 3 + nx(201), // goto A + nx(204), + nx(206), // result = append(result, x) + nx(207), + + nx(194), // for _, x := range (x == -4) + nx(195), // result = append(result, x) + nx(196), // if x == -4 + nx(197), // break + nx(207), + nx(208), // fmt.Println + }) + }) + + t.Run("TestGotoB1", func(t *testing.T) { + testseq2intl(t, fixture, grp, p, nil, []seqTest{ + funcBreak(t, "main.TestGotoB1"), + {contContinue, 211}, + nx(212), + nx(213), // for _, x := range (x == -1) + nx(213), + nx(214), // result = append(result, x) + nx(215), // if x == -4 + nx(218), // for _, y := range (y == 1) + nx(218), + nx(219), // if y == 3 + nx(222), // result = append(result, y) + nx(223), + + nx(218), // for _, y := range (y == 2) + nx(219), // if y == 3 + nx(222), // result = append(result, y) + nx(223), + + nx(218), // for _, y := range (y == 3) + nx(219), // if y == 3 + nx(220), // goto B + nx(223), + nx(225), + nx(227), // result = append(result, 999) + nx(228), // fmt.Println + }) + }) + }) +} + +func TestRangeOverFuncStepOut(t *testing.T) { + if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 23) { + t.Skip("N/A") + } + + testseq2(t, "rangeoverfunc", "", []seqTest{ + {contContinue, 97}, + {contStepout, 237}, + }) +} diff --git a/pkg/proc/stack.go b/pkg/proc/stack.go index 55034ebf6f..759e4d7600 100644 --- a/pkg/proc/stack.go +++ b/pkg/proc/stack.go @@ -197,6 +197,8 @@ type stackIterator struct { g0_sched_sp uint64 // value of g0.sched.sp (see comments around its use) g0_sched_sp_loaded bool // g0_sched_sp was loaded from g0 + count int + opts StacktraceOptions } @@ -353,17 +355,11 @@ func (it *stackIterator) stacktrace(depth int) ([]Stackframe, error) { if depth < 0 { return nil, errors.New("negative maximum stack depth") } - if it.opts&StacktraceG != 0 && it.g != nil { - it.switchToGoroutineStack() - it.top = true - } frames := make([]Stackframe, 0, depth+1) - for it.Next() { - frames = it.appendInlineCalls(frames, it.Frame()) - if len(frames) >= depth+1 { - break - } - } + it.stacktraceFunc(func(frame Stackframe) bool { + frames = append(frames, frame) + return len(frames) < depth+1 + }) if err := it.Err(); err != nil { if len(frames) == 0 { return nil, err @@ -373,22 +369,37 @@ func (it *stackIterator) stacktrace(depth int) ([]Stackframe, error) { return frames, nil } -func (it *stackIterator) appendInlineCalls(frames []Stackframe, frame Stackframe) []Stackframe { +func (it *stackIterator) stacktraceFunc(callback func(Stackframe) bool) { + if it.opts&StacktraceG != 0 && it.g != nil { + it.switchToGoroutineStack() + it.top = true + } + for it.Next() { + if !it.appendInlineCalls(callback, it.Frame()) { + break + } + } +} + +func (it *stackIterator) appendInlineCalls(callback func(Stackframe) bool, frame Stackframe) bool { if frame.Call.Fn == nil { - return append(frames, frame) + it.count++ + return callback(frame) } if frame.Call.Fn.cu.lineInfo == nil { - return append(frames, frame) + it.count++ + return callback(frame) } callpc := frame.Call.PC - if len(frames) > 0 { + if it.count > 0 { callpc-- } dwarfTree, err := frame.Call.Fn.cu.image.getDwarfTree(frame.Call.Fn.offset) if err != nil { - return append(frames, frame) + it.count++ + return callback(frame) } for _, entry := range reader.InlineStack(dwarfTree, callpc) { @@ -406,7 +417,8 @@ func (it *stackIterator) appendInlineCalls(frames []Stackframe, frame Stackframe } inlfn := &Function{Name: fnname, Entry: frame.Call.Fn.Entry, End: frame.Call.Fn.End, offset: entry.Offset, cu: frame.Call.Fn.cu} - frames = append(frames, Stackframe{ + it.count++ + callback(Stackframe{ Current: frame.Current, Call: Location{ frame.Call.PC, @@ -427,7 +439,8 @@ func (it *stackIterator) appendInlineCalls(frames []Stackframe, frame Stackframe frame.Call.Line = int(line) } - return append(frames, frame) + it.count++ + return callback(frame) } // advanceRegs calculates the DwarfRegisters for a next stack frame @@ -618,6 +631,8 @@ type Defer struct { link *Defer // Next deferred function argSz int64 // Always 0 in Go >=1.17 + rangefunc []*Defer // See explanation in $GOROOT/src/runtime/panic.go, comment to function runtime.deferrangefunc (this is the equivalent of the rangefunc variable and head fields, combined) + variable *Variable Unreadable error } @@ -644,7 +659,7 @@ func (g *G) readDefers(frames []Stackframe) { } if frames[i].TopmostDefer == nil { - frames[i].TopmostDefer = curdefer + frames[i].TopmostDefer = curdefer.topdefer() } if frames[i].SystemStack || curdefer.SP >= uint64(frames[i].Regs.CFA) { @@ -660,13 +675,19 @@ func (g *G) readDefers(frames []Stackframe) { // compared with deferred frames. i++ } else { - frames[i].Defers = append(frames[i].Defers, curdefer) + if len(curdefer.rangefunc) > 0 { + frames[i].Defers = append(frames[i].Defers, curdefer.rangefunc...) + } else { + frames[i].Defers = append(frames[i].Defers, curdefer) + } curdefer = curdefer.Next() } } } -func (d *Defer) load() { +const maxRangeFuncDefers = 10 + +func (d *Defer) load(canrecur bool) { v := d.variable // +rtype _defer v.loadValue(LoadConfig{false, 1, 0, 0, -1, 0}) if v.Unreadable != nil { @@ -701,6 +722,34 @@ func (d *Defer) load() { if linkvar.Addr != 0 { d.link = &Defer{variable: linkvar} } + + if canrecur { + h := v + for _, fieldname := range []string{"head", "u", "value"} { + if h == nil { + return + } + h = h.loadFieldNamed(fieldname) + } + if h != nil { + h := h.newVariable("", h.Addr, pointerTo(linkvar.DwarfType, h.bi.Arch), h.mem).maybeDereference() + if h.Addr != 0 { + hd := &Defer{variable: h} + for { + hd.load(false) + d.rangefunc = append(d.rangefunc, hd) + if hd.link == nil { + break + } + if len(d.rangefunc) > maxRangeFuncDefers { + // We don't have a way to know for sure that we haven't gone completely off-road while loading this list so limit it to an arbitrary maximum size. + break + } + hd = hd.link + } + } + } + } } // errSPDecreased is used when (*Defer).Next detects a corrupted linked @@ -715,13 +764,20 @@ func (d *Defer) Next() *Defer { if d.link == nil { return nil } - d.link.load() + d.link.load(true) if d.link.SP < d.SP { d.link.Unreadable = errSPDecreased } return d.link } +func (d *Defer) topdefer() *Defer { + if len(d.rangefunc) > 0 { + return d.rangefunc[0] + } + return d +} + // EvalScope returns an EvalScope relative to the argument frame of this deferred call. // The argument frame of a deferred call is stored in memory immediately // after the deferred header. @@ -813,3 +869,89 @@ func ruleString(rule *frame.DWRule, regnumToString func(uint64) string) string { return fmt.Sprintf("unknown_rule(%d)", rule.Rule) } } + +// rangeStack, if the topmost frame of the stack is a the body of a +// range-over-func statement, returns a slice containing the stack of range +// bodies on the stack, the frame of the function containing them and +// finally the function that called it. +// +// For example, given: +// +// func f() { +// for _ := range iterator1 { +// for _ := range iterator2 { +// fmt.Println() // <- YOU ARE HERE +// } +// } +// } +// +// It will return the following frames: +// +// 0. f-range2() +// 1. f-range1() +// 2. f() +// 3. function that called f() +// +// If the topmost frame of the stack is *not* the body closure of a +// range-over-func statement then nothing is returned. +func rangeStack(tgt *Target, g *G) ([]Stackframe, error) { + if g == nil { + return nil, nil + } + it, err := goroutineStackIterator(tgt, g, StacktraceSimple) + if err != nil { + return nil, err + } + frames := []Stackframe{} + stage := 0 + var rangeParent *Function + nonMonotonousSP := false + it.stacktraceFunc(func(fr Stackframe) bool { + //TODO(range-over-func): this is a heuristic, we should use .closureptr instead + + if len(frames) > 0 { + prev := &frames[len(frames)-1] + if fr.Regs.SP() <= prev.Regs.SP() { + nonMonotonousSP = true + return false + } + } + + switch stage { + case 0: + frames = append(frames, fr) + rangeParent = fr.Call.Fn.extra(tgt.BinInfo()).rangeParent + stage++ + if rangeParent == nil { + frames = nil + stage = 3 + return false + } + case 1: + if fr.Call.Fn.offset == rangeParent.offset { + frames = append(frames, fr) + stage++ + } else if fr.Call.Fn.extra(tgt.BinInfo()).rangeParent == rangeParent { + frames = append(frames, fr) + } + case 2: + frames = append(frames, fr) + stage++ + return false + case 3: + return false + } + return true + }) + if it.Err() != nil { + return nil, err + } + if nonMonotonousSP { + return nil, errors.New("stacktrace error, non-monotonous SP") + } + if stage != 3 { + return nil, errors.New("could not find range-over-func closure parent on the stack") + } + g.readDefers(frames) + return frames, nil +} diff --git a/pkg/proc/target_exec.go b/pkg/proc/target_exec.go index 2b68380957..0e2f696d5e 100644 --- a/pkg/proc/target_exec.go +++ b/pkg/proc/target_exec.go @@ -508,6 +508,16 @@ func (grp *TargetGroup) StepOut() error { return err } + rangeFrames, err := rangeStack(dbp, selg) + if err != nil { + return err + } + if rangeFrames != nil { + // There are range-over-func body closures skip all of them to the + // function containing them and its caller function. + topframe, retframe = rangeFrames[len(rangeFrames)-2], rangeFrames[len(rangeFrames)-1] + } + success := false defer func() { if !success { @@ -645,6 +655,7 @@ func next(dbp *Target, stepInto, inlinedStepOut bool) error { backward := dbp.recman.GetDirection() == Backward selg := dbp.SelectedGoroutine() curthread := dbp.CurrentThread() + bi := dbp.BinInfo() topframe, retframe, err := topframe(dbp, selg, curthread) if err != nil { return err @@ -663,6 +674,11 @@ func next(dbp *Target, stepInto, inlinedStepOut bool) error { panic("next called with inlinedStepOut but topframe was not inlined") } + rangeFrames, err := rangeStack(dbp, selg) + if err != nil { + return err + } + success := false defer func() { if !success { @@ -680,15 +696,14 @@ func next(dbp *Target, stepInto, inlinedStepOut bool) error { } } - sameGCond := sameGoroutineCondition(dbp.BinInfo(), selg, curthread.ThreadID()) + sameGCond := sameGoroutineCondition(bi, selg, curthread.ThreadID()) - var firstPCAfterPrologue uint64 + firstPCAfterPrologue, err := FirstPCAfterPrologue(dbp, topframe.Current.Fn, false) + if err != nil { + return err + } if backward { - firstPCAfterPrologue, err = FirstPCAfterPrologue(dbp, topframe.Current.Fn, false) - if err != nil { - return err - } if firstPCAfterPrologue == topframe.Current.PC { // We don't want to step into the prologue so we just execute a reverse step out instead if err := stepOutReverse(dbp, topframe, retframe, sameGCond); err != nil { @@ -705,7 +720,7 @@ func next(dbp *Target, stepInto, inlinedStepOut bool) error { } } - text, err := disassemble(dbp.Memory(), regs, dbp.Breakpoints(), dbp.BinInfo(), topframe.Current.Fn.Entry, topframe.Current.Fn.End, false) + text, err := disassemble(dbp.Memory(), regs, dbp.Breakpoints(), bi, topframe.Current.Fn.Entry, topframe.Current.Fn.End, false) if err != nil && stepInto { return err } @@ -720,7 +735,11 @@ func next(dbp *Target, stepInto, inlinedStepOut bool) error { } if !backward && !topframe.Current.Fn.cu.image.Stripped() { - _, err = setDeferBreakpoint(dbp, text, topframe, sameGCond, stepInto) + fr := topframe + if len(rangeFrames) != 0 && !stepInto { + fr = rangeFrames[len(rangeFrames)-2] + } + _, err = setDeferBreakpoint(dbp, text, fr, sameGCond, stepInto) if err != nil { return err } @@ -752,7 +771,7 @@ func next(dbp *Target, stepInto, inlinedStepOut bool) error { if inlinedStepOut { frame = retframe } - pcs, err = removeInlinedCalls(pcs, frame) + pcs, err = removeInlinedCalls(pcs, frame, bi) if err != nil { return err } @@ -768,7 +787,7 @@ func next(dbp *Target, stepInto, inlinedStepOut bool) error { } if !covered { - fn := dbp.BinInfo().PCToFunc(topframe.Ret) + fn := bi.PCToFunc(topframe.Ret) if selg != nil && fn != nil && fn.Name == "runtime.goexit" { return nil } @@ -776,8 +795,12 @@ func next(dbp *Target, stepInto, inlinedStepOut bool) error { } for _, pc := range pcs { + if !stepInto && topframe.Call.Fn.extra(bi).rangeParent != nil { + if pc < firstPCAfterPrologue { + continue + } + } if _, err := allowDuplicateBreakpoint(dbp.SetBreakpoint(0, pc, NextBreakpoint, sameFrameCond)); err != nil { - dbp.ClearSteppingBreakpoints() return err } } @@ -789,6 +812,37 @@ func next(dbp *Target, stepInto, inlinedStepOut bool) error { } } + // Stepping into range-over-func-bodies + if !stepInto && !inlinedStepOut { + rangeParent := topframe.Call.Fn.extra(bi).rangeParent + if rangeParent == nil { + rangeParent = topframe.Call.Fn + } + for _, fn := range rangeParent.extra(bi).rangeBodies { + if fn.Entry != 0 { + pc, err := FirstPCAfterPrologue(dbp, fn, false) + if err != nil { + return err + } + // TODO: this breakpoint must have a condition on .closureptr (https://go-review.googlesource.com/c/go/+/586975) + if _, err := allowDuplicateBreakpoint(dbp.SetBreakpoint(0, pc, NextBreakpoint, sameGCond)); err != nil { + return err + } + } + } + } + + // Set step-out breakpoints for range-over-func body closures + if !stepInto && selg != nil && topframe.Current.Fn.extra(bi).rangeParent != nil { + for _, fr := range rangeFrames[:len(rangeFrames)-1] { + if !fr.Inlined { + dbp.SetBreakpoint(0, fr.Current.PC, NextBreakpoint, astutil.And(sameGCond, frameoffCondition(&fr))) + } + } + topframe, retframe = rangeFrames[len(rangeFrames)-2], rangeFrames[len(rangeFrames)-1] + } + + // Step-out breakpoint if !topframe.Inlined { topframe, retframe := skipAutogeneratedWrappersOut(dbp, selg, curthread, &topframe, &retframe) retFrameCond := astutil.And(sameGCond, frameoffCondition(retframe)) @@ -801,7 +855,7 @@ func next(dbp *Target, stepInto, inlinedStepOut bool) error { // Return address could be wrong, if we are unable to set a breakpoint // there it's ok. if bp != nil { - configureReturnBreakpoint(dbp.BinInfo(), bp, topframe, retFrameCond) + configureReturnBreakpoint(bi, bp, topframe, retFrameCond) } } @@ -914,22 +968,41 @@ func FindDeferReturnCalls(text []AsmInstruction) []uint64 { } // Removes instructions belonging to inlined calls of topframe from pcs. -// If includeCurrentFn is true it will also remove all instructions -// belonging to the current function. -func removeInlinedCalls(pcs []uint64, topframe Stackframe) ([]uint64, error) { +func removeInlinedCalls(pcs []uint64, topframe Stackframe, bi *BinaryInfo) ([]uint64, error) { // TODO(derekparker) it should be possible to still use some internal // runtime information to do this. if topframe.Call.Fn == nil || topframe.Call.Fn.cu.image.Stripped() { return pcs, nil } + + topframeRangeParentName := "" + if topframe.Call.Fn.extra(bi).rangeParent != nil { + topframeRangeParentName = topframe.Call.Fn.extra(bi).rangeParent.Name + } + dwarfTree, err := topframe.Call.Fn.cu.image.getDwarfTree(topframe.Call.Fn.offset) if err != nil { return pcs, err } for _, e := range reader.InlineStack(dwarfTree, 0) { + // keep all PCs that belong to topframe if e.Offset == topframe.Call.Fn.offset { continue } + // also keep all PCs that belong to a range-over-func body closure that + // belongs to the same function as topframe or to the range parent of + // topframe. + fnname, _ := e.Val(dwarf.AttrName).(string) + ridx := rangeParentName(fnname) + var rpn string + if ridx == -1 { + rpn = fnname + } else { + rpn = fnname[:ridx] + } + if rpn == topframeRangeParentName { + continue + } for _, rng := range e.Ranges { pcs = removePCsBetween(pcs, rng[0], rng[1]) } diff --git a/pkg/proc/variables.go b/pkg/proc/variables.go index b795aec548..e092375d4c 100644 --- a/pkg/proc/variables.go +++ b/pkg/proc/variables.go @@ -494,7 +494,7 @@ func (g *G) Defer() *Defer { return nil } d := &Defer{variable: dvar} - d.load() + d.load(true) return d } @@ -1948,7 +1948,7 @@ func (v *Variable) loadFunctionPtr(recurseLevel int, cfg LoadConfig) { } v.Value = constant.MakeString(fn.Name) - cst := fn.closureStructType(v.bi) + cst := fn.extra(v.bi).closureStructType v.Len = int64(len(cst.Field)) if recurseLevel <= cfg.MaxVariableRecurse {