diff --git a/CHANGELOG.md b/CHANGELOG.md index 16410b3..d50976c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- **GOOBJ format specification.** [docs/GOOBJ.md](docs/GOOBJ.md) + documents the Go object file format in full: both containers, the 96 + byte header and all 19 blocks, every structure with its byte + offsets, symbol kinds and flag bits, all 106 relocation types with + the weak variants, aux symbols, the FuncInfo payload, the pc-value + table encoding, the content hashes and the builtin table, all + verified byte for byte against objects produced by Go 1.27.1's own + tools. - **Macro expansion and include splicing.** `gasm asm`, `gasm diff` and `gasm audit-instructions` now preprocess assembly the way the toolchain does: object and parameterised `#define` macros expand at diff --git a/README.md b/README.md index e0431e7..f9d8fd8 100644 --- a/README.md +++ b/README.md @@ -185,9 +185,9 @@ is written as that knowledge is produced during development. It is verified the way the code is verified: an encoding documented here is one that differential tests against `go tool asm` confirm byte-for-byte, and a format field documented here is one the linker -demonstrably reads. The result will live in this repository, so that -Plan 9 assembly finally carries a reference its own tooling is built -against. +demonstrably reads. The work has begun: [docs/GOOBJ.md](docs/GOOBJ.md) +specifies the object file format completely. The assembler language +reference follows. ## Direction @@ -314,6 +314,7 @@ recipe. ~/.local/share/man (MANDIR overrides); `just uninstall-man` removes them - [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md): components and data flow +- [docs/GOOBJ.md](docs/GOOBJ.md): the GOOBJ object file format specification - [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md): development setup and recipes - [CHANGELOG.md](CHANGELOG.md): release history diff --git a/docs/GOOBJ.md b/docs/GOOBJ.md new file mode 100644 index 0000000..18c4c57 --- /dev/null +++ b/docs/GOOBJ.md @@ -0,0 +1,657 @@ +# The GOOBJ object file format + +This document is a complete specification of GOOBJ, the object file format +that the Go toolchain's assembler, compiler and linker exchange, written for +implementers of independent producers and consumers. It documents the format +as shipped by Go 1.27.1, identified by the magic string `"\x00go120ld"`. + +No comparable document exists upstream. The format is defined only by the +source of the `cmd/internal/goobj` package inside the toolchain tree, it is an +internal interface with no stability promise, and it can change in any +release. This specification was therefore produced by reverse engineering +that source and by parsing real objects produced by `go tool asm` and +`go tool compile`, byte for byte, against the layout described here. Within +gasm-devkit it is kept honest by the differential tests in `asm/goobj_test.go` +and `asm/link_test.go`, which compare `gasm asm --format goobj` output against +the toolchain's own products and feed gasm objects to `go build`. + +Every numeric value in this document, every block index, structure size, flag +bit, type code and relocation number, was read from the Go 1.27.1 source at +`/usr/local/go/src/cmd/internal/goobj`, `cmd/internal/obj` and +`cmd/internal/objabi`, and exercised against assembled objects. + +## Containers + +The unit this document specifies is the **object**: one package's worth of +symbols, relocations and data. An object is never consumed naked. Two +wrappers exist in practice, and the linker dispatches on the first bytes of +the file. + +**The bare object**, written by `go tool asm`: + +```text +"go object linux amd64 go1.27.1 GOAMD64=v1 X:regabiwrappers,...\n" +"!\n" + +``` + +The first line is the toolchain configuration string, produced by +`objabi.HeaderString`: `go object`, the GOOS, the GOARCH, the toolchain +version, an optional architecture qualifier such as `GOAMD64=v1`, and +`X:` followed by the enabled experiments, comma separated. The linker requires +this line to match its own configuration exactly and rejects the file +otherwise; the `-f` linker flag waives the check. Header lines may be +followed by export data delimited by `$$` markers; the header region always +ends at the first line consisting of exactly `!`, and the GOOBJ blob starts +immediately after that line. + +**The package archive**, written by the compiler output pipeline and consumed +by `go build`: the classic `ar` format, magic `!\n`, with the export +data in a `__.PKGDEF` member and one or more objects as further members, each +carrying the bare-object structure above. `go tool pack` creates and +inspects such archives. + +| Consumer | Role | +|---|---| +| `cmd/asm` | writes objects from `.s` files | +| `cmd/compile` | writes objects from Go source | +| `cmd/link` | reads objects and archives, produces executables | +| `cmd/nm`, `cmd/objdump` | read objects through `cmd/internal/objfile` | + +## Conventions + +- All integers are **little endian**. +- There is **no alignment or padding** anywhere in the file; structures follow + one another byte by byte. +- Every offset stored in the file is **relative to the first byte of the GOOBJ + blob**, not to the start of the container. +- The blob opens with a 96 byte header that carries the byte offset of every + block. A block's length is the difference between its own offset and the + next block's, so the offset array is the only index the format needs. + +### Layout overview + +```mermaid +flowchart TB + A[Container header line and ! terminator] --> B[File header, 96 bytes] + B --> C[String table, implicit region] + C --> D[Autolib] + D --> E[PkgIndex] + E --> F[Files] + F --> G[Symbol definition arrays: Symdef, Hashed64def, Hasheddef, Nonpkgdef, Nonpkgref] + G --> H[RefFlags] + H --> I[Hash64 and Hash] + I --> J[RelocIndex, AuxIndex, DataIndex] + J --> K[Relocs] + K --> L[Aux] + L --> M[Data] + M --> N[RefNames] + N --> O[BlkEnd marks the end of the blob] +``` + +## The file header + +Exactly 96 bytes: 8 magic, 8 fingerprint, 4 flags, and 19 four byte block +offsets. + +| Offset | Size | Field | Meaning | +|---|---|---|---| +| 0 | 8 | Magic | `"\x00go120ld"`. A reader rejects anything else. The digits are the format version and have moved before; a new toolchain release may move them again. | +| 8 | 8 | Fingerprint | Identifies the package build. The compiler writes a hash of the export data; the assembler leaves all zero. The linker compares this against the fingerprint recorded by importers. | +| 16 | 4 | Flags | Bit field, see below. | +| 20 | 76 | Offsets | 19 `uint32` entries, one per block index 0 to 18. | + +Header flags: + +| Bit | Value | Name | Meaning | +|---|---|---|---| +| 0 | 1 | ObjFlagShared | built with `-shared` | +| 1 | 2 | reserved | was `ObjFlagNeedNameExpansion`, now unused | +| 2 | 4 | ObjFlagFromAssembly | produced from assembly source; `go tool asm` and gasm set this | +| 3 | 8 | ObjFlagUnlinkable | package path is invalid, the linker refuses to link | +| 4 | 16 | ObjFlagStd | standard library package | + +### Block indices + +The offset array is indexed by these constants, in file order: + +| Index | Constant | Contents | +|---|---|---| +| 0 | BlkAutolib | imported packages | +| 1 | BlkPkgIndex | referenced packages, indexed | +| 2 | BlkFile | source file names | +| 3 | BlkSymdef | symbol definitions, package scope | +| 4 | BlkHashed64def | short hashed definitions | +| 5 | BlkHasheddef | hashed definitions | +| 6 | BlkNonpkgdef | non-package definitions | +| 7 | BlkNonpkgref | non-package references | +| 8 | BlkRefFlags | flags of referenced symbols | +| 9 | BlkHash64 | 8 byte hashes for short hashed definitions | +| 10 | BlkHash | 16 byte hashes for hashed definitions | +| 11 | BlkRelocIndex | per symbol relocation start index | +| 12 | BlkAuxIndex | per symbol aux start index | +| 13 | BlkDataIndex | per symbol data offset | +| 14 | BlkReloc | relocations | +| 15 | BlkAux | aux symbol entries | +| 16 | BlkData | symbol payloads | +| 17 | BlkRefName | names of referenced symbols, for tools | +| 18 | BlkEnd | no contents; its offset is the end of the blob | + +## The string table + +There is no block index for strings. The table occupies the implicit region +between the end of the header (offset 96) and `Offsets[BlkAutolib]`, and every +string offset in the file points into that region. The writer de-duplicates: +each distinct string is stored once, in first-use order, and the empty string +is always the first entry, so its reference is length 0 and offset 96. + +A **string reference** is 8 bytes: `uint32` length, then `uint32` absolute +offset of the bytes. The bytes are stored raw, with no terminator. + +## Symbol references and the package index + +A **symbol reference** (SymRef) is 8 bytes: two `uint32`, `PkgIdx` and +`SymIdx`. The pair `{0, 0}` means nil. `PkgIdx` says which array the symbol +lives in: + +| Value | Constant | SymIdx indexes | +|---|---|---| +| 0 | PkgIdxInvalid | never valid in a written file | +| 1 and up, ascending | (imported packages) | the SymbolDefs array of the package named at PkgIndex entry `PkgIdx` | +| 0x7ffffffb | PkgIdxSelf | this object's Symdef array | +| 0x7ffffffc | PkgIdxBuiltin | the compiler's builtin table, see Builtins | +| 0x7ffffffd | PkgIdxHashed | this object's Hasheddef array | +| 0x7ffffffe | PkgIdxHashed64 | this object's Hashed64def array | +| 0x7fffffff | PkgIdxNone | NonPkgDefs, overflowing into NonPkgRefs | + +Assignment rules, as the toolchain performs them: + +- Every definition a package exports to the linker by index lands in Symdefs + with PkgIdxSelf. The compiler puts its functions and data here; the + assembler puts only its file-local static symbols here, everything else by + name, see below. +- External package references take indices 1, 2, 3, in order of first + reference during assembly; the package names go into PkgIndex at those + indices, entry 0 is the empty package and is never referenced. +- References to the compiler's builtin functions become PkgIdxBuiltin with + SymIdx set to the builtin's index. +- A symbol referenced **by name** rather than by index becomes PkgIdxNone and + its index counts through NonPkgDefs first, then continues into NonPkgRefs. + A producer must emit the definitions it made in NonPkgDefs and the pure + references in NonPkgRefs. +- The assembler's rule, from `cmd/internal/obj/sym.go`: every assembly symbol + is referenced by name, PkgIdxNone, **except** file-local static symbols, + whose names carry `<>` and which are referenced by index. The compiler also + forces references by name for symbols marked `//go:linkname` and for any + symbol with the DUPOK attribute, which the linker de-duplicates by name. + +## Symbol definition entries + +The five definition and reference arrays (block indices 3 to 7) share one +element layout, 21 bytes: + +| Offset | Size | Field | Meaning | +|---|---|---|---| +| 0 | 8 | Name | string reference | +| 8 | 2 | ABI | see table below | +| 10 | 1 | Type | symbol kind, see the kind table | +| 11 | 1 | Flag | bit field, see below | +| 12 | 1 | Flag2 | second bit field, see below | +| 13 | 4 | Siz | payload size in bytes, `uint32` | +| 17 | 4 | Align | alignment the linker must honour, `uint32` | + +The Name is a real string reference for hand-written symbols. The auxiliary +symbols the toolchain generates per function, the FuncInfo payload, the DWARF +entries, have empty names: length 0, and their identity is only via the Aux +entries that point at them by index. + +### The ABI field + +| Value | Meaning | +|---|---| +| 0 | ABI0, the stack based ABI, the ABI of every hand-written assembly function | +| 1 | ABIInternal, the register ABI of compiler-generated functions | +| 0xffff | static, a file-local symbol (`name<>(SB)`), `SymABIstatic` | + +### The Flag byte + +| Bit | Value | Name | Meaning | +|---|---|---|---| +| 0 | 1 | SymFlagDupok | duplicates allowed, the linker merges them | +| 1 | 2 | SymFlagLocal | file-local | +| 2 | 4 | SymFlagTypelink | belongs in the typelink table | +| 3 | 8 | SymFlagLeaf | leaf function | +| 4 | 16 | SymFlagNoSplit | no stack-split preamble | +| 5 | 32 | SymFlagReflectMethod | `//go:reflectmethod` reachability | +| 6 | 64 | SymFlagGoType | a Go type descriptor, `type:` name and SRODATA | + +Note that NoSplit is not reserved for explicit `NOSPLIT` declarations. On +amd64 the assembler itself marks any function whose frame is below +`abi.StackSmall` and whose body calls nothing that needs stack as NoSplit and +omits the split check, so a `TEXT` without `NOSPLIT` can still carry the bit. + +### The Flag2 byte + +| Bit | Value | Name | Meaning | +|---|---|---|---| +| 0 | 1 | SymFlagUsedInIface | type or itab reachable through an interface | +| 1 | 2 | SymFlagItab | an itab, `go:itab.` name and SRODATA | +| 2 | 4 | SymFlagDict | a generic dictionary symbol | +| 3 | 8 | SymFlagPkgInit | package initialisation function | +| 4 | 16 | SymFlagLinkname | reachable through `//go:linkname`; the assembler also sets it on `main.main` | +| 5 | 32 | SymFlagLinknameStd | linkname into the standard library | +| 6 | 64 | SymFlagABIWrapper | ABI transition wrapper | +| 7 | 128 | SymFlagWasmExport | `//go:wasmexport` target | + +### The Type byte: symbol kinds + +Values of `objabi.SymKind`, in numeric order: + +| Value | Name | Meaning | +|---|---|---| +| 0 | Sxxx | invalid zero value | +| 1 | STEXT | executable code | +| 2 | STEXTFIPS | executable code, FIPS section | +| 3 | SRODATA | read only data | +| 4 | SRODATAFIPS | read only data, FIPS section | +| 5 | SNOPTRDATA | data without pointers | +| 6 | SNOPTRDATAFIPS | data without pointers, FIPS section | +| 7 | SDATA | data, may contain pointers | +| 8 | SDATAFIPS | data, FIPS section | +| 9 | SBSS | zero initialised data | +| 10 | SNOPTRBSS | zero initialised data without pointers | +| 11 | STLSBSS | thread local zero initialised data | +| 12 | SDWARFCUINFO | DWARF compile unit information | +| 13 | SDWARFCONST | DWARF constants | +| 14 | SDWARFFCN | DWARF function entry | +| 15 | SDWARFABSFCN | DWARF absolute function entry | +| 16 | SDWARFTYPE | DWARF type information | +| 17 | SDWARFVAR | DWARF variable information | +| 18 | SDWARFRANGE | DWARF range lists | +| 19 | SDWARFLOC | DWARF location lists | +| 20 | SDWARFLINES | DWARF line programs | +| 21 | SDWARFADDR | DWARF address table | +| 22 | SLIBFUZZER_8BIT_COUNTER | libFuzzer coverage counter | +| 23 | SCOVERAGE_COUNTER | coverage counter | +| 24 | SCOVERAGE_AUXVAR | coverage auxiliary variable | +| 25 | SSEHUNWINDINFO | Windows SEH unwind information | + +## Referenced symbol flags (RefFlags) + +Element size 10 bytes, one per referenced external indexed symbol that +carries a non-zero Flag2: + +| Offset | Size | Field | +|---|---|---| +| 0 | 8 | Sym, a SymRef into another package | +| 8 | 1 | Flag, always 0 in current writers | +| 9 | 1 | Flag2, only SymFlagUsedInIface is ever written | + +The linker uses these to preserve reachability of interface conversions +across package boundaries. Entries with no flags are omitted entirely. + +## Hashes + +**Hash64**, block 9: one `uint64` per Hashed64def entry, in array order. Not +a hash at all: the writer copies the **first 8 bytes of the symbol's +payload**. Only symbols whose content-hash section byte is 0 may use the +short form. + +**Hash**, block 10: 16 bytes per Hasheddef entry: the first 16 bytes of a +SHA-256 computation over a seed byte `0x01` followed by the hash input. The +input, from `cmd/internal/obj/objfile.go`: + +1. the payload size, little endian `uint64`; +2. the section byte, one of `t` for STEXT, `f` for STEXTFIPS, `P` for pcdata, + `F` for the `go:func.*` and `go:funcrel.*` families, `T` for `type:` + symbols, otherwise 0; +3. for text symbols, the symbol name, which keeps distinct functions from + merging; +4. the payload with trailing zero bytes trimmed; +5. for each relocation: a 14 byte record, offset `uint32`, size `uint8`, + low type byte `uint8`, addend `int64`, followed by an encoding of the + target: tag byte 0 then the target's short hash, tag 1 then its full + hash, tag 2 then its expanded name, tag 3 then its builtin index, or, + for PkgIdxSelf and imported packages, no tag, then the package path + and the symbol index. + +Two symbols with equal hashes are interchangeable at link time, which is what +makes content addressing work. A producer that computes these hashes wrongly +produces objects that link but de-duplicate wrongly; gasm verifies them by +byte comparison against `go tool asm`. + +## The index arrays + +Three arrays of `uint32`, one element per **defined** symbol plus one final +element, in the order Symdefs, Hashed64defs, Hasheddefs, NonPkgDefs. With N +defined symbols, each array holds N + 1 entries, and the entry at N is the +total. + +- RelocIndex: entry i is where symbol i's relocations start in BlkReloc; + entry i + 1 minus entry i is its count. +- AuxIndex: the same construction over BlkAux. +- DataIndex: entry i is the byte offset of symbol i's payload within BlkData; + the count is the difference of neighbours. + +The toolchain writes relocations grouped per symbol in definition order, and +sorts each symbol's relocations by their Off field first. A producer that +skips the sort produces objects the linker still accepts, but that no longer +compare byte-for-byte with the toolchain's output. + +## Relocations + +Element size 23 bytes: + +| Offset | Size | Field | Meaning | +|---|---|---|---| +| 0 | 4 | Off | patch position, bytes from the start of the symbol's payload, `int32` | +| 4 | 1 | Siz | patch width in bytes | +| 5 | 2 | Type | relocation type, `uint16`, see the table | +| 7 | 8 | Add | addend, `int64` | +| 15 | 8 | Sym | target SymRef | + +The computed value `payload[Off:Off+Siz] += address(Sym) + Add` in the +flavour the type prescribes is the linker's job; the object only records the +request. A size 0 relocation patches nothing and exists purely as a marker +for the linker's reachability analysis. + +### Relocation types + +Values of `objabi.RelocType`. The assembler and compiler emit the generic +ones plus their own architecture's family; the rest exist for other ports and +for the linker itself. + +| Value | Name | Meaning | +|---|---|---| +| 1 | R_ADDR | absolute address | +| 2 | R_ADDRPOWER | ppc64: high adjusted plus low 16 bits across two D-form instructions | +| 3 | R_ADDRARM64 | arm64: adrp plus add pair | +| 4 | R_ADDRMIPS | mips: low 16 bits of an external address | +| 5 | R_ADDROFF | 32-bit offset from the section start to the symbol | +| 6 | R_SIZE | size of the referenced symbol | +| 7 | R_CALL | direct call, PC relative | +| 8 | R_CALLARM | arm: call with a shifted 24-bit field | +| 9 | R_CALLARM64 | arm64: BL | +| 10 | R_CALLIND | indirect call marker | +| 11 | R_CALLPOWER | ppc64: call | +| 12 | R_CALLMIPS | mips: non-PC-relative call target | +| 13 | R_CONST | constant value of the symbol | +| 14 | R_PCREL | PC relative displacement | +| 15 | R_TLS_LE | thread local, local exec offset | +| 16 | R_TLS_IE | thread local, initial exec GOT offset | +| 17 | R_GOTOFF | offset from the GOT base | +| 18 | R_PLT0 | PLT sequence, first instruction | +| 19 | R_PLT1 | PLT sequence, second instruction | +| 20 | R_PLT2 | PLT sequence, third instruction | +| 21 | R_USEFIELD | field reachability marker | +| 22 | R_USETYPE | type reachability marker, no bytes patched | +| 23 | R_USEIFACE | interface conversion marker, size 0 | +| 24 | R_USEIFACEMETHOD | interface method marker, size 0, addend is the method offset | +| 25 | R_USENAMEDMETHOD | keeps named methods alive | +| 26 | R_METHODOFF | like R_ADDROFF, the linker may zero it when the method is dead | +| 27 | R_KEEP | keeps the target alive if the source survives | +| 28 | R_POWER_TOC | ppc64: TOC relative | +| 29 | R_GOTPCREL | 32-bit PC relative GOT slot | +| 30 | R_JMPMIPS | mips: non-PC-relative jump target | +| 31 | R_DWARFSECREF | offset of the symbol from its section, DWARF use | +| 32 | R_ARM64_TLS_LE | arm64: MOV[NZ] immediate, TLS local exec | +| 33 | R_ARM64_TLS_IE | arm64: adrp plus ldr, TLS initial exec | +| 34 | R_ARM64_GOTPCREL | arm64: adrp plus ldr GOT slot | +| 35 | R_ARM64_GOT | arm64: GOT relative sequence | +| 36 | R_ARM64_PCREL | arm64: adrp plus add PC relative | +| 37 | R_ARM64_PCREL_LDST8 | arm64: adrp plus 8-bit load or store | +| 38 | R_ARM64_PCREL_LDST16 | arm64: adrp plus 16-bit load or store | +| 39 | R_ARM64_PCREL_LDST32 | arm64: adrp plus 32-bit load or store | +| 40 | R_ARM64_PCREL_LDST64 | arm64: adrp plus 64-bit load or store | +| 41 | R_ARM64_LDST8 | arm64: 12-bit load or store immediate, byte | +| 42 | R_ARM64_LDST16 | arm64: bits 11 to 1 of the address | +| 43 | R_ARM64_LDST32 | arm64: bits 11 to 2 | +| 44 | R_ARM64_LDST64 | arm64: bits 11 to 3 | +| 45 | R_ARM64_LDST128 | arm64: bits 11 to 4 | +| 46 | R_POWER_TLS_LE | ppc64: TLS local exec across two instructions | +| 47 | R_POWER_TLS_IE | ppc64: TLS initial exec via GOT | +| 48 | R_POWER_TLS | ppc64: marks the X-form instruction completing a TLS sequence | +| 49 | R_POWER_TLS_IE_PCREL34 | ppc64: prefixed TLS initial exec load | +| 50 | R_POWER_TLS_LE_TPREL34 | ppc64: prefixed TLS local exec | +| 51 | R_ADDRPOWER_DS | ppc64: DS-form second instruction, bits 15 to 2 | +| 52 | R_ADDRPOWER_GOT | ppc64: GOT entry relative to TOC | +| 53 | R_ADDRPOWER_GOT_PCREL34 | ppc64: PC relative GOT, prefixed | +| 54 | R_ADDRPOWER_PCREL | ppc64: PC relative across two D-form instructions | +| 55 | R_ADDRPOWER_TOCREL | ppc64: TOC relative across two D-form instructions | +| 56 | R_ADDRPOWER_TOCREL_DS | ppc64: TOC relative, DS form | +| 57 | R_ADDRPOWER_D34 | ppc64: prefixed absolute, 34 bits | +| 58 | R_ADDRPOWER_PCREL34 | ppc64: prefixed PC relative, 34 bits | +| 59 | R_RISCV_JAL | riscv64: 20-bit J-type offset | +| 60 | R_RISCV_JAL_TRAMP | riscv64: as R_RISCV_JAL, linker-generated trampolines only | +| 61 | R_RISCV_CALL | riscv64: AUIPC plus JALR pair | +| 62 | R_RISCV_PCREL_ITYPE | riscv64: AUIPC plus I-type pair | +| 63 | R_RISCV_PCREL_STYPE | riscv64: AUIPC plus S-type pair | +| 64 | R_RISCV_TLS_IE | riscv64: TLS initial exec, AUIPC plus I-type | +| 65 | R_RISCV_TLS_LE | riscv64: TLS local exec, LUI plus I-type | +| 66 | R_RISCV_GOT_HI20 | riscv64: high 20 bits of a GOT address | +| 67 | R_RISCV_GOT_PCREL_ITYPE | riscv64: GOT entry, AUIPC plus I-type | +| 68 | R_RISCV_PCREL_HI20 | riscv64: high 20 bits of a PC relative address | +| 69 | R_RISCV_PCREL_LO12_I | riscv64: low 12 bits, I-type | +| 70 | R_RISCV_PCREL_LO12_S | riscv64: low 12 bits, S-type | +| 71 | R_RISCV_BRANCH | riscv64: 12-bit branch offset | +| 72 | R_RISCV_ADD32 | riscv64: in-place addition, V + S + A | +| 73 | R_RISCV_SUB32 | riscv64: in-place subtraction, V - S - A | +| 74 | R_RISCV_RVC_BRANCH | riscv64: 8-bit compressed branch offset | +| 75 | R_RISCV_RVC_JUMP | riscv64: 11-bit compressed jump offset | +| 76 | R_PCRELDBL | s390x: PC relative, 2-byte aligned | +| 77 | R_LOONG64_ADDR_HI | loong64: bits 31 to 12 of an address | +| 78 | R_LOONG64_ADDR_LO | loong64: low 12 bits | +| 79 | R_LOONG64_ADDR64_HI | loong64: bits 63 to 52 | +| 80 | R_LOONG64_ADDR64_LO | loong64: bits 51 to 32 | +| 81 | R_LOONG64_ADDR_PCREL20_S2 | loong64: 22-bit aligned PC relative, PCADDI | +| 82 | R_LOONG64_TLS_LE_HI | loong64: TLS local exec, high bits | +| 83 | R_LOONG64_TLS_LE_LO | loong64: TLS local exec, low bits | +| 84 | R_CALLLOONG64 | loong64: 28-bit aligned BL | +| 85 | R_LOONG64_CALL36 | loong64: 38-bit aligned PCADDU18I plus JIRL | +| 86 | R_LOONG64_TLS_IE_HI | loong64: TLS initial exec via GOT, high | +| 87 | R_LOONG64_TLS_IE_LO | loong64: TLS initial exec via GOT, low | +| 88 | R_LOONG64_GOT_HI | loong64: GOT entry, high bits | +| 89 | R_LOONG64_GOT_LO | loong64: GOT entry, low bits | +| 90 | R_LOONG64_GOT64_HI | loong64: 64-bit GOT entry, high | +| 91 | R_LOONG64_GOT64_LO | loong64: 64-bit GOT entry, low | +| 92 | R_LOONG64_ADD64 | loong64: 64-bit in-place addition | +| 93 | R_LOONG64_SUB64 | loong64: 64-bit in-place subtraction | +| 94 | R_JMP16LOONG64 | loong64: 18-bit aligned conditional jump | +| 95 | R_JMP21LOONG64 | loong64: 23-bit aligned BEQZ or BNEZ | +| 96 | R_ADDRMIPSU | mips: sign-adjusted upper 16 bits | +| 97 | R_ADDRMIPSTLS | mips: TLS low 16 bits | +| 98 | R_ADDRCUOFF | pointer-sized offset from the DWARF compile unit start | +| 99 | R_WASMIMPORT | wasm: import module and name indices | +| 100 | R_XCOFFREF | aix: keeps the target alive, patches nothing | +| 101 | R_PEIMAGEOFF | windows: offset from the image base | +| 102 | R_INITORDER | orders inittask records, patches nothing | +| 103 | R_DWTXTADDR_U1 | writes a 1-byte ULEB .debug_addr index for the target function | +| 104 | R_DWTXTADDR_U2 | as above, 2 bytes | +| 105 | R_DWTXTADDR_U3 | as above, 3 bytes | +| 106 | R_DWTXTADDR_U4 | as above, 4 bytes; the assembler always picks this one | +| -32768 | R_WEAK | mask: the target need not be reachable, see below | +| -32767 | R_WEAKADDR | R_WEAK or R_ADDR | +| -32763 | R_WEAKADDROFF | R_WEAK or R_ADDROFF | + +R_WEAK is bit 15 set on a negative `int16`: a weak relocation is the base +type's value with bit 15 set. The linker strips the bit before dispatch. + +## Aux symbol entries + +Element size 9 bytes: a `uint8` type then a SymRef. Aux entries attach +auxiliary symbols to a definition; the arrays run per symbol in the order +given by AuxIndex. + +| Value | Name | Attaches | +|---|---|---| +| 0 | AuxGotype | the Go type of a data symbol | +| 1 | AuxFuncInfo | the FuncInfo payload of a text symbol | +| 2 | AuxFuncdata | one funcdata symbol; one entry per slot, nil slots carry the {0,0} reference | +| 3 | AuxDwarfInfo | DWARF debug info for the function | +| 4 | AuxDwarfLoc | DWARF location lists | +| 5 | AuxDwarfRanges | DWARF range lists | +| 6 | AuxDwarfLines | DWARF line program | +| 7 | AuxPcsp | pc-value table: SP adjustments | +| 8 | AuxPcfile | pc-value table: source file indices | +| 9 | AuxPcline | pc-value table: line numbers | +| 10 | AuxPcinline | pc-value table: inlining tree positions | +| 11 | AuxPcdata | one pc-value table per live variable slot | +| 12 | AuxWasmImport | wasm import description | +| 13 | AuxWasmType | wasm export type description | +| 14 | AuxSehUnwindInfo | Windows SEH unwind info | + +The writer emits them in the order Gotype, FuncInfo, Funcdata entries, +DwarfInfo, DwarfLoc, DwarfRanges, DwarfLines, Pcsp, Pcfile, Pcline, Pcinline, +SehUnwindInfo, Pcdata entries, WasmImport, WasmType, and skips any whose +payload would be empty. A function assembled from `.s` source by Go 1.27.1 +carries exactly: FuncInfo, the Funcdata slots including nils, DwarfInfo, +DwarfLines, Pcsp, Pcfile, Pcline and Pcinline; gasm's writer produces the +same set. + +The aux targets are either PkgIdxSelf definitions, PkgIdxHashed pcdata +symbols, or, for the funcdata of assembly functions, PkgIdxNone references +carrying names such as `pkg.Fn.args_stackmap` and `pkg.Fn.arginfo0`, which +resolve to definitions in the package's compiled Go code when there is any. + +## Symbol payloads (BlkData) + +The payloads of all defined symbols, in definition order, concatenated with +no padding; DataIndex gives each symbol's slice. A text symbol's payload is +its machine code, with the stack-split preamble and any morestack block +already included. A data symbol's payload is the bytes laid down by its DATA +directives, zero filled to its declared size. If a symbol was created from an +embedded file, the file's bytes follow the payload and count towards its +DataIndex extent; assembly producers never write this extension. + +### The FuncInfo payload + +An SDATA symbol with no name, referenced by AuxFuncInfo. 28 bytes minimum, +little endian: + +| Offset | Size | Field | Meaning | +|---|---|---|---| +| 0 | 4 | Args | argument area in bytes; 0x80000000 when the producer declared none | +| 4 | 4 | Locals | frame size in bytes | +| 8 | 1 | FuncID | runtime function classification, 0 means normal | +| 9 | 1 | FuncFlag | TopFrame = 1, SPWrite = 2, Asm = 4 | +| 10 | 2 | padding | zero, reserved to a 4 byte boundary | +| 12 | 4 | StartLine | source line of the TEXT declaration | +| 16 | 4 | NumFile | count of file indices that follow | +| 20 | 4 × NumFile | Files | indices into the Files block, ascending | +| then | 4 | NumInlTree | count of inlining tree nodes that follow | +| then | 24 × NumInlTree | InlTree | nodes, see below | + +One InlTree node, 24 bytes: `int32` parent index, `uint32` file index, +`int32` line, `uint32` PkgIdx and `uint32` SymIdx of the inlined function, and +`int32` parent PC. + +The assembler derives FuncID from the symbol name through +`objabi.GetFuncID`, so a runtime function with a name the runtime treats +specially gets that classification even when defined in assembly; an ordinary +name yields 0. FuncFlag carries the Asm bit, 4, for every assembly function. + +### The pc-value tables + +The AuxPcsp, AuxPcfile, AuxPcline, AuxPcinline and AuxPcdata payloads are +pc-value tables, each a sequence of value deltas and PC deltas: + +- a signed value delta, zig-zag encoded, `binary.PutVarint` form; +- an unsigned PC delta in ULEB128 form, counted in instruction units, the + raw delta divided by the architecture's minimum instruction length; +- the table ends with a final PC delta to the end of the function followed by + a zero byte. + +The first value applies from function entry. The encoding is the one +`cmd/internal/obj/pcln.go` calls funcpctab, and it is the same encoding the +final runtime pclntable carries. + +### The DWARF payloads + +AuxDwarfInfo, AuxDwarfLoc, AuxDwarfRanges and AuxDwarfLines reference SDWARF +symbols whose payloads are DWARF byte streams. The object format treats them +as opaque: the linker concatenates them into the final `.debug_*` sections +and resolves the relocations recorded inside them. The compiler produces +DWARF content per its own generation; gasm produces DWARF5 streams in +`asm/goobj_dwarf.go`. + +## Builtins + +Frequently referenced runtime functions are referenced by index rather than +by name: PkgIdxBuiltin with SymIdx set to the position in the generated table +`cmd/internal/goobj/builtinlist.go`, 299 entries in Go 1.27.1, names such as +`runtime.newobject` at index 0; 232 entries carry ABI 1 and the remaining 67 +ABI 0. Builtin names never enter the string table. The mapping only applies +while the object is not linked against shared libraries, and a linkname'd +symbol never counts as a builtin even when its name matches. + +## Fingerprints + +The 8 byte fingerprint identifies one build of a package. The compiler fills +it with a hash of the package's export data; the assembler leaves it zero. +The linker checks a package's fingerprint against the fingerprints its +importers recorded in their Autolib entries and rejects a mismatched build, +which is how stale objects are caught. + +## What a producer must do + +The checklist a third-party writer must satisfy for `go build` to accept its +objects, in one place: + +1. Write the container exactly: the `go object` line matching the target + toolchain's configuration string, the `!\n` terminator, then the blob. +2. Emit the 19 block offsets, in order, and make BlkEnd the blob length. +3. Deduplicate the string table, keep the empty string at offset 96, and + reference it everywhere a name appears. +4. Index relocations, aux entries and data per symbol with the N + 1 arrays, + definitions ordered Symdefs, Hashed64defs, Hasheddefs, NonPkgDefs. +5. Sort relocations by offset within each symbol. +6. Fill Siz with the true payload length, set Align for every + content-addressable symbol, and keep symbols under 2 GB. +7. Reference symbols by the package-index rules. An assembly producer + references everything outside the object by name, PkgIdxNone, + except its own file-local statics and the builtins; PkgIdxSelf is + reserved for definitions in this object. Assembly TEXT symbols + carry ABI 0. +8. Compute the content hashes exactly as the toolchain does, or emit no + hashed definitions at all. + +## How gasm-devkit implements and verifies it + +The writer lives in `asm/goobj.go`, which carries the shared container and the +amd64 relocation emission, with per-architecture relocation emitters in +`asm/goobjarm64.go`, `asm/goobjriscv.go` and `asm/goobjloong64.go`, symbol +resolution in `asm/goobj_resolve.go` and DWARF generation in +`asm/goobj_dwarf.go`. `gasm asm --format goobj -p pkg/path` writes objects +that `go build` consumes in place of the toolchain's own. + +Verification is differential and continuous: + +- `asm/goobj_test.go` compares gasm's GOOBJ output against `go tool asm` + output for the same source, byte for byte; +- `asm/link_test.go` builds real Go programs whose assembly comes from gasm + objects and runs them; +- `gasm verify` keeps the machine code itself identical to the toolchain's, + which is the precondition for the object comparison to be meaningful. + +## Versioning and drift + +The magic string carries the format generation, `go120ld` in Go 1.27.1. When +a toolchain release changes the format, it changes that string first, and the +linker refuses blobs whose magic it does not know. The watch points for a new +release are, in order: the magic, the block index list, the Aux type list, +the tail of the relocation table, the FuncInfo layout, and the builtin table +count. gasm's tests fail against any of these changes, which is the mechanism +that keeps this document and the writer current. + +The authoritative sources, for the release this document covers: + +- `cmd/internal/goobj/objfile.go`: the format, every structure in this + document; +- `cmd/internal/goobj/funcinfo.go`: FuncInfo and the inlining tree; +- `cmd/internal/goobj/builtinlist.go`: the builtin table; +- `cmd/internal/obj/objfile.go`: the writer, hash inputs and aux order; +- `cmd/internal/obj/sym.go`: package index assignment and the by-name rule; +- `cmd/internal/obj/pcln.go`: the pc-value encoding; +- `cmd/internal/objabi/reloctype.go`: relocation types; +- `cmd/internal/objabi/symkind.go`: symbol kinds; +- `cmd/link/internal/ld/lib.go`: container parsing and fingerprint checks.