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