From a80904ca1f5da4ee99053c46b35f4d6a2124f824 Mon Sep 17 00:00:00 2001 From: Alessandro Arzilli Date: Mon, 24 Mar 2025 18:24:49 +0100 Subject: [PATCH] proc: do not always allocate struct literals (#3953) Do not always allocate new struct literals to the target's memroy. Wait until they actually need to have an address. Fixes #1465 --- pkg/proc/eval.go | 14 +++++ pkg/proc/evalop/evalcompile.go | 104 +++++++++++++++++++++++++-------- pkg/proc/evalop/ops.go | 8 +++ pkg/proc/mem.go | 7 ++- pkg/proc/variables_test.go | 15 ++++- 5 files changed, 118 insertions(+), 30 deletions(-) diff --git a/pkg/proc/eval.go b/pkg/proc/eval.go index 5279287f..58202f73 100644 --- a/pkg/proc/eval.go +++ b/pkg/proc/eval.go @@ -1279,6 +1279,9 @@ func (stack *evalStack) executeOp() { v := newVariable("", typeAddr, rttyp, scope.BinInfo, scope.Mem) stack.push(v.pointerToVariable()) + case *evalop.PushNewFakeVariable: + stack.pushNewFakeVariable(scope, op.Type) + default: stack.err = fmt.Errorf("internal debugger error: unknown eval opcode: %#v", op) } @@ -1378,6 +1381,17 @@ func (stack *evalStack) pushIdent(scope *EvalScope, name string) (found bool) { return true } +func (stack *evalStack) pushNewFakeVariable(scope *EvalScope, typ godwarf.Type) { + cm, err := CreateCompositeMemory(scope.Mem, scope.BinInfo.Arch, *new(op.DwarfRegisters), []op.Piece{{Kind: op.ImmPiece, Bytes: make([]byte, typ.Size()), Size: int(typ.Size())}}, typ.Size()) + if err != nil { + stack.err = err + return + } + v := newVariable("", cm.base, typ, scope.BinInfo, cm) + v.Flags = VariableFakeAddress + stack.push(v) +} + func (scope *EvalScope) evalAST(t ast.Expr) (*Variable, error) { ops, err := evalop.CompileAST(scopeToEvalLookup{scope}, t, scope.evalopFlags()) if err != nil { diff --git a/pkg/proc/evalop/evalcompile.go b/pkg/proc/evalop/evalcompile.go index 3ff74715..b816dd38 100644 --- a/pkg/proc/evalop/evalcompile.go +++ b/pkg/proc/evalop/evalcompile.go @@ -111,7 +111,10 @@ func CompileSet(lookup evalLookup, lhexpr, rhexpr string, flags Flags) ([]Op, er return nil, err } - ctx.maybeMaterialize(rhe) + err = ctx.maybeMaterialize(rhe) + if err != nil { + return nil, err + } err = ctx.compileAST(lhe, false) if err != nil { @@ -419,21 +422,7 @@ func (ctx *compileCtx) compileAST(t ast.Expr, toplevel bool) error { return errFuncCallNotAllowedLitAlloc } - ctx.compileSpecialCall("runtime.mallocgc", []ast.Expr{ - &ast.BasicLit{Kind: token.INT, Value: "1"}, - node.Type, - &ast.Ident{Name: "true"}, - }, []Op{ - &PushConst{Value: constant.MakeInt64(1)}, - &PushRuntimeType{dtyp}, - &PushConst{Value: constant.MakeBool(true)}, - }, specialCallDoPinning) - ctx.pushOp(&TypeCast{ - DwarfType: godwarf.FakePointerType(dtyp, int64(ctx.PtrSize())), - Node: &ast.CallExpr{ - Fun: node.Type, - Args: []ast.Expr{&ast.Ident{Name: "new allocation"}}}}) - ctx.pushOp(&PointerDeref{&ast.StarExpr{X: &ast.Ident{Name: "new allocation"}}}) + ctx.pushOp(&PushNewFakeVariable{Type: dtyp}) for i, elt := range node.Elts { ctx.pushOp(&Dup{}) @@ -447,7 +436,10 @@ func (ctx *compileCtx) compileAST(t ast.Expr, toplevel bool) error { if err != nil { return err } - ctx.maybeMaterialize(elt.Value) + err = ctx.maybeMaterialize(elt.Value) + if err != nil { + return err + } default: ctx.pushOp(&Select{Name: typ.Field[i].Name}) rhe = elt @@ -455,7 +447,10 @@ func (ctx *compileCtx) compileAST(t ast.Expr, toplevel bool) error { if err != nil { return err } - ctx.maybeMaterialize(elt) + err = ctx.maybeMaterialize(elt) + if err != nil { + return err + } } ctx.pushOp(&Roll{1}) ctx.pushOp(&SetValue{Rhe: rhe}) @@ -736,7 +731,10 @@ func (ctx *compileCtx) compileFunctionCallNoPinning(node *ast.CallExpr, id int) if err != nil { return fmt.Errorf("error evaluating %q as argument %d in function %s: %v", astutil.ExprToString(arg), i+1, astutil.ExprToString(node.Fun), err) } - ctx.maybeMaterialize(arg) + err = ctx.maybeMaterialize(arg) + if err != nil { + return err + } ctx.pushOp(&CallInjectionCopyArg{id: id, ArgNum: i, ArgExpr: arg}) } @@ -760,12 +758,13 @@ func (ctx *compileCtx) compileFunctionCallWithPinning(node *ast.CallExpr, id int for i, arg := range node.Args { err := ctx.compileAST(arg, false) - if isStringLiteralThatNeedsAlloc(arg) { - ctx.compileAllocLiteralString() - } if err != nil { return fmt.Errorf("error evaluating %q as argument %d in function %s: %v", astutil.ExprToString(arg), i+1, astutil.ExprToString(node.Fun), err) } + err = ctx.maybeMaterialize(arg) + if err != nil { + return err + } } ctx.pushOp(&Roll{len(node.Args)}) @@ -805,12 +804,67 @@ func (ctx *compileCtx) compilePinningLoop(id int) { ctx.pushOp(&CallInjectionComplete2{id: id}) } -// If expr will produce a string literal allocate the string into the target -// program's address space. -func (ctx *compileCtx) maybeMaterialize(expr ast.Expr) { +// If expr will produce a string literal or a pointer to a literal allocate +// it into the target program's address space. +func (ctx *compileCtx) maybeMaterialize(expr ast.Expr) error { if isStringLiteralThatNeedsAlloc(expr) { ctx.compileAllocLiteralString() + return nil } + + expr = removeParen(expr) + addrof, isunary := expr.(*ast.UnaryExpr) + if !isunary || addrof.Op != token.AND { + return nil + } + lit, iscomplit := addrof.X.(*ast.CompositeLit) + if !iscomplit { + return nil + } + + dtyp, err := ctx.FindTypeExpr(lit.Type) + if err != nil { + return err + } + + switch godwarf.ResolveTypedef(dtyp).(type) { + case *godwarf.StructType, *godwarf.ArrayType: + if _, isaddrof := ctx.ops[len(ctx.ops)-1].(*AddrOf); isaddrof { + ctx.ops = ctx.ops[:len(ctx.ops)-1] + } else { + ctx.pushOp(&PointerDeref{&ast.StarExpr{X: &ast.Ident{Name: "unallocated-literal"}}}) + } + + ctx.compileSpecialCall("runtime.mallocgc", []ast.Expr{ + &ast.BasicLit{Kind: token.INT, Value: "1"}, + lit.Type, + &ast.Ident{Name: "true"}, + }, []Op{ + &PushConst{Value: constant.MakeInt64(1)}, + &PushRuntimeType{dtyp}, + &PushConst{Value: constant.MakeBool(true)}, + }, specialCallDoPinning) + ctx.pushOp(&TypeCast{ + DwarfType: godwarf.FakePointerType(dtyp, int64(ctx.PtrSize())), + Node: &ast.CallExpr{ + Fun: lit.Type, + Args: []ast.Expr{&ast.Ident{Name: "new allocation"}}}}) + + xderef := &ast.StarExpr{X: &ast.Ident{Name: "new-allocation"}} + xset := &ast.Ident{Name: "literal-allocation"} + + ctx.pushOp(&Dup{}) // stack after: [ ptrToRealLiteral, ptrToRealLiteral, fakeLiteral ] + ctx.pushOp(&PointerDeref{xderef}) // stack after: [ realLiteral, ptrToRealLiteral, fakeLiteral ] + ctx.pushOp(&Roll{2}) // stack after: [ fakeLiteral, realLiteral, ptrToRealLiteral ] + ctx.pushOp(&Roll{1}) // stack after: [ realLiteral, fakeLiteral, ptrToRealLiteral ] + ctx.pushOp(&SetValue{Rhe: xset}) // stack after: [ ptrToRealLiteral ] + return nil + + default: + // either *godwarf.MapType, *godwarf.SliceType or *godwarf.FuncType + return fmt.Errorf("allocating a literal of type %s not implemented", dtyp.String()) + } + } func Listing(depth []int, ops []Op) string { diff --git a/pkg/proc/evalop/ops.go b/pkg/proc/evalop/ops.go index c3388850..0f61fe64 100644 --- a/pkg/proc/evalop/ops.go +++ b/pkg/proc/evalop/ops.go @@ -347,3 +347,11 @@ type PushRuntimeType struct { } func (*PushRuntimeType) depthCheck() (npop, npush int) { return 0, 1 } + +// PushNewFakeVariable pushes a new debugger-allocated variable on to the +// stack with the given type. +type PushNewFakeVariable struct { + Type godwarf.Type +} + +func (*PushNewFakeVariable) depthCheck() (npop, npush int) { return 0, 1 } diff --git a/pkg/proc/mem.go b/pkg/proc/mem.go index 530ca5c0..d758a9db 100644 --- a/pkg/proc/mem.go +++ b/pkg/proc/mem.go @@ -190,7 +190,11 @@ func (mem *compositeMemory) WriteMemory(addr uint64, data []byte) (int, error) { return 0, errors.New("write out of bounds") } if mem.regs.ChangeFunc == nil { - return 0, errors.New("can not write registers") + for _, piece := range mem.pieces { + if piece.Kind == op.RegPiece { + return 0, errors.New("can not write registers") + } + } } copy(mem.data[addr:], data) @@ -216,7 +220,6 @@ func (mem *compositeMemory) WriteMemory(addr uint64, data []byte) (int, error) { return donesz + n, err } case op.ImmPiece: - //TODO(aarzilli): maybe return an error if the user tried to change the value? // nothing to do default: panic("unsupported piece kind") diff --git a/pkg/proc/variables_test.go b/pkg/proc/variables_test.go index 141b3ca4..609f73c2 100644 --- a/pkg/proc/variables_test.go +++ b/pkg/proc/variables_test.go @@ -669,6 +669,9 @@ func getEvalExpressionTestCases() []varTest { {"mnil[\"Malone\"]", false, "", "", "", errors.New("key not found")}, {"m1[80:]", false, "", "", "", errors.New("map index out of bounds")}, {"mlarge", false, "map[main.largestruct]main.largestruct [{name: \"one\", v: [256]uint8 [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...+192 more]}: {name: \"oneval\", v: [256]uint8 [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...+192 more]}, ]", "map[main.largestruct]main.largestruct [...]", "map[main.largestruct]main.largestruct", nil}, + {"m3[main.astruct{1,1}]", false, "42", "42", "int", nil}, + {"m4[main.astruct{2,2}]", false, "main.astruct {A: 22, B: 22}", "main.astruct {A: 22, B: 22}", "main.astruct", nil}, + {"m3[main.astruct{3,3}]", false, "", "", "", errors.New("key not found")}, // interfaces {"err1", true, "error(*main.astruct) *{A: 1, B: 2}", "error(*main.astruct) 0x…", "error", nil}, @@ -982,6 +985,12 @@ func TestEvalExpression(t *testing.T) { // this type of eval is unsupported with the current version of Go. return } + if err != nil && err.Error() == "expression *ast.CompositeLit not implemented" { + if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 23) || runtime.GOARCH == "386" { + // composite literals not supported before 1.22 + return + } + } if tc.err == nil { assertNoError(err, t, fmt.Sprintf("EvalExpression(%s) returned an error", tc.name)) assertVariable(t, variable, tc) @@ -1403,11 +1412,11 @@ func TestCallFunction(t *testing.T) { } var testcases123 = []testCaseCallFunction{ - {`mul2(main.a2struct{Y: 3})`, []string{":int:6"}, nil, 1}, - {`mul2(main.a2struct{4})`, []string{":int:8"}, nil, 1}, + {`mul2(main.a2struct{Y: 3})`, []string{":int:6"}, nil, 0}, + {`mul2(main.a2struct{4})`, []string{":int:8"}, nil, 0}, {`mul2ptr(&main.a2struct{Y: 3})`, []string{":int:6"}, nil, 1}, {`mul2ptr(&main.a2struct{1})`, []string{":int:2"}, nil, 1}, - {`m[main.intpair{3, 1}]`, []string{`:string:"three,one"`}, nil, 1}, + {`m[main.intpair{3, 1}]`, []string{`:string:"three,one"`}, nil, 0}, } withTestProcessArgs("fncall", t, ".", nil, protest.AllNonOptimized, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {