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