diff --git a/Documentation/cli/expr.md b/Documentation/cli/expr.md index 573daaedd3e44c6d338c2cae2f35a43af6ebfb1b..db18a676f50a1d091e890fd5f2cb76783a86c6ea 100644 --- a/Documentation/cli/expr.md +++ b/Documentation/cli/expr.md @@ -5,7 +5,8 @@ Delve can evaluate a subset of go expression language, specifically the followin - All (binary and unary) on basic types except <-, ++ and -- - Comparison operators on any type - Type casts between numeric types -- Type casts of integer constants into any pointer type +- Type casts of integer constants into any pointer type and vice versa +- Type casts between string, []byte and []rune - Struct member access (i.e. `somevar.memberfield`) - Slicing and indexing operators on arrays, slices and strings - Map access diff --git a/_fixtures/testvariables2.go b/_fixtures/testvariables2.go index 476e0af3a6acc797e27db0733a9c84cd56363d27..e227fea8960debb9ab3dacd637f0ebbf6eee0835 100644 --- a/_fixtures/testvariables2.go +++ b/_fixtures/testvariables2.go @@ -235,6 +235,9 @@ func main() { emptyslice := []string{} emptymap := make(map[string]string) + byteslice := []byte{116, 195, 168, 115, 116} + runeslice := []rune{116, 232, 115, 116} + var amb1 = 1 runtime.Breakpoint() for amb1 := 0; amb1 < 10; amb1++ { @@ -242,5 +245,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) + 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) } diff --git a/pkg/proc/eval.go b/pkg/proc/eval.go index be92f516e73383550f033f19ae2764c475370aeb..c111962c0469e5addf5a40ddca20d6d5a1b139a7 100644 --- a/pkg/proc/eval.go +++ b/pkg/proc/eval.go @@ -26,7 +26,10 @@ func (scope *EvalScope) EvalExpression(expr string, cfg LoadConfig) (*Variable, return nil, err } - ev, err := scope.evalAST(t) + ev, err := scope.evalToplevelTypeCast(t, cfg) + if ev == nil && err == nil { + ev, err = scope.evalAST(t) + } if err != nil { return nil, err } @@ -37,6 +40,113 @@ func (scope *EvalScope) EvalExpression(expr string, cfg LoadConfig) (*Variable, return ev, nil } +// evalToplevelTypeCast implements certain type casts that we only support +// at the outermost levels of an expression. +func (scope *EvalScope) evalToplevelTypeCast(t ast.Expr, cfg LoadConfig) (*Variable, error) { + call, _ := t.(*ast.CallExpr) + if call == nil || len(call.Args) != 1 { + return nil, nil + } + targetTypeStr := exprToString(removeParen(call.Fun)) + var targetType godwarf.Type + switch targetTypeStr { + case "[]byte", "[]uint8": + targetType = fakeSliceType(&godwarf.IntType{BasicType: godwarf.BasicType{CommonType: godwarf.CommonType{ByteSize: 1, Name: "uint8"}, BitSize: 8, BitOffset: 0}}) + case "[]int32", "[]rune": + targetType = fakeSliceType(&godwarf.IntType{BasicType: godwarf.BasicType{CommonType: godwarf.CommonType{ByteSize: 1, Name: "int32"}, BitSize: 32, BitOffset: 0}}) + case "string": + var err error + targetType, err = scope.BinInfo.findType("string") + if err != nil { + return nil, err + } + default: + return nil, nil + } + + argv, err := scope.evalToplevelTypeCast(call.Args[0], cfg) + if argv == nil && err == nil { + argv, err = scope.evalAST(call.Args[0]) + } + if err != nil { + return nil, err + } + argv.loadValue(cfg) + if argv.Unreadable != nil { + return nil, argv.Unreadable + } + + v := newVariable("", 0, targetType, scope.BinInfo, scope.Mem) + v.loaded = true + + converr := fmt.Errorf("can not convert %q to %s", exprToString(call.Args[0]), targetTypeStr) + + switch targetTypeStr { + case "[]byte", "[]uint8": + if argv.Kind != reflect.String { + return nil, converr + } + for i, ch := range []byte(constant.StringVal(argv.Value)) { + e := scope.newVariable("", argv.Addr+uintptr(i), targetType.(*godwarf.SliceType).ElemType) + e.loaded = true + e.Value = constant.MakeInt64(int64(ch)) + v.Children = append(v.Children, *e) + } + v.Len = int64(len(v.Children)) + v.Cap = v.Len + return v, nil + + case "[]int32", "[]rune": + if argv.Kind != reflect.String { + return nil, converr + } + for i, ch := range constant.StringVal(argv.Value) { + e := scope.newVariable("", argv.Addr+uintptr(i), targetType.(*godwarf.SliceType).ElemType) + e.loaded = true + e.Value = constant.MakeInt64(int64(ch)) + v.Children = append(v.Children, *e) + } + v.Len = int64(len(v.Children)) + v.Cap = v.Len + return v, nil + + case "string": + if argv.Kind != reflect.Slice { + return nil, nil + } + switch elemType := argv.RealType.(*godwarf.SliceType).ElemType.(type) { + case *godwarf.UintType: + if elemType.Name != "uint8" && elemType.Name != "byte" { + return nil, nil + } + bytes := make([]byte, len(argv.Children)) + for i := range argv.Children { + n, _ := constant.Int64Val(argv.Children[i].Value) + bytes[i] = byte(n) + } + v.Value = constant.MakeString(string(bytes)) + + case *godwarf.IntType: + if elemType.Name != "int32" && elemType.Name != "rune" { + return nil, nil + } + runes := make([]rune, len(argv.Children)) + for i := range argv.Children { + n, _ := constant.Int64Val(argv.Children[i].Value) + runes[i] = rune(n) + } + v.Value = constant.MakeString(string(runes)) + + default: + return nil, nil + } + v.Len = int64(len(constant.StringVal(v.Value))) + return v, nil + } + + return nil, nil +} + func (scope *EvalScope) evalAST(t ast.Expr) (*Variable, error) { switch node := t.(type) { case *ast.CallExpr: @@ -125,6 +235,17 @@ 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 + } + return n +} + // Eval type cast expressions func (scope *EvalScope) evalTypeCast(node *ast.CallExpr) (*Variable, error) { argv, err := scope.evalAST(node.Args[0]) @@ -139,13 +260,7 @@ func (scope *EvalScope) evalTypeCast(node *ast.CallExpr) (*Variable, error) { fnnode := node.Fun // remove all enclosing parenthesis from the type name - for { - p, ok := fnnode.(*ast.ParenExpr) - if !ok { - break - } - fnnode = p.X - } + fnnode = removeParen(fnnode) styp, err := scope.BinInfo.findTypeExpr(fnnode) if err != nil { @@ -188,6 +303,9 @@ func (scope *EvalScope) evalTypeCast(node *ast.CallExpr) (*Variable, error) { x, _ := constant.Float64Val(argv.Value) v.Value = constant.MakeUint64(uint64(x)) return v, nil + case reflect.Ptr: + v.Value = constant.MakeUint64(uint64(argv.Children[0].Addr)) + return v, nil } case *godwarf.IntType: switch argv.Kind { @@ -1126,18 +1244,7 @@ func (v *Variable) reslice(low int64, high int64) (*Variable, error) { typ := v.DwarfType if _, isarr := v.DwarfType.(*godwarf.ArrayType); isarr { - typ = &godwarf.SliceType{ - StructType: godwarf.StructType{ - CommonType: godwarf.CommonType{ - ByteSize: 24, - Name: "", - }, - StructName: fmt.Sprintf("[]%s", v.fieldType.Common().Name), - Kind: "struct", - Field: nil, - }, - ElemType: v.fieldType, - } + typ = fakeSliceType(v.fieldType) } r := v.newVariable("", 0, typ) @@ -1149,3 +1256,18 @@ func (v *Variable) reslice(low int64, high int64) (*Variable, error) { return r, nil } + +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, + } +} diff --git a/pkg/proc/types.go b/pkg/proc/types.go index d2a1db8a63ed254ed5820f802c06949a131e9899..c1b0bf61b1c1a734b310a5e3ffdb74db6112593e 100644 --- a/pkg/proc/types.go +++ b/pkg/proc/types.go @@ -81,6 +81,32 @@ func (bi *BinaryInfo) findTypeExpr(expr ast.Expr) (godwarf.Type, error) { } return pointerTo(ptyp, bi.Arch), nil } + if anode, ok := expr.(*ast.ArrayType); ok { + // Byte array types (i.e. [N]byte) are only present in DWARF if they are + // used by the program, but it's convenient to make all of them available + // to the user so that they can be used to read arbitrary memory, byte by + // byte. + + alen, litlen := anode.Len.(*ast.BasicLit) + if litlen && alen.Kind == token.INT { + n, _ := strconv.Atoi(alen.Value) + switch exprToString(anode.Elt) { + case "byte", "uint8": + btyp, err := bi.findType("uint8") + if err != nil { + return nil, err + } + return &godwarf.ArrayType{ + CommonType: godwarf.CommonType{ + ReflectKind: reflect.Array, + ByteSize: int64(n), + Name: fmt.Sprintf("[%d]uint8", n)}, + Type: btyp, + StrideBitSize: 8, + Count: int64(n)}, nil + } + } + } return bi.findType(exprToString(expr)) } diff --git a/service/api/prettyprint.go b/service/api/prettyprint.go index 6614311c6e309604b79a48b023c6e5485a27e1b3..947a8328ad901180599a34aa0e5db1709c01d0f0 100644 --- a/service/api/prettyprint.go +++ b/service/api/prettyprint.go @@ -34,7 +34,7 @@ func (v *Variable) writeTo(buf io.Writer, top, newlines, includeType bool, inden return } - if !top && v.Addr == 0 { + if !top && v.Addr == 0 && v.Value == "" { if includeType && v.Type != "void" { fmt.Fprintf(buf, "%s nil", v.Type) } else { diff --git a/service/test/variables_test.go b/service/test/variables_test.go index 9c0e5260bbda17c69d73df49d5a832c2373545f7..beabe15757d046b72a98d9e9fed29cc5d838072b 100644 --- a/service/test/variables_test.go +++ b/service/test/variables_test.go @@ -714,6 +714,19 @@ func TestEvalExpression(t *testing.T) { {"emptyslice", false, `[]string len: 0, cap: 0, []`, `[]string len: 0, cap: 0, []`, "[]string", nil}, {"emptymap", false, `map[string]string []`, `map[string]string []`, "map[string]string", nil}, {"mnil", false, `map[string]main.astruct nil`, `map[string]main.astruct nil`, "map[string]main.astruct", nil}, + + // conversions between string/[]byte/[]rune (issue #548) + {"runeslice", true, `[]int32 len: 4, cap: 4, [116,232,115,116]`, `[]int32 len: 4, cap: 4, [...]`, "[]int32", nil}, + {"byteslice", true, `[]uint8 len: 5, cap: 5, [116,195,168,115,116]`, `[]uint8 len: 5, cap: 5, [...]`, "[]uint8", nil}, + {"[]byte(str1)", false, `[]uint8 len: 11, cap: 11, [48,49,50,51,52,53,54,55,56,57,48]`, `[]uint8 len: 11, cap: 11, [48,49,50,51,52,53,54,55,56,57,48]`, "[]uint8", nil}, + {"[]uint8(str1)", false, `[]uint8 len: 11, cap: 11, [48,49,50,51,52,53,54,55,56,57,48]`, `[]uint8 len: 11, cap: 11, [48,49,50,51,52,53,54,55,56,57,48]`, "[]uint8", nil}, + {"[]rune(str1)", false, `[]int32 len: 11, cap: 11, [48,49,50,51,52,53,54,55,56,57,48]`, `[]int32 len: 11, cap: 11, [48,49,50,51,52,53,54,55,56,57,48]`, "[]int32", nil}, + {"[]int32(str1)", false, `[]int32 len: 11, cap: 11, [48,49,50,51,52,53,54,55,56,57,48]`, `[]int32 len: 11, cap: 11, [48,49,50,51,52,53,54,55,56,57,48]`, "[]int32", nil}, + {"string(byteslice)", false, `"tèst"`, `""`, "string", nil}, + {"[]int32(string(byteslice))", false, `[]int32 len: 4, cap: 4, [116,232,115,116]`, `[]int32 len: 0, cap: 0, nil`, "[]int32", nil}, + {"string(runeslice)", false, `"tèst"`, `""`, "string", nil}, + {"[]byte(string(runeslice))", false, `[]uint8 len: 5, cap: 5, [116,195,168,115,116]`, `[]uint8 len: 0, cap: 0, nil`, "[]uint8", nil}, + {"*(*[5]byte)(uintptr(&byteslice[0]))", false, `[5]uint8 [116,195,168,115,116]`, `[5]uint8 [...]`, "[5]uint8", nil}, } ver, _ := goversion.Parse(runtime.Version())