lineinfo.go raw

   1  package main
   2  
   3  import (
   4  	"debug/dwarf"
   5  	"debug/elf"
   6  	"fmt"
   7  	"os"
   8  	"path/filepath"
   9  	"sort"
  10  	"strings"
  11  )
  12  
  13  func extractLineInfo(outdir string) error {
  14  	objPath := filepath.Join(outdir, "module.O0.o")
  15  	nFuncs, err := extractFunctionLineInfo(objPath, outdir)
  16  	if err != nil {
  17  		return fmt.Errorf("lineinfo: %w", err)
  18  	}
  19  	fmt.Printf("  LINEINFO: %d functions\n", nFuncs)
  20  	return nil
  21  }
  22  
  23  func extractFunctionLineInfo(objPath, outdir string) (int, error) {
  24  	segDir := filepath.Join(outdir, "lineinfo")
  25  	if err := os.MkdirAll(segDir, 0755); err != nil {
  26  		return 0, err
  27  	}
  28  
  29  	f, err := elf.Open(objPath)
  30  	if err != nil {
  31  		return 0, fmt.Errorf("elf open: %w", err)
  32  	}
  33  	defer f.Close()
  34  
  35  	syms, err := f.Symbols()
  36  	if err != nil {
  37  		return 0, fmt.Errorf("elf symbols: %w", err)
  38  	}
  39  
  40  	type funcSym struct {
  41  		name   string
  42  		offset uint64
  43  		size   uint64
  44  	}
  45  	var funcs []funcSym
  46  	for _, sym := range syms {
  47  		if elf.ST_TYPE(sym.Info) != elf.STT_FUNC || sym.Size == 0 {
  48  			continue
  49  		}
  50  		if sym.Section == elf.SHN_UNDEF || int(sym.Section) >= len(f.Sections) {
  51  			continue
  52  		}
  53  		if f.Sections[sym.Section].Name != ".text" {
  54  			continue
  55  		}
  56  		funcs = append(funcs, funcSym{name: sym.Name, offset: sym.Value, size: sym.Size})
  57  	}
  58  
  59  	sort.Slice(funcs, func(i, j int) bool { return funcs[i].offset < funcs[j].offset })
  60  
  61  	dw, err := f.DWARF()
  62  	if err != nil {
  63  		return 0, fmt.Errorf("dwarf: %w", err)
  64  	}
  65  
  66  	reader := dw.Reader()
  67  	var lineEntries []dwarf.LineEntry
  68  	for {
  69  		entry, err := reader.Next()
  70  		if err != nil {
  71  			return 0, fmt.Errorf("dwarf read: %w", err)
  72  		}
  73  		if entry == nil {
  74  			break
  75  		}
  76  		if entry.Tag != dwarf.TagCompileUnit {
  77  			continue
  78  		}
  79  		lr, err := dw.LineReader(entry)
  80  		if err != nil || lr == nil {
  81  			continue
  82  		}
  83  		var le dwarf.LineEntry
  84  		for {
  85  			err := lr.Next(&le)
  86  			if err != nil {
  87  				break
  88  			}
  89  			lineEntries = append(lineEntries, le)
  90  		}
  91  	}
  92  
  93  	sort.Slice(lineEntries, func(i, j int) bool {
  94  		return lineEntries[i].Address < lineEntries[j].Address
  95  	})
  96  
  97  	n := 0
  98  	for _, fn := range funcs {
  99  		start := fn.offset
 100  		end := fn.offset + fn.size
 101  
 102  		var sb strings.Builder
 103  		for _, le := range lineEntries {
 104  			if le.Address < start || le.Address >= end {
 105  				continue
 106  			}
 107  			relOff := le.Address - start
 108  			fmt.Fprintf(&sb, "0x%04x  %s:%d\n", relOff, filepath.Base(le.File.Name), le.Line)
 109  		}
 110  
 111  		if sb.Len() == 0 {
 112  			continue
 113  		}
 114  
 115  		safeName := sanitizeIRName(fn.name)
 116  		filename := safeName + ".lineinfo"
 117  		if err := os.WriteFile(filepath.Join(segDir, filename), []byte(sb.String()), 0644); err != nil {
 118  			return 0, err
 119  		}
 120  		n++
 121  	}
 122  
 123  	return n, nil
 124  }
 125