When you compile a function call like printf("hello"), the compiler doesn't know where printf will eventually live in memory. It leaves a placeholder and creates a relocation entry — a note that says "patch this address when you know the final location."
Without relocations, the linker couldn't combine object files into a program, and the dynamic linker couldn't load shared libraries.
A relocation entry is a "TODO note" for the linker:
When the linker resolves a symbol, it walks all relocation entries for that symbol, computes the correct value, and writes it into the binary at the specified offset.
ELF defines two relocation structures:
# Elf64_Rel (without addend, rarely used) # r_offset (uint64_t) Where to patch # r_info (uint64_t) Symbol index + relocation type # Elf64_Rela (with addend, used on x86-64) # r_offset (uint64_t) Where to patch # r_info (uint64_t) Symbol index + relocation type # r_addend (int64_t) Constant addend for the computation
The r_info field packs two values:
Relocations live in sections named .rela.text (for code), .rela.data (for data), .rela.dyn (dynamic relocations), and .rela.plt (PLT stubs).
$ readelf -rW a.o Relocation section '.rela.text' at offset 0x2a0 contains 3 entries: Offset Info Type Sym. Value Sym. Name + Addend 00000000000c 000500000004 R_X86_64_PLT32 0000000000000000 printf - 4 000000000015 000300000004 R_X86_64_PLT32 0000000000000000 foo - 4 000000000022 000600000002 R_X86_64_PC32 0000000000000000 bar + 0
This says:
After the linker resolves them:
$ objdump -d a.out | grep -A1 "call"
1125: e8 b6 ff ff ff call 10e0 printf@plt
112e: e8 4d 00 00 00 call 1180 foo
1139: 8b 05 c1 00 00 00 mov eax,rip+0xc1 # bar's value# See relocations in an object file # reloc_test.c: calls an external global variable gcc -c reloc_test.c readelf -rW reloc_test.o # See relocations in a final executable gcc reloc_test.c -o reloc_test readelf -rW reloc_test | head -20
What is the purpose of the r_addend field in Elf64_Rela?
Why are most modern systems using Elf64_Rela instead of Elf64_Rel?
Each relocation entry specifies a type that determines how the patched value is computed. The next concept covers the different relocation types and when each one is used.