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:
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
|
trampoline bool // DW_AT_trampoline attribute set to true
|
||||||
|
|
||||||
// InlinedCalls lists all inlined calls to this function
|
// 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
|
// 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
|
// 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')
|
return len(name) > n && name[:n] == "runtime." && !('A' <= name[n] && name[n] <= 'Z')
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fn *Function) closureStructType(bi *BinaryInfo) *godwarf.StructType {
|
func rangeParentName(fnname string) int {
|
||||||
if fn.closureStructTypeCached != nil {
|
const rangeSuffix = "-range"
|
||||||
return fn.closureStructTypeCached
|
ridx := strings.Index(fnname, rangeSuffix)
|
||||||
|
if ridx <= 0 {
|
||||||
|
return -1
|
||||||
}
|
}
|
||||||
dwarfTree, err := fn.cu.image.getDwarfTree(fn.offset)
|
ok := true
|
||||||
if err != nil {
|
for i := ridx + len(rangeSuffix); i < len(fnname); i++ {
|
||||||
return nil
|
if fnname[i] < '0' || fnname[i] > '9' {
|
||||||
|
ok = false
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
st := &godwarf.StructType{
|
if !ok {
|
||||||
Kind: "struct",
|
return -1
|
||||||
}
|
}
|
||||||
vars := reader.Variables(dwarfTree, 0, 0, reader.VariablesNoDeclLineCheck|reader.VariablesSkipInlinedSubroutines)
|
return ridx
|
||||||
for _, v := range vars {
|
}
|
||||||
off, ok := v.Val(godwarf.AttrGoClosureOffset).(int64)
|
|
||||||
if ok {
|
// rangeParentName, if this function is a range-over-func body closure
|
||||||
n, _ := v.Val(dwarf.AttrName).(string)
|
// returns the name of the parent function, otherwise returns ""
|
||||||
typ, err := v.Type(fn.cu.image.dwarf, fn.cu.image.index, fn.cu.image.typeCache)
|
func (fn *Function) rangeParentName() string {
|
||||||
if err == nil {
|
if fn.rangeParentNameCache == 0 {
|
||||||
sz := typ.Common().ByteSize
|
ridx := rangeParentName(fn.Name)
|
||||||
st.Field = append(st.Field, &godwarf.StructField{
|
fn.rangeParentNameCache = ridx
|
||||||
Name: n,
|
}
|
||||||
Type: typ,
|
if fn.rangeParentNameCache < 0 {
|
||||||
ByteOffset: off,
|
return ""
|
||||||
ByteSize: sz,
|
}
|
||||||
BitOffset: off * 8,
|
return fn.Name[:fn.rangeParentNameCache]
|
||||||
BitSize: sz * 8,
|
}
|
||||||
})
|
|
||||||
|
// 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 {
|
return fn.extraCache
|
||||||
lf := st.Field[len(st.Field)-1]
|
|
||||||
st.ByteSize = lf.ByteOffset + lf.Type.Common().ByteSize
|
|
||||||
}
|
|
||||||
fn.closureStructTypeCached = st
|
|
||||||
return st
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type constantsMap map[dwarfRef]*constantType
|
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) {
|
func testseq2Args(wd string, args []string, buildFlags protest.BuildFlags, t *testing.T, program string, initialLocation string, testcases []seqTest) {
|
||||||
protest.AllowRecording(t)
|
protest.AllowRecording(t)
|
||||||
|
t.Helper()
|
||||||
withTestProcessArgs(program, t, wd, args, buildFlags, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
|
withTestProcessArgs(program, t, wd, args, buildFlags, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
|
||||||
|
checkBreakpointClear := true
|
||||||
var bp *proc.Breakpoint
|
var bp *proc.Breakpoint
|
||||||
if initialLocation != "" {
|
if initialLocation != "" {
|
||||||
bp = setFunctionBreakpoint(p, t, initialLocation)
|
bp = setFunctionBreakpoint(p, t, initialLocation)
|
||||||
} else if testcases[0].cf == contContinue {
|
} else if testcases[0].cf == contContinue {
|
||||||
bp = setFileBreakpoint(p, t, fixture.Source, testcases[0].pos.(int))
|
bp = setFileBreakpoint(p, t, fixture.Source, testcases[0].pos.(int))
|
||||||
|
} else if testcases[0].cf == contNothing {
|
||||||
|
// Do nothing
|
||||||
|
checkBreakpointClear = false
|
||||||
} else {
|
} else {
|
||||||
panic("testseq2 can not set initial breakpoint")
|
panic("testseq2 can not set initial breakpoint")
|
||||||
}
|
}
|
||||||
if traceTestseq2 {
|
if traceTestseq2 {
|
||||||
t.Logf("initial breakpoint %v", bp)
|
t.Logf("initial breakpoint %v", bp)
|
||||||
}
|
}
|
||||||
regs, err := p.CurrentThread().Registers()
|
|
||||||
assertNoError(err, t, "Registers")
|
|
||||||
|
|
||||||
f, ln := currentLineNumber(p, t)
|
testseq2intl(t, fixture, grp, p, bp, testcases)
|
||||||
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 {
|
if countBreakpoints(p) != 0 && checkBreakpointClear {
|
||||||
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 {
|
|
||||||
t.Fatal("Not all breakpoints were cleaned up", len(p.Breakpoints().M))
|
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) {
|
func TestNextGeneral(t *testing.T) {
|
||||||
var testcases []nextTest
|
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 uint64 // value of g0.sched.sp (see comments around its use)
|
||||||
g0_sched_sp_loaded bool // g0_sched_sp was loaded from g0
|
g0_sched_sp_loaded bool // g0_sched_sp was loaded from g0
|
||||||
|
|
||||||
|
count int
|
||||||
|
|
||||||
opts StacktraceOptions
|
opts StacktraceOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -353,17 +355,11 @@ func (it *stackIterator) stacktrace(depth int) ([]Stackframe, error) {
|
|||||||
if depth < 0 {
|
if depth < 0 {
|
||||||
return nil, errors.New("negative maximum stack depth")
|
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)
|
frames := make([]Stackframe, 0, depth+1)
|
||||||
for it.Next() {
|
it.stacktraceFunc(func(frame Stackframe) bool {
|
||||||
frames = it.appendInlineCalls(frames, it.Frame())
|
frames = append(frames, frame)
|
||||||
if len(frames) >= depth+1 {
|
return len(frames) < depth+1
|
||||||
break
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := it.Err(); err != nil {
|
if err := it.Err(); err != nil {
|
||||||
if len(frames) == 0 {
|
if len(frames) == 0 {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -373,22 +369,37 @@ func (it *stackIterator) stacktrace(depth int) ([]Stackframe, error) {
|
|||||||
return frames, nil
|
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 {
|
if frame.Call.Fn == nil {
|
||||||
return append(frames, frame)
|
it.count++
|
||||||
|
return callback(frame)
|
||||||
}
|
}
|
||||||
if frame.Call.Fn.cu.lineInfo == nil {
|
if frame.Call.Fn.cu.lineInfo == nil {
|
||||||
return append(frames, frame)
|
it.count++
|
||||||
|
return callback(frame)
|
||||||
}
|
}
|
||||||
|
|
||||||
callpc := frame.Call.PC
|
callpc := frame.Call.PC
|
||||||
if len(frames) > 0 {
|
if it.count > 0 {
|
||||||
callpc--
|
callpc--
|
||||||
}
|
}
|
||||||
|
|
||||||
dwarfTree, err := frame.Call.Fn.cu.image.getDwarfTree(frame.Call.Fn.offset)
|
dwarfTree, err := frame.Call.Fn.cu.image.getDwarfTree(frame.Call.Fn.offset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return append(frames, frame)
|
it.count++
|
||||||
|
return callback(frame)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, entry := range reader.InlineStack(dwarfTree, callpc) {
|
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}
|
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,
|
Current: frame.Current,
|
||||||
Call: Location{
|
Call: Location{
|
||||||
frame.Call.PC,
|
frame.Call.PC,
|
||||||
@ -427,7 +439,8 @@ func (it *stackIterator) appendInlineCalls(frames []Stackframe, frame Stackframe
|
|||||||
frame.Call.Line = int(line)
|
frame.Call.Line = int(line)
|
||||||
}
|
}
|
||||||
|
|
||||||
return append(frames, frame)
|
it.count++
|
||||||
|
return callback(frame)
|
||||||
}
|
}
|
||||||
|
|
||||||
// advanceRegs calculates the DwarfRegisters for a next stack frame
|
// advanceRegs calculates the DwarfRegisters for a next stack frame
|
||||||
@ -618,6 +631,8 @@ type Defer struct {
|
|||||||
link *Defer // Next deferred function
|
link *Defer // Next deferred function
|
||||||
argSz int64 // Always 0 in Go >=1.17
|
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
|
variable *Variable
|
||||||
Unreadable error
|
Unreadable error
|
||||||
}
|
}
|
||||||
@ -644,7 +659,7 @@ func (g *G) readDefers(frames []Stackframe) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if frames[i].TopmostDefer == nil {
|
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) {
|
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.
|
// compared with deferred frames.
|
||||||
i++
|
i++
|
||||||
} else {
|
} 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()
|
curdefer = curdefer.Next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Defer) load() {
|
const maxRangeFuncDefers = 10
|
||||||
|
|
||||||
|
func (d *Defer) load(canrecur bool) {
|
||||||
v := d.variable // +rtype _defer
|
v := d.variable // +rtype _defer
|
||||||
v.loadValue(LoadConfig{false, 1, 0, 0, -1, 0})
|
v.loadValue(LoadConfig{false, 1, 0, 0, -1, 0})
|
||||||
if v.Unreadable != nil {
|
if v.Unreadable != nil {
|
||||||
@ -701,6 +722,34 @@ func (d *Defer) load() {
|
|||||||
if linkvar.Addr != 0 {
|
if linkvar.Addr != 0 {
|
||||||
d.link = &Defer{variable: linkvar}
|
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
|
// errSPDecreased is used when (*Defer).Next detects a corrupted linked
|
||||||
@ -715,13 +764,20 @@ func (d *Defer) Next() *Defer {
|
|||||||
if d.link == nil {
|
if d.link == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
d.link.load()
|
d.link.load(true)
|
||||||
if d.link.SP < d.SP {
|
if d.link.SP < d.SP {
|
||||||
d.link.Unreadable = errSPDecreased
|
d.link.Unreadable = errSPDecreased
|
||||||
}
|
}
|
||||||
return d.link
|
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.
|
// 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
|
// The argument frame of a deferred call is stored in memory immediately
|
||||||
// after the deferred header.
|
// 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)
|
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
|
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
|
success := false
|
||||||
defer func() {
|
defer func() {
|
||||||
if !success {
|
if !success {
|
||||||
@ -645,6 +655,7 @@ func next(dbp *Target, stepInto, inlinedStepOut bool) error {
|
|||||||
backward := dbp.recman.GetDirection() == Backward
|
backward := dbp.recman.GetDirection() == Backward
|
||||||
selg := dbp.SelectedGoroutine()
|
selg := dbp.SelectedGoroutine()
|
||||||
curthread := dbp.CurrentThread()
|
curthread := dbp.CurrentThread()
|
||||||
|
bi := dbp.BinInfo()
|
||||||
topframe, retframe, err := topframe(dbp, selg, curthread)
|
topframe, retframe, err := topframe(dbp, selg, curthread)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -663,6 +674,11 @@ func next(dbp *Target, stepInto, inlinedStepOut bool) error {
|
|||||||
panic("next called with inlinedStepOut but topframe was not inlined")
|
panic("next called with inlinedStepOut but topframe was not inlined")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rangeFrames, err := rangeFuncStackTrace(dbp, selg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
success := false
|
success := false
|
||||||
defer func() {
|
defer func() {
|
||||||
if !success {
|
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 {
|
if backward {
|
||||||
firstPCAfterPrologue, err = FirstPCAfterPrologue(dbp, topframe.Current.Fn, false)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if firstPCAfterPrologue == topframe.Current.PC {
|
if firstPCAfterPrologue == topframe.Current.PC {
|
||||||
// We don't want to step into the prologue so we just execute a reverse step out instead
|
// 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 {
|
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 {
|
if err != nil && stepInto {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -720,7 +735,11 @@ func next(dbp *Target, stepInto, inlinedStepOut bool) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !backward && !topframe.Current.Fn.cu.image.Stripped() {
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -752,7 +771,7 @@ func next(dbp *Target, stepInto, inlinedStepOut bool) error {
|
|||||||
if inlinedStepOut {
|
if inlinedStepOut {
|
||||||
frame = retframe
|
frame = retframe
|
||||||
}
|
}
|
||||||
pcs, err = removeInlinedCalls(pcs, frame)
|
pcs, err = removeInlinedCalls(pcs, frame, bi)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -768,7 +787,7 @@ func next(dbp *Target, stepInto, inlinedStepOut bool) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !covered {
|
if !covered {
|
||||||
fn := dbp.BinInfo().PCToFunc(topframe.Ret)
|
fn := bi.PCToFunc(topframe.Ret)
|
||||||
if selg != nil && fn != nil && fn.Name == "runtime.goexit" {
|
if selg != nil && fn != nil && fn.Name == "runtime.goexit" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -776,8 +795,12 @@ func next(dbp *Target, stepInto, inlinedStepOut bool) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, pc := range pcs {
|
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 {
|
if _, err := allowDuplicateBreakpoint(dbp.SetBreakpoint(0, pc, NextBreakpoint, sameFrameCond)); err != nil {
|
||||||
dbp.ClearSteppingBreakpoints()
|
|
||||||
return err
|
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 {
|
if !topframe.Inlined {
|
||||||
topframe, retframe := skipAutogeneratedWrappersOut(dbp, selg, curthread, &topframe, &retframe)
|
topframe, retframe := skipAutogeneratedWrappersOut(dbp, selg, curthread, &topframe, &retframe)
|
||||||
retFrameCond := astutil.And(sameGCond, frameoffCondition(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
|
// Return address could be wrong, if we are unable to set a breakpoint
|
||||||
// there it's ok.
|
// there it's ok.
|
||||||
if bp != nil {
|
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.
|
// Removes instructions belonging to inlined calls of topframe from pcs.
|
||||||
// If includeCurrentFn is true it will also remove all instructions
|
func removeInlinedCalls(pcs []uint64, topframe Stackframe, bi *BinaryInfo) ([]uint64, error) {
|
||||||
// belonging to the current function.
|
|
||||||
func removeInlinedCalls(pcs []uint64, topframe Stackframe) ([]uint64, error) {
|
|
||||||
// TODO(derekparker) it should be possible to still use some internal
|
// TODO(derekparker) it should be possible to still use some internal
|
||||||
// runtime information to do this.
|
// runtime information to do this.
|
||||||
if topframe.Call.Fn == nil || topframe.Call.Fn.cu.image.Stripped() {
|
if topframe.Call.Fn == nil || topframe.Call.Fn.cu.image.Stripped() {
|
||||||
return pcs, nil
|
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)
|
dwarfTree, err := topframe.Call.Fn.cu.image.getDwarfTree(topframe.Call.Fn.offset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return pcs, err
|
return pcs, err
|
||||||
}
|
}
|
||||||
for _, e := range reader.InlineStack(dwarfTree, 0) {
|
for _, e := range reader.InlineStack(dwarfTree, 0) {
|
||||||
|
// keep all PCs that belong to topframe
|
||||||
if e.Offset == topframe.Call.Fn.offset {
|
if e.Offset == topframe.Call.Fn.offset {
|
||||||
continue
|
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 {
|
for _, rng := range e.Ranges {
|
||||||
pcs = removePCsBetween(pcs, rng[0], rng[1])
|
pcs = removePCsBetween(pcs, rng[0], rng[1])
|
||||||
}
|
}
|
||||||
|
|||||||
@ -494,7 +494,7 @@ func (g *G) Defer() *Defer {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
d := &Defer{variable: dvar}
|
d := &Defer{variable: dvar}
|
||||||
d.load()
|
d.load(true)
|
||||||
return d
|
return d
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1948,7 +1948,7 @@ func (v *Variable) loadFunctionPtr(recurseLevel int, cfg LoadConfig) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
v.Value = constant.MakeString(fn.Name)
|
v.Value = constant.MakeString(fn.Name)
|
||||||
cst := fn.closureStructType(v.bi)
|
cst := fn.extra(v.bi).closureStructType
|
||||||
v.Len = int64(len(cst.Field))
|
v.Len = int64(len(cst.Field))
|
||||||
|
|
||||||
if recurseLevel <= cfg.MaxVariableRecurse {
|
if recurseLevel <= cfg.MaxVariableRecurse {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user