diff --git a/_fixtures/testvariables.go b/_fixtures/testvariables.go index 96d060c3..001e8cbd 100644 --- a/_fixtures/testvariables.go +++ b/_fixtures/testvariables.go @@ -7,6 +7,12 @@ type FooBar struct { Bur string } +// same member names, different order / types +type FooBar2 struct { + Bur int + Baz string +} + func barfoo() { a1 := "bur" fmt.Println(a1) @@ -21,6 +27,7 @@ func foobar(baz string, bar FooBar) { a5 = []int{1, 2, 3, 4, 5} a6 = FooBar{Baz: 8, Bur: "word"} a7 = &FooBar{Baz: 5, Bur: "strum"} + a8 = FooBar2{Bur: 10, Baz: "feh"} neg = -1 i8 = int8(1) f32 = float32(1.2) @@ -28,7 +35,7 @@ func foobar(baz string, bar FooBar) { ) barfoo() - fmt.Println(a1, a2, a3, a4, a5, a6, a7, baz, neg, i8, f32, i32, bar) + fmt.Println(a1, a2, a3, a4, a5, a6, a7, a8, baz, neg, i8, f32, i32, bar) } func main() { diff --git a/dwarf/reader/reader.go b/dwarf/reader/reader.go index d466e48a..ef02602a 100755 --- a/dwarf/reader/reader.go +++ b/dwarf/reader/reader.go @@ -60,7 +60,44 @@ func (reader *Reader) SeekToFunction(pc uint64) (*dwarf.Entry, error) { return nil, fmt.Errorf("unable to find function context") } -// NextScopeVariable moves the reader to the next debug entry that describes a local variable +// SeekToType moves the reader to the type specified by the entry, +// optionally resolving typedefs and pointer types. If the reader is set +// to a struct type the NextMemberVariable call can be used to walk all member data +func (reader *Reader) SeekToType(entry *dwarf.Entry, resolveTypedefs bool, resolvePointerTypes bool) (*dwarf.Entry, error) { + offset, ok := entry.Val(dwarf.AttrType).(dwarf.Offset) + if !ok { + return nil, fmt.Errorf("entry does not have a type attribute") + } + + // Seek to the first type offset + reader.Seek(offset) + + // Walk the types to the base + for typeEntry, err := reader.Next(); typeEntry != nil; typeEntry, err = reader.Next() { + if err != nil { + return nil, err + } + + if typeEntry.Tag == dwarf.TagTypedef && !resolveTypedefs { + return typeEntry, nil + } + + if typeEntry.Tag == dwarf.TagPointerType && !resolvePointerTypes { + return typeEntry, nil + } + + offset, ok = typeEntry.Val(dwarf.AttrType).(dwarf.Offset) + if !ok { + return typeEntry, nil + } + + reader.Seek(offset) + } + + return nil, fmt.Errorf("no type entry found") +} + +// NextScopeVariable moves the reader to the next debug entry that describes a local variable and returns the entry func (reader *Reader) NextScopeVariable() (*dwarf.Entry, error) { for entry, err := reader.Next(); entry != nil; entry, err = reader.Next() { if err != nil { @@ -83,3 +120,27 @@ func (reader *Reader) NextScopeVariable() (*dwarf.Entry, error) { // No more items return nil, nil } + +// NextMememberVariable moves the reader to the next debug entry that describes a member variable and returns the entry +func (reader *Reader) NextMemberVariable() (*dwarf.Entry, error) { + for entry, err := reader.Next(); entry != nil; entry, err = reader.Next() { + if err != nil { + return nil, err + } + + // All member variables will be at the same depth + reader.SkipChildren() + + // End of the current depth + if entry.Tag == 0 { + break + } + + if entry.Tag == dwarf.TagMember { + return entry, nil + } + } + + // No more items + return nil, nil +} diff --git a/proctl/variables.go b/proctl/variables.go index e7fc8c35..2c606c5d 100644 --- a/proctl/variables.go +++ b/proctl/variables.go @@ -10,6 +10,7 @@ import ( "unsafe" "github.com/derekparker/delve/dwarf/op" + "github.com/derekparker/delve/dwarf/reader" ) type Variable struct { @@ -143,15 +144,27 @@ func instructionsFor(name string, dbp *DebuggedProcess, reader *dwarf.Reader, me if err != nil { return nil, err } + return instructionsForEntry(entry) +} + +func instructionsForEntry(entry *dwarf.Entry) ([]byte, error) { + if entry.Tag == dwarf.TagMember { + instructions, ok := entry.Val(dwarf.AttrDataMemberLoc).([]byte) + if !ok { + return nil, fmt.Errorf("member data has no data member location attribute") + } + // clone slice to prevent stomping on the dwarf data + return append([]byte{}, instructions...), nil + } + + // non-member instructions, ok := entry.Val(dwarf.AttrLocation).([]byte) if !ok { - instructions, ok = entry.Val(dwarf.AttrDataMemberLoc).([]byte) - if !ok { - return nil, fmt.Errorf("type assertion failed") - } - return instructions, nil + return nil, fmt.Errorf("entry has no location attribute") } - return instructions, nil + + // clone slice to prevent stomping on the dwarf data + return append([]byte{}, instructions...), nil } func executeMemberStackProgram(base, instructions []byte) (uint64, error) { @@ -314,10 +327,12 @@ func (thread *ThreadContext) EvalSymbol(name string) (*Variable, error) { return nil, err } + varName := name + memberName := "" if strings.Contains(name, ".") { idx := strings.Index(name, ".") - data := thread.Process.Dwarf - return evaluateStructMember(thread, data, reader.Reader, name[:idx], name[idx+1:]) + varName = name[:idx] + memberName = name[idx+1:] } for entry, err := reader.NextScopeVariable(); entry != nil; entry, err = reader.NextScopeVariable() { @@ -330,8 +345,11 @@ func (thread *ThreadContext) EvalSymbol(name string) (*Variable, error) { continue } - if n == name { - return thread.extractVariableFromEntry(entry) + if n == varName { + if len(memberName) == 0 { + return thread.extractVariableFromEntry(entry) + } + return thread.evaluateStructMember(entry, reader, memberName) } } @@ -375,30 +393,65 @@ func findDwarfEntry(name string, reader *dwarf.Reader, member bool) (*dwarf.Entr return nil, fmt.Errorf("could not find symbol value for %s", name) } -func evaluateStructMember(thread *ThreadContext, data *dwarf.Data, reader *dwarf.Reader, parent, member string) (*Variable, error) { - parentInstr, err := instructionsFor(parent, thread.Process, reader, false) +func (thread *ThreadContext) evaluateStructMember(parentEntry *dwarf.Entry, reader *reader.Reader, memberName string) (*Variable, error) { + parentInstr, err := instructionsForEntry(parentEntry) if err != nil { return nil, err } - memberInstr, err := instructionsFor(member, thread.Process, reader, true) - if err != nil { - return nil, err - } - reader.Seek(0) - entry, err := findDwarfEntry(member, reader, true) - if err != nil { - return nil, err - } - offset, ok := entry.Val(dwarf.AttrType).(dwarf.Offset) + + // get parent variable name + parentName, ok := parentEntry.Val(dwarf.AttrName).(string) if !ok { - return nil, fmt.Errorf("type assertion failed") + return nil, fmt.Errorf("unable to retrive variable name") } - t, err := data.Type(offset) + + // Seek reader to the type information so members can be iterated + _, err = reader.SeekToType(parentEntry, true, false) if err != nil { return nil, err } - val, err := thread.extractValue(append(parentInstr, memberInstr...), 0, t) - return &Variable{Name: strings.Join([]string{parent, member}, "."), Type: t.String(), Value: val}, nil + + // Iterate to find member by name + for memberEntry, err := reader.NextMemberVariable(); memberEntry != nil; memberEntry, err = reader.NextMemberVariable() { + if err != nil { + return nil, err + } + + name, ok := memberEntry.Val(dwarf.AttrName).(string) + if !ok { + continue + } + + if name == memberName { + + memberInstr, err := instructionsForEntry(memberEntry) + if err != nil { + return nil, err + } + + offset, ok := memberEntry.Val(dwarf.AttrType).(dwarf.Offset) + if !ok { + return nil, fmt.Errorf("type assertion failed") + } + + data := thread.Process.Dwarf + t, err := data.Type(offset) + if err != nil { + return nil, err + } + + app := append(parentInstr, memberInstr...) + + val, err := thread.extractValue(app, 0, t) + + if err != nil { + return nil, err + } + return &Variable{Name: strings.Join([]string{parentName, memberName}, "."), Type: t.String(), Value: val}, nil + } + } + + return nil, fmt.Errorf("member %s not found for %s", memberName, parentName) } // Extracts the name, type, and value of a variable from a dwarf entry diff --git a/proctl/variables_test.go b/proctl/variables_test.go index 03b2f8eb..5f9e1c41 100644 --- a/proctl/variables_test.go +++ b/proctl/variables_test.go @@ -42,16 +42,19 @@ func TestVariableEvaluation(t *testing.T) { {"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"}, } withTestProcess(executablePath, t, func(p *DebuggedProcess) { - pc, _, _ := p.GoSymTable.LineToPC(fp, 30) + pc, _, _ := p.GoSymTable.LineToPC(fp, 37) _, err := p.Break(uintptr(pc)) assertNoError(err, t, "Break() returned an error") @@ -76,7 +79,7 @@ func TestVariableFunctionScoping(t *testing.T) { } withTestProcess(executablePath, t, func(p *DebuggedProcess) { - pc, _, _ := p.GoSymTable.LineToPC(fp, 30) + pc, _, _ := p.GoSymTable.LineToPC(fp, 37) _, err := p.Break(uintptr(pc)) assertNoError(err, t, "Break() returned an error") @@ -91,7 +94,7 @@ func TestVariableFunctionScoping(t *testing.T) { assertNoError(err, t, "Unable to find variable a1") // Move scopes, a1 exists here by a2 does not - pc, _, _ = p.GoSymTable.LineToPC(fp, 12) + pc, _, _ = p.GoSymTable.LineToPC(fp, 18) _, err = p.Break(uintptr(pc)) assertNoError(err, t, "Break() returned an error") @@ -147,6 +150,7 @@ func TestLocalVariables(t *testing.T) { {"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"}, @@ -158,7 +162,7 @@ func TestLocalVariables(t *testing.T) { } withTestProcess(executablePath, t, func(p *DebuggedProcess) { - pc, _, _ := p.GoSymTable.LineToPC(fp, 30) + pc, _, _ := p.GoSymTable.LineToPC(fp, 37) _, err := p.Break(uintptr(pc)) assertNoError(err, t, "Break() returned an error")