├── example ├── private-strdup.c ├── Makefile └── example.c ├── .hgignore ├── .gitmodules ├── lib └── Makefile ├── Makefile ├── test ├── elfproto-simple │ ├── Makefile │ ├── gdb-script │ └── elfproto-simple.c ├── simple │ └── simple.c └── README ├── src ├── elfproto.h ├── dlbind_internal.h ├── Makefile ├── symhash.h ├── symhash.c ├── libdlbind.c └── elfproto.c ├── include └── dlbind.h └── README /example/private-strdup.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | char *__private_strdup(const char *s) { return strdup(s); } 4 | -------------------------------------------------------------------------------- /.hgignore: -------------------------------------------------------------------------------- 1 | syntax: glob 2 | blah 3 | concept 4 | concept.works 5 | *.a 6 | *.o 7 | *.so 8 | test 9 | example/example 10 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "example/libsystrap"] 2 | path = example/libsystrap 3 | url = https://github.com/stephenrkell/libsystrap.git 4 | -------------------------------------------------------------------------------- /lib/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: default symlinks 2 | 3 | default: symlinks 4 | 5 | symlinks: 6 | ln -sf ../src/libdlbind.a . 7 | 8 | clean: 9 | rm -f libdlbind.a 10 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: default all clean test 2 | 3 | default: all 4 | 5 | all: 6 | $(MAKE) -C src 7 | $(MAKE) -C lib 8 | 9 | clean: 10 | $(MAKE) -C src clean 11 | $(MAKE) -C lib clean 12 | 13 | test: 14 | $(MAKE) -C test 15 | -------------------------------------------------------------------------------- /test/elfproto-simple/Makefile: -------------------------------------------------------------------------------- 1 | LDFLAGS += -L../../src 2 | LDLIBS += -ldlbind -lelf -ldl 3 | CFLAGS += -g -Wextra -I../../include 4 | 5 | default: elfproto-simple 6 | 7 | run: elfproto-simple 8 | gdb --eval-command "source gdb-script" ./elfproto-simple 9 | 10 | clean: 11 | rm -f elfproto-simple 12 | -------------------------------------------------------------------------------- /src/elfproto.h: -------------------------------------------------------------------------------- 1 | #ifndef LIBDLBIND_ELFPROTO_H_ 2 | #define LIBDLBIND_ELFPROTO_H_ 3 | 4 | #include 5 | 6 | #define DT_DLBIND_TEXTBUMP 0x6ffffee0 7 | #define DT_DLBIND_RODATABUMP 0x6ffffee1 8 | #define DT_DLBIND_DATABUMP 0x6ffffee2 9 | #define DT_DLBIND_DYNSTRBUMP 0x6ffffee3 10 | #define DT_DLBIND_DYNSYMBUMP 0x6ffffee4 11 | 12 | #ifndef MAX_SYMS 13 | #define MAX_SYMS 65536 14 | #endif 15 | 16 | #ifndef NBUCKET 17 | #define NBUCKET 2048 18 | #endif 19 | 20 | #ifdef __cplusplus 21 | extern "C" { 22 | #endif 23 | extern size_t _dlbind_elfproto_headerscn_sz; 24 | extern size_t _dlbind_elfproto_memsz; 25 | extern void *_dlbind_elfproto_begin; 26 | #ifdef __cplusplus 27 | } 28 | #endif 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /example/Makefile: -------------------------------------------------------------------------------- 1 | # HACK 2 | include libsystrap/contrib/config.mk 3 | 4 | CFLAGS += -g 5 | 6 | vpath %.c libsystrap/contrib 7 | 8 | example example.o: CFLAGS += -I../include -Ilibsystrap/include -I$(LIBRUNT_DIR)/include 9 | example: LDFLAGS += -fno-lto -L../lib -Llibsystrap/lib -L$(LIBRUNT_DIR)/lib \ 10 | -Wl,--defsym,__wrap___runt_files_metadata_by_addr=__runt_files_metadata_by_addr \ 11 | -Wl,--defsym,__wrap___runt_files_notify_load=__runt_files_notify_load \ 12 | -Wl,--defsym,__private_malloc=malloc -Wl,--defsym,__private_free=free \ 13 | -Wl,--defsym,__private_realloc=realloc 14 | example: LDLIBS += -ldlbind -lsystrap -Wl,--push-state -Wl,-static -lrunt -Wl,--pop-state -ldl 15 | 16 | example: dlmalloc.o private-strdup.o ../lib/libdlbind.a 17 | 18 | .PHONY: default 19 | default: example 20 | 21 | .PHONY: clean 22 | clean: 23 | rm -f example *.o 24 | -------------------------------------------------------------------------------- /src/dlbind_internal.h: -------------------------------------------------------------------------------- 1 | #ifndef DLBIND_INTERNAL_H_ 2 | #define DLBIND_INTERNAL_H_ 3 | 4 | #ifndef TEXT_SZ 5 | #define TEXT_SZ 33554432 6 | #endif 7 | 8 | #ifndef DATA_SZ 9 | #define DATA_SZ 16777216 10 | #endif 11 | 12 | #ifndef RODATA_SZ 13 | #define RODATA_SZ 8388608 14 | #endif 15 | 16 | #ifndef PAGE_SIZE 17 | #define PAGE_SIZE 4096 18 | #endif 19 | 20 | /* FIXME: this shouldn't be a fixed value, for security. 21 | * Instead, randomize it at elfproto instantiation time. 22 | * On 64-bit platforms we can reserve a large vaddr space, 23 | * giving plenty of entropy. On 32-bit platforms we may 24 | * need to do some kind of "emulated W|X" using mremap and 25 | * hysteresis, and/or to whitelist which code is allowed 26 | * to do writes. */ 27 | #ifndef TEXT_WRITABLE_VADDR_DELTA 28 | #define TEXT_WRITABLE_VADDR_DELTA 67108864 29 | #endif 30 | 31 | extern __thread const char *dlbind_open_active_on __attribute__((visibility("hidden"))); 32 | void memcpy_elfproto_to(void *dest) __attribute__((visibility("hidden"))); 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | CC ?= gcc 2 | 3 | LDFLAGS += -L`pwd` -Wl,-R`pwd` 4 | LDLIBS += -ldl -lelf 5 | CFLAGS += -std=c11 -g -fPIC -Wextra #-DHAVE_ELF_SETSHSTRNDX 6 | CFLAGS += -I$(LIBRUNT_BASE)/include 7 | CFLAGS += -I../include 8 | ifneq ($(DEBUG),1) 9 | CFLAGS += -DNDEBUG 10 | endif 11 | CXXFLAGS += -std=gnu++14 -fpermissive 12 | CXXFLAGS += -g3 -fPIC 13 | 14 | CFLAGS += -DFAKE_RELOAD 15 | 16 | CC_DEPS := $(patsubst %.cc,.%.d,$(wildcard *.cc)) 17 | C_DEPS := $(patsubst %.c,.%.d,$(wildcard *.c)) 18 | 19 | DEPS := $(CC_DEPS) $(C_DEPS) 20 | 21 | default: libdlbind.a #test 22 | 23 | .%.d: %.c 24 | $(CC) $(CFLAGS) -c -MM "$<" > "$@" || rm -f "$@" 25 | .%.d: %.cc 26 | $(CXX) $(CXXFLAGS) -c -MM "$<" > "$@" || rm -f "$@" 27 | 28 | -include $(DEPS) 29 | 30 | libdlbind.so: libdlbind.o 31 | $(CC) $(CFLAGS) -shared -o "$@" "$<" 32 | 33 | libdlbind.a: libdlbind.o symhash.o elfproto.o 34 | ar r "$@" $^ 35 | 36 | # Use the static version of libdlbind for now, so we can ltrace -lelf 37 | test: test.c libdlbind.a 38 | $(CC) $(CFLAGS) $(LDFLAGS) -o "$@" "$<" libdlbind.a $(LDLIBS) #/usr/lib/libelf.so.0.8.13 39 | 40 | make clean: 41 | rm -f *.o test libdlbind.so libdlbind.a 42 | -------------------------------------------------------------------------------- /src/symhash.h: -------------------------------------------------------------------------------- 1 | #ifndef LIBDLBIND_SYMHASH_H_ 2 | #define LIBDLBIND_SYMHASH_H_ 3 | 4 | #include 5 | 6 | void 7 | elf64_hash_init( 8 | char *section, /* hash section */ 9 | size_t size, /* hash section size in bytes */ 10 | unsigned nbucket, /* nbucket */ 11 | unsigned nsyms, 12 | Elf64_Sym *symtab /* [nsyms] */, 13 | const char *strtab 14 | ); 15 | 16 | void 17 | elf64_hash_put( 18 | char *section, /* has section */ 19 | size_t size, /* hash section size in bytes */ 20 | unsigned nbucket, /* nbucket -- must match existing section! */ 21 | unsigned nsyms, /* symbol table entry count */ 22 | Elf64_Sym *symtab /* [nsyms] */, /* symbol table */ 23 | const char *strtab, 24 | unsigned symind /* assume this symind was unused previously! */ 25 | ); 26 | 27 | Elf64_Sym * 28 | elf64_hash_get( 29 | char *section, /* has section */ 30 | size_t size, /* hash section size in bytes */ 31 | unsigned nbucket, /* nbucket -- must match existing section! */ 32 | unsigned nsyms, /* symbol table entry count */ 33 | Elf64_Sym *symtab /* [nsyms] */, /* symbol table */ 34 | const char *strtab, 35 | const char *key 36 | ); 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /test/simple/simple.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "dlbind.h" 8 | 9 | int get_42(void) 10 | { 11 | return 42; 12 | } 13 | void end_of_get_42(void) {} 14 | 15 | int main(void) 16 | { 17 | // create libfoo 18 | void *l = dlcreate("foo"); 19 | 20 | // get some memory 21 | size_t len = (char*)end_of_get_42 - (char*)get_42; 22 | const char *alloc = dlalloc(l, len, SHF_EXECINSTR); 23 | assert(alloc); 24 | 25 | // copy our function into it 26 | // get a writable alias of the memory 27 | char *writable_alloc = /*dl_find_alias_with(SHF_READ|SHF_WRITE, alloc, l) */ 28 | (char*)((uintptr_t) alloc + 33554432); // HACK 29 | memcpy(writable_alloc, get_42, len); 30 | 31 | // FIXME: reinstate a realloc call: *reallocate* to the smaller size (was 200) 32 | // dlrealloc(l, alloc, len); 33 | 34 | // FIXME: this test case doesn't work! libdlbind only works within liballocs 35 | // where we can hook open() calls and change MAP_PRIVATE to MAP_SHARED. 36 | // Perhaps we can make libsystrap a dependency of libdlbind? 37 | 38 | // bind it 39 | void *reloaded = dlbind(l, "meaning", (void*)alloc, len, STT_FUNC); 40 | assert(reloaded == l); 41 | 42 | // dlsym our function 43 | int (*func)(void) = (int(*)(void)) dlsym(l, "meaning"); 44 | assert(func); 45 | 46 | // call it 47 | int m = func(); 48 | printf("libdlbind-loaded function returned: %d\n", m); 49 | 50 | // FIXME: reinstate a dldelete call. 51 | // dldelete 52 | // dldelete(l); 53 | 54 | return 0; 55 | } 56 | -------------------------------------------------------------------------------- /test/elfproto-simple/gdb-script: -------------------------------------------------------------------------------- 1 | break main 2 | run 3 | break dlopen 4 | cont 5 | catch syscall open 6 | catch syscall mmap 7 | disable 1 8 | disable 2 9 | cont 10 | set $rsi = ($rsi & ~3) + 2 11 | cont 12 | cont 13 | set $r10 = ($r10 & ~3) + 1 14 | cont 15 | cont 16 | set $r10 = ($r10 & ~3) + 1 17 | cont 18 | cont 19 | set $r10 = ($r10 & ~3) + 1 20 | cont 21 | cont 22 | set $r10 = ($r10 & ~3) + 1 23 | cont 24 | disable 3 25 | disable 4 26 | break dlclose 27 | cont 28 | shell ls -t /run/shm/tmp.dlbind.* | head -n1 | xargs readelf -WesSdD 29 | enable 3 30 | enable 4 31 | cont 32 | set $rsi = ($rsi & ~3) + 2 33 | cont 34 | cont 35 | set $r10 = ($r10 & ~3) + 1 36 | cont 37 | cont 38 | set $r10 = ($r10 & ~3) + 1 39 | cont 40 | cont 41 | set $r10 = ($r10 & ~3) + 1 42 | cont 43 | cont 44 | set $r10 = ($r10 & ~3) + 1 45 | cont 46 | disable 3 47 | disable 4 48 | cont 49 | shell ls -t /run/shm/tmp.dlbind.* | head -n1 | xargs readelf -WesSdD 50 | enable 3 51 | enable 4 52 | cont 53 | set $rsi = ($rsi & ~3) + 2 54 | cont 55 | cont 56 | set $r10 = ($r10 & ~3) + 1 57 | cont 58 | cont 59 | set $r10 = ($r10 & ~3) + 1 60 | cont 61 | cont 62 | set $r10 = ($r10 & ~3) + 1 63 | cont 64 | cont 65 | set $r10 = ($r10 & ~3) + 1 66 | cont 67 | disable 3 68 | disable 4 69 | cont 70 | shell ls -t /run/shm/tmp.dlbind.* | head -n1 | xargs readelf -WesSdD 71 | enable 3 72 | enable 4 73 | cont 74 | set $rsi = ($rsi & ~3) + 2 75 | cont 76 | cont 77 | set $r10 = ($r10 & ~3) + 1 78 | cont 79 | cont 80 | set $r10 = ($r10 & ~3) + 1 81 | cont 82 | cont 83 | set $r10 = ($r10 & ~3) + 1 84 | cont 85 | cont 86 | set $r10 = ($r10 & ~3) + 1 87 | cont 88 | finish 89 | finish 90 | finish 91 | finish 92 | finish 93 | finish 94 | finish 95 | finish 96 | finish 97 | finish 98 | -------------------------------------------------------------------------------- /test/README: -------------------------------------------------------------------------------- 1 | We don't currently have automated tests. 2 | 3 | This is because libdlbind needs syscall trapping to work. We have this in 4 | liballocs, which can successfully use libdlbind, but not yet in a 5 | freestanding libdlbind. 6 | 7 | We use system call trapping to intercept open() when dlopen() is active from 8 | dlcreate(), and change O_RDONLY to O_RDWR. Similarly we changea MAP_PRIVATE 9 | to MAP_SHARED. Unlike a normal DSO, our object is a temporary and we really 10 | want changes to it to be visible on disk. 11 | 12 | The Right Thing is for the dynamic linker to offer the libdlbind API. The 13 | Next-Best Thing is for us to make libsystrap a submodule of libdlbind, and 14 | provide a build that does the necessary trapping/rewriting. 15 | 16 | Is this a hack or a concept? It's more concept than hack. Currently, 17 | debuggers like gdb assume that they can read ELF metadata from binaries on 18 | disk, because it won't have changed in-memory. We do change it, which is why 19 | we need our changes to be reflected on disk, hence MAP_SHARED. We can think 20 | of this as mapping the files in 'persistent image' mode, and that this is 21 | sane because the temporary object is private to our application. In 22 | MAP_PRIVATE, we are also requesting effectively a 'private copy', and the 23 | difference is that that private copy is anonymous. Clearly it *should* have a 24 | name. Any DSO could be opened in 'persistent mode', in which case it is 25 | logically copied to a named temporary file rather than MAP_PRIVATE'ing the 26 | 'prototypical' DSO. 27 | 28 | For this 'temporary' file, maybe a delimited subrange of /proc/nnn/core would 29 | be good enough if we could name that subrange as a file in its own right. 30 | Ideally we would delimit it in terms of allocation names rather than byte 31 | extents. 32 | 33 | Perhaps Oscar-style 'MAP_SHARED for MAP_PRIVATE' would allow us to give a 34 | temporary name to *every* privately-copied file range? i.e. a novel 35 | implementation of MAP_PRIVATE that is backed by a named temporary. By default 36 | we would unlink the temporary, but it could be salvaged via the magic 37 | /proc/pid/fd/nnn symlinks. 38 | -------------------------------------------------------------------------------- /include/dlbind.h: -------------------------------------------------------------------------------- 1 | #ifndef DLBIND_H_ 2 | #define DLBIND_H_ 3 | 4 | #include 5 | #include 6 | 7 | /* For forcing init (for early-startup usage). */ 8 | void __libdlbind_do_init(void); 9 | 10 | /* We extend the libdl interface with calls to dynamically create 11 | * new libraries. */ 12 | 13 | /* Create a new shared library in this address space. */ 14 | void *dlcreate(const char *libname); 15 | 16 | /* The same, but providing an mmap function */ 17 | void *dlcreate_with_mmap(const char *libname, void*(*mmap)(void *, size_t, int, int, int, off_t)); 18 | 19 | /* Allocate a chunk of space in the file. The flags are SHF_* flags. */ 20 | void *dlalloc(void *l, size_t sz, unsigned flags); 21 | 22 | /* Create a new symbol binding within a library created using dlnew(). 23 | * FIXME: maybe by varargs, allow specifying the namespace, calling convention 24 | * and version string. Note that these need only be meaningful for text 25 | * symbols. 26 | * Currently this returns the reloaded handle. We shouldn't push 27 | * reloading onto clients. */ 28 | void *dlbind(void *lib, const char *symname, void *obj, size_t len, Elf64_Word type); 29 | 30 | /* Closes and releases all resources associated with this handle. 31 | * It must have been returned by dlcreate(). */ 32 | // FIXME: this is not implemented. Instead we unlink everything in a destructor 33 | int dldelete(void *); 34 | 35 | /* Lookup a text symbol, accounting for namespace, calling convention 36 | * and version string. */ 37 | void *dldsym(void *handle, const char *symbol, void *namespace, 38 | unsigned long conv, const char *version_string); 39 | 40 | /* The "conv" descriptor has the following encoding. 41 | * LSB: must be 1 -- allows us to extend the encoding 42 | to be specified by a pointer to block, later. 43 | * 4 bits: describing per-call options 44 | * 3 bits * n: describing per-argument options, for up to 45 | 9 arguments (32-bit sytems), or 46 | 19 arguments (64-bit systems). 47 | * 48 | * Think "multiple entry points". 49 | */ 50 | enum argument_options 51 | { 52 | INDIRECT = 1, 53 | APP_CHECKED = 2, /* e.g. nonnull, ... */ 54 | LANGVM_CHECKED = 4, /* e.g. already [known to be] a valid Java object, ... 55 | -- hmm, could merge this with INDIRECT and save bits? */ 56 | MAX_PLUS_ONE = 8 57 | }; 58 | 59 | #endif 60 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | This is libdlbind, an extension to System V-style dynamic loaders to 2 | allow dynamic allocation of objects and dynamic binding of symbols. 3 | 4 | Its main use-case is in JIT compilers, or other run-time code 5 | generators. Rather than allocating code memory yourself, allocating it 6 | with dlbind keeps the dynamic loader informed -- along with any debugger 7 | that might be attached. This means that your JITted code and data are 8 | debuggable "by default" just like any other dynamically loaded content. 9 | It also means that the usual dlsym() and dladdr() interfaces will work 10 | against your JITted output. 11 | 12 | Currently, the implementation is very minimal and naive. It does work, 13 | with some caveats: 14 | 15 | - you must have a writable /run/shm directory; 16 | 17 | - no deallocation or unbinding yet; 18 | 19 | - when defining a new symbol, the dlreload() call, necessary for 20 | introducing the new symbols, requires some instrumentation of the 21 | dynamic linker or and/or modifications to the debugger. To do this 22 | introduction, the linker looks for a "change in the link map". To signal 23 | this, we have two options. 24 | 25 | (1) really unload (unmap) and reload (re-open/mmap) the file; 26 | 27 | (2) don't really close the file (hence keeping the mappings in memory), 28 | but make the debugger re-read the symbols anyway. 29 | 30 | To make (2) work, we need to make the debugger think that the link map 31 | has changed even when it hasn't, i.e. to be more conservative and 32 | (correctly) avoid assuming that an unchanged link map means unchanged 33 | symbol tables. This is probably the "right way" but means extending the 34 | protocol between dynamic linker and debugger. This is not done yet. 35 | 36 | Fortunately we can make (1) work already. The main caveat is that we 37 | rely on getting the same object base address when we do the re-load... 38 | currently this seems to work on glibc's ld.so. To ensure data written to 39 | the file persists across close/reopen, we also require (logically) a 40 | modified dynamic linker, so that the mmap() calls it makes for libdlbind 41 | objects request MAP_SHARED rather than MAP_PRIVATE semantics. As a 42 | quick-and-dirty solution, we can trap these syscalls and tweak the 43 | flags. This is what our gdb-script 44 | does, as a minimal demo of correct operation. In liballocs 45 | , the same idea is used but 46 | the syscall trapping is automated and happens online. Medium-term, a 47 | modified dynamic linker created with libgerald 48 | will be a more robust 49 | solution... most readily for tweaking the mmap flags, but also allowing 50 | us to implement the RTLD_RELOADABLE extension mentioned above. 51 | -------------------------------------------------------------------------------- /test/elfproto-simple/elfproto-simple.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "dlbind.h" 12 | 13 | int get_42(void) 14 | { 15 | return 42; 16 | } 17 | void end_of_get_42(void) {} 18 | 19 | int get_43(void) 20 | { 21 | return 43; 22 | } 23 | void end_of_get_43(void) {} 24 | 25 | #define MAX(a, b) (((a) < (b)) ? (b) : (a)) 26 | 27 | #ifndef PAGE_SIZE 28 | #define PAGE_SIZE 4096 29 | #endif 30 | 31 | #define PAGE_BOUNDARY_LOWER_OR_EQ(addr) (void*)(((uintptr_t) (addr)) % PAGE_SIZE == 0 ? (addr) : \ 32 | (void*)(((uintptr_t)(addr) / PAGE_SIZE) * PAGE_SIZE)) 33 | #define PAGE_BOUNDARY_HIGHER_OR_EQ(addr) (void*)(((uintptr_t) (addr)) % PAGE_SIZE == 0 ? (addr) : \ 34 | (void*)((((uintptr_t)(addr) / PAGE_SIZE) + 1) * PAGE_SIZE)) 35 | 36 | int main(void) 37 | { 38 | /* dlnew the file */ 39 | struct link_map *handle = dlcreate(NULL); 40 | assert(handle); 41 | 42 | /* now allow, bind, etc.. */ 43 | int *p_int = dlalloc(handle, sizeof (int), SHF_WRITE); 44 | assert(p_int); 45 | handle = dlbind(handle, "my_int", p_int, sizeof *p_int, STT_OBJECT); 46 | assert(handle); 47 | void *addr = dlsym(handle, "my_int"); 48 | assert(addr); 49 | assert(addr == p_int); 50 | 51 | /* Can we modify data/code and have it survive a reload? */ 52 | *p_int = 69105; 53 | size_t sizeof_get_42 = (char*) end_of_get_42 - (char*) get_42; 54 | size_t sizeof_get_43 = (char*) end_of_get_43 - (char*) get_43; 55 | size_t function_size = MAX(sizeof_get_42, sizeof_get_43); 56 | int (*p_func)(void) = dlalloc(handle, function_size, SHF_EXECINSTR); 57 | /* We have to mprotect it before we write it. */ 58 | int ret = mprotect(PAGE_BOUNDARY_LOWER_OR_EQ(p_func), 59 | PAGE_BOUNDARY_HIGHER_OR_EQ(p_func) - PAGE_BOUNDARY_LOWER_OR_EQ(p_func), 60 | PROT_READ|PROT_WRITE); 61 | assert(ret == 0); 62 | /* Write the function */ 63 | memcpy(p_func, (char*) get_42, sizeof_get_42); 64 | /* Bind it */ 65 | handle = dlbind(handle, "my_func", p_func, function_size, STT_FUNC); 66 | addr = dlsym(handle, "my_func"); 67 | assert(addr); 68 | assert(addr == p_func); 69 | /* Re-mprotect it. */ 70 | ret = mprotect(PAGE_BOUNDARY_LOWER_OR_EQ(p_func), 71 | PAGE_BOUNDARY_HIGHER_OR_EQ(p_func) - PAGE_BOUNDARY_LOWER_OR_EQ(p_func), 72 | PROT_READ|PROT_EXEC); 73 | assert(ret == 0); 74 | assert(p_func() == 42); 75 | 76 | /* Did our int value survive? */ 77 | assert(*p_int == 69105); 78 | /* Can we modify the code we just wrote? */ 79 | ret = mprotect(PAGE_BOUNDARY_LOWER_OR_EQ(p_func), 80 | PAGE_BOUNDARY_HIGHER_OR_EQ(p_func) - PAGE_BOUNDARY_LOWER_OR_EQ(p_func), 81 | PROT_READ|PROT_WRITE); 82 | assert(ret == 0); 83 | memcpy(p_func, (char*) get_43, sizeof_get_43); 84 | ret = mprotect(PAGE_BOUNDARY_LOWER_OR_EQ(p_func), 85 | PAGE_BOUNDARY_HIGHER_OR_EQ(p_func) - PAGE_BOUNDARY_LOWER_OR_EQ(p_func), 86 | PROT_READ|PROT_EXEC); 87 | assert(ret == 0); 88 | /* Does the code modification survive a reload? */ 89 | handle = dlreload(handle); 90 | assert(p_func() == 43); 91 | 92 | return 0; 93 | } 94 | -------------------------------------------------------------------------------- /src/symhash.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "symhash.h" 7 | 8 | /* Mutable implementation of the ELF symbol hash table. 9 | * 10 | * The table is fixed-size, for now, and uses an upper-bound 11 | * on the symtab size. 12 | * 13 | * No public look-up function! Use dlsym(). 14 | * 15 | */ 16 | 17 | static 18 | unsigned long 19 | elf64_hash(const unsigned char *name) 20 | { 21 | unsigned long h = 0, g; 22 | while (*name) 23 | { 24 | h = (h << 4) + *name++; 25 | if (0 != (g = (h & 0xf0000000))) h ^= g >> 24; 26 | h &= 0x0fffffff; 27 | } 28 | return h; 29 | } 30 | 31 | static 32 | void 33 | chain_sym(Elf64_Word *buckets, 34 | unsigned nbucket, 35 | unsigned nchain, 36 | const char *name, 37 | unsigned symind 38 | ) 39 | { 40 | assert(name[0] != '\0'); 41 | 42 | /* Which bucket does the sym go in? */ 43 | unsigned bucket = elf64_hash(name) % nbucket; 44 | /* Find a place to chain it */ 45 | Elf64_Word *pos = &buckets[bucket]; 46 | while (*pos != STN_UNDEF) 47 | { 48 | pos = &buckets[nbucket + *pos]; 49 | } 50 | *pos = symind; 51 | } 52 | static 53 | unsigned 54 | bucket_lookup(Elf64_Word *buckets, 55 | unsigned nbucket, 56 | unsigned nchain, 57 | const char *name, 58 | Elf64_Sym *symtab, 59 | const char *strtab 60 | ) 61 | { 62 | /* Which bucket does the sym go in? */ 63 | unsigned bucket = elf64_hash(name) % nbucket; 64 | Elf64_Word *chain = buckets + nbucket; 65 | /* Find it */ 66 | Elf64_Word *pos = &buckets[bucket]; 67 | for (Elf64_Word *pos = &buckets[bucket]; *pos != STN_UNDEF; pos = &chain[*pos]) 68 | { 69 | unsigned stroff = symtab[*pos].st_name; 70 | if (0 == strcmp(strtab + stroff, name)) return *pos; 71 | } 72 | return STN_UNDEF; 73 | } 74 | 75 | void 76 | elf64_hash_init( 77 | char *section, /* hash section */ 78 | size_t size, /* hash section size in bytes */ 79 | unsigned nbucket, /* nbucket */ 80 | unsigned nsyms, 81 | Elf64_Sym *symtab /* [nsyms] */, 82 | const char *strtab 83 | ) 84 | { 85 | /* nchain is nsyms */ 86 | Elf64_Word *words = (Elf64_Word *) section; 87 | words[0] = nbucket; 88 | words[1] = nsyms; // i.e. nchain 89 | for (unsigned i = 1; i < nsyms; ++i) 90 | { 91 | const char *symname = &strtab[symtab[i].st_name]; 92 | elf64_hash_put(section, size, nbucket, nsyms, symtab, strtab, i); 93 | } 94 | } 95 | 96 | void 97 | elf64_hash_put( 98 | char *section, /* has section */ 99 | size_t size, /* hash section size in bytes */ 100 | unsigned nbucket, /* nbucket -- must match existing section! */ 101 | unsigned nsyms, /* symbol table entry count */ 102 | Elf64_Sym *symtab, /* symbol table */ 103 | const char *strtab, 104 | unsigned symind /* assume this symind was unused previously! */ 105 | ) 106 | { 107 | const char *key = &strtab[symtab[symind].st_name]; 108 | 109 | // the empty string is always in the table 110 | if (*key == '\0') return; 111 | 112 | /* Assert that symname is not currently used */ 113 | Elf64_Word *words = (Elf64_Word *) section; 114 | Elf64_Word *buckets = words + 2; 115 | assert(STN_UNDEF == bucket_lookup(buckets, 116 | nbucket, 117 | nsyms, /* nchain is nsyms */ 118 | key, 119 | symtab, 120 | strtab 121 | ) 122 | ); 123 | 124 | /* Assert that symind is not in the table. */ 125 | 126 | 127 | /* Chain it. */ 128 | chain_sym(buckets, nbucket, nsyms, key, symind); 129 | } 130 | 131 | Elf64_Sym * 132 | elf64_hash_get( 133 | char *section, /* has section */ 134 | size_t size, /* hash section size in bytes */ 135 | unsigned nbucket, /* nbucket -- must match existing section! */ 136 | unsigned nsyms, /* symbol table entry count */ 137 | Elf64_Sym *symtab, /* symbol table */ 138 | const char *strtab, 139 | const char *key 140 | ) 141 | { 142 | /* nchain is nsyms */ 143 | Elf64_Word *words = (Elf64_Word *) section; 144 | /* Assert that symname is not currently used */ 145 | unsigned pos = bucket_lookup(&words[2], 146 | nbucket, 147 | nsyms, 148 | key, 149 | symtab, 150 | strtab 151 | ); 152 | if (pos == STN_UNDEF) return NULL; 153 | else return symtab + pos; 154 | } 155 | 156 | // elf_hash_del( 157 | -------------------------------------------------------------------------------- /example/example.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "raw-syscalls-defs.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include "librunt.h" 17 | #include "dlbind.h" 18 | #include "systrap.h" 19 | #include "relf.h" 20 | 21 | // HACK for TEXT_WRITABLE_VADDR_DELTA 22 | #include "../src/dlbind_internal.h" 23 | 24 | int get_42(void) 25 | { 26 | return 42; 27 | } 28 | void end_of_get_42(void) {} 29 | 30 | // FIXME: should not be hidden? 31 | extern __thread const char *dlbind_open_active_on __attribute__((visibility("hidden"))); 32 | 33 | void mmap_replacement(struct generic_syscall *s, post_handler *post) __attribute__((visibility("hidden"))); 34 | void mmap_replacement(struct generic_syscall *s, post_handler *post) 35 | { 36 | /* Unpack the mmap arguments. */ 37 | void *addr = (void*) s->args[0]; 38 | size_t length = s->args[1]; 39 | int prot = s->args[2]; 40 | int flags = s->args[3]; 41 | int fd = s->args[4]; 42 | off_t offset = s->args[5]; 43 | if (dlbind_open_active_on) 44 | { 45 | flags &= ~(0x3 /*MAP_SHARED|MAP_PRIVATE*/); 46 | flags |= 0x1 /* MAP_SHARED */; 47 | } 48 | void *ret = raw_mmap(addr, length, prot, flags, fd, offset); 49 | /* Do the post-handling and resume. */ 50 | post(s, (long) ret, 1); 51 | } 52 | 53 | void openat_replacement(struct generic_syscall *s, post_handler *post) __attribute__((visibility("hidden"))); 54 | void openat_replacement(struct generic_syscall *s, post_handler *post) 55 | { 56 | /* Unpack the arguments */ 57 | int dirfd = (int) s->args[0]; 58 | const char *path = (const char *) s->args[1]; 59 | int flags = s->args[2]; 60 | mode_t mode = s->args[3]; 61 | 62 | if (dlbind_open_active_on) 63 | { 64 | flags &= ~(O_RDWR|O_RDONLY); 65 | flags |= O_RDWR; 66 | } 67 | int ret = raw_openat(dirfd, path, flags, mode); 68 | 69 | /* Do the post-handling and resume. */ 70 | post(s, ret, 1); 71 | } 72 | 73 | static void trap_ldso_mappings(void) 74 | { 75 | /* To trap the text segment of the ld.so, we get its phdrs 76 | * from auxv. */ 77 | __runt_auxv_init(); 78 | const ElfW(auxv_t) *p_auxv; 79 | ElfW(auxv_t) *p_auxv_end; 80 | int ret = __runt_auxv_get_auxv(&p_auxv, &p_auxv_end); 81 | if (!ret) abort(); 82 | // we'll want shdrs -- hmm; get the interpreter name 83 | ElfW(Phdr) *prog_phdr = (void*) auxv_lookup((ElfW(auxv_t) *) p_auxv, AT_PHDR)->a_un.a_val; 84 | unsigned prog_phnum = (unsigned long) auxv_lookup((ElfW(auxv_t) *) p_auxv, AT_PHNUM)->a_un.a_val; 85 | // first we need the program's load address 86 | uintptr_t load_addr = (uintptr_t) -1; 87 | for (unsigned i = 0; i < prog_phnum; ++i) 88 | { 89 | if (prog_phdr[i].p_type == PT_PHDR) 90 | { 91 | load_addr = (uintptr_t) prog_phdr - prog_phdr[i].p_vaddr; 92 | break; 93 | } 94 | } 95 | if (load_addr == (uintptr_t) -1) abort(); 96 | char *buf = NULL; 97 | // now we get get the interp name 98 | for (unsigned i = 0; i < prog_phnum; ++i) 99 | { 100 | if (prog_phdr[i].p_type == PT_INTERP) 101 | { 102 | buf = alloca(prog_phdr[i].p_filesz); 103 | memcpy(buf, (void*) (load_addr + prog_phdr[i].p_vaddr), prog_phdr[i].p_filesz); 104 | } 105 | } 106 | if (buf == NULL) abort(); 107 | // now we can open the file and snarf the shdrs 108 | int fd = open(buf, O_RDONLY); 109 | assert(fd != -1); 110 | struct stat s; 111 | ret = fstat(fd, &s); 112 | assert(ret == 0); 113 | unsigned long mapping_len = PAGE_SIZE * ((s.st_size + (PAGE_SIZE-1)) / PAGE_SIZE); 114 | void *mapping = mmap(NULL, mapping_len, PROT_READ, MAP_PRIVATE, fd, 0); 115 | assert(mapping != (void*) -1); 116 | void *ldso_base = (void*) auxv_lookup((ElfW(auxv_t) *) p_auxv, AT_BASE)->a_un.a_val; 117 | ElfW(Ehdr) *ehdr = ldso_base; 118 | // finally can we get its phdrs? 119 | ElfW(Phdr) *phdr = (void*)(((uintptr_t) ldso_base) + ehdr->e_phoff); 120 | printf("first phdr has type %d\n", phdr->p_type); 121 | for (unsigned i = 0; i < ehdr->e_phnum; ++i) 122 | { 123 | if (phdr[i].p_type == PT_LOAD && 124 | phdr[i].p_flags & PF_X) 125 | { 126 | /* ... and we have the shdrs? */ 127 | trap_one_executable_region_given_shdrs( 128 | ldso_base + phdr[i].p_vaddr, 129 | ldso_base + phdr[i].p_vaddr + phdr[i].p_memsz, 130 | "/lib64/ld-linux-x86-64.so.2" /* HACK */, 131 | /* is_writable */ phdr[i].p_flags & PF_W, /* is_readable */ phdr[i].p_flags & PF_R, 132 | (void*)(((uintptr_t) mapping) + ehdr->e_shoff), ehdr->e_shnum, (uintptr_t) ldso_base); 133 | } 134 | } 135 | munmap(mapping, mapping_len); 136 | close(fd); 137 | } 138 | 139 | int main(void) 140 | { 141 | /* As well as doing our libdlbind stuff, we have to use libsystrap 142 | * to 143 | * - install sigill handler 144 | * - set traps in ld.so 145 | * - install a handler for mmap that does s/MAP_PRIVATE/MAP_SHARED/ 146 | * - install a handler for open that does s/O_RDONLY/O_RDWR/ 147 | */ 148 | replaced_syscalls[__NR_mmap] = mmap_replacement; 149 | replaced_syscalls[__NR_openat] = openat_replacement; 150 | install_sigill_handler(); 151 | trap_ldso_mappings(); 152 | 153 | // create libfoo 154 | void *l = dlcreate("foo"); 155 | 156 | // get some memory 157 | size_t len = (char*)end_of_get_42 - (char*)get_42; 158 | const char *alloc = dlalloc(l, len, SHF_EXECINSTR); 159 | assert(alloc); 160 | 161 | // copy our function into it 162 | // get a writable alias of the memory 163 | char *writable_alloc = /*dl_find_alias_with(SHF_READ|SHF_WRITE, alloc, l) */ 164 | (char*)((uintptr_t) alloc + TEXT_WRITABLE_VADDR_DELTA); // HACK 165 | memcpy(writable_alloc, get_42, len); 166 | 167 | // FIXME: reinstate a realloc call: *reallocate* to the smaller size (was 200) 168 | // dlrealloc(l, alloc, len); 169 | 170 | // FIXME: this test case doesn't work! libdlbind only works within liballocs 171 | // where we can hook open() calls and change MAP_PRIVATE to MAP_SHARED. 172 | // Perhaps we can make libsystrap a dependency of libdlbind? 173 | 174 | // bind it 175 | void *reloaded = dlbind(l, "meaning", (void*)alloc, len, STT_FUNC); 176 | assert(reloaded == l); 177 | 178 | struct link_map *m = (struct link_map *) l; 179 | // before we dlsym our function, sysv_hash_lookup it 180 | ElfW(Word) *sysv_hash = get_sysv_hash_from_dyn(m->l_ld, m->l_addr); 181 | ElfW(Sym) *symtab = get_dynsym_from_dyn(m->l_ld, m->l_addr); 182 | if (!symtab) return 0; 183 | ElfW(Sym) *symtab_end = symtab + dynamic_symbol_count_from_dyn(m->l_ld, m->l_addr); 184 | unsigned char *strtab = get_dynstr_from_dyn(m->l_ld, m->l_addr); 185 | unsigned char *strtab_end = strtab + dynamic_xlookup(m->l_ld, DT_STRSZ)->d_un.d_val; 186 | assert(sysv_hash); 187 | ElfW(Sym) *found = /*sysv_*/hash_lookup(sysv_hash, symtab, strtab, "meaning"); 188 | assert(found); 189 | 190 | // dlsym our function 191 | int (*func)(void) = (int(*)(void)) dlsym(l, "meaning"); 192 | assert(func); 193 | 194 | // call it 195 | int v = func(); 196 | printf("libdlbind-loaded function returned: %d\n", v); 197 | 198 | // sleep 199 | printf("sleeping so you can attach a debugger if you like...\n"); 200 | sleep(10); 201 | 202 | // FIXME: reinstate a dldelete call. 203 | // dldelete 204 | // dldelete(l); 205 | 206 | return 0; 207 | } 208 | -------------------------------------------------------------------------------- /src/libdlbind.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include "elfproto.h" 17 | #include "symhash.h" 18 | #include "dlbind.h" 19 | #include "dlbind_internal.h" 20 | #include "relf.h" 21 | 22 | char *strdup(const char *s); /* why is not good enough for this? */ 23 | 24 | /* Out-of-band signalling for detecting dlopening. */ 25 | __thread const char *dlbind_open_active_on __attribute__((visibility("hidden"))); 26 | /* Remove this once weak thread-locals actually work! */ 27 | int dlbind_dummy __attribute__((visibility("hidden"))); 28 | 29 | static void do_reload(void *h); 30 | 31 | /* Allocate a chunk of space in the file. */ 32 | void *dlalloc(void *handle, size_t sz, unsigned flags) 33 | { 34 | struct link_map *l = handle; 35 | Elf64_Sxword dt_bump; 36 | switch (flags & (SHF_WRITE|SHF_EXECINSTR)) 37 | { 38 | case SHF_WRITE: 39 | // use .data 40 | dt_bump = DT_DLBIND_DATABUMP; 41 | break; 42 | case SHF_EXECINSTR: 43 | // use .text: 44 | dt_bump = DT_DLBIND_TEXTBUMP; 45 | break; 46 | case (SHF_WRITE|SHF_EXECINSTR): 47 | // not allowed -- use multiple mappings 48 | return NULL; 49 | case 0: 50 | // use .rodata: 51 | dt_bump = DT_DLBIND_RODATABUMP; 52 | break; 53 | default: 54 | return NULL; 55 | } 56 | 57 | for (Elf64_Dyn *d = (Elf64_Dyn*) l->l_ld; d->d_tag != DT_NULL; ++d) 58 | { 59 | if (d->d_tag == dt_bump) 60 | { 61 | void *ret = (char*) d->d_un.d_ptr; 62 | *((char**) &d->d_un.d_ptr) += sz; 63 | return (void*)((uintptr_t) l->l_addr + ret); 64 | } 65 | } 66 | 67 | return NULL; 68 | } 69 | 70 | /* Create a new symbol binding within a library created using dlnew(). 71 | * The varargs may be used to specify the namespace, calling convention 72 | * and version string. Note that these need only be meaningful for text 73 | * symbols. Whether the object belongs in text, data or rodata is inferred 74 | * from the flags of the memory object containing address obj. */ 75 | void *dlbind(void *lib, const char *symname, void *obj, size_t len, ElfW(Word) type) 76 | { 77 | struct link_map *l = (struct link_map *) lib; 78 | Elf64_Dyn *found_dynsym_ent = dynamic_lookup(l->l_ld, DT_SYMTAB); 79 | Elf64_Dyn *found_dynstr_ent = dynamic_lookup(l->l_ld, DT_STRTAB); 80 | Elf64_Dyn *found_hash_ent = dynamic_lookup(l->l_ld, DT_HASH); 81 | /* What's the next free global symbol index? */ 82 | Elf64_Dyn *dynstr_bump_ent = dynamic_lookup(l->l_ld, DT_DLBIND_DYNSTRBUMP); 83 | Elf64_Dyn *dynsym_bump_ent = dynamic_lookup(l->l_ld, DT_DLBIND_DYNSYMBUMP); 84 | 85 | /* Sometimes .dynamic entries are relocated, sometimes they're not. 86 | * FIXME: understand when this does and doesn't happen. 87 | * FIXME: seems to be a risk of double-relocation if we do the reloc ourselves. 88 | * Let's try it anyway. 89 | */ 90 | #define FIXUP_PTR(p, base) \ 91 | ((uintptr_t) (p) > (uintptr_t) (base) \ 92 | ? (void*)(p) \ 93 | : (void*)((*((uintptr_t *) &(p)) = ((uintptr_t)(base) + (uintptr_t)(p))))) 94 | 95 | unsigned strind = dynstr_bump_ent->d_un.d_val; 96 | char *dynstr_insertion_pt = (char*) FIXUP_PTR(found_dynstr_ent->d_un.d_ptr, l->l_addr) + strind; 97 | unsigned symind = dynsym_bump_ent->d_un.d_val; 98 | Elf64_Sym *dynsym_insertion_pt = (Elf64_Sym *) FIXUP_PTR(found_dynsym_ent->d_un.d_ptr, l->l_addr) 99 | + symind; 100 | strcpy(dynstr_insertion_pt, symname); 101 | dynstr_bump_ent->d_un.d_val += strlen(symname) + 1; 102 | 103 | Elf64_Sym *shdr_sym = elf64_hash_get( 104 | (char*) FIXUP_PTR(found_hash_ent->d_un.d_ptr, l->l_addr), 105 | (2 + NBUCKET + MAX_SYMS) * sizeof (Elf64_Word), 106 | NBUCKET, 107 | MAX_SYMS, 108 | (Elf64_Sym*) FIXUP_PTR(found_dynsym_ent->d_un.d_ptr, l->l_addr), 109 | (char*) FIXUP_PTR(found_dynstr_ent->d_un.d_ptr, l->l_addr), 110 | "_SHDRS" 111 | ); 112 | assert(shdr_sym); 113 | Elf64_Shdr *shdr = (Elf64_Shdr*) ((char*) l->l_addr + shdr_sym->st_value); 114 | assert(shdr == dlsym(lib, "_SHDRS")); 115 | if (!shdr) return NULL; 116 | unsigned n_shdrs = shdr_sym->st_size / sizeof (Elf64_Shdr); 117 | Elf64_Shdr *preceding_shdr = NULL; 118 | for (Elf64_Shdr *i_shdr = shdr; i_shdr < shdr + n_shdrs; ++i_shdr) 119 | { 120 | char *shdr_addr = (char*) l->l_addr + i_shdr->sh_addr; 121 | if (shdr_addr <= (char*) obj 122 | && (!preceding_shdr || shdr_addr > (char*) l->l_addr + preceding_shdr->sh_addr)) 123 | { 124 | preceding_shdr = i_shdr; 125 | } 126 | } 127 | *dynsym_insertion_pt = (Elf64_Sym) { 128 | .st_name = strind, 129 | .st_info = ELF64_ST_INFO(STB_GLOBAL, type), 130 | .st_other = ELF64_ST_VISIBILITY(STV_DEFAULT), 131 | .st_shndx = preceding_shdr ? (preceding_shdr - shdr) : SHN_ABS, 132 | .st_value = preceding_shdr ? (uintptr_t) obj - (uintptr_t) l->l_addr : (uintptr_t) obj, 133 | .st_size = len 134 | }; 135 | dynsym_bump_ent->d_un.d_val--; 136 | elf64_hash_put( 137 | (char*) FIXUP_PTR(found_hash_ent->d_un.d_ptr, l->l_addr), /* hash section */ 138 | (2 + NBUCKET + MAX_SYMS) * sizeof (Elf64_Word), /* hash section size in bytes */ 139 | NBUCKET, /* nbucket -- must match existing section! */ 140 | MAX_SYMS, /* symbol table entry count */ 141 | (Elf64_Sym*) FIXUP_PTR(found_dynsym_ent->d_un.d_ptr, l->l_addr), /* symbol table */ 142 | (char*) FIXUP_PTR(found_dynstr_ent->d_un.d_ptr, l->l_addr), 143 | symind /* assume this symind was unused previously! */ 144 | ); 145 | 146 | // Elf64_Shdr *shdr = dlsym(lib, "_SHDRS"); 147 | // if (!shdr) return -1; 148 | // Elf64_Dyn *found_dynsym_ent = dynamic_lookup(dynamic, DT_SYMTAB); 149 | // if (!found_dynsym_ent) return -3; 150 | // Elf64_Dyn *found_hash_ent = dynamic_lookup(dynamic, DT_HASH); 151 | // if (!found_hash_ent) return -4; 152 | // 153 | // /* Walk the shdrs until we find the dynsym */ 154 | // while (shdr->sh_vaddr != found_dynsym_ent->d_un.d_ptr) ++shdr; 155 | // // FIXME: check for end somehow -- maybe _SHDRS symbol size info 156 | // 157 | // unsigned nlocal = shdr->sh_info; 158 | 159 | do_reload(l); 160 | return l; // dlreload(l); 161 | } 162 | 163 | extern void _dl_debug_state(void); 164 | 165 | static 166 | void do_reload(void *h) 167 | { 168 | /* Unless FAKE_RELOAD is defined, we really do (potentially) 169 | * unload and reload the object. This is the easiest for 170 | * debuggers to grok. But it potentially moves the object, 171 | * which is bad for clients. 172 | * If FAKE_RELOAD is defined, we instead remove it from 173 | * the link map (behind the ld.so's back), call the r_brk 174 | * function, then relink it. This will not move the object, 175 | * but I think I never got it working as intended with gdb. */ 176 | struct link_map *l = (struct link_map *) h; 177 | void *old_load_addr = (void*) l->l_addr; 178 | char *copied = strdup(l->l_name); 179 | 180 | /* Un-relocate any relocations applied by ld.so. 181 | * We didn't create any relocation records, so we are mostly okay. 182 | * BUT ld.so also relocates certain d_ptr values in the .dynamic section. 183 | * So undo that bit. */ 184 | #ifndef FAKE_RELOAD 185 | Elf64_Dyn *found_dynsym_ent = dynamic_lookup(l->l_ld, DT_SYMTAB); 186 | if ((char*) found_dynsym_ent->d_un.d_ptr >= (char*) l->l_addr) 187 | { 188 | found_dynsym_ent->d_un.d_ptr -= (uintptr_t) l->l_addr; 189 | } 190 | Elf64_Dyn *found_dynstr_ent = dynamic_lookup(l->l_ld, DT_STRTAB); 191 | if ((char*) found_dynstr_ent->d_un.d_ptr >= (char*) l->l_addr) 192 | { 193 | found_dynstr_ent->d_un.d_ptr -= (uintptr_t) l->l_addr; 194 | } 195 | Elf64_Dyn *found_hash_ent = dynamic_lookup(l->l_ld, DT_HASH); 196 | if ((char*) found_hash_ent->d_un.d_ptr >= (char*) l->l_addr) 197 | { 198 | found_hash_ent->d_un.d_ptr -= (uintptr_t) l->l_addr; 199 | } 200 | Elf64_Dyn *found_rela_ent = dynamic_lookup(l->l_ld, DT_RELA); 201 | if ((char*) found_rela_ent->d_un.d_ptr >= (char*) l->l_addr) 202 | { 203 | found_rela_ent->d_un.d_ptr -= (uintptr_t) l->l_addr; 204 | } 205 | int failed = dlclose(l); 206 | _Bool still_loaded = 0; 207 | /* We might not actually have been unloaded. In particular, if we have dlsym()'d 208 | * any of the contents of the object, ld.so remembers the "dependency" and can 209 | * mark our object as RTLD_NODELETE. So check whether we were really unloaded, 210 | * and if not, undo the un-relocation we just did, because the next dlopen will 211 | * NOT go through the relocation step. FIXME: this is racy. */ 212 | if (!failed) for (struct link_map *test_l = _r_debug.r_map; test_l; test_l = test_l->l_next) 213 | { 214 | if (l == test_l) 215 | { 216 | /* We didn't actually get unloaded. */ 217 | still_loaded = 1; 218 | } 219 | } 220 | if (failed || still_loaded) 221 | { 222 | /* Okay. Undo the un-relocation. */ 223 | if ((char*) found_dynsym_ent->d_un.d_ptr < (char*) l->l_addr) 224 | { 225 | found_dynsym_ent->d_un.d_ptr += (uintptr_t) l->l_addr; 226 | } 227 | Elf64_Dyn *found_dynstr_ent = dynamic_lookup(l->l_ld, DT_STRTAB); 228 | if ((char*) found_dynstr_ent->d_un.d_ptr < (char*) l->l_addr) 229 | { 230 | found_dynstr_ent->d_un.d_ptr += (uintptr_t) l->l_addr; 231 | } 232 | Elf64_Dyn *found_hash_ent = dynamic_lookup(l->l_ld, DT_HASH); 233 | if ((char*) found_hash_ent->d_un.d_ptr < (char*) l->l_addr) 234 | { 235 | found_hash_ent->d_un.d_ptr += (uintptr_t) l->l_addr; 236 | } 237 | Elf64_Dyn *found_rela_ent = dynamic_lookup(l->l_ld, DT_RELA); 238 | if ((char*) found_rela_ent->d_un.d_ptr < (char*) l->l_addr) 239 | { 240 | found_rela_ent->d_un.d_ptr += (uintptr_t) l->l_addr; 241 | } 242 | } 243 | dlbind_open_active_on = copied; 244 | void *new_handle = dlopen(copied, RTLD_NOW | RTLD_GLOBAL /*| RTLD_NODELETE*/); 245 | dlbind_open_active_on = NULL; 246 | assert(new_handle); 247 | #else /* FAKE_RELOAD */ 248 | struct link_map *old_handle = l; 249 | void *new_handle = old_handle; 250 | void (*r_brk)(void) = (void*) find_r_debug()->r_brk; 251 | 252 | /* Temporarily remove it from the linked list and signal to the debugger. 253 | * HACK: this is racy. */ 254 | if (old_handle->l_prev) old_handle->l_prev->l_next = old_handle->l_next; 255 | if (old_handle->l_next) old_handle->l_next->l_prev = old_handle->l_prev; 256 | r_brk(); 257 | /* Now put it back and signal again. HACK again. */ 258 | if (old_handle->l_prev) old_handle->l_prev->l_next = old_handle; 259 | if (old_handle->l_next) old_handle->l_next->l_prev = old_handle; 260 | r_brk(); 261 | #endif 262 | /* It's important we get the old load address back. */ 263 | assert((void*) ((struct link_map *) new_handle)->l_addr == (old_load_addr)); 264 | free(copied); 265 | // return new_handle; 266 | } 267 | 268 | static _Bool path_is_viable(const char *path) 269 | { 270 | _Bool can_access = (0 == access(path, R_OK|W_OK|X_OK)); 271 | if (!can_access) return 0; 272 | struct statvfs buf; 273 | int ret = statvfs(path, &buf); 274 | if (ret != 0) return 0; 275 | if (buf.f_flag & ST_RDONLY) return 0; 276 | if (buf.f_flag & ST_NOEXEC) return 0; 277 | return 1; 278 | } 279 | 280 | #define DLCREATE_MAX 16 281 | static unsigned next_free_unlink_entry; 282 | static const char *unlink_list[DLCREATE_MAX]; 283 | 284 | void *dlcreate_with_mmap(const char *libname, void*(*mmap)(void */*addr*/, size_t /*length*/, 285 | int /*prot*/, int /*flags*/, int /*fd*/, off_t /*offset*/)) 286 | { 287 | if (next_free_unlink_entry == DLCREATE_MAX) return NULL; 288 | // FIXME: proper error handling in this function (and file) please 289 | // FIXME: pay attention to libname 290 | /* Create a file, truncate it, mmap it */ 291 | // FIXME: use POSIX shared-memory interface with some pretence at portability 292 | char filename0[] = "/run/shm/tmp.dlbind.XXXXXX"; 293 | char filename1[] = "/tmp/tmp.dlbind.XXXXXX"; 294 | char *filename = path_is_viable("/run/shm/.") ? filename0 : filename1; 295 | int fd = mkostemp(filename, O_RDWR|O_CREAT); 296 | if (fd == -1) err(1, "mkostemp(\"%s\", O_RDWR|O_CREAT)", filename); 297 | unlink_list[next_free_unlink_entry++] = strdup(filename); 298 | /* Truncate the file to the necessary size */ 299 | int ret = ftruncate(fd, _dlbind_elfproto_memsz); 300 | if (ret != 0) err(1, "truncating %s to %ld bytes", filename, _dlbind_elfproto_memsz); 301 | /* The relatively large mapping is a problem for the liballocs + ecfs use case, 302 | * because we get many extraneous megabytes in our coredump. Perhaps we can 303 | * supply an alternative mmap function here? */ 304 | void *addr = mmap(NULL, _dlbind_elfproto_memsz, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); 305 | if (addr == MAP_FAILED) err(1, "mapping %s", filename); 306 | /* Copy in the ELF proto contents. We don't need to copy the actual sections 307 | * area, which should all be zero. And be even more clever about sparseness, 308 | * since large parts of the dynsym and dynstr are initially zeroed too. */ 309 | memcpy_elfproto_to(addr); 310 | munmap(addr, _dlbind_elfproto_memsz); 311 | /* dlopen the file */ 312 | char *proc_filename = NULL; 313 | struct link_map *handle = NULL; 314 | ret = asprintf(&proc_filename, "/proc/%d/fd/%d", getpid(), fd); 315 | if (ret <= 0) goto out; 316 | assert(proc_filename != NULL); 317 | dlbind_open_active_on = proc_filename; 318 | handle = dlopen(proc_filename, RTLD_NOW | RTLD_GLOBAL/* | RTLD_NODELETE*/); 319 | dlbind_open_active_on = NULL; 320 | unlink(filename); 321 | /* At this point, the fd remains open but the file is unlinked. Cleanup 322 | * problem solved! We can still access it through the magic /proc symlink. 323 | * And so can the debugger! FIXME: this is too Linux-specific for my liking. 324 | * FIXME: do the close() in dldelete(), rather than having it hang around 325 | * until the process exits. */ 326 | out: 327 | if (!handle) { close(fd); err(1, "dlopening %s (really %s)", filename, proc_filename); } 328 | if (proc_filename) free(proc_filename); 329 | return handle; 330 | } 331 | 332 | void *dlcreate(const char *libname) 333 | { 334 | return dlcreate_with_mmap(libname, mmap); 335 | } 336 | -------------------------------------------------------------------------------- /src/elfproto.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "symhash.h" 7 | #include "elfproto.h" 8 | #include "dlbind_internal.h" 9 | 10 | /* We define the basic headers and structure of the ELF file 11 | * we're instantiating. 12 | * 13 | * To instantiate this prototype, we: 14 | * 15 | * - create a new temporary file 16 | * - mmap it (*not* a private mapping) 17 | * - write its ELF headers etc., allowing a certain 18 | * amount of space for text (rx), rodata (r) and data (rw). 19 | * - all this space is trailing; we just define the headers here. 20 | * - dlopen() it -- this will make a *private* mapping, and if we 21 | * use RTLD_NODELETE, it will keep the data sections around on a 22 | * reload. 23 | * - It's therefore important that any updates are made to the live, 24 | * dlopen()'d copy, not our on-disk mmap() copy. In effect, the mmap()'d 25 | * copy on disk is just a zygote, and could just be a fixed data file. 26 | * The difference is that we must give it a fresh name to allow multiple 27 | * copies per process, without the loader thinking they're the "same" object. 28 | * - In effect, RTLD_NODELETE undoes the unwanted "private mapping" effect 29 | * from this process's point of view: reloading does not throw away changes. 30 | * - PROBLEM: from the debugger's point of view, the file hasn't changed! 31 | * So we need to hack the loader so that it doesn't use MAP_PRIVATE. 32 | */ 33 | 34 | #define DYNAMIC_N 14 35 | #define RELA_DYN_N 1 36 | #define SHSTRTAB_SZ 128 37 | #define PHDRS_N 7 38 | #define SHDRS_N 10 39 | #define AVERAGE_SYM_LENGTH 10 40 | #define DYNSTR_SZ (AVERAGE_SYM_LENGTH * MAX_SYMS) 41 | 42 | // forward-declare everything up-front, so that we can cross-refer 43 | /* FIXME: forward-declaration requires us to use "extern" but then "static". 44 | * Sadly the only way around this seems to be to write this file in assembly. */ 45 | 46 | Elf64_Ehdr ehdr __attribute__((visibility("hidden"),section(".elf_zygote"))); 47 | Elf64_Phdr phdrs[PHDRS_N] __attribute__((visibility("hidden"),section(".elf_zygote"))); 48 | Elf64_Shdr shdrs[SHDRS_N] __attribute__((visibility("hidden"),section(".elf_zygote"))); 49 | Elf64_Dyn dynamic[DYNAMIC_N] __attribute__((visibility("hidden"),section(".elf_zygote"))); 50 | Elf64_Sym dynsym[MAX_SYMS] __attribute__((visibility("hidden"),section(".elf_zygote"))); 51 | Elf64_Rela rela_dyn[RELA_DYN_N] __attribute__((visibility("hidden"),section(".elf_zygote"))); 52 | Elf64_Word hash[2 + NBUCKET + MAX_SYMS] __attribute__((visibility("hidden"),section(".elf_zygote"))) = { 53 | NBUCKET, 54 | MAX_SYMS 55 | }; 56 | char shstrtab[SHSTRTAB_SZ] __attribute__((visibility("hidden"),section(".elf_zygote"))) = { 57 | /* Offset 0 */ '\0', 58 | /* Offset 1 */ '.', 's', 'h', 's', 't', 'r', 't', 'a', 'b', '\0', 59 | /* Offset 11 */ '.', 't', 'e', 'x', 't', '\0', 60 | /* Offset 17 */ '.', 'd', 'a', 't', 'a', '\0', 61 | /* Offset 23 */ '.', 'r', 'o', 'd', 'a', 't', 'a', '\0', 62 | /* Offset 31 */ '.', 'd', 'y', 'n', 's', 'y', 'm', '\0', 63 | /* Offset 39 */ '.', 'd', 'y', 'n', 's', 't', 'r', '\0', 64 | /* Offset 47 */ '.', 'h', 'a', 's', 'h', '\0', 65 | /* Offset 53 */ '.', 'd', 'y', 'n', 'a', 'm', 'i', 'c', '\0', 66 | /* Offset 62 */ '.', 'r', 'e', 'l', 'a', '.', 'd', 'y', 'n', '\0' 67 | }; 68 | char dynstr_used[] __attribute__((visibility("hidden"),section(".elf_zygote"))) = { 69 | /* Offset 0 */ '\0', 70 | /* Offset 1 */ '_', 'D', 'Y', 'N', 'A', 'M', 'I', 'C', '\0', 71 | /* Offset 10 */ '_', 'S', 'H', 'D', 'R', 'S', '\0' /* first zero offset: 17 (update below!) */ 72 | }; 73 | //char dynstr_unused[DYNSTR_SZ - sizeof dynstr_used] __attribute__((visibility("hidden"),section(".elf_zygote"))); 74 | /* FIXME: this is a lot of zeroes. Can we make this NOBITS and 75 | * put it in an adjoining NOBITS section? Requires the client 76 | * to link us in a particular way. Better just to reduce the 77 | * number of zeroes. XXX: actually this is stupid. We memcpy 78 | * the zygote in pieces anyway. So making a single contiguous 79 | * in-memory .elf_zygote section that models it is pointless. 80 | * Just drop this and instead record separately the offset from 81 | * end of dynstr_used to first_user_word. */ 82 | //unsigned long first_user_word __attribute__((visibility("hidden"),section(".elf_zygote"))); 83 | #define OFFSET_TO_FIRST_USER_WORD (((uintptr_t) &dynstr_used[0] - (uintptr_t) &ehdr) + DYNSTR_SZ) 84 | 85 | /* globals */ 86 | size_t _dlbind_elfproto_headerscn_sz; 87 | size_t _dlbind_elfproto_memsz; 88 | void *_dlbind_elfproto_begin; 89 | 90 | static void init(void) __attribute__((constructor)); 91 | static void init(void) 92 | { 93 | static int done_init; 94 | if (done_init) return; 95 | 96 | /* Challenge: to keep the structure declarative, 97 | * allowing pointer-differencing (which is "non-compile-time-constant") 98 | * and not using C++'s static initializer mechanism (runs too late for liballocs). 99 | * We also can't forward-declare static members. 100 | * The Right Way is probably to suck up the ugliness of assignments rather than 101 | * initializer lists. Okay, let's do that. */ 102 | 103 | ehdr = (Elf64_Ehdr) { 104 | .e_ident = { '\x7f', 'E', 'L', 'F', ELFCLASS64, ELFDATA2LSB, EV_CURRENT, ELFOSABI_GNU, 0 }, 105 | .e_type = ET_DYN, 106 | .e_machine = EM_X86_64, 107 | .e_version = EV_CURRENT, 108 | .e_entry = 0, 109 | .e_phoff = (uintptr_t) &phdrs[0] - (uintptr_t) &ehdr, 110 | .e_shoff = (uintptr_t) &shdrs[0] - (uintptr_t) &ehdr, 111 | .e_flags = 0, 112 | .e_ehsize = sizeof (Elf64_Ehdr), 113 | .e_phentsize = sizeof (Elf64_Phdr), 114 | .e_phnum = PHDRS_N /* text, data, rodata, dynamic */, 115 | .e_shentsize = sizeof (Elf64_Shdr), 116 | .e_shnum = SHDRS_N /* null, shstrtab, text, data, rodata, dynsym, dynstr, hash, dynamic, rela.dyn */, 117 | .e_shstrndx = 1 118 | }; 119 | 120 | phdrs[0] = 121 | (Elf64_Phdr) { 122 | .p_type = PT_DYNAMIC, 123 | .p_flags = PF_R | PF_W, 124 | .p_offset = (uintptr_t) &dynamic[0] - (uintptr_t) &ehdr, 125 | .p_vaddr = (uintptr_t) &dynamic[0] - (uintptr_t) &ehdr, 126 | .p_paddr = (uintptr_t) &dynamic[0] - (uintptr_t) &ehdr, 127 | .p_filesz = sizeof dynamic, // could implement bss here 128 | .p_memsz = sizeof dynamic, 129 | .p_align = sizeof (void*) 130 | }; 131 | phdrs[1] = (Elf64_Phdr) { /* metadata mapping. Include the shdrs! dlbind() wants them. */ 132 | .p_type = PT_LOAD, 133 | .p_flags = PF_R | PF_W, 134 | .p_offset = (uintptr_t) &shdrs[0] - (uintptr_t) &ehdr, 135 | .p_vaddr = (uintptr_t) &shdrs[0] - (uintptr_t) &ehdr, 136 | .p_paddr = (uintptr_t) &shdrs[0] - (uintptr_t) &ehdr, 137 | .p_filesz = (uintptr_t) &dynstr_used[0] + DYNSTR_SZ - (uintptr_t) &shdrs[0], 138 | .p_memsz = (uintptr_t) &dynstr_used[0] + DYNSTR_SZ - (uintptr_t) &shdrs[0], 139 | .p_align = PAGE_SIZE 140 | }; 141 | phdrs[2] = (Elf64_Phdr) { 142 | .p_type = PT_LOAD, 143 | .p_flags = PF_R | PF_X, 144 | .p_offset = OFFSET_TO_FIRST_USER_WORD, 145 | .p_vaddr = OFFSET_TO_FIRST_USER_WORD, 146 | .p_paddr = OFFSET_TO_FIRST_USER_WORD, 147 | .p_filesz = TEXT_SZ, 148 | .p_memsz = TEXT_SZ, 149 | .p_align = PAGE_SIZE 150 | }; 151 | phdrs[3] = (Elf64_Phdr) { 152 | .p_type = PT_LOAD, 153 | .p_flags = PF_R, 154 | .p_offset = OFFSET_TO_FIRST_USER_WORD + TEXT_SZ, 155 | .p_vaddr = OFFSET_TO_FIRST_USER_WORD + TEXT_SZ, 156 | .p_paddr = OFFSET_TO_FIRST_USER_WORD + TEXT_SZ, 157 | .p_filesz = RODATA_SZ, 158 | .p_memsz = RODATA_SZ, 159 | .p_align = PAGE_SIZE 160 | }; 161 | phdrs[4] = (Elf64_Phdr) { 162 | .p_type = PT_LOAD, 163 | .p_flags = PF_R | PF_W, 164 | .p_offset = OFFSET_TO_FIRST_USER_WORD + TEXT_SZ + RODATA_SZ, 165 | .p_vaddr = OFFSET_TO_FIRST_USER_WORD + TEXT_SZ + RODATA_SZ, 166 | .p_paddr = OFFSET_TO_FIRST_USER_WORD + TEXT_SZ + RODATA_SZ, 167 | .p_filesz = DATA_SZ, // could implement bss here 168 | .p_memsz = DATA_SZ, 169 | .p_align = PAGE_SIZE 170 | }; 171 | phdrs[5] = (Elf64_Phdr) { // writable mapping of text 172 | .p_type = PT_LOAD, 173 | .p_flags = PF_R | PF_W, 174 | .p_offset = OFFSET_TO_FIRST_USER_WORD, 175 | .p_vaddr = OFFSET_TO_FIRST_USER_WORD + TEXT_WRITABLE_VADDR_DELTA, 176 | .p_paddr = OFFSET_TO_FIRST_USER_WORD + TEXT_WRITABLE_VADDR_DELTA, 177 | .p_filesz = TEXT_SZ, 178 | .p_memsz = TEXT_SZ, 179 | .p_align = PAGE_SIZE 180 | }; 181 | phdrs[6] = (Elf64_Phdr) { // writable mapping of text 182 | .p_type = PT_GNU_STACK, 183 | .p_flags = PF_R | PF_W, 184 | .p_offset = 0, 185 | .p_vaddr = 0, 186 | .p_paddr = 0, 187 | .p_filesz = 0, 188 | .p_memsz = 0, 189 | .p_align = 0x10 190 | }; 191 | // }; 192 | 193 | #define NDX_SHSTRTAB 1 194 | #define NDX_DYNAMIC 2 195 | #define NDX_DYNSYM 3 196 | #define NDX_RELA_DYN 4 197 | #define NDX_HASH 5 198 | #define NDX_DYNSTR 6 199 | #define NDX_TEXT 7 200 | #define NDX_RODATA 8 201 | #define NDX_DATA 9 202 | // FIXME: while we're using g++ and it doesn't support nontrivial designated 203 | // initializers, we don't designate even though we'd like to. 204 | shdrs 205 | [0] = (Elf64_Shdr) { // null 206 | .sh_name = 0, 207 | .sh_type = 0, 208 | .sh_flags = 0, 209 | .sh_addr = 0, 210 | .sh_offset = 0, 211 | .sh_size = 0, 212 | .sh_link = 0, 213 | .sh_info = 0, 214 | .sh_addralign = 0, 215 | .sh_entsize = 0 216 | }; 217 | shdrs[NDX_SHSTRTAB] = (Elf64_Shdr) { // shstrtab 218 | .sh_name = 1, 219 | .sh_type = SHT_STRTAB, 220 | .sh_flags = 0, 221 | .sh_addr = 0, 222 | .sh_offset = (uintptr_t) &shstrtab[0] - (uintptr_t) &ehdr, 223 | .sh_size = sizeof shstrtab, 224 | .sh_link = 0, 225 | .sh_info = 0, 226 | .sh_addralign = 1, 227 | .sh_entsize = 0 228 | }; 229 | shdrs[NDX_DYNAMIC] = (Elf64_Shdr) { // dynamic 230 | .sh_name = 53, 231 | .sh_type = SHT_DYNAMIC, 232 | .sh_flags = SHF_ALLOC | SHF_WRITE, 233 | .sh_addr = (uintptr_t) &dynamic[0] - (uintptr_t) &ehdr, 234 | .sh_offset = (uintptr_t) &dynamic[0] - (uintptr_t) &ehdr, 235 | .sh_size = sizeof dynamic, 236 | .sh_link = NDX_DYNSTR, 237 | .sh_info = 0, 238 | .sh_addralign = sizeof (Elf64_Dyn), 239 | .sh_entsize = sizeof (Elf64_Dyn) 240 | }; 241 | shdrs[NDX_DYNSYM] = (Elf64_Shdr) { // dynsym 242 | .sh_name = 31, 243 | .sh_type = SHT_DYNSYM, 244 | .sh_flags = SHF_ALLOC, 245 | .sh_addr = (uintptr_t) &dynsym[0] - (uintptr_t) &ehdr, 246 | .sh_offset = (uintptr_t) &dynsym[0] - (uintptr_t) &ehdr, 247 | .sh_size = sizeof dynsym, 248 | .sh_link = NDX_DYNSTR, 249 | .sh_info = MAX_SYMS - 1, /* index of the first non-local sym */ 250 | .sh_addralign = sizeof (Elf64_Sym), 251 | .sh_entsize = sizeof (Elf64_Sym) 252 | }; 253 | shdrs[NDX_RELA_DYN] = (Elf64_Shdr) { // rela.dyn 254 | .sh_name = 62, 255 | .sh_type = SHT_RELA, 256 | .sh_flags = SHF_ALLOC, 257 | .sh_addr = (uintptr_t) &rela_dyn[0] - (uintptr_t) &ehdr, 258 | .sh_offset = (uintptr_t) &rela_dyn[0] - (uintptr_t) &ehdr, 259 | .sh_size = sizeof rela_dyn, 260 | .sh_link = NDX_DYNSYM, /* dynsym section index */ 261 | .sh_info = 0, 262 | .sh_addralign = sizeof (Elf64_Rela), 263 | .sh_entsize = sizeof (Elf64_Rela) 264 | }; 265 | shdrs[NDX_HASH] = (Elf64_Shdr) { // hash 266 | .sh_name = 47, 267 | .sh_type = SHT_HASH, 268 | .sh_flags = SHF_ALLOC, 269 | .sh_addr = (uintptr_t) &hash[0] - (uintptr_t) &ehdr, 270 | .sh_offset = (uintptr_t) &hash[0] - (uintptr_t) &ehdr, 271 | .sh_size = sizeof hash, 272 | .sh_link = NDX_DYNSYM, /* dynsym section index */ 273 | .sh_info = 0, 274 | .sh_addralign = sizeof (Elf64_Word), 275 | .sh_entsize = sizeof (Elf64_Word) 276 | }; 277 | shdrs[NDX_DYNSTR] = (Elf64_Shdr) { // dynstr 278 | .sh_name = 39, 279 | .sh_type = SHT_STRTAB, 280 | .sh_flags = SHF_ALLOC, 281 | .sh_addr = (uintptr_t) &dynstr_used[0] - (uintptr_t) &ehdr, 282 | .sh_offset = (uintptr_t) &dynstr_used[0] - (uintptr_t) &ehdr, 283 | .sh_size = DYNSTR_SZ, 284 | .sh_link = 0, 285 | .sh_info = 0, 286 | .sh_addralign = 1, 287 | .sh_entsize = 0 288 | }; 289 | shdrs[NDX_TEXT] = (Elf64_Shdr) { // text 290 | .sh_name = 11, 291 | .sh_type = SHT_PROGBITS, 292 | .sh_flags = SHF_ALLOC | SHF_EXECINSTR, 293 | .sh_addr = OFFSET_TO_FIRST_USER_WORD, 294 | .sh_offset = OFFSET_TO_FIRST_USER_WORD, 295 | .sh_size = TEXT_SZ, 296 | .sh_link = 0, 297 | .sh_info = 0, 298 | .sh_addralign = 16, 299 | .sh_entsize = 0 300 | }; 301 | shdrs[NDX_RODATA] = (Elf64_Shdr) { // rodata 302 | .sh_name = 23, 303 | .sh_type = SHT_PROGBITS, 304 | .sh_flags = SHF_ALLOC, 305 | .sh_addr = OFFSET_TO_FIRST_USER_WORD + TEXT_SZ, 306 | .sh_offset = OFFSET_TO_FIRST_USER_WORD + TEXT_SZ, 307 | .sh_size = RODATA_SZ, 308 | .sh_link = 0, 309 | .sh_info = 0, 310 | .sh_addralign = sizeof (void*), 311 | .sh_entsize = 0 312 | }; 313 | shdrs[NDX_DATA] = (Elf64_Shdr) { // data 314 | .sh_name = 17, 315 | .sh_type = SHT_PROGBITS, 316 | .sh_flags = SHF_ALLOC | SHF_WRITE, 317 | .sh_addr = OFFSET_TO_FIRST_USER_WORD + TEXT_SZ + RODATA_SZ, 318 | .sh_offset = OFFSET_TO_FIRST_USER_WORD + TEXT_SZ + RODATA_SZ, 319 | .sh_size = DATA_SZ, 320 | .sh_link = 0, 321 | .sh_info = 0, 322 | .sh_addralign = sizeof (void*), 323 | .sh_entsize = 0 324 | }; 325 | // bss? 326 | //}; 327 | 328 | dynamic 329 | [0] = (Elf64_Dyn) { 330 | .d_tag = DT_HASH, 331 | .d_un = { d_ptr: (Elf64_Addr) shdrs[NDX_HASH].sh_addr } 332 | }; 333 | dynamic[1] = (Elf64_Dyn) { 334 | .d_tag = DT_STRTAB, 335 | .d_un = { d_ptr: (Elf64_Addr) shdrs[NDX_DYNSTR].sh_addr } 336 | }; 337 | dynamic[2] = (Elf64_Dyn) { 338 | .d_tag = DT_SYMTAB, 339 | .d_un = { d_ptr: (Elf64_Addr) shdrs[NDX_DYNSYM].sh_addr } 340 | }; 341 | dynamic[3] = (Elf64_Dyn) { 342 | .d_tag = DT_SYMENT, 343 | .d_un = { d_val: sizeof (Elf64_Sym) } 344 | }, 345 | dynamic[4] = (Elf64_Dyn) { 346 | .d_tag = DT_RELA, 347 | .d_un = { d_ptr: (Elf64_Addr) shdrs[NDX_RELA_DYN].sh_addr } 348 | }; 349 | dynamic[5] = (Elf64_Dyn) { 350 | .d_tag = DT_RELASZ, 351 | .d_un = { d_val: sizeof rela_dyn } 352 | }; 353 | dynamic[6] = (Elf64_Dyn) { 354 | .d_tag = DT_RELAENT, 355 | .d_un = { d_val: sizeof (Elf64_Rela) } 356 | }; 357 | dynamic[7] = (Elf64_Dyn) { 358 | .d_tag = DT_STRSZ, 359 | .d_un = { d_val: DYNSTR_SZ } 360 | }; 361 | dynamic[8] = (Elf64_Dyn) { 362 | .d_tag = DT_DLBIND_TEXTBUMP, 363 | .d_un = { d_ptr: shdrs[NDX_TEXT].sh_addr } 364 | }; 365 | dynamic[9] = (Elf64_Dyn) { 366 | .d_tag = DT_DLBIND_RODATABUMP, 367 | .d_un = { d_val: shdrs[NDX_RODATA].sh_addr } 368 | }; 369 | dynamic[10] = (Elf64_Dyn) { 370 | .d_tag = DT_DLBIND_DATABUMP, 371 | .d_un = { d_val: shdrs[NDX_DATA].sh_addr } 372 | }; 373 | dynamic[11] = (Elf64_Dyn) { 374 | .d_tag = DT_DLBIND_DYNSTRBUMP, 375 | .d_un = { d_val: 17 } 376 | }; 377 | dynamic[12] = (Elf64_Dyn) { 378 | .d_tag = DT_DLBIND_DYNSYMBUMP, 379 | .d_un = { d_val: MAX_SYMS - 2 } 380 | }; 381 | dynamic[13] = (Elf64_Dyn) { 382 | .d_tag = DT_NULL, 383 | .d_un = { d_val: 0 } 384 | }; 385 | // }; 386 | 387 | dynsym 388 | [0] = (Elf64_Sym) { 389 | .st_name = 0, 390 | .st_info = 0, 391 | .st_other = 0, 392 | .st_shndx = 0, 393 | .st_value = 0, 394 | .st_size = 0 395 | }; 396 | dynsym[1] = (Elf64_Sym) { // define a local symbol for the shdrs 397 | .st_name = 10, /* _SHDRS */ 398 | .st_info = ELF64_ST_INFO(STB_GLOBAL, STT_OBJECT), 399 | .st_other = ELF64_ST_VISIBILITY(STV_DEFAULT), 400 | .st_shndx = SHN_ABS, 401 | .st_value = phdrs[1].p_vaddr, 402 | .st_size = sizeof shdrs 403 | }; /* , // do this in the init function, until g++ supports designated initializers 404 | 405 | /*[MAX_SYMS - 1] = (Elf64_Sym) { // _DYNAMIC 406 | .st_name = 1, 407 | .st_info = ELF64_ST_INFO(STB_GLOBAL, STT_NOTYPE), 408 | .st_other = ELF64_ST_VISIBILITY(STV_DEFAULT), 409 | .st_shndx = 0, 410 | .st_value = shdrs[NDX_DYNAMIC].sh_addr, 411 | .st_size = sizeof dynamic 412 | }*/ 413 | //}; 414 | 415 | //static Elf64_Rela rela_dyn[RELA_DYN_N] __attribute__((section(".elf_zygote"))) = { 416 | // /* do we need RELATIVEs for the .dynamic section? */ 417 | //}; 418 | 419 | 420 | dynsym[MAX_SYMS - 1/* 2 */] = (Elf64_Sym) { // _DYNAMIC 421 | .st_name = 1, 422 | .st_info = ELF64_ST_INFO(STB_GLOBAL, STT_OBJECT), 423 | .st_other = ELF64_ST_VISIBILITY(STV_DEFAULT), 424 | .st_shndx = NDX_DYNAMIC, 425 | .st_value = /* NO! can't do this, because C++ initialization and destruction 426 | is weird: shdrs is initialized by code. */ // shdrs[NDX_DYNAMIC].sh_addr, 427 | // duplicate the address calculation instead 428 | (uintptr_t) &dynamic[0] - (uintptr_t) &ehdr, 429 | .st_size = sizeof dynamic 430 | }; 431 | // _SHDRS is a local sym 432 | 433 | elf64_hash_init( 434 | (char *) &hash[0], /* hash section */ 435 | sizeof hash, /* hash section size in bytes */ 436 | NBUCKET, /* nbucket */ 437 | MAX_SYMS, 438 | &dynsym[0], 439 | &dynstr_used[0] 440 | ); 441 | _dlbind_elfproto_headerscn_sz = OFFSET_TO_FIRST_USER_WORD; 442 | _dlbind_elfproto_memsz = _dlbind_elfproto_headerscn_sz + TEXT_SZ + DATA_SZ + RODATA_SZ; 443 | _dlbind_elfproto_begin = &ehdr; 444 | done_init = 1; 445 | } 446 | 447 | void memcpy_elfproto_to(void *dest) 448 | { 449 | /* Copy in the ELF proto contents. We don't need to copy the actual sections 450 | * area, which should all be zero. And be even more clever about sparseness, 451 | * since large parts of the hash, dynsym and dynstr are initially zeroed too. */ 452 | 453 | size_t offset0 = (char*) &dynsym[2] - (char*) &ehdr; 454 | size_t offset1 = (char*) &dynsym[MAX_SYMS - 1] - (char*) &ehdr; 455 | size_t offset2 = (char*) &hash[2 + NBUCKET + 2] - (char*) &ehdr; 456 | size_t offset3 = (char*) &hash[2 + NBUCKET + MAX_SYMS - 1] - (char*) &ehdr; 457 | size_t offset4 = &dynstr_used[0] + sizeof dynstr_used - (char*) &ehdr; 458 | size_t offset5 = &dynstr_used[0] + DYNSTR_SZ - (char*) &ehdr; 459 | size_t offset6 = OFFSET_TO_FIRST_USER_WORD; 460 | assert(offset6 < _dlbind_elfproto_memsz); 461 | 462 | // first chunk is up to dynsym's first two symbols 463 | memcpy( dest, _dlbind_elfproto_begin, offset0); 464 | // second chunk is from dynsym's last symbol to the hash chain entry for the first two symbols 465 | memcpy((char*) dest + offset1, _dlbind_elfproto_begin + offset1, offset2 - offset1); 466 | // third chunk is from the hash entry for the hash chain entry for the last symbol to the end of dynstr_used 467 | memcpy((char*) dest + offset3, _dlbind_elfproto_begin + offset3, offset4 - offset3); 468 | // fourth chunk is from the end of dynstr to the first user word 469 | memcpy((char*) dest + offset5, _dlbind_elfproto_begin + offset5, offset6 - offset5); 470 | } 471 | 472 | #ifdef __cplusplus 473 | extern "C" { 474 | #endif 475 | void __libdlbind_do_init() 476 | { 477 | /* PROBLEM: The C++ compiler compiles the zygote initializers using the 478 | * static initialization mechanism. This might not yet have run when our 479 | * caller forces us, so we'll see all-zeroes content. */ 480 | init(); 481 | } 482 | #ifdef __cplusplus 483 | } 484 | #endif 485 | --------------------------------------------------------------------------------