blob: c68edf4187fe1d7e9366f1aa8ebaa70c90aae271 [file] [log] [blame]
// 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
}