got_plt

Read this as well

Relocations

Relocations are entries in binaries that basically tell the linker to put stuff there. It could be compile time linker for statically linked binaries or run- time linker for dynamically linked stuff.

It basically says, “Determine the value of X., and put that value at offset Y”.

extern int foo;
int retfoo(void) {
  return foo;
}
gcc -c a.c
readelf --relocs ./a.o
Relocation section '.rel.text' at offset 0x2dc contains 1 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
00000004  00000801 R_386_32          00000000   foo

On running objdump -D ./a.o

./a.o:     file format elf32-i386


Disassembly of section .text:

00000000 <function>:
   0:    55         push   %ebp
   1:    89 e5                  mov    %esp,%ebp
   3:    a1 00 00 00 00         mov    0x0,%eax
   8:    5d                     pop    %ebp
   9:    c3                     ret

As you can see the value at 0x04 is 4 bytes of zeroes. These will be resolved at linker phase, and the actual address will be added there.

Run time example

readelf --headers /lib/libc.so.6
Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
[...]
  LOAD           0x000000 0x00000000 0x00000000 0x236ac 0x236ac R E 0x1000
  LOAD           0x023edc 0x00024edc 0x00024edc 0x0015c 0x001a4 RW  0x1000

As you can see, the addresses of the library are not resolved until runtime. There is no specific base address. Only the offsets are specified.

Another goal is code sharing. We don’t need 100 copies of the same library for 100 different processes.

At the same time, the data for each process attempting to access the shared library must be unique.

This is what is accomplished by the offset in the elf section. Note that this is virtual memory, so multiple processes can refer to the same address and still have separate data sections for themselves.

In AMD64 architecture you can directly move stuff with offset from current pc into registers and other funky stuff, so it’s easy. for i386 architecture, you need to load a fixed reference address, like load the stack pointer of a function to get the fixed address.

An example of this behaviour in 32 bit architecture.

0000040c <function>:
 40c:    55         push   %ebp
 40d:    89 e5                  mov    %esp,%ebp
 40f:    e8 0e 00 00 00         call   422 <__i686.get_pc_thunk.cx>
 414:    81 c1 5c 11 00 00      add    $0x115c,%ecx
 41a:    8b 81 18 00 00 00      mov    0x18(%ecx),%eax
 420:    5d                     pop    %ebp
 421:    c3                     ret

00000422 <__i686.get_pc_thunk.cx>:
 422:    8b 0c 24       mov    (%esp),%ecx
 425:    c3                     ret

In any case, you get the address of the functions this way. During runtime.

Now two cases arise when shared libraries are used:

PIE on

In this case, relative offset to the PLT is used. The first invocation jumps to puts@plt where it jumps to another address in in .got.plt. Over there in the first invocation it jumps back to .plt to the immediate next address, which further goes to the runtime linker/resolver.

In the next ISR/stack frame call/whatever you call it, the address at .got.plt is resolved and overwritten by the final resolved address for future invocation.

Then in the future invocations of the same function it immediately jumps to the function directly instead of the resolution bullwork. This also decreases execution time btw.

PIE off

Most of the details remain the same except the .plt address remains constant across invocations even if the library itself shifts because of ASLR.