// Copyright (c) 2026 Petr Balvín (https://petrbalvin.org) // SPDX-License-Identifier: BSD-3-Clause package asm import ( "bytes" "fmt" "os" "os/exec" "path/filepath" "sync" ) // GOObjectRISCV emits a GOOBJ object file for RISC-V. The layout is the // shared one in goobj.go — the toolchain preamble, the go120ld header with // its block offsets, the string table, the symbol definitions and the // reloc/aux/data index arrays — with the RISC-V preamble, the MinLC of 2 for // the pc-value deltas, and the single R_RISCV_PCREL_ITYPE/STYPE relocation // per AUIPC pair, matching `go tool asm`'s model (each pair is one 8-byte // relocation, not the ELF HI20/LO12 pair). func (img *Image) GOObjectRISCV(pkgPath, srcPath string) ([]byte, error) { pre, err := toolchainObjectPreambleRISCV() if err != nil { return nil, err } return img.emitGOObject(pkgPath, srcPath, pre, 2, func(r Reloc) (uint16, uint8) { switch r.Kind { case RelRISCVPCRELSType: return relocRISCVPcrelStype, 8 case RelRISCVJal: return relocRISCVJal, 4 default: return relocRISCVPcrelItype, 8 } }) } // RISC-V relocation types (cmd/internal/objabi). The Go linker applies // R_RISCV_PCREL_ITYPE/STYPE to an AUIPC + I/S-type instruction pair as a // single 8-byte field; R_RISCV_JAL covers a single 4-byte J-type instruction. const ( relocRISCVJal = 59 // R_RISCV_JAL relocRISCVPcrelItype = 62 // R_RISCV_PCREL_ITYPE relocRISCVPcrelStype = 63 // R_RISCV_PCREL_STYPE ) // toolchainObjectPreambleRISCV returns the "go object ...\n!\n" header // the installed go tool asm writes for riscv64, captured by assembling a // one-instruction probe (see toolchainObjectPreamble). var ( preambleRISCVOnce sync.Once preambleRISCV []byte preambleRISCVErr error ) func toolchainObjectPreambleRISCV() ([]byte, error) { preambleRISCVOnce.Do(func() { goBin, err := exec.LookPath("go") if err != nil { preambleRISCVErr = fmt.Errorf("GOOBJ emission needs the Go toolchain: %w", err) return } dir, err := os.MkdirTemp("", "gasm-preamble-riscv") if err != nil { preambleRISCVErr = err return } defer os.RemoveAll(dir) src := filepath.Join(dir, "probe_riscv64.s") if err := os.WriteFile(src, []byte("TEXT \u00b7x(SB), $0-0\n\tRET\n"), 0o644); err != nil { preambleRISCVErr = err return } obj := filepath.Join(dir, "probe.o") cmd := exec.Command(goBin, "tool", "asm", "-p", "probe", "-o", obj, src) cmd.Env = append(os.Environ(), "GOARCH=riscv64") if out, err := cmd.CombinedOutput(); err != nil { preambleRISCVErr = fmt.Errorf("probing the assembler for the object header: %v\n%s", err, out) return } data, err := os.ReadFile(obj) if err != nil { preambleRISCVErr = err return } i := bytes.Index(data, []byte("\n!\n")) if i < 0 || !bytes.HasPrefix(data[i+3:], []byte(goobjMagic)) { preambleRISCVErr = fmt.Errorf("unrecognised assembler object layout") return } preambleRISCV = data[:i+3] }) return preambleRISCV, preambleRISCVErr }