I assembled the following file with GAS, the GNU assembler:
.extern foo
.global bar
.section .text
bar:
call foo
ret
In the object file it produced there is a relocation entry of type R_X86_64_PLT32
in place where foo
is called. This relocation entry is resolved by entering value L + A - P
, where L
is the address of the PLT
entry for the symbol, A
addend, and P
is the place referenced by the relocation entry.
There is no PLT
defined in this file. Why did it choose to this type of relocation?
When linked with the object module produced by assembling the following file:
.global foo
.section .text
foo:
ret
The relocation entry gets resolved as it was of type R_X86_64_PC32
.
foo
could have been in a shared library, e.g. if foo
was getpid
or exit
. The linker has to invent a PLT entry in that case. .extern
is a no-op in GAS, it treats any symbol not defined in this source file as external. Which can be in another shared library, or just in another file to be linked into the same executable or shared object as this code.
I guess the R_X86_64_PLT32
relocation gives it permission to do that, instead of requiring the relocation to be direct? I don't know, I'd previously assumed this magic was all in the linker, but I guess it's partly the assembler choosing a relocation type which allows that. (If anyone knows more details, please post your own answer or comment.)
When the symbol does resolve statically (found in another .o
supplied to the linker), the linker can relax the relocation to a direct call, instead of creating a PLT entry for it. That's what's happening in your case.
The end result of all this:
call foo
eventually assembles + links into a call rel32
if the symbol is defined in another file you're linking staticallycall foo@plt
, with the linker inventing a PLT entry, so it still Just Works. (A call rel32
to a PLT stub which starts with jmp *got_entry_for_foo(%rip)
, which after the first call will hold the function address)IIRC, you had to manually write call foo@plt
if you were going to link into a PIE executable (which is a shared object). But I think this changed, and now the linker will auto-magically invent a PLT for you in that case, too. Using an R_X86_64_PLT32
may be a recent hack to make that work, or maybe it's what GAS always did.
See also Can't call C standard library function on 64-bit Linux from assembly (yasm) code for an example of ld
relaxing the relocation entries created by assembling call bar@plt
and call *bar@GOTPCREL(%rip)
(Like you get from Clang/GCC -fno-plt
)