# Binaries Operating Systems 2 Lecture <small>Warsaw University of Technology<br/>Faculty of Mathematics and Information Science</small> --- ### Logical Address Space Set of (valid) addresses which may be emitted by the running user task.  This may or may not be the same as physical ones. Usually those are **virtual addresses**, subject to further translation. Q: Where do those addresses, especially in `.text`, come from? --- ### How the code gets to the memory? Compilation is just the first step of the long path towards execution.  Each step **lays-out (bind addresses)** to some degree: - compiler decides on how locals are placed relative to each other - linker decides where all program defined objects go - loader puts program at desired place in memory, along with other loadables --- ### The compiler A compiler run generates a single **object file** out of a single **translation unit**. Each unit may introduce many memory residents: **objects** (variables) and **functions** (instructions).  Object files may reference objects and functions from other units. Consider calling functions and accessing variables from other translation units. Compiled object has no idea where it and others will land in memory. --- ### Executable Linkable Format ELF files are standard Linux file format designed for storing **various** binaries. Header specifies file type.  Each ELF may have a number of overlapping **sections** and **segments**. - Sections are named file parts consumed by the linker. - Segments are file regions loaded into memory by the loader. --- ### ELF Relocatables Object files generated by the compiler have type `Relocatable` and contain only sections. Each section has a name, type, size and offset within the file.  --- ### Data sections Defined, initialized globals are placed in `.data` section, uninitialized ones in `.bss`. Constants are placed `.rodata` section. Size of each is equal to aggregated size of all contained variables. Just by looking at the section contents one cannot say what is what:  Section `.bss` is not physically stored (disk space optimization). --- ### Code sections Compiled code is placed in `.text` section. All functions concatenated one by one.  --- ### Symbol tables To allow linker to search for objects and functions by name, dedicated `.symtab` section is generated by the compiler. | Value | Size | Type | Bind | Vis | Ndx | Name | |------------|------|--------|--------|---------|---------------|----------| | `00000000` | 11 | FUNC | LOCAL | DEFAULT | 1 (`.text`) | `foo` | | `0000000b` | 54 | FUNC | GLOBAL | DEFAULT | 1 (`.text`) | `goo` | | `00000000` | 4 | OBJECT | GLOBAL | DEFAULT | 3 (`.data`) | `a` | | `00000004` | 4 | OBJECT | GLOBAL | DEFAULT | 3 (`.data`) | `b` | | `00000000` | 4 | OBJECT | GLOBAL | DEFAULT | 4 (`.bss`) | `c` | | `00000004` | 4 | OBJECT | GLOBAL | DEFAULT | 4 (`.bss`) | `d` | | `00000000` | 4 | OBJECT | GLOBAL | DEFAULT | 5 (`.rodata`) | `e` | | `00000000` | 0 | NOTYPE | GLOBAL | DEFAULT | UND | `printf` | --- ### Relative addressing CPU instruction sets provide **relative addressing instructions**. Programs rarely specify absolute adresses. More often instruction encodes just an offset relative to value of some register. ```text // Read value from %rip + 0x2e91 to %eax register 1181: 8b 05 91 2e 00 00 mov 0x2e91(%rip),%eax ``` ```text // Call function at %rip + 11a8 (jump 11a8 bytes forward) 119b: e8 08 00 00 00 call 11a8 ``` Instruction pointer relative addressing is common trick to get **position-independent code**. Using it compiler can generate final version of local function calls. --- ### Relocation tables Generated code needs later adjustments. Relative placement of code and data section is unknown. Location of external objects and functions is not known.  Relocations are compiler-issued instructions for the linker. E.g. first one says: _Put into `.text` at offset `001a` the final address of symbol `a - 4 - 001a`_. --- ### The linker's job Linker glues common sections together. Doing so it picks relative object and instruction addresses. Linker allocates the address space (binds addresses).  It builds aggregated symbol table describing where objects and functions landed. --- ### Linker fixups It then follows all the relocation instructions, applying fixups to the code where addresses should go.  --- ### Dynamic Symbol Resolution Shared library symbol placement is not known at link time. Linker has to prepare the binary for the loader and provide it detailed instructions. Linkers generate a **Global Offset Table** section which is to be populated with function pointers at load time. Loader then may then just apply fixups to the table, without touching (large) code containing large number of calls.  Resolving large number of functions slows down program startup. Therefore, usually **lazy-initialization** of `.got` is employed. Linker generates small function trampolines in `.plt` section (**Procedure Linkage Table**) which jump to the target function if `.got` was already relocated or to the resolver (loader). ---  --- ### The loader's view of ELF Loader's main job is to put the ELF contents into memory. This is done by `mmap()`'ing relevant parts of file into the address space. It does not care about the meaning of the sections/symbols. It is interested only in size, alignment and memory protection (`RWX`) of each mapped region. Loadable ELF files define **segments** along with sections which serve as a loader's view of the file contents. ```text Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align LOAD 0x00001000 0x00001000 0x00001000 0x000002bd 0x000002bd R E 0x1000 ``` ---  RW data segment has larger memory size than file size due to `.bss`. --- ### Address Space Layout Randomization (ASLR) Thanks to position independent code kernel can map the binary at any adress. It picks a random **ASLR base address** thanks to which the program loads in a different place each time.  Note program may easily access first 4kB of ELF file. Single `LOAD` segment with GOT and RW data gets split into last two mappings. This is thanks to loader calling `mprotect()` after it's done filling `.got` protecting from overwriting function addresses later on. --- ### Dynamic loader Loader itself is a 'DYN' ELF userspace program. Linked binary points to the loader with path stored in the `.interp` ELF section: ```text > readelf -p .interp hello String dump of section '.interp': [ 0] /lib64/ld-linux-x86-64.so.2 ``` `.dynamic` section contains instructions for the loader like: - here are relocations (`.rela.dyn`), please apply them - here is `.got.plt`, please set it up for lazy resolution - here are initialization routines you need to run (`.init_array`) Loader itself is loaded at independent ALSR address (library base) than the program. The OS tells the loader where it was put and where the program is through the _auxiliary vector_ placed on the stack before loader is invoked. --- ### It's load time! Kernel handles `execve()` syscall: 1. Cleanups caller process - empties out memory mappings 2. Picks ASLR base for program, libraries, stack, heap and vDSO 3. Parses ELF headers and maps loadable segments into memory 4. Reads `.interp` section containing path to the loader 5. Maps the loader (`ld-linux-<arch>.so`) 6. Maps anonymous private stack segment into memory 7. Puts `argc`, `argv` and `envp` on stack 8. Puts _auxiliary vector_ of loader parameters onto stack - `AT_BASE`: `0x7f6a62062000` (library base) - `AT_ENTRY`: `0x5606657f3040` (program base) - `AT_PHDR`: `0x5606657f2040` (program headers) 9. Passes control to the loader entrypoint --- ### On to the ld-linux.so Loader begins with **self-relocation** knowing where itself was placed (`AT_BASE`). Loader reads address of mapped program headers from `AT_PHDR`. It finds `DYNAMIC` (`.dynamic`) segment containing loading instructions: 1. `NEEDED` libraries are loaded, whole dependency tree gets mapped and initialized recursively 2. `RELA` (`.rela.dyn`) relocations are executed, populating `.got` 3. `JMPREL` (`.rela.plt`) relocations are executed (only if `BIND NOW`) 4. `PLTGOT` (`.plt.got`) special entries are populated (`_dl_runtime_resolve()`) 5. `INIT_ARRAY` function are executed, initializing whole dependency tree bottom-up. Lastly loader jumps to `AT_ENTRY` (`_start`) passing control to the program. --- ### Runtime library loading Process may call the loader explicitly with function provided in `<dlfcn.h>`: ```c void *dl_handle = dlopen("libm.so.6", RTLD_LAZY); float (*sin)(float) = dlsym(dl_handle, "sin"); sin(1.0f); ``` This dynamically maps a new shared object into the address space and allows for runtime symbol resolution.