proc: allow evaluator to reference previous frames (#3534)

Fixes #3515
This commit is contained in:
Derek Parker 2023-10-24 09:57:39 -07:00 committed by GitHub
parent b9b553bccd
commit 1e2338d233
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 121 additions and 29 deletions

17
_fixtures/condframe.go Normal file

@ -0,0 +1,17 @@
package main
import "fmt"
func callme() {
for i := 0; i < 10; i++ {
callme2()
}
}
func callme2() {
fmt.Println("called again!!")
}
func main() {
callme()
}

@ -963,7 +963,19 @@ func (stack *evalStack) executeOp() {
stack.push(newConstant(op.Value, scope.Mem))
case *evalop.PushLocal:
vars, err := scope.Locals(0)
var vars []*Variable
var err error
if op.Frame != 0 {
var frameScope *EvalScope
frameScope, err = ConvertEvalScope(scope.target, scope.g.ID, int(op.Frame), 0)
if err != nil {
stack.err = err
return
}
vars, err = frameScope.Locals(0)
} else {
vars, err = scope.Locals(0)
}
if err != nil {
stack.err = err
return

@ -193,7 +193,7 @@ func (ctx *compileCtx) compileAST(t ast.Expr) error {
ctx.pushOp(&PushThreadID{})
case ctx.HasLocal(x.Name):
ctx.pushOp(&PushLocal{x.Name})
ctx.pushOp(&PushLocal{Name: x.Name})
ctx.pushOp(&Select{node.Sel.Name})
case ctx.HasGlobal(x.Name, node.Sel.Name):
@ -203,6 +203,27 @@ func (ctx *compileCtx) compileAST(t ast.Expr) error {
return ctx.compileUnary(node.X, &Select{node.Sel.Name})
}
case *ast.CallExpr:
ident, ok := x.Fun.(*ast.SelectorExpr)
if ok {
f, ok := ident.X.(*ast.Ident)
if ok && f.Name == "runtime" && ident.Sel.Name == "frame" {
switch arg := x.Args[0].(type) {
case *ast.BasicLit:
fr, err := strconv.ParseInt(arg.Value, 10, 8)
if err != nil {
return err
}
// Push local onto the stack to be evaluated in the new frame context.
ctx.pushOp(&PushLocal{Name: node.Sel.Name, Frame: fr})
return nil
default:
return fmt.Errorf("expected integer value for frame, got %v", arg)
}
}
}
return ctx.compileUnary(node.X, &Select{node.Sel.Name})
case *ast.BasicLit: // try to accept "package/path".varname syntax for package variables
s, err := strconv.Unquote(x.Value)
if err != nil {
@ -271,7 +292,6 @@ func (ctx *compileCtx) compileAST(t ast.Expr) error {
default:
return fmt.Errorf("expression %T not implemented", t)
}
return nil
}
@ -392,7 +412,7 @@ func (ctx *compileCtx) compileBuiltinCall(builtin string, args []ast.Expr) error
func (ctx *compileCtx) compileIdent(node *ast.Ident) error {
switch {
case ctx.HasLocal(node.Name):
ctx.pushOp(&PushLocal{node.Name})
ctx.pushOp(&PushLocal{Name: node.Name})
case ctx.HasGlobal("", node.Name):
ctx.pushOp(&PushPackageVar{"", node.Name})
case node.Name == "true" || node.Name == "false":

@ -40,6 +40,7 @@ func (*PushConst) depthCheck() (npop, npush int) { return 0, 1 }
// PushLocal pushes the local variable with the given name on the stack.
type PushLocal struct {
Name string
Frame int64
}
func (*PushLocal) depthCheck() (npop, npush int) { return 0, 1 }

@ -1,14 +1,13 @@
package native
import (
"fmt"
"debug/elf"
"fmt"
"syscall"
"unsafe"
sys "golang.org/x/sys/unix"
"github.com/go-delve/delve/pkg/proc"
"github.com/go-delve/delve/pkg/proc/linutil"
)
@ -45,4 +44,3 @@ func (t *nativeThread) restoreRegisters(savedRegs proc.Registers) error {
}
return restoreRegistersErr
}

@ -7,6 +7,7 @@ import (
"fmt"
"go/ast"
"go/constant"
"go/parser"
"go/token"
"io"
"math/rand"
@ -1770,6 +1771,36 @@ func TestCondBreakpoint(t *testing.T) {
})
}
func TestCondBreakpointWithFrame(t *testing.T) {
protest.AllowRecording(t)
withTestProcess("condframe", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
bp := setFileBreakpoint(p, t, fixture.Source, 12)
parsed, err := parser.ParseExpr("runtime.frame(1).i == 3")
if err != nil {
t.Fatalf("failed to parse expression: %v", err)
}
bp.UserBreaklet().Cond = parsed
assertNoError(grp.Continue(), t, "Continue()")
g := p.SelectedGoroutine()
scope, err := proc.ConvertEvalScope(p, g.ID, 1, 0)
if err != nil {
t.Fatal(err)
}
v, err := scope.EvalExpression("i", normalLoadConfig)
if err != nil {
t.Fatal(err)
}
vval, _ := constant.Int64Val(v.Value)
if vval != 3 {
t.Fatalf("Incorrect value for frame variable: %v", vval)
}
})
}
func TestCondBreakpointError(t *testing.T) {
protest.AllowRecording(t)
withTestProcess("parallel_next", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
@ -3381,7 +3412,7 @@ func TestCgoStacktrace(t *testing.T) {
logStacktrace(t, p, frames)
m := stacktraceCheck(t, tc, frames)
mismatch := (m == nil)
mismatch := m == nil
for i, j := range m {
if strings.HasPrefix(tc[i], "C.hellow") {
@ -5085,7 +5116,7 @@ func TestStepOutPreservesGoroutine(t *testing.T) {
}
pc := currentPC(p, t)
f, l, fn := p.BinInfo().PCToLine(pc)
var fnname string = "???"
var fnname = "???"
if fn != nil {
fnname = fn.Name
}

@ -1242,6 +1242,19 @@ func TestHitCondBreakpoint(t *testing.T) {
})
}
func TestCondBreakpointWithFrame(t *testing.T) {
withTestTerminal("condframe", t, func(term *FakeTerminal) {
term.MustExec("break bp1 callme2")
term.MustExec("condition bp1 runtime.frame(1).i == 3")
term.MustExec("continue")
out := term.MustExec("frame 1 print i")
t.Logf("%q", out)
if !strings.Contains(out, "3\n") {
t.Fatalf("wrong value of i")
}
})
}
func TestClearCondBreakpoint(t *testing.T) {
withTestTerminal("break", t, func(term *FakeTerminal) {
term.MustExec("break main.main:4")