├── .gitignore ├── lib ├── include │ ├── alloc.h │ ├── io.h │ ├── fmt.h │ ├── common.h │ ├── syscalls.h │ ├── auxv.h │ ├── syscall.h │ └── elf.h ├── Makefile └── src │ ├── io.c │ ├── syscalls.c │ ├── common.c │ ├── alloc.c │ └── fmt.c ├── 01_dynamic_linking ├── main.c ├── libgreet.c ├── Makefile └── README.md ├── 03_hello_dynld ├── main.c ├── dynld.S ├── Makefile ├── dynld.c └── README.md ├── 02_process_init ├── Makefile ├── entry.S ├── entry.c └── README.md ├── test ├── Makefile ├── test_helper.h └── checker.cc ├── 04_dynld_nostd ├── main.c ├── dynld.S ├── libgreet.c ├── Makefile ├── dynld.c └── README.md ├── LICENSE ├── README.md └── .clang-format /.gitignore: -------------------------------------------------------------------------------- 1 | *.so 2 | *.a 3 | *.o 4 | main 5 | entry 6 | checker 7 | -------------------------------------------------------------------------------- /lib/include/alloc.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // Copyright (c) 2021, Johannes Stoelp 4 | 5 | #pragma once 6 | 7 | void* alloc(unsigned size); 8 | void dealloc(void* ptr); 9 | -------------------------------------------------------------------------------- /lib/include/io.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // Copyright (c) 2020, Johannes Stoelp 4 | 5 | #pragma once 6 | 7 | int pfmt(const char* fmt, ...); 8 | int efmt(const char* fmt, ...); 9 | -------------------------------------------------------------------------------- /01_dynamic_linking/main.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // Copyright (c) 2020, Johannes Stoelp 4 | 5 | extern void greet(); 6 | 7 | int main() { 8 | greet(); 9 | return 0; 10 | } 11 | -------------------------------------------------------------------------------- /01_dynamic_linking/libgreet.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // Copyright (c) 2020, Johannes Stoelp 4 | 5 | #include 6 | 7 | void greet() { 8 | puts("Hello from libgreet.so!"); 9 | } 10 | -------------------------------------------------------------------------------- /lib/include/fmt.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // Copyright (c) 2020, Johannes Stoelp 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | int vfmt(char* buf, unsigned long len, const char* fmt, va_list ap); 10 | int fmt(char* buf, unsigned long len, const char* fmt, ...); 11 | -------------------------------------------------------------------------------- /03_hello_dynld/main.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // Copyright (c) 2021, Johannes Stoelp 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | void _start() { 11 | pfmt("Running %s @ %s\n", __FUNCTION__, __FILE__); 12 | syscall1(__NR_exit, 0); 13 | } 14 | -------------------------------------------------------------------------------- /01_dynamic_linking/Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: MIT 2 | # 3 | # Copyright (c) 2020, Johannes Stoelp 4 | 5 | inspect: build 6 | readelf -W --sections main 7 | readelf -W --dynamic main 8 | readelf -W --program-headers main 9 | readelf -W --string-dump .interp main 10 | gdb -q --batch -ex 'starti' -ex 'bt' ./main 11 | 12 | build: libgreet.c main.c 13 | gcc -o libgreet.so libgreet.c -shared -fPIC 14 | gcc -o main main.c -lgreet -L. -Wl,--rpath=$(CURDIR) 15 | 16 | clean: 17 | rm -f libgreet.so main 18 | -------------------------------------------------------------------------------- /02_process_init/Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: MIT 2 | # 3 | # Copyright (c) 2020, Johannes Stoelp 4 | 5 | show: entry 6 | ./entry 1 2 3 4 7 | 8 | entry: entry.S entry.c ../lib/libcommon.a 9 | gcc -o $@ \ 10 | -g -O0 \ 11 | -Wall -Wextra \ 12 | -I../lib/include \ 13 | -nostdlib \ 14 | -static \ 15 | -fno-stack-protector \ 16 | $^ 17 | 18 | ../lib/libcommon.a: 19 | make -C ../lib 20 | 21 | clean: 22 | rm -f entry 23 | -------------------------------------------------------------------------------- /test/Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: MIT 2 | # 3 | # Copyright (c) 2020, Johannes Stoelp 4 | 5 | check: build 6 | ./checker 7 | 8 | build: checker.cc test_helper.h ../lib/libcommon.a 9 | g++ -o checker \ 10 | -g -O2 \ 11 | -I ../lib/include \ 12 | -Wall -Wextra \ 13 | -fsanitize=address \ 14 | -fsanitize=pointer-compare \ 15 | -fsanitize=pointer-subtract \ 16 | -fsanitize=undefined \ 17 | $(filter-out %.h, $^) 18 | 19 | ../lib/libcommon.a: 20 | make -C ../lib 21 | 22 | clean: 23 | rm -f checker 24 | make -C ../lib clean 25 | -------------------------------------------------------------------------------- /04_dynld_nostd/main.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // Copyright (c) 2020, Johannes Stoelp 4 | 5 | #include 6 | 7 | // API of `libgreet.so`. 8 | extern const char* get_greet(); 9 | extern const char* get_greet2(); 10 | extern int gCalled; 11 | 12 | void _start() { 13 | pfmt("Running _start() @ %s\n", __FILE__); 14 | 15 | // Call function from libgreet.so -> generates PLT relocations (R_X86_64_JUMP_SLOT). 16 | pfmt("get_greet() -> %s\n", get_greet()); 17 | pfmt("get_greet2() -> %s\n", get_greet2()); 18 | 19 | // Reference global variable from libgreet.so -> generates RELA relocation (R_X86_64_COPY). 20 | pfmt("libgreet.so called %d times\n", gCalled); 21 | } 22 | -------------------------------------------------------------------------------- /lib/Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: MIT 2 | # 3 | # Copyright (c) 2020, Johannes Stoelp 4 | 5 | HDR+=include/alloc.h 6 | HDR+=include/auxv.h 7 | HDR+=include/elf.h 8 | HDR+=include/fmt.h 9 | HDR+=include/io.h 10 | HDR+=include/syscall.h 11 | HDR+=include/syscalls.h 12 | 13 | DEP+=src/alloc.o 14 | DEP+=src/common.o 15 | DEP+=src/fmt.o 16 | DEP+=src/io.o 17 | DEP+=src/syscalls.o 18 | 19 | libcommon.a: $(HDR) $(DEP) 20 | ar -crs $@ $(filter %.o, $^) 21 | 22 | src/%.o: src/%.c 23 | gcc -c -o $@ \ 24 | -g -O0 \ 25 | -Wall -Wextra \ 26 | -I$(CURDIR)/include \ 27 | -nostdlib \ 28 | -fno-stack-protector \ 29 | $< 30 | 31 | clean: 32 | rm -f $(DEP) 33 | rm -f libcommon.a 34 | -------------------------------------------------------------------------------- /lib/include/common.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // Copyright (c) 2020, Johannes Stoelp 4 | 5 | #pragma once 6 | 7 | #include "io.h" 8 | #include "syscalls.h" 9 | 10 | #define ERROR_ON(cond, fmt, ...) \ 11 | do { \ 12 | if ((cond)) { \ 13 | efmt("%s:%d " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__); \ 14 | _exit(1); \ 15 | } \ 16 | } while (0) 17 | 18 | 19 | void* memset(void* s, int c, size_t n); 20 | void* memcpy(void* d, const void* s, size_t n); 21 | 22 | -------------------------------------------------------------------------------- /04_dynld_nostd/dynld.S: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // Copyright (c) 2020, Johannes Stoelp 4 | 5 | #include 6 | 7 | .intel_syntax noprefix 8 | 9 | .section .text, "ax", @progbits 10 | .global dl_start 11 | dl_start: 12 | // $rsp is guaranteed to be 16-byte aligned. 13 | 14 | // Clear $rbp as specified by the SysV AMD64 ABI. 15 | xor rbp, rbp 16 | 17 | // Load pointer to process context prepared by execve(2) syscall as 18 | // specified in the SysV AMD64 ABI. 19 | // Save pointer in $rdi which is the arg0 (int/ptr) register. 20 | lea rdi, [rsp] 21 | 22 | // Stack frames must be 16-byte aligned before control is transfered to the 23 | // callees entry point. 24 | call dl_entry 25 | 26 | // Call exit(1) syscall to indicate error, dl_entry should not return. 27 | mov rdi, 1 28 | mov rax, __NR_exit 29 | syscall 30 | -------------------------------------------------------------------------------- /02_process_init/entry.S: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // Copyright (c) 2020, Johannes Stoelp 4 | 5 | #include 6 | 7 | #if !defined(__linux__) || !defined(__x86_64__) 8 | # error "Only supported in linux(x86_64)!" 9 | #endif 10 | 11 | .intel_syntax noprefix 12 | 13 | .section .text, "ax", @progbits 14 | .global _start 15 | _start: 16 | // $rsp is guaranteed to be 16-byte aligned. 17 | 18 | // Clear $rbp as specified by the SysV AMD64 ABI. 19 | xor rbp, rbp 20 | 21 | // Load pointer to process context prepared by execve(2) syscall as 22 | // specified in the SysV AMD64 ABI. 23 | // Save pointer in $rdi which is the arg0 (int/ptr) register. 24 | lea rdi, [rsp] 25 | 26 | // Stack frames must be 16-byte aligned before control is transfered to the 27 | // callees entry point. 28 | call entry 29 | 30 | // Call exit(0) syscall. 31 | mov rdi, 0 32 | mov rax, __NR_exit 33 | syscall 34 | -------------------------------------------------------------------------------- /03_hello_dynld/dynld.S: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // Copyright (c) 2021, Johannes Stoelp 4 | 5 | #if !defined(__linux__) || !defined(__x86_64__) 6 | # error "Only supported in linux(x86_64)!" 7 | #endif 8 | 9 | #include 10 | 11 | .intel_syntax noprefix 12 | 13 | .section .text, "ax", @progbits 14 | .global dl_start 15 | dl_start: 16 | // $rsp is guaranteed to be 16-byte aligned. 17 | 18 | // Clear $rbp as specified by the SysV AMD64 ABI. 19 | xor rbp, rbp 20 | 21 | // Load pointer to process context prepared by execve(2) syscall as 22 | // specified in the SysV AMD64 ABI. 23 | // Save pointer in $rdi which is the arg0 (int/ptr) register. 24 | lea rdi, [rsp] 25 | 26 | // Stack frames must be 16-byte aligned before control is transfered to the 27 | // callees entry point. 28 | call dl_entry 29 | 30 | // Call exit(1) syscall to indicate error, dl_entry should not return. 31 | mov rdi, 1 32 | mov rax, __NR_exit 33 | syscall 34 | -------------------------------------------------------------------------------- /04_dynld_nostd/libgreet.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // Copyright (c) 2020, Johannes Stoelp 4 | 5 | #include 6 | 7 | int gCalled = 0; 8 | 9 | const char* get_greet() { 10 | // Reference global variable -> generates RELA relocation (R_X86_64_GLOB_DAT). 11 | ++gCalled; 12 | return "Hello from libgreet.so!"; 13 | } 14 | 15 | const char* get_greet2() { 16 | // Reference global variable -> generates RELA relocation (R_X86_64_GLOB_DAT). 17 | ++gCalled; 18 | return "Hello 2 from libgreet.so!"; 19 | } 20 | 21 | // Definition of `static` function which is referenced from the `INIT` dynamic 22 | // section entry -> generates R_X86_64_RELATIVE relocation. 23 | __attribute__((constructor)) static void libinit() { 24 | pfmt("libgreet.so: libinit\n"); 25 | } 26 | 27 | // Definition of `non static` function which is referenced from the `FINI` 28 | // dynamic section entry -> generates R_X86_64_64 relocation. 29 | __attribute__((destructor)) void libfini() { 30 | pfmt("libgreet.so: libfini\n"); 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Johannes Stoelp 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dynld 2 | 3 | This repository contains studies about `process initialization` and `dynamic 4 | linking` on Linux(x86_64). It is mainly used for personal studies, but may be 5 | useful to some random internet user. 6 | 7 | All studies and discussions assume the following environment: 8 | 9 | - Arch: `x86_64` 10 | - OS: `Linux Kernel` 11 | - System ABI: `SystemV x86_64 ABI` 12 | - BinFMT: `ELF` 13 | 14 | The studies are structured as follows: 15 | 16 | 1. [Ch 01 dynamic linking](./01_dynamic_linking): 17 | Brief introduction to dynamic linking. 18 | 1. [Ch 02 process initialization](./02_process_init): 19 | Building a `no-std` executable and exploring the initial process state. 20 | 1. [Ch 03 dynamic linker skeleton](./03_hello_dynld): 21 | Building a skeleton for the dynamic linker which can run a `statically 22 | linked no-std` executable. 23 | 1. [Ch 04 dynld no-std](./04_dynld_nostd): 24 | Building a dynamic linker `dynld` which can initialize the execution 25 | environment for a `no-std` executable with a shared library dependency. 26 | 27 | ## License 28 | This project is licensed under the [MIT](LICENSE) license. 29 | -------------------------------------------------------------------------------- /03_hello_dynld/Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: MIT 2 | # 3 | # Copyright (c) 2021, Johannes Stoelp 4 | 5 | COMMON_CFLAGS := -g -O0 -Wall -Wextra \ 6 | -I../lib/include -nostdlib 7 | 8 | run: main 9 | ./$< 10 | 11 | main: dynld.so main.c ../lib/libcommon.a 12 | gcc -o $@ \ 13 | $(COMMON_CFLAGS) \ 14 | -Wl,--dynamic-linker=$(CURDIR)/dynld.so \ 15 | $(filter %.c %.a, $^) 16 | 17 | readelf -W --dynamic $@ 18 | readelf -W --string-dump .interp $@ 19 | readelf -W --program-headers $@ 20 | 21 | dynld.so: dynld.S dynld.c ../lib/libcommon.a 22 | gcc -o $@ \ 23 | $(COMMON_CFLAGS) \ 24 | -fPIC \ 25 | -fvisibility=hidden \ 26 | -Wl,--entry=dl_start \ 27 | -Wl,--no-undefined \ 28 | -fno-stack-protector \ 29 | $^ 30 | 31 | @if ! readelf -r $@ | grep 'There are no relocations in this file' >& /dev/null; then \ 32 | echo "ERROR: $@ contains relocations while we don't support relocations in $@!"; \ 33 | exit 1; \ 34 | fi 35 | 36 | ../lib/libcommon.a: 37 | make -C ../lib 38 | 39 | clean: 40 | rm -f main 41 | rm -f dynld.so 42 | -------------------------------------------------------------------------------- /lib/include/syscalls.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // Copyright (c) 2021, Johannes Stoelp 4 | 5 | #pragma once 6 | 7 | #include // size_t 8 | #include // ssize_t, off_t, ... 9 | 10 | extern int dynld_errno; 11 | 12 | // Syscall definitions taken from corresponding man pages, eg 13 | // open(2) 14 | // read(2) 15 | // ... 16 | 17 | #define O_RDONLY 00 18 | int open(const char* path, int flags); 19 | int close(int fd); 20 | 21 | #define F_OK 0 22 | #define R_OK 4 23 | int access(const char* path, int mode); 24 | 25 | ssize_t write(int fd, const void* buf, size_t count); 26 | ssize_t read(int fd, void* buf, size_t count); 27 | ssize_t pread(int fd, void* buf, size_t count, off_t offset); 28 | 29 | // mmap - prot: 30 | #define PROT_NONE 0x0 31 | #define PROT_READ 0x1 32 | #define PROT_WRITE 0x2 33 | #define PROT_EXEC 0x4 34 | // mmap - flags: 35 | #define MAP_PRIVATE 0x2 36 | #define MAP_ANONYMOUS 0x20 37 | #define MAP_FIXED 0x10 38 | // mmap - ret: 39 | #define MAP_FAILED ((void*)-1) 40 | void* mmap(void* addr, size_t length, int prot, int flags, int fd, off_t offset); 41 | int munmap(void* addr, size_t length); 42 | 43 | void _exit(int status); 44 | -------------------------------------------------------------------------------- /lib/src/io.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // Copyright (c) 2020, Johannes Stoelp 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | // `pfmt` uses fixed-size buffer on the stack for formating the message 11 | // (for simplicity and since we don't impl buffered I/O). 12 | // 13 | // NOTE: This allows to specify a large buffer on the stack, but for 14 | // the purpose of this study that's fine, we are cautious. 15 | #define MAX_PRINTF_LEN 128 16 | 17 | #define FD_STDOUT 1 18 | #define FD_STDERR 2 19 | 20 | static int vdfmt(int fd, const char* fmt, va_list ap) { 21 | char buf[MAX_PRINTF_LEN]; 22 | int ret = vfmt(buf, sizeof(buf), fmt, ap); 23 | 24 | if (ret > MAX_PRINTF_LEN - 1) { 25 | write(fd, buf, MAX_PRINTF_LEN - 1); 26 | 27 | static const char warn[] = "\npfmt: Message truncated, max length can be configured by defining MAX_PRINTF_LEN\n"; 28 | write(FD_STDERR, warn, sizeof(warn)); 29 | return MAX_PRINTF_LEN - 1; 30 | } 31 | 32 | write(fd, buf, ret); 33 | return ret; 34 | } 35 | 36 | int pfmt(const char* fmt, ...) { 37 | va_list ap; 38 | va_start(ap, fmt); 39 | int ret = vdfmt(FD_STDOUT, fmt, ap); 40 | va_end(ap); 41 | return ret; 42 | } 43 | 44 | int efmt(const char* fmt, ...) { 45 | va_list ap; 46 | va_start(ap, fmt); 47 | int ret = vdfmt(FD_STDERR, fmt, ap); 48 | va_end(ap); 49 | return ret; 50 | } 51 | -------------------------------------------------------------------------------- /lib/include/auxv.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // Copyright (c) 2020, Johannes Stoelp 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | /// ---------------- 10 | /// Auxiliary Vector 11 | /// ---------------- 12 | 13 | // NOTE: [x86-64] Either AT_EXECFD or AT_PHDR must be supplied by the Kernel. 14 | 15 | #define AT_NULL 0 /* [ignored] Mark end of auxiliary vetcor */ 16 | #define AT_IGNORE 1 /* [ignored] */ 17 | #define AT_EXECFD 2 /* [val] File descriptor of user program (in case Linux Kernel didn't mapped) */ 18 | #define AT_PHDR 3 /* [ptr] Address of Phdr of use program (in case Kernel mapped user program) */ 19 | #define AT_PHENT 4 /* [val] Size in bytes of one Phdr entry */ 20 | #define AT_PHNUM 5 /* [val] Number of Phdr entries */ 21 | #define AT_PAGESZ 6 /* [val] System page size */ 22 | #define AT_BASE 7 /* [ptr] `base address` interpreter was loaded to */ 23 | #define AT_FLAGS 8 /* [val] */ 24 | #define AT_ENTRY 9 /* [ptr] Entry point of user program */ 25 | #define AT_NOTELF 10 /* [val] >0 if not an ELF file */ 26 | #define AT_UID 11 /* [val] Real user id of process */ 27 | #define AT_EUID 12 /* [val] Effective user id of process */ 28 | #define AT_GID 13 /* [val] Real group id of process */ 29 | #define AT_EGID 14 /* [val] Effective user id of process */ 30 | #define AT_MAX_CNT 15 31 | 32 | typedef struct { 33 | uint64_t tag; 34 | union { 35 | uint64_t val; 36 | void* ptr; 37 | }; 38 | } Auxv64Entry; 39 | -------------------------------------------------------------------------------- /lib/src/syscalls.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // Copyright (c) 2021, Johannes Stoelp 4 | 5 | #include // __NR_* 6 | #include 7 | #include 8 | 9 | // Storage for `dynld_errno`. 10 | int dynld_errno; 11 | 12 | // Convert return value to errno/ret. 13 | static long syscall_ret(unsigned long ret) { 14 | if (ret > (unsigned long)-4096ul) { 15 | dynld_errno = -ret; 16 | return -1; 17 | } 18 | return ret; 19 | } 20 | 21 | int open(const char* path, int flags) { 22 | long ret = syscall2(__NR_open, path, flags); 23 | return syscall_ret(ret); 24 | } 25 | 26 | int close(int fd) { 27 | long ret = syscall1(__NR_close, fd); 28 | return syscall_ret(ret); 29 | } 30 | 31 | int access(const char* path, int mode) { 32 | long ret = syscall2(__NR_access, path, mode); 33 | return syscall_ret(ret); 34 | } 35 | 36 | ssize_t write(int fd, const void* buf, size_t count) { 37 | long ret = syscall3(__NR_write, fd, buf, count); 38 | return syscall_ret(ret); 39 | } 40 | 41 | ssize_t read(int fd, void* buf, size_t count) { 42 | long ret = syscall3(__NR_read, fd, buf, count); 43 | return syscall_ret(ret); 44 | } 45 | 46 | ssize_t pread(int fd, void* buf, size_t count, off_t offset) { 47 | long ret = syscall4(__NR_read, fd, buf, count, offset); 48 | return syscall_ret(ret); 49 | } 50 | 51 | void* mmap(void* addr, size_t length, int prot, int flags, int fd, off_t offset) { 52 | long ret = syscall6(__NR_mmap, addr, length, prot, flags, fd, offset); 53 | return (void*)syscall_ret(ret); 54 | } 55 | 56 | int munmap(void* addr, size_t length) { 57 | long ret = syscall2(__NR_munmap, addr, length); 58 | return syscall_ret(ret); 59 | } 60 | 61 | void _exit(int status) { 62 | syscall1(__NR_exit, status); 63 | __builtin_unreachable(); 64 | } 65 | -------------------------------------------------------------------------------- /04_dynld_nostd/Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: MIT 2 | # 3 | # Copyright (c) 2020, Johannes Stoelp 4 | 5 | COMMON_CFLAGS := -g -O0 -Wall -Wextra \ 6 | -I../lib/include \ 7 | -nostartfiles -nodefaultlibs -fno-stack-protector 8 | 9 | run: main 10 | ./$< 11 | 12 | # Build the example user program. 13 | # 14 | # We explicitly set the dynamic linker to `dynld.so` and use the ELF hash table 15 | # (DT_HASH), as we didn't implement support for the GNU hash table in our 16 | # dynamic linker. 17 | main: dynld.so libgreet.so main.c ../lib/libcommon.a 18 | gcc -o $@ \ 19 | $(COMMON_CFLAGS) \ 20 | -L$(CURDIR) -lgreet \ 21 | -Wl,--dynamic-linker=$(CURDIR)/dynld.so \ 22 | -Wl,--hash-style=sysv \ 23 | -no-pie \ 24 | $(filter %.c %.a, $^) 25 | 26 | #readelf -W --dynamic $@ 27 | #readelf -W --program-headers $@ 28 | #objdump --disassemble -j .plt -M intel $@ 29 | #objdump --disassemble=_start -M intel $@ 30 | 31 | # Build the example shared library. 32 | # 33 | # We explicitly use the ELF hash table (DT_HASH), as we didn't implement 34 | # support for the GNU hash table in our dynamic linker. 35 | libgreet.so: libgreet.c 36 | gcc -o $@ \ 37 | $(COMMON_CFLAGS) \ 38 | -fPIC -shared \ 39 | -Wl,--hash-style=sysv \ 40 | $^ 41 | 42 | # Build the dynamic linker. 43 | # 44 | # We assert that the dynamic linker doesn't contain any relocations as we 45 | # didn't implement support to resolve its own relocations. 46 | dynld.so: dynld.S dynld.c ../lib/libcommon.a 47 | gcc -o $@ \ 48 | $(COMMON_CFLAGS) \ 49 | -fPIC -static-pie \ 50 | -fvisibility=hidden \ 51 | -Wl,--entry=dl_start \ 52 | -Wl,--no-undefined \ 53 | $^ 54 | 55 | @if ! readelf -r $@ | grep 'There are no relocations in this file' >& /dev/null; then \ 56 | echo "ERROR: $@ contains relocations while we don't support relocations in $@!"; \ 57 | exit 1; \ 58 | fi 59 | 60 | ../lib/libcommon.a: 61 | make -C ../lib 62 | 63 | clean: 64 | rm -f main libgreet.so 65 | rm -f dynld.so 66 | make -C ../lib clean 67 | -------------------------------------------------------------------------------- /02_process_init/entry.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // Copyright (c) 2020, Johannes Stoelp 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #if !defined(__linux__) || !defined(__x86_64__) 10 | # error "Only supported in linux(x86_64)!" 11 | #endif 12 | 13 | void entry(const uint64_t* prctx) { 14 | // Interpret data on the stack passed by the OS kernel as specified in the 15 | // x86_64 SysV ABI. 16 | uint64_t argc = *prctx; 17 | const char** argv = (const char**)(prctx + 1); 18 | const char** envv = (const char**)(argv + argc + 1); 19 | 20 | // Count the number of environment variables in the `ENVP` segment. 21 | int envc = 0; 22 | for (const char** env = envv; *env; ++env) { 23 | ++envc; 24 | } 25 | 26 | uint64_t auxv[AT_MAX_CNT]; 27 | for (unsigned i = 0; i < AT_MAX_CNT; ++i) { 28 | auxv[i] = 0; 29 | } 30 | 31 | // Read the `AUXV` auxiliary vector segment. 32 | const Auxv64Entry* auxvp = (const Auxv64Entry*)(envv + envc + 1); 33 | for (; auxvp->tag != AT_NULL; ++auxvp) { 34 | if (auxvp->tag < AT_MAX_CNT) { 35 | auxv[auxvp->tag] = auxvp->val; 36 | } 37 | } 38 | 39 | // Print the data provided by the Linux Kernel on the stack. 40 | 41 | pfmt("Got %d arg(s)\n", argc); 42 | for (const char** arg = argv; *arg; ++arg) { 43 | pfmt("\targ = %s\n", *arg); 44 | } 45 | 46 | const int max_env = 10; 47 | pfmt("Print first %d env var(s)\n", max_env - 1); 48 | for (const char** env = envv; *env && (env - envv < max_env); ++env) { 49 | pfmt("\tenv = %s\n", *env); 50 | } 51 | 52 | pfmt("Print auxiliary vector\n"); 53 | pfmt("\tAT_EXECFD: %ld\n", auxv[AT_EXECFD]); 54 | pfmt("\tAT_PHDR : %p\n", auxv[AT_PHDR]); 55 | pfmt("\tAT_PHENT : %ld\n", auxv[AT_PHENT]); 56 | pfmt("\tAT_PHNUM : %ld\n", auxv[AT_PHNUM]); 57 | pfmt("\tAT_PAGESZ: %ld\n", auxv[AT_PAGESZ]); 58 | pfmt("\tAT_BASE : %lx\n", auxv[AT_BASE]); 59 | pfmt("\tAT_FLAGS : %ld\n", auxv[AT_FLAGS]); 60 | pfmt("\tAT_ENTRY : %p\n", auxv[AT_ENTRY]); 61 | pfmt("\tAT_NOTELF: %lx\n", auxv[AT_NOTELF]); 62 | pfmt("\tAT_UID : %ld\n", auxv[AT_UID]); 63 | pfmt("\tAT_EUID : %ld\n", auxv[AT_EUID]); 64 | pfmt("\tAT_GID : %ld\n", auxv[AT_GID]); 65 | pfmt("\tAT_EGID : %ld\n", auxv[AT_EGID]); 66 | } 67 | -------------------------------------------------------------------------------- /lib/src/common.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // Copyright (c) 2021, Johannes Stoelp 4 | 5 | #include 6 | 7 | #if !defined(__linux__) || !defined(__x86_64__) 8 | # error "Only supported on linux(x86_64)!" 9 | #endif 10 | 11 | void* memset(void* s, int c, size_t n) { 12 | asm volatile( 13 | "cld" 14 | "\n" 15 | "rep stosb" 16 | : "+D"(s), "+c"(n) 17 | : "a"(c) 18 | : "memory"); 19 | return s; 20 | } 21 | 22 | void* memcpy(void* d, const void* s, size_t n) { 23 | // Cases to distinguish resulting from s and d pointers. 24 | // 25 | // Case 1 - same 26 | // |------------| 27 | // s s+n 28 | // d d+n 29 | // 30 | // -> Nothing to copy. 31 | // 32 | // Case 2 - disjunct 33 | // |------------| |------------| 34 | // s s+n d d+n 35 | // 36 | // -> Nothing to worry, just copy the bytes from s to d. 37 | // 38 | // Case 3 - head overlap 39 | // |------------| 40 | // s s+n 41 | // |------------| 42 | // d d+n 43 | // 44 | // -> Destructive copy for s but all bytes get properly copied from s to d. 45 | // The user gets what he/she asked for. 46 | // 47 | // Case 4 - tail overlap 48 | // |------------| 49 | // s s+n 50 | // |------------| 51 | // d d+n 52 | // 53 | // -> With a simple forward copy we would override the tail of s while 54 | // copying into the head of d. This is destructive for s but we would 55 | // also copy "wrong" bytes into d when we copy the tail of s (as it is 56 | // already overwritten). 57 | // This copy could be done properly by copying backwards (on x86 we 58 | // could use the direction flag for string operations). 59 | // -> We don't support this here as it is not needed any of the examples. 60 | 61 | // Case 4. 62 | ERROR_ON(s <= d && d < (void*)((unsigned char*)s + n), "memcpy: Unsupported overlap!"); 63 | 64 | // Case 1. 65 | if (d == s) { 66 | return d; 67 | } 68 | 69 | // Case 2/3. 70 | asm volatile( 71 | "cld" 72 | "\n" 73 | "rep movsb" 74 | : "+D"(d), "+S"(s), "+c"(n) 75 | : 76 | : "memory"); 77 | return d; 78 | } 79 | -------------------------------------------------------------------------------- /test/test_helper.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // Copyright (c) 2020, Johannes Stoelp 4 | 5 | #pragma once 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | // Extremely trivial helper, just to get some tests out. 13 | 14 | struct TestFailed : std::exception {}; 15 | 16 | // Value based ASSERT_* helper. 17 | // 18 | // Requirements: 19 | // T1: comparable + printable (stream operator) 20 | // T2: comparable + printable (stream operator) 21 | 22 | template 23 | void ASSERT_EQ(T1 expected, T2 have) { 24 | if (expected != have) { 25 | std::cerr << "ASSERT_EQ failed:\n" 26 | << " expected: " << expected << '\n' 27 | << " have : " << have << '\n' 28 | << std::flush; 29 | throw TestFailed{}; 30 | } 31 | } 32 | 33 | // Char string based ASSERT_* helper. 34 | 35 | template<> 36 | void ASSERT_EQ(const char* expected, const char* have) { 37 | if (std::strcmp(expected, have) != 0) { 38 | std::cerr << "ASSERT_EQ failed:\n" 39 | << " expected: " << expected << '\n' 40 | << " have : " << have << '\n' 41 | << std::flush; 42 | throw TestFailed{}; 43 | } 44 | } 45 | 46 | template<> 47 | void ASSERT_EQ(const char* expected, char* have) { 48 | ASSERT_EQ(expected, static_cast(have)); 49 | } 50 | 51 | template<> 52 | void ASSERT_EQ(char* expected, const char* have) { 53 | ASSERT_EQ(static_cast(expected), have); 54 | } 55 | 56 | template<> 57 | void ASSERT_EQ(char* expected, char* have) { 58 | ASSERT_EQ(static_cast(expected), static_cast(have)); 59 | } 60 | 61 | // Simple test runner abstraction. 62 | 63 | struct Runner { 64 | void addTest(const char* name, std::function fn) { mTests.push_back(Test{name, fn}); } 65 | 66 | int runTests() { 67 | unsigned fail_cnt = 0; 68 | for (auto test : mTests) { 69 | try { 70 | test.fn(); 71 | std::cerr << "SUCCESS " << test.name << std::endl; 72 | } catch (TestFailed&) { 73 | ++fail_cnt; 74 | std::cerr << "FAIL " << test.name << std::endl; 75 | } catch (...) { 76 | ++fail_cnt; 77 | std::cerr << "FAIL " << test.name << "(caught unspecified exception)" << std::endl; 78 | } 79 | } 80 | return fail_cnt; 81 | } 82 | 83 | private: 84 | struct Test { 85 | const char* name; 86 | std::function fn; 87 | }; 88 | 89 | std::vector mTests{}; 90 | }; 91 | 92 | #define TEST_INIT Runner r; 93 | #define TEST_ADD(fn) r.addTest(#fn, fn); 94 | #define TEST_RUN r.runTests(); 95 | -------------------------------------------------------------------------------- /03_hello_dynld/dynld.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // Copyright (c) 2021, Johannes Stoelp 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | #if !defined(__linux__) || !defined(__x86_64__) 14 | # error "Only supported in linux(x86_64)!" 15 | #endif 16 | 17 | void dl_entry(const uint64_t* prctx) { 18 | // Interpret data on the stack passed by the OS kernel as specified in the 19 | // x86_64 SysV ABI. 20 | uint64_t argc = *prctx; 21 | const char** argv = (const char**)(prctx + 1); 22 | const char** envv = (const char**)(argv + argc + 1); 23 | 24 | // Count the number of environment variables in the `ENVP` segment. 25 | int envc = 0; 26 | for (const char** env = envv; *env; ++env) { 27 | ++envc; 28 | } 29 | 30 | uint64_t auxv[AT_MAX_CNT]; 31 | for (unsigned i = 0; i < AT_MAX_CNT; ++i) { 32 | auxv[i] = 0; 33 | } 34 | 35 | // Read the `AUXV` auxiliary vector segment. 36 | const Auxv64Entry* auxvp = (const Auxv64Entry*)(envv + envc + 1); 37 | for (; auxvp->tag != AT_NULL; ++auxvp) { 38 | if (auxvp->tag < AT_MAX_CNT) { 39 | auxv[auxvp->tag] = auxvp->val; 40 | } 41 | } 42 | 43 | // Get address of the entrypoint for the user executable and 44 | // transfer control. 45 | // Requirements for the user executable: 46 | // - no dependencies 47 | // - no relocations 48 | 49 | pfmt("[dynld]: Running %s @ %s\n", __FUNCTION__, __FILE__); 50 | 51 | // Either `AT_EXECFD` or `AT_PHDR` must be specified, we only 52 | // support `AT_PHDR` here. 53 | // 54 | // From the X86_64 SystemV ABI: 55 | // AT_EXECFD 56 | // At process creation the system may pass control to an 57 | // interpreter program. When this happens, the system places 58 | // either an entry of type `AT_EXECFD` or one of type `AT_PHDR` 59 | // in the auxiliary vector. The entry for type `AT_EXECFD` 60 | // contains a file descriptor open to read the application 61 | // program’s object file. 62 | // 63 | // AT_PHDR 64 | // The system may create the memory image of the application 65 | // program before passing control to the interpreter 66 | // program. When this happens the `AT_PHDR` entry tells the 67 | // interpreter where to find the program header table in the 68 | // memory image. 69 | ERROR_ON(auxv[AT_PHDR] == 0 || auxv[AT_EXECFD] != 0, "[dynld]: ERROR, expected Linux Kernel to map user executable!\n"); 70 | 71 | // Entrypoint must be defined. 72 | ERROR_ON(auxv[AT_ENTRY] == 0, "[dynld]: ERROR, AT_ENTRY not found in auxiliary vector!\n"); 73 | 74 | // Transfer control to user executable. 75 | void (*user_entry)() = (void (*)())auxv[AT_ENTRY]; 76 | pfmt("[dynld]: Got user entrypoint @0x%x\n", user_entry); 77 | user_entry(); 78 | 79 | syscall1(__NR_exit, 0); 80 | } 81 | -------------------------------------------------------------------------------- /lib/src/alloc.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // Copyright (c) 2021, Johannes Stoelp 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | // Extremely simple and non-thread safe implementation of a dynamic 11 | // memory allocator. Which will greatly suffer under fragmentation as 12 | // we neither use splitting nor coalesce free blocks. It uses 13 | // first-fit and always traverses the block list from the beginning. 14 | // 15 | // Bottom line, this allocator can be optimized in so many ways but it 16 | // doesn't really matter for the purpose of this studies and therefore 17 | // the allocator is implemented in the most naive way. 18 | 19 | // Allocation block descriptor. 20 | struct BlockDescriptor { 21 | unsigned mFree; 22 | unsigned mSize; 23 | struct BlockDescriptor* mNext; 24 | }; 25 | 26 | // Global Allocator. 27 | 28 | // Size of available memory to the allocator. 29 | enum { MEMORY_SIZE = 1 * 1024 * 1024 }; 30 | // Memory for the allocator (statically reserved in the `.bss` section). 31 | uint8_t gMemory[MEMORY_SIZE]; 32 | 33 | // Top index into `gMemory` to indicate next free memory. 34 | unsigned gMemoryTop; 35 | 36 | // List of allocated blocks (free + used). 37 | struct BlockDescriptor* gHead; 38 | 39 | // Request free memory from `gMemory` and advance the `gMemoryTop` index. 40 | static void* brk(unsigned size) { 41 | ERROR_ON(gMemoryTop + size >= MEMORY_SIZE, "Allocator OOM!"); 42 | const unsigned old_top = gMemoryTop; 43 | gMemoryTop += size; 44 | return (void*)(gMemory + old_top); 45 | } 46 | 47 | // Allocate memory chunk of `size` and return pointer to the chunk. 48 | void* alloc(unsigned size) { 49 | struct BlockDescriptor* current = 0; 50 | 51 | // Check if we have a free block in the list of allocated blocks 52 | // that matches the requested size. 53 | current = gHead; 54 | while (current) { 55 | if (current->mFree && current->mSize < size) { 56 | current->mFree = 0; 57 | return (void*)(current + 1); 58 | }; 59 | current = current->mNext; 60 | } 61 | 62 | // Compute real allocation size: Payload + BlockDescriptor. 63 | unsigned real_size = size + sizeof(struct BlockDescriptor); 64 | 65 | // No free block found in the list of blocks, allocate new block. 66 | current = brk(real_size); 67 | 68 | // Initialize new block. 69 | current->mFree = 0; 70 | current->mSize = size; 71 | current->mNext = 0; 72 | 73 | // Insert new block at the beginning of the list of blocks. 74 | if (gHead != 0) { 75 | current->mNext = gHead; 76 | } 77 | gHead = current; 78 | 79 | return (void*)(current + 1); 80 | } 81 | 82 | void dealloc(void* ptr) { 83 | // Get descriptor block. 84 | struct BlockDescriptor* current = (struct BlockDescriptor*)ptr - 1; 85 | 86 | // Mark block as free. 87 | ERROR_ON(current->mFree, "Tried to de-alloc free block!"); 88 | current->mFree = 1; 89 | } 90 | -------------------------------------------------------------------------------- /test/checker.cc: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // Copyright (c) 2020, Johannes Stoelp 4 | 5 | #include "test_helper.h" 6 | 7 | extern "C" { 8 | #include 9 | #include 10 | } 11 | 12 | void check_dec() { 13 | char have[16]; 14 | int len = fmt(have, sizeof(have), "%d %d", 12345, -54321); 15 | 16 | ASSERT_EQ("12345 -54321", have); 17 | ASSERT_EQ(12, len); 18 | ASSERT_EQ('\0', have[len]); 19 | } 20 | 21 | void check_dec_long() { 22 | char have[32]; 23 | int len = fmt(have, sizeof(have), "%ld %d", 8589934592 /* 2^33 */, 8589934592 /* 2^33 */); 24 | 25 | ASSERT_EQ("8589934592 0", have); 26 | ASSERT_EQ(12, len); 27 | ASSERT_EQ('\0', have[len]); 28 | } 29 | 30 | void check_hex() { 31 | char have[16]; 32 | int len = fmt(have, sizeof(have), "%x %x", 0xdeadbeef, 0xcafe); 33 | 34 | ASSERT_EQ("deadbeef cafe", have); 35 | ASSERT_EQ(13, len); 36 | ASSERT_EQ('\0', have[len]); 37 | } 38 | 39 | void check_hex_long() { 40 | char have[32]; 41 | int len = fmt(have, sizeof(have), "%lx %x", 0x1111222233334444, 0x1111222233334444); 42 | 43 | ASSERT_EQ("1111222233334444 33334444", have); 44 | ASSERT_EQ(25, len); 45 | ASSERT_EQ('\0', have[len]); 46 | } 47 | 48 | void check_char() { 49 | char have[4]; 50 | int len = fmt(have, sizeof(have), "%c%c%c", 'A', 'a', '\x01' /* non printable */); 51 | 52 | ASSERT_EQ("Aa\x01", have); 53 | ASSERT_EQ(3, len); 54 | ASSERT_EQ('\0', have[len]); 55 | } 56 | 57 | void check_ptr() { 58 | char have[16]; 59 | int len = fmt(have, sizeof(have), "%p %p", (void*)0xabcd, (void*)0x0); 60 | 61 | ASSERT_EQ("0xabcd 0x0", have); 62 | ASSERT_EQ(10, len); 63 | ASSERT_EQ('\0', have[len]); 64 | } 65 | 66 | void check_null() { 67 | int len = fmt(0, 0, "%s", "abcd1234efgh5678"); 68 | 69 | ASSERT_EQ(16, len); 70 | } 71 | 72 | void check_exact_len() { 73 | char have[8]; 74 | int len = fmt(have, sizeof(have), "%s", "12345678"); 75 | 76 | ASSERT_EQ("1234567", have); 77 | ASSERT_EQ(8, len); 78 | ASSERT_EQ('\0', have[7]); 79 | } 80 | 81 | void check_exceed_len() { 82 | char have[8]; 83 | int len = fmt(have, sizeof(have), "%s", "123456789abcedf"); 84 | 85 | ASSERT_EQ("1234567", have); 86 | ASSERT_EQ(15, len); 87 | ASSERT_EQ('\0', have[7]); 88 | } 89 | 90 | void check_memset() { 91 | unsigned char d[7] = {0}; 92 | void* ret = memset(d, '\x42', sizeof(d)); 93 | 94 | ASSERT_EQ(ret, d); 95 | for (unsigned i = 0; i < sizeof(d); ++i) { 96 | ASSERT_EQ(0x42, d[i]); 97 | } 98 | } 99 | 100 | void check_memcpy() { 101 | unsigned char s[5] = {5, 4, 3, 2, 1}; 102 | unsigned char d[5] = {0}; 103 | void* ret = memcpy(d, s, sizeof(d)); 104 | 105 | ASSERT_EQ(ret, d); 106 | for (unsigned i = 0; i < sizeof(d); ++i) { 107 | ASSERT_EQ(5-i, d[i]); 108 | } 109 | } 110 | 111 | int main() { 112 | TEST_INIT; 113 | TEST_ADD(check_dec); 114 | TEST_ADD(check_dec_long); 115 | TEST_ADD(check_hex); 116 | TEST_ADD(check_hex_long); 117 | TEST_ADD(check_char); 118 | TEST_ADD(check_ptr); 119 | TEST_ADD(check_null); 120 | TEST_ADD(check_exact_len); 121 | TEST_ADD(check_exceed_len); 122 | TEST_ADD(check_memset); 123 | TEST_ADD(check_memcpy); 124 | return TEST_RUN; 125 | } 126 | -------------------------------------------------------------------------------- /lib/src/fmt.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // Copyright (c) 2020, Johannes Stoelp 4 | 5 | #include 6 | 7 | static const char* num2dec(char* buf, unsigned long len, unsigned long long num) { 8 | char* pbuf = buf + len - 1; 9 | *pbuf = '\0'; 10 | 11 | if (num == 0) { 12 | *(--pbuf) = '0'; 13 | } 14 | 15 | while (num > 0 && pbuf != buf) { 16 | char d = (char)(num % 10) + '0'; 17 | *(--pbuf) = d; 18 | num /= 10; 19 | } 20 | return pbuf; 21 | } 22 | 23 | static const char* num2hex(char* buf, unsigned long len, unsigned long long num) { 24 | char* pbuf = buf + len - 1; 25 | *pbuf = '\0'; 26 | 27 | if (num == 0) { 28 | *(--pbuf) = '0'; 29 | } 30 | 31 | while (num > 0 && pbuf != buf) { 32 | char d = (num & 0xf); 33 | *(--pbuf) = (char)(d + (d > 9 ? 'a' - 10 : '0')); 34 | num >>= 4; 35 | } 36 | return pbuf; 37 | } 38 | 39 | int vfmt(char* buf, unsigned long len, const char* fmt, va_list ap) { 40 | unsigned i = 0; 41 | 42 | #define put(c) \ 43 | { \ 44 | char _c = (c); \ 45 | if (i < len) { \ 46 | buf[i] = _c; \ 47 | } \ 48 | ++i; \ 49 | } 50 | 51 | #define puts(s) \ 52 | while (*s) { \ 53 | put(*s++); \ 54 | } 55 | 56 | char scratch[32]; 57 | int l_cnt = 0; 58 | 59 | while (*fmt) { 60 | if (*fmt != '%') { 61 | put(*fmt++); 62 | continue; 63 | } 64 | 65 | l_cnt = 0; 66 | 67 | continue_fmt: 68 | switch (*(++fmt /* constume '%' */)) { 69 | case 'l': 70 | ++l_cnt; 71 | goto continue_fmt; 72 | case 'd': { 73 | long val = l_cnt > 0 ? va_arg(ap, long) : va_arg(ap, int); 74 | if (val < 0) { 75 | val *= -1; 76 | put('-'); 77 | } 78 | const char* ptr = num2dec(scratch, sizeof(scratch), (unsigned long)val); 79 | puts(ptr); 80 | } break; 81 | case 'x': { 82 | unsigned long val = l_cnt > 0 ? va_arg(ap, unsigned long) : va_arg(ap, unsigned); 83 | const char* ptr = num2hex(scratch, sizeof(scratch), val); 84 | puts(ptr); 85 | } break; 86 | case 'c': { 87 | char c = va_arg(ap, int); // By C standard, value passed to varg smaller than `sizeof(int)` will be converted to int. 88 | put(c); 89 | } break; 90 | case 's': { 91 | const char* ptr = va_arg(ap, const char*); 92 | puts(ptr); 93 | } break; 94 | case 'p': { 95 | const void* val = va_arg(ap, const void*); 96 | const char* ptr = num2hex(scratch, sizeof(scratch), (unsigned long long)val); 97 | put('0'); 98 | put('x'); 99 | puts(ptr); 100 | } break; 101 | default: 102 | put(*fmt); 103 | break; 104 | } 105 | ++fmt; 106 | } 107 | 108 | #undef puts 109 | #undef put 110 | 111 | if (buf) { 112 | i < len ? (buf[i] = '\0') : (buf[len - 1] = '\0'); 113 | } 114 | return i; 115 | } 116 | 117 | int fmt(char* buf, unsigned long len, const char* fmt, ...) { 118 | va_list ap; 119 | va_start(ap, fmt); 120 | int ret = vfmt(buf, len, fmt, ap); 121 | va_end(ap); 122 | return ret; 123 | } 124 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: MIT 2 | # 3 | # Copyright (c) 2020, Johannes Stoelp 4 | # doc : https://clang.llvm.org/docs/ClangFormatStyleOptions.html 5 | 6 | Language: Cpp 7 | Standard: Auto 8 | 9 | AccessModifierOffset: -2 10 | AlignAfterOpenBracket: Align 11 | AlignConsecutiveMacros: true 12 | AlignConsecutiveAssignments: false 13 | AlignConsecutiveDeclarations: false 14 | AlignEscapedNewlines: Left 15 | AlignOperands: true 16 | AlignTrailingComments: true 17 | AllowAllArgumentsOnNextLine: true 18 | AllowAllConstructorInitializersOnNextLine: true 19 | AllowAllParametersOfDeclarationOnNextLine: true 20 | AllowShortBlocksOnASingleLine: false 21 | AllowShortCaseLabelsOnASingleLine: false 22 | AllowShortFunctionsOnASingleLine: Inline 23 | AllowShortIfStatementsOnASingleLine: Never 24 | AllowShortLambdasOnASingleLine: All 25 | AllowShortLoopsOnASingleLine: false 26 | AlwaysBreakAfterReturnType: None 27 | AlwaysBreakBeforeMultilineStrings: true 28 | AlwaysBreakTemplateDeclarations: Yes 29 | BinPackArguments: true 30 | BinPackParameters: true 31 | BreakBeforeBraces: Custom 32 | BraceWrapping: 33 | AfterCaseLabel: false 34 | AfterClass: false 35 | AfterControlStatement: false 36 | AfterEnum: false 37 | AfterFunction: false 38 | AfterNamespace: false 39 | AfterStruct: false 40 | AfterUnion: false 41 | AfterExternBlock: false 42 | BeforeCatch: false 43 | BeforeElse: false 44 | IndentBraces: false 45 | SplitEmptyFunction: true 46 | SplitEmptyRecord: true 47 | SplitEmptyNamespace: true 48 | BreakBeforeBinaryOperators: None 49 | BreakBeforeTernaryOperators: true 50 | BreakConstructorInitializers: AfterColon 51 | BreakConstructorInitializersBeforeComma: false 52 | BreakInheritanceList: BeforeColon 53 | BreakBeforeInheritanceComma: false 54 | BreakStringLiterals: true 55 | ColumnLimit: 140 56 | CompactNamespaces: true 57 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 58 | ConstructorInitializerIndentWidth: 2 59 | ContinuationIndentWidth: 4 60 | Cpp11BracedListStyle: true 61 | DerivePointerAlignment: false 62 | DisableFormat: false 63 | ExperimentalAutoDetectBinPacking: false 64 | FixNamespaceComments: true 65 | IncludeBlocks: Preserve 66 | # Could use `IncludeCategories` to define rules for includes 67 | IndentCaseLabels: true 68 | IndentPPDirectives: AfterHash 69 | IndentWidth: 4 70 | IndentWrappedFunctionNames: false 71 | KeepEmptyLinesAtTheStartOfBlocks: false 72 | MacroBlockBegin: '' 73 | MacroBlockEnd: '' 74 | MaxEmptyLinesToKeep: 2 75 | NamespaceIndentation: All 76 | PenaltyBreakAssignment: 2 77 | PenaltyBreakBeforeFirstCallParameter: 1 78 | PenaltyBreakComment: 300 79 | PenaltyBreakFirstLessLess: 120 80 | PenaltyBreakString: 1000 81 | PenaltyBreakTemplateDeclaration: 10 82 | PenaltyExcessCharacter: 1000000 83 | PenaltyReturnTypeOnItsOwnLine: 200 84 | PointerAlignment: Left 85 | ReflowComments: true 86 | SortIncludes: true 87 | SortUsingDeclarations: true 88 | SpaceAfterCStyleCast: false 89 | SpaceAfterLogicalNot: false 90 | SpaceAfterTemplateKeyword: false 91 | SpaceBeforeAssignmentOperators: true 92 | SpaceBeforeCpp11BracedList: false 93 | SpaceBeforeCtorInitializerColon: true 94 | SpaceBeforeInheritanceColon: true 95 | SpaceBeforeParens: ControlStatements 96 | SpaceBeforeRangeBasedForLoopColon: true 97 | SpaceInEmptyParentheses: false 98 | SpacesBeforeTrailingComments: 2 99 | SpacesInAngles: false 100 | SpacesInCStyleCastParentheses: false 101 | SpacesInContainerLiterals: true 102 | SpacesInParentheses: false 103 | SpacesInSquareBrackets: false 104 | TabWidth: 4 105 | UseTab: Never 106 | -------------------------------------------------------------------------------- /lib/include/syscall.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // Copyright (c) 2020, Johannes Stoelp 4 | 5 | #pragma once 6 | 7 | #if !defined(__linux__) || !defined(__x86_64__) 8 | # error "Only supported on linux(x86_64)!" 9 | #endif 10 | 11 | // Inline ASM 12 | // Syntax: 13 | // asm asm-qualifiers (AssemblerTemplate : OutputOperands : InputOperands : Clobbers) 14 | // 15 | // Output operand constraints: 16 | // = | operand (variable) is written to by this instruction 17 | // + | operand (variable) is written to / read from by this instruction 18 | // 19 | // Input/Output operand constraints: 20 | // r | allocate general purpose register 21 | // 22 | // Machine specific constraints (x86_64): 23 | // a | a register (eg rax) 24 | // c | c register (eg rcx) 25 | // d | d register (eg rdx) 26 | // D | di register (eg rdi) 27 | // S | si register (eg rsi) 28 | // 29 | // Local register variables: 30 | // In case a specific register is required which can not be specified via a 31 | // machine specific constraint. 32 | // ```c 33 | // register long r12 asm ("r12") = 42; 34 | // asm("nop" : : "r"(r12)); 35 | // ``` 36 | // 37 | // Reference: 38 | // https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html 39 | // https://gcc.gnu.org/onlinedocs/gcc/Machine-Constraints.html#Machine-Constraints 40 | // https://gcc.gnu.org/onlinedocs/gcc/Local-Register-Variables.html 41 | // 42 | // 43 | // Linux syscall ABI - x86-64 44 | // #syscall: rax 45 | // ret : rax 46 | // instr : syscall 47 | // args : rdi rsi rdx r10 r8 r9 48 | // 49 | // Reference: 50 | // syscall(2) 51 | // 52 | // 53 | // X86_64 `syscall` instruction additionally clobbers following registers: 54 | // rcx Store return address. 55 | // r11 Store RFLAGS. 56 | // 57 | // Reference: 58 | // https://www.felixcloutier.com/x86/syscall 59 | 60 | #define argcast(A) ((long)(A)) 61 | #define syscall1(n, a1) _syscall1(n, argcast(a1)) 62 | #define syscall2(n, a1, a2) _syscall2(n, argcast(a1), argcast(a2)) 63 | #define syscall3(n, a1, a2, a3) _syscall3(n, argcast(a1), argcast(a2), argcast(a3)) 64 | #define syscall4(n, a1, a2, a3, a4) _syscall4(n, argcast(a1), argcast(a2), argcast(a3), argcast(a4)) 65 | #define syscall6(n, a1, a2, a3, a4, a5, a6) _syscall6(n, argcast(a1), argcast(a2), argcast(a3), argcast(a4), argcast(a5), argcast(a6)) 66 | 67 | static inline long _syscall1(long n, long a1) { 68 | long ret; 69 | asm volatile("syscall" : "=a"(ret) : "a"(n), "D"(a1) : "rcx", "r11", "memory"); 70 | return ret; 71 | } 72 | 73 | static inline long _syscall2(long n, long a1, long a2) { 74 | long ret; 75 | asm volatile("syscall" : "=a"(ret) : "a"(n), "D"(a1), "S"(a2) : "rcx", "r11", "memory"); 76 | return ret; 77 | } 78 | 79 | static inline long _syscall3(long n, long a1, long a2, long a3) { 80 | long ret; 81 | asm volatile("syscall" : "=a"(ret) : "a"(n), "D"(a1), "S"(a2), "d"(a3) : "rcx", "r11", "memory"); 82 | return ret; 83 | } 84 | 85 | static inline long _syscall4(long n, long a1, long a2, long a3, long a4) { 86 | long ret; 87 | register long r10 asm("r10") = a4; 88 | asm volatile("syscall" : "=a"(ret) : "a"(n), "D"(a1), "S"(a2), "d"(a3), "r"(r10) : "rcx", "r11", "memory"); 89 | return ret; 90 | } 91 | 92 | static inline long _syscall6(long n, long a1, long a2, long a3, long a4, long a5, long a6) { 93 | long ret; 94 | register long r10 asm("r10") = a4; 95 | register long r8 asm("r8") = a5; 96 | register long r9 asm("r9") = a6; 97 | asm volatile("syscall" : "=a"(ret) : "a"(n), "D"(a1), "S"(a2), "d"(a3), "r"(r10), "r"(r8), "r"(r9) : "rcx", "r11", "memory"); 98 | return ret; 99 | } 100 | -------------------------------------------------------------------------------- /03_hello_dynld/README.md: -------------------------------------------------------------------------------- 1 | # Hello `dynld` 2 | 3 | ### Goals 4 | - Build dynamic linker `dynld.so` which retrieves the user program's 5 | entrypoint (`AT_ENTRY`) from the auxiliary vector and transfers 6 | control to it. 7 | - Build `no-std` program with a custom `PT_INTERP` entry pointing to 8 | `dynld.so`. 9 | 10 | --- 11 | 12 | ## Crafting the `dynld.so` 13 | 14 | As described in the `goals` above, the idea in this section is to 15 | create a simple dynamic linker which just gets the `entrypoint` of the 16 | user application and then jumps to it. This means the linker does not 17 | support things like: 18 | - Loading of additional dependencies. 19 | - Performing re-locations. 20 | 21 | That said, this dynamic linker will not be particularly useful but 22 | act as a skeleton for the upcoming chapters. 23 | 24 | The `entrypoint` of the user executable started by the dynamic linker 25 | can be found in the `auxiliary vector` setup by the Linux Kernel (see 26 | [02_process_init](../02_process_init/README.md)). 27 | The entry of interest is the `AT_ENTRY`: 28 | ```text 29 | AT_ENTRY 30 | The `a_ptr` member of this entry holds the entry point of 31 | the application program to which the interpreter program should 32 | transfer control. 33 | ``` 34 | 35 | There are two additional entries that need to be discussed, 36 | `AT_EXECFD` and `AT_PHDR`. The x86_64 SystemV ABI states that the OS 37 | Kernel must provide one or the other in the `auxiliary vector`. 38 | For simplicity the dynamic linker in this section only supports 39 | `AT_PHDR`, which means it requires the OS Kernel to already memory map 40 | the user executable. 41 | ```text 42 | AT_EXECFD 43 | At process creation the system may pass control to an 44 | interpreter program. When this happens, the system places 45 | either an entry of type `AT_EXECFD` or one of type `AT_PHDR` 46 | in the auxiliary vector. The entry for type `AT_EXECFD` 47 | contains a file descriptor open to read the application 48 | program’s object file. 49 | 50 | AT_PHDR 51 | The system may create the memory image of the application 52 | program before passing control to the interpreter 53 | program. When this happens the `AT_PHDR` entry tells the 54 | interpreter where to find the program header table in the 55 | memory image. 56 | ``` 57 | 58 | Using the [`no-std` program](../02_process_init/entry.c) from chapter 59 | [02_process_init](../02_process_init) as starting point, loading and 60 | jumping to the `entrypoint` of the user program can be done as: 61 | ```c 62 | void (*user_entry)() = (void (*)())auxv[AT_ENTRY]; 63 | user_entry(); 64 | ``` 65 | 66 | ## User program 67 | 68 | The next step is to create the user program that will be jumped to by the 69 | dynamic linker created above. 70 | For now the functionality of the user program is not really important, but it 71 | must full-fill the requirements to not depend on any shared libraries or 72 | contain any relocations. 73 | For this purpose the following `no-std` program is used: 74 | ```c 75 | #include 76 | #include 77 | 78 | #include 79 | 80 | void _start() { 81 | pfmt("Running %s @ %s\n", __FUNCTION__, __FILE__); 82 | 83 | syscall1(__NR_exit, 0); 84 | } 85 | ``` 86 | 87 | The important step, when linking the user program, is to inform the 88 | static linker to add the `dynld.so` created above in the `.interp` 89 | section. This can be done with the following command line switch: 90 | ```bash 91 | gcc ... -Wl,--dynamic-linker= ... 92 | ``` 93 | > The full compile and link command can be seen in the [Makefile - main 94 | > target](./Makefile). 95 | 96 | The result can be seen in the `.interp` sections referenced by the 97 | `PT_INTERP` segment in the program headers: 98 | ```bash 99 | readelf -W --string-dump .interp main 100 | 101 | String dump of section '.interp': 102 | [ 0] /home/johannst/dev/dynld/03_hello_dynld/dynld.so 103 | ``` 104 | ```bash 105 | readelf -W --program-headers main 106 | 107 | Program Headers: 108 | Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align 109 | PHDR 0x000040 0x0000000000000040 0x0000000000000040 0x0002d8 0x0002d8 R 0x8 110 | INTERP 0x000318 0x0000000000000318 0x0000000000000318 0x000031 0x000031 R 0x1 111 | [Requesting program interpreter: /home/johannst/dev/dynld/03_hello_dynld/dynld.so] 112 | ... 113 | ``` 114 | 115 | As discussed in [01_dynamic_linking](../01_dynamic_linking/README.md) 116 | the `PT_INTERP` segment tells the Linux Kernel which dynamic linker to 117 | load to handle the startup of the user executable. 118 | 119 | When running the `./main` user program, the `dynld.so` will be loaded 120 | by the Linux Kernel and controlled will be handed over to it. The 121 | `dynld.so` will retrieve the `entrypoint` of the user program and then 122 | jump to it. 123 | 124 | ## Things to remember 125 | - As defined by the SystemV ABI the OS Kernel must either provide an 126 | entry for `AT_EXECFD` or `AT_PHDR` in the `auxiliary vector`. 127 | - The `AT_ENTRY` points to the `entrypoint` of the user program. 128 | - When linking with gcc, the dynamic linker can be specified via `-Wl,--dynamic-linker=`. 129 | -------------------------------------------------------------------------------- /01_dynamic_linking/README.md: -------------------------------------------------------------------------------- 1 | # Hello dynamic linking 2 | 3 | In `dynamic linking` a program can use code that is not contained in the 4 | program file itself but rather in separate library files, so called shared 5 | objects. 6 | 7 | In comparison a statically linked program contains all the `code` & `data` that 8 | it needs to run from start until completion. The program will be loaded by the 9 | Linux Kernel from the disk into the virtual address space and control is handed 10 | over to the mapped program which then executes. 11 | ```text 12 | @vm 13 | | | 14 | @disk |--------| 15 | +--------+ execve(2) | | <- $rip 16 | | prog A | ------------> | prog A | 17 | +--------+ | | 18 | |--------| 19 | | | 20 | ``` 21 | 22 | A dynamically linked program on the other hand needs to specify a `dynamic 23 | linker` which is basically a runtime interpreter. The Linux Kernel will 24 | additionally load that interpreter into the virtual address space and give 25 | control to the interpreter rather than the user program. 26 | The interpreter will prepare the execution environment for the user program 27 | and pass control to it afterwards. 28 | Typical tasks of the interpreter are: 29 | - Loading shared objects into memory (dependencies). 30 | - Performing re-location. 31 | - Running initialization routines. 32 | ```text 33 | @vm @vm 34 | | | | | 35 | @disk |--------| |----------| 36 | +--------------+ execve(2) | | | | <- $rip 37 | | prog A | ------------> | prog A | | prog A | 38 | +--------------+ | | load deps | | 39 | | interp ldso | |--------| ------------> |----------| 40 | +--------------+ | | | | 41 | | dep libgreet | |--------| |----------| 42 | +--------------+ | ldso | <- $rip | ldso | 43 | |--------| |----------| 44 | | | 45 | |----------| 46 | | libgreet | 47 | |----------| 48 | ``` 49 | > NOTE: Technically the Linux Kernel does not need to load the dynamically 50 | > linked user program itself, but that detail is not important here. 51 | 52 | In the `ELF` binary format the name of the dynamic linker is specified as a 53 | string in the special section `.interp`. 54 | ```bash 55 | readelf -W --string-dump .interp main 56 | 57 | String dump of section '.interp': 58 | [ 0] /lib64/ld-linux-x86-64.so.2 59 | ``` 60 | 61 | The `.interp` section is referenced by the `PT_INTERP` segment in the program 62 | headers. This segment is used by the Linux Kernel during the `execve(2)` 63 | syscall in the [`load_elf_binary`][load_elf_binary] function to check if the 64 | program needs a dynamic linker and if so to retrieve its name. 65 | ```bash 66 | readelf -W --sections --program-headers main 67 | 68 | Section Headers: 69 | [Nr] Name Type Address Off Size ES Flg Lk Inf Al 70 | [ 0] NULL 0000000000000000 000000 000000 00 0 0 0 71 | [ 1] .interp PROGBITS 00000000000002a8 0002a8 00001c 00 A 0 0 1 72 | ... 73 | 74 | Program Headers: 75 | Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align 76 | PHDR 0x000040 0x0000000000000040 0x0000000000000040 0x000268 0x000268 R 0x8 77 | INTERP 0x0002a8 0x00000000000002a8 0x00000000000002a8 0x00001c 0x00001c R 0x1 78 | [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2] 79 | ... 80 | ``` 81 | 82 | With the use of `gdb` it can be easily verified that the control is first 83 | passed to the dynamic linker and not the user program. This is shown by 84 | stopping at the first instruction of the new process (`starti`) and examining 85 | the backtrace (`bt`). Where `ld-linux-x86-64.so` is the dynamic linker as shown 86 | in the `.interp` section above. 87 | ```bash 88 | gdb -q --batch -ex 'starti' -ex 'bt' ./main 89 | 90 | Program stopped. 91 | 0x00007ffff7fd2090 in _start () from /lib64/ld-linux-x86-64.so.2 92 | #0 0x00007ffff7fd2090 in _start () from /lib64/ld-linux-x86-64.so.2 93 | #1 0x0000000000000001 in ?? () 94 | #2 0x00007fffffffe43e in ?? () 95 | #3 0x0000000000000000 in ?? () 96 | ``` 97 | > NOTE: Frames `#1 - #3` don't actually exist, gdb's unwinder just tried to further unwind the stack. 98 | 99 | ## Things to remember 100 | - Dynamically linked programs use code contained in separate library files. 101 | - The `dynamic linker` is an interpreter loaded by the Linux Kernel and gets 102 | control before the user program. 103 | - A dynamically linked program specifies the dynamic linker needed in the 104 | `.interp` section. 105 | 106 | [load_elf_binary]: https://elixir.bootlin.com/linux/v5.9.8/source/fs/binfmt_elf.c#L850 107 | -------------------------------------------------------------------------------- /lib/include/elf.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // Copyright (c) 2020, Johannes Stoelp 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | /// ---------- 10 | /// ELF Header 11 | /// ---------- 12 | 13 | // Index into `ident`. 14 | #define EI_MAG0 0 15 | #define EI_MAG1 1 16 | #define EI_MAG2 2 17 | #define EI_MAG3 3 18 | #define EI_CLASS 4 19 | #define EI_DATA 5 20 | #define EI_OSABI 7 21 | 22 | // indent[EI_CLASS] 23 | #define ELFCLASS32 1 24 | #define ELFCLASS64 2 25 | 26 | // indent[EI_CLASS] 27 | #define ELFDATA2LSB 1 28 | #define ELFDATA2MSB 2 29 | 30 | // indent[EI_OSABI] 31 | #define ELFOSABI_SYSV 0 32 | 33 | // Objec file `type`. 34 | #define ET_NONE 0 35 | #define ET_DYN 3 36 | 37 | typedef struct { 38 | uint8_t ident[16]; // ELF identification. 39 | uint16_t type; // Object file type. 40 | uint16_t machine; // Machine type. 41 | uint32_t version; // Object file version. 42 | uint64_t entry; // Entrypoint address. 43 | uint64_t phoff; // Program header file offset. 44 | uint64_t shoff; // Section header file offset. 45 | uint32_t flags; // Processor specific flags. 46 | uint16_t ehsize; // ELF header size. 47 | uint16_t phentsize; // Program header entry size. 48 | uint16_t phnum; // Number of program header entries. 49 | uint16_t shentsize; // Section header entry size. 50 | uint16_t shnum; // Number of section header entries. 51 | uint16_t shstrndx; // Section name string table index. 52 | } Elf64Ehdr; 53 | 54 | /// -------------- 55 | /// Program Header 56 | /// -------------- 57 | 58 | #define PT_NULL 0 /* ignored */ 59 | #define PT_LOAD 1 /* Mark loadable segment (allowed p_memsz > p_filesz). */ 60 | #define PT_DYNAMIC 2 /* Location of .dynamic section */ 61 | #define PT_INTERP 3 /* Location of .interp section */ 62 | #define PT_NOTE 4 /* Location of auxiliary information */ 63 | #define PT_SHLIB 5 /* Reserved, but unspecified semantic */ 64 | #define PT_PHDR 6 /* Location & size of program headers itself */ 65 | #define PT_TLS 7 /* Thread local storage */ 66 | 67 | #define PT_GNU_EH_FRAME 0x6474e550 /* [x86-64] stack unwinding tables */ 68 | #define PT_LOPROC 0x70000000 69 | #define PT_HIPROC 0x7fffffff 70 | 71 | #define PF_X 0x1 /* Phdr flag eXecute flag bitmask */ 72 | #define PF_W 0x2 /* Phdr flag Write flag bitmask */ 73 | #define PF_R 0x4 /* Phdr flag Read flag bitmask */ 74 | 75 | typedef struct { 76 | uint32_t type; // Segment kind. 77 | uint32_t flags; // Flags describing Segment attributes like R, W, X. 78 | uint64_t offset; // Offset into the file where the Segment starts. 79 | uint64_t vaddr; // Virtual address of first byte of Segment in memory. 80 | uint64_t paddr; // Physical address, ignored in our case. 81 | uint64_t filesz; // Number of bytes of the Segment in the file image. 82 | uint64_t memsz; // Number of bytes of the segement in memory. 83 | uint64_t align; 84 | } Elf64Phdr; 85 | 86 | /// --------------- 87 | /// Dynamic Section 88 | /// --------------- 89 | 90 | #define DT_NULL 0 /* [ignored] Marks end of dynamic section */ 91 | #define DT_NEEDED 1 /* [val] Name of needed library */ 92 | #define DT_PLTRELSZ 2 /* [val] Size in bytes of PLT relocs */ 93 | #define DT_PLTGOT 3 /* [ptr] Processor defined value */ 94 | #define DT_HASH 4 /* [ptr] Address of symbol hash table */ 95 | #define DT_STRTAB 5 /* [ptr] Address of string table */ 96 | #define DT_SYMTAB 6 /* [ptr] Address of symbol table */ 97 | #define DT_RELA 7 /* [ptr] Address of Rela relocs */ 98 | #define DT_RELASZ 8 /* [val] Total size of Rela relocs */ 99 | #define DT_RELAENT 9 /* [val] Size of one Rela reloc */ 100 | #define DT_STRSZ 10 /* [val] Size of string table */ 101 | #define DT_SYMENT 11 /* [val] Size of one symbol table entry */ 102 | #define DT_INIT 12 /* [ptr] Address of init function */ 103 | #define DT_FINI 13 /* [ptr] Address of termination function */ 104 | #define DT_SONAME 14 /* [val] Name of shared object */ 105 | #define DT_RPATH 15 /* [val] Library search path (deprecated) */ 106 | #define DT_SYMBOLIC 16 /* [ignored] Start symbol search here */ 107 | #define DT_REL 17 /* [ptr] Address of Rel relocs */ 108 | #define DT_RELSZ 18 /* [val] Total size of Rel relocs */ 109 | #define DT_RELENT 19 /* [val] Size of one Rel reloc */ 110 | #define DT_PLTREL 20 /* [val] Type of reloc in PLT */ 111 | #define DT_DEBUG 21 /* [ptr] For debugging; unspecified */ 112 | #define DT_TEXTREL 22 /* [ignored] Reloc might modify .text */ 113 | #define DT_JMPREL 23 /* [ptr] Address of PLT relocs */ 114 | #define DT_BIND_NOW 24 /* [ignored] Process relocations of object */ 115 | #define DT_INIT_ARRAY 25 /* [ptr] Address of array of initialization functions */ 116 | #define DT_FINI_ARRAY 26 /* [ptr] Address of array of termination functions */ 117 | #define DT_INIT_ARRAYSZ 27 /* [val] Size in bytes of the initialization array */ 118 | #define DT_FINI_ARRAYSZ 28 /* [val] Size in bytes of the termination array */ 119 | #define DT_MAX_CNT 29 120 | 121 | typedef struct { 122 | uint64_t tag; 123 | union { 124 | uint64_t val; 125 | void* ptr; 126 | }; 127 | } Elf64Dyn; 128 | 129 | /// ------------ 130 | /// Symbol Entry 131 | /// ------------ 132 | 133 | typedef struct { 134 | uint32_t name; // Symbol name (index into string table). 135 | uint8_t info; // Symbol Binding bits[7..4] + Symbol Type bits[3..0]. 136 | uint8_t other; // Reserved. 137 | uint16_t shndx; // Section table index. 138 | uint64_t value; // 139 | uint64_t size; // 140 | } Elf64Sym; 141 | 142 | #define ELF64_ST_BIND(i) ((i) >> 4) 143 | #define ELF64_ST_TYPE(i) ((i)&0xf) 144 | 145 | // Symbold Bindings. 146 | #define STB_GLOBAL 1 /* Global symbol, visible to all object files. */ 147 | #define STB_WEAK 2 /* Global scope, but with lower precedence than global symbols. */ 148 | 149 | // Symbol Types. 150 | #define STT_NOTYPE 0 /* No type. */ 151 | #define STT_OBJECT 1 /* Data Object. */ 152 | #define STT_FUNC 2 /* Function entry point. */ 153 | 154 | // Special Section Indicies. 155 | #define SHN_UNDEF 0 /* Undefined section. */ 156 | #define SHN_ABS 0xff1 /* Indicates an absolute value. */ 157 | 158 | /// ----------------- 159 | /// Relocations Entry 160 | /// ----------------- 161 | 162 | typedef struct { 163 | uint64_t offset; // Virtual address of the storage unit affected by the relocation. 164 | uint64_t info; // Symbol table index + relocation type. 165 | } Elf64Rel; 166 | 167 | typedef struct { 168 | uint64_t offset; // Virtual address of the storage unit affected by the relocation. 169 | uint64_t info; // Symbol table index + relocation type. 170 | int64_t addend; // Constant value used to compute the relocation value. 171 | } Elf64Rela; 172 | 173 | #define ELF64_R_SYM(i) ((i) >> 32) 174 | #define ELF64_R_TYPE(i) ((i)&0xffffffffL) 175 | 176 | // x86_64 relocation types. 177 | #define R_X86_64_64 1 /* Absolute 64bit address, address affected by relocation: `base + offset` */ 178 | #define R_X86_64_COPY 5 /* Copy content from sym addr to relocation address: `base + offset` */ 179 | #define R_X86_64_GLOB_DAT 6 /* Address affected by relocation: `base + offset` */ 180 | #define R_X86_64_JUMP_SLOT 7 /* Address affected by relocation: `base + offset` */ 181 | #define R_X86_64_RELATIVE 8 /* Relative address *`base + offset` = `base + addend` */ 182 | -------------------------------------------------------------------------------- /02_process_init/README.md: -------------------------------------------------------------------------------- 1 | # Process Initialization 2 | 3 | ### Goals 4 | - Understand initial process state on process entry as specified by the 5 | [SystemV x86-64 ABI][sysv_x86_64]. 6 | - Build a `no-std` program to analyze & visualize the initial process state. 7 | 8 | --- 9 | 10 | Before starting to implement a minimal dynamic linker the first step is to 11 | understand the `process initialization` procedure. 12 | This is important because when starting a `dynamically-linked` 13 | executable the control is first passed to the dynamic linker 14 | (interpreter) by the Linux Kernel as mentioned in 15 | [01_dynamic_linking](../01_dynamic_linking/README.md). 16 | 17 | Once the dynamic linker is executing it needs to prepare the execution 18 | environment for the dynamically-linked executable. The dynamic linker's main tasks are: 19 | - To load dependencies. 20 | - Perform re-locations. 21 | - Run initialization routines. 22 | 23 | After the execution environment is prepared the dynamic linker hands 24 | control to the user executable. 25 | 26 | Due to all this requirements the dynamic must be a free-standing 27 | executable with no dependencies. 28 | 29 | ## Stack state on process entry 30 | 31 | When launching an ELF executable the Linux Kernel will map in the 32 | memory segments from the ELF file and setup some data on the `stack` 33 | according to the specification in the [SystemV x86-64 ABI][sysv_x86_64] 34 | chapter _Initial Stack and Register State_. 35 | 36 | On process startup after `execve(2)` the stack looks as follows 37 | ```text 38 | +------------+ High Address 39 | | .. | 40 | | ENV strs |<-+ 41 | +->| ARG strs | | 42 | | | .. | | 43 | | +------------+ | 44 | | | .. | | 45 | | +------------+ | 46 | | | AT_NULL | | 47 | | +------------+ | 48 | | | AUXV | | 49 | | +------------+ | 50 | | | 0x0 | | 51 | | +------------+ | 52 | | | ENVP |--+ 53 | | +------------+ 54 | | | 0x0 | 55 | | +------------+ 56 | +--| ARGV | 57 | +------------+ 58 | $rsp ->| ARGC | 59 | +------------+ Low Address 60 | 61 | 62 | | Offset (in bytes) | Type | Description 63 | -----+-----------------------+------------------------+-------------------- 64 | AUXV | &ENVP + 8*#ENVP + 8 | struct { uint64_t[2] } | Auxiliary Vector 65 | 0x0 | &ENVP + 8*#ENVP | | 0 terinator (ENVP) 66 | ENVP | &ARGV + 8*ARGC + 8 | const char* [] | Environment ptrs 67 | 0x0 | &ARGV + 8*ARGC | | 0 terinator (ARGV) 68 | ARGV | $rsp + 8 | const char* [] | Argument ptrs 69 | ARGC | $rsp | uint64_t | Argument count 70 | ``` 71 | 72 | - `ARGV : const char* []` is an array of pointers to string literals 73 | holding the command line arguments. 74 | - `ARGV[0]` is special as it holds the path of the launched program. 75 | - `ARGC : uint64_t` is the number of command line arguments + 1 76 | - `ENVP : const char* []` is an array of pointers to string literals 77 | holding the environment variables as seen by this process 78 | - `AUXV : uint64_t[2]` is the `auxiliary vector` providing additional 79 | information like the `entry point` or the `program header` of the 80 | program. 81 | 82 | The `AUXV` segment consists of consecutive `AuxvEntry` elements terminated by the `DT_NULL` element. 83 | ```c 84 | struct AuxvEntry { 85 | uint64_t tag; 86 | uint64_t val; 87 | }; 88 | ``` 89 | The _Auxiliary Vector_ chapter in the [`x86-64 System V ABI`][sysv_x86_64] specifies 90 | the following tags: 91 | ```text 92 | AT_NULL = 0 93 | AT_IGNORE = 1 94 | AT_EXECFD = 2 95 | AT_PHDR = 3 96 | AT_PHENT = 4 97 | AT_PHNUM = 5 98 | AT_PAGESZ = 6 99 | AT_BASE = 7 100 | AT_FLAGS = 8 101 | AT_ENTRY = 9 102 | AT_NOTELF = 10 103 | AT_UID = 11 104 | AT_EUID = 12 105 | AT_GID = 13 106 | AT_EGID = 14 107 | ``` 108 | Where `AT_NULL` is used to indicate the end of `AUXV`. 109 | 110 | ## Register state on process entry 111 | 112 | Regarding the state of general purpose registers on process entry the 113 | [x86-64 SystemV ABI][sysv_x86_64] states that all registers except the ones listed 114 | below are in an unspecified state: 115 | - `$rbp`: content is unspecified, but user code should set it to zero to mark 116 | the deepest stack frame 117 | - `$rsp`: points to the beginning of the data block provided by the Kernel and 118 | is guaranteed to be 16-byte aligned at process entry 119 | - `$rdx`: function pointer that the application should register with 120 | `atexit(BA_OS)`. 121 | > Not sure here if clearing `$rbp` is strictly required as frame-pointer 122 | > chaining is optional and can be omitted (`gcc -fomit-frame-pointer`). 123 | 124 | ## Hands-on the first instruction 125 | 126 | Before exploring and visualizing the data passed by the Linux Kernel on the 127 | stack there is one more question to answer: 128 | **How to run the first instruction in a process?** 129 | 130 | Typically when building a `C` program the users entry point is the `main` 131 | function, however this won't contain the first instruction executed after the 132 | process entry. This can be seen by extracting the `entry point` from the ELF 133 | header and checking against the symbols in the program. Here the entry point is 134 | `0x1020` which belongs to the symbol `_start` and not `main`. 135 | ```bash 136 | readelf -h main | grep Entry 137 | Entry point address: 0x1020 138 | 139 | nm main | grep '1020\|main' 140 | 0000000000001119 T main 141 | 0000000000001020 T _start 142 | ``` 143 | 144 | This is because by default the `static linker` adds some extra code & libraries 145 | to the program like for example the `libc` and the `C-runtime (crt)` which 146 | contains the `_start` symbol and hence the first instruction executed. 147 | 148 | Passing `--trace` down to the `static linker` sheds some light onto which 149 | input files the static linker actually processes. 150 | ```bash 151 | echo 'void main() {}' | gcc -x c -o /dev/null - -Wl,--trace 152 | /usr/lib/gcc/x86_64-pc-linux-gnu/10.2.0/../../../../lib/Scrt1.o 153 | /usr/lib/gcc/x86_64-pc-linux-gnu/10.2.0/../../../../lib/crti.o 154 | /usr/lib/gcc/x86_64-pc-linux-gnu/10.2.0/crtbeginS.o 155 | /tmp/ccjZdjYx.o 156 | /usr/lib/gcc/x86_64-pc-linux-gnu/10.2.0/libgcc.a 157 | /usr/lib/gcc/x86_64-pc-linux-gnu/10.2.0/../../../../lib/libgcc_s.so 158 | /usr/lib/gcc/x86_64-pc-linux-gnu/10.2.0/../../../../lib/libc.so 159 | /usr/lib/ld-linux-x86-64.so.2 160 | /usr/lib/gcc/x86_64-pc-linux-gnu/10.2.0/crtendS.o 161 | /usr/lib/gcc/x86_64-pc-linux-gnu/10.2.0/../../../../lib/crtn.o 162 | ``` 163 | > `/tmp/ccjZdjYx.o` is a temporary file created by the compiler containing the 164 | > code echoed. 165 | 166 | The static linker can be explicitly told to not include any default files by 167 | using the `gcc -nostdlib` argument. 168 | ```bash 169 | echo 'void _start() {}' | gcc -x c -o /dev/null - -Wl,--trace -nostdlib 170 | /tmp/ccbfkCoZ.o 171 | ``` 172 | Quoting `man gcc` 173 | > `-nostdlib` Do not use the standard system startup files or libraries when linking. 174 | 175 | ## Examining the data from the Kernel 176 | 177 | With the capability to control the first instruction executed after process 178 | entry we finally can visualize the data passed by the Linux Kernel on the stack. 179 | 180 | First we provide the symbol `_start` (default entry point) which saves a 181 | pointer to the Kernel data in `$rdi` and jumps to a function called `entry`. 182 | The pointer is saved in `$rdi` because that's the register for the first 183 | argument of class `INTEGER` ([SystemV ABI Function Arugments][sysv_x86_64_fnarg]). 184 | ```asm 185 | .section .text, "ax", @progbits 186 | .global _start 187 | _start: 188 | // Clear $rbp. 189 | xor rbp, rbp 190 | 191 | // Load ptr to Kernel data. 192 | lea rdi, [rsp] 193 | 194 | call entry 195 | ... 196 | ``` 197 | The full source code of the `_start` function is available in [entry.S](./entry.S). 198 | 199 | The pointer passed to the `entry` function can be used to compute `ARGC`, 200 | `ARGV` and `ENVP` accordingly. 201 | ```c 202 | void entry(uint64_t* prctx) { 203 | uint64_t argc = *prctx; 204 | const char** argv = (const char**)(prctx + 1); 205 | const char** envv = (const char**)(argv + argc + 1); 206 | ... 207 | ``` 208 | 209 | To collect the `AUXV` entries we first need to count the number of environment 210 | variables as follows. 211 | ```c 212 | // entry.c 213 | ... 214 | int envc = 0; 215 | for (const char** env = envv; *env; ++env) { 216 | ++envc; 217 | } 218 | 219 | uint64_t auxv[AT_MAX_CNT]; 220 | for (unsigned i = 0; i < AT_MAX_CNT; ++i) { 221 | auxv[i] = 0; 222 | } 223 | 224 | const Auxv64Entry* auxvp = (const Auxv64Entry*)(envv + envc + 1); 225 | for (; auxvp->tag != AT_NULL; ++auxvp) { 226 | if (auxvp->tag < AT_MAX_CNT) { 227 | auxv[auxvp->tag] = auxvp->val; 228 | } 229 | } 230 | ... 231 | ``` 232 | 233 | Finally the data can be printed as 234 | ```c 235 | // entry.c 236 | ... 237 | pfmt("Got %d arg(s)\n", argc); 238 | for (const char** arg = argv; *arg; ++arg) { 239 | pfmt("\targ = %s\n", *arg); 240 | } 241 | 242 | const int max_env = 10; 243 | pfmt("Print first %d env var(s)\n", max_env - 1); 244 | for (const char** env = envv; *env && (env - envv < max_env); ++env) { 245 | pfmt("\tenv = %s\n", *env); 246 | } 247 | 248 | pfmt("Print auxiliary vector\n"); 249 | pfmt("\tAT_EXECFD: %ld\n", auxv[AT_EXECFD]); 250 | pfmt("\tAT_PHDR : %p\n", auxv[AT_PHDR]); 251 | pfmt("\tAT_PHENT : %ld\n", auxv[AT_PHENT]); 252 | pfmt("\tAT_PHNUM : %ld\n", auxv[AT_PHNUM]); 253 | pfmt("\tAT_PAGESZ: %ld\n", auxv[AT_PAGESZ]); 254 | pfmt("\tAT_BASE : %lx\n", auxv[AT_BASE]); 255 | pfmt("\tAT_FLAGS : %ld\n", auxv[AT_FLAGS]); 256 | pfmt("\tAT_ENTRY : %p\n", auxv[AT_ENTRY]); 257 | pfmt("\tAT_NOTELF: %lx\n", auxv[AT_NOTELF]); 258 | pfmt("\tAT_UID : %ld\n", auxv[AT_UID]); 259 | pfmt("\tAT_EUID : %ld\n", auxv[AT_EUID]); 260 | pfmt("\tAT_GID : %ld\n", auxv[AT_GID]); 261 | pfmt("\tAT_EGID : %ld\n", auxv[AT_EGID]); 262 | ... 263 | ``` 264 | The full source code of the `entry` function is available in [entry.c](./entry.c). 265 | 266 | Running the program as `./entry 1 2 3 4` it yields following output: 267 | ```text 268 | Got 5 arg(s) 269 | arg = ./entry 270 | arg = 1 271 | arg = 2 272 | arg = 3 273 | arg = 4 274 | Print first 9 env var(s) 275 | env = I3SOCK=/run/user/1000/i3/ipc-socket.1200 276 | env = LC_NAME=en_US.UTF-8 277 | env = LC_NUMERIC=en_US.UTF-8 278 | env = WINDOWID=46221701 279 | env = LC_ADDRESS=en_US.UTF-8 280 | env = GDM_LANG=en_US.utf8 281 | env = PWD=/home/johannst/dev/dynld/02_process_init 282 | env = MAIL=/var/spool/mail/johannst 283 | env = XDG_SESSION_PATH=/org/freedesktop/DisplayManager/Session 284 | Print auxiliary vector 285 | AT_EXECFD: 0 286 | AT_PHDR : 0x400040 287 | AT_PHENT : 56 288 | AT_PHNUM : 5 289 | AT_PAGESZ: 4096 290 | AT_BASE : 0 291 | AT_FLAGS : 0 292 | AT_ENTRY : 0x401000 293 | AT_NOTELF: 0 294 | AT_UID : 1000 295 | AT_EUID : 1000 296 | AT_GID : 1000 297 | AT_EGID : 1000 298 | ``` 299 | 300 | ## Things to remember 301 | - On process entry the Linux Kernel provides data on the stack as specified in 302 | the [SystemV ABI][sysv_x86_64]. 303 | - By default the `static linker` adds additional code which contains the 304 | `_start` symbol being the default process `entry point`. 305 | 306 | ## References & Source Code 307 | - [x86-64 SystemV ABI][sysv_x86_64] 308 | - [x86-64 SystemV ABI - Passing arguments to functions][sysv_x86_64_fnarg] 309 | - [entry.S](./entry.S) 310 | - [entry.c](./entry.c) 311 | 312 | [sysv_x86_64]: https://www.uclibc.org/docs/psABI-x86_64.pdf 313 | [sysv_x86_64_fnarg]: https://johannst.github.io/notes/arch/x86_64.html#passing-arguments-to-functions 314 | -------------------------------------------------------------------------------- /04_dynld_nostd/dynld.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // Copyright (c) 2021, Johannes Stoelp 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | // {{{ Global constans 15 | 16 | enum { 17 | // Hard-coded page size. 18 | // We assert against the `AT_PAGESZ` auxiliary vector entry. 19 | PAGE_SIZE = 4096, 20 | // Hard-coded upper limit of `DT_NEEDED` entries per dso 21 | // (for simplicity to not require allocations). 22 | MAX_NEEDED = 1, 23 | }; 24 | 25 | // }}} 26 | // {{{ SystemVDescriptor 27 | 28 | typedef struct { 29 | uint64_t argc; // Number of commandline arguments. 30 | const char** argv; // List of pointer to command line arguments. 31 | uint64_t envc; // Number of environment variables. 32 | const char** envv; // List of pointers to environment variables. 33 | uint64_t auxv[AT_MAX_CNT]; // Auxiliary vector entries. 34 | } SystemVDescriptor; 35 | 36 | // Interpret and extract data passed on the stack by the Linux Kernel 37 | // when loading the initial process image. 38 | // The data is organized according to the SystemV x86_64 ABI. 39 | static SystemVDescriptor get_systemv_descriptor(const uint64_t* prctx) { 40 | SystemVDescriptor sysv = {0}; 41 | 42 | sysv.argc = *prctx; 43 | sysv.argv = (const char**)(prctx + 1); 44 | sysv.envv = (const char**)(sysv.argv + sysv.argc + 1); 45 | 46 | // Count the number of environment variables in the `ENVP` segment. 47 | for (const char** env = sysv.envv; *env; ++env) { 48 | sysv.envc += 1; 49 | } 50 | 51 | // Decode auxiliary vector `AUXV`. 52 | for (const Auxv64Entry* auxvp = (const Auxv64Entry*)(sysv.envv + sysv.envc + 1); auxvp->tag != AT_NULL; ++auxvp) { 53 | if (auxvp->tag < AT_MAX_CNT) { 54 | sysv.auxv[auxvp->tag] = auxvp->val; 55 | } 56 | } 57 | 58 | return sysv; 59 | } 60 | 61 | // }}} 62 | // {{{ Dso 63 | 64 | typedef struct { 65 | uint8_t* base; // Base address. 66 | void (*entry)(); // Entry function. 67 | uint64_t dynamic[DT_MAX_CNT]; // `.dynamic` section entries. 68 | uint64_t needed[MAX_NEEDED]; // Shared object dependencies (`DT_NEEDED` entries). 69 | uint32_t needed_len; // Number of `DT_NEEDED` entries (SO dependencies). 70 | } Dso; 71 | 72 | static void decode_dynamic(Dso* dso, uint64_t dynoff) { 73 | // Decode `.dynamic` section of the `dso`. 74 | for (const Elf64Dyn* dyn = (const Elf64Dyn*)(dso->base + dynoff); dyn->tag != DT_NULL; ++dyn) { 75 | if (dyn->tag == DT_NEEDED) { 76 | ERROR_ON(dso->needed_len == MAX_NEEDED, "Too many dso dependencies!"); 77 | dso->needed[dso->needed_len++] = dyn->val; 78 | } else if (dyn->tag < DT_MAX_CNT) { 79 | dso->dynamic[dyn->tag] = dyn->val; 80 | } 81 | } 82 | 83 | // Check for string table entries. 84 | ERROR_ON(dso->dynamic[DT_STRTAB] == 0, "DT_STRTAB missing in dynamic section!"); 85 | ERROR_ON(dso->dynamic[DT_STRSZ] == 0, "DT_STRSZ missing in dynamic section!"); 86 | 87 | // Check for symbol table entries. 88 | ERROR_ON(dso->dynamic[DT_SYMTAB] == 0, "DT_SYMTAB missing in dynamic section!"); 89 | ERROR_ON(dso->dynamic[DT_SYMENT] == 0, "DT_SYMENT missing in dynamic section!"); 90 | ERROR_ON(dso->dynamic[DT_SYMENT] != sizeof(Elf64Sym), "ELf64Sym size miss-match!"); 91 | 92 | // Check for SystemV hash table. We only support SystemV hash tables 93 | // `DT_HASH`, not gnu hash tables `DT_GNU_HASH`. 94 | ERROR_ON(dso->dynamic[DT_HASH] == 0, "DT_HASH missing in dynamic section!"); 95 | } 96 | 97 | static Dso get_prog_dso(const SystemVDescriptor* sysv) { 98 | Dso prog = {0}; 99 | 100 | // Determine the base address of the user program. 101 | // We only support the case where the Kernel already mapped the 102 | // user program into the virtual address space and therefore the 103 | // auxiliary vector contains an `AT_PHDR` entry pointing to the 104 | // Program Headers of the user program. 105 | // In that case, the base address of the user program can be 106 | // computed by taking the absolute address of the `AT_PHDR` entry 107 | // and subtracting the relative address `p_vaddr` of the `PT_PHDR` 108 | // entry from the user programs Program Header iself. 109 | // 110 | // VMA 111 | // | | 112 | // PROG BASE -> | | ^ 113 | // | | | 114 | // | | | <---------------------+ 115 | // | | | | 116 | // AT_PHDR -> +---------+ v | 117 | // | | | 118 | // | | | 119 | // | PT_PHDR | -----> Elf64Phdr { .., vaddr, .. } 120 | // | | 121 | // | | 122 | // +---------+ 123 | // | | 124 | // 125 | // PROG BASE = AT_PHDR - PT_PHDR.vaddr 126 | ERROR_ON(sysv->auxv[AT_PHDR] == 0 || sysv->auxv[AT_EXECFD] != 0, "AT_PHDR entry missing in the AUXV!"); 127 | 128 | // Offset to the `.dynamic` section from the user programs `base addr`. 129 | uint64_t dynoff = 0; 130 | 131 | // Program header of the user program. 132 | const Elf64Phdr* phdr = (const Elf64Phdr*)sysv->auxv[AT_PHDR]; 133 | 134 | ERROR_ON(sysv->auxv[AT_PHENT] != sizeof(Elf64Phdr), "Elf64Phdr size miss-match!"); 135 | 136 | // Decode PHDRs of the user program. 137 | for (unsigned phdrnum = sysv->auxv[AT_PHNUM]; --phdrnum; ++phdr) { 138 | if (phdr->type == PT_PHDR) { 139 | ERROR_ON(sysv->auxv[AT_PHDR] < phdr->vaddr, "Expectation auxv[AT_PHDR] >= phdr->vaddr failed!"); 140 | prog.base = (uint8_t*)(sysv->auxv[AT_PHDR] - phdr->vaddr); 141 | } else if (phdr->type == PT_DYNAMIC) { 142 | dynoff = phdr->vaddr; 143 | } 144 | 145 | ERROR_ON(phdr->type == PT_TLS, "Thread local storage not supported found PT_TLS!"); 146 | } 147 | ERROR_ON(dynoff == 0, "PT_DYNAMIC entry missing in the user programs PHDR!"); 148 | 149 | // Decode `.dynamic` section. 150 | decode_dynamic(&prog, dynoff); 151 | 152 | // Get the entrypoint of the user program form the auxiliary vector. 153 | ERROR_ON(sysv->auxv[AT_ENTRY] == 0, "AT_ENTRY entry missing in the AUXV!"); 154 | prog.entry = (void (*)())sysv->auxv[AT_ENTRY]; 155 | 156 | return prog; 157 | } 158 | 159 | static uint64_t get_num_dynsyms(const Dso* dso) { 160 | ERROR_ON(dso->dynamic[DT_HASH] == 0, "DT_HASH missing in dynamic section!"); 161 | 162 | // Get SystemV hash table. 163 | const uint32_t* hashtab = (const uint32_t*)(dso->base + dso->dynamic[DT_HASH]); 164 | 165 | // SystemV hash table layout: 166 | // nbucket 167 | // nchain 168 | // bucket[nbuckets] 169 | // chain[nchains] 170 | // 171 | // From the SystemV ABI - Dynamic Linking - Hash Table: 172 | // Both `bucket` and `chain` hold symbol table indexes. Chain 173 | // table entries parallel the symbol table. The number of symbol 174 | // table entries should equal `nchain`. 175 | return hashtab[1]; 176 | } 177 | 178 | static const char* get_str(const Dso* dso, uint64_t idx) { 179 | ERROR_ON(dso->dynamic[DT_STRSZ] < idx, "String table indexed out-of-bounds!"); 180 | return (const char*)(dso->base + dso->dynamic[DT_STRTAB] + idx); 181 | } 182 | 183 | static const Elf64Sym* get_sym(const Dso* dso, uint64_t idx) { 184 | ERROR_ON(get_num_dynsyms(dso) < idx, "Symbol table index out-of-bounds!"); 185 | return (const Elf64Sym*)(dso->base + dso->dynamic[DT_SYMTAB]) + idx; 186 | } 187 | 188 | static const Elf64Rela* get_pltreloca(const Dso* dso, uint64_t idx) { 189 | ERROR_ON(dso->dynamic[DT_PLTRELSZ] < sizeof(Elf64Rela) * idx, "PLT relocation table indexed out-of-bounds!"); 190 | return (const Elf64Rela*)(dso->base + dso->dynamic[DT_JMPREL]) + idx; 191 | } 192 | 193 | static const Elf64Rela* get_reloca(const Dso* dso, uint64_t idx) { 194 | ERROR_ON(dso->dynamic[DT_RELASZ] < sizeof(Elf64Rela) * idx, "RELA relocation table indexed out-of-bounds!"); 195 | return (const Elf64Rela*)(dso->base + dso->dynamic[DT_RELA]) + idx; 196 | } 197 | 198 | // }}} 199 | // {{{ Init & Fini 200 | 201 | typedef void (*initfptr)(); 202 | 203 | static void init(const Dso* dso) { 204 | if (dso->dynamic[DT_INIT]) { 205 | initfptr* fn = (initfptr*)(dso->base + dso->dynamic[DT_INIT]); 206 | (*fn)(); 207 | } 208 | 209 | size_t nfns = dso->dynamic[DT_INIT_ARRAYSZ] / sizeof(initfptr); 210 | initfptr* fns = (initfptr*)(dso->base + dso->dynamic[DT_INIT_ARRAY]); 211 | while (nfns--) { 212 | (*fns++)(); 213 | } 214 | } 215 | 216 | typedef void (*finifptr)(); 217 | 218 | static void fini(const Dso* dso) { 219 | size_t nfns = dso->dynamic[DT_FINI_ARRAYSZ] / sizeof(finifptr); 220 | finifptr* fns = (finifptr*)(dso->base + dso->dynamic[DT_FINI_ARRAY]) + nfns /* reverse destruction order */; 221 | while (nfns--) { 222 | (*--fns)(); 223 | } 224 | 225 | if (dso->dynamic[DT_FINI]) { 226 | finifptr* fn = (finifptr*)(dso->base + dso->dynamic[DT_FINI]); 227 | (*fn)(); 228 | } 229 | } 230 | 231 | // }}} 232 | // {{{ Symbol lookup 233 | 234 | static inline int strcmp(const char* s1, const char* s2) { 235 | while (*s1 == *s2 && *s1) { 236 | ++s1; 237 | ++s2; 238 | } 239 | return *(unsigned char*)s1 - *(unsigned char*)s2; 240 | } 241 | 242 | // Perform naive lookup for global symbol and return address if symbol was found. 243 | // 244 | // For simplicity this lookup doesn't use the hash table (`DT_HASH` | 245 | // `DT_GNU_HASH`) but rather iterates of the dynamic symbol table. Using the 246 | // hash table doesn't change the lookup result, however it yields better 247 | // performance for large symbol tables. 248 | // 249 | // `dso` A handle to the dso which dynamic symbol table should be searched. 250 | // `symname` Name of the symbol to look up. 251 | static void* lookup_sym(const Dso* dso, const char* symname) { 252 | for (unsigned i = 0; i < get_num_dynsyms(dso); ++i) { 253 | const Elf64Sym* sym = get_sym(dso, i); 254 | 255 | if ((ELF64_ST_TYPE(sym->info) == STT_OBJECT || ELF64_ST_TYPE(sym->info) == STT_FUNC) && ELF64_ST_BIND(sym->info) == STB_GLOBAL && 256 | sym->shndx != SHN_UNDEF) { 257 | if (strcmp(symname, get_str(dso, sym->name)) == 0) { 258 | return dso->base + sym->value; 259 | } 260 | } 261 | } 262 | return 0; 263 | } 264 | 265 | // }}} 266 | // {{{ Map Shared Library Dependency 267 | 268 | static Dso map_dependency(const char* dependency) { 269 | // For simplicity we only search for SO dependencies in the current working dir. 270 | // So no support for DT_RPATH/DT_RUNPATH and LD_LIBRARY_PATH. 271 | ERROR_ON(access(dependency, R_OK) != 0, "Dependency '%s' does not exist!\n", dependency); 272 | 273 | const int fd = open(dependency, O_RDONLY); 274 | ERROR_ON(fd < 0, "Failed to open '%s'", dependency); 275 | 276 | Elf64Ehdr ehdr; 277 | // Read ELF header. 278 | ERROR_ON(read(fd, &ehdr, sizeof(ehdr)) != (ssize_t)sizeof(ehdr), "Failed to read Elf64Ehdr!"); 279 | 280 | // Check ELF magic. 281 | ERROR_ON(ehdr.ident[EI_MAG0] != '\x7f' || ehdr.ident[EI_MAG1] != 'E' || ehdr.ident[EI_MAG2] != 'L' || ehdr.ident[EI_MAG3] != 'F', 282 | "Dependency '%s' wrong ELF magic value!\n", dependency); 283 | // Check ELF header size. 284 | ERROR_ON(ehdr.ehsize != sizeof(ehdr), "Elf64Ehdr size miss-match!"); 285 | // Check for 64bit ELF. 286 | ERROR_ON(ehdr.ident[EI_CLASS] != ELFCLASS64, "Dependency '%s' is not 64bit ELF!\n", dependency); 287 | // Check for OS ABI. 288 | ERROR_ON(ehdr.ident[EI_OSABI] != ELFOSABI_SYSV, "Dependency '%s' is not built for SysV OS ABI!\n", dependency); 289 | // Check ELF type. 290 | ERROR_ON(ehdr.type != ET_DYN, "Dependency '%s' is not a dynamic library!"); 291 | // Check for Phdr. 292 | ERROR_ON(ehdr.phnum == 0, "Dependency '%s' has no Phdr!\n", dependency); 293 | 294 | 295 | Elf64Phdr phdr[ehdr.phnum]; 296 | // Check PHDR header size. 297 | ERROR_ON(ehdr.phentsize != sizeof(phdr[0]), "Elf64Phdr size miss-match!"); 298 | 299 | // Read Program headers at offset `phoff`. 300 | ERROR_ON(pread(fd, &phdr, sizeof(phdr), ehdr.phoff) != (ssize_t)sizeof(phdr), "Failed to read Elf64Phdr[%d]!\n", ehdr.phnum); 301 | 302 | // Compute start and end address used by the library based on the all the `PT_LOAD` program headers. 303 | uint64_t dynoff = 0; 304 | uint64_t addr_start = (uint64_t)-1; 305 | uint64_t addr_end = 0; 306 | for (unsigned i = 0; i < ehdr.phnum; ++i) { 307 | const Elf64Phdr* p = &phdr[i]; 308 | if (p->type == PT_DYNAMIC) { 309 | // Offset to `.dynamic` section. 310 | dynoff = p->vaddr; 311 | } else if (p->type == PT_LOAD) { 312 | // Find start & end address. 313 | if (p->vaddr < addr_start) { 314 | addr_start = p->vaddr; 315 | } else if (p->vaddr + p->memsz > addr_end) { 316 | addr_end = p->vaddr + p->memsz; 317 | } 318 | } 319 | 320 | ERROR_ON(phdr->type == PT_TLS, "Thread local storage not supported found PT_TLS!"); 321 | } 322 | 323 | // Align start address to the next lower page boundary. 324 | addr_start = addr_start & ~(PAGE_SIZE - 1); 325 | // Align end address to the next higher page boundary. 326 | addr_end = (addr_end + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1); 327 | 328 | // Reserve region big enough to map all `PT_LOAD` sections of `dependency`. 329 | uint8_t* map = mmap(0 /* addr */, addr_end - addr_start /* len */, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, 330 | -1 /* fd */, 0 /* file offset */); 331 | ERROR_ON(map == MAP_FAILED, "Failed to mmap address space for dependency '%s'\n", dependency); 332 | 333 | // Compute base address for library. 334 | uint8_t* base = map - addr_start; 335 | 336 | // Map in all `PT_LOAD` segments from the `dependency`. 337 | for (unsigned i = 0; i < ehdr.phnum; ++i) { 338 | const Elf64Phdr* p = &phdr[i]; 339 | if (p->type != PT_LOAD) { 340 | continue; 341 | } 342 | 343 | // Page align start & end address. 344 | uint64_t addr_start = p->vaddr & ~(PAGE_SIZE - 1); 345 | uint64_t addr_end = (p->vaddr + p->memsz + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1); 346 | 347 | // Page align file offset. 348 | uint64_t off = p->offset & ~(PAGE_SIZE - 1); 349 | 350 | // Compute segment permissions. 351 | uint32_t prot = (p->flags & PF_X ? PROT_EXEC : 0) | (p->flags & PF_R ? PROT_READ : 0) | (p->flags & PF_W ? PROT_WRITE : 0); 352 | 353 | // Mmap segment. 354 | ERROR_ON(mmap(base + addr_start, addr_end - addr_start, prot, MAP_PRIVATE | MAP_FIXED, fd, off) != base + addr_start, 355 | "Failed to map `PT_LOAD` section %d for dependency '%s'.", i, dependency); 356 | 357 | // From the SystemV ABI - Program Headers: 358 | // If the segment’s memorysize (memsz) is larger than the file size (filesz), the "extra" bytes are defined to hold the value 359 | // `0` and to follow the segment’s initialized are 360 | // 361 | // This is typically used by the `.bss` section. 362 | if (p->memsz > p->filesz) { 363 | memset(base + p->vaddr + p->filesz, 0 /* byte */, p->memsz - p->filesz /*len*/); 364 | } 365 | } 366 | 367 | // Close file descriptor. 368 | close(fd); 369 | 370 | Dso dso = {0}; 371 | dso.base = base; 372 | decode_dynamic(&dso, dynoff); 373 | return dso; 374 | } 375 | 376 | // }}} 377 | // {{{ Resolve relocations 378 | 379 | typedef struct LinkMap { 380 | const Dso* dso; // Pointer to Dso list object. 381 | const struct LinkMap* next; // Pointer to next LinkMap entry ('0' terminates the list). 382 | } LinkMap; 383 | 384 | // Resolve a single relocation of `dso`. 385 | // 386 | // Resolve the relocation `reloc` by looking up the address of the symbol 387 | // referenced by the relocation. If the address of the symbol was found the 388 | // relocation is patched, if the address was not found the process exits. 389 | static void resolve_reloc(const Dso* dso, const LinkMap* map, const Elf64Rela* reloc) { 390 | // Get symbol referenced by relocation. 391 | const int symidx = ELF64_R_SYM(reloc->info); 392 | const Elf64Sym* sym = get_sym(dso, symidx); 393 | const char* symname = get_str(dso, sym->name); 394 | 395 | // Get relocation type. 396 | const unsigned reloctype = ELF64_R_TYPE(reloc->info); 397 | 398 | // Find symbol address. 399 | void* symaddr = 0; 400 | // FIXME: Should relocations of type `R_X86_64_64` only be looked up in `dso` directly? 401 | if (reloctype == R_X86_64_RELATIVE) { 402 | // Symbols address is computed by re-basing the relative address based 403 | // on the DSOs base address. 404 | symaddr = (void*)(dso->base + reloc->addend); 405 | } else { 406 | // Special handling of `R_X86_64_COPY` relocations. 407 | // 408 | // The `R_X86_64_COPY` relocation type is used in the main program when 409 | // it references an object provided by a shared library (eg extern 410 | // declared variable). 411 | // The static linker will still allocate storage for the external 412 | // object in the main programs `.bss` section and any reference to the 413 | // object from the main program are resolved by the static linker to 414 | // the location in the `.bss` section directly (relative addressing). 415 | // During runtime, when resolving the `R_X86_64_COPY` relocation, the 416 | // dynamic linker will copy the initial value from the shared library 417 | // that actually provides the objects symbol into the location of the 418 | // main program. References to the object by other shared library are 419 | // resolved to the location in the main programs `.bss` section. 420 | // 421 | // LinkMap: Relocs: 422 | // 423 | // main program { sym: foo, type: R_X86_64_COPY } 424 | // | 425 | // v 426 | // libso { sym: foo, type: R_X86_64_GLOB_DAT } 427 | // // Also `foo` is defined in `libso`. 428 | // 429 | // libso 430 | // +-----------+ 431 | // | .text | 432 | // main prog | | ref 433 | // +-----------+ | ... [foo] |--+ 434 | // | .text | R_X86_64_GLOB_DAT | | | 435 | // ref | | Patch address of +-----------+ | 436 | // +--| ... [foo] | foo in .got. | .got | | 437 | // | | | +------------------>| foo: |<-+ 438 | // | +-----------+ | | | 439 | // | | .bss | | +-----------+ 440 | // | | | / | .data | 441 | // +->| foo: ... |<--------------------| foo: ... | 442 | // | | R_X86_64_COPY | | 443 | // +-----------+ Copy initial value. +-----------+ 444 | // 445 | // The handling of `R_X86_64_COPY` relocation assumes that the main 446 | // program is always the first entry in the link map. 447 | for (const LinkMap* lmap = (reloctype == R_X86_64_COPY ? map->next : map); lmap && symaddr == 0; lmap = lmap->next) { 448 | symaddr = lookup_sym(lmap->dso, symname); 449 | } 450 | } 451 | ERROR_ON(symaddr == 0, "Failed lookup symbol %s while resolving relocations!", symname); 452 | 453 | pfmt("Resolved reloc %s to %p (base %p)\n", reloctype == R_X86_64_RELATIVE ? "" : symname, symaddr, dso->base); 454 | 455 | // Perform relocation according to relocation type. 456 | switch (reloctype) { 457 | case R_X86_64_GLOB_DAT: /* GOT entry for data objects. */ 458 | case R_X86_64_JUMP_SLOT: /* PLT entry. */ 459 | case R_X86_64_64: /* 64bit relocation (non-lazy). */ 460 | case R_X86_64_RELATIVE: /* DSO base relative relocation. */ 461 | // Patch storage unit of relocation with absolute address of the symbol. 462 | *(uint64_t*)(dso->base + reloc->offset) = (uint64_t)symaddr; 463 | break; 464 | case R_X86_64_COPY: /* Reference to global variable in shared ELF file. */ 465 | // Copy initial value of variable into relocation address. 466 | memcpy(dso->base + reloc->offset, (void*)symaddr, sym->size); 467 | break; 468 | default: 469 | ERROR_ON(true, "Unsupported relocation type %d!\n", reloctype); 470 | } 471 | } 472 | 473 | // Resolve all relocations of `dso`. 474 | // 475 | // Resolve relocations from the PLT & RELA tables. Use `map` as link map which 476 | // defines the order of the symbol lookup. 477 | static void resolve_relocs(const Dso* dso, const LinkMap* map) { 478 | // Resolve all relocation from the RELA table found in `dso`. There is 479 | // typically one relocation per undefined dynamic object symbol (eg global 480 | // variables). 481 | for (unsigned long relocidx = 0; relocidx < (dso->dynamic[DT_RELASZ] / sizeof(Elf64Rela)); ++relocidx) { 482 | const Elf64Rela* reloc = get_reloca(dso, relocidx); 483 | resolve_reloc(dso, map, reloc); 484 | } 485 | 486 | // Resolve all relocation from the PLT jump table found in `dso`. There is 487 | // typically one relocation per undefined dynamic function symbol. 488 | for (unsigned long relocidx = 0; relocidx < (dso->dynamic[DT_PLTRELSZ] / sizeof(Elf64Rela)); ++relocidx) { 489 | const Elf64Rela* reloc = get_pltreloca(dso, relocidx); 490 | resolve_reloc(dso, map, reloc); 491 | } 492 | } 493 | 494 | // }}} 495 | // {{{ Dynamic Linking (lazy resolve) 496 | 497 | // Dynamic link handler for lazy resolve. 498 | // This handler is installed in the GOT[2] entry of `Dso` objects which holds 499 | // the address of the jump target for the PLT0 jump pad. 500 | // 501 | // Mark `dynresolve_entry` as `naked` because we don't want a prologue/epilogue 502 | // being generated so we have full control over the stack layout. 503 | // 504 | // `noreturn` Function never returns. 505 | // `naked` Don't generate prologue/epilogue sequences. 506 | __attribute__((noreturn)) __attribute__((naked)) static void dynresolve_entry() { 507 | asm("dynresolve_entry:\n\t" 508 | // Pop arguments of PLT0 from the stack into rdi/rsi registers 509 | // These are the first two integer arguments registers as defined by 510 | // the SystemV abi and hence will be passed correctly to `dynresolve`. 511 | "pop %rdi\n\t" // GOT[1] entry (pushed by PLT0 pad). 512 | "pop %rsi\n\t" // Relocation index (pushed by PLT0 pad). 513 | "jmp dynresolve"); 514 | } 515 | 516 | // `used` Force to emit code for function. 517 | // `unused` Don't warn about unused function. 518 | __attribute__((used)) __attribute__((unused)) static void dynresolve(uint64_t got1, uint64_t reloc_idx) { 519 | ERROR_ON(true, 520 | "ERROR: dynresolve request not supported!" 521 | "\n\tGOT[1] = 0x%x" 522 | "\n\treloc_idx = %d\n", 523 | got1, reloc_idx); 524 | } 525 | 526 | // }}} 527 | // {{{ Setup GOT 528 | 529 | static void setup_got(const Dso* dso) { 530 | // GOT entries {0, 1, 2} have special meaning for the dynamic link process. 531 | // GOT[0] Hold address of dynamic structure referenced by `_DYNAMIC`. 532 | // GOT[1] Argument pushed by PLT0 pad on stack before jumping to GOT[2], 533 | // can be freely used by dynamic linker to identify the caller. 534 | // GOT[2] Jump target for PLT0 pad when doing dynamic resolve (lazy). 535 | // 536 | // We will not make use of GOT[0]/GOT[1] here but only GOT[2]. 537 | 538 | // Install dynamic resolve handler. This handler is used when binding 539 | // symbols lazy. 540 | // 541 | // The handler is installed in the `GOT[2]` entry for each DSO object that 542 | // has a GOT. It is jumped to from the `PLT0` pad with the following two 543 | // arguments passed via the stack: 544 | // - GOT[1] entry. 545 | // - Relocation index. 546 | // 547 | // This can be seen in the following disassembly of section .plt: 548 | // PLT0: 549 | // push QWORD PTR [rip+0x3002] # GOT[1] 550 | // jmp QWORD PTR [rip+0x3004] # GOT[2] 551 | // nop DWORD PTR [rax+0x0] 552 | // 553 | // PLT1: 554 | // jmp QWORD PTR [rip+0x3002] # GOT[3]; entry for 555 | // push 0x0 # Relocation index 556 | // jmp 401000 557 | // 558 | // The handler at GOT[2] can pop the arguments as follows: 559 | // pop %rdi // GOT[1] entry. 560 | // pop %rsi // Relocation index. 561 | 562 | if (dso->dynamic[DT_PLTGOT] != 0) { 563 | uint64_t* got = (uint64_t*)(dso->base + dso->dynamic[DT_PLTGOT]); 564 | got[2] = (uint64_t)&dynresolve_entry; 565 | } 566 | } 567 | 568 | // }}} 569 | 570 | // {{{ Dynamic Linker Entrypoint 571 | 572 | void dl_entry(const uint64_t* prctx) { 573 | // Parse SystemV ABI block. 574 | const SystemVDescriptor sysv_desc = get_systemv_descriptor(prctx); 575 | 576 | // Ensure hard-coded page size value is correct. 577 | ERROR_ON(sysv_desc.auxv[AT_PAGESZ] != PAGE_SIZE, "Hard-coded PAGE_SIZE miss-match!"); 578 | 579 | // Initialize dso handle for user program but extracting necesarry 580 | // information from `AUXV` and the `PHDR`. 581 | const Dso dso_prog = get_prog_dso(&sysv_desc); 582 | 583 | // Map dependency. 584 | // 585 | // In this chapter the user program should have a single shared 586 | // object dependency, which is our `libgreet.so` no-std shared 587 | // library. 588 | // The `libgreet.so` library itself should not have any dynamic 589 | // dependencies. 590 | ERROR_ON(dso_prog.needed_len != 1, "User program should have exactly one dependency!"); 591 | 592 | const Dso dso_lib = map_dependency(get_str(&dso_prog, dso_prog.needed[0])); 593 | ERROR_ON(dso_lib.needed_len != 0, "The library should not have any further dependencies!"); 594 | 595 | // Setup LinkMap. 596 | // 597 | // Create a list of DSOs as link map with the following order: 598 | // main -> libgreet.so 599 | // The link map determines the symbol lookup order. 600 | const LinkMap map_lib = {.dso = &dso_lib, .next = 0}; 601 | const LinkMap map_prog = {.dso = &dso_prog, .next = &map_lib}; 602 | 603 | // Resolve relocations of the library (dependency). 604 | resolve_relocs(&dso_lib, &map_prog); 605 | // Resolve relocations of the main program. 606 | resolve_relocs(&dso_prog, &map_prog); 607 | 608 | // Initialize library. 609 | init(&dso_lib); 610 | // Initialize main program. 611 | init(&dso_prog); 612 | 613 | // Setup global offset table (GOT). 614 | // 615 | // This installs a dynamic resolve handler, which should not be called in 616 | // this example as we resolve all relocations before transferring control 617 | // to the user program. 618 | // For safety we still install a handler which will terminate the program 619 | // once it is called. If we wouldn't install this handler the program would 620 | // most probably SEGFAULT in case symbol binding would be invoked during 621 | // runtime. 622 | setup_got(&dso_lib); 623 | setup_got(&dso_prog); 624 | 625 | // Transfer control to user program. 626 | dso_prog.entry(); 627 | 628 | // Finalize main program. 629 | fini(&dso_prog); 630 | // Finalize library. 631 | fini(&dso_lib); 632 | 633 | _exit(0); 634 | } 635 | 636 | // }}} 637 | 638 | // vim:fdm=marker 639 | -------------------------------------------------------------------------------- /04_dynld_nostd/README.md: -------------------------------------------------------------------------------- 1 | # `dynld` no-std 2 | 3 | ### Goals 4 | - Create a `no-std` shared library `libgreet.so` which exposes some functions 5 | and variables. 6 | - Create a `no-std` user executable which dynamically links against 7 | `libgreet.so` and uses exposed functions and variables. 8 | - Create a dynamic linker `dynld.so` which can prepare the execution 9 | environment, by mapping the shared library dependency and resolving all 10 | relocations. 11 | 12 | > In code blocks included in this page, the error checking code is omitted to 13 | > purely focus on the functionality they are trying to show-case. 14 | 15 | --- 16 | 17 | ## Creating the shared library `libgreet.so` 18 | 19 | To challenge the dynamic linker at least a little bit, the shared library will 20 | contain different functionality to generate different kinds of relocations. 21 | 22 | The first part consists of a global variable `gCalled` and a global function 23 | `get_greet`. Since the global variable is referenced in the function and the 24 | variable does not have `internal` linkage, this will generate a relocation in 25 | the shared library object. 26 | ```cpp 27 | int gCalled = 0; 28 | 29 | const char* get_greet() { 30 | // Reference global variable -> generates RELA relocation (R_X86_64_GLOB_DAT). 31 | ++gCalled; 32 | return "Hello from libgreet.so!"; 33 | } 34 | ``` 35 | 36 | Additionally the shared library contains a `constructor` and `destructor` 37 | function which will be added to the `.init_array` and `.fini_array` sections 38 | accordingly. The dynamic linkers task is to run these function during 39 | initialization and shutdown of the shared library. 40 | ```cpp 41 | // Definition of `static` function which is referenced from the `DT_INIT_ARRAY` 42 | // dynamic section entry -> generates R_X86_64_RELATIVE relocation. 43 | __attribute__((constructor)) static void libinit() { 44 | pfmt("libgreet.so: libinit\n"); 45 | } 46 | 47 | // Definition of `non static` function which is referenced from the 48 | // `DT_FINI_ARRAY` dynamic section entry -> generates R_X86_64_64 relocation. 49 | __attribute__((destructor)) void libfini() { 50 | pfmt("libgreet.so: libfini\n"); 51 | } 52 | ``` 53 | > `constructor` / `destructor` are function attributes and their definition is 54 | > described in [gcc common function attributes][gcc-fn-attributes]. 55 | 56 | The generated relocations can be seen in the `readelf` output of the shared 57 | library ELF file. 58 | ```bash 59 | > readelf -r libgreet.so 60 | 61 | Relocation section '.rela.dyn' at offset 0x3f0 contains 3 entries: 62 | Offset Info Type Sym. Value Sym. Name + Addend 63 | 000000003e88 000000000008 R_X86_64_RELATIVE 1064 64 | 000000003e90 000300000001 R_X86_64_64 000000000000107c libfini + 0 65 | 000000003ff8 000400000006 R_X86_64_GLOB_DAT 0000000000004020 gCalled + 0 66 | 67 | Relocation section '.rela.plt' at offset 0x438 contains 1 entry: 68 | Offset Info Type Sym. Value Sym. Name + Addend 69 | 000000004018 000100000007 R_X86_64_JUMP_SLO 0000000000000000 pfmt + 0 70 | ``` 71 | 72 | Dumping the `.dynamic` section of the shared library, it can be see that there 73 | are `INIT_*` / `FINI_*` entries. These are generated as result of the 74 | `constructor` / `destructor` functions. 75 | The dynamic linker can make use of those entries at runtime to locate the 76 | `.init_array` / `.fini_array` sections and run the functions accordingly. 77 | ```bash 78 | > readelf -d libgreet.so 79 | 80 | Dynamic section at offset 0x2e98 contains 18 entries: 81 | Tag Type Name/Value 82 | 0x0000000000000019 (INIT_ARRAY) 0x3e88 83 | 0x000000000000001b (INIT_ARRAYSZ) 8 (bytes) 84 | 0x000000000000001a (FINI_ARRAY) 0x3e90 85 | 0x000000000000001c (FINI_ARRAYSZ) 8 (bytes) 86 | -- snip -- 87 | 0x0000000000000002 (PLTRELSZ) 24 (bytes) 88 | 0x0000000000000014 (PLTREL) RELA 89 | 0x0000000000000017 (JMPREL) 0x438 90 | 0x0000000000000007 (RELA) 0x3f0 91 | 0x0000000000000008 (RELASZ) 72 (bytes) 92 | 0x0000000000000009 (RELAENT) 24 (bytes) 93 | 0x0000000000000000 (NULL) 0x0 94 | ``` 95 | 96 | The full source code of the shared library is available in 97 | [libgreet.c](./libgreet.c). 98 | 99 | ## Creating the user executable 100 | 101 | The user program looks as follows, it will just make use of the `libgreet.so` 102 | global variable and functions. 103 | ```cpp 104 | // API of `libgreet.so`. 105 | extern const char* get_greet(); 106 | extern const char* get_greet2(); 107 | extern int gCalled; 108 | 109 | void _start() { 110 | pfmt("Running _start() @ %s\n", __FILE__); 111 | 112 | // Call function from libgreet.so -> generates PLT relocations (R_X86_64_JUMP_SLOT). 113 | pfmt("get_greet() -> %s\n", get_greet()); 114 | pfmt("get_greet2() -> %s\n", get_greet2()); 115 | 116 | // Reference global variable from libgreet.so -> generates RELA relocation (R_X86_64_COPY). 117 | pfmt("libgreet.so called %d times\n", gCalled); 118 | } 119 | ``` 120 | 121 | Inspecting the relocations again with `readelf` it can be seen that they 122 | contain entries for the referenced variable and functions of the shared 123 | library. 124 | ```bash 125 | > readelf -r main 126 | 127 | Relocation section '.rela.dyn' at offset 0x478 contains 1 entry: 128 | Offset Info Type Sym. Value Sym. Name + Addend 129 | 000000404028 000300000005 R_X86_64_COPY 0000000000404028 gCalled + 0 130 | 131 | Relocation section '.rela.plt' at offset 0x490 contains 2 entries: 132 | Offset Info Type Sym. Value Sym. Name + Addend 133 | 000000404018 000200000007 R_X86_64_JUMP_SLO 0000000000000000 get_greet + 0 134 | 000000404020 000400000007 R_X86_64_JUMP_SLO 0000000000000000 get_greet2 + 0 135 | ``` 136 | 137 | The last important piece is to dynamically link the user program against 138 | `libgreet.so` which will generate a `DT_NEEDED` entry in the `.dynamic` 139 | section. 140 | ```bash 141 | > readelf -r -d main 142 | 143 | Dynamic section at offset 0x2ec0 contains 15 entries: 144 | Tag Type Name/Value 145 | 0x0000000000000001 (NEEDED) Shared library: [libgreet.so] 146 | -- snip --- 147 | 0x0000000000000000 (NULL) 0x0 148 | ``` 149 | 150 | The full source code of the user program is available in 151 | [main.c](./main.c). 152 | 153 | ## Creating the dynamic linker `dynld.so` 154 | 155 | The dynamic linker developed here is kept simple and mainly used to explore the 156 | mechanics of dynamic linking. That said, it means that it is tailored 157 | specifically for the previously developed executable and won't support things as 158 | - Multiple shared library dependencies. 159 | - Dynamic symbol resolve during runtime (lazy bindings). 160 | - Passing arguments to the user program. 161 | - Thread locals storage (TLS). 162 | 163 | However, with a little effort, this dynamic linker could easily be extend and 164 | generalized more. 165 | 166 | Before diving into details, let's first define the high-level structure of 167 | `dynld.so`: 168 | 1. Decode initial process state from the stack([`SystemV ABI` 169 | context](../02_process_init/README.md#stack-state-on-process-entry)). 170 | 1. Map the `libgreet.so` shared library dependency. 171 | 1. Resolve all relocations of `libgreet.so` and `main`. 172 | 1. Run `INIT` functions of `libgreet.so` and `main`. 173 | 1. Transfer control to user program `main`. 174 | 1. Run `FINI` functions of `libgreet.so` and `main`. 175 | 176 | When discussing the dynamic linkers functionality below, it is helpful to 177 | understand and keep the following links between the ELF structures in mind. 178 | - From the `PHDR` the dynamic linker can find the `.dynamic` section. 179 | - From the `.dynamic` section, the dynamic linker can find all information 180 | required for dynamic linking such as the `relocation table`, `symbol table` and 181 | so on. 182 | ```text 183 | PHDR 184 | AT_PHDR ----> +------------+ 185 | | ... | 186 | | | .dynamic 187 | | PT_DYNAMIC | ----> +-----------+ 188 | | | | DT_SYMTAB | ----> [ Symbol Table (.dynsym) ] 189 | | ... | | DT_STRTAB | ----> [ String Table (.dynstr) ] 190 | +------------+ | DT_RELA | ----> [ Relocation Table (.rela.dyn) ] 191 | | DT_JMPREL | ----> [ Relocation Table (.rela.plt) ] 192 | | DT_NEEDED | ----> Shared Library Dependency 193 | | ... | 194 | +-----------+ 195 | ``` 196 | 197 | ### (1) Decode initial process state from the stack 198 | 199 | This step consists of decoding the `SystemV ABI` block on the stack into an 200 | appropriate data structure. The details about this have already been discussed 201 | in [02 Process initialization](../02_process_init/). 202 | ```c 203 | typedef struct { 204 | uint64_t argc; // Number of commandline arguments. 205 | const char** argv; // List of pointer to command line arguments. 206 | uint64_t envc; // Number of environment variables. 207 | const char** envv; // List of pointers to environment variables. 208 | uint64_t auxv[AT_MAX_CNT]; // Auxiliary vector entries. 209 | } SystemVDescriptor; 210 | 211 | void dl_entry(const uint64_t* prctx) { 212 | // Parse SystemV ABI block. 213 | const SystemVDescriptor sysv_desc = get_systemv_descriptor(prctx); 214 | ... 215 | ``` 216 | 217 | With the SystemV ABI descriptor, the next step is to extract the information of 218 | the user program that are of interest to the dynamic linker. 219 | That information is captured in a `dynamic shared object (dso)` structure as 220 | defined below. 221 | ```c 222 | typedef struct { 223 | uint8_t* base; // Base address. 224 | void (*entry)(); // Entry function. 225 | uint64_t dynamic[DT_MAX_CNT]; // `.dynamic` section entries. 226 | uint64_t needed[MAX_NEEDED]; // Shared object dependencies (`DT_NEEDED` entries). 227 | uint32_t needed_len; // Number of `DT_NEEDED` entries (SO dependencies). 228 | } Dso; 229 | ``` 230 | 231 | Filling in the `dso` structure is achieved by following the ELF structures as 232 | shown above. 233 | First, the address of the program headers can be found in the `AT_PHDR` entry 234 | in the auxiliary vector. From there the `.dynamic` section can be located by 235 | using the program header `PT_DYNAMIC->vaddr` entry. 236 | 237 | However before using the `vaddr` field, first the `base address` of the `dso` 238 | needs to be computed. This is important because addresses in the program header 239 | and the dynamic section are relative to the `base address`. 240 | 241 | The `base address` can be computed by using the `PT_PHDR` program header which 242 | describes the program headers itself. The absolute `base address` is then 243 | computed by subtracting the relative `PT_PHDR->vaddr` from the absolute address 244 | in the `AT_PDHR` entry from the auxiliary vector. Looking at the figure below 245 | this becomes more clear. 246 | ```text 247 | VMA 248 | | | 249 | base address -> | | - 250 | | | | <---------------------+ 251 | AT_PHDR -> +---------+ - | 252 | | | | 253 | | PT_PHDR | -----> Elf64Phdr { .., vaddr, .. } 254 | | | 255 | +---------+ 256 | | | 257 | ``` 258 | > For `non-pie` executables the `base address` is typically `0x0`, while for 259 | > `pie` executables it is typically **not** `0x0`. 260 | 261 | Looking at the concrete implementation in the dynamic linker, computing the 262 | `base address` is done while iterating over the program headers. The result is 263 | stored in the `dso` object representing the user program. 264 | ```c 265 | static Dso get_prog_dso(const SystemVDescriptor* sysv) { 266 | ... 267 | const Elf64Phdr* phdr = (const Elf64Phdr*)sysv->auxv[AT_PHDR]; 268 | for (unsigned phdrnum = sysv->auxv[AT_PHNUM]; --phdrnum; ++phdr) { 269 | if (phdr->type == PT_PHDR) { 270 | prog.base = (uint8_t*)(sysv->auxv[AT_PHDR] - phdr->vaddr); 271 | } else if (phdr->type == PT_DYNAMIC) { 272 | dynoff = phdr->vaddr; 273 | } 274 | } 275 | ``` 276 | 277 | Continuing, the next step is to decode the `.dynamic` section. Entries in the 278 | `.dynamic` section are comprised of `2 x 64bit` words and are interpreted as 279 | follows: 280 | ```c 281 | typedef struct { 282 | uint64_t tag; 283 | union { 284 | uint64_t val; 285 | void* ptr; 286 | }; 287 | } Elf64Dyn; 288 | ``` 289 | > Available `tags` are defined in [elf.h](../lib/include/elf.h). 290 | 291 | The `.dynamic` section is located by using the offset from the 292 | `PT_DYNAMIC->vaddr` entry and adding it to the absolute `base address` of the 293 | `dso`. When iterating over the program headers above, this offset was already 294 | stored in `dynoff` and passed to the `decode_dynamic` function. 295 | ```c 296 | static void decode_dynamic(Dso* dso, uint64_t dynoff) { 297 | for (const Elf64Dyn* dyn = (const Elf64Dyn*)(dso->base + dynoff); dyn->tag != DT_NULL; ++dyn) { 298 | if (dyn->tag == DT_NEEDED) { 299 | dso->needed[dso->needed_len++] = dyn->val; 300 | } else if (dyn->tag < DT_MAX_CNT) { 301 | dso->dynamic[dyn->tag] = dyn->val; 302 | } 303 | } 304 | ... 305 | ``` 306 | > The value of `DT_NEEDED` entries contain indexes into the `string table 307 | > (DR_STRTAB)` to get the name of the share library dependency. 308 | 309 | The last step to extract the information of the user program is to store the 310 | address of the `entry function` where the dynamic linker will pass control to 311 | once the execution environment is set up. 312 | The address of the `entry function` can be retrieved from the `AT_ENTRY` entry 313 | in the auxiliary vector. 314 | ```c 315 | static Dso get_prog_dso(const SystemVDescriptor* sysv) { 316 | ... 317 | prog.entry = (void (*)())sysv->auxv[AT_ENTRY]; 318 | ``` 319 | 320 | ### (2) Map `libgreet.so` 321 | 322 | The next step of the dynamic linker is to map the shared library dependency of 323 | the main program. Therefore the value of the `DT_NEEDED` entry in the 324 | `.dynamic` section is used. This entry holds an index into the `string table` 325 | where the name of the dependency can be retrieved from. 326 | ```c 327 | static const char* get_str(const Dso* dso, uint64_t idx) { 328 | return (const char*)(dso->base + dso->dynamic[DT_STRTAB] + idx); 329 | } 330 | 331 | void dl_entry(const uint64_t* prctx) { 332 | ... 333 | const Dso dso_lib = map_dependency(get_str(&dso_prog, dso_prog.needed[0])); 334 | ``` 335 | > In this concrete case the main program only has a single shared library 336 | > dependency. However ELF files can have multiple dependencies, in that case 337 | > the `.dynamic` section contains multiple `DT_NEEDED` entries. 338 | 339 | The task of the `map_dependency` function now is to iterate over the program 340 | headers of the shared library and map the segments described by each `PT_LOAD` 341 | entry from file system into the virtual address space of the process. 342 | 343 | To find the program headers, the first step is to read in the ELF header 344 | because this header contains the file offset and the number of program headers. 345 | This information is then used to read in the program headers from the file. 346 | ```c 347 | typedef struct { 348 | uint64_t phoff; // Program header file offset. 349 | uint16_t phnum; // Number of program header entries. 350 | ... 351 | } Elf64Ehdr; 352 | 353 | static Dso map_dependency(const char* dependency) { 354 | const int fd = open(dependency, O_RDONLY); 355 | 356 | // Read ELF header. 357 | Elf64Ehdr ehdr; 358 | read(fd, &ehdr, sizeof(ehdr); 359 | 360 | // Read Program headers at offset `phoff`. 361 | Elf64Phdr phdr[ehdr.phnum]; 362 | pread(fd, &phdr, sizeof(phdr), ehdr.phoff); 363 | ... 364 | ``` 365 | > Full definition of the `Elf64Ehdr` and `Elf64Phdr` structures are available 366 | > in [elf.h](../lib/include/elf.h). 367 | 368 | With the program headers available, the different `PT_LOAD` segments can be 369 | mapped. The strategy here is to first map a whole region in the virtual address 370 | space, big enough to hold all the `PT_LOAD` segments. Once the allocation 371 | succeeded the single `PT_LOAD` segments can be mapped over the allocated 372 | region. 373 | 374 | To compute the length of the initial allocation, the `start` and `end` address 375 | must be computed by iterating over all `PT_LOAD` entries and saving the minimal 376 | and maximal address. 377 | After that, the memory region is `mmaped` as private & anonymous mapping with 378 | `address == 0`, telling the OS to choose a virtual address, and `PROT_NONE` 379 | as the `PT_LOAD` segments define their own protection flags. 380 | ```c 381 | static Dso map_dependency(const char* dependency) { 382 | ... 383 | // Compute start and end address. 384 | uint64_t addr_start = (uint64_t)-1; 385 | uint64_t addr_end = 0; 386 | for (unsigned i = 0; i < ehdr.phnum; ++i) { 387 | const Elf64Phdr* p = &phdr[i]; 388 | if (p->type == PT_LOAD) { 389 | if (p->vaddr < addr_start) { 390 | addr_start = p->vaddr; 391 | } else if (p->vaddr + p->memsz > addr_end) { 392 | addr_end = p->vaddr + p->memsz; 393 | } 394 | } 395 | } 396 | 397 | // Page align addresses. 398 | addr_start = addr_start & ~(PAGE_SIZE - 1); 399 | addr_end = (addr_end + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1); 400 | 401 | // Allocate region big enough to fit all `PT_LOAD` sections. 402 | uint8_t* map = mmap(0 /* addr */, addr_end - addr_start /* len */, 403 | PROT_NONE /* prot */, MAP_PRIVATE | MAP_ANONYMOUS /* flags */, 404 | -1 /* fd */, 0 /* file offset */); 405 | ``` 406 | 407 | Now the single `PT_LOAD` segments can be mapped from the ELF file of the shared 408 | library using the open file descriptor `fd` from above.
409 | A segment could contain ELF sections of type `SHT_NOBITS` which contributes to 410 | the segments memory image but don't contain actual data in the ELF file on disk 411 | (typical for `.bss` the zero initialized section). Those sections are normally 412 | at the end of the segment making the `PT_LOAD->memzsz > PT_LOAD->filesz` and 413 | are initialized to `0` during runtime. 414 | ```c 415 | static Dso map_dependency(const char* dependency) { 416 | ... 417 | // Compute base address for library. 418 | uint8_t* base = map - addr_start; 419 | 420 | for (unsigned i = 0; i < ehdr.phnum; ++i) { 421 | const Elf64Phdr* p = &phdr[i]; 422 | if (p->type != PT_LOAD) { 423 | continue; 424 | } 425 | 426 | // Page align addresses. 427 | uint64_t addr_start = p->vaddr & ~(PAGE_SIZE - 1); 428 | uint64_t addr_end = (p->vaddr + p->memsz + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1); 429 | uint64_t off = p->offset & ~(PAGE_SIZE - 1); 430 | 431 | // Compute segment permissions. 432 | uint32_t prot = (p->flags & PF_X ? PROT_EXEC : 0) | 433 | (p->flags & PF_R ? PROT_READ : 0) | 434 | (p->flags & PF_W ? PROT_WRITE : 0); 435 | 436 | // Mmap single `PT_LOAD` segment. 437 | mmap(base + addr_start, addr_end - addr_start, prot, MAP_PRIVATE | MAP_FIXED, fd, off); 438 | 439 | // Initialize trailing length (no allocated in ELF file). 440 | if (p->memsz > p->filesz) { 441 | memset(base + p->vaddr + p->filesz, 0 /* byte */, p->memsz - p->filesz /*len*/); 442 | } 443 | } 444 | ``` 445 | 446 | With that the shared library dependency is mapped in to the virtual address 447 | space of the user program. The last step is to decode the `.dynamic` section 448 | and initialize the `dso` structure. This is the same as already done for the 449 | user program above and details can be seen in the implementation in 450 | [map_dependency - dynld.c](./dynld.c). 451 | 452 | ### (3) Resolve relocations 453 | 454 | After mapping the shared library the next step is to resolve relocations. 455 | This is the process of resolving references to symbols to actual addresses. For 456 | shared libraries this must be done at runtime rather than static link time as 457 | the `base address` of a shared library is only known at runtime. 458 | 459 | One central structure for resolving relocations is the `LinkMap`. This is a 460 | linked list of `dso` objects which defines the order in which `dso` objects are 461 | used when performing symbol lookup. 462 | 463 | ```c 464 | typedef struct LinkMap { 465 | const Dso* dso; // Pointer to Dso list object. 466 | const struct LinkMap* next; // Pointer to next LinkMap entry ('0' terminates the list). 467 | } LinkMap; 468 | ``` 469 | 470 | In this implementation the `LinkMap` is setup as follows `main -> libgreet.so`, 471 | meaning that symbols are first looked up in `main` and only if they are not 472 | found, `libgreet.so` will be searched. 473 | ```c 474 | void dl_entry(const uint64_t* prctx) { 475 | ... 476 | const LinkMap map_lib = {.dso = &dso_lib, .next = 0}; 477 | const LinkMap map_prog = {.dso = &dso_prog, .next = &map_lib}; 478 | ``` 479 | 480 | With the `LinkMap` setup the `dynld.so` can start processing relocations of the 481 | main program and the shared library. The dynamic linker will process the 482 | following two relocation tables for all `dso` objects on startup: 483 | - `DT_RELA`: Relocations that **must** be resolved during startup. 484 | - `DT_JMPREL`: Relocations associated with the procedure linkage table (those 485 | could be resolved lazily during runtime, but here they are directly resolved 486 | during startup). 487 | 488 | ```c 489 | static void resolve_relocs(const Dso* dso, const LinkMap* map) { 490 | for (unsigned long relocidx = 0; relocidx < (dso->dynamic[DT_RELASZ] / sizeof(Elf64Rela)); ++relocidx) { 491 | const Elf64Rela* reloc = get_reloca(dso, relocidx); 492 | resolve_reloc(dso, map, reloc); 493 | } 494 | 495 | for (unsigned long relocidx = 0; relocidx < (dso->dynamic[DT_PLTRELSZ] / sizeof(Elf64Rela)); ++relocidx) { 496 | const Elf64Rela* reloc = get_pltreloca(dso, relocidx); 497 | resolve_reloc(dso, map, reloc); 498 | } 499 | } 500 | ``` 501 | 502 | The x86_64 SystemV ABI states that x86_64 only uses `RELA` relocation entries, 503 | which are defined as: 504 | ```c 505 | typedef struct { 506 | uint64_t offset; // Virtual address of the storage unit affected by the relocation. 507 | uint64_t info; // Symbol table index + relocation type. 508 | int64_t addend; // Constant value used to compute the relocation value. 509 | } Elf64Rela; 510 | ``` 511 | So each relocation entry provides the following information required to perform 512 | the relocation 513 | - Virtual address of the storage unit that is affected by the relocation. This 514 | is the address in memory where the actual address of the resolved symbol will 515 | be stored to. It is encoded in the `Elf64Rela->offset` field. 516 | - The symbol that needs to be looked up to resolve the relocation. The 517 | **upper** 32 bit of the `Elf64Rela->info` encode the index into the symbol 518 | table. 519 | - The relocation type which describes how the relocation should be performed in 520 | detail. It is encoded in the **lower** 32 bit of the `Elf64Rela->info` field. 521 | 522 | The x86_64 SystemV ABI defines many relocation types. As an example, the 523 | following two sub-sections will discuss the relocation types 524 | `R_X86_64_JUMP_SLOT` and `R_X86_64_COPY`. 525 | 526 | #### Example: Resolving `R_X86_64_JUMP_SLOT` relocation from `DT_JMPREL` table 527 | 528 | Relocation of type `R_X86_64_JUMP_SLOT` are used for entries related to the 529 | `procedure linkage table (PLT)` which is used for function calls between `dso` 530 | objects. This can be seen here, as the main program calls for example the 531 | `get_greet` function provided by the `libgreet.so` shared library which creates 532 | such a relocation entry. 533 | ```bash 534 | > readelf -r main libgreet.so 535 | ... 536 | 537 | Relocation section '.rela.plt' at offset 0x490 contains 2 entries: 538 | Offset Info Type Sym. Value Sym. Name + Addend 539 | 000000404018 000200000007 R_X86_64_JUMP_SLO 0000000000000000 get_greet + 0 540 | 000000404020 000400000007 R_X86_64_JUMP_SLO 0000000000000000 get_greet2 + 0 541 | ``` 542 | 543 | To resolve relocations of this type the following steps need to be performed: 544 | 1. Extract the name of the symbol from the relocation entry. 545 | 1. Find the address of the symbol by walking the `LinkMap` and searching for 546 | the symbol. 547 | 1. Patch the affected address of the relocation entry with the address of the 548 | symbol. 549 | 550 | The code block below shows a simplified version of the `resolve_reloc` function 551 | which only shows lines that are important for handling relocations of type. 552 | `R_X86_64_JUMP_SLOT`. 553 | ```c 554 | static void resolve_reloc(const Dso* dso, const LinkMap* map, const Elf64Rela* reloc) { 555 | // Get symbol information. 556 | const int symidx = ELF64_R_SYM(reloc->info); 557 | const Elf64Sym* sym = get_sym(dso, symidx); 558 | const char* symname = get_str(dso, sym->name); 559 | 560 | // Get relocation type. 561 | const unsigned reloctype = ELF64_R_TYPE(reloc->info); 562 | // assume reloctype == R_X86_64_JUMP_SLOT 563 | 564 | // Lookup address of symbol. 565 | void* symaddr = 0; 566 | for (const LinkMap* lmap = map->next; lmap && symaddr == 0; lmap = lmap->next) { 567 | symaddr = lookup_sym(lmap->dso, symname); 568 | } 569 | 570 | // Patch address affected by the relocation. 571 | *(uint64_t*)(dso->base + reloc->offset) = (uint64_t)symaddr; 572 | } 573 | ``` 574 | > The full implementation of the `resolve_reloc` function can be reviewed in 575 | > [resolve_reloc - dynld.c](./dynld.c). 576 | 577 | #### Example: Resolving `R_X86_64_COPY` relocation from `DT_RELA` table 578 | 579 | Relocations of type `R_X86_64_COPY` are used in the main program when referring 580 | to an external object provided by a shared library, as for example a global 581 | variable. Here the main program makes use the global variable `extern int 582 | gCalled;` defined in the `libgreet.so` which creates relocations as shown 583 | in the `readelf` dump below. 584 | ```bash 585 | > readelf -r main libgreet.so 586 | 587 | File: main 588 | 589 | Relocation section '.rela.dyn' at offset 0x478 contains 1 entry: 590 | Offset Info Type Sym. Value Sym. Name + Addend 591 | 000000404028 000300000005 R_X86_64_COPY 0000000000404028 gCalled + 0 592 | 593 | ... 594 | 595 | File: libgreet.so 596 | 597 | Relocation section '.rela.dyn' at offset 0x3f0 contains 3 entries: 598 | Offset Info Type Sym. Value Sym. Name + Addend 599 | ... 600 | 000000003ff8 000400000006 R_X86_64_GLOB_DAT 0000000000004020 gCalled + 0 601 | 602 | ... 603 | ``` 604 | 605 | For relocations of this type, the static linker allocates space for the 606 | external symbol in the main programs `.bss` sections. 607 | ```bash 608 | > objdump -M intel -d -j .bss main 609 | 610 | main: file format elf64-x86-64 611 | 612 | Disassembly of section .bss: 613 | 614 | 0000000000404028 : 615 | 404028: 00 00 00 00 616 | ``` 617 | 618 | Any reference to the symbol from within the main program is directly resolved 619 | during static link time into the `.bss` section. 620 | ```bash 621 | > objdump -M intel -d main 622 | 623 | main: file format elf64-x86-64 624 | 625 | Disassembly of section .text: 626 | 627 | 0000000000401030 <_start>: 628 | ... 629 | 401088: 8b 05 9a 2f 00 00 mov eax,DWORD PTR [rip+0x2f9a] # 404028 630 | ... 631 | ``` 632 | 633 | The `R_X86_64_COPY` relocation instructs the dynamic linker now to copy the 634 | initial value from the shared library that provides it into the allocated space 635 | in the main programs `.bss` section. 636 | 637 | Shared libraries on the other hand that also reference the same symbol will go 638 | though a `GOT` entry that is patched by the dynamic linker to point to the 639 | location in the `.bss` section of the main program. 640 | Below this can be seen by the `mov` instruction at address `1024` that the 641 | relative address `3ff8` is dereferenced, which is the GOT entry for `gCalled`, 642 | to get the address of `gCalled`. The next instruction at `102b` then loads the 643 | value of `gCalled` iteself. In the `readelf` dump above it can be seen that 644 | there is a relocation of type `R_X86_64_GLOB_DAT` for symbol `gCalled` 645 | affecting the relative address `3ff8` in the shared library. 646 | ```bash 647 | > objdump -M intel -d -j .text -j .got libgreet.so 648 | 649 | libgreet.so: file format elf64-x86-64 650 | 651 | Disassembly of section .text: 652 | 653 | 0000000000001020 : 654 | 1020: 55 push rbp 655 | 1021: 48 89 e5 mov rbp,rsp 656 | 1024: 48 8b 05 cd 2f 00 00 mov rax,QWORD PTR [rip+0x2fcd] # 3ff8 657 | 102b: 8b 00 mov eax,DWORD PTR [rax] # load gCalled 658 | ... 659 | 660 | Disassembly of section .got: 661 | 662 | 0000000000003ff8 <.got>: 663 | ... 664 | ``` 665 | 666 | The following figure visualizes the described layout above in some more detail. 667 | ```text 668 | libso 669 | +-----------+ 670 | | .text | 671 | main prog | | ref 672 | +-----------+ | ... [foo] |--+ 673 | | .text | R_X86_64_GLOB_DAT | | | 674 | ref | | Patch address of +-----------+ | 675 | +--| ... [foo] | foo in .got. | .got | | 676 | | | | +------------------>| foo: |<-+ 677 | | +-----------+ | | | 678 | | | .bss | | +-----------+ 679 | | | | / | .data | 680 | +->| foo: ... |<--------------------| foo: ... | 681 | | | R_X86_64_COPY | | 682 | +-----------+ Copy initial value. +-----------+ 683 | ``` 684 | 685 | To resolve relocations of type `R_X86_64_COPY` the following steps need to be 686 | performed: 687 | 1. Extract the name of the symbol from the relocation entry. 688 | 1. Find the address of the symbol by walking the `LinkMap` and searching for 689 | the symbol and excluding the symbol table of the main program `dso`. 690 | 1. Copy over the initial value of the symbol into the affected address of the 691 | relocation entry (`.bss` section of the main program). 692 | 693 | The code block below shows a simplified version of the `resolve_reloc` function 694 | which only shows lines that are important for handling relocations of type. 695 | ```c 696 | static void resolve_reloc(const Dso* dso, const LinkMap* map, const Elf64Rela* reloc) { 697 | // Get symbol information. 698 | const int symidx = ELF64_R_SYM(reloc->info); 699 | const Elf64Sym* sym = get_sym(dso, symidx); 700 | const char* symname = get_str(dso, sym->name); 701 | 702 | // Get relocation type. 703 | const unsigned reloctype = ELF64_R_TYPE(reloc->info); 704 | // assume reloctype == R_X86_64_COPY 705 | 706 | // Lookup address of symbol. 707 | void* symaddr = 0; 708 | for (const LinkMap* lmap = (reloctype == R_X86_64_COPY ? map->next : map); lmap && symaddr == 0; lmap = lmap->next) { 709 | symaddr = lookup_sym(lmap->dso, symname); 710 | } 711 | 712 | // Copy initial value of variable into address affected by the relocation. 713 | memcpy(dso->base + reloc->offset, (void*)symaddr, sym->size); 714 | } 715 | ``` 716 | > The full implementation of the `resolve_reloc` function can be reviewed in 717 | > [resolve_reloc - dynld.c](./dynld.c). 718 | 719 | ### (4) Run `init` functions 720 | 721 | The next step before transferring control to the main program is to run all the 722 | `init` functions for the `dso` objects. Examples for those are global 723 | `constructors`. 724 | ```c 725 | typedef void (*initfptr)(); 726 | 727 | static void init(const Dso* dso) { 728 | if (dso->dynamic[DT_INIT]) { 729 | initfptr* fn = (initfptr*)(dso->base + dso->dynamic[DT_INIT]); 730 | (*fn)(); 731 | } 732 | 733 | size_t nfns = dso->dynamic[DT_INIT_ARRAYSZ] / sizeof(initfptr); 734 | initfptr* fns = (initfptr*)(dso->base + dso->dynamic[DT_INIT_ARRAY]); 735 | while (nfns--) { 736 | (*fns++)(); 737 | } 738 | } 739 | 740 | void dl_entry(const uint64_t* prctx) { 741 | ... 742 | // Initialize library. 743 | init(&dso_lib); 744 | // Initialize main program. 745 | init(&dso_prog); 746 | ... 747 | } 748 | ``` 749 | 750 | ### (5) Run the user program 751 | 752 | At that point the execution environment is setup and control can be transferred 753 | from the dynamic linker to the main program. 754 | ```c 755 | void dl_entry(const uint64_t* prctx) { 756 | ... 757 | // Transfer control to user program. 758 | dso_prog.entry(); 759 | ... 760 | } 761 | ``` 762 | 763 | ### (6) Run `fini` functions 764 | 765 | After the main program returned and before terminating the process all the 766 | `fini` functions for the `dso` objects are executed. Examples for those are 767 | global `destructors`. 768 | ```c 769 | typedef void (*finifptr)(); 770 | 771 | static void fini(const Dso* dso) { 772 | size_t nfns = dso->dynamic[DT_FINI_ARRAYSZ] / sizeof(finifptr); 773 | finifptr* fns = (finifptr*)(dso->base + dso->dynamic[DT_FINI_ARRAY]) + nfns /* reverse destruction order */; 774 | while (nfns--) { 775 | (*--fns)(); 776 | } 777 | 778 | if (dso->dynamic[DT_FINI]) { 779 | finifptr* fn = (finifptr*)(dso->base + dso->dynamic[DT_FINI]); 780 | (*fn)(); 781 | } 782 | } 783 | 784 | void dl_entry(const uint64_t* prctx) { 785 | ... 786 | // Finalize main program. 787 | fini(&dso_prog); 788 | // Finalize library. 789 | fini(&dso_lib); 790 | ... 791 | } 792 | ``` 793 | 794 | [gcc-fn-attributes]: https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#Common-Function-Attributes 795 | --------------------------------------------------------------------------------