普通视图

发现新文章,点击刷新页面。
今天 — 2026年2月17日首页

Call relocation types

作者 MaskRay
2026年2月16日 16:00

Most architectures encode branch/call instructions with a PC-relativedisplacement. This post discusses a specific category of branchrelocations: those used for function calls and tail calls. Somearchitectures use two ELF relocation types for a call instruction:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# i386, x86-64
call foo # R_386_PC32, R_X86_64_PC32
call foo@plt # R_386_PLT32, R_X86_64_PLT32

# m68k
bsr.l foo # R_68K_PC32
bsr.l foo@plt # R_68K_PLT32

# s390/s390x
brasl %r14, foo # R_390_PC32DBL
brasl %r14, foo@plt32 # R_390_PLT32DBL

# sparc
call foo, 0 # not PIC: R_SPARC_WDISP30
call foo, 0 # gas -KPIC: R_SPARC_WPLT30

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_PC32 for both call foo and.long foo - .
  • x86-64: R_X86_64_PC32 for both call fooand .long foo - .
  • m68k: R_68K_PC32 for bsr.l foo,move.l var,%d0, and .long foo - .
  • s390x: R_390_PC32DBL for brasl %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 Global OffsetTable was introduced for this purpose, along with GOT-generatingrelocation types. (Additionally, copyrelocations are a workaround for external data symbols from-fno-pic relocatable files.) To satisfy the addressuniqueness requirement, a PC-relative data relocation in an executablemust resolve to the same address as its counterpart in a sharedobject—this is why GOT indirection is used for symbols not known to bepreemptible.

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 PLT stubwhen the target is in another component, without any special codesequence at the call site.

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 relocationspecifier.

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_CALL26 for both bl andb.
  • PowerPC64 ELFv2: R_PPC64_REL24 forbl.

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_WPLT30 alongsideR_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 of R_PPC_PLTREL24 encodes the r30 GOT pointersetup). However, R_PPC_LOCAL24PC is entirely useless—alloccurrences can be replaced with R_PPC_REL24.
  • RISC-V: R_RISCV_CALL_PLT alongside the now-removedR_RISCV_CALL. The community recognized that only onerelocation is needed. R_RISCV_CALL_PLT is 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 https://sourceware.org/bugzilla/show_bug.cgi?id=22791switched to 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: [PATCHv2] x86: keep PLT32 relocation for local symbols instead of convertingto PC32.

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 rangeextension thunks: when the target is out of the branch instruction'srange, the linker can insert a thunk without the compiler or assemblerneeding to know. This works precisely because the relocation marks thesite as a branch—the linker knows it can redirect through a thunk.

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, relocationoverflow becomes a concern. There are proposals to add rangeextension thunks to x86-64, which would rely on the linker being able toidentify call sites—another reason to consistently useR_X86_64_PLT32 rather than R_X86_64_PC32 forcalls.

Recommendation forfuture architectures

For a specific instruction or pseudo instruction for function callsand tail calls, use a single call relocation type—no "PLT" vs. "non-PLT"distinction. The assembler should emit the same relocation, and thelinker, which knows whether the symbol is preemptible, decides whether aPLT stub or range extension thunk is needed. AArch64'sR_AARCH64_CALL26 and PowerPC64 ELFv2'sR_PPC64_REL24 demonstrate this approach well.

This discussion does not apply to intra-function branches, whichtarget local labels.

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
❌
❌