From 2c700230de0db2f84c537f00b388b183d35645ac Mon Sep 17 00:00:00 2001 From: Alessandro Arzilli Date: Tue, 17 Oct 2023 20:21:59 +0200 Subject: [PATCH] proc: use stack machine to evaluate expressions (#3508) * proc: use stack machine to evaluate expressions This commit splits expression evaluation into two parts. The first part (in pkg/proc/evalop/evalcompile.go) "compiles" as ast.Expr into a list of instructions (defined in pkg/proc/evalop/ops.go) for a stack machine (defined by `proc.(*evalStack)`). The second part is a stack machine (implemented by `proc.(*EvalScope).eval` and `proc.(*EvalScope).evalOne`) that has two modes of operation: in the main mode it executes inteructions from the list (by calling `evalOne`), in the second mode it executes the call injection protocol by calling `funcCallStep` repeatedly until it either the protocol finishes, needs more input from the stack machine (to set call arguments) or fails. This approach has several benefits: - it is now possible to remove the goroutine we use to evaluate expression and the channel used to communicate with the Continue loop. - every time we resume the target to execute the call injection protocol we need to update several local variables to match the changed state of the target, this is now done at the top level of the evaluation loop instead of being hidden inside a recurisive evaluator - using runtime.Pin to pin addresses returned by an injected call would allow us to use a more natural evaluation order for function calls, which would solve some bugs #3310, allow users to inspect values returned by a call injection #1599 and allow implementing some other features #1465. Doing this with the recursive evaluator, while keeping backwards compatibility with versions of Go that do not have runtime.Pin is very hard. However after this change we can simply conditionally change how compileFunctionCall works and add some opcodes. * review round 1 * review round 2 --- pkg/dwarf/godwarf/fakes.go | 55 ++ pkg/proc/eval.go | 1188 +++++++++++++++------------ pkg/proc/{ => evalop}/eval_go117.go | 2 +- pkg/proc/{ => evalop}/eval_go118.go | 2 +- pkg/proc/evalop/evalcompile.go | 556 +++++++++++++ pkg/proc/evalop/evalop_test.go | 75 ++ pkg/proc/evalop/ops.go | 250 ++++++ pkg/proc/fncall.go | 489 ++++++----- pkg/proc/variables.go | 27 +- pkg/proc/variables_fuzz_test.go | 7 +- pkg/proc/variables_test.go | 37 +- 11 files changed, 1874 insertions(+), 814 deletions(-) create mode 100644 pkg/dwarf/godwarf/fakes.go rename pkg/proc/{ => evalop}/eval_go117.go (84%) rename pkg/proc/{ => evalop}/eval_go118.go (84%) create mode 100644 pkg/proc/evalop/evalcompile.go create mode 100644 pkg/proc/evalop/evalop_test.go create mode 100644 pkg/proc/evalop/ops.go diff --git a/pkg/dwarf/godwarf/fakes.go b/pkg/dwarf/godwarf/fakes.go new file mode 100644 index 0000000000..da19afe476 --- /dev/null +++ b/pkg/dwarf/godwarf/fakes.go @@ -0,0 +1,55 @@ +package godwarf + +import ( + "fmt" + "math/bits" + "reflect" +) + +// FakeSliceType synthesizes a slice type with the given field type. +func FakeSliceType(fieldType Type) Type { + return &SliceType{ + StructType: StructType{ + CommonType: CommonType{ + ByteSize: 24, + Name: "", + }, + StructName: "[]" + fieldType.Common().Name, + Kind: "struct", + Field: nil, + }, + ElemType: fieldType, + } +} + +// FakeBasicType synthesizes a basic type numeric type (int8, uint16, +// float32, etc) +func FakeBasicType(name string, bitSize int) Type { + byteSize := bitSize / 8 + szr := bits.OnesCount64(uint64(byteSize^(byteSize-1))) - 1 // position of rightmost 1 bit, minus 1 + + basic := func(kind reflect.Kind) BasicType { + return BasicType{ + CommonType: CommonType{ + ByteSize: int64(byteSize), + Name: fmt.Sprintf("%s%d", name, bitSize), + ReflectKind: kind, + }, + BitSize: int64(bitSize), + BitOffset: 0, + } + } + + switch name { + case "int": + return &IntType{BasicType: basic(reflect.Int8 + reflect.Kind(szr))} + case "uint": + return &UintType{BasicType: basic(reflect.Uint8 + reflect.Kind(szr))} + case "float": + return &FloatType{BasicType: basic(reflect.Float32 + reflect.Kind(szr-2))} + case "complex": + return &ComplexType{BasicType: basic(reflect.Complex64 + reflect.Kind(szr-3))} + default: + panic("unsupported") + } +} diff --git a/pkg/proc/eval.go b/pkg/proc/eval.go index 5c4458a532..6e4edbd972 100644 --- a/pkg/proc/eval.go +++ b/pkg/proc/eval.go @@ -12,8 +12,8 @@ import ( "go/scanner" "go/token" "reflect" + "runtime/debug" "sort" - "strconv" "strings" "github.com/go-delve/delve/pkg/dwarf/godwarf" @@ -21,6 +21,7 @@ import ( "github.com/go-delve/delve/pkg/dwarf/reader" "github.com/go-delve/delve/pkg/goversion" "github.com/go-delve/delve/pkg/logflags" + "github.com/go-delve/delve/pkg/proc/evalop" ) var errOperationOnSpecialFloat = errors.New("operations on non-finite floats not implemented") @@ -199,9 +200,15 @@ func (scope *EvalScope) EvalExpression(expr string, cfg LoadConfig) (*Variable, return nil, err } + ops, err := evalop.Compile(scopeToEvalLookup{scope}, t) + if err != nil { + scope.callCtx.doReturn(nil, err) + return nil, err + } + scope.loadCfg = &cfg - ev, err := scope.evalAST(t) + ev, err := scope.eval(ops) if err != nil { scope.callCtx.doReturn(nil, err) return nil, err @@ -214,6 +221,93 @@ func (scope *EvalScope) EvalExpression(expr string, cfg LoadConfig) (*Variable, return ev, nil } +type scopeToEvalLookup struct { + *EvalScope +} + +func (s scopeToEvalLookup) FindTypeExpr(expr ast.Expr) (godwarf.Type, error) { + return s.BinInfo.findTypeExpr(expr) +} + +func (scope scopeToEvalLookup) HasLocal(name string) bool { + if scope.Fn == nil { + return false + } + + flags := reader.VariablesOnlyVisible + if scope.BinInfo.Producer() != "" && goversion.ProducerAfterOrEqual(scope.BinInfo.Producer(), 1, 15) { + flags |= reader.VariablesTrustDeclLine + } + + dwarfTree, err := scope.image().getDwarfTree(scope.Fn.offset) + if err != nil { + return false + } + + varEntries := reader.Variables(dwarfTree, scope.PC, scope.Line, flags) + for _, entry := range varEntries { + curname, _ := entry.Val(dwarf.AttrName).(string) + if curname == name { + return true + } + if len(curname) > 0 && curname[0] == '&' { + if curname[1:] == name { + return true + } + } + } + return false +} + +func (scope scopeToEvalLookup) HasGlobal(pkgName, varName string) bool { + hasGlobalInternal := func(name string) bool { + for _, pkgvar := range scope.BinInfo.packageVars { + if pkgvar.name == name || strings.HasSuffix(pkgvar.name, "/"+name) { + return true + } + } + for _, fn := range scope.BinInfo.Functions { + if fn.Name == name || strings.HasSuffix(fn.Name, "/"+name) { + return true + } + } + for _, ctyp := range scope.BinInfo.consts { + for _, cval := range ctyp.values { + if cval.fullName == name || strings.HasSuffix(cval.fullName, "/"+name) { + return true + } + } + } + return false + } + + if pkgName == "" { + if scope.Fn == nil { + return false + } + return hasGlobalInternal(scope.Fn.PackageName() + "." + varName) + } + + for _, pkgPath := range scope.BinInfo.PackageMap[pkgName] { + if hasGlobalInternal(pkgPath + "." + varName) { + return true + } + } + return hasGlobalInternal(pkgName + "." + varName) +} + +func (scope scopeToEvalLookup) LookupRegisterName(name string) (int, bool) { + s := validRegisterName(name) + if s == "" { + return 0, false + } + return scope.BinInfo.Arch.RegisterNameToDwarf(s) +} + +func (scope scopeToEvalLookup) HasBuiltin(name string) bool { + return supportedBuiltins[name] != nil +} + // ChanGoroutines returns the list of goroutines waiting to receive from or // send to the channel. func (scope *EvalScope) ChanGoroutines(expr string, start, count int) ([]int64, error) { @@ -490,13 +584,8 @@ func (scope *EvalScope) setValue(dstv, srcv *Variable, srcExpr string) error { } if srcv.Kind == reflect.String { - if err := allocString(scope, srcv); err != nil { - return err - } - if cm, ok := dstv.mem.(*compositeMemory); ok { - // allocString can change the current thread, recover it so that the - // registers are set on the correct thread. - cm.regs.ChangeFunc = scope.callCtx.p.CurrentThread().SetReg + if srcv.Base == 0 && srcv.Len > 0 && srcv.Flags&VariableConstant != 0 { + return errFuncCallNotAllowedStrAlloc } return dstv.writeString(uint64(srcv.Len), uint64(srcv.Base)) } @@ -522,37 +611,22 @@ func (scope *EvalScope) setValue(dstv, srcv *Variable, srcExpr string) error { // SetVariable sets the value of the named variable func (scope *EvalScope) SetVariable(name, value string) error { - t, err := parser.ParseExpr(name) + lhe, err := parser.ParseExpr(name) if err != nil { return err } - - xv, err := scope.evalAST(t) + rhe, err := parser.ParseExpr(value) if err != nil { return err } - if xv.Addr == 0 { - //lint:ignore ST1005 backwards compatibility - return fmt.Errorf("Can not assign to %q", name) - } - - if xv.Unreadable != nil { - //lint:ignore ST1005 backwards compatibility - return fmt.Errorf("Expression %q is unreadable: %v", name, xv.Unreadable) - } - - t, err = parser.ParseExpr(value) + ops, err := evalop.CompileSet(scopeToEvalLookup{scope}, lhe, rhe) if err != nil { return err } - yv, err := scope.evalAST(t) - if err != nil { - return err - } - - return scope.setValue(xv, yv, value) + _, err = scope.eval(ops) + return err } // LocalVariables returns all local variables from the current function scope. @@ -701,91 +775,357 @@ func (scope *EvalScope) image() *Image { return scope.BinInfo.funcToImage(scope.Fn) } -func (scope *EvalScope) evalAST(t ast.Expr) (*Variable, error) { - switch node := t.(type) { - case *ast.CallExpr: - return scope.evalTypeCastOrFuncCall(node) - - case *ast.Ident: - return scope.evalIdent(node) - - case *ast.ParenExpr: - // otherwise just eval recursively - return scope.evalAST(node.X) - - case *ast.SelectorExpr: // . - // try to interpret the selector as a package variable - if maybePkg, ok := node.X.(*ast.Ident); ok { - if maybePkg.Name == "runtime" && node.Sel.Name == "curg" { - if scope.g == nil { - typ, err := scope.BinInfo.findType("runtime.g") - if err != nil { - return nil, fmt.Errorf("could not find runtime.g: %v", err) - } - gvar := newVariable("curg", fakeAddressUnresolv, typ, scope.BinInfo, scope.Mem) - gvar.loaded = true - gvar.Flags = VariableFakeAddress - gvar.Children = append(gvar.Children, *newConstant(constant.MakeInt64(0), scope.Mem)) - gvar.Children[0].Name = "goid" - return gvar, nil - } - return scope.g.variable.clone(), nil - } else if maybePkg.Name == "runtime" && node.Sel.Name == "frameoff" { - return newConstant(constant.MakeInt64(scope.frameOffset), scope.Mem), nil - } else if maybePkg.Name == "runtime" && node.Sel.Name == "threadid" { - return newConstant(constant.MakeInt64(int64(scope.threadID)), scope.Mem), nil - } else if v, err := scope.findGlobal(maybePkg.Name, node.Sel.Name); err == nil { - return v, nil +// evalStack stores the stack machine used to evaluate a program made of +// evalop.Ops. +// When an opcode sets callInjectionContinue execution of the program will be suspended +// and the call injection protocol will be executed instead. +type evalStack struct { + stack []*Variable // current stack of Variable values + fncalls []*functionCallState // stack of call injections currently being executed + opidx int // program counter for the stack program + callInjectionContinue bool // when set program execution suspends and the call injection protocol is executed instead + err error +} + +func (s *evalStack) push(v *Variable) { + s.stack = append(s.stack, v) +} + +func (s *evalStack) pop() *Variable { + v := s.stack[len(s.stack)-1] + s.stack = s.stack[:len(s.stack)-1] + return v +} + +func (s *evalStack) peek() *Variable { + return s.stack[len(s.stack)-1] +} + +func (s *evalStack) fncallPush(fncall *functionCallState) { + s.fncalls = append(s.fncalls, fncall) +} + +func (s *evalStack) fncallPop() *functionCallState { + fncall := s.fncalls[len(s.fncalls)-1] + s.fncalls = s.fncalls[:len(s.fncalls)-1] + return fncall +} + +func (s *evalStack) fncallPeek() *functionCallState { + return s.fncalls[len(s.fncalls)-1] +} + +func (s *evalStack) pushErr(v *Variable, err error) { + s.err = err + s.stack = append(s.stack, v) +} + +func (scope *EvalScope) eval(ops []evalop.Op) (*Variable, error) { + if logflags.FnCall() { + fncallLog("eval program:\n%s", evalop.Listing(nil, ops)) + } + stack := &evalStack{} + + var spoff, bpoff, fboff int64 + + if scope.g != nil { + spoff = int64(scope.Regs.Uint64Val(scope.Regs.SPRegNum)) - int64(scope.g.stack.hi) + bpoff = int64(scope.Regs.Uint64Val(scope.Regs.BPRegNum)) - int64(scope.g.stack.hi) + fboff = scope.Regs.FrameBase - int64(scope.g.stack.hi) + } + var curthread Thread + if scope.g != nil && scope.g.Thread != nil { + curthread = scope.g.Thread + } + + // funcCallSteps executes the call injection protocol until more input from + // the stack program is needed (i.e. until the call injection either needs + // to copy function arguments, terminates or fails. + // Scope and curthread are updated every time the target program stops). + funcCallSteps := func() { + for stack.callInjectionContinue { + scope.callCtx.injectionThread = nil + g := scope.callCtx.doContinue() + // Go 1.15 will move call injection execution to a different goroutine, + // but we want to keep evaluation on the original goroutine. + if g.ID == scope.g.ID { + scope.g = g + } else { + // We are in Go 1.15 and we switched to a new goroutine, the original + // goroutine is now parked and therefore does not have a thread + // associated. + scope.g.Thread = nil + scope.g.Status = Gwaiting + scope.callCtx.injectionThread = g.Thread + } + + // adjust the value of registers inside scope + pcreg, bpreg, spreg := scope.Regs.Reg(scope.Regs.PCRegNum), scope.Regs.Reg(scope.Regs.BPRegNum), scope.Regs.Reg(scope.Regs.SPRegNum) + scope.Regs.ClearRegisters() + scope.Regs.AddReg(scope.Regs.PCRegNum, pcreg) + scope.Regs.AddReg(scope.Regs.BPRegNum, bpreg) + scope.Regs.AddReg(scope.Regs.SPRegNum, spreg) + scope.Regs.Reg(scope.Regs.SPRegNum).Uint64Val = uint64(spoff + int64(scope.g.stack.hi)) + scope.Regs.Reg(scope.Regs.BPRegNum).Uint64Val = uint64(bpoff + int64(scope.g.stack.hi)) + scope.Regs.FrameBase = fboff + int64(scope.g.stack.hi) + scope.Regs.CFA = scope.frameOffset + int64(scope.g.stack.hi) + curthread = g.Thread + + stack.callInjectionContinue = false + finished := funcCallStep(scope, stack, g.Thread) + if finished { + funcCallFinish(scope, stack) } } - // try to accept "package/path".varname syntax for package variables - if maybePkg, ok := node.X.(*ast.BasicLit); ok && maybePkg.Kind == token.STRING { - pkgpath, err := strconv.Unquote(maybePkg.Value) - if err == nil { - if v, err := scope.findGlobal(pkgpath, node.Sel.Name); err == nil { - return v, nil - } + } + + for stack.opidx < len(ops) && stack.err == nil { + stack.callInjectionContinue = false + scope.executeOp(stack, ops, curthread) + // If the instruction we just executed requests the call injection + // protocol by setting callInjectionContinue we switch to it. + if stack.callInjectionContinue { + funcCallSteps() + } + } + + // If there is an error we must undo all currently executing call + // injections before returning. + + if stack.err == nil && len(stack.fncalls) > 0 { + return nil, fmt.Errorf("internal debugger error: eval program finished without error but %d call injections still active", len(stack.fncalls)) + } + for len(stack.fncalls) > 0 { + fncall := stack.fncallPeek() + if fncall.undoInjection != nil { + // setTargetExecuted is set if evalop.CallInjectionSetTarget has been + // executed but evalop.CallInjectionComplete hasn't, we must undo the callOP + // call in evalop.CallInjectionSetTarget before continuing. + switch scope.BinInfo.Arch.Name { + case "amd64": + regs, _ := curthread.Registers() + setSP(curthread, regs.SP()+uint64(scope.BinInfo.Arch.PtrSize())) + setPC(curthread, fncall.undoInjection.oldpc) + case "arm64", "ppc64": + setLR(curthread, fncall.undoInjection.oldlr) + setPC(curthread, fncall.undoInjection.oldpc) + default: + panic("not implemented") } } - // if it's not a package variable then it must be a struct member access - return scope.evalStructSelector(node) + stack.callInjectionContinue = true + prevlen := len(stack.fncalls) + funcCallSteps() + if len(stack.fncalls) == prevlen { + return nil, fmt.Errorf("internal debugger error: could not undo injected call during error recovery, original error: %v", stack.err) + } + } - case *ast.TypeAssertExpr: // .() - return scope.evalTypeAssert(node) + var r *Variable + switch len(stack.stack) { + case 0: + // ok + case 1: + r = stack.peek() + default: + if stack.err == nil { + stack.err = fmt.Errorf("internal debugger error: wrong stack size at end %d", len(stack.stack)) + } + } + return r, stack.err +} + +// executeOp executes the opcode at ops[stack.opidx] and increments stack.opidx. +func (scope *EvalScope) executeOp(stack *evalStack, ops []evalop.Op, curthread Thread) { + defer func() { + err := recover() + if err != nil { + stack.err = fmt.Errorf("internal debugger error: %v (recovered)\n%s", err, string(debug.Stack())) + } + }() + switch op := ops[stack.opidx].(type) { + case *evalop.PushCurg: + if scope.g != nil { + stack.push(scope.g.variable.clone()) + } else { + typ, err := scope.BinInfo.findType("runtime.g") + if err != nil { + stack.err = fmt.Errorf("could not find runtime.g: %v", err) + return + } + gvar := newVariable("curg", fakeAddressUnresolv, typ, scope.BinInfo, scope.Mem) + gvar.loaded = true + gvar.Flags = VariableFakeAddress + gvar.Children = append(gvar.Children, *newConstant(constant.MakeInt64(0), scope.Mem)) + gvar.Children[0].Name = "goid" + stack.push(gvar) + } - case *ast.IndexExpr: - return scope.evalIndex(node) + case *evalop.PushFrameoff: + stack.push(newConstant(constant.MakeInt64(scope.frameOffset), scope.Mem)) - case *ast.SliceExpr: - if node.Slice3 { - return nil, fmt.Errorf("3-index slice expressions not supported") + case *evalop.PushThreadID: + stack.push(newConstant(constant.MakeInt64(int64(scope.threadID)), scope.Mem)) + + case *evalop.PushConst: + stack.push(newConstant(op.Value, scope.Mem)) + + case *evalop.PushLocal: + vars, err := scope.Locals(0) + if err != nil { + stack.err = err + return + } + found := false + for i := range vars { + if vars[i].Name == op.Name && vars[i].Flags&VariableShadowed == 0 { + stack.push(vars[i]) + found = true + break + } + } + if !found { + stack.err = fmt.Errorf("could not find symbol value for %s", op.Name) } - return scope.evalReslice(node) - case *ast.StarExpr: - // pointer dereferencing * - return scope.evalPointerDeref(node) + case *evalop.PushNil: + stack.push(nilVariable) - case *ast.UnaryExpr: - // The unary operators we support are +, - and & (note that unary * is parsed as ast.StarExpr) - switch node.Op { - case token.AND: - return scope.evalAddrOf(node) + case *evalop.PushRegister: + reg := scope.Regs.Reg(uint64(op.Regnum)) + if reg == nil { + stack.err = fmt.Errorf("could not find symbol value for %s", op.Regname) + return + } + reg.FillBytes() - default: - return scope.evalUnary(node) + var typ godwarf.Type + if len(reg.Bytes) <= 8 { + typ = godwarf.FakeBasicType("uint", 64) + } else { + var err error + typ, err = scope.BinInfo.findType("string") + if err != nil { + stack.err = err + return + } + } + + v := newVariable(op.Regname, 0, typ, scope.BinInfo, scope.Mem) + if v.Kind == reflect.String { + v.Len = int64(len(reg.Bytes) * 2) + v.Base = fakeAddressUnresolv + } + v.Addr = fakeAddressUnresolv + v.Flags = VariableCPURegister + v.reg = reg + stack.push(v) + + case *evalop.PushPackageVar: + pkgName := op.PkgName + replaceName := false + if pkgName == "" { + replaceName = true + pkgName = scope.Fn.PackageName() + } + v, err := scope.findGlobal(pkgName, op.Name) + if err != nil { + stack.err = err + return + } + if replaceName { + v.Name = op.Name } + stack.push(v) + + case *evalop.Select: + scope.evalStructSelector(op, stack) + + case *evalop.TypeAssert: + scope.evalTypeAssert(op, stack) - case *ast.BinaryExpr: - return scope.evalBinary(node) + case *evalop.PointerDeref: + scope.evalPointerDeref(op, stack) + + case *evalop.Unary: + scope.evalUnary(op, stack) + + case *evalop.AddrOf: + scope.evalAddrOf(op, stack) + + case *evalop.TypeCast: + scope.evalTypeCast(op, stack) + + case *evalop.Reslice: + scope.evalReslice(op, stack) + + case *evalop.Index: + scope.evalIndex(op, stack) + + case *evalop.Jump: + scope.evalJump(op, stack) + + case *evalop.Binary: + scope.evalBinary(op, stack) + + case *evalop.BoolToConst: + x := stack.pop() + if x.Kind != reflect.Bool { + stack.err = errors.New("internal debugger error: expected boolean") + return + } + x.loadValue(loadFullValue) + stack.push(newConstant(x.Value, scope.Mem)) - case *ast.BasicLit: - return newConstant(constant.MakeFromLiteral(node.Value, node.Kind, 0), scope.Mem), nil + case *evalop.Pop: + stack.pop() + + case *evalop.BuiltinCall: + vars := make([]*Variable, len(op.Args)) + for i := len(op.Args) - 1; i >= 0; i-- { + vars[i] = stack.pop() + } + stack.pushErr(supportedBuiltins[op.Name](vars, op.Args)) + + case *evalop.CallInjectionStart: + scope.evalCallInjectionStart(op, stack) + + case *evalop.CallInjectionSetTarget: + scope.evalCallInjectionSetTarget(op, stack, curthread) + + case *evalop.CallInjectionCopyArg: + fncall := stack.fncallPeek() + actualArg := stack.pop() + if actualArg.Name == "" { + actualArg.Name = exprToString(op.ArgExpr) + } + stack.err = funcCallCopyOneArg(scope, fncall, actualArg, &fncall.formalArgs[op.ArgNum], curthread) + + case *evalop.CallInjectionComplete: + stack.fncallPeek().undoInjection = nil + stack.callInjectionContinue = true + + case *evalop.CallInjectionAllocString: + stack.callInjectionContinue = scope.allocString(op.Phase, stack, curthread) + + case *evalop.SetValue: + lhv := stack.pop() + rhv := stack.pop() + stack.err = scope.setValue(lhv, rhv, exprToString(op.Rhe)) default: - return nil, fmt.Errorf("expression %T not implemented", t) + stack.err = fmt.Errorf("internal debugger error: unknown eval opcode: %#v", op) + } + stack.opidx++ +} + +func (scope *EvalScope) evalAST(t ast.Expr) (*Variable, error) { + ops, err := evalop.Compile(scopeToEvalLookup{scope}, t) + if err != nil { + return nil, err } + return scope.eval(ops) } func exprToString(t ast.Expr) string { @@ -794,113 +1134,45 @@ func exprToString(t ast.Expr) string { return buf.String() } -func removeParen(n ast.Expr) ast.Expr { - for { - p, ok := n.(*ast.ParenExpr) - if !ok { - break - } - n = p.X +func (scope *EvalScope) evalJump(op *evalop.Jump, stack *evalStack) { + x := stack.peek() + if op.Pop { + stack.pop() } - return n -} -// evalTypeCastOrFuncCall evaluates a type cast or a function call -func (scope *EvalScope) evalTypeCastOrFuncCall(node *ast.CallExpr) (*Variable, error) { - if len(node.Args) != 1 { - // Things that have more or less than one argument are always function calls. - return evalFunctionCall(scope, node) + var v bool + switch op.When { + case evalop.JumpIfTrue: + v = true + case evalop.JumpIfFalse: + v = false } - ambiguous := func() (*Variable, error) { - // Ambiguous, could be a function call or a type cast, if node.Fun can be - // evaluated then try to treat it as a function call, otherwise try the - // type cast. - _, err0 := scope.evalAST(node.Fun) - if err0 == nil { - return evalFunctionCall(scope, node) - } - v, err := scope.evalTypeCast(node) - if err == reader.ErrTypeNotFound { - return nil, fmt.Errorf("could not evaluate function or type %s: %v", exprToString(node.Fun), err0) + if x.Kind != reflect.Bool { + if op.Node != nil { + stack.err = fmt.Errorf("expression %q should be boolean not %s", exprToString(op.Node), x.Kind) + } else { + stack.err = errors.New("internal debugger error: expected boolean") } - return v, err + return } - - fnnode := node.Fun - for { - fnnode = removeParen(fnnode) - n, _ := fnnode.(*ast.StarExpr) - if n == nil { - break - } - fnnode = n.X - } - - switch n := fnnode.(type) { - case *ast.BasicLit: - // It can only be a ("type string")(x) type cast - return scope.evalTypeCast(node) - case *ast.ArrayType, *ast.StructType, *ast.FuncType, *ast.InterfaceType, *ast.MapType, *ast.ChanType: - return scope.evalTypeCast(node) - case *ast.SelectorExpr: - if _, isident := n.X.(*ast.Ident); isident { - return ambiguous() - } - return evalFunctionCall(scope, node) - case *ast.Ident: - if supportedBuiltins[n.Name] { - return evalFunctionCall(scope, node) - } - return ambiguous() - case *ast.IndexExpr: - // Ambiguous, could be a parametric type - switch n.X.(type) { - case *ast.Ident, *ast.SelectorExpr: - // Do the type-cast first since evaluating node.Fun could be expensive. - v, err := scope.evalTypeCast(node) - if err == nil || err != reader.ErrTypeNotFound { - return v, err - } - return evalFunctionCall(scope, node) - default: - return evalFunctionCall(scope, node) - } - case *astIndexListExpr: - return scope.evalTypeCast(node) - default: - // All other expressions must be function calls - return evalFunctionCall(scope, node) + x.loadValue(loadFullValue) + if x.Unreadable != nil { + stack.err = x.Unreadable + return + } + if constant.BoolVal(x.Value) == v { + stack.opidx = op.Target - 1 } } // Eval type cast expressions -func (scope *EvalScope) evalTypeCast(node *ast.CallExpr) (*Variable, error) { - argv, err := scope.evalAST(node.Args[0]) - if err != nil { - return nil, err - } +func (scope *EvalScope) evalTypeCast(op *evalop.TypeCast, stack *evalStack) { + argv := stack.pop() - fnnode := node.Fun + typ := resolveTypedef(op.DwarfType) - // remove all enclosing parenthesis from the type name - fnnode = removeParen(fnnode) - - targetTypeStr := exprToString(removeParen(node.Fun)) - styp, err := scope.BinInfo.findTypeExpr(fnnode) - if err != nil { - switch targetTypeStr { - case "[]byte", "[]uint8": - styp = fakeSliceType(fakeBasicType("uint", 8)) - case "[]int32", "[]rune": - styp = fakeSliceType(fakeBasicType("int", 32)) - default: - return nil, err - } - } - typ := resolveTypedef(styp) - - converr := fmt.Errorf("can not convert %q to %s", exprToString(node.Args[0]), typ.String()) + converr := fmt.Errorf("can not convert %q to %s", exprToString(op.Node.Args[0]), typ.String()) // compatible underlying types if typeCastCompatibleTypes(argv.RealType, typ) { @@ -910,11 +1182,12 @@ func (scope *EvalScope) evalTypeCast(node *ast.CallExpr) (*Variable, error) { argv.Children[0].OnlyAddr = true } argv.RealType = typ - argv.DwarfType = styp - return argv, nil + argv.DwarfType = op.DwarfType + stack.push(argv) + return } - v := newVariable("", 0, styp, scope.BinInfo, scope.Mem) + v := newVariable("", 0, op.DwarfType, scope.BinInfo, scope.Mem) v.loaded = true switch ttyp := typ.(type) { @@ -925,12 +1198,14 @@ func (scope *EvalScope) evalTypeCast(node *ast.CallExpr) (*Variable, error) { case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: // ok default: - return nil, converr + stack.err = converr + return } argv.loadValue(loadSingleValue) if argv.Unreadable != nil { - return nil, argv.Unreadable + stack.err = argv.Unreadable + return } n, _ := constant.Int64Val(argv.Value) @@ -944,53 +1219,64 @@ func (scope *EvalScope) evalTypeCast(node *ast.CallExpr) (*Variable, error) { v.Children = []Variable{*(newVariable("", uint64(n), ttyp.Type, scope.BinInfo, mem))} v.Children[0].OnlyAddr = true - return v, nil + stack.push(v) + return case *godwarf.UintType: argv.loadValue(loadSingleValue) if argv.Unreadable != nil { - return nil, argv.Unreadable + stack.err = argv.Unreadable + return } switch argv.Kind { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: n, _ := constant.Int64Val(argv.Value) v.Value = constant.MakeUint64(convertInt(uint64(n), false, ttyp.Size())) - return v, nil + stack.push(v) + return case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: n, _ := constant.Uint64Val(argv.Value) v.Value = constant.MakeUint64(convertInt(n, false, ttyp.Size())) - return v, nil + stack.push(v) + return case reflect.Float32, reflect.Float64: x, _ := constant.Float64Val(argv.Value) v.Value = constant.MakeUint64(uint64(x)) - return v, nil + stack.push(v) + return case reflect.Ptr: v.Value = constant.MakeUint64(uint64(argv.Children[0].Addr)) - return v, nil + stack.push(v) + return } case *godwarf.IntType: argv.loadValue(loadSingleValue) if argv.Unreadable != nil { - return nil, argv.Unreadable + stack.err = argv.Unreadable + return } switch argv.Kind { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: n, _ := constant.Int64Val(argv.Value) v.Value = constant.MakeInt64(int64(convertInt(uint64(n), true, ttyp.Size()))) - return v, nil + stack.push(v) + return case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: n, _ := constant.Uint64Val(argv.Value) v.Value = constant.MakeInt64(int64(convertInt(n, true, ttyp.Size()))) - return v, nil + stack.push(v) + return case reflect.Float32, reflect.Float64: x, _ := constant.Float64Val(argv.Value) v.Value = constant.MakeInt64(int64(x)) - return v, nil + stack.push(v) + return } case *godwarf.FloatType: argv.loadValue(loadSingleValue) if argv.Unreadable != nil { - return nil, argv.Unreadable + stack.err = argv.Unreadable + return } switch argv.Kind { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: @@ -999,12 +1285,14 @@ func (scope *EvalScope) evalTypeCast(node *ast.CallExpr) (*Variable, error) { fallthrough case reflect.Float32, reflect.Float64: v.Value = argv.Value - return v, nil + stack.push(v) + return } case *godwarf.ComplexType: argv.loadValue(loadSingleValue) if argv.Unreadable != nil { - return nil, argv.Unreadable + stack.err = argv.Unreadable + return } switch argv.Kind { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: @@ -1013,7 +1301,8 @@ func (scope *EvalScope) evalTypeCast(node *ast.CallExpr) (*Variable, error) { fallthrough case reflect.Float32, reflect.Float64: v.Value = argv.Value - return v, nil + stack.push(v) + return } } @@ -1023,7 +1312,8 @@ func (scope *EvalScope) evalTypeCast(node *ast.CallExpr) (*Variable, error) { } argv.loadValue(cfg) if argv.Unreadable != nil { - return nil, argv.Unreadable + stack.err = argv.Unreadable + return } switch ttyp := typ.(type) { @@ -1031,7 +1321,8 @@ func (scope *EvalScope) evalTypeCast(node *ast.CallExpr) (*Variable, error) { switch ttyp.ElemType.Common().ReflectKind { case reflect.Uint8: if argv.Kind != reflect.String { - return nil, converr + stack.err = converr + return } for i, ch := range []byte(constant.StringVal(argv.Value)) { e := newVariable("", argv.Addr+uint64(i), typ.(*godwarf.SliceType).ElemType, scope.BinInfo, argv.mem) @@ -1041,11 +1332,13 @@ func (scope *EvalScope) evalTypeCast(node *ast.CallExpr) (*Variable, error) { } v.Len = int64(len(v.Children)) v.Cap = v.Len - return v, nil + stack.push(v) + return case reflect.Int32: if argv.Kind != reflect.String { - return nil, converr + stack.err = converr + return } for i, ch := range constant.StringVal(argv.Value) { e := newVariable("", argv.Addr+uint64(i), typ.(*godwarf.SliceType).ElemType, scope.BinInfo, argv.mem) @@ -1055,7 +1348,8 @@ func (scope *EvalScope) evalTypeCast(node *ast.CallExpr) (*Variable, error) { } v.Len = int64(len(v.Children)) v.Cap = v.Len - return v, nil + stack.push(v) + return } case *godwarf.StringType: @@ -1064,13 +1358,15 @@ func (scope *EvalScope) evalTypeCast(node *ast.CallExpr) (*Variable, error) { s := constant.StringVal(argv.Value) v.Value = constant.MakeString(s) v.Len = int64(len(s)) - return v, nil + stack.push(v) + return case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint, reflect.Uintptr: b, _ := constant.Int64Val(argv.Value) s := string(rune(b)) v.Value = constant.MakeString(s) v.Len = int64(len(s)) - return v, nil + stack.push(v) + return case reflect.Slice, reflect.Array: var elem godwarf.Type if argv.Kind == reflect.Slice { @@ -1081,7 +1377,8 @@ func (scope *EvalScope) evalTypeCast(node *ast.CallExpr) (*Variable, error) { switch elemType := elem.(type) { case *godwarf.UintType: if elemType.Name != "uint8" && elemType.Name != "byte" { - return nil, converr + stack.err = converr + return } bytes := make([]byte, len(argv.Children)) for i := range argv.Children { @@ -1092,7 +1389,8 @@ func (scope *EvalScope) evalTypeCast(node *ast.CallExpr) (*Variable, error) { case *godwarf.IntType: if elemType.Name != "int32" && elemType.Name != "rune" { - return nil, converr + stack.err = converr + return } runes := make([]rune, len(argv.Children)) for i := range argv.Children { @@ -1102,14 +1400,16 @@ func (scope *EvalScope) evalTypeCast(node *ast.CallExpr) (*Variable, error) { v.Value = constant.MakeString(string(runes)) default: - return nil, converr + stack.err = converr + return } v.Len = int64(len(constant.StringVal(v.Value))) - return v, nil + stack.push(v) + return } } - return nil, converr + stack.err = converr } // typeCastCompatibleTypes returns true if typ1 and typ2 are compatible for @@ -1192,42 +1492,12 @@ func convertInt(n uint64, signed bool, size int64) uint64 { return r } -var supportedBuiltins = map[string]bool{"cap": true, "len": true, "complex": true, "imag": true, "real": true} - -func (scope *EvalScope) evalBuiltinCall(node *ast.CallExpr) (*Variable, error) { - fnnode, ok := node.Fun.(*ast.Ident) - if !ok { - return nil, nil - } - - callBuiltinWithArgs := func(builtin func([]*Variable, []ast.Expr) (*Variable, error)) (*Variable, error) { - args := make([]*Variable, len(node.Args)) - - for i := range node.Args { - v, err := scope.evalAST(node.Args[i]) - if err != nil { - return nil, err - } - args[i] = v - } - - return builtin(args, node.Args) - } - - switch fnnode.Name { - case "cap": - return callBuiltinWithArgs(capBuiltin) - case "len": - return callBuiltinWithArgs(lenBuiltin) - case "complex": - return callBuiltinWithArgs(complexBuiltin) - case "imag": - return callBuiltinWithArgs(imagBuiltin) - case "real": - return callBuiltinWithArgs(realBuiltin) - } - - return nil, nil +var supportedBuiltins = map[string]func([]*Variable, []ast.Expr) (*Variable, error){ + "cap": capBuiltin, + "len": lenBuiltin, + "complex": complexBuiltin, + "imag": imagBuiltin, + "real": realBuiltin, } func capBuiltin(args []*Variable, nodeargs []ast.Expr) (*Variable, error) { @@ -1347,7 +1617,7 @@ func complexBuiltin(args []*Variable, nodeargs []ast.Expr) (*Variable, error) { sz = 128 } - typ := fakeBasicType("complex", int(sz)) + typ := godwarf.FakeBasicType("complex", int(sz)) r := realev.newVariable("", 0, typ, nil) r.Value = constant.BinaryOp(realev.Value, token.ADD, constant.MakeImag(imagev.Value)) @@ -1392,164 +1662,96 @@ func realBuiltin(args []*Variable, nodeargs []ast.Expr) (*Variable, error) { return newConstant(constant.Real(arg.Value), arg.mem), nil } -// Evaluates identifier expressions -func (scope *EvalScope) evalIdent(node *ast.Ident) (*Variable, error) { - switch node.Name { - case "true", "false": - return newConstant(constant.MakeBool(node.Name == "true"), scope.Mem), nil - case "nil": - return nilVariable, nil - } - - vars, err := scope.Locals(0) - if err != nil { - return nil, err - } - for i := range vars { - if vars[i].Name == node.Name && vars[i].Flags&VariableShadowed == 0 { - return vars[i], nil - } - } - - // if it's not a local variable then it could be a package variable w/o explicit package name - if scope.Fn != nil { - if v, err := scope.findGlobal(scope.Fn.PackageName(), node.Name); err == nil { - v.Name = node.Name - return v, nil - } - } - - // not a local variable, nor a global variable, try a CPU register - if s := validRegisterName(node.Name); s != "" { - if regnum, ok := scope.BinInfo.Arch.RegisterNameToDwarf(s); ok { - if reg := scope.Regs.Reg(uint64(regnum)); reg != nil { - reg.FillBytes() - - var typ godwarf.Type - if len(reg.Bytes) <= 8 { - typ = fakeBasicType("uint", 64) - } else { - typ, err = scope.BinInfo.findType("string") - if err != nil { - return nil, err - } - } - - v := newVariable(node.Name, 0, typ, scope.BinInfo, scope.Mem) - if v.Kind == reflect.String { - v.Len = int64(len(reg.Bytes) * 2) - v.Base = fakeAddressUnresolv - } - v.Addr = fakeAddressUnresolv - v.Flags = VariableCPURegister - v.reg = reg - return v, nil - } - } - } - - return nil, fmt.Errorf("could not find symbol value for %s", node.Name) -} - // Evaluates expressions . where subexpr is not a package name -func (scope *EvalScope) evalStructSelector(node *ast.SelectorExpr) (*Variable, error) { - xv, err := scope.evalAST(node.X) - if err != nil { - return nil, err - } - +func (scope *EvalScope) evalStructSelector(op *evalop.Select, stack *evalStack) { + xv := stack.pop() // Prevent abuse, attempting to call "nil.member" directly. if xv.Addr == 0 && xv.Name == "nil" { - return nil, fmt.Errorf("%s (type %s) is not a struct", xv.Name, xv.TypeString()) + stack.err = fmt.Errorf("%s (type %s) is not a struct", xv.Name, xv.TypeString()) + return } // Prevent abuse, attempting to call "\"fake\".member" directly. if xv.Addr == 0 && xv.Name == "" && xv.DwarfType == nil && xv.RealType == nil { - return nil, fmt.Errorf("%s (type %s) is not a struct", xv.Value, xv.TypeString()) + stack.err = fmt.Errorf("%s (type %s) is not a struct", xv.Value, xv.TypeString()) + return } // Special type conversions for CPU register variables (REGNAME.int8, etc) if xv.Flags&VariableCPURegister != 0 && !xv.loaded { - return xv.registerVariableTypeConv(node.Sel.Name) + stack.pushErr(xv.registerVariableTypeConv(op.Name)) + return } - rv, err := xv.findMethod(node.Sel.Name) + rv, err := xv.findMethod(op.Name) if err != nil { - return nil, err + stack.err = err + return } if rv != nil { - return rv, nil + stack.push(rv) + return } - return xv.structMember(node.Sel.Name) + stack.pushErr(xv.structMember(op.Name)) } // Evaluates expressions .() -func (scope *EvalScope) evalTypeAssert(node *ast.TypeAssertExpr) (*Variable, error) { - xv, err := scope.evalAST(node.X) - if err != nil { - return nil, err - } +func (scope *EvalScope) evalTypeAssert(op *evalop.TypeAssert, stack *evalStack) { + xv := stack.pop() if xv.Kind != reflect.Interface { - return nil, fmt.Errorf("expression %q not an interface", exprToString(node.X)) + stack.err = fmt.Errorf("expression %q not an interface", exprToString(op.Node.X)) + return } xv.loadInterface(0, false, loadFullValue) if xv.Unreadable != nil { - return nil, xv.Unreadable + stack.err = xv.Unreadable + return } if xv.Children[0].Unreadable != nil { - return nil, xv.Children[0].Unreadable + stack.err = xv.Children[0].Unreadable + return } if xv.Children[0].Addr == 0 { - return nil, fmt.Errorf("interface conversion: %s is nil, not %s", xv.DwarfType.String(), exprToString(node.Type)) + stack.err = fmt.Errorf("interface conversion: %s is nil, not %s", xv.DwarfType.String(), exprToString(op.Node.Type)) + return } - // Accept .(data) as a type assertion that always succeeds, so that users - // can access the data field of an interface without actually having to - // type the concrete type. - if idtyp, isident := node.Type.(*ast.Ident); !isident || idtyp.Name != "data" { - typ, err := scope.BinInfo.findTypeExpr(node.Type) - if err != nil { - return nil, err - } - if xv.Children[0].DwarfType.Common().Name != typ.Common().Name { - return nil, fmt.Errorf("interface conversion: %s is %s, not %s", xv.DwarfType.Common().Name, xv.Children[0].TypeString(), typ.Common().Name) - } + typ := op.DwarfType + if typ != nil && xv.Children[0].DwarfType.Common().Name != typ.Common().Name { + stack.err = fmt.Errorf("interface conversion: %s is %s, not %s", xv.DwarfType.Common().Name, xv.Children[0].TypeString(), typ.Common().Name) + return } // loadInterface will set OnlyAddr for the data member since here we are // passing false to loadData, however returning the variable with OnlyAddr // set here would be wrong since, once the expression evaluation // terminates, the value of this variable will be loaded. xv.Children[0].OnlyAddr = false - return &xv.Children[0], nil + stack.push(&xv.Children[0]) } // Evaluates expressions [] (subscript access to arrays, slices and maps) -func (scope *EvalScope) evalIndex(node *ast.IndexExpr) (*Variable, error) { - xev, err := scope.evalAST(node.X) - if err != nil { - return nil, err - } +func (scope *EvalScope) evalIndex(op *evalop.Index, stack *evalStack) { + idxev := stack.pop() + xev := stack.pop() if xev.Unreadable != nil { - return nil, xev.Unreadable + stack.err = xev.Unreadable + return } if xev.Flags&VariableCPtr == 0 { xev = xev.maybeDereference() } - idxev, err := scope.evalAST(node.Index) - if err != nil { - return nil, err - } - - cantindex := fmt.Errorf("expression %q (%s) does not support indexing", exprToString(node.X), xev.TypeString()) + cantindex := fmt.Errorf("expression %q (%s) does not support indexing", exprToString(op.Node.X), xev.TypeString()) switch xev.Kind { case reflect.Ptr: if xev == nilVariable { - return nil, cantindex + stack.err = cantindex + return } if xev.Flags&VariableCPtr == 0 { _, isarrptr := xev.RealType.(*godwarf.PtrType).Type.(*godwarf.ArrayType) if !isarrptr { - return nil, cantindex + stack.err = cantindex + return } xev = xev.maybeDereference() } @@ -1557,133 +1759,134 @@ func (scope *EvalScope) evalIndex(node *ast.IndexExpr) (*Variable, error) { case reflect.Slice, reflect.Array, reflect.String: if xev.Base == 0 { - return nil, fmt.Errorf("can not index %q", exprToString(node.X)) + stack.err = fmt.Errorf("can not index %q", exprToString(op.Node.X)) + return } n, err := idxev.asInt() if err != nil { - return nil, err + stack.err = err + return } - return xev.sliceAccess(int(n)) + stack.pushErr(xev.sliceAccess(int(n))) + return case reflect.Map: idxev.loadValue(loadFullValue) if idxev.Unreadable != nil { - return nil, idxev.Unreadable + stack.err = idxev.Unreadable + return } - return xev.mapAccess(idxev) + stack.pushErr(xev.mapAccess(idxev)) + return default: - return nil, cantindex + stack.err = cantindex + return } } // Evaluates expressions [:] // HACK: slicing a map expression with [0:0] will return the whole map -func (scope *EvalScope) evalReslice(node *ast.SliceExpr) (*Variable, error) { - xev, err := scope.evalAST(node.X) +func (scope *EvalScope) evalReslice(op *evalop.Reslice, stack *evalStack) { + low, err := stack.pop().asInt() if err != nil { - return nil, err - } - if xev.Unreadable != nil { - return nil, xev.Unreadable + stack.err = err + return } - - var low, high int64 - - if node.Low != nil { - lowv, err := scope.evalAST(node.Low) - if err != nil { - return nil, err - } - low, err = lowv.asInt() + var high int64 + if op.HasHigh { + high, err = stack.pop().asInt() if err != nil { - return nil, fmt.Errorf("can not convert %q to int: %v", exprToString(node.Low), err) + stack.err = err + return } } - - if node.High == nil { + xev := stack.pop() + if xev.Unreadable != nil { + stack.err = xev.Unreadable + return + } + if !op.HasHigh { high = xev.Len - } else { - highv, err := scope.evalAST(node.High) - if err != nil { - return nil, err - } - high, err = highv.asInt() - if err != nil { - return nil, fmt.Errorf("can not convert %q to int: %v", exprToString(node.High), err) - } } switch xev.Kind { case reflect.Slice, reflect.Array, reflect.String: if xev.Base == 0 { - return nil, fmt.Errorf("can not slice %q", exprToString(node.X)) + stack.err = fmt.Errorf("can not slice %q", exprToString(op.Node.X)) + return } - return xev.reslice(low, high) + stack.pushErr(xev.reslice(low, high)) + return case reflect.Map: - if node.High != nil { - return nil, fmt.Errorf("second slice argument must be empty for maps") + if op.Node.High != nil { + stack.err = fmt.Errorf("second slice argument must be empty for maps") + return } xev.mapSkip += int(low) xev.mapIterator() // reads map length if int64(xev.mapSkip) >= xev.Len { - return nil, fmt.Errorf("map index out of bounds") + stack.err = fmt.Errorf("map index out of bounds") + return } - return xev, nil + stack.push(xev) + return case reflect.Ptr: if xev.Flags&VariableCPtr != 0 { - return xev.reslice(low, high) + stack.pushErr(xev.reslice(low, high)) + return } fallthrough default: - return nil, fmt.Errorf("can not slice %q (type %s)", exprToString(node.X), xev.TypeString()) + stack.err = fmt.Errorf("can not slice %q (type %s)", exprToString(op.Node.X), xev.TypeString()) + return } } // Evaluates a pointer dereference expression: * -func (scope *EvalScope) evalPointerDeref(node *ast.StarExpr) (*Variable, error) { - xev, err := scope.evalAST(node.X) - if err != nil { - return nil, err - } +func (scope *EvalScope) evalPointerDeref(op *evalop.PointerDeref, stack *evalStack) { + xev := stack.pop() if xev.Kind != reflect.Ptr { - return nil, fmt.Errorf("expression %q (%s) can not be dereferenced", exprToString(node.X), xev.TypeString()) + stack.err = fmt.Errorf("expression %q (%s) can not be dereferenced", exprToString(op.Node.X), xev.TypeString()) + return } if xev == nilVariable { - return nil, fmt.Errorf("nil can not be dereferenced") + stack.err = fmt.Errorf("nil can not be dereferenced") + return } if len(xev.Children) == 1 { // this branch is here to support pointers constructed with typecasts from ints xev.Children[0].OnlyAddr = false - return &(xev.Children[0]), nil + stack.push(&(xev.Children[0])) + return } xev.loadPtr() if xev.Unreadable != nil { val, ok := constant.Uint64Val(xev.Value) if ok && val == 0 { - return nil, fmt.Errorf("couldn't read pointer: %w", xev.Unreadable) + stack.err = fmt.Errorf("couldn't read pointer: %w", xev.Unreadable) + return } } rv := &xev.Children[0] if rv.Addr == 0 { - return nil, fmt.Errorf("nil pointer dereference") + stack.err = fmt.Errorf("nil pointer dereference") + return } - return rv, nil + stack.push(rv) } // Evaluates expressions & -func (scope *EvalScope) evalAddrOf(node *ast.UnaryExpr) (*Variable, error) { - xev, err := scope.evalAST(node.X) - if err != nil { - return nil, err - } +func (scope *EvalScope) evalAddrOf(op *evalop.AddrOf, stack *evalStack) { + xev := stack.pop() if xev.Addr == 0 || xev.DwarfType == nil { - return nil, fmt.Errorf("can not take address of %q", exprToString(node.X)) + stack.err = fmt.Errorf("can not take address of %q", exprToString(op.Node.X)) + return } - return xev.pointerToVariable(), nil + stack.push(xev.pointerToVariable()) } func (v *Variable) pointerToVariable() *Variable { @@ -1734,32 +1937,34 @@ func constantCompare(op token.Token, x, y constant.Value) (r bool, err error) { } // Evaluates expressions: - and + -func (scope *EvalScope) evalUnary(node *ast.UnaryExpr) (*Variable, error) { - xv, err := scope.evalAST(node.X) - if err != nil { - return nil, err - } +func (scope *EvalScope) evalUnary(op *evalop.Unary, stack *evalStack) { + xv := stack.pop() xv.loadValue(loadSingleValue) if xv.Unreadable != nil { - return nil, xv.Unreadable + stack.err = xv.Unreadable + return } if xv.FloatSpecial != 0 { - return nil, errOperationOnSpecialFloat + stack.err = errOperationOnSpecialFloat + return } if xv.Value == nil { - return nil, fmt.Errorf("operator %s can not be applied to %q", node.Op.String(), exprToString(node.X)) + stack.err = fmt.Errorf("operator %s can not be applied to %q", op.Node.Op.String(), exprToString(op.Node.X)) + return } - rc, err := constantUnaryOp(node.Op, xv.Value) + rc, err := constantUnaryOp(op.Node.Op, xv.Value) if err != nil { - return nil, err + stack.err = err + return } if xv.DwarfType != nil { r := xv.newVariable("", 0, xv.DwarfType, scope.Mem) r.Value = rc - return r, nil + stack.push(r) + return } - return newConstant(rc, xv.mem), nil + stack.push(newConstant(rc, xv.mem)) } func negotiateType(op token.Token, xv, yv *Variable) (godwarf.Type, error) { @@ -1826,53 +2031,36 @@ func negotiateTypeNil(op token.Token, v *Variable) error { } } -func (scope *EvalScope) evalBinary(node *ast.BinaryExpr) (*Variable, error) { - switch node.Op { - case token.INC, token.DEC, token.ARROW: - return nil, fmt.Errorf("operator %s not supported", node.Op.String()) - } +func (scope *EvalScope) evalBinary(binop *evalop.Binary, stack *evalStack) { + node := binop.Node + + yv := stack.pop() + xv := stack.pop() - xv, err := scope.evalAST(node.X) - if err != nil { - return nil, err - } if xv.Kind != reflect.String { // delay loading strings until we use them xv.loadValue(loadFullValue) } if xv.Unreadable != nil { - return nil, xv.Unreadable - } - - // short circuits logical operators - switch node.Op { - case token.LAND: - if !constant.BoolVal(xv.Value) { - return newConstant(xv.Value, xv.mem), nil - } - case token.LOR: - if constant.BoolVal(xv.Value) { - return newConstant(xv.Value, xv.mem), nil - } - } - - yv, err := scope.evalAST(node.Y) - if err != nil { - return nil, err + stack.err = xv.Unreadable + return } if yv.Kind != reflect.String { // delay loading strings until we use them yv.loadValue(loadFullValue) } if yv.Unreadable != nil { - return nil, yv.Unreadable + stack.err = yv.Unreadable + return } if xv.FloatSpecial != 0 || yv.FloatSpecial != 0 { - return nil, errOperationOnSpecialFloat + stack.err = errOperationOnSpecialFloat + return } typ, err := negotiateType(node.Op, xv, yv) if err != nil { - return nil, err + stack.err = err + return } op := node.Op @@ -1889,9 +2077,10 @@ func (scope *EvalScope) evalBinary(node *ast.BinaryExpr) (*Variable, error) { case token.EQL, token.LSS, token.GTR, token.NEQ, token.LEQ, token.GEQ: v, err := compareOp(op, xv, yv) if err != nil { - return nil, err + stack.err = err + return } - return newConstant(constant.MakeBool(v), xv.mem), nil + stack.push(newConstant(constant.MakeBool(v), xv.mem)) default: if xv.Kind == reflect.String { @@ -1901,20 +2090,24 @@ func (scope *EvalScope) evalBinary(node *ast.BinaryExpr) (*Variable, error) { yv.loadValue(loadFullValueLongerStrings) } if xv.Value == nil { - return nil, fmt.Errorf("operator %s can not be applied to %q", node.Op.String(), exprToString(node.X)) + stack.err = fmt.Errorf("operator %s can not be applied to %q", node.Op.String(), exprToString(node.X)) + return } if yv.Value == nil { - return nil, fmt.Errorf("operator %s can not be applied to %q", node.Op.String(), exprToString(node.Y)) + stack.err = fmt.Errorf("operator %s can not be applied to %q", node.Op.String(), exprToString(node.Y)) + return } rc, err := constantBinaryOp(op, xv.Value, yv.Value) if err != nil { - return nil, err + stack.err = err + return } if typ == nil { - return newConstant(rc, xv.mem), nil + stack.push(newConstant(rc, xv.mem)) + return } r := xv.newVariable("", 0, typ, scope.Mem) @@ -1929,7 +2122,7 @@ func (scope *EvalScope) evalBinary(node *ast.BinaryExpr) (*Variable, error) { n, _ := constant.Uint64Val(r.Value) r.Value = constant.MakeUint64(convertInt(n, false, typ.Size())) } - return r, nil + stack.push(r) } } @@ -2309,7 +2502,7 @@ func (v *Variable) reslice(low int64, high int64) (*Variable, error) { typ := v.DwarfType if _, isarr := v.DwarfType.(*godwarf.ArrayType); isarr || cptrNeedsFakeSlice { - typ = fakeSliceType(v.fieldType) + typ = godwarf.FakeSliceType(v.fieldType) } mem := v.mem @@ -2429,51 +2622,6 @@ func functionToVariable(fn *Function, bi *BinaryInfo, mem MemoryReadWriter) (*Va return v, nil } -func fakeBasicType(name string, bitSize int) godwarf.Type { - byteSize := bitSize / 8 - szr := popcnt(uint64(byteSize^(byteSize-1))) - 1 // position of rightmost 1 bit, minus 1 - - basic := func(kind reflect.Kind) godwarf.BasicType { - return godwarf.BasicType{ - CommonType: godwarf.CommonType{ - ByteSize: int64(byteSize), - Name: fmt.Sprintf("%s%d", name, bitSize), - ReflectKind: kind, - }, - BitSize: int64(bitSize), - BitOffset: 0, - } - } - - switch name { - case "int": - return &godwarf.IntType{BasicType: basic(reflect.Int8 + reflect.Kind(szr))} - case "uint": - return &godwarf.UintType{BasicType: basic(reflect.Uint8 + reflect.Kind(szr))} - case "float": - return &godwarf.FloatType{BasicType: basic(reflect.Float32 + reflect.Kind(szr-2))} - case "complex": - return &godwarf.ComplexType{BasicType: basic(reflect.Complex64 + reflect.Kind(szr-3))} - default: - panic("unsupported") - } -} - -func fakeSliceType(fieldType godwarf.Type) godwarf.Type { - return &godwarf.SliceType{ - StructType: godwarf.StructType{ - CommonType: godwarf.CommonType{ - ByteSize: 24, - Name: "", - }, - StructName: fmt.Sprintf("[]%s", fieldType.Common().Name), - Kind: "struct", - Field: nil, - }, - ElemType: fieldType, - } -} - func fakeArrayType(n uint64, fieldType godwarf.Type) godwarf.Type { stride := alignAddr(fieldType.Common().ByteSize, fieldType.Align()) return &godwarf.ArrayType{ diff --git a/pkg/proc/eval_go117.go b/pkg/proc/evalop/eval_go117.go similarity index 84% rename from pkg/proc/eval_go117.go rename to pkg/proc/evalop/eval_go117.go index 0c728bd431..4ed86dc40b 100644 --- a/pkg/proc/eval_go117.go +++ b/pkg/proc/evalop/eval_go117.go @@ -1,6 +1,6 @@ //go:build !go1.18 -package proc +package evalop import "go/ast" diff --git a/pkg/proc/eval_go118.go b/pkg/proc/evalop/eval_go118.go similarity index 84% rename from pkg/proc/eval_go118.go rename to pkg/proc/evalop/eval_go118.go index 67376a4365..81716cabd9 100644 --- a/pkg/proc/eval_go118.go +++ b/pkg/proc/evalop/eval_go118.go @@ -1,6 +1,6 @@ //go:build go1.18 -package proc +package evalop import "go/ast" diff --git a/pkg/proc/evalop/evalcompile.go b/pkg/proc/evalop/evalcompile.go new file mode 100644 index 0000000000..5b6519aa5b --- /dev/null +++ b/pkg/proc/evalop/evalcompile.go @@ -0,0 +1,556 @@ +package evalop + +import ( + "bytes" + "errors" + "fmt" + "go/ast" + "go/constant" + "go/printer" + "go/token" + "strconv" + "strings" + + "github.com/go-delve/delve/pkg/dwarf/godwarf" + "github.com/go-delve/delve/pkg/dwarf/reader" +) + +var ( + ErrFuncCallNotAllowed = errors.New("function calls not allowed without using 'call'") +) + +type compileCtx struct { + evalLookup + ops []Op + allowCalls bool + curCall int +} + +type evalLookup interface { + FindTypeExpr(ast.Expr) (godwarf.Type, error) + HasLocal(string) bool + HasGlobal(string, string) bool + HasBuiltin(string) bool + LookupRegisterName(string) (int, bool) +} + +// Compile compiles the expression t into a list of instructions. +func Compile(lookup evalLookup, t ast.Expr) ([]Op, error) { + ctx := &compileCtx{evalLookup: lookup, allowCalls: true} + err := ctx.compileAST(t) + if err != nil { + return nil, err + } + + err = ctx.depthCheck(1) + if err != nil { + return ctx.ops, err + } + return ctx.ops, nil +} + +// CompileSet compiles the expression setting lhe to rhe into a list of +// instructions. +func CompileSet(lookup evalLookup, lhe, rhe ast.Expr) ([]Op, error) { + ctx := &compileCtx{evalLookup: lookup, allowCalls: true} + err := ctx.compileAST(rhe) + if err != nil { + return nil, err + } + + if isStringLiteral(rhe) { + ctx.compileAllocLiteralString() + } + + err = ctx.compileAST(lhe) + if err != nil { + return nil, err + } + + ctx.pushOp(&SetValue{lhe: lhe, Rhe: rhe}) + + err = ctx.depthCheck(0) + if err != nil { + return ctx.ops, err + } + return ctx.ops, nil + +} + +func (ctx *compileCtx) compileAllocLiteralString() { + ctx.pushOp(&CallInjectionAllocString{Phase: 0}) + ctx.pushOp(&CallInjectionAllocString{Phase: 1}) + ctx.pushOp(&CallInjectionAllocString{Phase: 2}) +} + +func (ctx *compileCtx) pushOp(op Op) { + ctx.ops = append(ctx.ops, op) +} + +// depthCheck validates the list of instructions produced by Compile and +// CompileSet by peforming a stack depth check. +// It calculates the depth of the stack at every instruction in ctx.ops and +// checks that they have enough arguments to execute. For instructions that +// can be reached through multiple paths (because of a jump) it checks that +// all paths reach the instruction with the same stack depth. +// Finally it checks that the stack depth after all instructions have +// executed is equal to endDepth. +func (ctx *compileCtx) depthCheck(endDepth int) error { + depth := make([]int, len(ctx.ops)+1) // depth[i] is the depth of the stack before i-th instruction + for i := range depth { + depth[i] = -1 + } + depth[0] = 0 + + var err error + checkAndSet := func(j, d int) { // sets depth[j] to d after checking that we can + if depth[j] < 0 { + depth[j] = d + } + if d != depth[j] { + err = fmt.Errorf("internal debugger error: depth check error at instruction %d: expected depth %d have %d (jump target)\n%s", j, d, depth[j], Listing(depth, ctx.ops)) + } + } + + for i, op := range ctx.ops { + npop, npush := op.depthCheck() + if depth[i] < npop { + return fmt.Errorf("internal debugger error: depth check error at instruction %d: expected at least %d have %d\n%s", i, npop, depth[i], Listing(depth, ctx.ops)) + } + d := depth[i] - npop + npush + checkAndSet(i+1, d) + if jmp, _ := op.(*Jump); jmp != nil { + checkAndSet(jmp.Target, d) + } + if err != nil { + return err + } + } + + if depth[len(ctx.ops)] != endDepth { + return fmt.Errorf("internal debugger error: depth check failed: depth at the end is not %d (got %d)\n%s", depth[len(ctx.ops)], endDepth, Listing(depth, ctx.ops)) + } + return nil +} + +func (ctx *compileCtx) compileAST(t ast.Expr) error { + switch node := t.(type) { + case *ast.CallExpr: + return ctx.compileTypeCastOrFuncCall(node) + + case *ast.Ident: + return ctx.compileIdent(node) + + case *ast.ParenExpr: + // otherwise just eval recursively + return ctx.compileAST(node.X) + + case *ast.SelectorExpr: // . + switch x := node.X.(type) { + case *ast.Ident: + switch { + case x.Name == "runtime" && node.Sel.Name == "curg": + ctx.pushOp(&PushCurg{}) + + case x.Name == "runtime" && node.Sel.Name == "frameoff": + ctx.pushOp(&PushFrameoff{}) + + case x.Name == "runtime" && node.Sel.Name == "threadid": + ctx.pushOp(&PushThreadID{}) + + case ctx.HasLocal(x.Name): + ctx.pushOp(&PushLocal{x.Name}) + ctx.pushOp(&Select{node.Sel.Name}) + + case ctx.HasGlobal(x.Name, node.Sel.Name): + ctx.pushOp(&PushPackageVar{x.Name, node.Sel.Name}) + + default: + 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 { + return err + } + if ctx.HasGlobal(s, node.Sel.Name) { + ctx.pushOp(&PushPackageVar{s, node.Sel.Name}) + return nil + } + return ctx.compileUnary(node.X, &Select{node.Sel.Name}) + + default: + return ctx.compileUnary(node.X, &Select{node.Sel.Name}) + + } + + case *ast.TypeAssertExpr: // .() + return ctx.compileTypeAssert(node) + + case *ast.IndexExpr: + return ctx.compileBinary(node.X, node.Index, nil, &Index{node}) + + case *ast.SliceExpr: + if node.Slice3 { + return fmt.Errorf("3-index slice expressions not supported") + } + return ctx.compileReslice(node) + + case *ast.StarExpr: + // pointer dereferencing * + return ctx.compileUnary(node.X, &PointerDeref{node}) + + case *ast.UnaryExpr: + // The unary operators we support are +, - and & (note that unary * is parsed as ast.StarExpr) + switch node.Op { + case token.AND: + return ctx.compileUnary(node.X, &AddrOf{node}) + default: + return ctx.compileUnary(node.X, &Unary{node}) + } + + case *ast.BinaryExpr: + switch node.Op { + case token.INC, token.DEC, token.ARROW: + return fmt.Errorf("operator %s not supported", node.Op.String()) + } + // short circuits logical operators + var sop *Jump + switch node.Op { + case token.LAND: + sop = &Jump{When: JumpIfFalse, Node: node.X} + case token.LOR: + sop = &Jump{When: JumpIfTrue, Node: node.X} + } + err := ctx.compileBinary(node.X, node.Y, sop, &Binary{node}) + if err != nil { + return err + } + if sop != nil { + sop.Target = len(ctx.ops) + ctx.pushOp(&BoolToConst{}) + } + + case *ast.BasicLit: + ctx.pushOp(&PushConst{constant.MakeFromLiteral(node.Value, node.Kind, 0)}) + + default: + return fmt.Errorf("expression %T not implemented", t) + + } + return nil +} + +func (ctx *compileCtx) compileTypeCastOrFuncCall(node *ast.CallExpr) error { + if len(node.Args) != 1 { + // Things that have more or less than one argument are always function calls. + return ctx.compileFunctionCall(node) + } + + ambiguous := func() error { + // Ambiguous, could be a function call or a type cast, if node.Fun can be + // evaluated then try to treat it as a function call, otherwise try the + // type cast. + ctx2 := &compileCtx{evalLookup: ctx.evalLookup} + err0 := ctx2.compileAST(node.Fun) + if err0 == nil { + return ctx.compileFunctionCall(node) + } + return ctx.compileTypeCast(node, err0) + } + + fnnode := node.Fun + for { + fnnode = removeParen(fnnode) + n, _ := fnnode.(*ast.StarExpr) + if n == nil { + break + } + fnnode = n.X + } + + switch n := fnnode.(type) { + case *ast.BasicLit: + // It can only be a ("type string")(x) type cast + return ctx.compileTypeCast(node, nil) + case *ast.ArrayType, *ast.StructType, *ast.FuncType, *ast.InterfaceType, *ast.MapType, *ast.ChanType: + return ctx.compileTypeCast(node, nil) + case *ast.SelectorExpr: + if _, isident := n.X.(*ast.Ident); isident { + if typ, _ := ctx.FindTypeExpr(n); typ != nil { + return ctx.compileTypeCast(node, nil) + } + return ambiguous() + } + return ctx.compileFunctionCall(node) + case *ast.Ident: + if ctx.HasBuiltin(n.Name) { + return ctx.compileFunctionCall(node) + } + if ctx.HasGlobal("", n.Name) || ctx.HasLocal(n.Name) { + return ctx.compileFunctionCall(node) + } + return ctx.compileTypeCast(node, fmt.Errorf("could not find symbol value for %s", n.Name)) + case *ast.IndexExpr: + // Ambiguous, could be a parametric type + switch n.X.(type) { + case *ast.Ident, *ast.SelectorExpr: + // Do the type-cast first since evaluating node.Fun could be expensive. + err := ctx.compileTypeCast(node, nil) + if err == nil || err != reader.ErrTypeNotFound { + return err + } + return ctx.compileFunctionCall(node) + default: + return ctx.compileFunctionCall(node) + } + case *astIndexListExpr: + return ctx.compileTypeCast(node, nil) + default: + // All other expressions must be function calls + return ctx.compileFunctionCall(node) + } +} + +func (ctx *compileCtx) compileTypeCast(node *ast.CallExpr, ambiguousErr error) error { + err := ctx.compileAST(node.Args[0]) + if err != nil { + return err + } + + fnnode := node.Fun + + // remove all enclosing parenthesis from the type name + fnnode = removeParen(fnnode) + + targetTypeStr := exprToString(removeParen(node.Fun)) + styp, err := ctx.FindTypeExpr(fnnode) + if err != nil { + switch targetTypeStr { + case "[]byte", "[]uint8": + styp = godwarf.FakeSliceType(godwarf.FakeBasicType("uint", 8)) + case "[]int32", "[]rune": + styp = godwarf.FakeSliceType(godwarf.FakeBasicType("int", 32)) + default: + if ambiguousErr != nil && err == reader.ErrTypeNotFound { + return fmt.Errorf("could not evaluate function or type %s: %v", exprToString(node.Fun), ambiguousErr) + } + return err + } + } + + ctx.pushOp(&TypeCast{DwarfType: styp, Node: node}) + return nil +} + +func (ctx *compileCtx) compileBuiltinCall(builtin string, args []ast.Expr) error { + for _, arg := range args { + err := ctx.compileAST(arg) + if err != nil { + return err + } + } + ctx.pushOp(&BuiltinCall{builtin, args}) + return nil +} + +func (ctx *compileCtx) compileIdent(node *ast.Ident) error { + switch { + case ctx.HasLocal(node.Name): + ctx.pushOp(&PushLocal{node.Name}) + case ctx.HasGlobal("", node.Name): + ctx.pushOp(&PushPackageVar{"", node.Name}) + case node.Name == "true" || node.Name == "false": + ctx.pushOp(&PushConst{constant.MakeBool(node.Name == "true")}) + case node.Name == "nil": + ctx.pushOp(&PushNil{}) + default: + found := false + if regnum, ok := ctx.LookupRegisterName(node.Name); ok { + ctx.pushOp(&PushRegister{regnum, node.Name}) + found = true + } + if !found { + return fmt.Errorf("could not find symbol value for %s", node.Name) + } + } + return nil +} + +func (ctx *compileCtx) compileUnary(expr ast.Expr, op Op) error { + err := ctx.compileAST(expr) + if err != nil { + return err + } + ctx.pushOp(op) + return nil +} + +func (ctx *compileCtx) compileTypeAssert(node *ast.TypeAssertExpr) error { + err := ctx.compileAST(node.X) + if err != nil { + return err + } + // Accept .(data) as a type assertion that always succeeds, so that users + // can access the data field of an interface without actually having to + // type the concrete type. + if idtyp, isident := node.Type.(*ast.Ident); !isident || idtyp.Name != "data" { + typ, err := ctx.FindTypeExpr(node.Type) + if err != nil { + return err + } + ctx.pushOp(&TypeAssert{typ, node}) + return nil + } + ctx.pushOp(&TypeAssert{nil, node}) + return nil +} + +func (ctx *compileCtx) compileBinary(a, b ast.Expr, sop *Jump, op Op) error { + err := ctx.compileAST(a) + if err != nil { + return err + } + if sop != nil { + ctx.pushOp(sop) + } + err = ctx.compileAST(b) + if err != nil { + return err + } + ctx.pushOp(op) + return nil +} + +func (ctx *compileCtx) compileReslice(node *ast.SliceExpr) error { + err := ctx.compileAST(node.X) + if err != nil { + return err + } + + hasHigh := false + if node.High != nil { + hasHigh = true + err = ctx.compileAST(node.High) + if err != nil { + return err + } + } + + if node.Low != nil { + err = ctx.compileAST(node.Low) + if err != nil { + return err + } + } else { + ctx.pushOp(&PushConst{constant.MakeInt64(0)}) + } + + ctx.pushOp(&Reslice{Node: node, HasHigh: hasHigh}) + return nil +} + +func (ctx *compileCtx) compileFunctionCall(node *ast.CallExpr) error { + if fnnode, ok := node.Fun.(*ast.Ident); ok { + if ctx.HasBuiltin(fnnode.Name) { + return ctx.compileBuiltinCall(fnnode.Name, node.Args) + } + } + if !ctx.allowCalls { + return ErrFuncCallNotAllowed + } + + id := ctx.curCall + ctx.curCall++ + + oldAllowCalls := ctx.allowCalls + oldOps := ctx.ops + ctx.allowCalls = false + err := ctx.compileAST(node.Fun) + ctx.allowCalls = oldAllowCalls + hasFunc := false + if err != nil { + ctx.ops = oldOps + if err != ErrFuncCallNotAllowed { + return err + } + } else { + hasFunc = true + } + ctx.pushOp(&CallInjectionStart{HasFunc: hasFunc, id: id, Node: node}) + + // CallInjectionStart pushes true on the stack if it needs the function argument re-evaluated + var jmpif *Jump + if hasFunc { + jmpif = &Jump{When: JumpIfFalse, Pop: true} + ctx.pushOp(jmpif) + } + ctx.pushOp(&Pop{}) + err = ctx.compileAST(node.Fun) + if err != nil { + return err + } + if jmpif != nil { + jmpif.Target = len(ctx.ops) + } + + ctx.pushOp(&CallInjectionSetTarget{id: id}) + + for i, arg := range node.Args { + err := ctx.compileAST(arg) + if err != nil { + return fmt.Errorf("error evaluating %q as argument %d in function %s: %v", exprToString(arg), i+1, exprToString(node.Fun), err) + } + if isStringLiteral(arg) { + ctx.compileAllocLiteralString() + } + ctx.pushOp(&CallInjectionCopyArg{id: id, ArgNum: i, ArgExpr: arg}) + } + + ctx.pushOp(&CallInjectionComplete{id: id}) + + return nil +} + +func Listing(depth []int, ops []Op) string { + if depth == nil { + depth = make([]int, len(ops)+1) + } + buf := new(strings.Builder) + for i, op := range ops { + fmt.Fprintf(buf, " %3d (%2d->%2d) %#v\n", i, depth[i], depth[i+1], op) + } + return buf.String() +} + +func isStringLiteral(expr ast.Expr) bool { + switch expr := expr.(type) { + case *ast.BasicLit: + return expr.Kind == token.STRING + case *ast.BinaryExpr: + if expr.Op == token.ADD { + return isStringLiteral(expr.X) && isStringLiteral(expr.Y) + } + case *ast.ParenExpr: + return isStringLiteral(expr.X) + } + return false +} + +func removeParen(n ast.Expr) ast.Expr { + for { + p, ok := n.(*ast.ParenExpr) + if !ok { + break + } + n = p.X + } + return n +} + +func exprToString(t ast.Expr) string { + var buf bytes.Buffer + printer.Fprint(&buf, token.NewFileSet(), t) + return buf.String() +} diff --git a/pkg/proc/evalop/evalop_test.go b/pkg/proc/evalop/evalop_test.go new file mode 100644 index 0000000000..a05b5bb895 --- /dev/null +++ b/pkg/proc/evalop/evalop_test.go @@ -0,0 +1,75 @@ +package evalop + +import ( + "go/ast" + "go/parser" + "go/token" + "testing" +) + +func assertNoError(err error, t testing.TB, s string) { + t.Helper() + if err != nil { + t.Fatalf("failed assertion %s: %s\n", s, err) + } +} + +func TestEvalSwitchExhaustiveness(t *testing.T) { + // Checks that the switch statement in (*EvalScope).evalOne of + // pkg/proc/eval.go exhaustively covers all implementations of the + // evalop.Op interface. + + ops := make(map[string]bool) + + var fset, fset2 token.FileSet + f, err := parser.ParseFile(&fset, "ops.go", nil, 0) + assertNoError(err, t, "ParseFile") + for _, decl := range f.Decls { + decl, _ := decl.(*ast.FuncDecl) + if decl == nil { + continue + } + if decl.Name.Name != "depthCheck" { + continue + } + ops[decl.Recv.List[0].Type.(*ast.StarExpr).X.(*ast.Ident).Name] = false + } + + f, err = parser.ParseFile(&fset2, "../eval.go", nil, 0) + assertNoError(err, t, "ParseFile") + for _, decl := range f.Decls { + decl, _ := decl.(*ast.FuncDecl) + if decl == nil { + continue + } + if decl.Name.Name != "executeOp" { + continue + } + ast.Inspect(decl, func(n ast.Node) bool { + sw, _ := n.(*ast.TypeSwitchStmt) + if sw == nil { + return true + } + + for _, c := range sw.Body.List { + if len(c.(*ast.CaseClause).List) == 0 { + // default clause + continue + } + sel := c.(*ast.CaseClause).List[0].(*ast.StarExpr).X.(*ast.SelectorExpr) + if sel.X.(*ast.Ident).Name != "evalop" { + t.Fatalf("wrong case statement at: %v", fset2.Position(sel.Pos())) + } + + ops[sel.Sel.Name] = true + } + return false + }) + } + + for op := range ops { + if !ops[op] { + t.Errorf("evalop.Op %s not used in evalOne", op) + } + } +} diff --git a/pkg/proc/evalop/ops.go b/pkg/proc/evalop/ops.go new file mode 100644 index 0000000000..abd90d3ed7 --- /dev/null +++ b/pkg/proc/evalop/ops.go @@ -0,0 +1,250 @@ +package evalop + +import ( + "go/ast" + "go/constant" + + "github.com/go-delve/delve/pkg/dwarf/godwarf" +) + +// Op is a stack machine opcode +type Op interface { + depthCheck() (npop, npush int) +} + +// PushCurg pushes the current goroutine on the stack. +type PushCurg struct { +} + +func (*PushCurg) depthCheck() (npop, npush int) { return 0, 1 } + +// PushFrameoff pushes the frame offset for the current frame on the stack. +type PushFrameoff struct { +} + +func (*PushFrameoff) depthCheck() (npop, npush int) { return 0, 1 } + +// PushThreadID pushes the ID of the current thread on the stack. +type PushThreadID struct { +} + +func (*PushThreadID) depthCheck() (npop, npush int) { return 0, 1 } + +// PushConst pushes a constant on the stack. +type PushConst struct { + Value constant.Value +} + +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 +} + +func (*PushLocal) depthCheck() (npop, npush int) { return 0, 1 } + +// PushNil pushes an untyped nil on the stack. +type PushNil struct { +} + +func (*PushNil) depthCheck() (npop, npush int) { return 0, 1 } + +// PushRegister pushes the CPU register Regnum on the stack. +type PushRegister struct { + Regnum int + Regname string +} + +func (*PushRegister) depthCheck() (npop, npush int) { return 0, 1 } + +// PushPackageVar pushes a package variable on the stack. +type PushPackageVar struct { + PkgName, Name string // if PkgName == "" use current function's package +} + +func (*PushPackageVar) depthCheck() (npop, npush int) { return 0, 1 } + +// Select replaces the topmost stack variable v with v.Name. +type Select struct { + Name string +} + +func (*Select) depthCheck() (npop, npush int) { return 1, 1 } + +// TypeAssert replaces the topmost stack variable v with v.(DwarfType). +type TypeAssert struct { + DwarfType godwarf.Type + Node *ast.TypeAssertExpr +} + +func (*TypeAssert) depthCheck() (npop, npush int) { return 1, 1 } + +// PointerDeref replaces the topmost stack variable v with *v. +type PointerDeref struct { + Node *ast.StarExpr +} + +func (*PointerDeref) depthCheck() (npop, npush int) { return 1, 1 } + +// Unary applies the given unary operator to the topmost stack variable. +type Unary struct { + Node *ast.UnaryExpr +} + +func (*Unary) depthCheck() (npop, npush int) { return 1, 1 } + +// AddrOf replaces the topmost stack variable v with &v. +type AddrOf struct { + Node *ast.UnaryExpr +} + +func (*AddrOf) depthCheck() (npop, npush int) { return 1, 1 } + +// TypeCast replaces the topmost stack variable v with (DwarfType)(v). +type TypeCast struct { + DwarfType godwarf.Type + Node *ast.CallExpr +} + +func (*TypeCast) depthCheck() (npop, npush int) { return 1, 1 } + +// Reslice implements a reslice operation. +// If HasHigh is set it pops three variables, low, high and v, and pushes +// v[low:high]. +// Otherwise it pops two variables, low and v, and pushes v[low:]. +type Reslice struct { + HasHigh bool + Node *ast.SliceExpr +} + +func (op *Reslice) depthCheck() (npop, npush int) { + if op.HasHigh { + return 3, 1 + } else { + return 2, 1 + } +} + +// Index pops two variables, idx and v, and pushes v[idx]. +type Index struct { + Node *ast.IndexExpr +} + +func (*Index) depthCheck() (npop, npush int) { return 2, 1 } + +// Jump looks at the topmost stack variable and if it satisifies the +// condition specified by When it jumps to the stack machine instruction at +// Target+1. +// If Pop is set the topmost stack variable is also popped. +type Jump struct { + When JumpCond + Pop bool + Target int + Node ast.Expr +} + +func (jmpif *Jump) depthCheck() (npop, npush int) { + if jmpif.Pop { + return 1, 0 + } + return 0, 0 +} + +// JumpCond specifies a condition for the Jump instruction. +type JumpCond uint8 + +const ( + JumpIfFalse JumpCond = iota + JumpIfTrue +) + +// Binary pops two variables from the stack, applies the specified binary +// operator to them and pushes the result back on the stack. +type Binary struct { + Node *ast.BinaryExpr +} + +func (*Binary) depthCheck() (npop, npush int) { return 2, 1 } + +// BoolToConst pops the topmost variable from the stack, which must be a +// boolean variable, and converts it to a constant. +type BoolToConst struct { +} + +func (*BoolToConst) depthCheck() (npop, npush int) { return 1, 1 } + +// Pop removes the topmost variable from the stack. +type Pop struct { +} + +func (*Pop) depthCheck() (npop, npush int) { return 1, 0 } + +// BuiltinCall pops len(Args) argument from the stack, calls the specified +// builtin on them and pushes the result back on the stack. +type BuiltinCall struct { + Name string + Args []ast.Expr +} + +func (bc *BuiltinCall) depthCheck() (npop, npush int) { + return len(bc.Args), 1 +} + +// CallInjectionStart starts call injection by calling +// runtime.debugCallVn. +type CallInjectionStart struct { + id int // identifier for all the call injection instructions that belong to the same sequence, this only exists to make reading listings easier + HasFunc bool // target function already pushed on the stack + Node *ast.CallExpr +} + +func (*CallInjectionStart) depthCheck() (npop, npush int) { return 0, 1 } + +// CallInjectionSetTarget starts the call injection, after +// runtime.debugCallVn set up the stack for us, by copying the entry point +// of the function, setting the closure register and copying the receiver. +type CallInjectionSetTarget struct { + id int +} + +func (*CallInjectionSetTarget) depthCheck() (npop, npush int) { return 1, 0 } + +// CallInjectionCopyArg copies one argument for call injection. +type CallInjectionCopyArg struct { + id int + ArgNum int + ArgExpr ast.Expr +} + +func (*CallInjectionCopyArg) depthCheck() (npop, npush int) { return 1, 0 } + +// CallInjectionComplete resumes target execution so that the injected call can run. +type CallInjectionComplete struct { + id int +} + +func (*CallInjectionComplete) depthCheck() (npop, npush int) { return 0, 1 } + +// CallInjectionAllocString uses the call injection protocol to allocate the +// value of a string literal somewhere on the target's memory so that it can +// be assigned to a variable (or passed to a function). +// There are three phases to CallInjectionAllocString, distinguished by the +// Phase field. They must always appear in sequence in the program: +// +// CallInjectionAllocString{Phase: 0} +// CallInjectionAllocString{Phase: 1} +// CallInjectionAllocString{Phase: 2} +type CallInjectionAllocString struct { + Phase int +} + +func (op *CallInjectionAllocString) depthCheck() (npop, npush int) { return 1, 1 } + +// SetValue pops to variables from the stack, lhv and rhv, and sets lhv to +// rhv. +type SetValue struct { + lhe, Rhe ast.Expr +} + +func (*SetValue) depthCheck() (npop, npush int) { return 2, 0 } diff --git a/pkg/proc/fncall.go b/pkg/proc/fncall.go index 70d133934c..0fbbcddc27 100644 --- a/pkg/proc/fncall.go +++ b/pkg/proc/fncall.go @@ -19,12 +19,13 @@ import ( "github.com/go-delve/delve/pkg/dwarf/regnum" "github.com/go-delve/delve/pkg/goversion" "github.com/go-delve/delve/pkg/logflags" + "github.com/go-delve/delve/pkg/proc/evalop" ) // This file implements the function call injection introduced in go1.11. // // The protocol is described in $GOROOT/src/runtime/asm_amd64.s in the -// comments for function runtime·debugCallV1. +// comments for function runtime·debugCallVn. // // The main entry point is EvalExpressionWithCalls which will start a goroutine to // evaluate the provided expression. @@ -37,7 +38,12 @@ import ( // hits a breakpoint in the call injection protocol. // // The work of setting up the function call and executing the protocol is -// done by evalFunctionCall and funcCallStep. +// done by: +// +// - evalop.CallInjectionStart +// - evalop.CallInjectionSetTarget +// - evalCallInjectionCopyArg +// - evalCallInjectionComplete const ( debugCallFunctionNamePrefix1 = "debugCall" @@ -56,7 +62,6 @@ var ( errTooManyArguments = errors.New("too many arguments") errNotEnoughArguments = errors.New("not enough arguments") errNotAGoFunction = errors.New("not a Go function") - errFuncCallNotAllowed = errors.New("function calls not allowed without using 'call'") errFuncCallNotAllowedStrAlloc = errors.New("literal string can not be allocated because function calls are not allowed without using 'call'") ) @@ -82,9 +87,16 @@ type functionCallState struct { // panicvar is a variable used to store the value of the panic, if the // called function panics. panicvar *Variable - // lateCallFailure is set to true if the function call could not be - // completed after we started evaluating the arguments. - lateCallFailure bool + // undoInjection is set after evalop.CallInjectionSetTarget runs and cleared by evalCallInjectionComplete + // it contains informations on how to undo a function call injection without running it + undoInjection *undoInjection + + protocolReg uint64 + debugCallName string +} + +type undoInjection struct { + oldpc, oldlr uint64 } type callContext struct { @@ -250,23 +262,10 @@ func finishEvalExpressionWithCalls(t *Target, g *G, contReq continueRequest, ok return err } -// evalFunctionCall evaluates a function call. -// If this is a built-in function it's evaluated directly. -// Otherwise this will start the function call injection protocol and -// request that the target process resumes. -// See the comment describing the field EvalScope.callCtx for a description -// of the preconditions that make starting the function call protocol -// possible. -// See runtime.debugCallV1 in $GOROOT/src/runtime/asm_amd64.s for a -// description of the protocol. -func evalFunctionCall(scope *EvalScope, node *ast.CallExpr) (*Variable, error) { - r, err := scope.evalBuiltinCall(node) - if r != nil || err != nil { - // it was a builtin call - return r, err - } +func (scope *EvalScope) evalCallInjectionStart(op *evalop.CallInjectionStart, stack *evalStack) { if scope.callCtx == nil { - return nil, errFuncCallNotAllowed + stack.err = evalop.ErrFuncCallNotAllowed + return } thread := scope.g.Thread stacklo := scope.g.stack.lo @@ -278,63 +277,79 @@ func evalFunctionCall(scope *EvalScope, node *ast.CallExpr) (*Variable, error) { thread = scope.callCtx.injectionThread g2, err := GetG(thread) if err != nil { - return nil, err + stack.err = err + return } stacklo = g2.stack.lo } if thread == nil { - return nil, errGoroutineNotRunning + stack.err = errGoroutineNotRunning + return } p := scope.callCtx.p bi := scope.BinInfo if !p.SupportsFunctionCalls() { - return nil, errFuncCallUnsupportedBackend + stack.err = errFuncCallUnsupportedBackend + return } dbgcallfn, dbgcallversion := debugCallFunction(bi) if dbgcallfn == nil { - return nil, errFuncCallUnsupported + stack.err = errFuncCallUnsupported + return } // check that there are at least 256 bytes free on the stack regs, err := thread.Registers() if err != nil { - return nil, err + stack.err = err + return } regs, err = regs.Copy() if err != nil { - return nil, err + stack.err = err + return } if regs.SP()-bi.Arch.debugCallMinStackSize <= stacklo { - return nil, errNotEnoughStack + stack.err = errNotEnoughStack + return } protocolReg, ok := debugCallProtocolReg(bi.Arch.Name, dbgcallversion) if !ok { - return nil, errFuncCallUnsupported + stack.err = errFuncCallUnsupported + return } if bi.Arch.RegistersToDwarfRegisters(0, regs).Reg(protocolReg) == nil { - return nil, errFuncCallUnsupportedBackend + stack.err = errFuncCallUnsupportedBackend + return } fncall := functionCallState{ - expr: node, - savedRegs: regs, + expr: op.Node, + savedRegs: regs, + protocolReg: protocolReg, + debugCallName: dbgcallfn.Name, } - err = funcCallEvalFuncExpr(scope, &fncall, false) - if err != nil { - return nil, err + if op.HasFunc { + err = funcCallEvalFuncExpr(scope, stack, &fncall) + if err != nil { + stack.err = err + return + } } switch bi.Arch.Name { case "amd64": if err := callOP(bi, thread, regs, dbgcallfn.Entry); err != nil { - return nil, err + stack.err = err + return } // write the desired argument frame size at SP-(2*pointer_size) (the extra pointer is the saved PC) if err := writePointer(bi, scope.Mem, regs.SP()-3*uint64(bi.Arch.PtrSize()), uint64(fncall.argFrameSize)); err != nil { - return nil, err + stack.err = err + return } case "arm64", "ppc64le": // debugCallV2 on arm64 needs a special call sequence, callOP can not be used @@ -347,31 +362,37 @@ func evalFunctionCall(scope *EvalScope, node *ast.CallExpr) (*Variable, error) { } sp -= spOffset if err := setSP(thread, sp); err != nil { - return nil, err + stack.err = err + return } if err := writePointer(bi, scope.Mem, sp, regs.LR()); err != nil { - return nil, err + stack.err = err + return } if err := setLR(thread, regs.PC()); err != nil { - return nil, err + stack.err = err + return } if err := writePointer(bi, scope.Mem, sp-spOffset, uint64(fncall.argFrameSize)); err != nil { - return nil, err + stack.err = err + return } regs, err = thread.Registers() if err != nil { - return nil, err + stack.err = err + return } regs, err = regs.Copy() if err != nil { - return nil, err + stack.err = err + return } fncall.savedRegs = regs err = setPC(thread, dbgcallfn.Entry) if err != nil { - return nil, err + stack.err = err + return } - } fncallLog("function call initiated %v frame size %d goroutine %d (thread %d)", fncall.fn, fncall.argFrameSize, scope.g.ID, thread.ThreadID()) @@ -379,58 +400,38 @@ func evalFunctionCall(scope *EvalScope, node *ast.CallExpr) (*Variable, error) { 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() - spoff := int64(scope.Regs.Uint64Val(scope.Regs.SPRegNum)) - int64(scope.g.stack.hi) - bpoff := int64(scope.Regs.Uint64Val(scope.Regs.BPRegNum)) - int64(scope.g.stack.hi) - fboff := scope.Regs.FrameBase - int64(scope.g.stack.hi) - - for { - scope.callCtx.injectionThread = nil - g := scope.callCtx.doContinue() - // Go 1.15 will move call injection execution to a different goroutine, - // but we want to keep evaluation on the original goroutine. - if g.ID == scope.g.ID { - scope.g = g - } else { - // We are in Go 1.15 and we switched to a new goroutine, the original - // goroutine is now parked and therefore does not have a thread - // associated. - scope.g.Thread = nil - scope.g.Status = Gwaiting - scope.callCtx.injectionThread = g.Thread - } - - // adjust the value of registers inside scope - pcreg, bpreg, spreg := scope.Regs.Reg(scope.Regs.PCRegNum), scope.Regs.Reg(scope.Regs.BPRegNum), scope.Regs.Reg(scope.Regs.SPRegNum) - scope.Regs.ClearRegisters() - scope.Regs.AddReg(scope.Regs.PCRegNum, pcreg) - scope.Regs.AddReg(scope.Regs.BPRegNum, bpreg) - scope.Regs.AddReg(scope.Regs.SPRegNum, spreg) - scope.Regs.Reg(scope.Regs.SPRegNum).Uint64Val = uint64(spoff + int64(scope.g.stack.hi)) - scope.Regs.Reg(scope.Regs.BPRegNum).Uint64Val = uint64(bpoff + int64(scope.g.stack.hi)) - scope.Regs.FrameBase = fboff + int64(scope.g.stack.hi) - scope.Regs.CFA = scope.frameOffset + int64(scope.g.stack.hi) - - finished := funcCallStep(scope, &fncall, g.Thread, protocolReg, dbgcallfn.Name) - if finished { - break - } - } + stack.fncallPush(&fncall) + stack.push(newConstant(constant.MakeBool(fncall.fn == nil || fncall.receiver != nil || fncall.closureAddr != 0), scope.Mem)) + stack.callInjectionContinue = true +} +func funcCallFinish(scope *EvalScope, stack *evalStack) { + fncall := stack.fncallPop() if fncall.err != nil { - return nil, fncall.err + if stack.err == nil { + stack.err = fncall.err + } else { + fncallLog("additional fncall error: %v", fncall.err) + } + return } if fncall.panicvar != nil { - return nil, fncallPanicErr{fncall.panicvar} + if stack.err == nil { + stack.err = fncallPanicErr{fncall.panicvar} + } else { + fncallLog("additional fncall panic: %v", fncall.panicvar) + } + return } switch len(fncall.retvars) { case 0: r := newVariable("", 0, nil, scope.BinInfo, nil) r.loaded = true r.Unreadable = errors.New("no return values") - return r, nil + stack.push(r) case 1: - return fncall.retvars[0], nil + stack.push(fncall.retvars[0]) default: // create a fake variable without address or type to return multiple values r := newVariable("", 0, nil, scope.BinInfo, nil) @@ -439,7 +440,7 @@ func evalFunctionCall(scope *EvalScope, node *ast.CallExpr) (*Variable, error) { for i := range fncall.retvars { r.Children[i] = *fncall.retvars[i] } - return r, nil + stack.push(r) } } @@ -503,27 +504,10 @@ func callOP(bi *BinaryInfo, thread Thread, regs Registers, callAddr uint64) erro // funcCallEvalFuncExpr evaluates expr.Fun and returns the function that we're trying to call. // If allowCalls is false function calls will be disabled even if scope.callCtx != nil -func funcCallEvalFuncExpr(scope *EvalScope, fncall *functionCallState, allowCalls bool) error { +func funcCallEvalFuncExpr(scope *EvalScope, stack *evalStack, fncall *functionCallState) error { bi := scope.BinInfo - if !allowCalls { - callCtx := scope.callCtx - scope.callCtx = nil - defer func() { - scope.callCtx = callCtx - }() - } - - fnvar, err := scope.evalAST(fncall.expr.Fun) - if err == errFuncCallNotAllowed { - // we can't determine the frame size because callexpr.Fun can't be - // evaluated without enabling function calls, just set up an argument - // frame for the maximum possible argument size. - fncall.argFrameSize = maxArgFrameSize - return nil - } else if err != nil { - return err - } + fnvar := stack.peek() if fnvar.Kind != reflect.Func { return fmt.Errorf("expression %q is not a function", exprToString(fncall.expr.Fun)) } @@ -543,6 +527,7 @@ func funcCallEvalFuncExpr(scope *EvalScope, fncall *functionCallState, allowCall } fncall.closureAddr = fnvar.closureAddr + var err error fncall.argFrameSize, fncall.formalArgs, err = funcCallArgs(fncall.fn, bi, false) if err != nil { return err @@ -579,56 +564,7 @@ type funcCallArg struct { isret bool } -// funcCallEvalArgs evaluates the arguments of the function call, copying -// them into the argument frame starting at argFrameAddr. -func funcCallEvalArgs(callScope *EvalScope, fncall *functionCallState, thread Thread) error { - if callScope.g == nil { - // this should never happen - return errNoGoroutine - } - - formalScope, err := GoroutineScope(callScope.target, thread) - if err != nil { - return err - } - - if fncall.receiver != nil { - err := funcCallCopyOneArg(callScope, fncall, fncall.receiver, &fncall.formalArgs[0], formalScope) - if err != nil { - return err - } - fncall.formalArgs = fncall.formalArgs[1:] - } - - for i := range fncall.formalArgs { - formalArg := &fncall.formalArgs[i] - - actualArg, err := callScope.evalAST(fncall.expr.Args[i]) - if err != nil { - if _, ispanic := err.(fncallPanicErr); ispanic { - return err - } - return fmt.Errorf("error evaluating %q as argument %s in function %s: %v", exprToString(fncall.expr.Args[i]), formalArg.name, fncall.fn.Name, err) - } - actualArg.Name = exprToString(fncall.expr.Args[i]) - - // evalAST can cause the current thread to change, recover it - thread = callScope.callCtx.p.CurrentThread() - formalScope, err = GoroutineScope(callScope.target, thread) - if err != nil { - return err - } - - err = funcCallCopyOneArg(callScope, fncall, actualArg, formalArg, formalScope) - if err != nil { - return err - } - } - - return nil -} - -func funcCallCopyOneArg(scope *EvalScope, fncall *functionCallState, actualArg *Variable, formalArg *funcCallArg, formalScope *EvalScope) error { +func funcCallCopyOneArg(scope *EvalScope, fncall *functionCallState, actualArg *Variable, formalArg *funcCallArg, thread Thread) error { if scope.callCtx.checkEscape { //TODO(aarzilli): only apply the escapeCheck to leaking parameters. if err := escapeCheck(actualArg, formalArg.name, scope.g.stack); err != nil { @@ -644,6 +580,11 @@ func funcCallCopyOneArg(scope *EvalScope, fncall *functionCallState, actualArg * //TODO(aarzilli): automatic wrapping in interfaces for cases not handled // by convertToEface. + formalScope, err := GoroutineScope(scope.target, thread) + if err != nil { + return err + } + var formalArgVar *Variable if formalArg.dwarfEntry != nil { var err error @@ -842,9 +783,10 @@ const ( ) // funcCallStep executes one step of the function call injection protocol. -func funcCallStep(callScope *EvalScope, fncall *functionCallState, thread Thread, protocolReg uint64, debugCallName string) bool { +func funcCallStep(callScope *EvalScope, stack *evalStack, thread Thread) bool { p := callScope.callCtx.p bi := p.BinInfo() + fncall := stack.fncallPeek() regs, err := thread.Registers() if err != nil { @@ -852,7 +794,7 @@ func funcCallStep(callScope *EvalScope, fncall *functionCallState, thread Thread return true } - regval := bi.Arch.RegistersToDwarfRegisters(0, regs).Uint64Val(protocolReg) + regval := bi.Arch.RegistersToDwarfRegisters(0, regs).Uint64Val(fncall.protocolReg) if logflags.FnCall() { loc, _ := thread.Location() @@ -869,6 +811,7 @@ func funcCallStep(callScope *EvalScope, fncall *functionCallState, thread Thread switch regval { case debugCallRegPrecheckFailed: // 8 + stack.callInjectionContinue = true archoff := uint64(0) if bi.Arch.Name == "arm64" { archoff = 8 @@ -887,65 +830,6 @@ func funcCallStep(callScope *EvalScope, fncall *functionCallState, thread Thread case debugCallRegCompleteCall: // 0 p.fncallForG[callScope.g.ID].startThreadID = 0 - // evaluate arguments of the target function, copy them into its argument frame and call the function - if fncall.fn == nil || fncall.receiver != nil || fncall.closureAddr != 0 { - // if we couldn't figure out which function we are calling before - // (because the function we are calling is the return value of a call to - // another function) now we have to figure it out by recursively - // evaluating the function calls. - // This also needs to be done if the function call has a receiver - // argument or a closure address (because those addresses could be on the stack - // and have changed position between the start of the call and now). - - err := funcCallEvalFuncExpr(callScope, fncall, true) - if err != nil { - fncall.err = err - fncall.lateCallFailure = true - break - } - //TODO: double check that function call size isn't too big - - // funcCallEvalFuncExpr can start a function call injection itself, we - // need to recover the correct thread here. - thread = p.CurrentThread() - } - - // instead of evaluating the arguments we start first by pushing the call - // on the stack, this is the opposite of what would happen normally but - // it's necessary because otherwise the GC wouldn't be able to deal with - // the argument frame. - if fncall.closureAddr != 0 { - // When calling a function pointer we must set the DX register to the - // address of the function pointer itself. - setClosureReg(thread, fncall.closureAddr) - } - cfa := regs.SP() - oldpc := regs.PC() - var oldlr uint64 - if bi.Arch.Name == "arm64" || bi.Arch.Name == "ppc64le" { - oldlr = regs.LR() - } - callOP(bi, thread, regs, fncall.fn.Entry) - err = funcCallEvalArgs(callScope, fncall, thread) - thread = p.CurrentThread() // call evaluation in funcCallEvalArgs can cause the current thread to change - - if err != nil { - // rolling back the call, note: this works because we called regs.Copy() above - switch bi.Arch.Name { - case "amd64": - setSP(thread, cfa) - setPC(thread, oldpc) - case "arm64", "ppc64le": - setLR(thread, oldlr) - setPC(thread, oldpc) - default: - panic("not implemented") - } - fncall.err = err - fncall.lateCallFailure = true - break - } - case debugCallRegRestoreRegisters: // 16 // runtime requests that we restore the registers (all except pc and sp), // this is also the last step of the function call protocol. @@ -960,9 +844,8 @@ func funcCallStep(callScope *EvalScope, fncall *functionCallState, thread Thread fncall.err = fmt.Errorf("could not restore SP: %v", err) } fncallLog("stepping thread %d", thread.ThreadID()) - - if err := stepInstructionOut(callScope.callCtx.grp, p, thread, debugCallName, debugCallName); err != nil { - fncall.err = fmt.Errorf("could not step out of %s: %v", debugCallName, err) + if err := stepInstructionOut(callScope.callCtx.grp, p, thread, fncall.debugCallName, fncall.debugCallName); err != nil { + fncall.err = fmt.Errorf("could not step out of %s: %v", fncall.debugCallName, err) } if bi.Arch.Name == "amd64" { // The tail of debugCallV2 corrupts the state of RFLAGS, we must restore @@ -979,7 +862,8 @@ func funcCallStep(callScope *EvalScope, fncall *functionCallState, thread Thread case debugCallRegReadReturn: // 1 // read return arguments from stack - if fncall.panicvar != nil || fncall.lateCallFailure { + stack.callInjectionContinue = true + if fncall.panicvar != nil || fncall.err != nil { break } retScope, err := ThreadScope(p, thread) @@ -1030,6 +914,7 @@ func funcCallStep(callScope *EvalScope, fncall *functionCallState, thread Thread case debugCallRegReadPanic: // 2 // read panic value from stack + stack.callInjectionContinue = true archoff := uint64(0) if bi.Arch.Name == "arm64" { archoff = 8 @@ -1046,12 +931,51 @@ func funcCallStep(callScope *EvalScope, fncall *functionCallState, thread Thread default: // Got an unknown protocol register value, this is probably bad but the safest thing // possible is to ignore it and hope it didn't matter. + stack.callInjectionContinue = true fncallLog("unknown value of protocol register %#x", regval) } return false } +func (scope *EvalScope) evalCallInjectionSetTarget(op *evalop.CallInjectionSetTarget, stack *evalStack, thread Thread) { + fncall := stack.fncallPeek() + if fncall.fn == nil || fncall.receiver != nil || fncall.closureAddr != 0 { + funcCallEvalFuncExpr(scope, stack, fncall) + } + stack.pop() // target function, consumed by funcCallEvalFuncExpr either above or in evalop.CallInjectionStart + + regs, err := thread.Registers() + if err != nil { + stack.err = err + return + } + + if fncall.closureAddr != 0 { + // When calling a function pointer we must set the DX register to the + // address of the function pointer itself. + setClosureReg(thread, fncall.closureAddr) + } + + undo := new(undoInjection) + undo.oldpc = regs.PC() + if scope.BinInfo.Arch.Name == "arm64" || scope.BinInfo.Arch.Name == "ppc64le" { + undo.oldlr = regs.LR() + } + callOP(scope.BinInfo, thread, regs, fncall.fn.Entry) + + fncall.undoInjection = undo + + if fncall.receiver != nil { + err := funcCallCopyOneArg(scope, fncall, fncall.receiver, &fncall.formalArgs[0], thread) + if err != nil { + stack.err = err + return + } + fncall.formalArgs = fncall.formalArgs[1:] + } +} + func readStackVariable(t *Target, thread Thread, regs Registers, off uint64, typename string, loadCfg LoadConfig) (*Variable, error) { bi := thread.BinInfo() scope, err := ThreadScope(t, thread) @@ -1093,47 +1017,92 @@ func fakeFunctionEntryScope(scope *EvalScope, fn *Function, cfa int64, sp uint64 return nil } -// allocString allocates spaces for the contents of v if it needs to be allocated -func allocString(scope *EvalScope, v *Variable) error { - if v.Base != 0 || v.Len == 0 { - // already allocated - return nil - } +func (scope *EvalScope) allocString(phase int, stack *evalStack, curthread Thread) bool { + switch phase { + case 0: + x := stack.peek() + if !(x.Kind == reflect.String && x.Addr == 0 && (x.Flags&VariableConstant) != 0 && x.Len > 0) { + stack.opidx += 2 // skip the next two allocString phases, we don't need to do an allocation + return false + } + if scope.callCtx == nil { + // do not complain here, setValue will if no other errors happen + stack.opidx += 2 + return false + } + mallocv, err := scope.findGlobal("runtime", "mallocgc") + if mallocv == nil { + stack.err = err + return false + } + stack.push(mallocv) + scope.evalCallInjectionStart(&evalop.CallInjectionStart{HasFunc: true, Node: &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: &ast.Ident{Name: "runtime"}, + Sel: &ast.Ident{Name: "mallocgc"}, + }, + Args: []ast.Expr{ + &ast.BasicLit{Kind: token.INT, Value: "0"}, + &ast.Ident{Name: "nil"}, + &ast.Ident{Name: "false"}, + }, + }}, stack) + if stack.err == nil { + stack.pop() // return value of evalop.CallInjectionStart + } + return true - if scope.callCtx == nil { - return errFuncCallNotAllowedStrAlloc - } - savedLoadCfg := scope.callCtx.retLoadCfg - scope.callCtx.retLoadCfg = loadFullValue - defer func() { - scope.callCtx.retLoadCfg = savedLoadCfg - }() - mallocv, err := evalFunctionCall(scope, &ast.CallExpr{ - Fun: &ast.SelectorExpr{ - X: &ast.Ident{Name: "runtime"}, - Sel: &ast.Ident{Name: "mallocgc"}, - }, - Args: []ast.Expr{ - &ast.BasicLit{Kind: token.INT, Value: strconv.Itoa(int(v.Len))}, - &ast.Ident{Name: "nil"}, - &ast.Ident{Name: "false"}, - }, - }) - if err != nil { - return err - } - if mallocv.Unreadable != nil { - return mallocv.Unreadable - } - if mallocv.DwarfType.String() != "*void" { - return fmt.Errorf("unexpected return type for mallocgc call: %v", mallocv.DwarfType.String()) - } - if len(mallocv.Children) != 1 { - return errors.New("internal error, could not interpret return value of mallocgc call") + case 1: + fncall := stack.fncallPeek() + savedLoadCfg := scope.callCtx.retLoadCfg + scope.callCtx.retLoadCfg = loadFullValue + defer func() { + scope.callCtx.retLoadCfg = savedLoadCfg + }() + + scope.evalCallInjectionSetTarget(nil, stack, curthread) + + strvar := stack.peek() + + stack.err = funcCallCopyOneArg(scope, fncall, newConstant(constant.MakeInt64(strvar.Len), scope.Mem), &fncall.formalArgs[0], curthread) + if stack.err != nil { + return false + } + stack.err = funcCallCopyOneArg(scope, fncall, nilVariable, &fncall.formalArgs[1], curthread) + if stack.err != nil { + return false + } + stack.err = funcCallCopyOneArg(scope, fncall, newConstant(constant.MakeBool(false), scope.Mem), &fncall.formalArgs[2], curthread) + if stack.err != nil { + return false + } + return true + + case 2: + mallocv := stack.pop() + v := stack.pop() + if mallocv.Unreadable != nil { + stack.err = mallocv.Unreadable + return false + } + + if mallocv.DwarfType.String() != "*void" { + stack.err = fmt.Errorf("unexpected return type for mallocgc call: %v", mallocv.DwarfType.String()) + return false + } + + if len(mallocv.Children) != 1 { + stack.err = errors.New("internal error, could not interpret return value of mallocgc call") + return false + } + + v.Base = mallocv.Children[0].Addr + _, stack.err = scope.Mem.WriteMemory(v.Base, []byte(constant.StringVal(v.Value))) + stack.push(v) + return false } - v.Base = mallocv.Children[0].Addr - _, err = scope.Mem.WriteMemory(v.Base, []byte(constant.StringVal(v.Value))) - return err + + panic("unreachable") } func isCallInjectionStop(t *Target, thread Thread, loc *Location) bool { diff --git a/pkg/proc/variables.go b/pkg/proc/variables.go index fd18990d62..20f4c2843d 100644 --- a/pkg/proc/variables.go +++ b/pkg/proc/variables.go @@ -9,6 +9,7 @@ import ( "go/constant" "go/token" "math" + "math/bits" "reflect" "sort" "strconv" @@ -1685,6 +1686,9 @@ func (v *Variable) loadArrayValues(recurseLevel int, cfg LoadConfig) { if count > int64(cfg.MaxArrayValues) { count = int64(cfg.MaxArrayValues) } + if v.Base+uint64(v.stride*count) < v.Base { + v.Unreadable = fmt.Errorf("bad array base address %#x", v.Base) + } if v.stride < maxArrayStridePrefetch { v.mem = cacheMemory(v.mem, v.Base, int(v.stride*count)) @@ -1724,7 +1728,7 @@ func (v *Variable) readComplex(size int64) { return } - ftyp := fakeBasicType("float", int(fs*8)) + ftyp := godwarf.FakeBasicType("float", int(fs*8)) realvar := v.newVariable("real", v.Addr, ftyp, v.mem) imagvar := v.newVariable("imaginary", v.Addr+uint64(fs), ftyp, v.mem) @@ -2425,7 +2429,7 @@ func (v *Variable) registerVariableTypeConv(newtyp string) (*Variable, error) { break } } - if n == 0 || popcnt(uint64(n)) != 1 { + if n == 0 || bits.OnesCount64(uint64(n)) != 1 { return nil, fmt.Errorf("unknown CPU register type conversion to %q", newtyp) } n = n / 8 @@ -2444,23 +2448,6 @@ func (v *Variable) registerVariableTypeConv(newtyp string) (*Variable, error) { return v, nil } -// popcnt is the number of bits set to 1 in x. -// It's the same as math/bits.OnesCount64, copied here so that we can build -// on versions of go that don't have math/bits. -func popcnt(x uint64) int { - const m0 = 0x5555555555555555 // 01010101 ... - const m1 = 0x3333333333333333 // 00110011 ... - const m2 = 0x0f0f0f0f0f0f0f0f // 00001111 ... - const m = 1<<64 - 1 - x = x>>1&(m0&m) + x&(m0&m) - x = x>>2&(m1&m) + x&(m1&m) - x = (x>>4 + x) & (m2 & m) - x += x >> 8 - x += x >> 16 - x += x >> 32 - return int(x) & (1<<7 - 1) -} - func isCgoType(bi *BinaryInfo, typ godwarf.Type) bool { cu := bi.Images[typ.Common().Index].findCompileUnitForOffset(typ.Common().Offset) if cu == nil { @@ -2503,7 +2490,7 @@ func (cm constantsMap) Get(typ godwarf.Type) *constantType { sort.Sort(constantValuesByValue(ctyp.values)) for i := range ctyp.values { ctyp.values[i].name = strings.TrimPrefix(ctyp.values[i].name, typepkg) - if popcnt(uint64(ctyp.values[i].value)) == 1 { + if bits.OnesCount64(uint64(ctyp.values[i].value)) == 1 { ctyp.values[i].singleBit = true } } diff --git a/pkg/proc/variables_fuzz_test.go b/pkg/proc/variables_fuzz_test.go index 55ec7a26df..9f7aac1b5b 100644 --- a/pkg/proc/variables_fuzz_test.go +++ b/pkg/proc/variables_fuzz_test.go @@ -86,7 +86,12 @@ func FuzzEvalExpression(f *testing.F) { scope := &proc.EvalScope{Location: *fi.Loc, Regs: fi.Regs, Mem: memoryReaderWithFailingWrites{mem}, BinInfo: bi} for _, tc := range getEvalExpressionTestCases() { - scope.EvalExpression(tc.name, pnormalLoadConfig) + _, err := scope.EvalExpression(tc.name, pnormalLoadConfig) + if err != nil { + if strings.Contains(err.Error(), "internal debugger error") { + panic(err) + } + } } }) } diff --git a/pkg/proc/variables_test.go b/pkg/proc/variables_test.go index 17d14f5138..a1a00e9971 100644 --- a/pkg/proc/variables_test.go +++ b/pkg/proc/variables_test.go @@ -180,6 +180,7 @@ func TestVariableEvaluation2(t *testing.T) { } func TestSetVariable(t *testing.T) { + const errorPrefix = "ERROR:" var testcases = []struct { name string typ string // type of @@ -202,6 +203,7 @@ func TestSetVariable(t *testing.T) { {"s3", "[]int", `[]int len: 0, cap: 6, []`, "s4[2:5]", "[]int len: 3, cap: 3, [3,4,5]"}, {"s3", "[]int", "[]int len: 3, cap: 3, [3,4,5]", "arr1[:]", "[]int len: 4, cap: 4, [0,1,2,3]"}, + {"str1", "string", `"01234567890"`, `"new value"`, errorPrefix + "literal string can not be allocated because function calls are not allowed without using 'call'"}, } withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { @@ -218,11 +220,23 @@ func TestSetVariable(t *testing.T) { assertNoError(err, t, "EvalVariable()") assertVariable(t, variable, varTest{tc.name, true, tc.startVal, "", tc.typ, nil}) - assertNoError(setVariable(p, tc.name, tc.expr), t, fmt.Sprintf("SetVariable(%q, %q)", tc.name, tc.expr)) + err = setVariable(p, tc.name, tc.expr) + + if strings.HasPrefix(tc.finalVal, errorPrefix) { + experr := tc.finalVal[len(errorPrefix):] + if err == nil { + t.Fatalf("expected error %q but didn't get an error", experr) + } + if err.Error() != experr { + t.Fatalf("expected error %q got %v", experr, err) + } + } else { + assertNoError(err, t, fmt.Sprintf("SetVariable(%q, %q)", tc.name, tc.expr)) + variable, err = evalVariableWithCfg(p, tc.name, pnormalLoadConfig) + assertNoError(err, t, "EvalVariable()") + assertVariable(t, variable, varTest{tc.name, true, tc.finalVal, "", tc.typ, nil}) + } - variable, err = evalVariableWithCfg(p, tc.name, pnormalLoadConfig) - assertNoError(err, t, "EvalVariable()") - assertVariable(t, variable, varTest{tc.name, true, tc.finalVal, "", tc.typ, nil}) } }) } @@ -792,11 +806,11 @@ func getEvalExpressionTestCases() []varTest { {"bytearray[0] * bytearray[0]", false, "144", "144", "uint8", nil}, // function call / typecast errors - {"unknownthing(1, 2)", false, "", "", "", errors.New("function calls not allowed without using 'call'")}, - {"(unknownthing)(1, 2)", false, "", "", "", errors.New("function calls not allowed without using 'call'")}, + {"unknownthing(1, 2)", false, "", "", "", errors.New("could not find symbol value for unknownthing")}, + {"(unknownthing)(1, 2)", false, "", "", "", errors.New("could not find symbol value for unknownthing")}, {"afunc(2)", false, "", "", "", errors.New("function calls not allowed without using 'call'")}, {"(afunc)(2)", false, "", "", "", errors.New("function calls not allowed without using 'call'")}, - {"(*afunc)(2)", false, "", "", "", errors.New("could not evaluate function or type (*afunc): expression \"afunc\" (func()) can not be dereferenced")}, + {"(*afunc)(2)", false, "", "", "", errors.New("expression \"afunc\" (func()) can not be dereferenced")}, {"unknownthing(2)", false, "", "", "", errors.New("could not evaluate function or type unknownthing: could not find symbol value for unknownthing")}, {"(*unknownthing)(2)", false, "", "", "", errors.New("could not evaluate function or type (*unknownthing): could not find symbol value for unknownthing")}, {"(*strings.Split)(2)", false, "", "", "", errors.New("could not evaluate function or type (*strings.Split): could not find symbol value for strings")}, @@ -1171,7 +1185,8 @@ func TestCallFunction(t *testing.T) { {"callpanic()", []string{`~panic:interface {}:interface {}(string) "callpanic panicked"`}, nil}, {`stringsJoin(nil, "")`, []string{`:string:""`}, nil}, {`stringsJoin(stringslice, comma)`, []string{`:string:"one,two,three"`}, nil}, - {`stringsJoin(s1, comma)`, nil, errors.New(`error evaluating "s1" as argument v in function main.stringsJoin: could not find symbol value for s1`)}, + {`stringsJoin(stringslice, "~~")`, []string{`:string:"one~~two~~three"`}, nil}, + {`stringsJoin(s1, comma)`, nil, errors.New(`error evaluating "s1" as argument 1 in function stringsJoin: could not find symbol value for s1`)}, {`stringsJoin(intslice, comma)`, nil, errors.New("can not convert value of type []int to []string")}, {`noreturncall(2)`, nil, nil}, @@ -1220,7 +1235,7 @@ func TestCallFunction(t *testing.T) { {`onetwothree(intcallpanic(2))`, []string{`:[]int:[]int len: 3, cap: 3, [3,4,5]`}, nil}, {`onetwothree(intcallpanic(0))`, []string{`~panic:interface {}:interface {}(string) "panic requested"`}, nil}, {`onetwothree(intcallpanic(2)+1)`, []string{`:[]int:[]int len: 3, cap: 3, [4,5,6]`}, nil}, - {`onetwothree(intcallpanic("not a number"))`, nil, errors.New("error evaluating \"intcallpanic(\\\"not a number\\\")\" as argument n in function main.onetwothree: can not convert \"not a number\" constant to int")}, + {`onetwothree(intcallpanic("not a number"))`, nil, errors.New("can not convert \"not a number\" constant to int")}, // Variable setting tests {`pa2 = getAStructPtr(8); pa2`, []string{`pa2:*main.astruct:*main.astruct {X: 8}`}, nil}, @@ -1271,11 +1286,11 @@ func TestCallFunction(t *testing.T) { } var testcasesBefore114After112 = []testCaseCallFunction{ - {`strings.Join(s1, comma)`, nil, errors.New(`error evaluating "s1" as argument a in function strings.Join: could not find symbol value for s1`)}, + {`strings.Join(s1, comma)`, nil, errors.New(`error evaluating "s1" as argument 1 in function strings.Join: could not find symbol value for s1`)}, } var testcases114 = []testCaseCallFunction{ - {`strings.Join(s1, comma)`, nil, errors.New(`error evaluating "s1" as argument elems in function strings.Join: could not find symbol value for s1`)}, + {`strings.Join(s1, comma)`, nil, errors.New(`error evaluating "s1" as argument 1 in function strings.Join: could not find symbol value for s1`)}, } var testcases117 = []testCaseCallFunction{