diff --git a/_fixtures/testvariables2.go b/_fixtures/testvariables2.go index 8d448d6f21f2560078373aeb29f3ec09df7aa652..e5ae7dafe726518302de134ee1a1da26d2dc65ac 100644 --- a/_fixtures/testvariables2.go +++ b/_fixtures/testvariables2.go @@ -246,6 +246,8 @@ func main() { var nilstruct *astruct = nil + s4 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0} + var amb1 = 1 runtime.Breakpoint() for amb1 := 0; amb1 < 10; amb1++ { @@ -253,5 +255,5 @@ func main() { } runtime.Breakpoint() - fmt.Println(i1, i2, i3, p1, amb1, s1, s3, a1, p2, p3, s2, as1, str1, f1, fn1, fn2, nilslice, nilptr, ch1, chnil, m1, mnil, m2, m3, up1, i4, i5, i6, err1, err2, errnil, iface1, iface2, ifacenil, arr1, parr, cpx1, const1, iface3, iface4, recursive1, recursive1.x, iface5, iface2fn1, iface2fn2, bencharr, benchparr, mapinf, mainMenu, b, b2, sd, anonstruct1, anonstruct2, anoniface1, anonfunc, mapanonstruct1, ifacearr, efacearr, ni8, ni16, ni32, pinf, ninf, nan, zsvmap, zsslice, zsvar, tm, errtypednil, emptyslice, emptymap, byteslice, runeslice, longstr, nilstruct) + fmt.Println(i1, i2, i3, p1, amb1, s1, s3, a1, p2, p3, s2, as1, str1, f1, fn1, fn2, nilslice, nilptr, ch1, chnil, m1, mnil, m2, m3, up1, i4, i5, i6, err1, err2, errnil, iface1, iface2, ifacenil, arr1, parr, cpx1, const1, iface3, iface4, recursive1, recursive1.x, iface5, iface2fn1, iface2fn2, bencharr, benchparr, mapinf, mainMenu, b, b2, sd, anonstruct1, anonstruct2, anoniface1, anonfunc, mapanonstruct1, ifacearr, efacearr, ni8, ni16, ni32, pinf, ninf, nan, zsvmap, zsslice, zsvar, tm, errtypednil, emptyslice, emptymap, byteslice, runeslice, longstr, nilstruct, s4) } diff --git a/pkg/dwarf/godwarf/type.go b/pkg/dwarf/godwarf/type.go index d5dd4be9c9b75d00d842fdb71e63b56a6e6c668f..f46d7ba145797917b4b29d70e385baa064e71d63 100644 --- a/pkg/dwarf/godwarf/type.go +++ b/pkg/dwarf/godwarf/type.go @@ -849,6 +849,9 @@ func readType(d *dwarf.Data, name string, r *dwarf.Reader, off dwarf.Offset, typ b = t.Type.Size() case *PtrType: b = int64(addressSize) + case *FuncType: + // on Go < 1.10 function types do not have a DW_AT_byte_size attribute. + b = int64(addressSize) } } typ.Common().ByteSize = b diff --git a/pkg/proc/eval.go b/pkg/proc/eval.go index ec1cb320b9b92ef2c9b2947276ed14160ce8cda2..059457c044111cc50c176d0bcc8c0d9d459dc1ae 100644 --- a/pkg/proc/eval.go +++ b/pkg/proc/eval.go @@ -1160,10 +1160,18 @@ func (v *Variable) asUint() (uint64, error) { return n, nil } +type typeConvErr struct { + srcType, dstType godwarf.Type +} + +func (err *typeConvErr) Error() string { + return fmt.Sprintf("can not convert value of type %s to %s", err.srcType.String(), err.dstType.String()) +} + func (v *Variable) isType(typ godwarf.Type, kind reflect.Kind) error { if v.DwarfType != nil { if typ != nil && typ.String() != v.RealType.String() { - return fmt.Errorf("can not convert value of type %s to %s", v.DwarfType.String(), typ.String()) + return &typeConvErr{v.DwarfType, typ} } return nil } diff --git a/pkg/proc/types.go b/pkg/proc/types.go index 788cf9ac8e49353c059819b2947144967f0159e8..cf28319a7cc9586b8b68fba2eb3edc241eaf9dea 100644 --- a/pkg/proc/types.go +++ b/pkg/proc/types.go @@ -1221,3 +1221,36 @@ func constructTypeForKind(kind int64, bi *BinaryInfo) (*godwarf.StructType, erro return nil, nil } } + +func dwarfToRuntimeType(bi *BinaryInfo, mem MemoryReadWriter, typ godwarf.Type) (typeAddr uint64, typeKind uint64, found bool, err error) { + rdr := bi.DwarfReader() + rdr.Seek(typ.Common().Offset) + e, err := rdr.Next() + if err != nil { + return 0, 0, false, err + } + off, ok := e.Val(godwarf.AttrGoRuntimeType).(uint64) + if !ok { + return 0, 0, false, nil + } + + if err := loadModuleData(bi, mem); err != nil { + return 0, 0, false, err + } + + //TODO(aarzilli): when we support plugins this should be the plugin + //corresponding to the shared object containing entry 'e'. + typeAddr = uint64(bi.moduleData[0].types) + off + + rtyp, err := bi.findType("runtime._type") + if err != nil { + return 0, 0, false, err + } + _type := newVariable("", uintptr(typeAddr), rtyp, bi, mem) + kindv := _type.loadFieldNamed("kind") + if kindv.Unreadable != nil || kindv.Kind != reflect.Uint { + return 0, 0, false, fmt.Errorf("unreadable interface type: %v", kindv.Unreadable) + } + typeKind, _ = constant.Uint64Val(kindv.Value) + return typeAddr, typeKind, true, nil +} diff --git a/pkg/proc/variables.go b/pkg/proc/variables.go index 390bef48648362f88b1addc527e09c67194b5a05..a5d3e5050ad67e643cea7e4eff507b8dbf5cada8 100644 --- a/pkg/proc/variables.go +++ b/pkg/proc/variables.go @@ -611,17 +611,7 @@ func (scope *EvalScope) SetVariable(name, value string) error { return err } - yv.loadValue(loadSingleValue) - - if err := yv.isType(xv.RealType, xv.Kind); err != nil { - return err - } - - if yv.Unreadable != nil { - return fmt.Errorf("Expression \"%s\" is unreadable: %v", value, yv.Unreadable) - } - - return xv.setValue(yv) + return xv.setValue(yv, value) } // LocalVariables returns all local variables from the current function scope. @@ -998,33 +988,109 @@ func (v *Variable) loadValueInternal(recurseLevel int, cfg LoadConfig) { } } -func (v *Variable) setValue(y *Variable) error { - var err error - switch v.Kind { +// setValue writes the value of srcv to dstv. +// * If srcv is a numerical literal constant and srcv is of a compatible type +// the necessary type conversion is performed. +// * If srcv is nil and dstv is of a nil'able type then dstv is nilled. +// * If srcv is the empty string and dstv is a string then dstv is set to the +// empty string. +// * If dstv is an "interface {}" and srcv is either an interface (possibly +// non-empty) or a pointer shaped type (map, channel, pointer or struct +// containing a single pointer field) the type conversion to "interface {}" +// is performed. +// * If srcv and dstv have the same type and are both addressable then the +// contents of srcv are copied byte-by-byte into dstv +func (dstv *Variable) setValue(srcv *Variable, srcExpr string) error { + srcv.loadValue(loadSingleValue) + + typerr := srcv.isType(dstv.RealType, dstv.Kind) + if _, isTypeConvErr := typerr.(*typeConvErr); isTypeConvErr { + // attempt iface -> eface and ptr-shaped -> eface conversions. + return convertToEface(srcv, dstv) + } + if typerr != nil { + return typerr + } + + if srcv.Unreadable != nil { + return fmt.Errorf("Expression \"%s\" is unreadable: %v", srcExpr, srcv.Unreadable) + } + + // Numerical types + switch dstv.Kind { case reflect.Float32, reflect.Float64: - f, _ := constant.Float64Val(y.Value) - err = v.writeFloatRaw(f, v.RealType.Size()) + f, _ := constant.Float64Val(srcv.Value) + return dstv.writeFloatRaw(f, dstv.RealType.Size()) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - n, _ := constant.Int64Val(y.Value) - err = v.writeUint(uint64(n), v.RealType.Size()) + n, _ := constant.Int64Val(srcv.Value) + return dstv.writeUint(uint64(n), dstv.RealType.Size()) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - n, _ := constant.Uint64Val(y.Value) - err = v.writeUint(n, v.RealType.Size()) + n, _ := constant.Uint64Val(srcv.Value) + return dstv.writeUint(n, dstv.RealType.Size()) case reflect.Bool: - err = v.writeBool(constant.BoolVal(y.Value)) + return dstv.writeBool(constant.BoolVal(srcv.Value)) case reflect.Complex64, reflect.Complex128: - real, _ := constant.Float64Val(constant.Real(y.Value)) - imag, _ := constant.Float64Val(constant.Imag(y.Value)) - err = v.writeComplex(real, imag, v.RealType.Size()) - default: - if t, isptr := v.RealType.(*godwarf.PtrType); isptr { - err = v.writeUint(uint64(y.Children[0].Addr), int64(t.ByteSize)) - } else { - return fmt.Errorf("can not set variables of type %s (not implemented)", v.Kind.String()) - } + real, _ := constant.Float64Val(constant.Real(srcv.Value)) + imag, _ := constant.Float64Val(constant.Imag(srcv.Value)) + return dstv.writeComplex(real, imag, dstv.RealType.Size()) } - return err + // nilling nillable variables + if srcv == nilVariable { + return dstv.writeZero() + } + + // set a string to "" + if srcv.Kind == reflect.String && srcv.Len == 0 { + return dstv.writeZero() + } + + // slice assignment (this is not handled by the writeCopy below so that + // results of a reslice operation can be used here). + if srcv.Kind == reflect.Slice { + return dstv.writeSlice(srcv.Len, srcv.Cap, srcv.Base) + } + + // allow any integer to be converted to any pointer + if t, isptr := dstv.RealType.(*godwarf.PtrType); isptr { + return dstv.writeUint(uint64(srcv.Children[0].Addr), int64(t.ByteSize)) + } + + // byte-by-byte copying for everything else, but the source must be addressable + if srcv.Addr != 0 { + return dstv.writeCopy(srcv) + } + + return fmt.Errorf("can not set variables of type %s (not implemented)", dstv.Kind.String()) +} + +// 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 +// interface or a pointer shaped variable (map, channel, pointer or struct +// containing a single pointer) +func convertToEface(srcv, dstv *Variable) error { + if dstv.RealType.String() != "interface {}" { + return &typeConvErr{srcv.DwarfType, dstv.RealType} + } + if _, isiface := srcv.RealType.(*godwarf.InterfaceType); isiface { + // iface -> eface conversion + _type, data, _ := srcv.readInterface() + if srcv.Unreadable != nil { + return srcv.Unreadable + } + _type = _type.maybeDereference() + dstv.writeEmptyInterface(uint64(_type.Addr), data) + return nil + } + typeAddr, typeKind, runtimeTypeFound, err := dwarfToRuntimeType(srcv.bi, srcv.mem, srcv.RealType) + if err != nil { + return err + } + if !runtimeTypeFound || typeKind&kindDirectIface == 0 { + return &typeConvErr{srcv.DwarfType, dstv.RealType} + } + return dstv.writeEmptyInterface(typeAddr, srcv) } func readStringInfo(mem MemoryReadWriter, arch Arch, addr uintptr) (uintptr, int64, error) { @@ -1078,13 +1144,19 @@ func readStringValue(mem MemoryReadWriter, addr uintptr, strlen int64, cfg LoadC return retstr, nil } +const ( + sliceArrayFieldName = "array" + sliceLenFieldName = "len" + sliceCapFieldName = "cap" +) + func (v *Variable) loadSliceInfo(t *godwarf.SliceType) { v.mem = cacheMemory(v.mem, v.Addr, int(t.Size())) var err error for _, f := range t.Field { switch f.Name { - case "array": + case sliceArrayFieldName: var base uint64 base, err = readUintRaw(v.mem, uintptr(int64(v.Addr)+f.ByteOffset), f.Type.Size()) if err == nil { @@ -1097,14 +1169,14 @@ func (v *Variable) loadSliceInfo(t *godwarf.SliceType) { } v.fieldType = ptrType.Type } - case "len": + case sliceLenFieldName: lstrAddr, _ := v.toField(f) lstrAddr.loadValue(loadSingleValue) err = lstrAddr.Unreadable if err == nil { v.Len, _ = constant.Int64Val(lstrAddr.Value) } - case "cap": + case sliceCapFieldName: cstrAddr, _ := v.toField(f) cstrAddr.loadValue(loadSingleValue) err = cstrAddr.Unreadable @@ -1366,6 +1438,56 @@ func (v *Variable) writeBool(value bool) error { return err } +func (v *Variable) writeZero() error { + val := make([]byte, v.RealType.Size()) + _, err := v.mem.WriteMemory(v.Addr, val) + return err +} + +// writeInterface writes the empty interface of type typeAddr and data as the data field. +func (v *Variable) writeEmptyInterface(typeAddr uint64, data *Variable) error { + dstType, dstData, _ := v.readInterface() + if v.Unreadable != nil { + return v.Unreadable + } + dstType.writeUint(typeAddr, dstType.RealType.Size()) + dstData.writeCopy(data) + return nil +} + +func (v *Variable) writeSlice(len, cap int64, base uintptr) error { + for _, f := range v.RealType.(*godwarf.SliceType).Field { + switch f.Name { + case sliceArrayFieldName: + arrv, _ := v.toField(f) + if err := arrv.writeUint(uint64(base), arrv.RealType.Size()); err != nil { + return err + } + case sliceLenFieldName: + lenv, _ := v.toField(f) + if err := lenv.writeUint(uint64(len), lenv.RealType.Size()); err != nil { + return err + } + case sliceCapFieldName: + capv, _ := v.toField(f) + if err := capv.writeUint(uint64(cap), capv.RealType.Size()); err != nil { + return err + } + } + } + return nil +} + +func (dstv *Variable) writeCopy(srcv *Variable) error { + buf := make([]byte, srcv.RealType.Size()) + _, err := srcv.mem.ReadMemory(buf, srcv.Addr) + if err != nil { + return err + } + _, err = dstv.mem.WriteMemory(dstv.Addr, buf) + return err +} + func (v *Variable) readFunctionPtr() { val := make([]byte, v.bi.Arch.PtrSize()) _, err := v.mem.ReadMemory(val, v.Addr) @@ -1674,11 +1796,7 @@ func mapEvacuated(b *Variable) bool { return true } -func (v *Variable) loadInterface(recurseLevel int, loadData bool, cfg LoadConfig) { - var _type, data *Variable - var err error - isnil := false - +func (v *Variable) readInterface() (_type, data *Variable, isnil bool) { // An interface variable is implemented either by a runtime.iface // struct or a runtime.eface struct. The difference being that empty // interfaces (i.e. "interface {}") are represented by runtime.eface @@ -1694,15 +1812,7 @@ func (v *Variable) loadInterface(recurseLevel int, loadData bool, cfg LoadConfig // // In either case the _type field is a pointer to a runtime._type struct. // - // Before go1.7 _type used to have a field named 'string' containing - // the name of the type. Since go1.7 the field has been replaced by a - // str field that contains an offset in the module data, the concrete - // type must be calculated using the str address along with the value - // of v.tab._type (v._type for empty interfaces). - // - // The following code works for both runtime.iface and runtime.eface - // and sets the go17 flag when the 'string' field can not be found - // but the str field was found + // The following code works for both runtime.iface and runtime.eface. v.mem = cacheMemory(v.mem, v.Addr, int(v.RealType.Size())) @@ -1715,6 +1825,7 @@ func (v *Variable) loadInterface(recurseLevel int, loadData bool, cfg LoadConfig tab = tab.maybeDereference() isnil = tab.Addr == 0 if !isnil { + var err error _type, err = tab.structMember("_type") if err != nil { v.Unreadable = fmt.Errorf("invalid interface type: %v", err) @@ -1723,14 +1834,16 @@ func (v *Variable) loadInterface(recurseLevel int, loadData bool, cfg LoadConfig } case "_type": // for runtime.eface _type, _ = v.toField(f) - _type = _type.maybeDereference() - isnil = _type.Addr == 0 - if !isnil { - } + isnil = _type.maybeDereference().Addr == 0 case "data": data, _ = v.toField(f) } } + return +} + +func (v *Variable) loadInterface(recurseLevel int, loadData bool, cfg LoadConfig) { + _type, data, isnil := v.readInterface() if isnil { // interface to nil diff --git a/service/test/variables_test.go b/service/test/variables_test.go index cb4d7c08f965a80ab34998bccc28a27fcc8d7bcb..dba327a5be73ffd358347b695f9141104dab3c30 100644 --- a/service/test/variables_test.go +++ b/service/test/variables_test.go @@ -216,6 +216,54 @@ func TestVariableEvaluation(t *testing.T) { }) } +func TestSetVariable(t *testing.T) { + var testcases = []struct { + name string + typ string // type of + startVal string // original value of + expr string + finalVal string // new value of after executing = + }{ + {"b.ptr", "*main.A", "*main.A {val: 1337}", "nil", "*main.A nil"}, + {"m2", "map[int]*main.astruct", "map[int]*main.astruct [1: *{A: 10, B: 11}, ]", "nil", "map[int]*main.astruct nil"}, + {"fn1", "main.functype", "main.afunc", "nil", "nil"}, + {"ch1", "chan int", "chan int 4/10", "nil", "chan int nil"}, + {"s2", "[]main.astruct", "[]main.astruct len: 8, cap: 8, [{A: 1, B: 2},{A: 3, B: 4},{A: 5, B: 6},{A: 7, B: 8},{A: 9, B: 10},{A: 11, B: 12},{A: 13, B: 14},{A: 15, B: 16}]", "nil", "[]main.astruct len: 0, cap: 0, nil"}, + {"err1", "error", "error(*main.astruct) *{A: 1, B: 2}", "nil", "error nil"}, + {"s1[0]", "string", `"one"`, `""`, `""`}, + {"as1", "main.astruct", "main.astruct {A: 1, B: 1}", `m1["Malone"]`, "main.astruct {A: 2, B: 3}"}, + + {"iface1", "interface {}", "interface {}(*main.astruct) *{A: 1, B: 2}", "nil", "interface {} nil"}, + {"iface1", "interface {}", "interface {} nil", "iface2", "interface {}(string) \"test\""}, + {"iface1", "interface {}", "interface {}(string) \"test\"", "parr", "interface {}(*[4]int) *[0,1,2,3]"}, + + {"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]"}, + } + + withTestProcess("testvariables2", t, func(p proc.Process, fixture protest.Fixture) { + assertNoError(proc.Continue(p), t, "Continue()") + + for _, tc := range testcases { + if tc.name == "iface1" && tc.expr == "parr" { + if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 11) { + // conversion pointer -> eface not supported prior to Go 1.11 + continue + } + } + variable, err := evalVariable(p, tc.name, pnormalLoadConfig) + assertNoError(err, t, "EvalVariable()") + assertVariable(t, variable, varTest{tc.name, true, tc.startVal, "", tc.typ, nil}) + + assertNoError(setVariable(p, tc.name, tc.expr), t, "SetVariable()") + + variable, err = evalVariable(p, tc.name, pnormalLoadConfig) + assertNoError(err, t, "EvalVariable()") + assertVariable(t, variable, varTest{tc.name, true, tc.finalVal, "", tc.typ, nil}) + } + }) +} + func TestVariableEvaluationShort(t *testing.T) { testcases := []varTest{ {"a1", true, "\"foofoofoofoofoofoo\"", "", "string", nil},