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:
aarzilli 2021-08-22 16:46:00 +02:00 committed by Alessandro Arzilli
parent cf58ba4380
commit de322cd113
3 changed files with 80 additions and 30 deletions

@ -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))