github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/link/dwtest/dwtest.go (about)

     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  
     5  package dwtest
     6  
     7  import (
     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  
    21  type 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.
    30  func (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  
    72  func (e *Examiner) DIEs() []*dwarf.Entry {
    73  	return e.dies
    74  }
    75  
    76  func indent(ilevel int) {
    77  	for i := 0; i < ilevel; i++ {
    78  		fmt.Printf("  ")
    79  	}
    80  }
    81  
    82  // For debugging new tests
    83  func (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
   104  func (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
   112  func (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'
   120  func (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'
   128  func (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
   138  func (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.
   149  func (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.
   167  func (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.
   190  func (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  }