mirror of
https://github.com/go-delve/delve.git
synced 2025-10-29 09:46:56 +08:00
proc: initial stepping with range-over-func support (#3736)
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
This commit is contained in:
committed by
GitHub
parent
40670aadc2
commit
2ec2e831d6
328
_fixtures/rangeoverfunc.go
Normal file
328
_fixtures/rangeoverfunc.go
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
@ -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,104 @@ 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
|
||||
}
|
||||
|
||||
if fn.cu.image.Stripped() {
|
||||
fn.extraCache = &functionExtra{}
|
||||
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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(st.Field) > 0 {
|
||||
lf := st.Field[len(st.Field)-1]
|
||||
st.ByteSize = lf.ByteOffset + lf.Type.Common().ByteSize
|
||||
}
|
||||
fn.closureStructTypeCached = st
|
||||
return st
|
||||
return fn.extraCache
|
||||
}
|
||||
|
||||
type constantsMap map[dwarfRef]*constantType
|
||||
|
||||
@ -488,120 +488,128 @@ 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))
|
||||
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
|
||||
}
|
||||
testseq2intl(t, fixture, grp, p, bp, testcases)
|
||||
|
||||
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()
|
||||
|
||||
if traceTestseq2 {
|
||||
t.Logf("at %#x %s:%d", pc, 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))
|
||||
}
|
||||
}
|
||||
|
||||
if countBreakpoints(p) != 0 {
|
||||
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("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))
|
||||
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 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) {
|
||||
var testcases []nextTest
|
||||
|
||||
@ -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},
|
||||
})
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
// rangeFuncStackTrace, 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 rangeFuncStackTrace(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
|
||||
nonMonotonicSP := 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() {
|
||||
nonMonotonicSP = 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 nonMonotonicSP {
|
||||
return nil, errors.New("corrupted stack (SP not monotonically decreasing)")
|
||||
}
|
||||
if stage != 3 {
|
||||
return nil, errors.New("could not find range-over-func closure parent on the stack")
|
||||
}
|
||||
g.readDefers(frames)
|
||||
return frames, nil
|
||||
}
|
||||
|
||||
@ -508,6 +508,16 @@ func (grp *TargetGroup) StepOut() error {
|
||||
return err
|
||||
}
|
||||
|
||||
rangeFrames, err := rangeFuncStackTrace(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 := rangeFuncStackTrace(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])
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user