proc: use (*Variable).setValue in fncall
This commit is contained in:
parent
12a3f8bb97
commit
9335c54014
@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func callstacktrace() (stacktrace string) {
|
||||
@ -27,11 +28,21 @@ func callpanic() {
|
||||
panic("callpanic panicked")
|
||||
}
|
||||
|
||||
func stringsJoin(v []string, sep string) string {
|
||||
// This is needed because strings.Join is in an optimized package and
|
||||
// because of a bug in the compiler arguments of optimized functions don't
|
||||
// have a location.
|
||||
return strings.Join(v, sep)
|
||||
}
|
||||
|
||||
var zero = 0
|
||||
|
||||
func main() {
|
||||
one, two := 1, 2
|
||||
intslice := []int{1, 2, 3}
|
||||
stringslice := []string{"one", "two", "three"}
|
||||
comma := ","
|
||||
runtime.Breakpoint()
|
||||
call1(one, two)
|
||||
fmt.Println(one, two, zero, callpanic, callstacktrace)
|
||||
fmt.Println(one, two, zero, callpanic, callstacktrace, stringsJoin, intslice, stringslice, comma)
|
||||
}
|
||||
|
@ -421,38 +421,45 @@ func (bi *BinaryInfo) loclistInit(data []byte) {
|
||||
bi.loclist.ptrSz = bi.Arch.PtrSize()
|
||||
}
|
||||
|
||||
// Location returns the location described by attribute attr of entry.
|
||||
// This will either be an int64 address or a slice of Pieces for locations
|
||||
// that don't correspond to a single memory address (registers, composite
|
||||
// locations).
|
||||
func (bi *BinaryInfo) Location(entry reader.Entry, attr dwarf.Attr, pc uint64, regs op.DwarfRegisters) (int64, []op.Piece, string, error) {
|
||||
func (bi *BinaryInfo) locationExpr(entry reader.Entry, attr dwarf.Attr, pc uint64) ([]byte, string, error) {
|
||||
a := entry.Val(attr)
|
||||
if a == nil {
|
||||
return 0, nil, "", fmt.Errorf("no location attribute %s", attr)
|
||||
return nil, "", fmt.Errorf("no location attribute %s", attr)
|
||||
}
|
||||
if instr, ok := a.([]byte); ok {
|
||||
var descr bytes.Buffer
|
||||
fmt.Fprintf(&descr, "[block] ")
|
||||
op.PrettyPrint(&descr, instr)
|
||||
addr, pieces, err := op.ExecuteStackProgram(regs, instr)
|
||||
return addr, pieces, descr.String(), err
|
||||
return instr, descr.String(), nil
|
||||
}
|
||||
off, ok := a.(int64)
|
||||
if !ok {
|
||||
return 0, nil, "", fmt.Errorf("could not interpret location attribute %s", attr)
|
||||
return nil, "", fmt.Errorf("could not interpret location attribute %s", attr)
|
||||
}
|
||||
if bi.loclist.data == nil {
|
||||
return 0, nil, "", fmt.Errorf("could not find loclist entry at %#x for address %#x (no debug_loc section found)", off, pc)
|
||||
return nil, "", fmt.Errorf("could not find loclist entry at %#x for address %#x (no debug_loc section found)", off, pc)
|
||||
}
|
||||
instr := bi.loclistEntry(off, pc)
|
||||
if instr == nil {
|
||||
return 0, nil, "", fmt.Errorf("could not find loclist entry at %#x for address %#x", off, pc)
|
||||
return nil, "", fmt.Errorf("could not find loclist entry at %#x for address %#x", off, pc)
|
||||
}
|
||||
var descr bytes.Buffer
|
||||
fmt.Fprintf(&descr, "[%#x:%#x] ", off, pc)
|
||||
op.PrettyPrint(&descr, instr)
|
||||
return instr, descr.String(), nil
|
||||
}
|
||||
|
||||
// Location returns the location described by attribute attr of entry.
|
||||
// This will either be an int64 address or a slice of Pieces for locations
|
||||
// that don't correspond to a single memory address (registers, composite
|
||||
// locations).
|
||||
func (bi *BinaryInfo) Location(entry reader.Entry, attr dwarf.Attr, pc uint64, regs op.DwarfRegisters) (int64, []op.Piece, string, error) {
|
||||
instr, descr, err := bi.locationExpr(entry, attr, pc)
|
||||
if err != nil {
|
||||
return 0, nil, "", err
|
||||
}
|
||||
addr, pieces, err := op.ExecuteStackProgram(regs, instr)
|
||||
return addr, pieces, descr.String(), err
|
||||
return addr, pieces, descr, err
|
||||
}
|
||||
|
||||
// loclistEntry returns the loclist entry in the loclist starting at off,
|
||||
|
@ -260,9 +260,9 @@ func funcCallArgFrame(fn *Function, actualArgs []*Variable, g *G, bi *BinaryInfo
|
||||
return nil, err
|
||||
}
|
||||
typ = resolveTypedef(typ)
|
||||
locprog, ok := entry.Val(dwarf.AttrLocation).([]byte)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unsupported location expression for argument %s", argname)
|
||||
locprog, _, err := bi.locationExpr(entry, dwarf.AttrLocation, fn.Entry)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not get argument location of %s: %v", argname, err)
|
||||
}
|
||||
off, _, err := op.ExecuteStackProgram(op.DwarfRegisters{CFA: CFA, FrameBase: CFA}, locprog)
|
||||
if err != nil {
|
||||
@ -295,37 +295,23 @@ func funcCallArgFrame(fn *Function, actualArgs []*Variable, g *G, bi *BinaryInfo
|
||||
|
||||
// constructs arguments frame
|
||||
argmem = make([]byte, argFrameSize)
|
||||
argmemWriter := &bufferMemoryReadWriter{argmem}
|
||||
for i := range formalArgs {
|
||||
formalArg := &formalArgs[i]
|
||||
actualArg := actualArgs[i]
|
||||
|
||||
if actualArg.Addr == 0 {
|
||||
//TODO(aarzilli): at least some of this needs to be supported
|
||||
return nil, ErrNoAddrUnsupported
|
||||
}
|
||||
|
||||
if actualArg.RealType != formalArg.typ {
|
||||
return nil, fmt.Errorf("cannot use %s (type %s) as type %s in argument to %s", actualArg.Name, actualArg.DwarfType.String(), formalArg.typ.String(), fn.Name)
|
||||
}
|
||||
|
||||
//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)
|
||||
}
|
||||
|
||||
//TODO(aarzilli): automatic type conversions
|
||||
//TODO(aarzilli): automatic wrapping in interfaces?
|
||||
//TODO(aarzilli): autmoatic wrapping in interfaces for cases not handled
|
||||
// by convertToEface.
|
||||
|
||||
buf := make([]byte, actualArg.RealType.Size())
|
||||
sz, err := actualArg.mem.ReadMemory(buf, actualArg.Addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read argument %s: %v", actualArg.Name, err)
|
||||
formalArgVar := newVariable(formalArg.name, uintptr(formalArg.off+fakeAddress), formalArg.typ, bi, argmemWriter)
|
||||
if err := formalArgVar.setValue(actualArg, actualArg.Name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if int64(sz) != actualArg.RealType.Size() {
|
||||
return nil, fmt.Errorf("short read for argument %s: %d != %d %x", actualArg.Name, sz, actualArg.RealType.Size(), buf)
|
||||
}
|
||||
|
||||
copy(argmem[formalArg.off:], buf)
|
||||
}
|
||||
|
||||
return argmem, nil
|
||||
|
@ -136,3 +136,18 @@ func DereferenceMemory(mem MemoryReadWriter) MemoryReadWriter {
|
||||
}
|
||||
return mem
|
||||
}
|
||||
|
||||
// bufferMemoryReadWriter is dummy a MemoryReadWriter backed by a []byte.
|
||||
type bufferMemoryReadWriter struct {
|
||||
buf []byte
|
||||
}
|
||||
|
||||
func (mem *bufferMemoryReadWriter) ReadMemory(buf []byte, addr uintptr) (n int, err error) {
|
||||
copy(buf, mem.buf[addr-fakeAddress:][:len(buf)])
|
||||
return len(buf), nil
|
||||
}
|
||||
|
||||
func (mem *bufferMemoryReadWriter) WriteMemory(addr uintptr, data []byte) (written int, err error) {
|
||||
copy(mem.buf[addr-fakeAddress:], data)
|
||||
return len(data), nil
|
||||
}
|
||||
|
@ -243,3 +243,14 @@ func SafeRemoveAll(dir string) {
|
||||
}
|
||||
os.Remove(dir)
|
||||
}
|
||||
|
||||
// MustSupportFunctionCalls skips this test if function calls are
|
||||
// unsupported on this backend/architecture pair.
|
||||
func MustSupportFunctionCalls(t *testing.T, testBackend string) {
|
||||
if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 11) {
|
||||
t.Skip("this version of Go does not support function calls")
|
||||
}
|
||||
if runtime.GOOS != "linux" || testBackend != "native" {
|
||||
t.Skip("this backend does not support function calls")
|
||||
}
|
||||
}
|
||||
|
@ -1066,7 +1066,7 @@ func (dstv *Variable) setValue(srcv *Variable, srcExpr string) error {
|
||||
|
||||
// convertToEface converts srcv into an "interface {}" and writes it to
|
||||
// dstv.
|
||||
// Dstv must be a variable of type "inteface {}" and srcv must either by an
|
||||
// Dstv must be a variable of type "inteface {}" and srcv must either be an
|
||||
// interface or a pointer shaped variable (map, channel, pointer or struct
|
||||
// containing a single pointer)
|
||||
func convertToEface(srcv, dstv *Variable) error {
|
||||
|
@ -1508,9 +1508,7 @@ func mustHaveDebugCalls(t *testing.T, c service.Client) {
|
||||
}
|
||||
|
||||
func TestClientServerFunctionCall(t *testing.T) {
|
||||
if runtime.GOOS != "linux" || testBackend != "native" {
|
||||
t.Skip("unsupported")
|
||||
}
|
||||
protest.MustSupportFunctionCalls(t, testBackend)
|
||||
withTestClient2("fncall", t, func(c service.Client) {
|
||||
mustHaveDebugCalls(t, c)
|
||||
c.SetReturnValuesLoadConfig(&normalLoadConfig)
|
||||
@ -1541,9 +1539,7 @@ func TestClientServerFunctionCall(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestClientServerFunctionCallBadPos(t *testing.T) {
|
||||
if runtime.GOOS != "linux" || testBackend != "native" {
|
||||
t.Skip("unsupported")
|
||||
}
|
||||
protest.MustSupportFunctionCalls(t, testBackend)
|
||||
withTestClient2("fncall", t, func(c service.Client) {
|
||||
mustHaveDebugCalls(t, c)
|
||||
loc, err := c.FindLocation(api.EvalScope{-1, 0}, "fmt/print.go:649")
|
||||
@ -1566,9 +1562,7 @@ func TestClientServerFunctionCallBadPos(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestClientServerFunctionCallPanic(t *testing.T) {
|
||||
if runtime.GOOS != "linux" || testBackend != "native" {
|
||||
t.Skip("unsupported")
|
||||
}
|
||||
protest.MustSupportFunctionCalls(t, testBackend)
|
||||
withTestClient2("fncall", t, func(c service.Client) {
|
||||
mustHaveDebugCalls(t, c)
|
||||
c.SetReturnValuesLoadConfig(&normalLoadConfig)
|
||||
@ -1594,9 +1588,7 @@ func TestClientServerFunctionCallPanic(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestClientServerFunctionCallStacktrace(t *testing.T) {
|
||||
if runtime.GOOS != "linux" || testBackend != "native" {
|
||||
t.Skip("unsupported")
|
||||
}
|
||||
protest.MustSupportFunctionCalls(t, testBackend)
|
||||
withTestClient2("fncall", t, func(c service.Client) {
|
||||
mustHaveDebugCalls(t, c)
|
||||
c.SetReturnValuesLoadConfig(&api.LoadConfig{false, 0, 2048, 0, 0})
|
||||
|
@ -1,6 +1,7 @@
|
||||
package service_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"sort"
|
||||
@ -1060,3 +1061,70 @@ func TestIssue1075(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestCallFunction(t *testing.T) {
|
||||
protest.MustSupportFunctionCalls(t, testBackend)
|
||||
|
||||
var testcases = []struct {
|
||||
expr string // call expression to evaluate
|
||||
outs []string // list of return parameters in this format: <param name>:<param type>:<param value>
|
||||
err error // if not nil should return an error
|
||||
}{
|
||||
{"call1(one, two)", []string{":int:3"}, nil},
|
||||
{"call1(one+two, 4)", []string{":int:7"}, nil},
|
||||
{"callpanic()", []string{`~panic:interface {}:interface {}(string) "callpanic panicked"`}, nil},
|
||||
{`stringsJoin(nil, "")`, []string{`:string:""`}, nil},
|
||||
{`stringsJoin(stringslice, ",")`, nil, errors.New("can not set variables of type string (not implemented)")},
|
||||
{`stringsJoin(stringslice, comma)`, []string{`:string:"one,two,three"`}, nil},
|
||||
{`stringsJoin(s1, comma)`, nil, errors.New("could not find symbol value for s1")},
|
||||
{`stringsJoin(intslice, comma)`, nil, errors.New("can not convert value of type []int to []string")},
|
||||
}
|
||||
|
||||
withTestProcess("fncall", t, func(p proc.Process, fixture protest.Fixture) {
|
||||
_, err := proc.FindFunctionLocation(p, "runtime.debugCallV1", true, 0)
|
||||
if err != nil {
|
||||
t.Skip("function calls not supported on this version of go")
|
||||
}
|
||||
assertNoError(proc.Continue(p), t, "Continue()")
|
||||
for _, tc := range testcases {
|
||||
err := proc.CallFunction(p, tc.expr, &pnormalLoadConfig)
|
||||
if tc.err != nil {
|
||||
if err == nil {
|
||||
t.Fatalf("call %q: expected error %q, got no error", tc.expr, tc.err.Error())
|
||||
}
|
||||
if tc.err.Error() != err.Error() {
|
||||
t.Fatalf("call %q: expected error %q, got %q", tc.expr, tc.err.Error(), err.Error())
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("call %q: error %q", tc.expr, err.Error())
|
||||
}
|
||||
|
||||
retvals := p.CurrentThread().Common().ReturnValues(pnormalLoadConfig)
|
||||
|
||||
if len(retvals) != len(tc.outs) {
|
||||
t.Fatalf("call %q: wrong number of return parameters", tc.expr)
|
||||
}
|
||||
|
||||
for i := range retvals {
|
||||
outfields := strings.SplitN(tc.outs[i], ":", 3)
|
||||
tgtName, tgtType, tgtValue := outfields[0], outfields[1], outfields[2]
|
||||
|
||||
if tgtName != "" && tgtName != retvals[i].Name {
|
||||
t.Fatalf("call %q output parameter %d: expected name %q, got %q", tc.expr, i, tgtName, retvals[i].Name)
|
||||
}
|
||||
|
||||
cv := api.ConvertVar(retvals[i])
|
||||
|
||||
if cv.Type != tgtType {
|
||||
t.Fatalf("call %q, output parameter %d: expected type %q, got %q", tc.expr, i, tgtType, cv.Type)
|
||||
}
|
||||
if cvs := cv.SinglelineString(); cvs != tgtValue {
|
||||
t.Fatalf("call %q, output parameter %d: expected value %q, got %q", tc.expr, i, tgtValue, cvs)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user