1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
|
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package dwtest
import (
"debug/dwarf"
"errors"
"fmt"
"os"
)
// Helper type for supporting queries on DIEs within a DWARF
// .debug_info section. Invoke the populate() method below passing in
// a dwarf.Reader, which will read in all DIEs and keep track of
// parent/child relationships. Queries can then be made to ask for
// DIEs by name or by offset. This will hopefully reduce boilerplate
// for future test writing.
type Examiner struct {
dies []*dwarf.Entry
idxByOffset map[dwarf.Offset]int
kids map[int][]int
parent map[int]int
byname map[string][]int
}
// Populate the Examiner using the DIEs read from rdr.
func (ex *Examiner) Populate(rdr *dwarf.Reader) error {
ex.idxByOffset = make(map[dwarf.Offset]int)
ex.kids = make(map[int][]int)
ex.parent = make(map[int]int)
ex.byname = make(map[string][]int)
var nesting []int
for entry, err := rdr.Next(); entry != nil; entry, err = rdr.Next() {
if err != nil {
return err
}
if entry.Tag == 0 {
// terminator
if len(nesting) == 0 {
return errors.New("nesting stack underflow")
}
nesting = nesting[:len(nesting)-1]
continue
}
idx := len(ex.dies)
ex.dies = append(ex.dies, entry)
if _, found := ex.idxByOffset[entry.Offset]; found {
return errors.New("DIE clash on offset")
}
ex.idxByOffset[entry.Offset] = idx
if name, ok := entry.Val(dwarf.AttrName).(string); ok {
ex.byname[name] = append(ex.byname[name], idx)
}
if len(nesting) > 0 {
parent := nesting[len(nesting)-1]
ex.kids[parent] = append(ex.kids[parent], idx)
ex.parent[idx] = parent
}
if entry.Children {
nesting = append(nesting, idx)
}
}
if len(nesting) > 0 {
return errors.New("unterminated child sequence")
}
return nil
}
func (e *Examiner) DIEs() []*dwarf.Entry {
return e.dies
}
func indent(ilevel int) {
for i := 0; i < ilevel; i++ {
fmt.Printf(" ")
}
}
// For debugging new tests
func (ex *Examiner) DumpEntry(idx int, dumpKids bool, ilevel int) {
if idx >= len(ex.dies) {
fmt.Fprintf(os.Stderr, "DumpEntry: bad DIE %d: index out of range\n", idx)
return
}
entry := ex.dies[idx]
indent(ilevel)
fmt.Printf("0x%x: %v\n", idx, entry.Tag)
for _, f := range entry.Field {
indent(ilevel)
fmt.Printf("at=%v val=0x%x\n", f.Attr, f.Val)
}
if dumpKids {
ksl := ex.kids[idx]
for _, k := range ksl {
ex.DumpEntry(k, true, ilevel+2)
}
}
}
// Given a DIE offset, return the previously read dwarf.Entry, or nil
func (ex *Examiner) EntryFromOffset(off dwarf.Offset) *dwarf.Entry {
if idx, found := ex.idxByOffset[off]; found && idx != -1 {
return ex.entryFromIdx(idx)
}
return nil
}
// Return the ID that Examiner uses to refer to the DIE at offset off
func (ex *Examiner) IdxFromOffset(off dwarf.Offset) int {
if idx, found := ex.idxByOffset[off]; found {
return idx
}
return -1
}
// Return the dwarf.Entry pointer for the DIE with id 'idx'
func (ex *Examiner) entryFromIdx(idx int) *dwarf.Entry {
if idx >= len(ex.dies) || idx < 0 {
return nil
}
return ex.dies[idx]
}
// Returns a list of child entries for a die with ID 'idx'
func (ex *Examiner) Children(idx int) []*dwarf.Entry {
sl := ex.kids[idx]
ret := make([]*dwarf.Entry, len(sl))
for i, k := range sl {
ret[i] = ex.entryFromIdx(k)
}
return ret
}
// Returns parent DIE for DIE 'idx', or nil if the DIE is top level
func (ex *Examiner) Parent(idx int) *dwarf.Entry {
p, found := ex.parent[idx]
if !found {
return nil
}
return ex.entryFromIdx(p)
}
// ParentCU returns the enclosing compilation unit DIE for the DIE
// with a given index, or nil if for some reason we can't establish a
// parent.
func (ex *Examiner) ParentCU(idx int) *dwarf.Entry {
for {
parentDie := ex.Parent(idx)
if parentDie == nil {
return nil
}
if parentDie.Tag == dwarf.TagCompileUnit {
return parentDie
}
idx = ex.IdxFromOffset(parentDie.Offset)
}
}
// FileRef takes a given DIE by index and a numeric file reference
// (presumably from a decl_file or call_file attribute), looks up the
// reference in the .debug_line file table, and returns the proper
// string for it. We need to know which DIE is making the reference
// so as to find the right compilation unit.
func (ex *Examiner) FileRef(dw *dwarf.Data, dieIdx int, fileRef int64) (string, error) {
// Find the parent compilation unit DIE for the specified DIE.
cuDie := ex.ParentCU(dieIdx)
if cuDie == nil {
return "", fmt.Errorf("no parent CU DIE for DIE with idx %d?", dieIdx)
}
// Construct a line reader and then use it to get the file string.
lr, lrerr := dw.LineReader(cuDie)
if lrerr != nil {
return "", fmt.Errorf("d.LineReader: %v", lrerr)
}
files := lr.Files()
if fileRef < 0 || int(fileRef) > len(files)-1 {
return "", fmt.Errorf("Examiner.FileRef: malformed file reference %d", fileRef)
}
return files[fileRef].Name, nil
}
// Return a list of all DIEs with name 'name'. When searching for DIEs
// by name, keep in mind that the returned results will include child
// DIEs such as params/variables. For example, asking for all DIEs named
// "p" for even a small program will give you 400-500 entries.
func (ex *Examiner) Named(name string) []*dwarf.Entry {
sl := ex.byname[name]
ret := make([]*dwarf.Entry, len(sl))
for i, k := range sl {
ret[i] = ex.entryFromIdx(k)
}
return ret
}
|