Refactor member variable evaluation so it works in all cases
This commit is contained in:
parent
ed6d4049b6
commit
2c5527c6c9
@ -7,6 +7,12 @@ type FooBar struct {
|
|||||||
Bur string
|
Bur string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// same member names, different order / types
|
||||||
|
type FooBar2 struct {
|
||||||
|
Bur int
|
||||||
|
Baz string
|
||||||
|
}
|
||||||
|
|
||||||
func barfoo() {
|
func barfoo() {
|
||||||
a1 := "bur"
|
a1 := "bur"
|
||||||
fmt.Println(a1)
|
fmt.Println(a1)
|
||||||
@ -21,6 +27,7 @@ func foobar(baz string, bar FooBar) {
|
|||||||
a5 = []int{1, 2, 3, 4, 5}
|
a5 = []int{1, 2, 3, 4, 5}
|
||||||
a6 = FooBar{Baz: 8, Bur: "word"}
|
a6 = FooBar{Baz: 8, Bur: "word"}
|
||||||
a7 = &FooBar{Baz: 5, Bur: "strum"}
|
a7 = &FooBar{Baz: 5, Bur: "strum"}
|
||||||
|
a8 = FooBar2{Bur: 10, Baz: "feh"}
|
||||||
neg = -1
|
neg = -1
|
||||||
i8 = int8(1)
|
i8 = int8(1)
|
||||||
f32 = float32(1.2)
|
f32 = float32(1.2)
|
||||||
@ -28,7 +35,7 @@ func foobar(baz string, bar FooBar) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
barfoo()
|
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() {
|
func main() {
|
||||||
|
@ -60,7 +60,44 @@ func (reader *Reader) SeekToFunction(pc uint64) (*dwarf.Entry, error) {
|
|||||||
return nil, fmt.Errorf("unable to find function context")
|
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) {
|
func (reader *Reader) NextScopeVariable() (*dwarf.Entry, error) {
|
||||||
for entry, err := reader.Next(); entry != nil; entry, err = reader.Next() {
|
for entry, err := reader.Next(); entry != nil; entry, err = reader.Next() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -83,3 +120,27 @@ func (reader *Reader) NextScopeVariable() (*dwarf.Entry, error) {
|
|||||||
// No more items
|
// No more items
|
||||||
return nil, nil
|
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
|
||||||
|
}
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/derekparker/delve/dwarf/op"
|
"github.com/derekparker/delve/dwarf/op"
|
||||||
|
"github.com/derekparker/delve/dwarf/reader"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Variable struct {
|
type Variable struct {
|
||||||
@ -143,15 +144,27 @@ func instructionsFor(name string, dbp *DebuggedProcess, reader *dwarf.Reader, me
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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)
|
instructions, ok := entry.Val(dwarf.AttrLocation).([]byte)
|
||||||
if !ok {
|
if !ok {
|
||||||
instructions, ok = entry.Val(dwarf.AttrDataMemberLoc).([]byte)
|
return nil, fmt.Errorf("entry has no location attribute")
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("type assertion failed")
|
|
||||||
}
|
|
||||||
return instructions, nil
|
|
||||||
}
|
}
|
||||||
return instructions, nil
|
|
||||||
|
// clone slice to prevent stomping on the dwarf data
|
||||||
|
return append([]byte{}, instructions...), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func executeMemberStackProgram(base, instructions []byte) (uint64, error) {
|
func executeMemberStackProgram(base, instructions []byte) (uint64, error) {
|
||||||
@ -314,10 +327,12 @@ func (thread *ThreadContext) EvalSymbol(name string) (*Variable, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
varName := name
|
||||||
|
memberName := ""
|
||||||
if strings.Contains(name, ".") {
|
if strings.Contains(name, ".") {
|
||||||
idx := strings.Index(name, ".")
|
idx := strings.Index(name, ".")
|
||||||
data := thread.Process.Dwarf
|
varName = name[:idx]
|
||||||
return evaluateStructMember(thread, data, reader.Reader, name[:idx], name[idx+1:])
|
memberName = name[idx+1:]
|
||||||
}
|
}
|
||||||
|
|
||||||
for entry, err := reader.NextScopeVariable(); entry != nil; entry, err = reader.NextScopeVariable() {
|
for entry, err := reader.NextScopeVariable(); entry != nil; entry, err = reader.NextScopeVariable() {
|
||||||
@ -330,8 +345,11 @@ func (thread *ThreadContext) EvalSymbol(name string) (*Variable, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if n == name {
|
if n == varName {
|
||||||
return thread.extractVariableFromEntry(entry)
|
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)
|
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) {
|
func (thread *ThreadContext) evaluateStructMember(parentEntry *dwarf.Entry, reader *reader.Reader, memberName string) (*Variable, error) {
|
||||||
parentInstr, err := instructionsFor(parent, thread.Process, reader, false)
|
parentInstr, err := instructionsForEntry(parentEntry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
memberInstr, err := instructionsFor(member, thread.Process, reader, true)
|
|
||||||
if err != nil {
|
// get parent variable name
|
||||||
return nil, err
|
parentName, ok := parentEntry.Val(dwarf.AttrName).(string)
|
||||||
}
|
|
||||||
reader.Seek(0)
|
|
||||||
entry, err := findDwarfEntry(member, reader, true)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
offset, ok := entry.Val(dwarf.AttrType).(dwarf.Offset)
|
|
||||||
if !ok {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// Extracts the name, type, and value of a variable from a dwarf entry
|
||||||
|
@ -42,16 +42,19 @@ func TestVariableEvaluation(t *testing.T) {
|
|||||||
{"a5", "len: 5 cap: 5 [1 2 3 4 5]", "struct []int"},
|
{"a5", "len: 5 cap: 5 [1 2 3 4 5]", "struct []int"},
|
||||||
{"a6", "main.FooBar {Baz: 8, Bur: word}", "main.FooBar"},
|
{"a6", "main.FooBar {Baz: 8, Bur: word}", "main.FooBar"},
|
||||||
{"a7", "*main.FooBar {Baz: 5, Bur: strum}", "*main.FooBar"},
|
{"a7", "*main.FooBar {Baz: 5, Bur: strum}", "*main.FooBar"},
|
||||||
|
{"a8", "main.FooBar2 {Bur: 10, Baz: feh}", "main.FooBar2"},
|
||||||
{"baz", "bazburzum", "struct string"},
|
{"baz", "bazburzum", "struct string"},
|
||||||
{"neg", "-1", "int"},
|
{"neg", "-1", "int"},
|
||||||
{"i8", "1", "int8"},
|
{"i8", "1", "int8"},
|
||||||
{"f32", "1.2", "float32"},
|
{"f32", "1.2", "float32"},
|
||||||
{"a6.Baz", "8", "int"},
|
{"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"},
|
{"i32", "[2]int32 [1 2]", "[2]int32"},
|
||||||
}
|
}
|
||||||
|
|
||||||
withTestProcess(executablePath, t, func(p *DebuggedProcess) {
|
withTestProcess(executablePath, t, func(p *DebuggedProcess) {
|
||||||
pc, _, _ := p.GoSymTable.LineToPC(fp, 30)
|
pc, _, _ := p.GoSymTable.LineToPC(fp, 37)
|
||||||
|
|
||||||
_, err := p.Break(uintptr(pc))
|
_, err := p.Break(uintptr(pc))
|
||||||
assertNoError(err, t, "Break() returned an error")
|
assertNoError(err, t, "Break() returned an error")
|
||||||
@ -76,7 +79,7 @@ func TestVariableFunctionScoping(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
withTestProcess(executablePath, t, func(p *DebuggedProcess) {
|
withTestProcess(executablePath, t, func(p *DebuggedProcess) {
|
||||||
pc, _, _ := p.GoSymTable.LineToPC(fp, 30)
|
pc, _, _ := p.GoSymTable.LineToPC(fp, 37)
|
||||||
|
|
||||||
_, err := p.Break(uintptr(pc))
|
_, err := p.Break(uintptr(pc))
|
||||||
assertNoError(err, t, "Break() returned an error")
|
assertNoError(err, t, "Break() returned an error")
|
||||||
@ -91,7 +94,7 @@ func TestVariableFunctionScoping(t *testing.T) {
|
|||||||
assertNoError(err, t, "Unable to find variable a1")
|
assertNoError(err, t, "Unable to find variable a1")
|
||||||
|
|
||||||
// Move scopes, a1 exists here by a2 does not
|
// 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))
|
_, err = p.Break(uintptr(pc))
|
||||||
assertNoError(err, t, "Break() returned an error")
|
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"},
|
{"a5", "len: 5 cap: 5 [1 2 3 4 5]", "struct []int"},
|
||||||
{"a6", "main.FooBar {Baz: 8, Bur: word}", "main.FooBar"},
|
{"a6", "main.FooBar {Baz: 8, Bur: word}", "main.FooBar"},
|
||||||
{"a7", "*main.FooBar {Baz: 5, Bur: strum}", "*main.FooBar"},
|
{"a7", "*main.FooBar {Baz: 5, Bur: strum}", "*main.FooBar"},
|
||||||
|
{"a8", "main.FooBar2 {Bur: 10, Baz: feh}", "main.FooBar2"},
|
||||||
{"f32", "1.2", "float32"},
|
{"f32", "1.2", "float32"},
|
||||||
{"i32", "[2]int32 [1 2]", "[2]int32"},
|
{"i32", "[2]int32 [1 2]", "[2]int32"},
|
||||||
{"i8", "1", "int8"},
|
{"i8", "1", "int8"},
|
||||||
@ -158,7 +162,7 @@ func TestLocalVariables(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
withTestProcess(executablePath, t, func(p *DebuggedProcess) {
|
withTestProcess(executablePath, t, func(p *DebuggedProcess) {
|
||||||
pc, _, _ := p.GoSymTable.LineToPC(fp, 30)
|
pc, _, _ := p.GoSymTable.LineToPC(fp, 37)
|
||||||
|
|
||||||
_, err := p.Break(uintptr(pc))
|
_, err := p.Break(uintptr(pc))
|
||||||
assertNoError(err, t, "Break() returned an error")
|
assertNoError(err, t, "Break() returned an error")
|
||||||
|
Loading…
Reference in New Issue
Block a user