blob: 498a27b04a62e1da42391f84380178e84c5f56bb [file]
package main
import (
"bytes"
"debug/elf"
"encoding/binary"
"flag"
"fmt"
"html/template"
"io"
"log"
"os"
"path/filepath"
"strings"
_ "embed"
)
var (
//go:embed embed/lib.h.in
embedLibH string
//go:embed embed/lib.inc.s.in
embedLibIncS string
//go:embed embed/lib_init.c.in
embedLibInitC string
//go:embed embed/lib_trampolines.S.in
embedLibTrampolinesS string
)
func fatal(v ...interface{}) {
fmt.Fprintln(os.Stderr, v...)
os.Exit(1)
}
type StackArgInfo struct {
Sret uint32
Args []StackArg
}
type StackArg struct {
Offset uint32
Size uint32
}
func ObjGetStackArgs(file *elf.File) (map[string]StackArgInfo, bool) {
sec := file.Section(".stack_args")
if sec == nil {
return nil, false
}
syms, err := file.Symbols()
if err != nil {
log.Fatal(err)
}
symtab := make(map[uint64]string)
for _, sym := range syms {
symtab[sym.Value] = sym.Name
}
info := make(map[string]StackArgInfo)
b64 := make([]byte, 8)
b32 := make([]byte, 4)
idx := uint64(0)
for idx < sec.Size {
sec.ReadAt(b64, int64(idx))
idx += 8
fn := binary.LittleEndian.Uint64(b64)
sec.ReadAt(b32, int64(idx))
idx += 4
sret := binary.LittleEndian.Uint32(b32)
sec.ReadAt(b32, int64(idx))
idx += 4
entries := binary.LittleEndian.Uint32(b32)
var args []StackArg
for i := uint32(0); i < entries; i++ {
// stack offset
sec.ReadAt(b32, int64(idx))
idx += 4
offset := binary.LittleEndian.Uint32(b32)
// size
sec.ReadAt(b32, int64(idx))
idx += 4
size := binary.LittleEndian.Uint32(b32)
args = append(args, StackArg{
Offset: offset,
Size: size,
})
}
sym := symtab[fn]
info[sym] = StackArgInfo{
Sret: sret,
Args: args,
}
}
return info, true
}
func FindDynamicSymbols(input string) []string {
var syms []string
f, err := elf.Open(input)
if err != nil {
log.Fatalf("Failed to open ELF file %s: %v", input, err)
}
defer f.Close()
symbols, err := f.DynamicSymbols()
if err != nil {
log.Fatalf("Failed to read dynamic symbols: %v", err)
}
for _, sym := range symbols {
if elf.ST_TYPE(sym.Info) == elf.STT_FUNC && elf.ST_BIND(sym.Info) == elf.STB_GLOBAL && sym.Section != elf.SHN_UNDEF {
syms = append(syms, sym.Name)
}
}
return syms
}
func ExecTemplate(w io.Writer, name string, data string, vars map[string]any, funcs template.FuncMap) {
tmpl := template.New(name)
tmpl.Funcs(funcs)
tmpl, err := tmpl.Parse(data)
if err != nil {
fatal(err)
}
err = tmpl.Execute(w, vars)
if err != nil {
fatal(err)
}
}
type Options struct {
Input string
Syms []string
Lib string
LibPrefix string
LibPath string
Dynamic bool
Embed bool
NoVerify bool
Constructor bool
Verbose bool
NoSigaltstack bool
StackArgs map[string]StackArgInfo
}
// Returns sret, nstack.
func GetStackInfo(stackArgs map[string]StackArgInfo, s string, warn bool) (int, int) {
if stackArgs == nil {
return 0, 0
}
info, ok := stackArgs[s]
if !ok {
return 0, 0
}
if info.Sret != 0 && warn {
fmt.Fprintf(os.Stderr, "warning: %s has struct return (unsupported)\n", s)
}
args := info.Args
n := 0
for _, a := range args {
n += int(a.Size)
}
if n != 0 && warn {
fmt.Fprintf(os.Stderr, "warning: %s has %d bytes of stack arguments (unsupported)\n", s, n)
}
return int(info.Sret), n
}
func GenInc(file string, opts Options) {
w, err := os.Create(file)
if err != nil {
fatal(err)
}
ExecTemplate(w, file, embedLibIncS, map[string]any{
"lib": opts.Lib,
"lib_path": opts.LibPath,
}, nil)
w.Close()
}
func GenTrampolines(file string, opts Options) {
w, err := os.Create(file)
if err != nil {
fatal(err)
}
for _, s := range opts.Syms {
GetStackInfo(opts.StackArgs, s, true)
}
ExecTemplate(w, file, embedLibTrampolinesS, map[string]any{
"lib": opts.Lib,
"lib_prefix": opts.LibPrefix,
"syms": opts.Syms,
}, map[string]any{
"n_stack_args": func(s string) int {
_, n := GetStackInfo(opts.StackArgs, s, false)
return n
},
})
w.Close()
}
func GenInit(file string, opts Options) {
w, err := os.Create(file)
if err != nil {
fatal(err)
}
embedData := ""
if opts.Embed {
data, err := os.ReadFile(opts.Input)
if err != nil {
fatal(err)
}
buf := &bytes.Buffer{}
for _, b := range data {
fmt.Fprintf(buf, "%d,", b)
}
embedData = buf.String()
}
ExecTemplate(w, file, embedLibInitC, map[string]any{
"lib": opts.Lib,
"lib_path": opts.LibPath,
"syms": opts.Syms,
"dynamic": opts.Dynamic,
"no_verify": opts.NoVerify,
"embed": opts.Embed,
"embed_data": embedData,
"constructor": opts.Constructor,
"verbose": opts.Verbose,
"no_sigaltstack": opts.NoSigaltstack,
}, nil)
w.Close()
}
func GenInitHeader(file string, lib string) {
w, err := os.Create(file)
if err != nil {
fatal(err)
}
ExecTemplate(w, file, embedLibH, map[string]any{
"lib": lib,
}, nil)
w.Close()
}
func main() {
lib := flag.String("lib", "lib", "library name for function prefixes")
libPath := flag.String("lib-path", "", "path to library executable at runtime")
genTrampolines := flag.String("gen-trampolines", "", "output file for trampolines")
genInit := flag.String("gen-init", "", "output file for initialization functions")
genInc := flag.String("gen-inc", "", "output file for .incbin file")
symbols := flag.String("symbols", "", "comma-separated list of exported symbols")
symbolsFile := flag.String("symbols-file", "", "list of symbols in a file, one line per symbol")
symPrefix := flag.String("symbols-prefix", "", "determine list of symbols based on matching a prefix")
symbolsAll := flag.Bool("symbols-all", false, "bind all dynamically exported symbols")
libPrefix := flag.String("lib-prefix", "", "prefix to put on library symbols")
embedF := flag.Bool("embed", false, "fully embed the input library into the data segment")
noVerify := flag.Bool("no-verify", false, "disable verification")
noConstructor := flag.Bool("no-constructor", false, "disable constructor for automatic initialization")
verbose := flag.Bool("verbose", false, "enable verbose logging")
noSigaltstack := flag.Bool("no-sigaltstack", false, "disable automatic sigaltstack initialization")
flag.Parse()
args := flag.Args()
if len(args) <= 0 {
fatal("no input")
}
input := args[0]
dynamic := false
if strings.HasSuffix(input, ".so") {
dynamic = true
if *embedF {
fatal("-embed is not supported with shared libraries")
}
}
var wantedSyms []string
if *symbolsFile != "" {
data, err := os.ReadFile(*symbolsFile)
if err != nil {
log.Fatal(err)
}
lines := strings.Split(string(data), "\n")
for _, l := range lines {
l = strings.TrimSpace(l)
if l != "" {
wantedSyms = append(wantedSyms, l)
}
}
}
if *symbols != "" {
wantedSyms = append(wantedSyms, strings.Split(*symbols, ",")...)
}
if (*genTrampolines != "" || *genInit != "") && !*symbolsAll && *symbols == "" && *symbolsFile == "" && *symPrefix == "" && !dynamic {
fatal("error: must provide a way to match symbols (-symbols, -symbols-file, -symbols-prefix, -symbols-all)")
}
var syms []string
var allSyms []string
if *symbolsAll || *symPrefix != "" {
allSyms = FindDynamicSymbols(input)
}
if *symbolsAll {
syms = allSyms
} else {
syms = wantedSyms
}
if *symPrefix != "" {
for _, s := range allSyms {
if strings.HasPrefix(s, *symPrefix) {
syms = append(syms, s)
}
}
}
if *libPath == "" {
*libPath = input
}
f, err := elf.Open(input)
if err != nil {
log.Fatalf("Failed to open ELF file %s: %v", input, err)
}
stackArgs, ok := ObjGetStackArgs(f)
if !ok {
fmt.Fprintln(os.Stderr, "warning: no .stack_args section found")
}
f.Close()
opts := Options{
Input: input,
Syms: syms,
Lib: *lib,
LibPrefix: *libPrefix,
LibPath: *libPath,
Dynamic: dynamic,
Embed: *embedF,
NoVerify: *noVerify,
Constructor: !*noConstructor,
Verbose: *verbose,
NoSigaltstack: *noSigaltstack,
StackArgs: stackArgs,
}
if *genTrampolines != "" {
GenTrampolines(*genTrampolines, opts)
}
if *genInit != "" {
GenInit(*genInit, opts)
GenInitHeader(filepath.Join(filepath.Dir(*genInit), *lib+".h"), *lib)
}
if *genInc != "" {
GenInc(*genInc, opts)
}
}