From 287f736e614a2931f57e9aabf42105e3cf3e8992 Mon Sep 17 00:00:00 2001 From: johannst Date: Sat, 17 Apr 2021 23:47:17 +0200 Subject: 04: able to map dependency & resolve reolcs and execture main program (initial commit) --- 04_dynld_nostd/Makefile | 60 ++++++ 04_dynld_nostd/dynld.S | 27 +++ 04_dynld_nostd/dynld.c | 464 ++++++++++++++++++++++++++++++++++++++++++++++ 04_dynld_nostd/libgreet.c | 9 + 04_dynld_nostd/main.c | 18 ++ 5 files changed, 578 insertions(+) create mode 100644 04_dynld_nostd/Makefile create mode 100644 04_dynld_nostd/dynld.S create mode 100644 04_dynld_nostd/dynld.c create mode 100644 04_dynld_nostd/libgreet.c create mode 100644 04_dynld_nostd/main.c (limited to '04_dynld_nostd') diff --git a/04_dynld_nostd/Makefile b/04_dynld_nostd/Makefile new file mode 100644 index 0000000..8cd9d35 --- /dev/null +++ b/04_dynld_nostd/Makefile @@ -0,0 +1,60 @@ +# Copyright (c) 2020 Johannes Stoelp + +COMMON_CFLAGS := -g -O0 -Wall -Wextra \ + -I../lib/include \ + -nostartfiles -nodefaultlibs + +run: main + ./$< + +main: dynld.so main.c ../lib/libcommon.a + @# For now ew only add support for ELF hash tables (DT_HASH). + @# Therefore we specify the `hash-style` below. + gcc -o libgreet.so \ + $(COMMON_CFLAGS) \ + -fPIC -shared \ + -Wl,--hash-style=sysv \ + libgreet.c + + @if readelf -W -S libgreet.so | grep plt >& /dev/null; then \ + echo "ERROR: libgreet.so contains PLT while we don't support relocation calls in libgreet.so!"; \ + echo " All function calls in libgreet.so must be statically resolved!"; \ + exit 1; \ + fi + + @# For now ew only add support for ELF hash tables (DT_HASH). + @# Therefore we specify the `hash-style` below. + gcc -o $@ \ + $(COMMON_CFLAGS) \ + -L$(CURDIR) -lgreet \ + -Wl,--dynamic-linker=$(CURDIR)/dynld.so \ + -Wl,--hash-style=sysv \ + -no-pie \ + $(filter %.c %.a, $^) + + readelf -W --dynamic $@ + readelf -W --program-headers $@ + objdump --disassemble -j .plt -M intel $@ + objdump --disassemble=_start -M intel $@ + +dynld.so: dynld.S dynld.c ../lib/libcommon.a + gcc -o $@ \ + $(COMMON_CFLAGS) \ + -fPIC -static-pie \ + -fvisibility=hidden \ + -Wl,--entry=dl_start \ + -Wl,--no-allow-shlib-undefined \ + $^ + + @if readelf -W -S $@ | grep plt >& /dev/null; then \ + echo "ERROR: $@ contains PLT while we don't support relocation calls in $@!"; \ + echo " All function calls in $@ must be statically resolved!"; \ + exit 1; \ + fi + +../lib/libcommon.a: + make -C ../lib + +clean: + rm -f main libgreet.so + rm -f dynld.so diff --git a/04_dynld_nostd/dynld.S b/04_dynld_nostd/dynld.S new file mode 100644 index 0000000..8a634f5 --- /dev/null +++ b/04_dynld_nostd/dynld.S @@ -0,0 +1,27 @@ +// Copyright (c) 2020 Johannes Stoelp + +#include + +.intel_syntax noprefix + +.section .text, "ax", @progbits +.global dl_start +dl_start: + // $rsp is guaranteed to be 16-byte aligned. + + // Clear $rbp as specified by the SysV AMD64 ABI. + xor rbp, rbp + + // Load pointer to process context prepared by execve(2) syscall as + // specified in the SysV AMD64 ABI. + // Save pointer in $rdi which is the arg0 (int/ptr) register. + lea rdi, [rsp] + + // Stack frames must be 16-byte aligned before control is transfered to the + // callees entry point. + call dl_entry + + // Call exit(1) syscall to indicate error, dl_entry should not return. + mov rdi, 1 + mov rax, __NR_exit + syscall diff --git a/04_dynld_nostd/dynld.c b/04_dynld_nostd/dynld.c new file mode 100644 index 0000000..ad600f5 --- /dev/null +++ b/04_dynld_nostd/dynld.c @@ -0,0 +1,464 @@ +// Copyright (c) 2021 Johannes Stoelp + +#include +#include +#include +#include +#include + +#include +#include + + +/// ---------------- +/// Global Constants +/// ---------------- + +enum { + // Hard-coded page size. + // We assert against the `AT_PAGESZ` auxiliary vector entry. + PAGE_SIZE = 4096, + // Hard-coded upper limit of `DT_NEEDED` entries per dso + // (for simplicity to not require allocations). + MAX_NEEDED = 1, +}; + + +/// -------- +/// Execinfo +/// -------- + +typedef struct { + uint64_t argc; // Number of commandline arguments. + const char** argv; // List of pointer to command line arguments. + uint64_t envc; // Number of environment variables. + const char** envv; // List of pointers to environment variables. + uint64_t auxv[AT_MAX_CNT]; // Auxiliary vector entries. +} ExecInfo; + +// Interpret and extract data passed on the stack by the Linux Kernel +// when loading the initial process image. +// The data is organized according to the SystemV x86_64 ABI. +ExecInfo get_exec_info(const uint64_t* prctx) { + ExecInfo info = {0}; + + info.argc = *prctx; + info.argv = (const char**)(prctx + 1); + info.envv = (const char**)(info.argv + info.argc + 1); + + // Count the number of environment variables in the `ENVP` segment. + for (const char** env = info.envv; *env; ++env) { + info.envc += 1; + } + + // Decode auxiliary vector `AUXV`. + for (const Auxv64Entry* auxvp = (const Auxv64Entry*)(info.envv + info.envc + 1); auxvp->tag != AT_NULL; ++auxvp) { + if (auxvp->tag < AT_MAX_CNT) { + info.auxv[auxvp->tag] = auxvp->val; + } + } + + return info; +} + + +/// --- +/// Dso +/// --- + +typedef struct { + uint8_t* base; // Base address. + void (*entry)(); // Entry function. + uint64_t dynamic[DT_MAX_CNT]; // `.dynamic` section entries. + uint64_t needed[MAX_NEEDED]; // Shared object dependencies (`DT_NEEDED` entries). + uint8_t needed_len; // Number of `DT_NEEDED` entries (SO dependencies). +} Dso; + +void decode_dynamic(Dso* dso, uint64_t dynoff) { + // Decode `.dynamic` section of the `dso`. + for (const Elf64Dyn* dyn = (const Elf64Dyn*)(dso->base + dynoff); dyn->tag != DT_NULL; ++dyn) { + if (dyn->tag == DT_NEEDED) { + ERROR_ON(dso->needed_len == MAX_NEEDED, "Too many dso dependencies!"); + dso->needed[dso->needed_len++] = dyn->val; + } else if (dyn->tag < DT_MAX_CNT) { + dso->dynamic[dyn->tag] = dyn->val; + } + } + + // Check for string table entries. + ERROR_ON(dso->dynamic[DT_STRTAB] == 0, "DT_STRTAB missing in dynamic section!"); + ERROR_ON(dso->dynamic[DT_STRSZ] == 0, "DT_STRSZ missing in dynamic section!"); + + // Check for symbol table entries. + ERROR_ON(dso->dynamic[DT_SYMTAB] == 0, "DT_SYMTAB missing in dynamic section!"); + ERROR_ON(dso->dynamic[DT_SYMENT] == 0, "DT_SYMENT missing in dynamic section!"); + ERROR_ON(dso->dynamic[DT_SYMENT] != sizeof(Elf64Sym), "ELf64Sym size miss-match!"); + + // Check for relocation entries related to PLT. + // ERROR_ON(dso->dynamic[DT_JMPREL] == 0, "DT_JMPREL missing in dynamic section!"); + // ERROR_ON(dso->dynamic[DT_PLTRELSZ] == 0, "DT_PLTRELSZ missing in dynamic section!"); + // ERROR_ON(dso->dynamic[DT_PLTREL] == 0, "DT_PLTREL missing in dynamic section!"); + // ERROR_ON(dso->dynamic[DT_PLTREL] != DT_RELA, "x86_64 only uses RELA entries!"); + + // Check for SystemV hash table. We only support SystemV hash tables + // `DT_HASH`, not gnu hash tables `DT_GNU_HASH`. + ERROR_ON(dso->dynamic[DT_HASH] == 0, "DT_HASH missing in dynamic section!"); +} + +Dso get_prog_info(const ExecInfo* info) { + Dso prog = {0}; + + // Determine the base address of the user program. + // We only support the case where the Kernel already mapped the + // user program into the virtual address space and therefore the + // auxiliary vector contains an `AT_PHDR` entry pointing to the + // Program Headers of the user program. + // In that case, the base address of the user program can be + // computed by taking the absolute address of the `AT_PHDR` entry + // and subtracting the relative address `p_vaddr` of the `PT_PHDR` + // entry from the user programs Program Header iself. + // + // VMA + // | | + // PROG BASE -> | | ^ + // | | | + // | | | <---------------------+ + // | | | | + // AT_PHDR -> +---------+ v | + // | | | + // | | | + // | PT_PHDR | -----> Elf64Phdr { .., vaddr, .. } + // | | + // | | + // +---------+ + // | | + // + // PROG BASE = AT_PHDR - PT_PHDR.vaddr + ERROR_ON(info->auxv[AT_PHDR] == 0 || info->auxv[AT_EXECFD] != 0, "AT_PHDR entry missing in the AUXV!"); + + // Offset to the `.dynamic` section from the user programs `base addr`. + uint64_t dynoff = 0; + + // Program header of the user program. + const Elf64Phdr* phdr = (const Elf64Phdr*)info->auxv[AT_PHDR]; + + ERROR_ON(info->auxv[AT_PHENT] != sizeof(Elf64Phdr), "Elf64Phdr size miss-match!"); + + // Decode PHDRs of the user program. + for (unsigned phdrnum = info->auxv[AT_PHNUM]; --phdrnum; ++phdr) { + if (phdr->type == PT_PHDR) { + ERROR_ON(info->auxv[AT_PHDR] < phdr->vaddr, "Expectation auxv[AT_PHDR] >= phdr->vaddr failed!"); + prog.base = (uint8_t*)(info->auxv[AT_PHDR] - phdr->vaddr); + } else if (phdr->type == PT_DYNAMIC) { + dynoff = phdr->vaddr; + } + } + ERROR_ON(dynoff == 0, "PT_DYNAMIC entry missing in the user programs PHDR!"); + + // Decode `.dynamic` section. + decode_dynamic(&prog, dynoff); + + // Get the entrypoint of the user program form the auxiliary vector. + ERROR_ON(info->auxv[AT_ENTRY] == 0, "AT_ENTRY entry missing in the AUXV!"); + prog.entry = (void (*)())info->auxv[AT_ENTRY]; + + return prog; +} + +uint64_t get_num_dynsyms(const Dso* dso) { + ERROR_ON(dso->dynamic[DT_HASH] == 0, "DT_HASH missing in dynamic section!"); + + // Get SystemV hash table. + const uint32_t* hashtab = (const uint32_t*)(dso->base + dso->dynamic[DT_HASH]); + + // SystemV hash table layout: + // nbucket + // nchain + // bucket[nbuckets] + // chain[nchains] + // + // From the SystemV ABI - Dynamic Linking - Hash Table: + // Both `bucket` and `chain` hold symbol table indexes. Chain + // table entries parallel the symbol table. The number of symbol + // table entries should equal `nchain`. + return hashtab[1]; +} + +const char* get_str(const Dso* dso, const uint64_t idx) { + ERROR_ON(dso->dynamic[DT_STRSZ] < idx, "String table indexed out-of-bounds!"); + return (const char*)(dso->base + dso->dynamic[DT_STRTAB] + idx); +} + +const Elf64Sym* get_sym(const Dso* dso, const uint64_t idx) { + ERROR_ON(get_num_dynsyms(dso) < idx, "Symbol table index out-of-bounds!"); + return (const Elf64Sym*)(dso->base + dso->dynamic[DT_SYMTAB]) + idx; +} + +const Elf64Rela* get_pltreloc(const Dso* dso, const uint64_t idx) { + ERROR_ON(dso->dynamic[DT_PLTRELSZ] < sizeof(Elf64Rela) * idx, "PLT relocation table indexed out-of-bounds!"); + return (const Elf64Rela*)(dso->base + dso->dynamic[DT_JMPREL]) + idx; +} + + +/// ------------- +/// Symbol lookup +/// ------------- + +int strcmp(const char* s1, const char* s2) { + while (*s1 == *s2 && *s1) { + ++s1; + ++s2; + } + return *(unsigned char*)s1 - *(unsigned char*)s2; +} + +void* lookup_sym(const Dso* dso, const char* sym_name) { + for (unsigned i = 0; i < get_num_dynsyms(dso); ++i) { + const Elf64Sym* sym = get_sym(dso, i); + + if (ELF64_ST_TYPE(sym->info) == STT_FUNC && ELF64_ST_BIND(sym->info) == STB_GLOBAL && sym->shndx != SHN_UNDEF) { + if (strcmp(sym_name, get_str(dso, sym->name)) == 0) { + return dso->base + sym->value; + } + } + } + + return 0; +} + + +/// ----------------------------- +/// Map Shared Library Dependency +/// ----------------------------- + +Dso map_dependency(const char* dependency) { + // For simplicity we only search for SO dependencies in the current working dir. + ERROR_ON(access(dependency, R_OK) != 0, "Dependency '%s' does not exist!\n", dependency); + + const int fd = open(dependency, O_RDONLY); + ERROR_ON(fd < 0, "Failed to open '%s'", dependency); + + Elf64Ehdr ehdr; + // Read ELF header. + ERROR_ON(read(fd, &ehdr, sizeof(ehdr)) != (ssize_t)sizeof(ehdr), "Failed to read Elf64Ehdr!"); + + // Check ELF magic. + ERROR_ON(ehdr.ident[EI_MAG0] != '\x7f' || ehdr.ident[EI_MAG1] != 'E' || ehdr.ident[EI_MAG2] != 'L' || ehdr.ident[EI_MAG3] != 'F', + "Dependency '%s' wrong ELF magic value!\n", dependency); + // Check ELF header size. + ERROR_ON(ehdr.ehsize != sizeof(ehdr), "Elf64Ehdr size miss-match!"); + // Check for 64bit ELF. + ERROR_ON(ehdr.ident[EI_CLASS] != ELFCLASS64, "Dependency '%s' is not 64bit ELF!\n", dependency); + // Check for OS ABI. + ERROR_ON(ehdr.ident[EI_OSABI] != ELFOSABI_SYSV, "Dependency '%s' is not built for SysV OS ABI!\n", dependency); + // Check ELF type. + ERROR_ON(ehdr.type != ET_DYN, "Dependency '%s' is not a dynamic library!"); + // Check for Phdr. + ERROR_ON(ehdr.phnum == 0, "Dependency '%s' has no Phdr!\n", dependency); + + + Elf64Phdr phdr[ehdr.phnum]; + // Check PHDR header size. + ERROR_ON(ehdr.phentsize != sizeof(phdr[0]), "Elf64Phdr size miss-match!"); + + // Read Program headers at offset `phoff`. + ERROR_ON(pread(fd, &phdr, sizeof(phdr), ehdr.phoff) != (ssize_t)sizeof(phdr), "Failed to read Elf64Phdr[%d]!\n", ehdr.phnum); + + // Compute start and end address used by the library based on the all the `PT_LOAD` program headers. + uint64_t dynoff = 0; + uint64_t addr_start = (uint64_t)-1; + uint64_t addr_end = 0; + for (unsigned i = 0; i < ehdr.phnum; ++i) { + const Elf64Phdr* p = &phdr[i]; + if (p->type == PT_DYNAMIC) { + // Offset to `.dynamic` section. + dynoff = p->vaddr; + } else if (p->type == PT_LOAD) { + // Find start & end address. + if (p->vaddr < addr_start) { + addr_start = p->vaddr; + } else if (p->vaddr + p->memsz > addr_end) { + addr_end = p->vaddr + p->memsz; + } + } + } + + // Align start address to the next lower page boundary. + addr_start = addr_start & ~(PAGE_SIZE - 1); + // Align end address to the next higher page boundary. + addr_end = (addr_end + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1); + + // Reserve region big enough to map all `PT_LOAD` sections of `dependency`. + uint8_t* map = mmap(0 /* addr */, addr_end - addr_start /* len */, PROT_EXEC | PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, + -1 /* fd */, 0 /* file offset */); + ERROR_ON(map == MAP_FAILED, "Failed to mmap address space for dependency '%s'\n", dependency); + + // Compute base address for library. + uint8_t* base = map - addr_start; + + // Map in all `PT_LOAD` segments from the `dependency`. + for (unsigned i = 0; i < ehdr.phnum; ++i) { + const Elf64Phdr* p = &phdr[i]; + ; + if (p->type != PT_LOAD) { + continue; + } + + // Page align start & end address. + uint64_t addr_start = p->vaddr & ~(PAGE_SIZE - 1); + uint64_t addr_end = (p->vaddr + p->memsz + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1); + + // Page align file offset. + uint64_t off = p->offset & ~(PAGE_SIZE - 1); + + // Compute segment permissions. + uint32_t prot = (p->flags & PF_X ? PROT_EXEC : 0) | (p->flags & PF_R ? PROT_READ : 0) | (p->flags & PF_W ? PROT_WRITE : 0); + + // Mmap segment. + ERROR_ON(mmap(base + addr_start, addr_end - addr_start, prot, MAP_PRIVATE | MAP_FIXED, fd, off) != base + addr_start, + "Failed to map `PT_LOAD` section %d for dependency '%s'.", i, dependency); + + // From the SystemV ABI - Program Headers: + // If the segment’s memorysize (memsz) is larger than the file size (filesz), the "extra" bytes are defined to hold the value + // `0` and to follow the segment’s initialized are + // + // This is typically used by the `.bss` section. + if (p->memsz > p->filesz) { + memset(base + addr_start + p->filesz, 0 /* byte */, p->memsz - p->filesz /*len*/); + } + } + + // Close file descriptor. + close(fd); + + Dso dso = {0}; + dso.base = base; + decode_dynamic(&dso, dynoff); + return dso; +} + + +/// ------------------------------ +/// Dynamic Linking (lazy resolve) +/// ------------------------------ + +struct LinkMap { + const Dso* dso; // Pointer to Dso list object. + const struct LinkMap* next; // Pointer to next LinkMap entry. +}; +typedef struct LinkMap LinkMap; + +void resolve_relocs(const Dso* dso, const LinkMap* map) { + for (unsigned long relocidx = 0; relocidx < (dso->dynamic[DT_PLTRELSZ] / sizeof(Elf64Rela)); ++relocidx) { + const Elf64Rela* reloc = get_pltreloc(dso, relocidx); + ERROR_ON(ELF64_R_TYPE(reloc->info) != R_X86_64_JUMP_SLOT, "Expected relocation entry of type X86_64_JUMP_SLOT!"); + + const int symidx = ELF64_R_SYM(reloc->info); + const char* symname = get_str(dso, get_sym(dso, symidx)->name); + + void* symaddr = 0; + for (const LinkMap* lmap = map; lmap && symaddr == 0; lmap = lmap->next) { + symaddr = lookup_sym(lmap->dso, symname); + } + ERROR_ON(symaddr == 0, "Failed lookup symbol %s while resolving relocations!", symname); + + pfmt("Resolved reloc %s to %p\n", symname, symaddr); + + // Patch storage unit of relocation with absolute address of the symbol. + *(uint64_t*)(dso->base + reloc->offset) = (uint64_t)symaddr; + } +} + + +/// ------------------------------ +/// Dynamic Linking (lazy resolve) +/// ------------------------------ + +// `noreturn` Function never returns. +// `naked` Don't generate prologue/epilogue sequences. +__attribute__((noreturn)) __attribute__((naked)) static void dynresolve_entry() { + asm("dynresolve_entry:\n\t" + // Pop arguments on stack from PLT0 into rdi/rsi argument registers. + "pop %rdi\n\t" // GOT[1] entry (pushed by PLT0 pad). + "pop %rsi\n\t" // Relocation index (pushed by PLT0 pad). + "jmp dynresolve"); +} + +// `used` Foce to emit code for function. +// `unused` Don't warn about unused function. +__attribute__((used)) __attribute__((unused)) static void dynresolve(uint64_t got1, uint64_t reloc_idx) { + ERROR_ON(true, + "ERROR: dynresolve request not supported!" + "\n\tGOT[1] = 0x%x" + "\n\treloc_idx = %d\n", + got1, reloc_idx); +} + + +/// ------------------------- +/// Dynamic Linker Entrypoint +/// ------------------------- + +void dl_entry(const uint64_t* prctx) { + // Parse SystemV ABI block. + const ExecInfo exec_info = get_exec_info(prctx); + + // Ensure hard-coded page size value is correct. + ERROR_ON(exec_info.auxv[AT_PAGESZ] != PAGE_SIZE, "Hard-coded PAGE_SIZE miss-match!"); + + // Initialize dso handle for user program but extracting necesarry + // information from `AUXV` and the `PHDR`. + const Dso dso_prog = get_prog_info(&exec_info); + + // Map dependency. + // + // In this chapter the user program should have a single shared + // object dependency, which is our `libgreet.so` no-std shared + // library. + // The `libgreet.so` library itself should not have any dynamic + // dependencies. + ERROR_ON(dso_prog.needed_len != 1, "User program should have exactly one dependency!"); + + const Dso dso_lib = map_dependency(get_str(&dso_prog, dso_prog.needed[0])); + ERROR_ON(dso_lib.needed_len != 0, "The programs dependency should be stand-alone!"); + + // Setup LinkMap. + // + // Create a list of DSOs as link map with the following order: + // main -> libgreet.so + // The link map determines the symbol lookup order. + const LinkMap map_lib = {.dso = &dso_lib, .next = 0}; + const LinkMap map_prog = {.dso = &dso_prog, .next = &map_lib}; + + // Resolve relocations for the main program. + resolve_relocs(&dso_prog, &map_prog); + + + // Install dynamic resolve handler. + // + // The dynamic resolve handler is used when binding symbols lazily. Hence + // it should not be called in this example as we resolve all relocations + // before transfering controll to the user program. + // For safety we still install a handler which will terminate the program + // once it is called, if we wouldn't install this handler the program would + // most probably SEGFAULT. + // + // The handler is installed in the `GOT[2]` entry for each DSO object that + // has an GOT. It is jumped to from the `PLT0` pad with the following two + // arguments passed via the stack: + // pop %rdi // GOT[1] entry. + // pop %rsi // Relocation index. + { + uint64_t* got = (uint64_t*)(dso_prog.base + dso_prog.dynamic[DT_PLTGOT]); + // Jump target for PLT0 pad. + got[2] = (uint64_t)&dynresolve_entry; + } + + // GOT[0]; // Hold address of dynamic structure referenced by `_DYNAMIC`. + // GOT[1]; // Pushed by PLT0 pad on stack before jumping to got[2] -> Word the dynamic linker can use to identify the caller. + // GOT[2]; // Jump target for PLT0 pad (when doing lazy resolving). + + dso_prog.entry(); + + _exit(0); +} diff --git a/04_dynld_nostd/libgreet.c b/04_dynld_nostd/libgreet.c new file mode 100644 index 0000000..f2a96a3 --- /dev/null +++ b/04_dynld_nostd/libgreet.c @@ -0,0 +1,9 @@ +// Copyright (c) 2020 Johannes Stoelp + +const char* get_greet() { + return "Hello from libgreet.so!"; +} + +const char* get_greet2() { + return "Hello 2 from libgreet.so!"; +} diff --git a/04_dynld_nostd/main.c b/04_dynld_nostd/main.c new file mode 100644 index 0000000..ca250ef --- /dev/null +++ b/04_dynld_nostd/main.c @@ -0,0 +1,18 @@ +// Copyright (c) 2020 Johannes Stoelp + +#include +#include + +// API of `libgreet.so`. +extern const char* get_greet(); +extern const char* get_greet2(); + +void _start() { + pfmt("Running _start() @ %s\n", __FILE__); + + // Call function from libgreet.so -> generates PLT entry. + pfmt("get_greet() -> %s\n", get_greet()); + pfmt("get_greet2() -> %s\n", get_greet2()); + + _exit(0); +} -- cgit v1.2.3 From 85230524414b6d27664bf77c8584bfeced6c71cb Mon Sep 17 00:00:00 2001 From: johannst Date: Wed, 21 Apr 2021 23:41:59 +0200 Subject: add support to resolve all relocations in PLT & RELA tables; add global variable as example to libgreet.so --- 04_dynld_nostd/Makefile | 6 --- 04_dynld_nostd/dynld.c | 115 ++++++++++++++++++++++++++++++++++------------ 04_dynld_nostd/libgreet.c | 4 ++ 04_dynld_nostd/main.c | 6 ++- 4 files changed, 95 insertions(+), 36 deletions(-) (limited to '04_dynld_nostd') diff --git a/04_dynld_nostd/Makefile b/04_dynld_nostd/Makefile index 8cd9d35..c1a472a 100644 --- a/04_dynld_nostd/Makefile +++ b/04_dynld_nostd/Makefile @@ -16,12 +16,6 @@ main: dynld.so main.c ../lib/libcommon.a -Wl,--hash-style=sysv \ libgreet.c - @if readelf -W -S libgreet.so | grep plt >& /dev/null; then \ - echo "ERROR: libgreet.so contains PLT while we don't support relocation calls in libgreet.so!"; \ - echo " All function calls in libgreet.so must be statically resolved!"; \ - exit 1; \ - fi - @# For now ew only add support for ELF hash tables (DT_HASH). @# Therefore we specify the `hash-style` below. gcc -o $@ \ diff --git a/04_dynld_nostd/dynld.c b/04_dynld_nostd/dynld.c index ad600f5..56d48b7 100644 --- a/04_dynld_nostd/dynld.c +++ b/04_dynld_nostd/dynld.c @@ -105,7 +105,7 @@ void decode_dynamic(Dso* dso, uint64_t dynoff) { ERROR_ON(dso->dynamic[DT_HASH] == 0, "DT_HASH missing in dynamic section!"); } -Dso get_prog_info(const ExecInfo* info) { +Dso get_prog_dso(const ExecInfo* info) { Dso prog = {0}; // Determine the base address of the user program. @@ -194,11 +194,16 @@ const Elf64Sym* get_sym(const Dso* dso, const uint64_t idx) { return (const Elf64Sym*)(dso->base + dso->dynamic[DT_SYMTAB]) + idx; } -const Elf64Rela* get_pltreloc(const Dso* dso, const uint64_t idx) { +const Elf64Rela* get_pltreloca(const Dso* dso, const uint64_t idx) { ERROR_ON(dso->dynamic[DT_PLTRELSZ] < sizeof(Elf64Rela) * idx, "PLT relocation table indexed out-of-bounds!"); return (const Elf64Rela*)(dso->base + dso->dynamic[DT_JMPREL]) + idx; } +const Elf64Rela* get_reloca(const Dso* dso, const uint64_t idx) { + ERROR_ON(dso->dynamic[DT_RELASZ] < sizeof(Elf64Rela) * idx, "RELA relocation table indexed out-of-bounds!"); + return (const Elf64Rela*)(dso->base + dso->dynamic[DT_RELA]) + idx; +} + /// ------------- /// Symbol lookup @@ -212,17 +217,26 @@ int strcmp(const char* s1, const char* s2) { return *(unsigned char*)s1 - *(unsigned char*)s2; } -void* lookup_sym(const Dso* dso, const char* sym_name) { +// Perform naive lookup for global symbol and return address if symbol was found. +// +// For simplicity this lookup doesn't use the hash table (`DT_HASH` | +// `DT_GNU_HASH`) but rather iterates of the dynamic symbol table. Using the +// hash table doesn't change the lookup result, however it yields better +// performance for large symbol tables. +// +// `dso` A handle to the dso which dynamic symbol table should be searched. +// `sym_name` Name of the symbol to look up. +// `sym_type` Type of the symbol to look up (STT_OBJECT | STT_FUNC). +void* lookup_sym(const Dso* dso, const char* sym_name, unsigned sym_type) { for (unsigned i = 0; i < get_num_dynsyms(dso); ++i) { const Elf64Sym* sym = get_sym(dso, i); - if (ELF64_ST_TYPE(sym->info) == STT_FUNC && ELF64_ST_BIND(sym->info) == STB_GLOBAL && sym->shndx != SHN_UNDEF) { + if (ELF64_ST_TYPE(sym->info) == sym_type && ELF64_ST_BIND(sym->info) == STB_GLOBAL && sym->shndx != SHN_UNDEF) { if (strcmp(sym_name, get_str(dso, sym->name)) == 0) { return dso->base + sym->value; } } } - return 0; } @@ -324,7 +338,7 @@ Dso map_dependency(const char* dependency) { // // This is typically used by the `.bss` section. if (p->memsz > p->filesz) { - memset(base + addr_start + p->filesz, 0 /* byte */, p->memsz - p->filesz /*len*/); + memset(base + p->vaddr + p->filesz, 0 /* byte */, p->memsz - p->filesz /*len*/); } } @@ -338,34 +352,74 @@ Dso map_dependency(const char* dependency) { } -/// ------------------------------ -/// Dynamic Linking (lazy resolve) -/// ------------------------------ +/// ------------------- +/// Resolve relocations +/// ------------------- struct LinkMap { const Dso* dso; // Pointer to Dso list object. - const struct LinkMap* next; // Pointer to next LinkMap entry. + const struct LinkMap* next; // Pointer to next LinkMap entry ('0' terminates the list). }; typedef struct LinkMap LinkMap; -void resolve_relocs(const Dso* dso, const LinkMap* map) { - for (unsigned long relocidx = 0; relocidx < (dso->dynamic[DT_PLTRELSZ] / sizeof(Elf64Rela)); ++relocidx) { - const Elf64Rela* reloc = get_pltreloc(dso, relocidx); - ERROR_ON(ELF64_R_TYPE(reloc->info) != R_X86_64_JUMP_SLOT, "Expected relocation entry of type X86_64_JUMP_SLOT!"); - - const int symidx = ELF64_R_SYM(reloc->info); - const char* symname = get_str(dso, get_sym(dso, symidx)->name); - - void* symaddr = 0; - for (const LinkMap* lmap = map; lmap && symaddr == 0; lmap = lmap->next) { - symaddr = lookup_sym(lmap->dso, symname); - } - ERROR_ON(symaddr == 0, "Failed lookup symbol %s while resolving relocations!", symname); +// Resolve a single relocation of `dso`. +// +// Resolve the relocation `reloc` by looking up the address of the symbol +// referenced by the relocation. If the address of the symbol was found the +// relocation is patched, if the address was not found the process exits. +static void resolve_reloc(const Dso* dso, const LinkMap* map, const Elf64Rela* reloc, unsigned symtype) { + // Get symbol referenced by relocation. + const int symidx = ELF64_R_SYM(reloc->info); + const Elf64Sym* sym = get_sym(dso, symidx); + const char* symname = get_str(dso, sym->name); + + // Get relocation typy. + unsigned reloctype = ELF64_R_TYPE(reloc->info); + + // Lookup symbol address. + void* symaddr = 0; + // TODO: Explain special handling of R_X86_64_COPY. + for (const LinkMap* lmap = (reloctype == R_X86_64_COPY ? map->next : map); lmap && symaddr == 0; lmap = lmap->next) { + symaddr = lookup_sym(lmap->dso, symname, symtype); + } + ERROR_ON(symaddr == 0, "Failed lookup symbol %s while resolving relocations!", symname); + + pfmt("Resolved reloc %s to %p (base %p)\n", symname, symaddr, dso->base); + + // Perform relocation according to relocation type. + switch (reloctype) { + case R_X86_64_GLOB_DAT: /* GOT entry for data objects. */ + case R_X86_64_JUMP_SLOT: /* PLT entry. */ + // Patch storage unit of relocation with absolute address of the symbol. + *(uint64_t*)(dso->base + reloc->offset) = (uint64_t)symaddr; + break; + case R_X86_64_COPY: /* Reference to global variable in shared ELF file. */ + // Copy initial value of variable into relocation address. + memcpy(dso->base + reloc->offset, (void*)symaddr, sym->size); + break; + default: + ERROR_ON(true, "Unsupported relocation type %d!\n", reloctype); + } +} - pfmt("Resolved reloc %s to %p\n", symname, symaddr); +// Resolve all relocations of `dso`. +// +// Resolve relocations from the PLT & RELA tables. Use `map` as link map which +// defines the order of the symbol lookup. +static void resolve_relocs(const Dso* dso, const LinkMap* map) { + // Resolve all relocation from the RELA table found in `dso`. There is + // typically one relocation per undefined dynamic object symbol (eg global + // variables). + for (unsigned long relocidx = 0; relocidx < (dso->dynamic[DT_RELASZ] / sizeof(Elf64Rela)); ++relocidx) { + const Elf64Rela* reloc = get_reloca(dso, relocidx); + resolve_reloc(dso, map, reloc, STT_OBJECT); + } - // Patch storage unit of relocation with absolute address of the symbol. - *(uint64_t*)(dso->base + reloc->offset) = (uint64_t)symaddr; + // Resolve all relocation from the PLT jump table found in `dso`. There is + // typically one relocation per undefined dynamic function symbol. + for (unsigned long relocidx = 0; relocidx < (dso->dynamic[DT_PLTRELSZ] / sizeof(Elf64Rela)); ++relocidx) { + const Elf64Rela* reloc = get_pltreloca(dso, relocidx); + resolve_reloc(dso, map, reloc, STT_FUNC); } } @@ -408,7 +462,7 @@ void dl_entry(const uint64_t* prctx) { // Initialize dso handle for user program but extracting necesarry // information from `AUXV` and the `PHDR`. - const Dso dso_prog = get_prog_info(&exec_info); + const Dso dso_prog = get_prog_dso(&exec_info); // Map dependency. // @@ -430,9 +484,11 @@ void dl_entry(const uint64_t* prctx) { const LinkMap map_lib = {.dso = &dso_lib, .next = 0}; const LinkMap map_prog = {.dso = &dso_prog, .next = &map_lib}; - // Resolve relocations for the main program. - resolve_relocs(&dso_prog, &map_prog); + // Resolve relocations of the library (dependency). + resolve_relocs(&dso_lib, &map_prog); + // Resolve relocations of the main program. + resolve_relocs(&dso_prog, &map_prog); // Install dynamic resolve handler. // @@ -458,6 +514,7 @@ void dl_entry(const uint64_t* prctx) { // GOT[1]; // Pushed by PLT0 pad on stack before jumping to got[2] -> Word the dynamic linker can use to identify the caller. // GOT[2]; // Jump target for PLT0 pad (when doing lazy resolving). + // Transfer control to user program. dso_prog.entry(); _exit(0); diff --git a/04_dynld_nostd/libgreet.c b/04_dynld_nostd/libgreet.c index f2a96a3..439e236 100644 --- a/04_dynld_nostd/libgreet.c +++ b/04_dynld_nostd/libgreet.c @@ -1,9 +1,13 @@ // Copyright (c) 2020 Johannes Stoelp +int gCalled = 0; + const char* get_greet() { + ++gCalled; return "Hello from libgreet.so!"; } const char* get_greet2() { + ++gCalled; return "Hello 2 from libgreet.so!"; } diff --git a/04_dynld_nostd/main.c b/04_dynld_nostd/main.c index ca250ef..a787158 100644 --- a/04_dynld_nostd/main.c +++ b/04_dynld_nostd/main.c @@ -6,13 +6,17 @@ // API of `libgreet.so`. extern const char* get_greet(); extern const char* get_greet2(); +extern int gCalled; void _start() { pfmt("Running _start() @ %s\n", __FILE__); - // Call function from libgreet.so -> generates PLT entry. + // Call function from libgreet.so -> generates PLT relocations. pfmt("get_greet() -> %s\n", get_greet()); pfmt("get_greet2() -> %s\n", get_greet2()); + // Reference global variable from libgreet.so -> generates RELA relocation. + pfmt("libgreet.so called %d times\n", gCalled); + _exit(0); } -- cgit v1.2.3 From e6716313137bc397d7d7d42faf07b06a5fea65c1 Mon Sep 17 00:00:00 2001 From: johannst Date: Wed, 21 Apr 2021 23:44:16 +0200 Subject: own make target for libgreet.so --- 04_dynld_nostd/Makefile | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) (limited to '04_dynld_nostd') diff --git a/04_dynld_nostd/Makefile b/04_dynld_nostd/Makefile index c1a472a..e72dde8 100644 --- a/04_dynld_nostd/Makefile +++ b/04_dynld_nostd/Makefile @@ -7,15 +7,7 @@ COMMON_CFLAGS := -g -O0 -Wall -Wextra \ run: main ./$< -main: dynld.so main.c ../lib/libcommon.a - @# For now ew only add support for ELF hash tables (DT_HASH). - @# Therefore we specify the `hash-style` below. - gcc -o libgreet.so \ - $(COMMON_CFLAGS) \ - -fPIC -shared \ - -Wl,--hash-style=sysv \ - libgreet.c - +main: dynld.so libgreet.so main.c ../lib/libcommon.a @# For now ew only add support for ELF hash tables (DT_HASH). @# Therefore we specify the `hash-style` below. gcc -o $@ \ @@ -31,6 +23,15 @@ main: dynld.so main.c ../lib/libcommon.a objdump --disassemble -j .plt -M intel $@ objdump --disassemble=_start -M intel $@ +libgreet.so: libgreet.c + @# For now ew only add support for ELF hash tables (DT_HASH). + @# Therefore we specify the `hash-style` below. + gcc -o $@ \ + $(COMMON_CFLAGS) \ + -fPIC -shared \ + -Wl,--hash-style=sysv \ + $^ + dynld.so: dynld.S dynld.c ../lib/libcommon.a gcc -o $@ \ $(COMMON_CFLAGS) \ -- cgit v1.2.3 From 6f7efd7075fcea29922536d8fb1ad810182437cd Mon Sep 17 00:00:00 2001 From: johannst Date: Sun, 25 Apr 2021 23:32:40 +0200 Subject: main: remove exit syscall --- 04_dynld_nostd/main.c | 2 -- 1 file changed, 2 deletions(-) (limited to '04_dynld_nostd') diff --git a/04_dynld_nostd/main.c b/04_dynld_nostd/main.c index a787158..709fad0 100644 --- a/04_dynld_nostd/main.c +++ b/04_dynld_nostd/main.c @@ -17,6 +17,4 @@ void _start() { // Reference global variable from libgreet.so -> generates RELA relocation. pfmt("libgreet.so called %d times\n", gCalled); - - _exit(0); } -- cgit v1.2.3 From 05f740db6fe966d32256d4ed3b897f7b3e051fff Mon Sep 17 00:00:00 2001 From: johannst Date: Sun, 25 Apr 2021 23:38:36 +0200 Subject: added support for R_X86_64_64/R_X86_64_RELATIVE relocations + added init/fini --- 04_dynld_nostd/dynld.c | 78 +++++++++++++++++++++++++++++++++++++++-------- 04_dynld_nostd/libgreet.c | 10 ++++++ 2 files changed, 75 insertions(+), 13 deletions(-) (limited to '04_dynld_nostd') diff --git a/04_dynld_nostd/dynld.c b/04_dynld_nostd/dynld.c index 56d48b7..a7b0fe1 100644 --- a/04_dynld_nostd/dynld.c +++ b/04_dynld_nostd/dynld.c @@ -205,6 +205,41 @@ const Elf64Rela* get_reloca(const Dso* dso, const uint64_t idx) { } +/// ----------- +/// Init & Fini +/// ----------- + +typedef void (*initfptr)(); + +static void init(const Dso* dso) { + if (dso->dynamic[DT_INIT]) { + initfptr* fn = (initfptr*)(dso->base + dso->dynamic[DT_INIT]); + (*fn)(); + } + + size_t nfns = dso->dynamic[DT_INIT_ARRAYSZ] / sizeof(initfptr); + initfptr* fns = (initfptr*)(dso->base + dso->dynamic[DT_INIT_ARRAY]); + while (nfns--) { + (*fns++)(); + } +} + +typedef void (*finifptr)(); + +static void fini(const Dso* dso) { + size_t nfns = dso->dynamic[DT_FINI_ARRAYSZ] / sizeof(finifptr); + finifptr* fns = (finifptr*)(dso->base + dso->dynamic[DT_FINI_ARRAY]) + nfns /* reverse destruction order */; + while (nfns--) { + (*--fns)(); + } + + if (dso->dynamic[DT_FINI]) { + finifptr* fn = (finifptr*)(dso->base + dso->dynamic[DT_FINI]); + (*fn)(); + } +} + + /// ------------- /// Symbol lookup /// ------------- @@ -226,12 +261,12 @@ int strcmp(const char* s1, const char* s2) { // // `dso` A handle to the dso which dynamic symbol table should be searched. // `sym_name` Name of the symbol to look up. -// `sym_type` Type of the symbol to look up (STT_OBJECT | STT_FUNC). -void* lookup_sym(const Dso* dso, const char* sym_name, unsigned sym_type) { +void* lookup_sym(const Dso* dso, const char* sym_name) { for (unsigned i = 0; i < get_num_dynsyms(dso); ++i) { const Elf64Sym* sym = get_sym(dso, i); - if (ELF64_ST_TYPE(sym->info) == sym_type && ELF64_ST_BIND(sym->info) == STB_GLOBAL && sym->shndx != SHN_UNDEF) { + if ((ELF64_ST_TYPE(sym->info) == STT_OBJECT || ELF64_ST_TYPE(sym->info) == STT_FUNC) && ELF64_ST_BIND(sym->info) == STB_GLOBAL && + sym->shndx != SHN_UNDEF) { if (strcmp(sym_name, get_str(dso, sym->name)) == 0) { return dso->base + sym->value; } @@ -367,7 +402,7 @@ typedef struct LinkMap LinkMap; // Resolve the relocation `reloc` by looking up the address of the symbol // referenced by the relocation. If the address of the symbol was found the // relocation is patched, if the address was not found the process exits. -static void resolve_reloc(const Dso* dso, const LinkMap* map, const Elf64Rela* reloc, unsigned symtype) { +static void resolve_reloc(const Dso* dso, const LinkMap* map, const Elf64Rela* reloc) { // Get symbol referenced by relocation. const int symidx = ELF64_R_SYM(reloc->info); const Elf64Sym* sym = get_sym(dso, symidx); @@ -376,20 +411,28 @@ static void resolve_reloc(const Dso* dso, const LinkMap* map, const Elf64Rela* r // Get relocation typy. unsigned reloctype = ELF64_R_TYPE(reloc->info); - // Lookup symbol address. + // Find symbol address. void* symaddr = 0; - // TODO: Explain special handling of R_X86_64_COPY. - for (const LinkMap* lmap = (reloctype == R_X86_64_COPY ? map->next : map); lmap && symaddr == 0; lmap = lmap->next) { - symaddr = lookup_sym(lmap->dso, symname, symtype); + // FIXME: Should relocations of type `R_X86_64_64` only be looked up in `dso` directly? + if (reloctype == R_X86_64_RELATIVE) { + // Symbols address is computed by re-basing the relative address based on the DSOs base address. + symaddr = (void*)(dso->base + reloc->addend); + } else { + // TODO: Explain special handling of R_X86_64_COPY. + for (const LinkMap* lmap = (reloctype == R_X86_64_COPY ? map->next : map); lmap && symaddr == 0; lmap = lmap->next) { + symaddr = lookup_sym(lmap->dso, symname); + } } ERROR_ON(symaddr == 0, "Failed lookup symbol %s while resolving relocations!", symname); - pfmt("Resolved reloc %s to %p (base %p)\n", symname, symaddr, dso->base); + pfmt("Resolved reloc %s to %p (base %p)\n", reloctype == R_X86_64_RELATIVE ? "" : symname, symaddr, dso->base); // Perform relocation according to relocation type. switch (reloctype) { case R_X86_64_GLOB_DAT: /* GOT entry for data objects. */ case R_X86_64_JUMP_SLOT: /* PLT entry. */ + case R_X86_64_64: /* 64bit relocation (non-lazy). */ + case R_X86_64_RELATIVE: /* DSO base relative relocation. */ // Patch storage unit of relocation with absolute address of the symbol. *(uint64_t*)(dso->base + reloc->offset) = (uint64_t)symaddr; break; @@ -412,14 +455,14 @@ static void resolve_relocs(const Dso* dso, const LinkMap* map) { // variables). for (unsigned long relocidx = 0; relocidx < (dso->dynamic[DT_RELASZ] / sizeof(Elf64Rela)); ++relocidx) { const Elf64Rela* reloc = get_reloca(dso, relocidx); - resolve_reloc(dso, map, reloc, STT_OBJECT); + resolve_reloc(dso, map, reloc); } // Resolve all relocation from the PLT jump table found in `dso`. There is // typically one relocation per undefined dynamic function symbol. for (unsigned long relocidx = 0; relocidx < (dso->dynamic[DT_PLTRELSZ] / sizeof(Elf64Rela)); ++relocidx) { const Elf64Rela* reloc = get_pltreloca(dso, relocidx); - resolve_reloc(dso, map, reloc, STT_FUNC); + resolve_reloc(dso, map, reloc); } } @@ -486,11 +529,15 @@ void dl_entry(const uint64_t* prctx) { // Resolve relocations of the library (dependency). resolve_relocs(&dso_lib, &map_prog); - // Resolve relocations of the main program. resolve_relocs(&dso_prog, &map_prog); - // Install dynamic resolve handler. + // Initialize library. + init(&dso_lib); + // Initialize main program. + init(&dso_prog); + + // Install dynamic resolve handler (lazy resolve). // // The dynamic resolve handler is used when binding symbols lazily. Hence // it should not be called in this example as we resolve all relocations @@ -517,5 +564,10 @@ void dl_entry(const uint64_t* prctx) { // Transfer control to user program. dso_prog.entry(); + // Finalize main program. + fini(&dso_prog); + // Finalize library. + fini(&dso_lib); + _exit(0); } diff --git a/04_dynld_nostd/libgreet.c b/04_dynld_nostd/libgreet.c index 439e236..e697690 100644 --- a/04_dynld_nostd/libgreet.c +++ b/04_dynld_nostd/libgreet.c @@ -1,5 +1,7 @@ // Copyright (c) 2020 Johannes Stoelp +#include + int gCalled = 0; const char* get_greet() { @@ -11,3 +13,11 @@ const char* get_greet2() { ++gCalled; return "Hello 2 from libgreet.so!"; } + +__attribute__((constructor)) static void libinit() { /* static -> generates R_X86_64_RELATIVE relocation */ + pfmt("libgreet.so: libinit\n"); +} + +__attribute__((destructor)) void libfini() { /* non static -> generates R_X86_64_64 relocation */ + pfmt("libgreet.so: libfini\n"); +} -- cgit v1.2.3 From c7fddba6753757be7def5670d8388e0d20bd4eab Mon Sep 17 00:00:00 2001 From: johannst Date: Mon, 26 Apr 2021 21:34:20 +0200 Subject: setup routine for got + brush up some comments --- 04_dynld_nostd/dynld.c | 85 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 61 insertions(+), 24 deletions(-) (limited to '04_dynld_nostd') diff --git a/04_dynld_nostd/dynld.c b/04_dynld_nostd/dynld.c index a7b0fe1..30e60d5 100644 --- a/04_dynld_nostd/dynld.c +++ b/04_dynld_nostd/dynld.c @@ -471,17 +471,22 @@ static void resolve_relocs(const Dso* dso, const LinkMap* map) { /// Dynamic Linking (lazy resolve) /// ------------------------------ +// Mark `dynresolve_entry` as `naked` because we want to fully control the +// stack layout. +// // `noreturn` Function never returns. // `naked` Don't generate prologue/epilogue sequences. __attribute__((noreturn)) __attribute__((naked)) static void dynresolve_entry() { asm("dynresolve_entry:\n\t" - // Pop arguments on stack from PLT0 into rdi/rsi argument registers. + // Pop arguments of PLT0 from the stack into rdi/rsi registers + // These are the first two integer arguments registers as defined by + // the SystemV abi and hence will be passed correctly to `dynresolve`. "pop %rdi\n\t" // GOT[1] entry (pushed by PLT0 pad). "pop %rsi\n\t" // Relocation index (pushed by PLT0 pad). "jmp dynresolve"); } -// `used` Foce to emit code for function. +// `used` Force to emit code for function. // `unused` Don't warn about unused function. __attribute__((used)) __attribute__((unused)) static void dynresolve(uint64_t got1, uint64_t reloc_idx) { ERROR_ON(true, @@ -492,6 +497,50 @@ __attribute__((used)) __attribute__((unused)) static void dynresolve(uint64_t go } +/// --------- +/// Setup GOT +/// --------- + +void setup_got(const Dso* dso) { + // GOT entries {0, 1, 2} have special meaning for the dynamic link process. + // GOT[0] Hold address of dynamic structure referenced by `_DYNAMIC`. + // GOT[1] Argument pushed by PLT0 pad on stack before jumping to GOT[2], + // can be freely used by dynamic linker to identify the caller. + // GOT[2] Jump target for PLT0 pad when doing dynamic resolve (lazy). + // + // We will not make use of GOT[0]/GOT[1] here but only GOT[2]. + + // Install dynamic resolve handler. This handler is used when binding + // symbols lazy. + // + // The handler is installed in the `GOT[2]` entry for each DSO object that + // has a GOT. It is jumped to from the `PLT0` pad with the following two + // arguments passed via the stack: + // - GOT[1] entry. + // - Relocation index. + // + // This can be seen in the following disassembly of section .plt: + // PLT0: + // push QWORD PTR [rip+0x3002] # GOT[1] + // jmp QWORD PTR [rip+0x3004] # GOT[2] + // nop DWORD PTR [rax+0x0] + // + // PLT1: + // jmp QWORD PTR [rip+0x3002] # GOT[3]; entry for + // push 0x0 # Relocation index + // jmp 401000 + // + // The handler at GOT[2] can pop the arguments as follows: + // pop %rdi // GOT[1] entry. + // pop %rsi // Relocation index. + + if (dso->dynamic[DT_PLTGOT] != 0) { + uint64_t* got = (uint64_t*)(dso->base + dso->dynamic[DT_PLTGOT]); + got[2] = (uint64_t)&dynresolve_entry; + } +} + + /// ------------------------- /// Dynamic Linker Entrypoint /// ------------------------- @@ -517,7 +566,7 @@ void dl_entry(const uint64_t* prctx) { ERROR_ON(dso_prog.needed_len != 1, "User program should have exactly one dependency!"); const Dso dso_lib = map_dependency(get_str(&dso_prog, dso_prog.needed[0])); - ERROR_ON(dso_lib.needed_len != 0, "The programs dependency should be stand-alone!"); + ERROR_ON(dso_lib.needed_len != 0, "The library should not have any further dependencies!"); // Setup LinkMap. // @@ -537,29 +586,17 @@ void dl_entry(const uint64_t* prctx) { // Initialize main program. init(&dso_prog); - // Install dynamic resolve handler (lazy resolve). + // Setup global offset table (GOT). // - // The dynamic resolve handler is used when binding symbols lazily. Hence - // it should not be called in this example as we resolve all relocations - // before transfering controll to the user program. + // This installs a dynamic resolve handler, which should not be called in + // this example as we resolve all relocations before transferring control + // to the user program. // For safety we still install a handler which will terminate the program - // once it is called, if we wouldn't install this handler the program would - // most probably SEGFAULT. - // - // The handler is installed in the `GOT[2]` entry for each DSO object that - // has an GOT. It is jumped to from the `PLT0` pad with the following two - // arguments passed via the stack: - // pop %rdi // GOT[1] entry. - // pop %rsi // Relocation index. - { - uint64_t* got = (uint64_t*)(dso_prog.base + dso_prog.dynamic[DT_PLTGOT]); - // Jump target for PLT0 pad. - got[2] = (uint64_t)&dynresolve_entry; - } - - // GOT[0]; // Hold address of dynamic structure referenced by `_DYNAMIC`. - // GOT[1]; // Pushed by PLT0 pad on stack before jumping to got[2] -> Word the dynamic linker can use to identify the caller. - // GOT[2]; // Jump target for PLT0 pad (when doing lazy resolving). + // once it is called. If we wouldn't install this handler the program would + // most probably SEGFAULT in case symbol binding would be invoked during + // runtime. + setup_got(&dso_lib); + setup_got(&dso_prog); // Transfer control to user program. dso_prog.entry(); -- cgit v1.2.3 From 96cf3e4ee49c256b214795e30eb66801bcdcef6d Mon Sep 17 00:00:00 2001 From: johannst Date: Mon, 26 Apr 2021 21:41:43 +0200 Subject: Makefile: update comments --- 04_dynld_nostd/Makefile | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) (limited to '04_dynld_nostd') diff --git a/04_dynld_nostd/Makefile b/04_dynld_nostd/Makefile index e72dde8..e4dceb1 100644 --- a/04_dynld_nostd/Makefile +++ b/04_dynld_nostd/Makefile @@ -7,9 +7,12 @@ COMMON_CFLAGS := -g -O0 -Wall -Wextra \ run: main ./$< +# Build the example user program. +# +# We explicitly set the dynamic linker to `dynld.so` and use the ELF hash table +# (DT_HASH), as we didn't implement support for the GNU hash table in our +# dynamic linker. main: dynld.so libgreet.so main.c ../lib/libcommon.a - @# For now ew only add support for ELF hash tables (DT_HASH). - @# Therefore we specify the `hash-style` below. gcc -o $@ \ $(COMMON_CFLAGS) \ -L$(CURDIR) -lgreet \ @@ -18,20 +21,26 @@ main: dynld.so libgreet.so main.c ../lib/libcommon.a -no-pie \ $(filter %.c %.a, $^) - readelf -W --dynamic $@ - readelf -W --program-headers $@ - objdump --disassemble -j .plt -M intel $@ - objdump --disassemble=_start -M intel $@ + #readelf -W --dynamic $@ + #readelf -W --program-headers $@ + #objdump --disassemble -j .plt -M intel $@ + #objdump --disassemble=_start -M intel $@ +# Build the example shared library. +# +# We explicitly use the ELF hash table (DT_HASH), as we didn't implement +# support for the GNU hash table in our dynamic linker. libgreet.so: libgreet.c - @# For now ew only add support for ELF hash tables (DT_HASH). - @# Therefore we specify the `hash-style` below. gcc -o $@ \ $(COMMON_CFLAGS) \ -fPIC -shared \ -Wl,--hash-style=sysv \ $^ +# Build the dynamic linker. +# +# We assert that the dynamic linker doesn't contain any relocations as we +# didn't implement support to resolve its own relocations. dynld.so: dynld.S dynld.c ../lib/libcommon.a gcc -o $@ \ $(COMMON_CFLAGS) \ @@ -41,9 +50,8 @@ dynld.so: dynld.S dynld.c ../lib/libcommon.a -Wl,--no-allow-shlib-undefined \ $^ - @if readelf -W -S $@ | grep plt >& /dev/null; then \ - echo "ERROR: $@ contains PLT while we don't support relocation calls in $@!"; \ - echo " All function calls in $@ must be statically resolved!"; \ + @if ! readelf -r $@ | grep 'There are no relocations in this file' >& /dev/null; then \ + echo "ERROR: $@ contains relocations while we don't support relocations in $@!"; \ exit 1; \ fi -- cgit v1.2.3 From 7da4d2b9cfa1d09991431f849d62a8a8d30f5e51 Mon Sep 17 00:00:00 2001 From: johannst Date: Mon, 26 Apr 2021 21:48:01 +0200 Subject: libgreet/main: updated comments --- 04_dynld_nostd/libgreet.c | 10 ++++++++-- 04_dynld_nostd/main.c | 5 ++--- 2 files changed, 10 insertions(+), 5 deletions(-) (limited to '04_dynld_nostd') diff --git a/04_dynld_nostd/libgreet.c b/04_dynld_nostd/libgreet.c index e697690..f90bdbf 100644 --- a/04_dynld_nostd/libgreet.c +++ b/04_dynld_nostd/libgreet.c @@ -5,19 +5,25 @@ int gCalled = 0; const char* get_greet() { + // Reference global variable -> generates RELA relocation (R_X86_64_GLOB_DAT). ++gCalled; return "Hello from libgreet.so!"; } const char* get_greet2() { + // Reference global variable -> generates RELA relocation (R_X86_64_GLOB_DAT). ++gCalled; return "Hello 2 from libgreet.so!"; } -__attribute__((constructor)) static void libinit() { /* static -> generates R_X86_64_RELATIVE relocation */ +// Definition of `static` function which is referenced from the `INIT` dynamic +// section entry -> generates R_X86_64_RELATIVE relocation. +__attribute__((constructor)) static void libinit() { pfmt("libgreet.so: libinit\n"); } -__attribute__((destructor)) void libfini() { /* non static -> generates R_X86_64_64 relocation */ +// Definition of `non static` function which is referenced from the `FINI` +// dynamic section entry -> generates R_X86_64_64 relocation. +__attribute__((destructor)) void libfini() { pfmt("libgreet.so: libfini\n"); } diff --git a/04_dynld_nostd/main.c b/04_dynld_nostd/main.c index 709fad0..6efa5fc 100644 --- a/04_dynld_nostd/main.c +++ b/04_dynld_nostd/main.c @@ -1,7 +1,6 @@ // Copyright (c) 2020 Johannes Stoelp #include -#include // API of `libgreet.so`. extern const char* get_greet(); @@ -11,10 +10,10 @@ extern int gCalled; void _start() { pfmt("Running _start() @ %s\n", __FILE__); - // Call function from libgreet.so -> generates PLT relocations. + // Call function from libgreet.so -> generates PLT relocations (R_X86_64_JUMP_SLOT). pfmt("get_greet() -> %s\n", get_greet()); pfmt("get_greet2() -> %s\n", get_greet2()); - // Reference global variable from libgreet.so -> generates RELA relocation. + // Reference global variable from libgreet.so -> generates RELA relocation (R_X86_64_COPY). pfmt("libgreet.so called %d times\n", gCalled); } -- cgit v1.2.3 From 2aeab48d030e7730679d9c7b6e20f15437cd4e0a Mon Sep 17 00:00:00 2001 From: johannst Date: Mon, 26 Apr 2021 21:52:39 +0200 Subject: dynld: add fold marker as code grew --- 04_dynld_nostd/dynld.c | 62 +++++++++++++++++++------------------------------- 1 file changed, 23 insertions(+), 39 deletions(-) (limited to '04_dynld_nostd') diff --git a/04_dynld_nostd/dynld.c b/04_dynld_nostd/dynld.c index 30e60d5..9b14590 100644 --- a/04_dynld_nostd/dynld.c +++ b/04_dynld_nostd/dynld.c @@ -9,10 +9,7 @@ #include #include - -/// ---------------- -/// Global Constants -/// ---------------- +// {{{ Global constans enum { // Hard-coded page size. @@ -23,10 +20,8 @@ enum { MAX_NEEDED = 1, }; - -/// -------- -/// Execinfo -/// -------- +// }}} +// {{{ Execinfo typedef struct { uint64_t argc; // Number of commandline arguments. @@ -61,10 +56,8 @@ ExecInfo get_exec_info(const uint64_t* prctx) { return info; } - -/// --- -/// Dso -/// --- +// }}} +// {{{ Dso typedef struct { uint8_t* base; // Base address. @@ -204,10 +197,8 @@ const Elf64Rela* get_reloca(const Dso* dso, const uint64_t idx) { return (const Elf64Rela*)(dso->base + dso->dynamic[DT_RELA]) + idx; } - -/// ----------- -/// Init & Fini -/// ----------- +// }}} +// {{{ Init & Fini typedef void (*initfptr)(); @@ -239,10 +230,8 @@ static void fini(const Dso* dso) { } } - -/// ------------- -/// Symbol lookup -/// ------------- +// }}} +// {{{ Symbol lookup int strcmp(const char* s1, const char* s2) { while (*s1 == *s2 && *s1) { @@ -275,10 +264,8 @@ void* lookup_sym(const Dso* dso, const char* sym_name) { return 0; } - -/// ----------------------------- -/// Map Shared Library Dependency -/// ----------------------------- +// }}} +// {{{ Map Shared Library Dependency Dso map_dependency(const char* dependency) { // For simplicity we only search for SO dependencies in the current working dir. @@ -386,10 +373,8 @@ Dso map_dependency(const char* dependency) { return dso; } - -/// ------------------- -/// Resolve relocations -/// ------------------- +// }}} +// {{{ Resolve relocations struct LinkMap { const Dso* dso; // Pointer to Dso list object. @@ -466,10 +451,8 @@ static void resolve_relocs(const Dso* dso, const LinkMap* map) { } } - -/// ------------------------------ -/// Dynamic Linking (lazy resolve) -/// ------------------------------ +// }}} +// {{{ Dynamic Linking (lazy resolve) // Mark `dynresolve_entry` as `naked` because we want to fully control the // stack layout. @@ -496,10 +479,8 @@ __attribute__((used)) __attribute__((unused)) static void dynresolve(uint64_t go got1, reloc_idx); } - -/// --------- -/// Setup GOT -/// --------- +// }}} +// {{{ Setup GOT void setup_got(const Dso* dso) { // GOT entries {0, 1, 2} have special meaning for the dynamic link process. @@ -540,10 +521,9 @@ void setup_got(const Dso* dso) { } } +// }}} -/// ------------------------- -/// Dynamic Linker Entrypoint -/// ------------------------- +// {{{ Dynamic Linker Entrypoint void dl_entry(const uint64_t* prctx) { // Parse SystemV ABI block. @@ -608,3 +588,7 @@ void dl_entry(const uint64_t* prctx) { _exit(0); } + +// }}} + +// vim:fdm=marker -- cgit v1.2.3 From c05511262096681c39691ec5c3fbe30797ba7c4a Mon Sep 17 00:00:00 2001 From: johannst Date: Mon, 26 Apr 2021 22:33:37 +0200 Subject: dynld: mark internal functions static --- 04_dynld_nostd/dynld.c | 41 +++++++++++++++++------------------------ 1 file changed, 17 insertions(+), 24 deletions(-) (limited to '04_dynld_nostd') diff --git a/04_dynld_nostd/dynld.c b/04_dynld_nostd/dynld.c index 9b14590..02fbe9e 100644 --- a/04_dynld_nostd/dynld.c +++ b/04_dynld_nostd/dynld.c @@ -34,7 +34,7 @@ typedef struct { // Interpret and extract data passed on the stack by the Linux Kernel // when loading the initial process image. // The data is organized according to the SystemV x86_64 ABI. -ExecInfo get_exec_info(const uint64_t* prctx) { +static ExecInfo get_exec_info(const uint64_t* prctx) { ExecInfo info = {0}; info.argc = *prctx; @@ -64,10 +64,10 @@ typedef struct { void (*entry)(); // Entry function. uint64_t dynamic[DT_MAX_CNT]; // `.dynamic` section entries. uint64_t needed[MAX_NEEDED]; // Shared object dependencies (`DT_NEEDED` entries). - uint8_t needed_len; // Number of `DT_NEEDED` entries (SO dependencies). + uint32_t needed_len; // Number of `DT_NEEDED` entries (SO dependencies). } Dso; -void decode_dynamic(Dso* dso, uint64_t dynoff) { +static void decode_dynamic(Dso* dso, uint64_t dynoff) { // Decode `.dynamic` section of the `dso`. for (const Elf64Dyn* dyn = (const Elf64Dyn*)(dso->base + dynoff); dyn->tag != DT_NULL; ++dyn) { if (dyn->tag == DT_NEEDED) { @@ -87,18 +87,12 @@ void decode_dynamic(Dso* dso, uint64_t dynoff) { ERROR_ON(dso->dynamic[DT_SYMENT] == 0, "DT_SYMENT missing in dynamic section!"); ERROR_ON(dso->dynamic[DT_SYMENT] != sizeof(Elf64Sym), "ELf64Sym size miss-match!"); - // Check for relocation entries related to PLT. - // ERROR_ON(dso->dynamic[DT_JMPREL] == 0, "DT_JMPREL missing in dynamic section!"); - // ERROR_ON(dso->dynamic[DT_PLTRELSZ] == 0, "DT_PLTRELSZ missing in dynamic section!"); - // ERROR_ON(dso->dynamic[DT_PLTREL] == 0, "DT_PLTREL missing in dynamic section!"); - // ERROR_ON(dso->dynamic[DT_PLTREL] != DT_RELA, "x86_64 only uses RELA entries!"); - // Check for SystemV hash table. We only support SystemV hash tables // `DT_HASH`, not gnu hash tables `DT_GNU_HASH`. ERROR_ON(dso->dynamic[DT_HASH] == 0, "DT_HASH missing in dynamic section!"); } -Dso get_prog_dso(const ExecInfo* info) { +static Dso get_prog_dso(const ExecInfo* info) { Dso prog = {0}; // Determine the base address of the user program. @@ -158,7 +152,7 @@ Dso get_prog_dso(const ExecInfo* info) { return prog; } -uint64_t get_num_dynsyms(const Dso* dso) { +static uint64_t get_num_dynsyms(const Dso* dso) { ERROR_ON(dso->dynamic[DT_HASH] == 0, "DT_HASH missing in dynamic section!"); // Get SystemV hash table. @@ -177,22 +171,22 @@ uint64_t get_num_dynsyms(const Dso* dso) { return hashtab[1]; } -const char* get_str(const Dso* dso, const uint64_t idx) { +static const char* get_str(const Dso* dso, uint64_t idx) { ERROR_ON(dso->dynamic[DT_STRSZ] < idx, "String table indexed out-of-bounds!"); return (const char*)(dso->base + dso->dynamic[DT_STRTAB] + idx); } -const Elf64Sym* get_sym(const Dso* dso, const uint64_t idx) { +static const Elf64Sym* get_sym(const Dso* dso, uint64_t idx) { ERROR_ON(get_num_dynsyms(dso) < idx, "Symbol table index out-of-bounds!"); return (const Elf64Sym*)(dso->base + dso->dynamic[DT_SYMTAB]) + idx; } -const Elf64Rela* get_pltreloca(const Dso* dso, const uint64_t idx) { +static const Elf64Rela* get_pltreloca(const Dso* dso, uint64_t idx) { ERROR_ON(dso->dynamic[DT_PLTRELSZ] < sizeof(Elf64Rela) * idx, "PLT relocation table indexed out-of-bounds!"); return (const Elf64Rela*)(dso->base + dso->dynamic[DT_JMPREL]) + idx; } -const Elf64Rela* get_reloca(const Dso* dso, const uint64_t idx) { +static const Elf64Rela* get_reloca(const Dso* dso, uint64_t idx) { ERROR_ON(dso->dynamic[DT_RELASZ] < sizeof(Elf64Rela) * idx, "RELA relocation table indexed out-of-bounds!"); return (const Elf64Rela*)(dso->base + dso->dynamic[DT_RELA]) + idx; } @@ -233,7 +227,7 @@ static void fini(const Dso* dso) { // }}} // {{{ Symbol lookup -int strcmp(const char* s1, const char* s2) { +static inline int strcmp(const char* s1, const char* s2) { while (*s1 == *s2 && *s1) { ++s1; ++s2; @@ -249,14 +243,14 @@ int strcmp(const char* s1, const char* s2) { // performance for large symbol tables. // // `dso` A handle to the dso which dynamic symbol table should be searched. -// `sym_name` Name of the symbol to look up. -void* lookup_sym(const Dso* dso, const char* sym_name) { +// `symname` Name of the symbol to look up. +static void* lookup_sym(const Dso* dso, const char* symname) { for (unsigned i = 0; i < get_num_dynsyms(dso); ++i) { const Elf64Sym* sym = get_sym(dso, i); if ((ELF64_ST_TYPE(sym->info) == STT_OBJECT || ELF64_ST_TYPE(sym->info) == STT_FUNC) && ELF64_ST_BIND(sym->info) == STB_GLOBAL && sym->shndx != SHN_UNDEF) { - if (strcmp(sym_name, get_str(dso, sym->name)) == 0) { + if (strcmp(symname, get_str(dso, sym->name)) == 0) { return dso->base + sym->value; } } @@ -267,7 +261,7 @@ void* lookup_sym(const Dso* dso, const char* sym_name) { // }}} // {{{ Map Shared Library Dependency -Dso map_dependency(const char* dependency) { +static Dso map_dependency(const char* dependency) { // For simplicity we only search for SO dependencies in the current working dir. ERROR_ON(access(dependency, R_OK) != 0, "Dependency '%s' does not exist!\n", dependency); @@ -376,11 +370,10 @@ Dso map_dependency(const char* dependency) { // }}} // {{{ Resolve relocations -struct LinkMap { +typedef struct LinkMap { const Dso* dso; // Pointer to Dso list object. const struct LinkMap* next; // Pointer to next LinkMap entry ('0' terminates the list). -}; -typedef struct LinkMap LinkMap; +} LinkMap; // Resolve a single relocation of `dso`. // @@ -482,7 +475,7 @@ __attribute__((used)) __attribute__((unused)) static void dynresolve(uint64_t go // }}} // {{{ Setup GOT -void setup_got(const Dso* dso) { +static void setup_got(const Dso* dso) { // GOT entries {0, 1, 2} have special meaning for the dynamic link process. // GOT[0] Hold address of dynamic structure referenced by `_DYNAMIC`. // GOT[1] Argument pushed by PLT0 pad on stack before jumping to GOT[2], -- cgit v1.2.3 From fc137e7d0263a0fe908ca1a150e34a9c8b9902d4 Mon Sep 17 00:00:00 2001 From: johannst Date: Mon, 26 Apr 2021 22:47:53 +0200 Subject: add check for PT_TLS phdr --- 04_dynld_nostd/dynld.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to '04_dynld_nostd') diff --git a/04_dynld_nostd/dynld.c b/04_dynld_nostd/dynld.c index 02fbe9e..6e1bebf 100644 --- a/04_dynld_nostd/dynld.c +++ b/04_dynld_nostd/dynld.c @@ -139,6 +139,8 @@ static Dso get_prog_dso(const ExecInfo* info) { } else if (phdr->type == PT_DYNAMIC) { dynoff = phdr->vaddr; } + + ERROR_ON(phdr->type == PT_TLS, "Thread local storage not supported found PT_TLS!"); } ERROR_ON(dynoff == 0, "PT_DYNAMIC entry missing in the user programs PHDR!"); @@ -311,6 +313,8 @@ static Dso map_dependency(const char* dependency) { addr_end = p->vaddr + p->memsz; } } + + ERROR_ON(phdr->type == PT_TLS, "Thread local storage not supported found PT_TLS!"); } // Align start address to the next lower page boundary. -- cgit v1.2.3