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