ld.gold links object files into an executable, but calling it by hand means you own every piece the compiler driver normally hides from you. This guide assembles and links a tiny x86-64 ELF program, inspects the linker's decisions, and verifies the finished file actually runs. The examples use GNU gold from GNU Binutils for Ubuntu 2.42, package version 2.42-4ubuntu2.10. Allow about fifteen minutes. You need a shell, the installed binutils tools, and permission to create files in a temporary directory. No elevated privileges are required.
ld.gold is the gold ELF linker. The installed names gold, ld.gold, and the target-prefixed aliases in this guide all point to identical local manpage content, but a target-prefixed linker still only produces output for its own target. Do not mix objects, libraries or linkers from different architectures.
Start with read-only checks. None of these need sudo:
$ command -v ld.gold
/usr/bin/ld.gold
$ ld.gold --version
GNU gold (GNU Binutils for Ubuntu 2.42) 1.16
$ ld.gold --print-output-format
elf_x86_64
The exact path may differ. The output format is the useful checkpoint here: an x86-64 linker should report an x86-64 ELF format, not an AArch64 one. Use ld.gold --help for the full option list on this installed build.
Work in a temporary directory so the experiment cannot overwrite a project binary. The assembly exits with status 7 through the Linux x86-64 system call interface:
$ workdir=$(mktemp -d)
$ trap 'rm -rf "$workdir"' EXIT
$ cd "$workdir"
$ printf '%s\n' \
'.section .text' \
'.globl _start' \
'_start:' \
' mov $60, %rax' \
' mov $7, %rdi' \
' syscall' > exit.s
$ as -o exit.o exit.s
$ file exit.o
exit.o: ELF 64-bit LSB relocatable, x86-64, ...
The ... stands in for host-specific wording from file. Check the architecture, not the punctuation of its description. If as reports an incompatible architecture, stop here and use the assembler that belongs to the same target as ld.gold.
Pass the object file and an explicit output name. -o controls the output path; leave it out and the linker falls back to its default output name, which is easy to miss in a scripted build:
$ ld.gold -o exit exit.o
$ file exit
exit: ELF 64-bit LSB executable, x86-64, ...
$ ./exit
$ printf 'program status: %s\n' "$?"
program status: 7
This output is deliberately freestanding: no C runtime, no library search. The installed gold defaults to a non-PIE executable; reach for -pie only when the inputs and runtime contract actually require position independence. A normal C or C++ program needs compiler startup objects, libraries and a compatible dynamic linker, so use the compiler driver with its gold-selection option whenever you want the full language runtime. Calling ld.gold directly makes sense when you are deliberately managing those inputs yourself.
Checkpoint: the link succeeded, the output is an ELF executable, and its exit status is 7. An undefined _start means the assembly was not assembled or the object was not passed. An architecture mismatch means you should inspect both files with file before touching any linker flags.
A map records how input sections and symbols contributed to the output. Write it to a separate file with -Map:
$ ld.gold -Map=exit.map -o exit-mapped exit.o
$ test -s exit.map
$ sed -n '1,12p' exit.map
Archive member included to satisfy reference by file (symbol)
Allocating common symbols
Common symbols
Memory map
LOAD exit.o
.text 0x00000000004000b0 0x15 exit.o
0x00000000004000b0 _start
Addresses and byte counts vary with the linker build and layout. What matters is that the map exists and names the input object and _start. -M sends the same kind of map to standard output instead, handy for a one-off diagnostic but noisy in a build log.
--gc-sections removes unreferenced input sections. It only has something to discard when code was compiled or assembled into separate sections, and the entry point or another retained root must still reach the code you actually need:
$ printf '%s\n' \
'.section .text' \
'.globl unused_function' \
'unused_function:' \
' ret' \
'.globl _start' \
'_start:' \
' mov $60, %rax' \
' mov $7, %rdi' \
' syscall' > sections.s
$ as -o sections.o sections.s
$ ld.gold --gc-sections --print-gc-sections -o sections sections.o
Removing unused section from file sections.o: .text
The diagnostic may include more detail, and the exact section layout is build-dependent. Do not enable this option blindly: an entry point, linker script rule or explicit retention rule must keep every section required at runtime. Run the resulting program and inspect its symbols before adopting this in a release link.
-T FILE loads a linker script, while --version-script FILE controls symbol versions for a shared object. Both can change addresses, visibility and the runtime ABI, so review them as source code, keep them under version control, and test the exact output. Do not paste in an untrusted script, and do not use --noinhibit-exec to treat a failed link as successful: that option can produce an output file even when errors occurred.
Library search has two distinct parts. -L DIR adds a link-time search directory and -l NAME searches for a library. -rpath DIR records a runtime search path, so it changes where the loader may look for shared libraries. Prefer trusted, deliberate paths: an accidental writable runtime directory can turn into a code-loading security problem.
For repeatable diagnostics, add --trace to print each input file, or --fatal-warnings to make warnings fail the link outright. These are ordinary command-line choices, not repair mechanisms. If a service binary is being replaced, stop the service through its normal administrator workflow, keep the previous file, and have a tested rollback ready before deploying. Everything in this guide only creates files under the temporary directory and needs no service disruption or elevated privilege.
ld.gold --version and --print-output-format matched the toolchain you intended.--gc-sections, -T, -L and -rpath alter the result.