Runtime#
The “runtime” for an assembly program is the assemble, link,
load chain plus the ABI that fixes the boundaries: how
argc / argv reach _start, how libc’s crt0 calls
main, how shared libraries are loaded by ld.so, how
position-independent code addresses data and functions through
the GOT and PLT.
For the toolchain commands themselves, see Tooling.
Pipeline#
Stage |
What happens |
|---|---|
assemble |
|
link |
one or more |
load |
kernel’s |
A minimal sequence.
$ nasm -felf64 hello.asm -o hello.o
$ ld -o hello hello.o # freestanding
$ ./hello
$ gcc -nostartfiles -o hello hello.o # with libc, no crt0
$ gcc -o hello hello.o # with libc + crt0 (calls main)
ELF#
An ELF binary on Linux has two views: sections (used by the
linker; .text, .data, .rodata, .bss, .symtab,
.rela.text, .eh_frame) and segments (used by the
loader; LOAD, DYNAMIC, INTERP, PHDR, GNU_RELRO).
readelf -l ./binary shows the segments; readelf -S shows
the sections; objdump -d disassembles .text.
$ readelf -h ./hello # header
$ readelf -S ./hello # sections
$ readelf -l ./hello # segments / program headers
$ objdump -d ./hello # disassembly
Entry points#
A freestanding ELF binary’s entry point is the symbol _start
(by convention; the linker accepts any name via -e). The
kernel sets up the stack with argc, argv, envp,
auxv (see CLI) and jumps.
A libc-linked binary’s entry point is still _start (provided
by crt0), which sets up the C runtime (heap, TLS, stdio
buffers, __libc_start_main) and then calls main.
kernel execve
-> _start (crt1.o)
-> __libc_start_main
-> __libc_init_first
-> _init (constructors)
-> main(argc, argv, envp)
-> _fini (destructors)
-> exit(status)
Static vs dynamic linking#
Static: every required
.ois copied into the binary. No runtime dependencies; portable across hosts that share an ABI.-staticflag.Dynamic: the binary records required
.sonames; the dynamic loader (ld.so) maps them in at startup. Smaller binaries; binary depends on the host’s libc version.
$ gcc -static -o hello hello.c
$ gcc -o hello hello.c # dynamic
$ ldd ./hello # see required .so's
$ readelf -d ./hello # see DYNAMIC entries
PIE and PIC#
PIE (Position-Independent Executable): the binary can load at any base address; ASLR randomises the base each run. Modern Linux distros build PIE by default.
PIC (Position-Independent Code): the code does not assume a fixed load address; mandatory for
.soshared libraries.
PIC and PIE require RIP-relative addressing on x86-64 (mov
rax, [rel msg] in NASM, leaq msg(%rip), %rax in GAS).
; PIC-safe: RIP-relative
lea rsi, [rel msg]
mov rax, qword [rel counter]
; Not PIE-safe: absolute address
mov rsi, msg ; relocates; fails under PIE
GOT and PLT#
When the dynamic loader resolves a function from a shared
library, the call goes through the PLT (Procedure Linkage
Table) which jumps through the GOT (Global Offset Table).
First call: stub resolves via ld.so and patches GOT.
Subsequent calls: direct jump through GOT.
call printf@plt ; in the operator's code
printf@plt:
jmp qword [rel printf@got] ; through GOT
push 0 ; reloc index
jmp PLT0 ; resolver
For data, use the GOT directly through
@GOTPCREL references (in GAS) or NASM’s wrt ..got /
wrt ..gotpcrel. The compiler emits these for the operator
in compiled code; pure assembly rarely needs them.
Relocations#
An object file’s instructions that reference external symbols
carry relocations, requests for the linker to fill in the
right address. readelf -r ./file.o shows them.
Reloc type (x86-64) |
Meaning |
|---|---|
|
32-bit PC-relative; for near calls and jumps |
|
32-bit PC-relative to PLT entry |
|
32-bit PC-relative to GOT entry |
|
64-bit absolute; not PIE-safe |
|
32-bit absolute (zero / sign extended) |
If the linker rejects an absolute reloc in a PIE build, the operator switches to a RIP-relative form.
TLS (thread-local storage)#
On x86-64 Linux, TLS is accessed through the fs segment
register; per-thread variables live at negative offsets from
[fs:0]. The compiler emits this for __thread variables;
hand-written assembly uses the same pattern.
mov rax, qword [fs:0x28] ; stack canary (classic example)
ARM64 TLS uses mrs x0, tpidr_el0.
ABI summary#
Memorise three:
System V AMD64 (Linux / macOS / BSD on x86-64). See Functions.
Microsoft x64 (Windows on x86-64).
AAPCS64 (ARM64 everywhere).
These determine register usage in calls, return values, stack alignment, and which registers are caller-vs callee-saved.
Sections and segments#
The operator’s main sections.
Section |
Use |
|---|---|
|
executable code. R-X. |
|
read-only data (string literals, lookup tables). R. |
|
initialised read-write data. RW. |
|
zero-initialised data; takes no file space. RW. |
|
DWARF unwind information. |
|
symbol tables. |
|
relocations. |
The loader groups sections into segments by permission; .text
and .rodata go into the R-X LOAD segment, .data and
.bss into the RW LOAD segment.