Skip to content
Advertisement

How does the ELF64 loader know to update the initial addresses in .got.plt?

Consider the following program hello.c:

#include <stdio.h>

int main(int argc, char** argv)
{
    printf("hello");
    return 0;
}

The file is compiled with gcc -o hello -Og -g hello.c and then loaded with gdb hello.

Inspecting the GOT for the call to printf with p 'printf@got.plt' gives

$1 = (<text from jump slot in .got.plt, no debug info>) 0x1036 <printf@plt+6>

which is the offset of the second instruction in the corresponding PLT entry relative to the start of the section.

After starting and linking the program with starti, p 'printf@got.plt' now gives

$2 = (<text from jump slot in .got.plt, no debug info>) 0x555555555036 <printf@plt+6>

which is the absolute address of the second instruction in the corresponding PLT entry.

I understand what is going on and why. My question is how does the dynamic linker/loader know to update the section offset (0x1036) to the absolute address (0x555555555036)?

A p &'printf@got.plt' before linking gives

$1 = (<text from jump slot in .got.plt, no debug info> *) 0x4018 <printf@got.plt>

and readelf -r simple shows a relocation entry for this address

Relocation section '.rela.plt' at offset 0x550 contains 1 entry:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000004018  000200000007 R_X86_64_JUMP_SLO 0000000000000000 printf@GLIBC_2.2.5 + 0

But my reading of the System V Application Binary Interface AMD64 Architecture Processor Supplement, p.76, is that these relocation entries are only used when LD_BIND_NOW is non-null. Are there other relocation entries that I missed? What is the mechanism for rebasing offsets relative to the GOT’s ultimate address?

Advertisement

Answer

According to Drepper’s How To Write Shared Libraries, the dynamic linker relocates two kinds of dependencies:

  1. Relative relocations: dependencies to locations within the same object. The linker simply adds the load address of the object to the offset to the target destination.
  2. Symbol relocations: more complicated and expensive process based on a sophisticated symbol resolution algorithm.

For the PLT’s GOT, Drepper states (ยง1.5.5) At startup time the dynamic linker fills the GOT slot with the address pointing to the second instruction of the appropriate PLT entry. A reading of the glibc source code suggests that the linker indeed loops through the R_X86_64_JUMP_SLOT relocations (elf/do-rel.h:elf_dynamic_do_Rel) and increments the offsets they contain (sysdeps/x86_64/dl-machine.h:elf_machine_lazy_rel):

if (__glibc_likely (r_type == R_X86_64_JUMP_SLOT))
  {
    /* Prelink has been deprecated.  */
    if (__glibc_likely (map->l_mach.plt == 0))
      *reloc_addr += l_addr;
    else
      ...

when lazy PLT binding is used (the default case).

User contributions licensed under: CC BY-SA
4 People found this is helpful
Advertisement