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