├── .clang-format ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── arch ├── aarch64.h ├── arm.h └── x86_64.h ├── lib-support.h ├── loader.c ├── test_lib.c └── test_loader.c /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Chromium 2 | Language: Cpp 3 | MaxEmptyLinesToKeep: 3 4 | AllowShortIfStatementsOnASingleLine: false 5 | AllowShortLoopsOnASingleLine: false 6 | DerivePointerAlignment: false 7 | PointerAlignment: Right 8 | TabWidth: 4 9 | UseTab: Never 10 | IndentWidth: 4 11 | BreakBeforeBraces: Linux 12 | AccessModifierOffset: -4 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out/ 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | min-dl is freely redistributable under the two-clause BSD License: 2 | 3 | Copyright (C) 2016, 2018 National Cheng Kung University, Taiwan. 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions 8 | are met: 9 | 10 | 1. Redistributions of source code must retain the above copyright notice, 11 | this list of conditions and the following disclaimer. 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 19 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE 20 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 26 | THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CROSS_COMPILE_SUFFIX ?= -linux-gnueabi 2 | CROSS_COMPILE ?= 3 | CC = $(CROSS_COMPILE)gcc 4 | CFLAGS = -std=gnu99 -Wall -Werror -g -D_GNU_SOURCE 5 | CFLAGS += -DPROG_HEADER=prog_header 6 | 7 | OUT = out 8 | 9 | ARCH = $(basename $(notdir $(wildcard arch/*.h))) 10 | CHECK_ARCH = $(addprefix check_, $(ARCH)) 11 | CHECK_CC_ARCH = $(addprefix check_cc_, $(ARCH)) 12 | BIN = $(OUT) $(OUT)/test_lib.so $(OUT)/loader 13 | all: $(BIN) 14 | 15 | $(OUT): 16 | @mkdir -p $(OUT) 17 | 18 | $(OUT)/test_lib.o: test_lib.c 19 | $(CC) $(CFLAGS) -fvisibility=hidden -shared -fPIC -c $< \ 20 | -o $@ -MMD -MF $@.d 21 | 22 | $(OUT)/test_lib.so: $(OUT)/test_lib.o 23 | $(CC) -shared -Wl,--entry=prog_header -Wl,-z,defs -nostdlib \ 24 | $< -o $@ 25 | 26 | $(OUT)/%.o: %.c 27 | $(CC) $(CFLAGS) -o $@ -MMD -MF $@.d -c $< 28 | 29 | LOADER_OBJS = $(OUT)/loader.o $(OUT)/test_loader.o 30 | $(OUT)/loader: $(LOADER_OBJS) 31 | $(CC) -o $@ $(LOADER_OBJS) 32 | 33 | $(CHECK_CC_ARCH):: 34 | @echo "Check cross compiler CROSS_COMPILE_SUFFIX=$(CROSS_COMPILE_SUFFIX) exist or not" 35 | @echo "If failed, please specify CROSS_COMPILE_SUFFIX" 36 | @which $(patsubst check_cc_%,%,$@)$(CROSS_COMPILE_SUFFIX)-gcc 37 | 38 | # The old version ld (< 2.28) will corrupt the global variable array which 39 | # contains another global variables with -shared option involved. 40 | # For example, func_table will contain NULL after linking test_lib.so. 41 | check_cc_aarch64:: 42 | @$(eval LD_VERSION=$(shell echo `aarch64$(CROSS_COMPILE_SUFFIX)-ld -v | grep -oE '[^ ]+$$'`)) 43 | @$(eval LD_VERSION=$(shell echo $(LD_VERSION) | awk -F "." '{print $$1$$2}')) 44 | @if [ $(LD_VERSION) -lt 228 ]; then \ 45 | echo "Error: aarch64$(CROSS_COMPILE_SUFFIX)-ld version must >= 2.28 in AARCH64."; \ 46 | return 1;\ 47 | fi; 48 | 49 | $(ARCH): % : check_cc_% 50 | @make CROSS_COMPILE=$@$(CROSS_COMPILE_SUFFIX)- all 51 | 52 | $(CHECK_ARCH): check_% : % 53 | @(cd $(OUT) && qemu-$* -L /usr/$*$(CROSS_COMPILE_SUFFIX)/ ./loader) 54 | 55 | check: $(BIN) 56 | @(cd $(OUT) && ./loader) 57 | 58 | clean: 59 | rm -rf $(OUT) 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # min-dl: minimal dynamic linker implementation 2 | 3 | To support dynamic linking, each ELF shared libary and each executable that 4 | uses shared libraries has a Procedure Linkage Table (PLT), which adds a level 5 | of indirection for function calls analogous to that provided by the GOT for 6 | data. The PLT also permits "lazy evaluation", that is, not resolving 7 | procedure addresses until they are called for the first time. 8 | 9 | Since the PLT tends to have a lot more entries than the GOT, and most of the 10 | routines will never be called in any given program, that can both speed 11 | startup and save considerable time overall. 12 | 13 | `min-dl` introduces a straightforward way to load the specified shared 14 | objects and then perform the necessary relocations, including the shared 15 | objects that the target shared object uses. 16 | 17 | # Licensing 18 | `min-dl` is freely redistributable under the two-clause BSD License. 19 | Use of this source code is governed by a BSD-style license that can be found 20 | in the `LICENSE` file. 21 | -------------------------------------------------------------------------------- /arch/aarch64.h: -------------------------------------------------------------------------------- 1 | #ifndef _MIN_DL_AARCH64_H_ 2 | #define _MIN_DL_AARCH64_H_ 3 | 4 | #include 5 | 6 | #define _PUSH_S(x) str x, [sp, ES_HASH()-16]! 7 | #define _PUSH(x,y) \ 8 | ldr x3, =x \n \ 9 | ldr x2, [x3] \n \ 10 | str x2, [sp, ES_HASH()-16]! 11 | #define _PUSH_IMM(x) \ 12 | mov x0, ES_HASH()x \n \ 13 | str x0, [sp, ES_HASH()-16]! 14 | #define _PUSH_STACK_STATE stp x29, x30, [sp, #-16]! 15 | #define _POP_STACK_STATE ldp x29, x30, [sp] \n \ 16 | add sp, sp, ES_HASH()16 17 | #define _POP_S(x) ldr x, [sp] \n \ 18 | add sp, sp, ES_HASH()16 19 | #define _POP(x,y) ldr x, [y] 20 | #define _JMP_S(x) b x 21 | #define _JMP_REG(x) br x 22 | #define _JMP(x,y) \ 23 | ldr x3, =x \n \ 24 | ldr x2, [x3] \n \ 25 | br x2 26 | #define _CALL(x) bl x 27 | #define REG_IP ip 28 | #define REG_ARG_1 x0 29 | #define REG_ARG_2 x1 30 | #define REG_RET x0 31 | #define LABEL_PREFIX "=" 32 | #define SYS_ADDR_ATTR "quad" 33 | 34 | typedef ElfW(Rela) ElfW_Reloc; 35 | #define ELFW_DT_RELW DT_RELA 36 | #define ELFW_DT_RELWSZ DT_RELASZ 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /arch/arm.h: -------------------------------------------------------------------------------- 1 | #ifndef _MIN_DL_ARM_H_ 2 | #define _MIN_DL_ARM_H_ 3 | 4 | #include 5 | 6 | #define _PUSH_S(x) push x 7 | #define _PUSH(x,y) \ 8 | ldr r3, =x \n \ 9 | ldr r2, [r3] \n \ 10 | push {r2} 11 | #define _PUSH_IMM(x) \ 12 | mov r0, ES_HASH()x \n \ 13 | push {r0} 14 | #define _PUSH_STACK_STATE push {r11, lr} 15 | #define _POP_STACK_STATE pop {r11, lr} 16 | #define _POP_S(x) pop x 17 | #define _POP(x,y) ldr x, [y] 18 | #define _JMP_S(x) b x 19 | #define _JMP_REG(x) bx x 20 | #define _JMP(x,y) \ 21 | ldr r3, =x \n \ 22 | ldr r2, [r3] \n \ 23 | bx r2 24 | #define _CALL(x) bl x 25 | #define REG_IP ip 26 | #define REG_ARG_1 {r0} 27 | #define REG_ARG_2 {r1} 28 | #define REG_RET r0 29 | #define LABEL_PREFIX "=" 30 | #define SYS_ADDR_ATTR "word" 31 | 32 | typedef ElfW(Rel) ElfW_Reloc; 33 | #define ELFW_DT_RELW DT_REL 34 | #define ELFW_DT_RELWSZ DT_RELSZ 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /arch/x86_64.h: -------------------------------------------------------------------------------- 1 | #ifndef _MIN_DL_X86_64_H_ 2 | #define _MIN_DL_X86_64_H_ 3 | 4 | #include 5 | 6 | #define _PUSH_S(x) pushq x 7 | #define _PUSH(x,y) pushq x(y) 8 | #define _PUSH_IMM(x) pushq $##x 9 | #define _PUSH_STACK_STATE 10 | #define _POP_STACK_STATE 11 | #define _POP_S(x) pop x 12 | #define _POP(x,y) pop x 13 | #define _JMP_S(x) jmp x 14 | #define _JMP_REG(x) _JMP_S(x) 15 | #define _JMP(x,y) jmp *x(y) 16 | #define _CALL(x) call x 17 | #define REG_IP %rip 18 | #define REG_ARG_1 %rdi 19 | #define REG_ARG_2 %rsi 20 | #define REG_RET *%rax 21 | #define SYS_ADDR_ATTR "quad" 22 | #define LABEL_PREFIX 23 | 24 | typedef ElfW(Rela) ElfW_Reloc; 25 | #define ELFW_DT_RELW DT_RELA 26 | #define ELFW_DT_RELWSZ DT_RELASZ 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /lib-support.h: -------------------------------------------------------------------------------- 1 | #ifndef _SHARED_H_ 2 | #define _SHARED_H_ 3 | 4 | #include 5 | 6 | #define STR(...) #__VA_ARGS__ 7 | #define XSTR(...) STR(__VA_ARGS__) 8 | #define _ES_HASH # 9 | #define ES_HASH() _ES_HASH 10 | #ifndef ELFW 11 | #define ELFW(type) _ELFW(__ELF_NATIVE_CLASS, type) 12 | #define _ELFW(bits, type) __ELFW(bits, type) 13 | #define __ELFW(bits, type) ELF##bits##_##type 14 | #endif 15 | 16 | #if defined(__x86_64__) 17 | #include "arch/x86_64.h" 18 | #elif defined(__arm__) 19 | #include "arch/arm.h" 20 | #elif defined(__aarch64__) 21 | #include "arch/aarch64.h" 22 | #else 23 | #error "Unsupported architecture" 24 | #endif 25 | 26 | /* 27 | * Every architecture needs to define its own assembly macro with 28 | * prefix '_' in arch/, and matches all asm() blocks where the macro 29 | * will be expanded. 30 | */ 31 | #define PUSH_S(x) XSTR(_PUSH_S(x)) 32 | #define PUSH(x,y) XSTR(_PUSH(x,y)) 33 | #define PUSH_IMM(x) XSTR(_PUSH_IMM(x)) 34 | #define PUSH_STACK_STATE XSTR(_PUSH_STACK_STATE) 35 | #define JMP_S(x) XSTR(_JMP_S(x)) 36 | #define JMP_REG(x) XSTR(_JMP_REG(x)) 37 | #define JMP(x,y) XSTR(_JMP(x,y)) 38 | #define POP_S(x) XSTR(_POP_S(x)) 39 | #define POP(x,y) XSTR(_POP(x,y)) 40 | #define POP_STACK_STATE XSTR(_POP_STACK_STATE) 41 | #define CALL(x) XSTR(_CALL(x)) 42 | 43 | typedef void *(*plt_resolver_t)(void *handle, int import_id); 44 | 45 | struct program_header { 46 | void **plt_trampoline; 47 | void **plt_handle; 48 | void **pltgot; 49 | void *user_info; 50 | }; 51 | 52 | extern void *pltgot_imports[]; 53 | 54 | #define MDL_PLT_BEGIN \ 55 | asm(".pushsection .text,\"ax\", \"progbits\"" "\n" \ 56 | "slowpath_common:" "\n" \ 57 | PUSH(plt_handle, REG_IP) "\n" \ 58 | JMP(plt_trampoline, REG_IP) "\n" \ 59 | ".popsection" /* start of PLTGOT table. */ "\n" \ 60 | ".pushsection .my_pltgot,\"aw\",\"progbits\"" "\n" \ 61 | "pltgot_imports:" "\n" \ 62 | ".popsection" "\n"); 63 | 64 | #define MDL_PLT_ENTRY(number, name) \ 65 | asm(".pushsection .text,\"ax\", \"progbits\"" "\n" \ 66 | #name ":" "\n" \ 67 | JMP(pltgot_ ##name, REG_IP) "\n" \ 68 | "slowpath_" #name ":" "\n" \ 69 | PUSH_IMM(number) "\n" \ 70 | JMP_S(slowpath_common) "\n" \ 71 | ".popsection" /* entry in PLTGOT table */ "\n" \ 72 | ".pushsection .my_pltgot,\"aw\",\"progbits\"" "\n" \ 73 | "pltgot_" #name ":" "\n" \ 74 | "." SYS_ADDR_ATTR " slowpath_" #name "\n" \ 75 | ".popsection" "\n"); 76 | 77 | #define MDL_DEFINE_HEADER(user_info_value) \ 78 | void *plt_trampoline; \ 79 | void *plt_handle; \ 80 | struct program_header PROG_HEADER = { \ 81 | .plt_trampoline = &plt_trampoline, \ 82 | .plt_handle = &plt_handle, \ 83 | .pltgot = pltgot_imports, \ 84 | .user_info = user_info_value, \ 85 | }; 86 | 87 | typedef struct __DLoader_Internal *dloader_p; 88 | extern struct __DLoader_API__ { 89 | dloader_p (*load)(const char *filename); 90 | void *(*get_info)(dloader_p); 91 | void (*set_plt_resolver)(dloader_p, plt_resolver_t, void *handle); 92 | void (*set_plt_entry)(dloader_p, int import_id, void *func); 93 | } DLoader; 94 | 95 | #endif 96 | -------------------------------------------------------------------------------- /loader.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "lib-support.h" 15 | 16 | #define MAX_PHNUM 12 17 | 18 | #define ELFW_R_TYPE(x) ELFW(R_TYPE)(x) 19 | #define ELFW_R_SYM(x) ELFW(R_SYM)(x) 20 | 21 | struct __DLoader_Internal { 22 | uintptr_t load_bias; 23 | void *entry; 24 | ElfW(Dyn) *pt_dynamic; 25 | 26 | void **dt_pltgot; 27 | ElfW_Reloc *dt_jmprel; 28 | size_t plt_entries; 29 | 30 | plt_resolver_t user_plt_resolver; 31 | void *user_plt_resolver_handle; 32 | }; 33 | 34 | /* 35 | * In recent glibc, even simple functions like memset and strlen can 36 | * depend on complex startup code, because they are defined using 37 | * STT_GNU_IFUNC. 38 | */ 39 | static inline 40 | void my_bzero(void *buf, size_t n) 41 | { 42 | char *p = buf; 43 | while (n-- > 0) 44 | *p++ = 0; 45 | } 46 | 47 | static inline 48 | size_t my_strlen(const char *s) 49 | { 50 | size_t n = 0; 51 | while (*s++ != '\0') 52 | ++n; 53 | return n; 54 | } 55 | 56 | /* 57 | * We're avoiding libc, so no printf. The only nontrivial thing we need 58 | * is rendering numbers, which is, in fact, pretty trivial. 59 | * bufsz of course must be enough to hold INT_MIN in decimal. 60 | */ 61 | static void iov_int_string(int value, struct iovec *iov, 62 | char *buf, size_t bufsz) 63 | { 64 | static const char * const lookup = "9876543210123456789" + 9; 65 | char *p = &buf[bufsz]; 66 | int negative = value < 0; 67 | do { 68 | --p; 69 | *p = lookup[value % 10]; 70 | value /= 10; 71 | } while (value != 0); 72 | if (negative) 73 | *--p = '-'; 74 | iov->iov_base = p; 75 | iov->iov_len = &buf[bufsz] - p; 76 | } 77 | 78 | #define STRING_IOV(string_constant, cond) \ 79 | { (void *) string_constant, cond ? (sizeof(string_constant) - 1) : 0 } 80 | 81 | __attribute__((noreturn)) 82 | static void fail(const char *filename, const char *message, 83 | const char *item, int value) 84 | { 85 | char valbuf[32]; 86 | struct iovec iov[] = { 87 | STRING_IOV("[loader] ", 1), 88 | {(void *) filename, my_strlen(filename)}, 89 | STRING_IOV(": ", 1), 90 | {(void *) message, my_strlen(message)}, 91 | {(void *) item, !item ? 0 : my_strlen(item)}, 92 | STRING_IOV("=", !item), 93 | {NULL, 0}, 94 | {"\n", 1}, 95 | }; 96 | const int niov = sizeof(iov) / sizeof(iov[0]); 97 | if (item != NULL) 98 | iov_int_string(value, &iov[6], valbuf, sizeof(valbuf)); 99 | 100 | writev(2, iov, niov); 101 | exit(2); 102 | } 103 | 104 | static int prot_from_phdr(const ElfW(Phdr) *phdr) 105 | { 106 | int prot = 0; 107 | if (phdr->p_flags & PF_R) 108 | prot |= PROT_READ; 109 | if (phdr->p_flags & PF_W) 110 | prot |= PROT_WRITE; 111 | if (phdr->p_flags & PF_X) 112 | prot |= PROT_EXEC; 113 | return prot; 114 | } 115 | 116 | static inline 117 | uintptr_t round_up(uintptr_t value, uintptr_t size) 118 | { 119 | return (value + size - 1) & -size; 120 | } 121 | 122 | static inline 123 | uintptr_t round_down(uintptr_t value, uintptr_t size) 124 | { 125 | return value & -size; 126 | } 127 | 128 | /* 129 | * Handle the "bss" portion of a segment, where the memory size 130 | * exceeds the file size and we zero-fill the difference. 131 | * For any whole pages in this region, we over-map anonymous pages. 132 | * For the sub-page remainder, we zero-fill bytes directly. 133 | */ 134 | static void handle_bss(const ElfW(Phdr) *ph, ElfW(Addr) load_bias, 135 | size_t pagesize) 136 | { 137 | if (ph->p_memsz > ph->p_filesz) { 138 | ElfW(Addr) file_end = ph->p_vaddr + load_bias + ph->p_filesz; 139 | ElfW(Addr) file_page_end = round_up(file_end, pagesize); 140 | ElfW(Addr) page_end = 141 | round_up(ph->p_vaddr + load_bias + ph->p_memsz, pagesize); 142 | if (page_end > file_page_end) 143 | mmap((void *) file_page_end, 144 | page_end - file_page_end, prot_from_phdr(ph), 145 | MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0); 146 | if (file_page_end > file_end && (ph->p_flags & PF_W)) 147 | my_bzero((void *) file_end, file_page_end - file_end); 148 | } 149 | } 150 | 151 | ElfW(Word) get_dynamic_entry(ElfW(Dyn) *dynamic, int field) 152 | { 153 | for (; dynamic->d_tag != DT_NULL; dynamic++) { 154 | if (dynamic->d_tag == field) 155 | return dynamic->d_un.d_val; 156 | } 157 | return 0; 158 | } 159 | 160 | /* 161 | * Use pre-defined macro in arch/ to support different 162 | * system-specific assembly, see lib-support.h. 163 | */ 164 | void plt_trampoline(); 165 | asm(".pushsection .text,\"ax\",\"progbits\"" "\n" 166 | "plt_trampoline:" "\n" 167 | POP_S(REG_ARG_1) /* Argument 1 */ "\n" 168 | POP_S(REG_ARG_2) /* Argument 2 */ "\n" 169 | PUSH_STACK_STATE "\n" 170 | CALL(system_plt_resolver) "\n" 171 | POP_STACK_STATE "\n" 172 | JMP_REG(REG_RET) "\n" 173 | ".popsection" "\n"); 174 | 175 | void *system_plt_resolver(dloader_p o, int import_id) 176 | { 177 | return o->user_plt_resolver(o->user_plt_resolver_handle, import_id); 178 | } 179 | 180 | dloader_p api_load(const char *filename) 181 | { 182 | size_t pagesize = 0x1000; 183 | int fd = open(filename, O_RDONLY); 184 | ElfW(Ehdr) ehdr; 185 | pread(fd, &ehdr, sizeof(ehdr), 0); 186 | 187 | if (ehdr.e_ident[EI_MAG0] != ELFMAG0 || 188 | ehdr.e_ident[EI_MAG1] != ELFMAG1 || 189 | ehdr.e_ident[EI_MAG2] != ELFMAG2 || 190 | ehdr.e_ident[EI_MAG3] != ELFMAG3 || 191 | ehdr.e_version != EV_CURRENT || ehdr.e_ehsize != sizeof(ehdr) || 192 | ehdr.e_phentsize != sizeof(ElfW(Phdr))) 193 | fail(filename, "File has no valid ELF header!", NULL, 0); 194 | 195 | switch (ehdr.e_machine) { 196 | case EM_X86_64: 197 | case EM_ARM: 198 | case EM_AARCH64: 199 | break; 200 | default: 201 | fail(filename, "ELF file has wrong architecture! ", 202 | "e_machine", ehdr.e_machine); 203 | break; 204 | } 205 | 206 | ElfW(Phdr) phdr[MAX_PHNUM]; 207 | if (ehdr.e_phnum > sizeof(phdr) / sizeof(phdr[0]) || ehdr.e_phnum < 1) 208 | fail(filename, "ELF file has unreasonable ", "e_phnum", ehdr.e_phnum); 209 | 210 | if (ehdr.e_type != ET_DYN) 211 | fail(filename, "ELF file not ET_DYN! ", "e_type", ehdr.e_type); 212 | 213 | pread(fd, phdr, sizeof(phdr[0]) * ehdr.e_phnum, ehdr.e_phoff); 214 | 215 | size_t i = 0; 216 | while (i < ehdr.e_phnum && phdr[i].p_type != PT_LOAD) 217 | ++i; 218 | if (i == ehdr.e_phnum) 219 | fail(filename, "ELF file has no PT_LOAD header!", NULL, 0); 220 | 221 | /* 222 | * ELF requires that PT_LOAD segments be in ascending order of p_vaddr. 223 | * Find the last one to calculate the whole address span of the image. 224 | */ 225 | const ElfW(Phdr) *first_load = &phdr[i]; 226 | const ElfW(Phdr) *last_load = &phdr[ehdr.e_phnum - 1]; 227 | while (last_load > first_load && last_load->p_type != PT_LOAD) 228 | --last_load; 229 | 230 | /* 231 | * Total memory size of phdr between first and last PT_LOAD. 232 | */ 233 | size_t span = last_load->p_vaddr + last_load->p_memsz - first_load->p_vaddr; 234 | 235 | /* 236 | * Map the first segment and reserve the space used for the rest and 237 | * for holes between segments. 238 | */ 239 | const uintptr_t mapping = 240 | (uintptr_t) mmap((void *) round_down(first_load->p_vaddr, pagesize), 241 | span, prot_from_phdr(first_load), MAP_PRIVATE, fd, 242 | round_down(first_load->p_offset, pagesize)); 243 | 244 | /* 245 | * Mapping will not always equal to round_down(first_load->p_vaddr, pagesize). 246 | */ 247 | const ElfW(Addr) load_bias = 248 | mapping - round_down(first_load->p_vaddr, pagesize); 249 | 250 | if (first_load->p_offset > ehdr.e_phoff || 251 | first_load->p_filesz < 252 | ehdr.e_phoff + (ehdr.e_phnum * sizeof(ElfW(Phdr)))) 253 | fail(filename, "First load segment of ELF does not contain phdrs!", 254 | NULL, 0); 255 | 256 | const ElfW(Phdr) *ro_load = NULL; 257 | if (!(first_load->p_flags & PF_W)) 258 | ro_load = first_load; 259 | 260 | handle_bss(first_load, load_bias, pagesize); 261 | 262 | ElfW(Addr) last_end = first_load->p_vaddr + load_bias + 263 | first_load->p_memsz; 264 | 265 | /* Map the remaining segments, and protect any holes between them. */ 266 | for (const ElfW(Phdr) *ph = first_load + 1; ph <= last_load; ++ph) { 267 | if (ph->p_type == PT_LOAD) { 268 | ElfW(Addr) last_page_end = round_up(last_end, pagesize); 269 | 270 | last_end = ph->p_vaddr + load_bias + ph->p_memsz; 271 | ElfW(Addr) start = round_down(ph->p_vaddr + load_bias, pagesize); 272 | ElfW(Addr) end = round_up(last_end, pagesize); 273 | 274 | if (start > last_page_end) 275 | mprotect((void *) last_page_end, 276 | start - last_page_end, PROT_NONE); 277 | 278 | mmap((void *) start, end - start, 279 | prot_from_phdr(ph), MAP_PRIVATE | MAP_FIXED, fd, 280 | round_down(ph->p_offset, pagesize)); 281 | 282 | handle_bss(ph, load_bias, pagesize); 283 | if (!(ph->p_flags & PF_W) && !ro_load) 284 | ro_load = ph; 285 | } 286 | } 287 | 288 | /* Find PT_DYNAMIC header. */ 289 | ElfW(Dyn) *dynamic = NULL; 290 | for (i = 0; i < ehdr.e_phnum; ++i) { 291 | if (phdr[i].p_type == PT_DYNAMIC) { 292 | assert(dynamic == NULL); 293 | dynamic = (ElfW(Dyn) *) (load_bias + phdr[i].p_vaddr); 294 | } 295 | } 296 | assert(dynamic != NULL); 297 | 298 | ElfW(Addr) ro_start = ro_load->p_offset + load_bias; 299 | ElfW(Addr) ro_end = ro_start + ro_load->p_memsz; 300 | ElfW_Reloc *relocs = 301 | (ElfW_Reloc *)(load_bias + get_dynamic_entry(dynamic, ELFW_DT_RELW)); 302 | size_t relocs_size = get_dynamic_entry(dynamic, ELFW_DT_RELWSZ); 303 | for (i = 0; i < relocs_size / sizeof(ElfW_Reloc); i++) { 304 | ElfW_Reloc *reloc = &relocs[i]; 305 | int reloc_type = ELFW_R_TYPE(reloc->r_info); 306 | switch (reloc_type) { 307 | case R_X86_64_RELATIVE: 308 | case R_ARM_RELATIVE: 309 | case R_AARCH64_RELATIVE: 310 | { 311 | ElfW(Addr) *addr = (ElfW(Addr) *)(load_bias + reloc->r_offset); 312 | /* 313 | * If addr loactes in read-only PT_LOAD section, i.e., .text, then 314 | * we give the memory fragment WRITE permission during relocating 315 | * its address. Reset its access permission after relocation to 316 | * avoid some secure issue. 317 | */ 318 | if ((intptr_t) addr < ro_end && (intptr_t) addr >= ro_start) { 319 | mprotect((void*) round_down((intptr_t) addr, pagesize), 320 | pagesize, PROT_WRITE); 321 | *addr += load_bias; 322 | mprotect((void*) round_down((intptr_t) addr, pagesize), 323 | pagesize, prot_from_phdr(ro_load)); 324 | } 325 | else 326 | *addr += load_bias; 327 | break; 328 | } 329 | default: 330 | assert(0); 331 | } 332 | } 333 | 334 | dloader_p o = malloc(sizeof(struct __DLoader_Internal)); 335 | assert(o != NULL); 336 | 337 | o->load_bias = load_bias; 338 | o->entry = (void *)(ehdr.e_entry + load_bias); 339 | o->pt_dynamic = dynamic; 340 | o->dt_pltgot = NULL; 341 | o->plt_entries = 0; 342 | uintptr_t pltgot = get_dynamic_entry(dynamic, DT_PLTGOT); 343 | if (pltgot != 0) { 344 | o->dt_pltgot = (void **) (pltgot + load_bias); 345 | o->dt_jmprel = (ElfW_Reloc *) (get_dynamic_entry(dynamic, DT_JMPREL) + 346 | load_bias); 347 | } 348 | 349 | close(fd); 350 | return o; 351 | } 352 | 353 | void *api_get_user_info(dloader_p o) 354 | { 355 | return ((struct program_header *) (o->entry))->user_info; 356 | } 357 | 358 | void api_set_plt_resolver(dloader_p o, plt_resolver_t resolver, void *handle) 359 | { 360 | struct program_header *PROG_HEADER = o->entry; 361 | *PROG_HEADER->plt_trampoline = (void *) plt_trampoline; 362 | *PROG_HEADER->plt_handle = o; 363 | o->user_plt_resolver = resolver; 364 | o->user_plt_resolver_handle = handle; 365 | } 366 | 367 | void api_set_plt_entry(dloader_p o, int import_id, void *func) 368 | { 369 | ((struct program_header *) (o->entry))->pltgot[import_id] = func; 370 | } 371 | 372 | struct __DLoader_API__ DLoader = { 373 | .load = api_load, 374 | .get_info = api_get_user_info, 375 | .set_plt_resolver = api_set_plt_resolver, 376 | .set_plt_entry = api_set_plt_entry, 377 | }; 378 | -------------------------------------------------------------------------------- /test_lib.c: -------------------------------------------------------------------------------- 1 | #include "lib-support.h" 2 | 3 | const char *foo() { return __func__; } 4 | const char *bar() { return __func__; } 5 | 6 | const char *import_func0(); 7 | const char *import_func1(); 8 | 9 | MDL_PLT_BEGIN; 10 | MDL_PLT_ENTRY(0, import_func0); 11 | MDL_PLT_ENTRY(1, import_func1); 12 | 13 | const char *test_import0() 14 | { 15 | return import_func0(); 16 | } 17 | 18 | const char *test_import1() 19 | { 20 | return import_func1(); 21 | } 22 | 23 | void *func_table[] = { 24 | foo, bar, 25 | test_import0, test_import1, 26 | }; 27 | 28 | MDL_DEFINE_HEADER(func_table); 29 | -------------------------------------------------------------------------------- /test_loader.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "lib-support.h" 6 | 7 | const char *test_import0() { return __func__; } 8 | const char *test_import1() { return __func__; } 9 | 10 | static int resolver_call_count = 0; 11 | static int resolver_last_id = -1; 12 | 13 | static void *plt_resolver(void *handle, int import_id) 14 | { 15 | dloader_p o = handle; 16 | printf("resolver called for func #%i\n", import_id); 17 | resolver_call_count++; 18 | resolver_last_id = import_id; 19 | 20 | void *funcs[] = { 21 | (void *) test_import0, (void *) test_import1, 22 | }; 23 | void *func = funcs[import_id]; 24 | DLoader.set_plt_entry(o, import_id, func); 25 | return func; 26 | } 27 | 28 | int main() 29 | { 30 | typedef const char *(*func_t)(void); 31 | 32 | dloader_p o = DLoader.load("test_lib.so"); 33 | void **func_table = DLoader.get_info(o); 34 | 35 | const char *(*func)(void); 36 | const char *result; 37 | 38 | printf("Test exported functions >\n"); 39 | 40 | func = (func_t) func_table[0]; 41 | result = func(); 42 | assert(!strcmp(result, "foo")); 43 | 44 | func = (func_t) func_table[1]; 45 | result = func(); 46 | assert(!strcmp(result, "bar")); 47 | 48 | printf("OK!\n"); 49 | 50 | printf("Test imported functions >\n"); 51 | DLoader.set_plt_resolver(o, plt_resolver, 52 | /* user_plt_resolver_handle */ o); 53 | 54 | func = (func_t) func_table[2]; 55 | result = func(); 56 | assert(!strcmp(result, "test_import0")); 57 | assert(resolver_call_count == 1); 58 | assert(resolver_last_id == 0); 59 | resolver_call_count = 0; 60 | result = func(); 61 | assert(!strcmp(result, "test_import0")); 62 | assert(resolver_call_count == 0); 63 | 64 | func = (func_t) func_table[3]; 65 | result = func(); 66 | assert(!strcmp(result, "test_import1")); 67 | assert(resolver_call_count == 1); 68 | assert(resolver_last_id == 1); 69 | resolver_call_count = 0; 70 | result = func(); 71 | assert(!strcmp(result, "test_import1")); 72 | assert(resolver_call_count == 0); 73 | 74 | printf("OK!\n"); 75 | 76 | return 0; 77 | } 78 | --------------------------------------------------------------------------------