linuxelfdynamic-linkingrelocation

Who performs runtime relocations?


I'm trying to better understand runtime relocations in Linux, specifically who performs them in different situations. Below is my current understanding, is it accurate?

Thanks


Solution

  • Your answers are all (mostly) correct.

    You can observe where the relocation is happening using a debugger, and confirm your understanding.

    Example:

    #include <stdio.h>
    
    int main()
    {
      printf("%d\n", 123);
      return 0;
    }
    

    Let's start with position-dependent, dynamically linked binary.

    gcc -g -fno-pie -no-pie t.c
    gdb -q ./a.out
    Reading symbols from ./a.out...
    (gdb) starti
    Starting program: /tmp/a.out
    
    Program stopped.
    0x00007ffff7fd2090 in _start () from /lib64/ld-linux-x86-64.so.2
    (gdb) disas main
    Dump of assembler code for function main:
       0x0000000000401126 <+0>:     push   %rbp
       0x0000000000401127 <+1>:     mov    %rsp,%rbp
       0x000000000040112a <+4>:     mov    $0x7b,%esi
       0x000000000040112f <+9>:     mov    $0x402010,%edi
       0x0000000000401134 <+14>:    mov    $0x0,%eax
       0x0000000000401139 <+19>:    call   0x401030 <printf@plt>
       0x000000000040113e <+24>:    mov    $0x0,%eax
       0x0000000000401143 <+29>:    pop    %rbp
       0x0000000000401144 <+30>:    ret
    End of assembler dump.
    
    (gdb) disas 0x401030
    Dump of assembler code for function printf@plt:
       0x0000000000401030 <+0>:     jmp    *0x2fe2(%rip)        # 0x404018 <printf@got.plt>
       0x0000000000401036 <+6>:     push   $0x0
       0x000000000040103b <+11>:    jmp    0x401020
    End of assembler dump.
    

    Here we can see that the address to be relocated is 0x404018. Let's see where that address gets updated:

    (gdb) watch *(void**)0x404018
    Hardware watchpoint 1: *(void**)0x404018
    (gdb) c
    Continuing.
    
    Hardware watchpoint 1: *(void**)0x404018
    
    Old value = (void *) 0x401036 <printf@plt+6>
    New value = (void *) 0x7ffff7e54270 <printf>
    0x00007ffff7fe0f10 in _dl_fixup (l=<optimized out>, reloc_arg=<optimized out>) at dl-runtime.c:146
    146     dl-runtime.c: No such file or directory.
    (gdb) bt
    #0  0x00007ffff7fe0f10 in _dl_fixup (l=<optimized out>, reloc_arg=<optimized out>) at dl-runtime.c:146
    #1  0x00007ffff7fe84fe in _dl_runtime_resolve_xsavec () at ../sysdeps/x86_64/dl-trampoline.h:126
    #2  0x000000000040113e in main () at t.c:5
    
    (gdb) info symbol $pc
    _dl_fixup + 288 in section .text of /lib64/ld-linux-x86-64.so.2
    

    So in the dynamically linked non-pie case, it is indeed the dynamic loader performs relocations.

    Note: this is a lazy relocation, and is happening on-demand, after main is already running.

    If we make it non-lazy, it will be performed by ld.so before invoking main:

    gcc -g -fno-pie -no-pie t.c -Wl,-z,now
    

    Repeat above steps and observe the relocation happening before main:

    (gdb) run
    Starting program: /tmp/a.out
    
    Hardware watchpoint 1: *(void**)0x403fe8
    
    Old value = (void *) 0x401036 <printf@plt+6>
    New value = (void *) 0x7ffff7e54270
    elf_machine_rela (skip_ifunc=<optimized out>, reloc_addr_arg=<optimized out>, version=<optimized out>, sym=<optimized out>, reloc=<optimized out>, map=<optimized out>)
        at ../sysdeps/x86_64/dl-machine.h:464
    464     ../sysdeps/x86_64/dl-machine.h: No such file or directory.
    (gdb) bt
    #0  elf_machine_rela (skip_ifunc=<optimized out>, reloc_addr_arg=<optimized out>, version=<optimized out>, sym=<optimized out>, reloc=<optimized out>, map=<optimized out>)
        at ../sysdeps/x86_64/dl-machine.h:464
    #1  elf_dynamic_do_Rela (skip_ifunc=<optimized out>, lazy=<optimized out>, nrelative=<optimized out>, relsize=<optimized out>, reladdr=<optimized out>, map=0x7ffff7ffe1a0)
        at do-rel.h:137
    #2  _dl_relocate_object (l=l@entry=0x7ffff7ffe1a0, scope=<optimized out>, reloc_mode=<optimized out>, consider_profiling=<optimized out>, consider_profiling@entry=0)
        at dl-reloc.c:274
    #3  0x00007ffff7fd555b in dl_main (phdr=<optimized out>, phnum=<optimized out>, user_entry=<optimized out>, auxv=<optimized out>) at rtld.c:2341
    #4  0x00007ffff7fec1a2 in _dl_sysdep_start (start_argptr=start_argptr@entry=0x7fffffffe380, dl_main=dl_main@entry=0x7ffff7fd34c0 <dl_main>) at ../elf/dl-sysdep.c:252
    #5  0x00007ffff7fd3021 in _dl_start_final (arg=0x7fffffffe380) at rtld.c:504
    #6  _dl_start (arg=0x7fffffffe380) at rtld.c:597
    #7  0x00007ffff7fd2098 in _start () from /lib64/ld-linux-x86-64.so.2
    

    Repeat for other build combinations.