From 7c61e2a1cb7b71352ee3342079351804f46fb434 Mon Sep 17 00:00:00 2001 From: epipho Date: Thu, 1 Jan 2015 04:00:44 -0500 Subject: [PATCH] EvalSymbol supports evaluating struct members on pointers. Fixed panic when evaluating a nil pointer. --- _fixtures/testvariables.go | 3 +- proctl/variables.go | 102 ++++++++++++++++++++++++++++--------- proctl/variables_test.go | 84 +++++++++++++++++------------- 3 files changed, 130 insertions(+), 59 deletions(-) diff --git a/_fixtures/testvariables.go b/_fixtures/testvariables.go index 001e8cbd..9516a16d 100644 --- a/_fixtures/testvariables.go +++ b/_fixtures/testvariables.go @@ -28,6 +28,7 @@ func foobar(baz string, bar FooBar) { a6 = FooBar{Baz: 8, Bur: "word"} a7 = &FooBar{Baz: 5, Bur: "strum"} a8 = FooBar2{Bur: 10, Baz: "feh"} + a9 = (*FooBar)(nil) neg = -1 i8 = int8(1) f32 = float32(1.2) @@ -35,7 +36,7 @@ func foobar(baz string, bar FooBar) { ) barfoo() - fmt.Println(a1, a2, a3, a4, a5, a6, a7, a8, baz, neg, i8, f32, i32, bar) + fmt.Println(a1, a2, a3, a4, a5, a6, a7, a8, a9, baz, neg, i8, f32, i32, bar) } func main() { diff --git a/proctl/variables.go b/proctl/variables.go index 7a1aa2b1..53eb0ff5 100644 --- a/proctl/variables.go +++ b/proctl/variables.go @@ -394,7 +394,7 @@ func findDwarfEntry(name string, reader *dwarf.Reader, member bool) (*dwarf.Entr } func (thread *ThreadContext) evaluateStructMember(parentEntry *dwarf.Entry, reader *reader.Reader, memberName string) (*Variable, error) { - parentInstr, err := instructionsForEntry(parentEntry) + parentAddr, err := thread.extractVariableDataAddress(parentEntry, reader) if err != nil { return nil, err } @@ -406,7 +406,7 @@ func (thread *ThreadContext) evaluateStructMember(parentEntry *dwarf.Entry, read } // Seek reader to the type information so members can be iterated - _, err = reader.SeekToType(parentEntry, true, false) + _, err = reader.SeekToType(parentEntry, true, true) if err != nil { return nil, err } @@ -423,6 +423,11 @@ func (thread *ThreadContext) evaluateStructMember(parentEntry *dwarf.Entry, read } if name == memberName { + // nil ptr. wait until here to throw a nil pointer error to prioritize no such member error + if parentAddr == 0 { + return nil, fmt.Errorf("%s is nil", parentName) + } + memberInstr, err := instructionsForEntry(memberEntry) if err != nil { return nil, err @@ -439,8 +444,11 @@ func (thread *ThreadContext) evaluateStructMember(parentEntry *dwarf.Entry, read return nil, err } - app := append(parentInstr, memberInstr...) - val, err := thread.extractValue(app, 0, t) + baseAddr := make([]byte, 8) + binary.LittleEndian.PutUint64(baseAddr, uint64(parentAddr)) + + parentInstructions := append([]byte{op.DW_OP_addr}, baseAddr...) + val, err := thread.extractValue(append(parentInstructions, memberInstr...), 0, t) if err != nil { return nil, err } @@ -448,7 +456,7 @@ func (thread *ThreadContext) evaluateStructMember(parentEntry *dwarf.Entry, read } } - return nil, fmt.Errorf("member %s not found for %s", memberName, parentName) + return nil, fmt.Errorf("%s has no member %s", parentName, memberName) } // Extracts the name, type, and value of a variable from a dwarf entry @@ -490,27 +498,70 @@ func (thread *ThreadContext) extractVariableFromEntry(entry *dwarf.Entry) (*Vari return &Variable{Name: n, Type: t.String(), Value: val}, nil } -// Extracts the value from the instructions given in the DW_AT_location entry. -// We execute the stack program described in the DW_OP_* instruction stream, and -// then grab the value from the other processes memory. -func (thread *ThreadContext) extractValue(instructions []byte, addr int64, typ interface{}) (string, error) { - address := addr +// Execute the stack program taking into account the current stack frame +func (thread *ThreadContext) executeStackProgram(instructions []byte) (int64, error) { + regs, err := thread.Registers() + if err != nil { + return 0, err + } - if address == 0 { - regs, err := thread.Registers() + fde, err := thread.Process.FrameEntries.FDEForPC(regs.PC()) + if err != nil { + return 0, err + } + + fctx := fde.EstablishFrame(regs.PC()) + cfaOffset := fctx.CFAOffset() + int64(regs.SP()) + + address, err := op.ExecuteStackProgram(cfaOffset, instructions) + if err != nil { + return 0, err + } + return address, nil +} + +// Extracts the address of a variable, dereferencing any pointers +func (thread *ThreadContext) extractVariableDataAddress(entry *dwarf.Entry, reader *reader.Reader) (int64, error) { + instructions, err := instructionsForEntry(entry) + if err != nil { + return 0, err + } + + address, err := thread.executeStackProgram(instructions) + if err != nil { + return 0, err + } + + // dereference pointers to get down the concrete type + for typeEntry, err := reader.SeekToType(entry, true, false); typeEntry != nil; typeEntry, err = reader.SeekToType(typeEntry, true, false) { if err != nil { - return "", err + return 0, err } - fde, err := thread.Process.FrameEntries.FDEForPC(regs.PC()) + if typeEntry.Tag != dwarf.TagPointerType { + break + } + + ptraddress := uintptr(address) + + ptr, err := thread.readMemory(ptraddress, ptrsize) if err != nil { - return "", err + return 0, err } + address = int64(binary.LittleEndian.Uint64(ptr)) + } - fctx := fde.EstablishFrame(regs.PC()) - cfaOffset := fctx.CFAOffset() + int64(regs.SP()) + return address, nil +} - address, err = op.ExecuteStackProgram(cfaOffset, instructions) +// Extracts the value from the instructions given in the DW_AT_location entry. +// We execute the stack program described in the DW_OP_* instruction stream, and +// then grab the value from the other processes memory. +func (thread *ThreadContext) extractValue(instructions []byte, addr int64, typ interface{}) (string, error) { + var err error + + if addr == 0 { + addr, err = thread.executeStackProgram(instructions) if err != nil { return "", err } @@ -522,20 +573,25 @@ func (thread *ThreadContext) extractValue(instructions []byte, addr int64, typ i typ = tt.Type } - ptraddress := uintptr(address) + ptraddress := uintptr(addr) switch t := typ.(type) { case *dwarf.PtrType: ptr, err := thread.readMemory(ptraddress, ptrsize) if err != nil { return "", err } - val, err := thread.extractValue(nil, int64(binary.LittleEndian.Uint64(ptr)), t.Type) + + intaddr := int64(binary.LittleEndian.Uint64(ptr)) + if intaddr == 0 { + return fmt.Sprintf("%s nil", t.String()), nil + } + + val, err := thread.extractValue(nil, intaddr, t.Type) if err != nil { return "", err } - retstr := fmt.Sprintf("*%s", val) - return retstr, nil + return fmt.Sprintf("*%s", val), nil case *dwarf.StructType: switch t.StructName { case "string": @@ -547,7 +603,7 @@ func (thread *ThreadContext) extractValue(instructions []byte, addr int64, typ i // the value of all the members of the struct. fields := make([]string, 0, len(t.Field)) for _, field := range t.Field { - val, err := thread.extractValue(nil, field.ByteOffset+address, field.Type) + val, err := thread.extractValue(nil, field.ByteOffset+addr, field.Type) if err != nil { return "", err } diff --git a/proctl/variables_test.go b/proctl/variables_test.go index 5214dfde..47003598 100644 --- a/proctl/variables_test.go +++ b/proctl/variables_test.go @@ -1,6 +1,7 @@ package proctl import ( + "errors" "path/filepath" "sort" "testing" @@ -10,6 +11,7 @@ type varTest struct { name string value string varType string + err error } func assertVariable(t *testing.T, variable *Variable, expected varTest) { @@ -35,26 +37,31 @@ func TestVariableEvaluation(t *testing.T) { } testcases := []varTest{ - {"a1", "foo", "struct string"}, - {"a2", "6", "int"}, - {"a3", "7.23", "float64"}, - {"a4", "[2]int [1 2]", "[2]int"}, - {"a5", "len: 5 cap: 5 [1 2 3 4 5]", "struct []int"}, - {"a6", "main.FooBar {Baz: 8, Bur: word}", "main.FooBar"}, - {"a7", "*main.FooBar {Baz: 5, Bur: strum}", "*main.FooBar"}, - {"a8", "main.FooBar2 {Bur: 10, Baz: feh}", "main.FooBar2"}, - {"baz", "bazburzum", "struct string"}, - {"neg", "-1", "int"}, - {"i8", "1", "int8"}, - {"f32", "1.2", "float32"}, - {"a6.Baz", "8", "int"}, - {"a8.Baz", "feh", "struct string"}, - {"a8", "main.FooBar2 {Bur: 10, Baz: feh}", "main.FooBar2"}, // reread variable after member - {"i32", "[2]int32 [1 2]", "[2]int32"}, + {"a1", "foo", "struct string", nil}, + {"a2", "6", "int", nil}, + {"a3", "7.23", "float64", nil}, + {"a4", "[2]int [1 2]", "[2]int", nil}, + {"a5", "len: 5 cap: 5 [1 2 3 4 5]", "struct []int", nil}, + {"a6", "main.FooBar {Baz: 8, Bur: word}", "main.FooBar", nil}, + {"a7", "*main.FooBar {Baz: 5, Bur: strum}", "*main.FooBar", nil}, + {"a8", "main.FooBar2 {Bur: 10, Baz: feh}", "main.FooBar2", nil}, + {"a9", "*main.FooBar nil", "*main.FooBar", nil}, + {"baz", "bazburzum", "struct string", nil}, + {"neg", "-1", "int", nil}, + {"i8", "1", "int8", nil}, + {"f32", "1.2", "float32", nil}, + {"a6.Baz", "8", "int", nil}, + {"a7.Baz", "5", "int", nil}, + {"a8.Baz", "feh", "struct string", nil}, + {"a9.Baz", "nil", "int", errors.New("a9 is nil")}, + {"a9.NonExistent", "nil", "int", errors.New("a9 has no member NonExistent")}, + {"a8", "main.FooBar2 {Bur: 10, Baz: feh}", "main.FooBar2", nil}, // reread variable after member + {"i32", "[2]int32 [1 2]", "[2]int32", nil}, + {"NonExistent", "", "", errors.New("could not find symbol value for NonExistent")}, } withTestProcess(executablePath, t, func(p *DebuggedProcess) { - pc, _, _ := p.GoSymTable.LineToPC(fp, 37) + pc, _, _ := p.GoSymTable.LineToPC(fp, 38) _, err := p.Break(pc) assertNoError(err, t, "Break() returned an error") @@ -64,8 +71,14 @@ func TestVariableEvaluation(t *testing.T) { for _, tc := range testcases { variable, err := p.EvalSymbol(tc.name) - assertNoError(err, t, "EvalSymbol() returned an error") - assertVariable(t, variable, tc) + if tc.err == nil { + assertNoError(err, t, "EvalSymbol() returned an error") + assertVariable(t, variable, tc) + } else { + if tc.err.Error() != err.Error() { + t.Fatalf("Unexpected error. Expected %s got %s", tc.err.Error(), err.Error()) + } + } } }) } @@ -79,7 +92,7 @@ func TestVariableFunctionScoping(t *testing.T) { } withTestProcess(executablePath, t, func(p *DebuggedProcess) { - pc, _, _ := p.GoSymTable.LineToPC(fp, 37) + pc, _, _ := p.GoSymTable.LineToPC(fp, 38) _, err := p.Break(pc) assertNoError(err, t, "Break() returned an error") @@ -143,26 +156,27 @@ func TestLocalVariables(t *testing.T) { }{ {(*ThreadContext).LocalVariables, []varTest{ - {"a1", "foo", "struct string"}, - {"a2", "6", "int"}, - {"a3", "7.23", "float64"}, - {"a4", "[2]int [1 2]", "[2]int"}, - {"a5", "len: 5 cap: 5 [1 2 3 4 5]", "struct []int"}, - {"a6", "main.FooBar {Baz: 8, Bur: word}", "main.FooBar"}, - {"a7", "*main.FooBar {Baz: 5, Bur: strum}", "*main.FooBar"}, - {"a8", "main.FooBar2 {Bur: 10, Baz: feh}", "main.FooBar2"}, - {"f32", "1.2", "float32"}, - {"i32", "[2]int32 [1 2]", "[2]int32"}, - {"i8", "1", "int8"}, - {"neg", "-1", "int"}}}, + {"a1", "foo", "struct string", nil}, + {"a2", "6", "int", nil}, + {"a3", "7.23", "float64", nil}, + {"a4", "[2]int [1 2]", "[2]int", nil}, + {"a5", "len: 5 cap: 5 [1 2 3 4 5]", "struct []int", nil}, + {"a6", "main.FooBar {Baz: 8, Bur: word}", "main.FooBar", nil}, + {"a7", "*main.FooBar {Baz: 5, Bur: strum}", "*main.FooBar", nil}, + {"a8", "main.FooBar2 {Bur: 10, Baz: feh}", "main.FooBar2", nil}, + {"a9", "*main.FooBar nil", "*main.FooBar", nil}, + {"f32", "1.2", "float32", nil}, + {"i32", "[2]int32 [1 2]", "[2]int32", nil}, + {"i8", "1", "int8", nil}, + {"neg", "-1", "int", nil}}}, {(*ThreadContext).FunctionArguments, []varTest{ - {"bar", "main.FooBar {Baz: 10, Bur: lorem}", "main.FooBar"}, - {"baz", "bazburzum", "struct string"}}}, + {"bar", "main.FooBar {Baz: 10, Bur: lorem}", "main.FooBar", nil}, + {"baz", "bazburzum", "struct string", nil}}}, } withTestProcess(executablePath, t, func(p *DebuggedProcess) { - pc, _, _ := p.GoSymTable.LineToPC(fp, 37) + pc, _, _ := p.GoSymTable.LineToPC(fp, 38) _, err := p.Break(pc) assertNoError(err, t, "Break() returned an error") -- GitLab