// 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=%v\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 }