Call relocation types
Most architectures encode branch/call instructions with a PC-relativedisplacement. Some architectures use two ELF relocation types for a callinstruction:
1 |
# i386, x86-64 |
This post describes why I think this happened.
Static linking: one typesuffices
In the static linking model, all symbols are resolved at link time:every symbol is either defined in a relocatable object file or anundefined weak symbol. A branch instruction with a PC-relativedisplacement—x86 call, m68k bsr.l, s390brasl—can reuse the same PC-relative data relocation typeused for data references.
- i386:
R_386_PC32for bothcall fooand.long foo - . - x86-64:
R_X86_64_PC32for bothcall fooand.long foo - . - m68k:
R_68K_PC32forbsr.l foo,move.l var,%d0, and.long foo - . - s390x:
R_390_PC32DBLforbrasl %r14, foo,larl %r1, var, and.long foo - .
No separate "call" relocation type is needed. The linker simplypatches the displacement to point to the symbol address.
Dynamic linking changes thepicture
With System V Release 4 style shared libraries, variable access andfunction calls diverge.
For variables and function addresses, a referencefrom one component to a symbol defined in another cannot use a plainPC-relative relocation, because the distance between the two componentsis not known at link time. The -fno-pic relocatable files.)
For function calls, the situation is different. Acall instruction has "transfer control there by any means" semantics -the caller usually doesn't care how the callee is reached, onlythat it gets there. This allows the linker to interpose a
Variable accesses do not have the same semantics - so the PC-relativedata relocation type cannot be reused on a call instruction.
This is why separate branch relocation types were introduced:R_386_PLT32, R_68K_PLT32,R_390_PLT32DBL, and so on. The relocation type carries thesemantic information: "this is a function call that can use PLTindirection."
Misleading names
The @plt notation in assembly and the PLT32relocation type names are misleading. They suggest that a PLT entry isinvolved, but that is often not the case - when the callee is defined inthe same component, the linker resolves the branch directly—no PLT entryis created.
R_386_CALL32 and R_X86_64_CALL32 would havebeen a better name.
In addition, the @plt notation itself is problematic asa
Architecture comparison
Single type (clean design). Some architecturesrecognized from the start that one call relocation type is sufficient.The linker can decide whether a PLT stub is needed based on the symbol'sbinding and visibility.
- AArch64:
R_AARCH64_CALL26for bothblandb. - PowerPC64 ELFv2:
R_PPC64_REL24forbl.
These architectures never had the naming confusion—there is no "PLT"in the relocation name, and no redundant pair.
Redundant pairs (misguided). Some architecturesintroduced separate "PLT" and "non-PLT" call relocation types, creatinga distinction without a real difference.
- SPARC:
R_SPARC_WPLT30alongsideR_SPARC_WDISP30. The assembler decides at assembly timebased on PIC mode and symbol preemptivity, when ideally the linkershould make these decisions. - PPC32:
R_PPC_REL24(non-PIC) andR_PPC_PLTREL24(PIC) have genuinely different semantics(the addend ofR_PPC_PLTREL24encodes the r30 GOT pointersetup). However,R_PPC_LOCAL24PCis entirely useless—alloccurrences can be replaced withR_PPC_REL24. - RISC-V:
R_RISCV_CALL_PLTalongside the now-removedR_RISCV_CALL. The community recognized that only onerelocation is needed.R_RISCV_CALL_PLTis kept (despite thename, does not mandate a PLT entry).
x86-64 started with R_X86_64_PC32 forcall foo (inherited from the static-linking mindset) andR_X86_64_PLT32 for call foo@plt (symbols notcompile-time known to be non-preemptible). In 2018, binutils R_X86_64_PLT32 for call foo. LLVMintegrated assembler followed suit.
This means R_X86_64_PC32 is now effectively reserved fordata references, and R_X86_64_PLT32 marks all calls—a cleanseparation achieved by convention.
However, GNU Assembler still produces R_X86_64_PC32 whencall foo references an STB_LOCAL symbol. I'vesent a patch to fix this:
GCC's s390 port seems to always generate @plt (even forhidden visibility functions), leading to R_390_PLT32DBLrelocations.
Range extension thunks
A dedicated call relocation type also enables
On AArch64 and PowerPC64, this is well established. On x86-64, the±2GiB range of call/jmp has been sufficient sofar, but as executables grow, R_X86_64_PLT32 rather than R_X86_64_PC32 forcalls.
Recommendation forfuture architectures
Use a single call relocation type—no "PLT" vs. "non-PLT" distinction.The assembler should emit the same relocation for every callinstruction, and the linker, which knows whether the symbol ispreemptible, decides whether a PLT stub or range extension thunk isneeded. AArch64's R_AARCH64_CALL26 and PowerPC64 ELFv2'sR_PPC64_REL24 demonstrate this approach well.
See also
- Allabout Procedure Linkage Table for how PLT works
- All aboutGlobal Offset Table for the data-access counterpart
Copyrelocations, canonical PLT entries and protected visibility for thevariable-access complications Relocationgeneration in assemblers for how assemblers decide which relocationsto emit Longbranches in compilers, assemblers, and linkers for range extensionthunks