aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.clang-format2
-rw-r--r--04_dynld_nostd/Makefile63
-rw-r--r--04_dynld_nostd/dynld.S27
-rw-r--r--04_dynld_nostd/dynld.c591
-rw-r--r--04_dynld_nostd/libgreet.c29
-rw-r--r--04_dynld_nostd/main.c19
-rw-r--r--lib/Makefile7
-rw-r--r--lib/include/alloc.h6
-rw-r--r--lib/include/auxv.h2
-rw-r--r--lib/include/common.h21
-rw-r--r--lib/include/elf.h156
-rw-r--r--lib/include/syscall.h58
-rw-r--r--lib/include/syscalls.h40
-rw-r--r--lib/src/alloc.c87
-rw-r--r--lib/src/common.c38
-rw-r--r--lib/src/fmt.c12
-rw-r--r--lib/src/io.c11
-rw-r--r--lib/src/syscalls.c62
-rw-r--r--test/checker.cc56
-rw-r--r--test/test_helper.h5
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<>