1

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?

tbrk
  • 1,290
  • 1
  • 13
  • 20
  • After some more digging, it seems to me that the linker/loader does use the `R_X86_64_JUMP_SLO` relocation entries to rebase the offsets into the PLT when the segment containing `.plt.got` is loaded. The relevant source code seems to be in glibc at `elf/do-rel.h:elf_dynamic_do_Rel` and `sysdeps/x86_64/dl-machine.h:elf_machine_lazy_rel`. As I don't completely understand the elf loader code, any expert confirmation of this would be welcome! โ€“ tbrk May 22 '20 at 09:44
  • 1
    https://www.akkadia.org/drepper/dsohowto.pdf explains it thoroughly, no answer can do a better job, probably. โ€“ Maxim Egorushkin May 22 '20 at 12:01
  • Thanks @MaximEgorushkin. I read through the reference you suggest and I guess you're probably right. I've added an answer with everything I currently understand. โ€“ tbrk May 22 '20 at 14:35

1 Answers1

0

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

tbrk
  • 1,290
  • 1
  • 13
  • 20