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{