diff options
-rw-r--r-- | .clang-format | 2 | ||||
-rw-r--r-- | 04_dynld_nostd/Makefile | 63 | ||||
-rw-r--r-- | 04_dynld_nostd/dynld.S | 27 | ||||
-rw-r--r-- | 04_dynld_nostd/dynld.c | 591 | ||||
-rw-r--r-- | 04_dynld_nostd/libgreet.c | 29 | ||||
-rw-r--r-- | 04_dynld_nostd/main.c | 19 | ||||
-rw-r--r-- | lib/Makefile | 7 | ||||
-rw-r--r-- | lib/include/alloc.h | 6 | ||||
-rw-r--r-- | lib/include/auxv.h | 2 | ||||
-rw-r--r-- | lib/include/common.h | 21 | ||||
-rw-r--r-- | lib/include/elf.h | 156 | ||||
-rw-r--r-- | lib/include/syscall.h | 58 | ||||
-rw-r--r-- | lib/include/syscalls.h | 40 | ||||
-rw-r--r-- | lib/src/alloc.c | 87 | ||||
-rw-r--r-- | lib/src/common.c | 38 | ||||
-rw-r--r-- | lib/src/fmt.c | 12 | ||||
-rw-r--r-- | lib/src/io.c | 11 | ||||
-rw-r--r-- | lib/src/syscalls.c | 62 | ||||
-rw-r--r-- | test/checker.cc | 56 | ||||
-rw-r--r-- | test/test_helper.h | 5 |
20 files changed, 1226 insertions, 66 deletions
diff --git a/.clang-format b/.clang-format index 0e80858..74845b8 100644 --- a/.clang-format +++ b/.clang-format @@ -60,7 +60,7 @@ DerivePointerAlignment: false DisableFormat: false ExperimentalAutoDetectBinPacking: false FixNamespaceComments: true -IncludeBlocks: Regroup +IncludeBlocks: Preserve # Could use `IncludeCategories` to define rules for includes IndentCaseLabels: true IndentPPDirectives: AfterHash diff --git a/04_dynld_nostd/Makefile b/04_dynld_nostd/Makefile new file mode 100644 index 0000000..e4dceb1 --- /dev/null +++ b/04_dynld_nostd/Makefile @@ -0,0 +1,63 @@ +# Copyright (c) 2020 Johannes Stoelp + +COMMON_CFLAGS := -g -O0 -Wall -Wextra \ + -I../lib/include \ + -nostartfiles -nodefaultlibs + +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 + 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 $@ + +# 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 + 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) \ + -fPIC -static-pie \ + -fvisibility=hidden \ + -Wl,--entry=dl_start \ + -Wl,--no-allow-shlib-undefined \ + $^ + + @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 + +../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 <asm/unistd.h> + +.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..6e1bebf --- /dev/null +++ b/04_dynld_nostd/dynld.c @@ -0,0 +1,591 @@ +// Copyright (c) 2021 Johannes Stoelp + +#include <auxv.h> +#include <common.h> +#include <elf.h> +#include <io.h> +#include <syscalls.h> + +#include <stdbool.h> +#include <stdint.h> + +// {{{ Global constans + +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. +static 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). + uint32_t needed_len; // Number of `DT_NEEDED` entries (SO dependencies). +} Dso; + +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) { + 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 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!"); +} + +static Dso get_prog_dso(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(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!"); + + // 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; +} + +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. + 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]; +} + +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); +} + +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; +} + +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; +} + +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; +} + +// }}} +// {{{ 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 + +static inline int strcmp(const char* s1, const char* s2) { + while (*s1 == *s2 && *s1) { + ++s1; + ++s2; + } + return *(unsigned char*)s1 - *(unsigned char*)s2; +} + +// 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. +// `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(symname, get_str(dso, sym->name)) == 0) { + return dso->base + sym->value; + } + } + } + return 0; +} + +// }}} +// {{{ Map Shared Library 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); + + 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; + } + } + + ERROR_ON(phdr->type == PT_TLS, "Thread local storage not supported found PT_TLS!"); + } + + // 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 + p->vaddr + 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; +} + +// }}} +// {{{ Resolve relocations + +typedef struct LinkMap { + const Dso* dso; // Pointer to Dso list object. + const struct LinkMap* next; // Pointer to next LinkMap entry ('0' terminates the list). +} LinkMap; + +// 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) { + // 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); + + // Find symbol address. + void* symaddr = 0; + // 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", reloctype == R_X86_64_RELATIVE ? "<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; + 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); + } +} + +// 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); + } + + // 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); + } +} + +// }}} +// {{{ 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 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` 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, + "ERROR: dynresolve request not supported!" + "\n\tGOT[1] = 0x%x" + "\n\treloc_idx = %d\n", + got1, reloc_idx); +} + +// }}} +// {{{ Setup GOT + +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], + // 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 <PLT1> + // push 0x0 # Relocation index + // jmp 401000 <PLT0> + // + // 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 + +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_dso(&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 library should not have any further dependencies!"); + + // 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 of the library (dependency). + resolve_relocs(&dso_lib, &map_prog); + // Resolve relocations of the main program. + resolve_relocs(&dso_prog, &map_prog); + + // Initialize library. + init(&dso_lib); + // Initialize main program. + init(&dso_prog); + + // Setup global offset table (GOT). + // + // 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 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(); + + // Finalize main program. + fini(&dso_prog); + // Finalize library. + fini(&dso_lib); + + _exit(0); +} + +// }}} + +// vim:fdm=marker diff --git a/04_dynld_nostd/libgreet.c b/04_dynld_nostd/libgreet.c new file mode 100644 index 0000000..f90bdbf --- /dev/null +++ b/04_dynld_nostd/libgreet.c @@ -0,0 +1,29 @@ +// Copyright (c) 2020 Johannes Stoelp + +#include <io.h> + +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!"; +} + +// 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"); +} + +// 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 new file mode 100644 index 0000000..6efa5fc --- /dev/null +++ b/04_dynld_nostd/main.c @@ -0,0 +1,19 @@ +// Copyright (c) 2020 Johannes Stoelp + +#include <io.h> + +// 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 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 (R_X86_64_COPY). + pfmt("libgreet.so called %d times\n", gCalled); +} diff --git a/lib/Makefile b/lib/Makefile index 29c103a..5f8a1a9 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -1,13 +1,18 @@ # Copyright (c) 2020 Johannes Stoelp +HDR+=include/alloc.h HDR+=include/auxv.h HDR+=include/elf.h HDR+=include/fmt.h HDR+=include/io.h HDR+=include/syscall.h +HDR+=include/syscalls.h -DEP+=src/io.o +DEP+=src/alloc.o +DEP+=src/common.o DEP+=src/fmt.o +DEP+=src/io.o +DEP+=src/syscalls.o libcommon.a: $(HDR) $(DEP) ar -crs $@ $(filter %.o, $^) diff --git a/lib/include/alloc.h b/lib/include/alloc.h new file mode 100644 index 0000000..baf8d3d --- /dev/null +++ b/lib/include/alloc.h @@ -0,0 +1,6 @@ +// Copyright (c) 2021 Johannes Stoelp + +#pragma once + +void* alloc(unsigned size); +void dealloc(void* ptr); diff --git a/lib/include/auxv.h b/lib/include/auxv.h index 42dac38..1ac953e 100644 --- a/lib/include/auxv.h +++ b/lib/include/auxv.h @@ -15,7 +15,7 @@ #define AT_EXECFD 2 /* [val] File descriptor of user program (in case Linux Kernel didn't mapped) */ #define AT_PHDR 3 /* [ptr] Address of Phdr of use program (in case Kernel mapped user program) */ #define AT_PHENT 4 /* [val] Size in bytes of one Phdr entry */ -#define AT_PHNUM 5 /* [val] Number of Phread entries */ +#define AT_PHNUM 5 /* [val] Number of Phdr entries */ #define AT_PAGESZ 6 /* [val] System page size */ #define AT_BASE 7 /* [ptr] `base address` interpreter was loaded to */ #define AT_FLAGS 8 /* [val] */ diff --git a/lib/include/common.h b/lib/include/common.h index 5ea6050..d006d71 100644 --- a/lib/include/common.h +++ b/lib/include/common.h @@ -3,14 +3,17 @@ #pragma once #include "io.h" -#include "syscall.h" +#include "syscalls.h" -#include <asm/unistd.h> - -#define ERROR_ON(cond, ...) \ - do { \ - if ((cond)) { \ - efmt(__VA_ARGS__); \ - syscall1(__NR_exit, 1); \ - } \ +#define ERROR_ON(cond, fmt, ...) \ + do { \ + if ((cond)) { \ + efmt("%s:%d " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__); \ + _exit(1); \ + } \ } while (0) + + +void* memset(void* s, int c, size_t n); +void* memcpy(void* d, const void* s, size_t n); + diff --git a/lib/include/elf.h b/lib/include/elf.h index a0fe6f7..0317859 100644 --- a/lib/include/elf.h +++ b/lib/include/elf.h @@ -4,6 +4,51 @@ #include <stdint.h> +/// ---------- +/// ELF Header +/// ---------- + +// Index into `ident`. +#define EI_MAG0 0 +#define EI_MAG1 1 +#define EI_MAG2 2 +#define EI_MAG3 3 +#define EI_CLASS 4 +#define EI_DATA 5 +#define EI_OSABI 7 + +// indent[EI_CLASS] +#define ELFCLASS32 1 +#define ELFCLASS64 2 + +// indent[EI_CLASS] +#define ELFDATA2LSB 1 +#define ELFDATA2MSB 2 + +// indent[EI_OSABI] +#define ELFOSABI_SYSV 0 + +// Objec file `type`. +#define ET_NONE 0 +#define ET_DYN 3 + +typedef struct { + uint8_t ident[16]; // ELF identification. + uint16_t type; // Object file type. + uint16_t machine; // Machine type. + uint32_t version; // Object file version. + uint64_t entry; // Entrypoint address. + uint64_t phoff; // Program header file offset. + uint64_t shoff; // Section header file offset. + uint32_t flags; // Processor specific flags. + uint16_t ehsize; // ELF header size. + uint16_t phentsize; // Program header entry size. + uint16_t phnum; // Number of program header entries. + uint16_t shentsize; // Section header entry size. + uint16_t shnum; // Number of section header entries. + uint16_t shstrndx; // Section name string table index. +} Elf64Ehdr; + /// -------------- /// Program Header /// -------------- @@ -15,6 +60,7 @@ #define PT_NOTE 4 /* Location of auxiliary information */ #define PT_SHLIB 5 /* Reserved, but unspecified semantic */ #define PT_PHDR 6 /* Location & size of program headers itself */ +#define PT_TLS 7 /* Thread local storage */ #define PT_GNU_EH_FRAME 0x6474e550 /* [x86-64] stack unwinding tables */ #define PT_LOPROC 0x70000000 @@ -39,32 +85,36 @@ typedef struct { /// Dynamic Section /// --------------- -#define DT_NULL 0 /* [ignored] Marks end of dynamic section */ -#define DT_NEEDED 1 /* [val] Name of needed library */ -#define DT_PLTRELSZ 2 /* [val] Size in bytes of PLT relocs */ -#define DT_PLTGOT 3 /* [ptr] Processor defined value */ -#define DT_HASH 4 /* [ptr] Address of symbol hash table */ -#define DT_STRTAB 5 /* [ptr] Address of string table */ -#define DT_SYMTAB 6 /* [ptr] Address of symbol table */ -#define DT_RELA 7 /* [ptr] Address of Rela relocs */ -#define DT_RELASZ 8 /* [val] Total size of Rela relocs */ -#define DT_RELAENT 9 /* [val] Size of one Rela reloc */ -#define DT_STRSZ 10 /* [val] Size of string table */ -#define DT_SYMENT 11 /* [val] Size of one symbol table entry */ -#define DT_INIT 12 /* [ptr] Address of init function */ -#define DT_FINI 13 /* [ptr] Address of termination function */ -#define DT_SONAME 14 /* [val] Name of shared object */ -#define DT_RPATH 15 /* [val] Library search path (deprecated) */ -#define DT_SYMBOLIC 16 /* [ignored] Start symbol search here */ -#define DT_REL 17 /* [ptr] Address of Rel relocs */ -#define DT_RELSZ 18 /* [val] Total size of Rel relocs */ -#define DT_RELENT 19 /* [val] Size of one Rel reloc */ -#define DT_PLTREL 20 /* [val] Type of reloc in PLT */ -#define DT_DEBUG 21 /* [ptr] For debugging; unspecified */ -#define DT_TEXTREL 22 /* [ignored] Reloc might modify .text */ -#define DT_JMPREL 23 /* [ptr] Address of PLT relocs */ -#define DT_BIND_NOW 24 /* [ignored] Process relocations of object */ -#define DT_MAX_CNT 25 +#define DT_NULL 0 /* [ignored] Marks end of dynamic section */ +#define DT_NEEDED 1 /* [val] Name of needed library */ +#define DT_PLTRELSZ 2 /* [val] Size in bytes of PLT relocs */ +#define DT_PLTGOT 3 /* [ptr] Processor defined value */ +#define DT_HASH 4 /* [ptr] Address of symbol hash table */ +#define DT_STRTAB 5 /* [ptr] Address of string table */ +#define DT_SYMTAB 6 /* [ptr] Address of symbol table */ +#define DT_RELA 7 /* [ptr] Address of Rela relocs */ +#define DT_RELASZ 8 /* [val] Total size of Rela relocs */ +#define DT_RELAENT 9 /* [val] Size of one Rela reloc */ +#define DT_STRSZ 10 /* [val] Size of string table */ +#define DT_SYMENT 11 /* [val] Size of one symbol table entry */ +#define DT_INIT 12 /* [ptr] Address of init function */ +#define DT_FINI 13 /* [ptr] Address of termination function */ +#define DT_SONAME 14 /* [val] Name of shared object */ +#define DT_RPATH 15 /* [val] Library search path (deprecated) */ +#define DT_SYMBOLIC 16 /* [ignored] Start symbol search here */ +#define DT_REL 17 /* [ptr] Address of Rel relocs */ +#define DT_RELSZ 18 /* [val] Total size of Rel relocs */ +#define DT_RELENT 19 /* [val] Size of one Rel reloc */ +#define DT_PLTREL 20 /* [val] Type of reloc in PLT */ +#define DT_DEBUG 21 /* [ptr] For debugging; unspecified */ +#define DT_TEXTREL 22 /* [ignored] Reloc might modify .text */ +#define DT_JMPREL 23 /* [ptr] Address of PLT relocs */ +#define DT_BIND_NOW 24 /* [ignored] Process relocations of object */ +#define DT_INIT_ARRAY 25 /* [ptr] Address of array of initialization functions */ +#define DT_FINI_ARRAY 26 /* [ptr] Address of array of termination functions */ +#define DT_INIT_ARRAYSZ 27 /* [val] Size in bytes of the initialization array */ +#define DT_FINI_ARRAYSZ 28 /* [val] Size in bytes of the termination array */ +#define DT_MAX_CNT 29 typedef struct { uint64_t tag; @@ -73,3 +123,57 @@ typedef struct { void* ptr; }; } Elf64Dyn; + +/// ------------ +/// Symbol Entry +/// ------------ + +typedef struct { + uint32_t name; // Symbol name (index into string table). + uint8_t info; // Symbol Binding bits[7..4] + Symbol Type bits[3..0]. + uint8_t other; // Reserved. + uint16_t shndx; // Section table index. + uint64_t value; // + uint64_t size; // +} Elf64Sym; + +#define ELF64_ST_BIND(i) ((i) >> 4) +#define ELF64_ST_TYPE(i) ((i)&0xf) + +// Symbold Bindings. +#define STB_GLOBAL 1 /* Global symbol, visible to all object files. */ +#define STB_WEAK 2 /* Global scope, but with lower precedence than global symbols. */ + +// Symbol Types. +#define STT_NOTYPE 0 /* No type. */ +#define STT_OBJECT 1 /* Data Object. */ +#define STT_FUNC 2 /* Function entry point. */ + +// Special Section Indicies. +#define SHN_UNDEF 0 /* Undefined section. */ +#define SHN_ABS 0xff1 /* Indicates an absolute value. */ + +/// ----------------- +/// Relocations Entry +/// ----------------- + +typedef struct { + uint64_t offset; // Virtual address of the storage unit affected by the relocation. + uint64_t info; // Symbol table index + relocation type. +} Elf64Rel; + +typedef struct { + uint64_t offset; // Virtual address of the storage unit affected by the relocation. + uint64_t info; // Symbol table index + relocation type. + int64_t addend; // Constant value used to compute the relocation value. +} Elf64Rela; + +#define ELF64_R_SYM(i) ((i) >> 32) +#define ELF64_R_TYPE(i) ((i)&0xffffffffL) + +// x86_64 relocation types. +#define R_X86_64_64 1 /* Absolute 64bit address, address affected by relocation: `base + offset` */ +#define R_X86_64_COPY 5 /* Copy content from sym addr to relocation address: `base + offset` */ +#define R_X86_64_GLOB_DAT 6 /* Address affected by relocation: `base + offset` */ +#define R_X86_64_JUMP_SLOT 7 /* Address affected by relocation: `base + offset` */ +#define R_X86_64_RELATIVE 8 /* Relative address *`base + offset` = `base + addend` */ diff --git a/lib/include/syscall.h b/lib/include/syscall.h index 4947155..10dc545 100644 --- a/lib/include/syscall.h +++ b/lib/include/syscall.h @@ -19,6 +19,7 @@ // // Machine specific constraints (x86_64): // a | a register (eg rax) +// c | c register (eg rcx) // d | d register (eg rdx) // D | di register (eg rdi) // S | si register (eg rsi) @@ -35,29 +36,62 @@ // https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html // https://gcc.gnu.org/onlinedocs/gcc/Machine-Constraints.html#Machine-Constraints // https://gcc.gnu.org/onlinedocs/gcc/Local-Register-Variables.html - -// Linux syscall ABI -// x86-64 -// #syscall: rax -// ret : rax -// instr : syscall -// args : rdi rsi rdx r10 r8 r9 +// +// +// Linux syscall ABI - x86-64 +// #syscall: rax +// ret : rax +// instr : syscall +// args : rdi rsi rdx r10 r8 r9 // // Reference: // syscall(2) +// +// +// X86_64 `syscall` instruction additionally clobbers following registers: +// rcx Store return address. +// r11 Store RFLAGS. +// +// Reference: +// https://www.felixcloutier.com/x86/syscall -#define argcast(A) ((long)(A)) -#define syscall1(n, a1) _syscall1(n, argcast(a1)) -#define syscall3(n, a1, a2, a3) _syscall3(n, argcast(a1), argcast(a2), argcast(a3)) +#define argcast(A) ((long)(A)) +#define syscall1(n, a1) _syscall1(n, argcast(a1)) +#define syscall2(n, a1, a2) _syscall2(n, argcast(a1), argcast(a2)) +#define syscall3(n, a1, a2, a3) _syscall3(n, argcast(a1), argcast(a2), argcast(a3)) +#define syscall4(n, a1, a2, a3, a4) _syscall4(n, argcast(a1), argcast(a2), argcast(a3), argcast(a4)) +#define syscall6(n, a1, a2, a3, a4, a5, a6) _syscall6(n, argcast(a1), argcast(a2), argcast(a3), argcast(a4), argcast(a5), argcast(a6)) static inline long _syscall1(long n, long a1) { long ret; - asm volatile("syscall" : "=a"(ret) : "a"(n), "D"(a1) : "memory"); + asm volatile("syscall" : "=a"(ret) : "a"(n), "D"(a1) : "rcx", "r11", "memory"); + return ret; +} + +static inline long _syscall2(long n, long a1, long a2) { + long ret; + asm volatile("syscall" : "=a"(ret) : "a"(n), "D"(a1), "S"(a2) : "rcx", "r11", "memory"); return ret; } static inline long _syscall3(long n, long a1, long a2, long a3) { long ret; - asm volatile("syscall" : "=a"(ret) : "a"(n), "D"(a1), "S"(a2), "d"(a3) : "memory"); + asm volatile("syscall" : "=a"(ret) : "a"(n), "D"(a1), "S"(a2), "d"(a3) : "rcx", "r11", "memory"); + return ret; +} + +static inline long _syscall4(long n, long a1, long a2, long a3, long a4) { + long ret; + register long r10 asm("r10") = a4; + asm volatile("syscall" : "=a"(ret) : "a"(n), "D"(a1), "S"(a2), "d"(a3), "r"(r10) : "rcx", "r11", "memory"); + return ret; +} + +static inline long _syscall6(long n, long a1, long a2, long a3, long a4, long a5, long a6) { + long ret; + register long r10 asm("r10") = a4; + register long r8 asm("r8") = a5; + register long r9 asm("r9") = a6; + asm volatile("syscall" : "=a"(ret) : "a"(n), "D"(a1), "S"(a2), "d"(a3), "r"(r10), "r"(r8), "r"(r9) : "rcx", "r11", "memory"); return ret; } diff --git a/lib/include/syscalls.h b/lib/include/syscalls.h new file mode 100644 index 0000000..7808e98 --- /dev/null +++ b/lib/include/syscalls.h @@ -0,0 +1,40 @@ +// Copyright (c) 2021 Johannes Stoelp + +#pragma once + +#include <stddef.h> // size_t +#include <sys/types.h> // ssize_t, off_t, ... + +extern int dynld_errno; + +// Syscall definitions taken from corresponding man pages, eg +// open(2) +// read(2) +// ... + +#define O_RDONLY 00 +int open(const char* path, int flags); +int close(int fd); + +#define F_OK 0 +#define R_OK 4 +int access(const char* path, int mode); + +ssize_t write(int fd, const void* buf, size_t count); +ssize_t read(int fd, void* buf, size_t count); +ssize_t pread(int fd, void* buf, size_t count, off_t offset); + +// mmap - prot: +#define PROT_READ 0x1 +#define PROT_WRITE 0x2 +#define PROT_EXEC 0x4 +// mmap - flags: +#define MAP_PRIVATE 0x2 +#define MAP_ANONYMOUS 0x20 +#define MAP_FIXED 0x10 +// mmap - ret: +#define MAP_FAILED ((void*)-1) +void* mmap(void* addr, size_t length, int prot, int flags, int fd, off_t offset); +int munmap(void* addr, size_t length); + +void _exit(int status); diff --git a/lib/src/alloc.c b/lib/src/alloc.c new file mode 100644 index 0000000..8b05d9e --- /dev/null +++ b/lib/src/alloc.c @@ -0,0 +1,87 @@ +// Copyright (c) 2021 Johannes Stoelp + +#include <alloc.h> +#include <common.h> + +#include <stdint.h> + +// Extremely simple and non-thread safe implementation of a dynamic +// memory allocator. Which will greatly suffer under fragmentation as +// we neither use splitting nor coalesce free blocks. It uses +// first-fit and always traverses the block list from the beginning. +// +// Bottom line, this allocator can be optimized in so many ways but it +// doesn't really matter for the purpose of this studies and therefore +// the allocator is implemented in the most naive way. + +// Allocation block descriptor. +struct BlockDescriptor { + unsigned mFree; + unsigned mSize; + struct BlockDescriptor* mNext; +}; + +// Global Allocator. + +// Size of available memory to the allocator. +enum { MEMORY_SIZE = 1 * 1024 * 1024 }; +// Memory for the allocator (statically reserved in the `.bss` section). +uint8_t gMemory[MEMORY_SIZE]; + +// Top index into `gMemory` to indicate next free memory. +unsigned gMemoryTop; + +// List of allocated blocks (free + used). +struct BlockDescriptor* gHead; + +// Request free memory from `gMemory` and advance the `gMemoryTop` index. +static void* brk(unsigned size) { + ERROR_ON(gMemoryTop + size >= MEMORY_SIZE, "Allocator OOM!"); + const unsigned old_top = gMemoryTop; + gMemoryTop += size; + return (void*)(gMemory + old_top); +} + +// Allocate memory chunk of `size` and return pointer to the chunk. +void* alloc(unsigned size) { + struct BlockDescriptor* current = 0; + + // Check if we have a free block in the list of allocated blocks + // that matches the requested size. + current = gHead; + while (current) { + if (current->mFree && current->mSize < size) { + current->mFree = 0; + return (void*)(current + 1); + }; + current = current->mNext; + } + + // Compute real allocation size: Payload + BlockDescriptor. + unsigned real_size = size + sizeof(struct BlockDescriptor); + + // No free block found in the list of blocks, allocate new block. + current = brk(real_size); + + // Initialize new block. + current->mFree = 0; + current->mSize = size; + current->mNext = 0; + + // Insert new block at the beginning of the list of blocks. + if (gHead != 0) { + current->mNext = gHead; + } + gHead = current; + + return (void*)(current + 1); +} + +void dealloc(void* ptr) { + // Get descriptor block. + struct BlockDescriptor* current = (struct BlockDescriptor*)ptr - 1; + + // Mark block as free. + ERROR_ON(current->mFree, "Tried to de-alloc free block!"); + current->mFree = 1; +} diff --git a/lib/src/common.c b/lib/src/common.c new file mode 100644 index 0000000..dd806bf --- /dev/null +++ b/lib/src/common.c @@ -0,0 +1,38 @@ +// Copyright (c) 2021 Johannes Stoelp + +#include <common.h> + +#if !defined(__linux__) || !defined(__x86_64__) +# error "Only supported on linux(x86_64)!" +#endif + +void* memset(void* s, int c, size_t n) { + asm volatile( + "cld" + "\n" + "rep stosb" + : "+D"(s), "+c"(n) + : "a"(c) + : "memory"); + return s; +} + +void* memcpy(void* d, const void* s, size_t n) { + // When `d` points into `[s, s+n[` we would override `s` while copying into `d`. + // |------------|--------| + // s d s+n + // -> We don't support. + // + // When `d` points into `]s-n, s[` it is destructive for `s` but all data + // from `s` are copied into `d`. The user gets what he asks for. + // -> Supported. + ERROR_ON(s <= d && d < (void*)((unsigned char*)s + n), "memcpy: Unsupported overlap!"); + asm volatile( + "cld" + "\n" + "rep movsb" + : "+D"(d), "+S"(s), "+c"(n) + : + : "memory"); + return d; +} diff --git a/lib/src/fmt.c b/lib/src/fmt.c index be1ca3a..24ddd98 100644 --- a/lib/src/fmt.c +++ b/lib/src/fmt.c @@ -11,7 +11,7 @@ static const char* num2dec(char* buf, unsigned long len, unsigned long long num) } while (num > 0 && pbuf != buf) { - char d = (num % 10) + '0'; + char d = (char)(num % 10) + '0'; *(--pbuf) = d; num /= 10; } @@ -28,7 +28,7 @@ static const char* num2hex(char* buf, unsigned long len, unsigned long long num) while (num > 0 && pbuf != buf) { char d = (num & 0xf); - *(--pbuf) = d + (d > 9 ? 'a' - 10 : '0'); + *(--pbuf) = (char)(d + (d > 9 ? 'a' - 10 : '0')); num >>= 4; } return pbuf; @@ -51,7 +51,7 @@ int vfmt(char* buf, unsigned long len, const char* fmt, va_list ap) { put(*s++); \ } - char scratch[16]; + char scratch[32]; int l_cnt = 0; while (*fmt) { @@ -73,7 +73,7 @@ int vfmt(char* buf, unsigned long len, const char* fmt, va_list ap) { val *= -1; put('-'); } - const char* ptr = num2dec(scratch, sizeof(scratch), val); + const char* ptr = num2dec(scratch, sizeof(scratch), (unsigned long)val); puts(ptr); } break; case 'x': { @@ -81,6 +81,10 @@ int vfmt(char* buf, unsigned long len, const char* fmt, va_list ap) { const char* ptr = num2hex(scratch, sizeof(scratch), val); puts(ptr); } break; + case 'c': { + char c = va_arg(ap, int); // By C standard, value passed to varg smaller than `sizeof(int)` will be converted to int. + put(c); + } break; case 's': { const char* ptr = va_arg(ap, const char*); puts(ptr); diff --git a/lib/src/io.c b/lib/src/io.c index b5e0dc5..5194042 100644 --- a/lib/src/io.c +++ b/lib/src/io.c @@ -1,10 +1,9 @@ // Copyright (c) 2020 Johannes Stoelp -#include <io.h> #include <fmt.h> +#include <io.h> #include <syscall.h> - -#include <asm/unistd.h> +#include <syscalls.h> // `pfmt` uses fixed-size buffer on the stack for formating the message // (for simplicity and since we don't impl buffered I/O). @@ -21,14 +20,14 @@ static int vdfmt(int fd, const char* fmt, va_list ap) { int ret = vfmt(buf, sizeof(buf), fmt, ap); if (ret > MAX_PRINTF_LEN - 1) { - syscall3(__NR_write, fd, buf, MAX_PRINTF_LEN - 1); + write(fd, buf, MAX_PRINTF_LEN - 1); static const char warn[] = "\npfmt: Message truncated, max length can be configured by defining MAX_PRINTF_LEN\n"; - syscall3(__NR_write, FD_STDERR, warn, sizeof(warn)); + write(FD_STDERR, warn, sizeof(warn)); return MAX_PRINTF_LEN - 1; } - syscall3(__NR_write, fd, buf, ret); + write(fd, buf, ret); return ret; } diff --git a/lib/src/syscalls.c b/lib/src/syscalls.c new file mode 100644 index 0000000..074fced --- /dev/null +++ b/lib/src/syscalls.c @@ -0,0 +1,62 @@ +// Copyright (c) 2021 Johannes Stoelp + +#include <asm/unistd.h> // __NR_* +#include <syscall.h> +#include <syscalls.h> + +// Storage for `dynld_errno`. +int dynld_errno; + +// Convert return value to errno/ret. +static long syscall_ret(unsigned long ret) { + if (ret > (unsigned long)-4096ul) { + dynld_errno = -ret; + return -1; + } + return ret; +} + +int open(const char* path, int flags) { + long ret = syscall2(__NR_open, path, flags); + return syscall_ret(ret); +} + +int close(int fd) { + long ret = syscall1(__NR_close, fd); + return syscall_ret(ret); +} + +int access(const char* path, int mode) { + long ret = syscall2(__NR_access, path, mode); + return syscall_ret(ret); +} + +ssize_t write(int fd, const void* buf, size_t count) { + long ret = syscall3(__NR_write, fd, buf, count); + return syscall_ret(ret); +} + +ssize_t read(int fd, void* buf, size_t count) { + long ret = syscall3(__NR_read, fd, buf, count); + return syscall_ret(ret); +} + +ssize_t pread(int fd, void* buf, size_t count, off_t offset) { + long ret = syscall4(__NR_read, fd, buf, count, offset); + return syscall_ret(ret); +} + +void* mmap(void* addr, size_t length, int prot, int flags, int fd, off_t offset) { + long ret = syscall6(__NR_mmap, addr, length, prot, flags, fd, offset); + return (void*)syscall_ret(ret); +} + +int munmap(void* addr, size_t length) { + long ret = syscall2(__NR_munmap, addr, length); + return syscall_ret(ret); +} + +void _exit(int status) { + syscall1(__NR_exit, status); + __builtin_unreachable(); +} diff --git a/test/checker.cc b/test/checker.cc index 2707929..3d425bf 100644 --- a/test/checker.cc +++ b/test/checker.cc @@ -3,7 +3,8 @@ #include "test_helper.h" extern "C" { - #include <fmt.h> +#include <common.h> +#include <fmt.h> } void check_dec() { @@ -15,6 +16,15 @@ void check_dec() { ASSERT_EQ('\0', have[len]); } +void check_dec_long() { + char have[32]; + int len = fmt(have, sizeof(have), "%ld %d", 8589934592 /* 2^33 */, 8589934592 /* 2^33 */); + + ASSERT_EQ("8589934592 0", have); + ASSERT_EQ(12, len); + ASSERT_EQ('\0', have[len]); +} + void check_hex() { char have[16]; int len = fmt(have, sizeof(have), "%x %x", 0xdeadbeef, 0xcafe); @@ -24,6 +34,24 @@ void check_hex() { ASSERT_EQ('\0', have[len]); } +void check_hex_long() { + char have[32]; + int len = fmt(have, sizeof(have), "%lx %x", 0x1111222233334444, 0x1111222233334444); + + ASSERT_EQ("1111222233334444 33334444", have); + ASSERT_EQ(25, len); + ASSERT_EQ('\0', have[len]); +} + +void check_char() { + char have[4]; + int len = fmt(have, sizeof(have), "%c%c%c", 'A', 'a', '\x01' /* non printable */); + + ASSERT_EQ("Aa\x01", have); + ASSERT_EQ(3, len); + ASSERT_EQ('\0', have[len]); +} + void check_ptr() { char have[16]; int len = fmt(have, sizeof(have), "%p %p", (void*)0xabcd, (void*)0x0); @@ -57,13 +85,39 @@ void check_exceed_len() { ASSERT_EQ('\0', have[7]); } +void check_memset() { + unsigned char d[7] = {0}; + void* ret = memset(d, '\x42', sizeof(d)); + + ASSERT_EQ(ret, d); + for (unsigned i = 0; i < sizeof(d); ++i) { + ASSERT_EQ(0x42, d[i]); + } +} + +void check_memcpy() { + unsigned char s[5] = {5, 4, 3, 2, 1}; + unsigned char d[5] = {0}; + void* ret = memcpy(d, s, sizeof(d)); + + ASSERT_EQ(ret, d); + for (unsigned i = 0; i < sizeof(d); ++i) { + ASSERT_EQ(5-i, d[i]); + } +} + int main() { TEST_INIT; TEST_ADD(check_dec); + TEST_ADD(check_dec_long); TEST_ADD(check_hex); + TEST_ADD(check_hex_long); + TEST_ADD(check_char); TEST_ADD(check_ptr); TEST_ADD(check_null); TEST_ADD(check_exact_len); TEST_ADD(check_exceed_len); + TEST_ADD(check_memset); + TEST_ADD(check_memcpy); return TEST_RUN; } diff --git a/test/test_helper.h b/test/test_helper.h index b78d9a6..f1356a4 100644 --- a/test/test_helper.h +++ b/test/test_helper.h @@ -28,11 +28,6 @@ void ASSERT_EQ(T1 expected, T2 have) { } } -template<typename T1, typename T2> -void ASSERT_EQ(T1* expected, T2* have) { - ASSERT_EQ(*expected, *have); -} - // Char string based ASSERT_* helper. template<> |