bin.go raw
1 package main
2
3 import (
4 "debug/elf"
5 "fmt"
6 "os"
7 "os/exec"
8 "path/filepath"
9 "sort"
10 "strings"
11 )
12
13 func extractBIN(outdir string) error {
14 for _, opt := range optLevels {
15 label := optLabel(opt)
16 nFuncs, err := emitAndExtractBIN(label, outdir)
17 if err != nil {
18 return fmt.Errorf("bin %s: %w", label, err)
19 }
20 fmt.Printf(" BIN %s: %d functions\n", label, nFuncs)
21 }
22 return nil
23 }
24
25 func emitAndExtractBIN(label, outdir string) (int, error) {
26 modIR := "module.ll"
27 if label != "" {
28 modIR = "module." + label + ".ll"
29 }
30 irPath := filepath.Join(outdir, modIR)
31 irData, err := os.ReadFile(irPath)
32 if err != nil {
33 return 0, fmt.Errorf("read IR: %w", err)
34 }
35
36 cmd := exec.Command("llc", "-filetype=obj", "-o", "-")
37 cmd.Stdin = strings.NewReader(string(irData))
38 objData, err := cmd.Output()
39 if err != nil {
40 if exitErr, ok := err.(*exec.ExitError); ok {
41 return 0, fmt.Errorf("llc obj: %s", string(exitErr.Stderr))
42 }
43 return 0, fmt.Errorf("llc obj: %w", err)
44 }
45
46 objFilename := "module.o"
47 if label != "" {
48 objFilename = "module." + label + ".o"
49 }
50 objPath := filepath.Join(outdir, objFilename)
51 if err := os.WriteFile(objPath, objData, 0644); err != nil {
52 return 0, err
53 }
54
55 return extractFunctionBIN(objPath, label, outdir)
56 }
57
58 func extractFunctionBIN(objPath, label, outdir string) (int, error) {
59 segDir := filepath.Join(outdir, "bin")
60 if err := os.MkdirAll(segDir, 0755); err != nil {
61 return 0, err
62 }
63
64 f, err := elf.Open(objPath)
65 if err != nil {
66 return 0, fmt.Errorf("elf open: %w", err)
67 }
68 defer f.Close()
69
70 syms, err := f.Symbols()
71 if err != nil {
72 return 0, fmt.Errorf("elf symbols: %w", err)
73 }
74
75 // Find .text section.
76 var textSection *elf.Section
77 for _, s := range f.Sections {
78 if s.Name == ".text" {
79 textSection = s
80 break
81 }
82 }
83 if textSection == nil {
84 return 0, fmt.Errorf("no .text section in %s", objPath)
85 }
86
87 textData, err := textSection.Data()
88 if err != nil {
89 return 0, fmt.Errorf("read .text: %w", err)
90 }
91
92 // Collect function symbols (STT_FUNC with nonzero size in .text).
93 type funcSym struct {
94 name string
95 offset uint64
96 size uint64
97 }
98 var funcs []funcSym
99 for _, sym := range syms {
100 if elf.ST_TYPE(sym.Info) != elf.STT_FUNC {
101 continue
102 }
103 if sym.Size == 0 {
104 continue
105 }
106 if sym.Section == elf.SHN_UNDEF {
107 continue
108 }
109 if int(sym.Section) >= len(f.Sections) {
110 continue
111 }
112 if f.Sections[sym.Section].Name != ".text" {
113 continue
114 }
115 // sym.Value is relative to the section in relocatable objects.
116 funcs = append(funcs, funcSym{
117 name: sym.Name,
118 offset: sym.Value,
119 size: sym.Size,
120 })
121 }
122
123 sort.Slice(funcs, func(i, j int) bool {
124 return funcs[i].offset < funcs[j].offset
125 })
126
127 n := 0
128 for _, fn := range funcs {
129 if fn.offset+fn.size > uint64(len(textData)) {
130 continue
131 }
132 bytes := textData[fn.offset : fn.offset+fn.size]
133
134 var sb strings.Builder
135 for off := 0; off < len(bytes); off += 16 {
136 end := off + 16
137 if end > len(bytes) {
138 end = len(bytes)
139 }
140 fmt.Fprintf(&sb, "%04x:", off)
141 for _, b := range bytes[off:end] {
142 fmt.Fprintf(&sb, " %02x", b)
143 }
144 sb.WriteByte('\n')
145 }
146
147 safeName := sanitizeIRName(fn.name)
148 filename := safeName + ".bin.hex"
149 if label != "" {
150 filename = safeName + "." + label + ".bin.hex"
151 }
152
153 if err := os.WriteFile(filepath.Join(segDir, filename), []byte(sb.String()), 0644); err != nil {
154 return 0, err
155 }
156 n++
157 }
158
159 return n, nil
160 }
161