proc: fix deferreturn detection for Go 1.18
Go 1.18 removed the jmpdefer call from deferreturn, now deferreturn is a normal function call that can appear on the stack, rules for detecting a deferreturn call must be changed and new code must be added to skip it while stepping out.
This commit is contained in:
parent
cf58ba4380
commit
de322cd113
@ -372,6 +372,13 @@ func isPanicCall(frames []Stackframe) (bool, int) {
|
||||
}
|
||||
|
||||
func isDeferReturnCall(frames []Stackframe, deferReturns []uint64) (bool, uint64) {
|
||||
if len(frames) >= 2 && (len(deferReturns) > 0) {
|
||||
// On Go 1.18 and later runtime.deferreturn doesn't use jmpdefer anymore,
|
||||
// it's a normal function making normal calls to deferred functions.
|
||||
if frames[1].Current.Fn != nil && frames[1].Current.Fn.Name == "runtime.deferreturn" {
|
||||
return true, 0
|
||||
}
|
||||
}
|
||||
if len(frames) >= 1 {
|
||||
for _, pc := range deferReturns {
|
||||
if frames[0].Ret == pc {
|
||||
|
@ -517,7 +517,7 @@ func testseq2Args(wd string, args []string, buildFlags protest.BuildFlags, t *te
|
||||
}
|
||||
switch pos := tc.pos.(type) {
|
||||
case int:
|
||||
if ln != pos {
|
||||
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:
|
||||
@ -4816,33 +4816,64 @@ func TestBackwardNextDeferPanic(t *testing.T) {
|
||||
if testBackend != "rr" {
|
||||
t.Skip("Reverse stepping test needs rr")
|
||||
}
|
||||
testseq2(t, "defercall", "", []seqTest{
|
||||
{contContinue, 12},
|
||||
{contReverseNext, 11},
|
||||
{contReverseNext, 10},
|
||||
{contReverseNext, 9},
|
||||
{contReverseNext, 27},
|
||||
if goversion.VersionAfterOrEqual(runtime.Version(), 1, 18) {
|
||||
testseq2(t, "defercall", "", []seqTest{
|
||||
{contContinue, 12},
|
||||
{contReverseNext, 11},
|
||||
{contReverseNext, 10},
|
||||
{contReverseNext, 9},
|
||||
{contReverseNext, 27},
|
||||
|
||||
{contContinueToBreakpoint, 12}, // skip first call to sampleFunction
|
||||
{contContinueToBreakpoint, 6}, // go to call to sampleFunction through deferreturn
|
||||
{contReverseNext, 13},
|
||||
{contReverseNext, 12},
|
||||
{contReverseNext, 11},
|
||||
{contReverseNext, 10},
|
||||
{contReverseNext, 9},
|
||||
{contReverseNext, 27},
|
||||
{contContinueToBreakpoint, 12}, // skip first call to sampleFunction
|
||||
{contContinueToBreakpoint, 6}, // go to call to sampleFunction through deferreturn
|
||||
{contReverseNext, -1}, // runtime.deferreturn, maybe we should try to skip this
|
||||
{contReverseStepout, 13},
|
||||
{contReverseNext, 12},
|
||||
{contReverseNext, 11},
|
||||
{contReverseNext, 10},
|
||||
{contReverseNext, 9},
|
||||
{contReverseNext, 27},
|
||||
|
||||
{contContinueToBreakpoint, 18}, // go to panic call
|
||||
{contNext, 6}, // panic so the deferred call happens
|
||||
{contReverseNext, 18},
|
||||
{contReverseNext, 17},
|
||||
{contReverseNext, 16},
|
||||
{contReverseNext, 15},
|
||||
{contReverseNext, 23},
|
||||
{contReverseNext, 22},
|
||||
{contReverseNext, 21},
|
||||
{contReverseNext, 28},
|
||||
})
|
||||
{contContinueToBreakpoint, 18}, // go to panic call
|
||||
{contNext, 6}, // panic so the deferred call happens
|
||||
{contReverseNext, 18},
|
||||
{contReverseNext, 17},
|
||||
{contReverseNext, 16},
|
||||
{contReverseNext, 15},
|
||||
{contReverseNext, 23},
|
||||
{contReverseNext, 22},
|
||||
{contReverseNext, 21},
|
||||
{contReverseNext, 28},
|
||||
})
|
||||
} else {
|
||||
testseq2(t, "defercall", "", []seqTest{
|
||||
{contContinue, 12},
|
||||
{contReverseNext, 11},
|
||||
{contReverseNext, 10},
|
||||
{contReverseNext, 9},
|
||||
{contReverseNext, 27},
|
||||
|
||||
{contContinueToBreakpoint, 12}, // skip first call to sampleFunction
|
||||
{contContinueToBreakpoint, 6}, // go to call to sampleFunction through deferreturn
|
||||
{contReverseNext, 13},
|
||||
{contReverseNext, 12},
|
||||
{contReverseNext, 11},
|
||||
{contReverseNext, 10},
|
||||
{contReverseNext, 9},
|
||||
{contReverseNext, 27},
|
||||
|
||||
{contContinueToBreakpoint, 18}, // go to panic call
|
||||
{contNext, 6}, // panic so the deferred call happens
|
||||
{contReverseNext, 18},
|
||||
{contReverseNext, 17},
|
||||
{contReverseNext, 16},
|
||||
{contReverseNext, 15},
|
||||
{contReverseNext, 23},
|
||||
{contReverseNext, 22},
|
||||
{contReverseNext, 21},
|
||||
{contReverseNext, 28},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssue1925(t *testing.T) {
|
||||
|
@ -819,6 +819,10 @@ func isAutogenerated(loc Location) bool {
|
||||
return loc.File == "<autogenerated>" && loc.Line == 1
|
||||
}
|
||||
|
||||
func isAutogeneratedOrDeferReturn(loc Location) bool {
|
||||
return isAutogenerated(loc) || (loc.Fn != nil && loc.Fn.Name == "runtime.deferreturn")
|
||||
}
|
||||
|
||||
// skipAutogeneratedWrappers skips autogenerated wrappers when setting a
|
||||
// step-into breakpoint.
|
||||
// See genwrapper in: $GOROOT/src/cmd/compile/internal/gc/subr.go
|
||||
@ -878,12 +882,13 @@ func skipAutogeneratedWrappersIn(p Process, startfn *Function, startpc uint64) (
|
||||
// skipAutogeneratedWrappersOut skip autogenerated wrappers when setting a
|
||||
// step out breakpoint.
|
||||
// See genwrapper in: $GOROOT/src/cmd/compile/internal/gc/subr.go
|
||||
// It also skips runtime.deferreturn frames (which are only ever on the stack on Go 1.18 or later)
|
||||
func skipAutogeneratedWrappersOut(g *G, thread Thread, startTopframe, startRetframe *Stackframe) (topframe, retframe *Stackframe) {
|
||||
topframe, retframe = startTopframe, startRetframe
|
||||
if startTopframe.Ret == 0 {
|
||||
return
|
||||
}
|
||||
if !isAutogenerated(startRetframe.Current) {
|
||||
if !isAutogeneratedOrDeferReturn(startRetframe.Current) {
|
||||
return
|
||||
}
|
||||
retfn := thread.BinInfo().PCToFunc(startTopframe.Ret)
|
||||
@ -909,7 +914,7 @@ func skipAutogeneratedWrappersOut(g *G, thread Thread, startTopframe, startRetfr
|
||||
return
|
||||
}
|
||||
file, line := frame.Current.Fn.cu.lineInfo.PCToLine(frame.Current.Fn.Entry, frame.Current.Fn.Entry)
|
||||
if !isAutogenerated(Location{File: file, Line: line, Fn: frame.Current.Fn}) {
|
||||
if !isAutogeneratedOrDeferReturn(Location{File: file, Line: line, Fn: frame.Current.Fn}) {
|
||||
return &frames[i-1], &frames[i]
|
||||
}
|
||||
}
|
||||
@ -1015,13 +1020,20 @@ func stepOutReverse(p *Target, topframe, retframe Stackframe, sameGCond ast.Expr
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if ok, pc := isDeferReturnCall(frames, deferReturns); ok {
|
||||
callpc = pc
|
||||
} else {
|
||||
callpc, err = findCallInstrForRet(p, p.Memory(), topframe.Ret, retframe.Current.Fn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// check if the call instruction to this frame is a call to runtime.deferreturn
|
||||
if len(frames) > 0 {
|
||||
frames[0].Ret = callpc
|
||||
}
|
||||
if ok, pc := isDeferReturnCall(frames, deferReturns); ok && pc != 0 {
|
||||
callpc = pc
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
_, err = allowDuplicateBreakpoint(p.SetBreakpoint(callpc, NextBreakpoint, sameGCond))
|
||||
|
Loading…
Reference in New Issue
Block a user