Worked examples — Object files — .o - .obj, symbol table, relocation entries
You have met the relocation formula on the parent note:
This page does one thing: it throws every kind of case at that formula so you never meet a scenario in the wild you haven't already solved on paper.
The scenario matrix
Think of a relocation as having a few "knobs". Each knob has a small set of settings. The matrix below lists every setting we must cover, and which worked example nails it.
| # | Case class | The knob's settings | Covered by |
|---|---|---|---|
| A | Reloc kind | absolute () vs PC-relative () | Ex 1, Ex 2 |
| B | Displacement sign | forward jump () vs backward jump () | Ex 2, Ex 3 |
| C | Addend | zero addend vs non-zero () | Ex 1, Ex 2 |
| D | Symbol binding | DEFINED here vs UND (undefined, needs another .o) |
Ex 1, Ex 4 |
| E | Degenerate distance | (patch points at itself) | Ex 5 |
| F | Zero-size input | .bss symbol — takes no file bytes |
Ex 6 |
| G | Overflow / limit | displacement doesn't fit in 32 bits | Ex 7 |
| H | Word problem | real linker command, real readelf reading |
Ex 8 |
| I | Exam twist | two definitions of one symbol (multiple-definition) | Ex 9 |
For the picture-based examples we'll use one number line: memory addresses increasing to the right. See it once:
Ex 1 — Absolute reloc, zero addend (cells A, C, D-defined)
Forecast: guess before reading — will the bytes be g's address, or a distance? (Absolute → the address itself.)
- Identify the knobs. Why this step? Every relocation is solved by first reading , , off the entry. Here , , and because it's absolute we don't need .
- Pick the formula: absolute ⇒ . Why this step? The bytes of
pmust hold the actual address ofgso the running program can dereference it. A distance would be meaningless for a data pointer. - Compute: . Why this step? Just the arithmetic; the addend is 0 because
&ghas no+offset.
Verify: . Plugging back, ✓. Units: an address, correct for a pointer.
Ex 2 — PC-relative forward call, addend (cells A, B-forward, C-nonzero, D-UND)
Forecast: add is ahead of the call — so is the displacement positive or negative?
- Read knobs: , , . Why this step? PC-relative needs all three; is the slot itself, not the next instruction.
- Formula: PC-relative ⇒ . Why this step? The CPU computes the branch target as (address of next instruction) displacement. The addend soaks up the 4 displacement bytes between and that next instruction, so the jump lands exactly on
add. - Compute in decimal: , . . Why this step? , a small positive displacement — forward, matching our forecast.
Verify: next-instruction address . Target ✓. Lands exactly on add.
Ex 3 — PC-relative backward call (cell B-backward)
Forecast: target is below in memory → expect a negative displacement.
- Knobs: , , . Why this step? Same PC-relative case; only the numbers moved.
- . Why this step? Negative, as forecast — the CPU jumps backwards.
- Interpret the bytes: the field is a signed 32-bit integer; is stored two's-complement as
0xFFFFFEFC. Why this step? Machine code never stores a minus sign; negativity lives in the top bit.
Verify: next instruction . Target ✓.
Ex 4 — Undefined symbol that is never resolved (cell D-UND, error path)
Forecast: does this fail at compile or at link time?
- Compiling
main.calone succeeds —addwas declared (extern), so the compiler is happy. Why this step? The compiler only checks types & declarations, not existence of the definition. - The
.ocarries a symbol-table entryaddmarked UND, plus a relocation waiting for its address. Why this step? This is the "I am looking foradd" badge from the parent note. - The linker scans every input for a DEFINED
add. Finding none, it cannot compute , so it cannot apply . Why this step? No ⇒ no arithmetic possible ⇒ hard stop. - Result:
undefined reference to 'add'. Why this step? This is a link error; adding a#includewould not help — you must supply the defining.o/library. See Linker — symbol resolution and relocation.
Verify: the fix is gcc main.o add.o. Conceptually: number of unresolved UND symbols must reach before an executable is produced. Here it was , so failure. ✓ (see Static vs Dynamic Linking for how a shared lib supplies later instead.)
Ex 5 — Degenerate case: symbol patches itself, (cell E)
Forecast: if "here" equals "there", is the displacement zero?
- Knobs: , . Why this step? Set up the degenerate values.
- . Why this step? The and cancel; only the addend survives.
- Read it: means "jump to 4 bytes before the next instruction" — i.e. back to the start of this instruction's operand. Why this step? Shows the addend is doing real geometric work even when ; it's never just decoration.
Verify: next instruction . Target ✓. Even in the degenerate case the formula lands on exactly.
Ex 6 — Zero-size file input: .bss (cell F)
Forecast: guess the on-disk byte count for a million ints.
bigis uninitialised ⇒ goes to.bss. Why this step?.bssis the zero-init section; it stores a size, not the zeros themselves (see Sections — .text .data .bss .rodata).- File cost bytes. The symbol records size bytes. Why this step? The loader zero-fills at run time, so the disk image stays tiny.
- Any pointer to
bigstill needs an absolute reloc () once the loader assigns its address. Why this step? Zero file bytes doesn't mean zero relocations — the address is still unknown at compile time.
Verify: if this were .data, file would grow by bytes. In .bss it grows by . Savings bytes ✓.
Ex 7 — Limiting case: displacement overflow (cell G)
Forecast: does a 3-gigabyte forward jump fit in 32 signed bits?
- Max positive displacement . Why this step? Establishes the ceiling of a signed 32-bit slot.
- Required . Why this step? Compare need vs capacity — the number cannot be represented.
- The linker emits
relocation truncated to fit: R_X86_64_PC32. Why this step? Writing the number would silently corrupt the top bits, so the linker refuses. Fix: compile with a larger code model (or useR_X86_64_64absolute / a GOT/PLT indirection).
Verify: ⇒ overflow ✓. And , so a 64-bit absolute reloc would fit. ✓
Ex 8 — Word problem: reading a real readelf -r line (cell H)
Forecast: which of Ex 1–7 does this reduce to?
- Compute the slot's final address : base offset . Why this step? The reloc's
Offsetis within the section; needs the section's final placement added. - Read (from
add - 4), . Why this step? These are the other two knobs, straight off the line. - It's
R_X86_64_PLT32⇒ PC-relative ⇒ . Why this step? Same case as Ex 2, now driven by real tool output. - Compute: , . . Why this step? Final answer, the byte value (little-endian:
2B 00 00 00).
Verify: next instr ; target ✓.
Ex 9 — Exam twist: two definitions of one symbol (cell I)
Forecast: two globals, same name — merge, error, or silent bug?
- Each compiler sees only its own file, so each emits a GLOBAL tentative definition symbol
x. Neither compilation fails. Why this step? The compiler has no cross-file view; uniqueness of globals is the linker's job (see Name mangling in C++ for how C++ makes symbol names unique, unlike C). - At link, the linker finds two DEFINED
xwith GLOBAL binding. Why this step? Each "I can providex" badge must be unique; two providers is a conflict. - Outcome depends on the era: modern GCC (
-fno-common) ⇒multiple definition of 'x'error; legacy common-symbol behaviour ⇒ silently merges into onex(a classic aliasing bug). Why this step? Shows both the safe error and the dangerous silent path. - Fix:
static int x;(file-local, becomes a LOCAL symbol) or one definitionint x;plusextern int x;elsewhere. Why this step? Restores the "exactly one provider" invariant.
Verify: count of GLOBAL DEFINED x symbols must be for a clean link. Two files ⇒ count ⇒ conflict ✓.
Recall Which formula for each reloc kind?
Absolute (R_X86_64_64) uses :::
PC-relative (R_X86_64_PC32 / PLT32) uses :::
Why the in PC-relative? ::: the CPU adds the stored value to the next instruction's address, so the linker must store a distance from here, not an absolute address.
Recall Sign & overflow sanity
A forward jump gives displacement of what sign? ::: positive A backward jump gives what sign? ::: negative A 4-byte PC32 field holds what range? ::: signed 32-bit, ; beyond it the linker says "relocation truncated to fit"