sorcery-go/pkg/toolchain/btc.go

710 lines
27 KiB
Go
Executable File

// Package toolchain provides BTC.sh (Build Tool Chain) integration for
// sorcery-go. BTC.sh forges a sovereign, forensically-stamped GCC toolchain
// whose provenance can be verified through ELF notes and extended filesystem
// attributes.
//
// BTC 0.4.0+ supports multi-architecture cross-compilation:
//
// x86_64: haswell, haswell-ep, skylake, skylake-x, skylake-server,
// znver1, znver2, znver3, znver4,
// apu-zn1, apu-zn2, apu-zn3, apu-zn4,
// atom-silvermont, atom-goldmont, atom-tremont, atom-sierraforest
// mipsel: mipselr2 (MIPS32R2 LE, o32 ABI, musl)
// arm: armv7 (Cortex-A NEON hard-float, musl)
// tilegx: tilegx (Tilera TILE-Gx72, musl)
//
// This file implements probing, environment setup, stamp verification, and
// binary stamping that mirrors BTC.sh's own f_stamp_binary and verification
// routines.
package toolchain
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"regexp"
"runtime"
"sort"
"strings"
"dcos.net/sorcery-go/pkg/config"
)
// BTCStamp holds the forensic identification data extracted from a BTC-built
// binary. BTC.sh stamps every compiled artifact with two mechanisms:
//
// 1. An ELF NOTE section named ".note.BTC" containing human-readable fields.
// 2. Extended attributes (xattr): user.btc.identity and user.btc.hash.
type BTCStamp struct {
Org string // Origin organization (e.g., "dcos.net")
Kernel string // Kernel version at build time
Arch string // Target architecture / target ID
Label string // SYS_LABEL — unique forge identifier
Forge string // Build step name (e.g., "stage2-gcc")
Identity string // xattr user.btc.identity value
Hash string // xattr user.btc.hash (SHA-256 of the binary)
HasNote bool // .note.BTC section present
HasXAttr bool // xattr identity present
Valid bool // true if Hash matches the binary's actual SHA-256
}
// BTCForge represents a probed BTC.sh installation with its golden image.
// BTC 0.4.0+ populates CrossMode, TargetID, TargetTriple, and CLib from
// the manifest JSON sidecar written by BTC.sh.
type BTCForge struct {
Config *config.Config
SYSLabel string // auto-detected or from config
GoldenImage string // path to the {SYS_LABEL}-toolchain-golden.tar.xz
Available bool // true if BTC.sh and golden image are usable
TargetArch string // base architecture (x86_64, arm, mipsel, tilegx)
ExtractedDir string // path where the golden image is extracted (if any)
// BTC 0.4.0 multi-arch fields (populated from manifest JSON)
CrossMode bool // true if this is a cross-compiled toolchain
TargetID string // BTC target identifier (haswell, znver3, armv7, etc.)
TargetTriple string // GCC target triple (e.g., arm-dcosnet-linux-musleabihf)
TargetMarch string // -march value (e.g., armv7-a, mips32r2, tilegx)
CLib string // C library: "glibc" or "musl"
Family string // architecture family (intel, amd, mips, arm, tile)
Manifest *BTCManifest
}
// BTCManifest is the JSON structure of the {SYS_LABEL}-manifest.json
// sidecar written by BTC.sh 0.4.0+. It contains structured metadata
// that is more reliable than parsing filenames.
type BTCManifest struct {
BTCVersion string `json:"btc_version"`
Mode string `json:"mode"`
CrossMode bool `json:"cross_mode"`
SysLabel string `json:"sys_label"`
TargetID string `json:"target_id"`
TargetArch string `json:"target_arch"`
TargetCPU string `json:"target_cpu"`
TargetMarch string `json:"target_march"`
TargetTriple string `json:"target_triple"`
HostArch string `json:"host_arch"`
ISATag string `json:"isa_tag"`
OptTag string `json:"opt_tag"`
ABI string `json:"abi"`
CLib string `json:"clib"`
Endian string `json:"endian"`
Family string `json:"family"`
Description string `json:"description"`
KernelMin string `json:"kernel_min"`
Kernel string `json:"kernel"`
Binutils string `json:"binutils"`
GCC string `json:"gcc"`
GLibc string `json:"glibc"`
Musl string `json:"musl"`
Libxcrypt string `json:"libxcrypt"`
GoldenImage string `json:"golden_image"`
CFlags string `json:"cflags"`
Ldflags string `json:"ldflags"`
}
// ISA flag table — maps ISA tag (uppercase) to compiler flags.
// Table-driven lookup replaces if/else chains (SEI CERT CTR50-JP).
var isaFlags = map[string]string{
"AVX2": " -mavx2",
"AVX512": " -mavx512f -mavx512dq -mavx512vl -mavx512bw",
"SSE4_2": " -msse4.2",
"NEON": " -mfpu=neon -mfloat-abi=hard",
"MIPS32": "",
"TILE": "",
}
// Probe checks whether BTC.sh and its golden image exist on disk and returns
// a BTCForge struct describing what was found. If the config supplies a
// BTCSYSLabel it is used directly; otherwise the label is parsed from the
// golden image filename.
//
// For BTC 0.4.0+, the manifest JSON sidecar is loaded to populate
// CrossMode, TargetID, TargetTriple, CLib, and other structured fields.
func Probe(cfg *config.Config) *BTCForge {
forge := &BTCForge{
Config: cfg,
}
// Check BTC.sh exists and is executable.
info, err := os.Stat(cfg.BTCPath)
if err != nil || info.IsDir() {
return forge
}
if info.Mode()&0111 == 0 {
return forge
}
// Check BTCRoot for a golden image matching *-toolchain-golden.tar.xz.
pattern := filepath.Join(cfg.BTCRoot, "*-toolchain-golden.tar.xz")
matches, err := filepath.Glob(pattern)
if err != nil || len(matches) == 0 {
return forge
}
// Use the first match. If multiple golden images exist the caller can
// disambiguate via BTCSYSLabel.
goldenPath := matches[0]
base := filepath.Base(goldenPath)
sysLabel := parseGoldenLabel(base)
// If the config provides a label, verify it matches or use the config
// value and search for the corresponding image.
if cfg.BTCSYSLabel != "" {
candidate := filepath.Join(cfg.BTCRoot, cfg.BTCSYSLabel+"-toolchain-golden.tar.xz")
if _, err := os.Stat(candidate); err == nil {
sysLabel = cfg.BTCSYSLabel
goldenPath = candidate
} else {
// Config label does not match any file; use the discovered one.
sysLabel = cfg.BTCSYSLabel
}
}
forge.SYSLabel = sysLabel
forge.GoldenImage = goldenPath
forge.Available = true
forge.TargetArch = cfg.HostArch
if forge.TargetArch == "" {
forge.TargetArch = hostArch()
}
// Try to load the manifest for richer metadata (BTC 0.4.0+).
if manifest := loadManifest(cfg.BTCRoot, sysLabel); manifest != nil {
forge.Manifest = manifest
forge.CrossMode = manifest.CrossMode
forge.TargetID = manifest.TargetID
forge.TargetTriple = manifest.TargetTriple
forge.TargetMarch = manifest.TargetMarch
forge.CLib = manifest.CLib
forge.Family = manifest.Family
forge.TargetArch = manifest.TargetArch
}
return forge
}
// loadManifest reads a BTC manifest JSON sidecar if it exists.
func loadManifest(btcRoot, sysLabel string) *BTCManifest {
manifestPath := filepath.Join(btcRoot, sysLabel+"-manifest.json")
data, err := os.ReadFile(manifestPath)
if err != nil {
return nil
}
var m BTCManifest
if err := json.Unmarshal(data, &m); err != nil {
log.Printf("btc: warning: failed to parse manifest %s: %v", manifestPath, err)
return nil
}
return &m
}
// ListTargets scans the BTC root for all available golden images and
// returns a slice of BTCForge structs, one per found toolchain. This
// is useful for the WebUI "Toolchain Lab" view to show operators which
// cross-compilers are available.
func ListTargets(btcRoot string) []*BTCForge {
pattern := filepath.Join(btcRoot, "*-toolchain-golden.tar.xz")
matches, err := filepath.Glob(pattern)
if err != nil {
return nil
}
sort.Strings(matches)
forges := make([]*BTCForge, 0, len(matches))
for _, goldenPath := range matches {
base := filepath.Base(goldenPath)
sysLabel := parseGoldenLabel(base)
if sysLabel == "" {
continue
}
forge := &BTCForge{
SYSLabel: sysLabel,
GoldenImage: goldenPath,
Available: true,
}
// Load manifest for structured metadata.
if manifest := loadManifest(btcRoot, sysLabel); manifest != nil {
forge.Manifest = manifest
forge.CrossMode = manifest.CrossMode
forge.TargetID = manifest.TargetID
forge.TargetTriple = manifest.TargetTriple
forge.TargetMarch = manifest.TargetMarch
forge.CLib = manifest.CLib
forge.Family = manifest.Family
forge.TargetArch = manifest.TargetArch
} else {
// Legacy 0.3.x: parse from SYS_LABEL.
forge.TargetArch = hostArch()
forge.CLib = "glibc"
}
forges = append(forges, forge)
}
return forges
}
// parseGoldenLabel extracts the SYS_LABEL from a golden image filename of
// the form "{SYS_LABEL}-toolchain-golden.tar.xz".
func parseGoldenLabel(filename string) string {
base := strings.TrimSuffix(filename, "-toolchain-golden.tar.xz")
if base == filename {
return ""
}
return base
}
// SetExtractedDir records where the golden image has been extracted so that
// BuildEnv can point PATH at the correct bin/ subdirectories.
func (f *BTCForge) SetExtractedDir(dir string) {
f.ExtractedDir = dir
}
// BuildEnv returns the environment variables needed for a Cast pipeline to
// compile with the BTC sovereign toolchain. The returned map is intended to
// be merged into the sandbox environment.
//
// For BTC 0.4.0+, if a manifest is loaded, the CFLAGS and triple come from
// the manifest's structured data rather than being guessed from the label.
// Cross-compiled toolchains use {triple}-gcc/{triple}-g++ naming.
//
// If the golden image has been extracted (ExtractedDir is set), PATH is
// pointed at the extracted tree. Otherwise, BTC's GLOBAL_CFLAGS are
// assembled with --sysroot pointing at BTCRoot.
func (f *BTCForge) BuildEnv() map[string]string {
env := make(map[string]string)
if !f.Available {
return env
}
// Determine the sysroot and bin directory.
newroot := f.BTCRoot
binDir := ""
if f.ExtractedDir != "" {
newroot = f.ExtractedDir
binDir = filepath.Join(f.ExtractedDir, "bin")
if _, err := os.Stat(binDir); err != nil {
binDir = filepath.Join(f.ExtractedDir, "usr", "bin")
}
}
// Determine the target triple and march.
// Prefer manifest data (BTC 0.4.0+), fall back to derivation.
triple := f.TargetTriple
march := f.TargetMarch
if triple == "" {
// Legacy 0.3.x derivation
arch := f.TargetArch
if arch == "" {
arch = hostArch()
}
triple = arch + "-dcosnet-linux-gnu"
}
if march == "" {
march = f.TargetID
if march == "" {
march = f.TargetArch
}
}
// PATH: prepend the BTC bin directories.
if binDir != "" {
env["PATH"] = binDir + ":" + os.Getenv("PATH")
}
// Compiler executables.
// Cross-toolchains (0.4.0+) use {triple}-gcc naming.
// Native toolchains may have plain gcc in the sysroot.
if f.CrossMode && triple != "" {
env["CC"] = triple + "-gcc"
env["CXX"] = triple + "-g++"
} else {
env["CC"] = triple + "-gcc"
env["CXX"] = triple + "-g++"
}
// Build CFLAGS.
// For BTC 0.4.0+ with a manifest, use the manifest's cflags.
// Otherwise, assemble from march + ISA + sysroot.
var cflags string
if f.Manifest != nil && f.Manifest.CFlags != "" {
// Manifest cflags may contain --sysroot pointing at the build-time
// cleanroom. Rewrite to use the actual sysroot/newroot.
cflags = rewriteSysroot(f.Manifest.CFlags, newroot)
} else {
// Legacy assembly.
cflags = fmt.Sprintf("-O3 -march=%s -flto -ffat-lto-objects --sysroot=%s -pipe",
march, newroot)
}
env["CFLAGS"] = cflags
env["CXXFLAGS"] = cflags
// BTC GLOBAL_LDFLAGS.
var ldflags string
if f.Manifest != nil && f.Manifest.Ldflags != "" {
ldflags = rewriteSysroot(f.Manifest.Ldflags, newroot)
} else {
ldflags = fmt.Sprintf("-Wl,-O1 -Wl,--as-needed -flto --sysroot=%s", newroot)
}
env["LDFLAGS"] = ldflags
// Forensic tracking.
if f.SYSLabel != "" {
env["BTC_SYS_LABEL"] = f.SYSLabel
}
env["BTC_MODE"] = "1"
if f.TargetID != "" {
env["BTC_TARGET_ID"] = f.TargetID
}
if f.CrossMode {
env["BTC_CROSS"] = "1"
}
if f.CLib != "" {
env["BTC_CLIB"] = f.CLib
}
if f.TargetTriple != "" {
env["BTC_TARGET_TRIPLE"] = f.TargetTriple
}
return env
}
// rewriteSysroot replaces --sysroot=<old> flags with --sysroot=<new>.
// This is needed when the manifest's cflags/ldflags contain the build-time
// cleanroom path but the toolchain has been extracted to a different location.
func rewriteSysroot(flags, newroot string) string {
// Match --sysroot=<anything> (greedy to end of flag value).
re := regexp.MustCompile(`--sysroot=\S+`)
return re.ReplaceAllString(flags, "--sysroot="+newroot)
}
// VerifyStamp reads and verifies BTC forensic stamps from a binary. It uses
// readelf to extract the .note.BTC ELF note section and getfattr to read the
// extended filesystem attributes.
func (f *BTCForge) VerifyStamp(binPath string) (*BTCStamp, error) {
stamp := &BTCStamp{}
// Verify the file exists.
if _, err := os.Stat(binPath); err != nil {
return nil, fmt.Errorf("btc: verify stamp: %w", err)
}
// Extract .note.BTC via readelf.
noteOut, err := exec.Command("readelf", "-n", binPath).CombinedOutput()
if err == nil {
parsed := parseNoteBTC(string(noteOut))
if parsed != nil {
stamp.Org = parsed.Org
stamp.Kernel = parsed.Kernel
stamp.Arch = parsed.Arch
stamp.Label = parsed.Label
stamp.Forge = parsed.Forge
stamp.HasNote = parsed.HasNote
}
}
// Extract xattrs via getfattr.
xattrOut, err := exec.Command("getfattr", "-d", "--name=user.btc.identity", "--name=user.btc.hash", binPath).CombinedOutput()
if err == nil {
identity, hash := parseXAttrs(string(xattrOut))
stamp.Identity = identity
stamp.Hash = hash
stamp.HasXAttr = identity != ""
}
// Validate the hash against the actual file content.
if stamp.Hash != "" {
actual, err := fileSHA256(binPath)
if err == nil {
stamp.Valid = (actual == strings.TrimPrefix(stamp.Hash, "sha256:"))
}
}
return stamp, nil
}
// StampBinary applies BTC forensic stamps to a compiled binary. It:
//
// 1. Creates a small assembly object containing the .note.BTC ELF note.
// 2. Injects the note via objcopy --add-section.
// 3. Sets extended attributes user.btc.identity and user.btc.hash.
// 4. Separates debug symbols (objcopy --only-keep-debug, strip, add-gnu-debuglink).
//
// If objcopy or the assembler is unavailable the function logs a warning
// and continues rather than failing the build.
func (f *BTCForge) StampBinary(binPath string, forgeStep string) error {
// Determine stamp fields.
org := "dcos.net"
kernel := detectKernel()
arch := f.TargetID
if arch == "" {
arch = f.TargetArch
}
if arch == "" {
arch = hostArch()
}
label := f.SYSLabel
if label == "" {
label = "unknown"
}
// Step 1: Create the .note.BTC assembly source in a temp directory.
noteDir, err := os.MkdirTemp("", "btc-stamp-*")
if err != nil {
return fmt.Errorf("btc: stamp: create temp dir: %w", err)
}
defer os.RemoveAll(noteDir)
asmSrc := buildNoteAsm(org, kernel, arch, label, forgeStep)
asmPath := filepath.Join(noteDir, "btc_note.S")
if err := os.WriteFile(asmPath, []byte(asmSrc), 0644); err != nil {
return fmt.Errorf("btc: stamp: write asm: %w", err)
}
// Step 2: Assemble the note object.
// For cross-compiled toolchains, use the cross-assembler when available.
asmCmd := "as"
if f.CrossMode && f.TargetTriple != "" {
crossAsm := f.TargetTriple + "-as"
if _, err := exec.LookPath(crossAsm); err == nil {
asmCmd = crossAsm
}
}
objPath := filepath.Join(noteDir, "btc_note.o")
if err := exec.Command(asmCmd, "-o", objPath, asmPath).Run(); err != nil {
log.Printf("btc: warning: assembler not available, skipping .note.BTC injection: %v", err)
applyXAttrStamps(binPath, label)
return nil
}
// Step 3: Copy the original binary with the note section injected.
stampedPath := binPath + ".btc-stamped"
if err := exec.Command("objcopy", "--add-section", ".note.BTC="+objPath, binPath, stampedPath).Run(); err != nil {
log.Printf("btc: warning: objcopy not available, skipping .note.BTC injection: %v", err)
applyXAttrStamps(binPath, label)
return nil
}
// Replace the original with the stamped version.
if err := os.Rename(stampedPath, binPath); err != nil {
// Clean up the stamped copy on failure.
os.Remove(stampedPath)
return fmt.Errorf("btc: stamp: replace binary: %w", err)
}
// Step 4: Set extended attributes and separate debug symbols.
applyXAttrStamps(binPath, label)
return nil
}
// applyXAttrStamps sets the BTC identity and hash extended attributes
// on a binary, then optionally separates debug symbols.
func applyXAttrStamps(binPath, label string) {
identity := label
fileHash, err := fileSHA256(binPath)
if err != nil {
fileHash = "sha256:error"
}
setXAttr(binPath, "user.btc.identity", identity)
setXAttr(binPath, "user.btc.hash", fileHash)
separateDebugSymbols(binPath)
}
// ---------------------------------------------------------------------------
// Internal helpers
// ---------------------------------------------------------------------------
// noteFieldRe matches the pipe-delimited fields inside a .note.BTC
// readelf output block. BTC.sh writes a single line like:
//
// "Org: dcos.net|K:7.1|Arch:haswell|Label:DCOSNET-HASWELL-AVX2-LTO|Forge:binutils-configure"
//
// We parse the full pipe-delimited string to extract each field.
var noteFieldRe = regexp.MustCompile(`Org:\s*([^|]+)\|K:([^|]+)\|Arch:([^|]+)\|Label:([^|]+)\|Forge:([^|]+)`)
// parseNoteBTC extracts stamp fields from readelf -n output and returns
// a BTCStamp with HasNote set to true. Returns nil if the .note.BTC
// section is not found or contains no recognizable fields.
func parseNoteBTC(readelfOutput string) *BTCStamp {
s := &BTCStamp{}
// Find the .note.BTC block.
idx := strings.Index(readelfOutput, "note.BTC")
if idx == -1 {
return nil
}
// Parse only within the note.BTC section (stop at the next section).
block := readelfOutput[idx:]
if nextSection := strings.Index(block, "\nDisplaying notes found in:"); nextSection > 0 {
block = block[:nextSection]
}
matches := noteFieldRe.FindStringSubmatch(block)
if len(matches) >= 6 {
s.Org = strings.TrimSpace(matches[1])
s.Kernel = strings.TrimSpace(matches[2])
s.Arch = strings.TrimSpace(matches[3])
s.Label = strings.TrimSpace(matches[4])
s.Forge = strings.TrimSpace(matches[5])
}
// Only return if we found at least one field.
if s.Org == "" && s.Kernel == "" && s.Arch == "" {
return nil
}
s.HasNote = true
return s
}
// parseXAttrs extracts user.btc.identity and user.btc.hash values from
// getfattr -d output. The format is:
//
// user.btc.identity="BTC-SYS_LABEL-5.15.0-sovereign"
// user.btc.hash="sha256:abcdef..."
var xattrIdentityRe = regexp.MustCompile(`user\.btc\.identity="([^"]*)"`)
var xattrHashRe = regexp.MustCompile(`user\.btc\.hash="([^"]*)"`)
func parseXAttrs(getfattrOutput string) (identity, hash string) {
if m := xattrIdentityRe.FindStringSubmatch(getfattrOutput); len(m) > 1 {
identity = m[1]
}
if m := xattrHashRe.FindStringSubmatch(getfattrOutput); len(m) > 1 {
hash = m[1]
}
return identity, hash
}
// buildNoteAsm generates an architecture-neutral ELF NOTE assembly source
// that defines a .note.BTC section with the given fields. The output
// uses raw .byte directives for portability across all ELF targets
// (x86_64, arm, mipsel, tilegx). This mirrors BTC.sh's f_stamp_binary
// routine which uses the same approach.
func buildNoteAsm(org, kernel, arch, label, forge string) string {
// Use the same pipe-delimited format as BTC.sh and Fester for
// cross-project compatibility. BTC.sh's f_stamp_binary writes:
// Org: dcos.net|K:7.1|Arch:haswell|Label:DCOSNET-HASWELL-AVX2-LTO|Forge:binutils-configure
desc := fmt.Sprintf("Org: %s|K:%s|Arch:%s|Label:%s|Forge:%s",
org, kernel, arch, label, forge)
// Build the ELF NOTE in raw form using .byte directives.
// ELF Note structure: namesz (4) + descsz (4) + type (4) + name + desc.
name := "BTC"
nameBytes := append([]byte(name), 0)
descBytes := []byte(desc)
noteType := uint32(1) // NT_VERSION — matches BTC.sh's .long 1
var b strings.Builder
b.WriteString(" .section .note.BTC, \"a\", @note\n")
b.WriteString(" .align 4\n")
b.WriteString(" .globl __btc_note_start\n")
b.WriteString("__btc_note_start:\n")
// namesz (little-endian).
b.WriteString(fmt.Sprintf(" .long %d\n", len(nameBytes)))
// descsz.
b.WriteString(fmt.Sprintf(" .long %d\n", len(descBytes)))
// type.
b.WriteString(fmt.Sprintf(" .long %d\n", noteType))
// name bytes.
for _, c := range nameBytes {
b.WriteString(fmt.Sprintf(" .byte %d\n", c))
}
// Pad name to 4-byte alignment.
if len(nameBytes)%4 != 0 {
pad := 4 - (len(nameBytes) % 4)
for i := 0; i < pad; i++ {
b.WriteString(" .byte 0\n")
}
}
// Description bytes.
for _, c := range descBytes {
b.WriteString(fmt.Sprintf(" .byte %d\n", c))
}
// Pad description to 4-byte alignment.
if len(descBytes)%4 != 0 {
pad := 4 - (len(descBytes) % 4)
for i := 0; i < pad; i++ {
b.WriteString(" .byte 0\n")
}
}
b.WriteString(" .align 4\n")
b.WriteString(" .globl __btc_note_end\n")
b.WriteString("__btc_note_end:\n")
b.WriteString(" .previous\n")
return b.String()
}
// detectKernel returns the running kernel version string (e.g., "5.15.0-generic").
func detectKernel() string {
var utsname runtime.Utsname
if err := runtime.Uname(&utsname); err != nil {
return "unknown"
}
return strings.TrimRight(string(utsname.Release[:]), "\x00")
}
// fileSHA256 returns the hex-encoded SHA-256 of a file (without the "sha256:" prefix).
func fileSHA256(path string) (string, error) {
f, err := os.Open(path)
if err != nil {
return "", err
}
defer f.Close()
h := sha256.New()
if _, err := h.ReadFrom(f); err != nil {
return "", err
}
return hex.EncodeToString(h.Sum(nil)), nil
}
// setXAttr sets an extended filesystem attribute on a file. If setfattr is
// not available or the filesystem does not support xattrs, the function
// logs a warning and continues.
func setXAttr(path, attr, value string) {
if err := exec.Command("setfattr", "-n", attr, "-v", value, path).Run(); err != nil {
log.Printf("btc: warning: setfattr %s on %s failed: %v", attr, path, err)
}
}
// separateDebugSymbols extracts debug info from a binary into a separate
// .debug file, strips the binary, and links the debug file back. This
// mirrors BTC.sh's debug symbol separation steps:
//
// objcopy --only-keep-debug {bin} {bin}.debug
// strip --strip-unneeded {bin}
// objcopy --add-gnu-debuglink={bin}.debug {bin}
func separateDebugSymbols(binPath string) {
debugPath := binPath + ".debug"
// Extract debug symbols.
if err := exec.Command("objcopy", "--only-keep-debug", binPath, debugPath).Run(); err != nil {
log.Printf("btc: warning: objcopy --only-keep-debug failed for %s: %v", binPath, err)
return
}
// Strip the binary.
if err := exec.Command("strip", "--strip-unneeded", binPath).Run(); err != nil {
log.Printf("btc: warning: strip failed for %s: %v", binPath, err)
return
}
// Link the debug file back.
if err := exec.Command("objcopy", "--add-gnu-debuglink="+debugPath, binPath).Run(); err != nil {
log.Printf("btc: warning: objcopy --add-gnu-debuglink failed for %s: %v", binPath, err)
}
}