普通视图

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

Call relocation types

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

Most architectures encode direct branch/call instructions with aPC-relative displacement. This post discusses a specific category ofbranch relocations: those used for direct function calls and tail calls.Some architectures use two ELF relocation types for a callinstruction:

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@plt # 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 pointerequality requirement, a PC-relative data relocation in anexecutable must resolve to the same address as its counterpart in ashared object—this is why GOT indirection is used for symbols not knownat compile time to be preemptible.

For direct function calls, the situation isdifferent. A call instruction has "transfer control there by any means"semantics - the caller usually doesn't care how the callee isreached, only that it gets there. This allows the linker to interpose aPLTstub when the target is in another component, without any specialcode sequence at the call site. Alternatively, some architecturessupport an indirect call sequence that bypasses PLT entirely: -fno-plton x86 and -mno-plton MIPS (o32/n32 non-PIC).

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 bl andR_AARCH64_JUMP26 for b.
  • 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

When a branch target is out of range, some architectures allow thelinker to insert a rangeextension thunks: On AArch64 and PowerPC64, this is wellestablished.

On x86-64, the ±2GiB range of call/jmp hasbeen sufficient so far, but as executables grow, relocationoverflow becomes a concern. There are proposals to add rangeextension thunks to x86-64, which would require the linker to identifycall sites-a PC-relative data relocation like R_X86_64_PC32would not be suitable (due to pointer equality requirement), makingconsistent use of R_X86_64_PLT32 for calls all the moreimportant.

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 is needed. Optionally enable range extension thunks for therelocation type. AArch64's R_AARCH64_CALL26 and PowerPC64ELFv2's R_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
❌
❌