Quiz Entry - updated: 2026.07.10
Why does PC-relative relocation use an addend of -4 on x86_64?
Because by the time the CPU reads the 4-byte offset, the PC has already advanced past it; the −4 addend cancels that 4-byte head start.
* The relocation patches the offset field at 0xa, but the CPU adds the offset to the PC at 0xe — four bytes further on, which the −4 addend cancels. *
When the CPU executes a call:
- The opcode byte
e8sits at0x9; the 4-byte offset field follows at0xa - A
call rel32is 5 bytes, so once it's fetched the PC already points to the next instruction at0xe - The CPU computes the target as
PC + offset = 0xe + offset
Example disassembly:
Address Bytes Instruction
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: b8 00 00 00 00 mov $0x0,%eax
9: e8 00 00 00 00 callq e <main+0xe> ← offset field at 0xa
e: 5d pop %rbp ← PC points here after the call is fetched
Relocation calculation:
encoded_offset = target - PC
= (symbol + addend) - PC
= (symbol + addend) - (position + 4)
The relocation's position is the offset field at 0xa; the PC is 0xe, so PC - position = 0xe - 0xa = 4. The linker folds that constant into the addend as -4.
That's why R_X86_64_PC32 and R_X86_64_PLT32 use addend = -4: to compensate for the PC being 4 bytes ahead of the offset field by the time the call is decoded.
Go deeper:
Eli Bendersky — Load-time relocation of shared libraries — the offset arithmetic behind PC-relative relocations, worked through real disassembly.