提交 d2904322 编写于 作者: A aarzilli 提交者: Derek Parker

proc: add flag to disable escape checking in function calls

Fix escape checking in function calls  and add a flag to disable it.
上级 143cf6ae
......@@ -73,6 +73,8 @@ Aliases: bp
## call
Resumes process, injecting a function call (EXPERIMENTAL!!!)
call [-unsafe] <function call expression>
Current limitations:
- only pointers to stack-allocated objects can be passed as argument.
- only some automatic type conversions are supported.
......@@ -174,8 +176,8 @@ Aliases: disass
## down
Move the current frame down.
down [<m>]
down [<m>] <command>
down [<m>]
down [<m>] <command>
Move the current frame down by <m>. The second form runs the command on the given frame.
......@@ -201,8 +203,8 @@ Aliases: quit q
## frame
Set the current frame, or execute command on a different frame.
frame <m>
frame <m> <command>
frame <m>
frame <m> <command>
The first form sets frame used by subsequent commands such as "print" or "set".
The second form runs the command on the given frame.
......@@ -394,8 +396,8 @@ If regex is specified only the types matching it will be returned.
## up
Move the current frame up.
up [<m>]
up [<m>] <command>
up [<m>]
up [<m>] <command>
Move the current frame up by <m>. The second form runs the command on the given frame.
......
......@@ -39,6 +39,10 @@ type astruct struct {
X int
}
type a2struct struct {
Y int
}
func (a astruct) VRcvr(x int) string {
return fmt.Sprintf("%d + %d = %d", x, a.X, x+a.X)
}
......@@ -66,6 +70,11 @@ func makeclos(pa *astruct) func(int) string {
}
var ga astruct
var globalPA2 *a2struct
func escapeArg(pa2 *a2struct) {
globalPA2 = pa2
}
func main() {
one, two := 1, 2
......@@ -74,6 +83,7 @@ func main() {
comma := ","
a := astruct{X: 3}
pa := &astruct{X: 6}
a2 := a2struct{Y: 7}
var vable_a VRcvrable = a
var vable_pa VRcvrable = pa
......@@ -88,5 +98,5 @@ func main() {
runtime.Breakpoint()
call1(one, two)
fn2clos(2)
fmt.Println(one, two, zero, callpanic, callstacktrace, stringsJoin, intslice, stringslice, comma, a.VRcvr, a.PRcvr, pa, vable_a, vable_pa, pable_pa, fn2clos, fn2glob, fn2valmeth, fn2ptrmeth, fn2nil, ga)
fmt.Println(one, two, zero, callpanic, callstacktrace, stringsJoin, intslice, stringslice, comma, a.VRcvr, a.PRcvr, pa, vable_a, vable_pa, pable_pa, fn2clos, fn2glob, fn2valmeth, fn2ptrmeth, fn2nil, ga, escapeArg, a2)
}
......@@ -876,3 +876,13 @@ func zeroArray(t Type) {
t = at.Type
}
}
func resolveTypedef(typ Type) Type {
for {
if tt, ok := typ.(*TypedefType); ok {
typ = tt.Type
} else {
return typ
}
}
}
......@@ -1208,7 +1208,7 @@ func (err *typeConvErr) Error() string {
func (v *Variable) isType(typ godwarf.Type, kind reflect.Kind) error {
if v.DwarfType != nil {
if typ != nil && typ.String() != v.RealType.String() {
if typ == nil || !sameType(typ, v.RealType) {
return &typeConvErr{v.DwarfType, typ}
}
return nil
......@@ -1265,6 +1265,34 @@ func (v *Variable) isType(typ godwarf.Type, kind reflect.Kind) error {
return nil
}
func sameType(t1, t2 godwarf.Type) bool {
// Because of a bug in the go linker a type that refers to another type
// (for example a pointer type) will usually use the typedef but rarely use
// the non-typedef entry directly.
// For types that we read directly from go this is fine because it's
// consistent, however we also synthesize some types ourselves
// (specifically pointers and slices) and we always use a reference through
// a typedef.
t1 = resolveTypedef(t1)
t2 = resolveTypedef(t2)
if tt1, isptr1 := t1.(*godwarf.PtrType); isptr1 {
tt2, isptr2 := t2.(*godwarf.PtrType)
if !isptr2 {
return false
}
return sameType(tt1.Type, tt2.Type)
}
if tt1, isslice1 := t1.(*godwarf.SliceType); isslice1 {
tt2, isslice2 := t2.(*godwarf.SliceType)
if !isslice2 {
return false
}
return sameType(tt1.ElemType, tt2.ElemType)
}
return t1.String() == t2.String()
}
func (v *Variable) sliceAccess(idx int) (*Variable, error) {
if idx < 0 || int64(idx) >= v.Len {
return nil, fmt.Errorf("index out of bounds")
......
......@@ -81,7 +81,7 @@ type functionCallState struct {
// CallFunction starts a debugger injected function call on the current thread of p.
// See runtime.debugCallV1 in $GOROOT/src/runtime/asm_amd64.s for a
// description of the protocol.
func CallFunction(p Process, expr string, retLoadCfg *LoadConfig) error {
func CallFunction(p Process, expr string, retLoadCfg *LoadConfig, checkEscape bool) error {
bi := p.BinInfo()
if !p.Common().fncallEnabled {
return errFuncCallUnsupportedBackend
......@@ -126,7 +126,7 @@ func CallFunction(p Process, expr string, retLoadCfg *LoadConfig) error {
return err
}
argmem, err := funcCallArgFrame(fn, argvars, g, bi)
argmem, err := funcCallArgFrame(fn, argvars, g, bi, checkEscape)
if err != nil {
return err
}
......@@ -259,7 +259,7 @@ type funcCallArg struct {
// funcCallArgFrame checks type and pointer escaping for the arguments and
// returns the argument frame.
func funcCallArgFrame(fn *Function, actualArgs []*Variable, g *G, bi *BinaryInfo) (argmem []byte, err error) {
func funcCallArgFrame(fn *Function, actualArgs []*Variable, g *G, bi *BinaryInfo, checkEscape bool) (argmem []byte, err error) {
argFrameSize, formalArgs, err := funcCallArgs(fn, bi, false)
if err != nil {
return nil, err
......@@ -278,9 +278,11 @@ func funcCallArgFrame(fn *Function, actualArgs []*Variable, g *G, bi *BinaryInfo
formalArg := &formalArgs[i]
actualArg := actualArgs[i]
//TODO(aarzilli): only apply the escapeCheck to leaking parameters.
if err := escapeCheck(actualArg, formalArg.name, g); err != nil {
return nil, fmt.Errorf("can not pass %s to %s: %v", actualArg.Name, formalArg.name, err)
if checkEscape {
//TODO(aarzilli): only apply the escapeCheck to leaking parameters.
if err := escapeCheck(actualArg, formalArg.name, g); err != nil {
return nil, fmt.Errorf("cannot use %s as argument %s in function %s: %v", actualArg.Name, formalArg.name, fn.Name, err)
}
}
//TODO(aarzilli): autmoatic wrapping in interfaces for cases not handled
......@@ -343,7 +345,13 @@ func funcCallArgs(fn *Function, bi *BinaryInfo, includeRet bool) (argFrameSize i
func escapeCheck(v *Variable, name string, g *G) error {
switch v.Kind {
case reflect.Ptr:
w := v.maybeDereference()
var w *Variable
if len(v.Children) == 1 {
// this branch is here to support pointers constructed with typecasts from ints or the '&' operator
w = &v.Children[0]
} else {
w = v.maybeDereference()
}
return escapeCheckPointer(w.Addr, name, g)
case reflect.Chan, reflect.String, reflect.Slice:
return escapeCheckPointer(v.Base, name, g)
......
......@@ -125,12 +125,12 @@ A tracepoint is a breakpoint that does not stop the execution of the program, in
See also: "help on", "help cond" and "help clear"`},
{aliases: []string{"restart", "r"}, cmdFn: restart, helpMsg: `Restart process.
restart [checkpoint]
restart [-noargs] newargv...
restart [checkpoint]
restart [-noargs] newargv...
For recorded processes restarts from the start or from the specified
checkpoint. For normal processes restarts the process, optionally changing
the arguments. With -noargs, the process starts with an empty commandline.
For recorded processes restarts from the start or from the specified
checkpoint. For normal processes restarts the process, optionally changing
the arguments. With -noargs, the process starts with an empty commandline.
`},
{aliases: []string{"continue", "c"}, cmdFn: c.cont, helpMsg: "Run until breakpoint or program termination."},
{aliases: []string{"step", "s"}, cmdFn: c.step, helpMsg: "Single step through program."},
......@@ -139,6 +139,8 @@ See also: "help on", "help cond" and "help clear"`},
{aliases: []string{"stepout"}, cmdFn: c.stepout, helpMsg: "Step out of the current function."},
{aliases: []string{"call"}, cmdFn: c.call, helpMsg: `Resumes process, injecting a function call (EXPERIMENTAL!!!)
call [-unsafe] <function call expression>
Current limitations:
- only pointers to stack-allocated objects can be passed as argument.
- only some automatic type conversions are supported.
......@@ -259,8 +261,8 @@ Show source around current point or provided linespec.`},
},
helpMsg: `Set the current frame, or execute command on a different frame.
frame <m>
frame <m> <command>
frame <m>
frame <m> <command>
The first form sets frame used by subsequent commands such as "print" or "set".
The second form runs the command on the given frame.`},
......@@ -270,8 +272,8 @@ The second form runs the command on the given frame.`},
},
helpMsg: `Move the current frame up.
up [<m>]
up [<m>] <command>
up [<m>]
up [<m>] <command>
Move the current frame up by <m>. The second form runs the command on the given frame.`},
{aliases: []string{"down"},
......@@ -280,8 +282,8 @@ Move the current frame up by <m>. The second form runs the command on the given
},
helpMsg: `Move the current frame down.
down [<m>]
down [<m>] <command>
down [<m>]
down [<m>] <command>
Move the current frame down by <m>. The second form runs the command on the given frame.`},
{aliases: []string{"source"}, cmdFn: c.sourceCommand, helpMsg: `Executes a file containing a list of delve commands
......@@ -980,7 +982,13 @@ func (c *Commands) call(t *Term, ctx callContext, args string) error {
if err := scopePrefixSwitch(t, ctx); err != nil {
return err
}
state, err := exitedToError(t.client.Call(args))
const unsafePrefix = "-unsafe "
unsafe := false
if strings.HasPrefix(args, unsafePrefix) {
unsafe = true
args = args[len(unsafePrefix):]
}
state, err := exitedToError(t.client.Call(args, unsafe))
c.frame = 0
if err != nil {
printcontextNoState(t)
......
......@@ -308,6 +308,8 @@ type DebuggerCommand struct {
ReturnInfoLoadConfig *LoadConfig
// Expr is the expression argument for a Call command
Expr string `json:"expr,omitempty"`
// UnsafeCall disabled parameter escape checking for function calls
UnsafeCall bool `json:"unsafeCall,omitempty"`
}
// BreakpointInfo contains informations about the current breakpoint
......
......@@ -39,7 +39,7 @@ type Client interface {
// StepOut continues to the return address of the current function
StepOut() (*api.DebuggerState, error)
// Call resumes process execution while making a function call.
Call(expr string) (*api.DebuggerState, error)
Call(expr string, unsafe bool) (*api.DebuggerState, error)
// SingleStep will step a single cpu instruction.
StepInstruction() (*api.DebuggerState, error)
......
......@@ -565,7 +565,7 @@ func (d *Debugger) Command(command *api.DebuggerCommand) (*api.DebuggerState, er
err = proc.Continue(d.target)
case api.Call:
d.log.Debugf("function call %s", command.Expr)
err = proc.CallFunction(d.target, command.Expr, api.LoadConfigToProc(command.ReturnInfoLoadConfig))
err = proc.CallFunction(d.target, command.Expr, api.LoadConfigToProc(command.ReturnInfoLoadConfig), !command.UnsafeCall)
case api.Rewind:
d.log.Debug("rewinding")
if err := d.target.Direction(proc.Backward); err != nil {
......
......@@ -148,9 +148,9 @@ func (c *RPCClient) StepOut() (*api.DebuggerState, error) {
return &out.State, err
}
func (c *RPCClient) Call(expr string) (*api.DebuggerState, error) {
func (c *RPCClient) Call(expr string, unsafe bool) (*api.DebuggerState, error) {
var out CommandOut
err := c.call("Command", &api.DebuggerCommand{Name: api.Call, ReturnInfoLoadConfig: c.retValLoadCfg, Expr: expr}, &out)
err := c.call("Command", &api.DebuggerCommand{Name: api.Call, ReturnInfoLoadConfig: c.retValLoadCfg, Expr: expr, UnsafeCall: unsafe}, &out)
return &out.State, err
}
......
......@@ -1525,7 +1525,7 @@ func TestClientServerFunctionCall(t *testing.T) {
state := <-c.Continue()
assertNoError(state.Err, t, "Continue()")
beforeCallFn := state.CurrentThread.Function.Name()
state, err := c.Call("call1(one, two)")
state, err := c.Call("call1(one, two)", false)
assertNoError(err, t, "Call()")
t.Logf("returned to %q", state.CurrentThread.Function.Name())
if state.CurrentThread.Function.Name() != beforeCallFn {
......@@ -1564,7 +1564,7 @@ func TestClientServerFunctionCallBadPos(t *testing.T) {
state = <-c.Continue()
assertNoError(state.Err, t, "Continue()")
state, err = c.Call("main.call1(main.zero, main.zero)")
state, err = c.Call("main.call1(main.zero, main.zero)", false)
if err == nil || err.Error() != "call not at safe point" {
t.Fatalf("wrong error or no error: %v", err)
}
......@@ -1578,7 +1578,7 @@ func TestClientServerFunctionCallPanic(t *testing.T) {
c.SetReturnValuesLoadConfig(&normalLoadConfig)
state := <-c.Continue()
assertNoError(state.Err, t, "Continue()")
state, err := c.Call("callpanic()")
state, err := c.Call("callpanic()", false)
assertNoError(err, t, "Call()")
t.Logf("at: %s:%d", state.CurrentThread.File, state.CurrentThread.Line)
if state.CurrentThread.ReturnValues == nil {
......@@ -1604,7 +1604,7 @@ func TestClientServerFunctionCallStacktrace(t *testing.T) {
c.SetReturnValuesLoadConfig(&api.LoadConfig{false, 0, 2048, 0, 0})
state := <-c.Continue()
assertNoError(state.Err, t, "Continue()")
state, err := c.Call("callstacktrace()")
state, err := c.Call("callstacktrace()", false)
assertNoError(err, t, "Call()")
t.Logf("at: %s:%d", state.CurrentThread.File, state.CurrentThread.Line)
if state.CurrentThread.ReturnValues == nil {
......
......@@ -1101,7 +1101,8 @@ func TestCallFunction(t *testing.T) {
// The following set of calls was constructed using https://docs.google.com/document/d/1bMwCey-gmqZVTpRax-ESeVuZGmjwbocYs1iHplK-cjo/pub as a reference
{`a.VRcvr(1)`, []string{`:string:"1 + 3 = 4"`}, nil}, // direct call of a method with value receiver / on a value
{`a.VRcvr(1)`, []string{`:string:"1 + 3 = 4"`}, nil}, // direct call of a method with value receiver / on a value
{`a.PRcvr(2)`, []string{`:string:"2 - 3 = -1"`}, nil}, // direct call of a method with pointer receiver / on a value
{`pa.VRcvr(3)`, []string{`:string:"3 + 6 = 9"`}, nil}, // direct call of a method with value receiver / on a pointer
{`pa.PRcvr(4)`, []string{`:string:"4 - 6 = -2"`}, nil}, // direct call of a method with pointer receiver / on a pointer
......@@ -1125,8 +1126,14 @@ func TestCallFunction(t *testing.T) {
{"fn2nil()", nil, errors.New("nil pointer dereference")},
{"ga.PRcvr(2)", []string{`:string:"2 - 0 = 2"`}, nil},
{"escapeArg(&a2)", nil, errors.New("cannot use &a2 as argument pa2 in function main.escapeArg: stack object passed to escaping pointer: pa2")},
{"-unsafe escapeArg(&a2)", nil, nil}, // LEAVE THIS AS THE LAST ITEM, IT BREAKS THE TARGET PROCESS!!!
}
const unsafePrefix = "-unsafe "
withTestProcess("fncall", t, func(p proc.Process, fixture protest.Fixture) {
_, err := proc.FindFunctionLocation(p, "runtime.debugCallV1", true, 0)
if err != nil {
......@@ -1134,8 +1141,16 @@ func TestCallFunction(t *testing.T) {
}
assertNoError(proc.Continue(p), t, "Continue()")
for _, tc := range testcases {
err := proc.CallFunction(p, tc.expr, &pnormalLoadConfig)
expr := tc.expr
checkEscape := true
if strings.HasPrefix(expr, unsafePrefix) {
expr = expr[len(unsafePrefix):]
checkEscape = false
}
t.Logf("call %q", tc.expr)
err := proc.CallFunction(p, expr, &pnormalLoadConfig, checkEscape)
if tc.err != nil {
if err == nil {
t.Fatalf("call %q: expected error %q, got no error", tc.expr, tc.err.Error())
}
......@@ -1156,7 +1171,6 @@ func TestCallFunction(t *testing.T) {
retvals[i] = api.ConvertVar(retvalsVar[i])
}
t.Logf("call %q", tc.expr)
for i := range retvals {
t.Logf("\t%s = %s", retvals[i].Name, retvals[i].SinglelineString())
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册