diff options
author | comex | 2015-01-19 19:12:32 -0500 |
---|---|---|
committer | comex | 2015-01-19 19:12:32 -0500 |
commit | c25b9e2337aad02073a199619f6f754f15cccd38 (patch) | |
tree | 603f3bcdc6eb687c11c077f350cde53fc1700898 /lib/darwin/objc.c | |
parent | some reorganization (diff) | |
download | substitute-c25b9e2337aad02073a199619f6f754f15cccd38.tar.gz |
more reorganization - move OS X/iOS specific stuff into its own directory
Diffstat (limited to 'lib/darwin/objc.c')
-rw-r--r-- | lib/darwin/objc.c | 205 |
1 files changed, 205 insertions, 0 deletions
diff --git a/lib/darwin/objc.c b/lib/darwin/objc.c new file mode 100644 index 0000000..398e3e7 --- /dev/null +++ b/lib/darwin/objc.c @@ -0,0 +1,205 @@ +#if defined(__APPLE__) +#include "substitute.h" +#include "substitute-internal.h" +#include "objc.h" +#include <stddef.h> +#include <pthread.h> +#include <mach/mach.h> +#include <sys/mman.h> +#include <sys/queue.h> +#include <errno.h> + +/* These trampolines will call e->func(e->arg1, e->arg2), and jump to there, + * preserving all arguments. imp_implementationWithBlock would be easier and + * maybe a bit faster, but it's impossible to avoid throwing away registers + * without having to ask whether the selector is stret or not. */ + +struct tramp_info_page_header { + uint32_t magic; + uint32_t version; + struct tramp_info_page_entry *first_free; + size_t nfree; + LIST_ENTRY(tramp_info_page_header) free_pages; +}; + +enum { + TRAMP_MAGIC = 0xf00df17e, + TRAMP_VERSION = 0, +}; + +struct tramp_info_page_entry { + union { + struct tramp_info_page_entry *next_free; + void *func; + }; + void *arg1; + void *arg2; +}; + +_Static_assert(TRAMP_INFO_PAGE_ENTRY_SIZE == sizeof(struct tramp_info_page_entry), + "TRAMP_INFO_PAGE_ENTRY_SIZE"); +_Static_assert(sizeof(struct tramp_info_page_header) + + TRAMPOLINES_PER_PAGE * sizeof(struct tramp_info_page_entry) <= _PAGE_SIZE, + "header+entries too big"); + +static pthread_mutex_t tramp_mutex = PTHREAD_MUTEX_INITIALIZER; +LIST_HEAD(tramp_info_page_list, tramp_info_page_header) + tramp_free_page_list = LIST_HEAD_INITIALIZER(tramp_info_page_list); + +extern char remap_start[]; + +static int get_trampoline(void *func, void *arg1, void *arg2, void *tramp_ptr) { + int ret, rerrno = 0; + pthread_mutex_lock(&tramp_mutex); + + struct tramp_info_page_header *header = LIST_FIRST(&tramp_free_page_list); + if (!header) { + if (PAGE_SIZE > _PAGE_SIZE) + panic("%s: strange PAGE_SIZE %x\n", __func__, PAGE_SIZE); + void *new_pages = mmap(NULL, _PAGE_SIZE * 2, PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_ANON, -1, 0); + if (new_pages == MAP_FAILED) { + ret = SUBSTITUTE_ERR_OOM; + rerrno = errno; + goto out; + } + vm_address_t tramp_page = (vm_address_t) new_pages; + vm_prot_t cur_prot, max_prot; + kern_return_t kr = vm_remap( + mach_task_self(), + &tramp_page, + _PAGE_SIZE, + _PAGE_SIZE - 1, + VM_FLAGS_OVERWRITE | VM_FLAGS_FIXED, + mach_task_self(), + (vm_address_t) remap_start, + FALSE, /* copy */ + &cur_prot, + &max_prot, + VM_INHERIT_NONE); + if (kr != KERN_SUCCESS || tramp_page != (vm_address_t) new_pages) { + ret = SUBSTITUTE_ERR_VM; + goto out; + } + header = new_pages + _PAGE_SIZE * 2 - sizeof(*header); + header->magic = TRAMP_MAGIC; + header->version = TRAMP_VERSION; + header->first_free = NULL; + header->nfree = TRAMPOLINES_PER_PAGE; + LIST_INSERT_HEAD(&tramp_free_page_list, header, free_pages); + } + + void *page = (void *) (((uintptr_t) header) & ~(_PAGE_SIZE - 1)); + struct tramp_info_page_entry *entries = page; + struct tramp_info_page_entry *entry = header->first_free; + if (entry == NULL) { + entry = &entries[TRAMPOLINES_PER_PAGE - header->nfree]; + entry->next_free = NULL; + } + + header->first_free = entry->next_free; + if (--header->nfree == 0) + LIST_REMOVE(header, free_pages); + + entry->func = func; + entry->arg1 = arg1; + entry->arg2 = arg2; + void *tramp = (page - PAGE_SIZE) + (entry - entries) * TRAMPOLINE_SIZE; +#ifdef __arm__ + tramp += 1; +#endif + *(void **) tramp_ptr = tramp; + ret = SUBSTITUTE_OK; +out: + pthread_mutex_unlock(&tramp_mutex); + errno = rerrno; + return ret; +} + +static void free_trampoline(void *tramp) { + pthread_mutex_lock(&tramp_mutex); + void *page = (void *) (((uintptr_t) tramp) & ~(_PAGE_SIZE - 1)); + size_t i = (tramp - page) / TRAMPOLINE_SIZE; + struct tramp_info_page_entry *entries = page + _PAGE_SIZE; + struct tramp_info_page_entry *entry = &entries[i]; + struct tramp_info_page_header *header = page + 2 * _PAGE_SIZE - sizeof(*header); + + if (header->magic != TRAMP_MAGIC) + panic("%s: bad pointer\n", __func__); + if (header->version != TRAMP_VERSION) { + /* shouldn't happen, but just in case multiple versions of this library + * are mixed up */ + return; + } + + entry->next_free = header->first_free; + header->first_free = entry; + header->nfree++; + if (header->nfree == 1) + LIST_INSERT_HEAD(&tramp_free_page_list, header, free_pages); + else if (header->nfree == TRAMPOLINES_PER_PAGE && + /* have others? */ + (LIST_FIRST(&tramp_free_page_list) != header || + LIST_NEXT(header, free_pages))) { + /* free the trampoline and info pages */ + LIST_REMOVE(header, free_pages); + munmap(page, 2 * _PAGE_SIZE); + } + + pthread_mutex_unlock(&tramp_mutex); +} + +static IMP dereference(IMP *old_ptr, UNUSED void *_) { + return *old_ptr; +} + +EXPORT +int substitute_hook_objc_message(Class class, SEL selector, void *replacement, + void *old_ptr, bool *created_imp_ptr) { + int ret; + Method meth = class_getInstanceMethod(class, selector); + if (meth == NULL) + return SUBSTITUTE_ERR_NO_SUCH_SELECTOR; + const char *types = method_getTypeEncoding(meth); + + if (created_imp_ptr) + *created_imp_ptr = false; + + /* temporary trampoline just tries again */ + IMP temp = NULL; + if (old_ptr) { + if ((ret = get_trampoline(dereference, old_ptr, NULL, &temp))) + return ret; + *(IMP *) old_ptr = temp; + } + + IMP old = class_replaceMethod(class, selector, replacement, types); + if (old) { + if (old_ptr) + *(IMP *) old_ptr = old; + } else { + if (old_ptr) { + Class super = class_getSuperclass(class); + if (!super) { + /* this ought to only be possible if the method was removed in + * the meantime, since we found the method above and it + * couldn't have been found in a superclass, but the objc2 + * runtime doesn't allow removing methods. */ + panic("%s: no superclass but the method didn't exist\n", __func__); + } + ret = get_trampoline(class_getMethodImplementation, super, selector, old_ptr); + if (created_imp_ptr) + *created_imp_ptr = true; + } + } + + if (temp) + free_trampoline(temp); + return SUBSTITUTE_OK; +} + +EXPORT +void substitute_free_created_imp(IMP imp) { + free_trampoline(imp); +} +#endif |