proc: make nested function calls work when stopped at a sw breakpoint (#2232)

evalFunctionCall needs to remove the breakpoint from the current thread
after starting the function call injection, otherwise Continue will
think that the thread is stopped at a breakpoint and return to the user
instead of continuing the call injection.
This commit is contained in:
Alessandro Arzilli 2020-12-10 18:03:11 +01:00 committed by GitHub
parent 807664b34b
commit d3e9158e9e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 21 additions and 1 deletions

@ -193,7 +193,7 @@ func main() {
d := &Derived{3, Base{4}} d := &Derived{3, Base{4}}
runtime.Breakpoint() runtime.Breakpoint() // breakpoint here
call1(one, two) call1(one, two)
fn2clos(2) fn2clos(2)
strings.LastIndexByte(stringslice[1], 'w') strings.LastIndexByte(stringslice[1], 'w')

@ -316,6 +316,7 @@ func evalFunctionCall(scope *EvalScope, node *ast.CallExpr) (*Variable, error) {
fncallLog("function call initiated %v frame size %d goroutine %d (thread %d)", fncall.fn, fncall.argFrameSize, scope.g.ID, thread.ThreadID()) fncallLog("function call initiated %v frame size %d goroutine %d (thread %d)", fncall.fn, fncall.argFrameSize, scope.g.ID, thread.ThreadID())
thread.Breakpoint().Clear() // since we moved address in PC the thread is no longer stopped at a breakpoint, leaving the breakpoint set will confuse Continue
p.fncallForG[scope.g.ID].startThreadID = thread.ThreadID() p.fncallForG[scope.g.ID].startThreadID = thread.ThreadID()
spoff := int64(scope.Regs.Uint64Val(scope.Regs.SPRegNum)) - int64(scope.g.stack.hi) spoff := int64(scope.Regs.Uint64Val(scope.Regs.SPRegNum)) - int64(scope.g.stack.hi)
@ -946,6 +947,10 @@ func isCallInjectionStop(t *Target, thread Thread, loc *Location) bool {
if !strings.HasPrefix(loc.Fn.Name, debugCallFunctionNamePrefix1) && !strings.HasPrefix(loc.Fn.Name, debugCallFunctionNamePrefix2) { if !strings.HasPrefix(loc.Fn.Name, debugCallFunctionNamePrefix1) && !strings.HasPrefix(loc.Fn.Name, debugCallFunctionNamePrefix2) {
return false return false
} }
if loc.PC == loc.Fn.Entry {
// call injection just started, did not make any progress before being interrupted by a concurrent breakpoint.
return false
}
text, err := disassembleCurrentInstruction(t, thread, -1) text, err := disassembleCurrentInstruction(t, thread, -1)
if err != nil || len(text) <= 0 { if err != nil || len(text) <= 0 {
return false return false

@ -4,6 +4,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"go/constant" "go/constant"
"io/ioutil"
"path/filepath" "path/filepath"
"runtime" "runtime"
"sort" "sort"
@ -1269,6 +1270,9 @@ func TestCallFunction(t *testing.T) {
if err != nil { if err != nil {
t.Skip("function calls not supported on this version of go") t.Skip("function calls not supported on this version of go")
} }
testCallFunctionSetBreakpoint(t, p, fixture)
assertNoError(p.Continue(), t, "Continue()") assertNoError(p.Continue(), t, "Continue()")
for _, tc := range testcases { for _, tc := range testcases {
testCallFunction(t, p, tc) testCallFunction(t, p, tc)
@ -1302,6 +1306,17 @@ func TestCallFunction(t *testing.T) {
}) })
} }
func testCallFunctionSetBreakpoint(t *testing.T, p *proc.Target, fixture protest.Fixture) {
buf, err := ioutil.ReadFile(fixture.Source)
assertNoError(err, t, "ReadFile")
for i, line := range strings.Split(string(buf), "\n") {
if strings.Contains(line, "// breakpoint here") {
setFileBreakpoint(p, t, fixture, i+1)
return
}
}
}
func testCallFunction(t *testing.T, p *proc.Target, tc testCaseCallFunction) { func testCallFunction(t *testing.T, p *proc.Target, tc testCaseCallFunction) {
const unsafePrefix = "-unsafe " const unsafePrefix = "-unsafe "