assemblylinkerx86-64gnu-assemblerrelocation

GAS creates a PLT relocation entry for call to an extern symbol?


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.


Solution

  • 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:

    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)