diff options
author | comex | 2015-01-10 15:40:42 -0500 |
---|---|---|
committer | comex | 2015-01-10 15:40:42 -0500 |
commit | 2ab9357b35d2f8e4e217c570ef8b8c66b7e0a891 (patch) | |
tree | 80d6f0442ad95793c70e54a9ae8e10d18a45dfa3 /lib | |
parent | nah - this is nice looking but nonportable (diff) | |
download | substitute-2ab9357b35d2f8e4e217c570ef8b8c66b7e0a891.tar.gz |
simplify
Diffstat (limited to 'lib')
-rw-r--r-- | lib/find-syms.c | 173 | ||||
-rw-r--r-- | lib/substitute.h | 23 |
2 files changed, 188 insertions, 8 deletions
diff --git a/lib/find-syms.c b/lib/find-syms.c new file mode 100644 index 0000000..e62481a --- /dev/null +++ b/lib/find-syms.c @@ -0,0 +1,173 @@ +#ifdef __APPLE__ + +#include <stdbool.h> +#include <mach-o/loader.h> +#include <mach-o/dyld.h> +#include <mach-o/dyld_images.h> +#include <dlfcn.h> +#include <pthread.h> + +#include "substitute.h" +#include "substitute-internal.h" + +struct LibSystemHelpers { + uintptr_t version; + void (*acquireGlobalDyldLock)(); + void (*releaseGlobalDyldLock)(); + /* ... */ +}; + +extern const struct dyld_all_image_infos *_dyld_get_all_image_infos(); + +static pthread_once_t dyld_inspect_once = PTHREAD_ONCE_INIT; +/* and its fruits: */ +static struct LibSystemHelpers *libsystem_helpers; +static const struct mach_header *(*my_dyld_get_image_header)(uint32_t); +static const char *(*my_dyld_get_image_name)(uint32_t); +static intptr_t (*my_dyld_get_image_vmaddr_slide)(uint32_t); +static uint32_t (*my_dyld_image_count)(); + +#ifdef __LP64__ +typedef struct mach_header_64 mach_header_x; +typedef struct segment_command_64 segment_command_x; +typedef struct section_64 section_x; +#define LC_SEGMENT_X LC_SEGMENT_64 +#else +typedef struct mach_header mach_header_x; +typedef struct segment_command segment_command_x; +typedef struct section section_x; +#define LC_SEGMENT_X LC_SEGMENT +#endif + +static void *sym_to_ptr(substitute_sym *sym, ssize_t slide) { + uintptr_t addr = sym->n_value; + addr += slide; + if (sym->n_desc & N_ARM_THUMB_DEF) + addr |= 1; + return (void *) addr; +} + +static void substitute_find_syms_raw(const void *hdr, ssize_t *slide, const char **names, substitute_sym **syms, size_t count) { + memset(syms, 0, sizeof(*syms) * count); + + /* note: no verification at all */ + const mach_header_x *mh = hdr; + uint32_t ncmds = mh->ncmds; + struct load_command *lc = (void *) (mh + 1); + struct symtab_command syc; + for (uint32_t i = 0; i < ncmds; i++) { + if (lc->cmd == LC_SYMTAB) { + syc = *(struct symtab_command *) lc; + goto ok; + } + lc = (void *) lc + lc->cmdsize; + } + return; /* no symtab, no symbols */ +ok: ; + substitute_sym *symtab = NULL; + const char *strtab = NULL; + lc = (void *) (mh + 1); + for (uint32_t i = 0; i < ncmds; i++) { + if (lc->cmd == LC_SEGMENT_X) { + segment_command_x *sc = (void *) lc; + if (syc.symoff - sc->fileoff < sc->filesize) + symtab = (void *) sc->vmaddr + syc.symoff - sc->fileoff; + if (syc.stroff - sc->fileoff < sc->filesize) + strtab = (void *) sc->vmaddr + syc.stroff - sc->fileoff; + if (*slide == -1 && sc->fileoff == 0) { + // used only for dyld + *slide = (uintptr_t) hdr - sc->vmaddr; + } + if (symtab && strtab) + goto ok2; + } + lc = (void *) lc + lc->cmdsize; + } + return; /* uh... weird */ +ok2: ; + symtab = (void *) symtab + *slide; + strtab = (void *) strtab + *slide; + /* This could be optimized for efficiency with a large number of names... */ + for (uint32_t i = 0; i < syc.nsyms; i++) { + substitute_sym *sym = &symtab[i]; + uint32_t strx = sym->n_un.n_strx; + const char *name = strx == 0 ? "" : strtab + strx; + for (size_t j = 0; j < count; j++) { + if (!strcmp(name, names[j])) { + syms[j] = sym; + break; + } + } + } +} + +/* This is a mess because the usual _dyld_image_count loop is not thread safe. + * Since it uses a std::vector and (a) erases from it (making it possible for a + * loop to skip entries) and (b) and doesn't even lock it in + * _dyld_get_image_header etc., this is true even if the image is guaranteed to + * be found, including the possibility to crash. + * How do we solve this? Inception - we steal dyld's private symbols... + * We could avoid the symbols by calling the vtable of dlopen handles, but that + * seems unstable. + */ + +static void inspect_dyld() { + const struct dyld_all_image_infos *aii = _dyld_get_all_image_infos(); + const void *dyld_hdr = aii->dyldImageLoadAddress; + + const char *names[2] = { "__ZN4dyld17gLibSystemHelpersE", "__dyld_func_lookup" }; + substitute_sym *syms[2]; + ssize_t dyld_slide = -1; + substitute_find_syms_raw(dyld_hdr, &dyld_slide, names, syms, 2); + if (!syms[0] || !syms[1]) + panic("couldn't find dyld::gLibSystemHelpers\n"); + + libsystem_helpers = *(struct LibSystemHelpers **) sym_to_ptr(syms[0], dyld_slide); + if (!libsystem_helpers) + panic("dyld::gLibSystemHelpers was NULL\n"); + + /* We get the internal versions of _dyld_get_image_count and friends in case the normal ones are fixed in the future to use locking (in which case we'd be double locking). */ + int (*_dyld_func_lookup)(const char *name, void **address) = sym_to_ptr(syms[1], dyld_slide); + if (!_dyld_func_lookup("__dyld_get_image_header", (void **) &my_dyld_get_image_header) || + !_dyld_func_lookup("__dyld_get_image_vmaddr_slide", (void **) &my_dyld_get_image_vmaddr_slide) || + !_dyld_func_lookup("__dyld_get_image_name", (void **) &my_dyld_get_image_name) || + !_dyld_func_lookup("__dyld_image_count", (void **) &my_dyld_image_count)) { + panic("dyld_func_lookup failure\n"); + } +} + +/* 'dlhand' keeps the image alive */ +static bool find_image_hdr_and_slide(const char *filename, const void **hdr, ssize_t *slide, void **dlhand) { + /* this is just for the refcount; maybe unnecessary for current APIs */ + *dlhand = dlopen(filename, RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD); + if (!*dlhand) + return false; + + pthread_once(&dyld_inspect_once, inspect_dyld); + libsystem_helpers->acquireGlobalDyldLock(); + for (uint32_t i = 0, cnt = my_dyld_image_count(); i < cnt; i++) { + const char *name = my_dyld_get_image_name(i); + printf("%s < %s \n", name, filename); + if (!strcmp(name, filename)) { + *hdr = my_dyld_get_image_header(i); + *slide = my_dyld_get_image_vmaddr_slide(i); + libsystem_helpers->releaseGlobalDyldLock(); + return true; + } + } + panic("%s: found in dlopen but not _dyld_get_image_name\n", __func__); +} + +int substitute_find_syms(const char *filename, const char **names, + substitute_sym **syms, size_t count) { + const void *hdr; + ssize_t slide; + void *dlhand; + if (!find_image_hdr_and_slide(filename, &hdr, &slide, &dlhand)) + return SUBSTITUTE_ERR_MODULE_NOT_FOUND; + substitute_find_syms_raw(hdr, &slide, names, syms, count); + dlclose(dlhand); + return SUBSTITUTE_OK; +} + +#endif diff --git a/lib/substitute.h b/lib/substitute.h index cadfa80..d4287fb 100644 --- a/lib/substitute.h +++ b/lib/substitute.h @@ -5,6 +5,8 @@ #pragma once +#include <stdlib.h> + // TODO add numbers enum { SUBSTITUTE_OK = 0, @@ -14,26 +16,31 @@ enum { int substitute_hook_function(void *function, void *replacement, int options, void *result); -/* Darwin specific */ +#if 1 /* declare substitute_find_syms? */ + #ifdef __APPLE__ #include <mach-o/nlist.h> #ifdef __LP64__ -typedef struct nlist_64 substitute_nlist; +typedef struct nlist_64 substitute_sym; #else -typedef struct nlist substitute_nlist; +typedef struct nlist substitute_sym; +#endif +#else +#error No definition for substitute_sym! #endif /* Look up private symbols in an image currently loaded into the process. - * Vaguely inspired by nlist(3), but somewhat different. * - * @filename the executable/library path (c.f. dyld(3)) + * @filename the executable/library path (c.f. dyld(3) on Darwin) * @names an array of symbol names to search for - * @nlist an array of substitute_nlist *, one per name; on return, each entry + * @nlist an array of substitute_sym *, one per name; on return, each entry * will be a pointer into the symbol table for that image, or NULL if the * symbol wasn't found * @count number of names * * @return SUBSTITUTE_OK or SUBSTITUTE_ERR_MODULE_NOT_FOUND */ -int substitute_nlist(const char *filename, const char **names, - substitute_nlist **nlist, size_t count); +int substitute_find_syms(const char *filename, const char **names, + substitute_sym **syms, size_t count); + +#endif /* 1 */ |