aboutsummaryrefslogtreecommitdiff
path: root/lib/darwin
diff options
context:
space:
mode:
authorcomex2015-01-19 19:12:32 -0500
committercomex2015-01-19 19:12:32 -0500
commitc25b9e2337aad02073a199619f6f754f15cccd38 (patch)
tree603f3bcdc6eb687c11c077f350cde53fc1700898 /lib/darwin
parentsome reorganization (diff)
downloadsubstitute-c25b9e2337aad02073a199619f6f754f15cccd38.tar.gz
more reorganization - move OS X/iOS specific stuff into its own directory
Diffstat (limited to 'lib/darwin')
-rw-r--r--lib/darwin/find-syms.c142
-rw-r--r--lib/darwin/interpose.c224
-rw-r--r--lib/darwin/objc-asm.S72
-rw-r--r--lib/darwin/objc.c205
-rw-r--r--lib/darwin/objc.h23
-rw-r--r--lib/darwin/substrate-compat.c55
6 files changed, 721 insertions, 0 deletions
diff --git a/lib/darwin/find-syms.c b/lib/darwin/find-syms.c
new file mode 100644
index 0000000..44adf78
--- /dev/null
+++ b/lib/darwin/find-syms.c
@@ -0,0 +1,142 @@
+#ifdef __APPLE__
+
+#include <stdbool.h>
+#include <dlfcn.h>
+#include <pthread.h>
+
+#include "substitute.h"
+#include "substitute-internal.h"
+
+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 uintptr_t (*ImageLoaderMachO_getSlide)(void *);
+static const struct mach_header *(*ImageLoaderMachO_machHeader)(void *);
+
+static void *sym_to_ptr(substitute_sym *sym, intptr_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 find_syms_raw(const void *hdr, intptr_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. As is,
+ * the method used is somewhat convoluted in an attempt to maximize stability.
+ */
+
+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] = { "__ZNK16ImageLoaderMachO8getSlideEv", "__ZNK16ImageLoaderMachO10machHeaderEv" };
+ substitute_sym *syms[2];
+ intptr_t dyld_slide = -1;
+ find_syms_raw(dyld_hdr, &dyld_slide, names, syms, 2);
+ if (!syms[0] || !syms[1])
+ substitute_panic("couldn't find ImageLoader methods\n");
+ ImageLoaderMachO_getSlide = sym_to_ptr(syms[0], dyld_slide);
+ ImageLoaderMachO_machHeader = sym_to_ptr(syms[1], dyld_slide);
+}
+
+/* 'dlopen_header' keeps the image alive */
+EXPORT
+struct substitute_image *substitute_open_image(const char *filename) {
+ pthread_once(&dyld_inspect_once, inspect_dyld);
+
+ void *dlhandle = dlopen(filename, RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD);
+ if (!dlhandle)
+ return NULL;
+
+ const void *image_header = ImageLoaderMachO_machHeader(dlhandle);
+ intptr_t slide = ImageLoaderMachO_getSlide(dlhandle);
+
+ struct substitute_image *im = malloc(sizeof(*im));
+ if (!im)
+ return NULL;
+ im->slide = slide;
+ im->dlhandle = dlhandle;
+ im->image_header = image_header;
+ return im;
+}
+
+EXPORT
+void substitute_close_image(struct substitute_image *im) {
+ dlclose(im->dlhandle); /* ignore errors */
+ free(im);
+}
+
+EXPORT
+int substitute_find_private_syms(struct substitute_image *im, const char **names,
+ substitute_sym **syms, size_t nsyms) {
+ find_syms_raw(im->image_header, &im->slide, names, syms, nsyms);
+ return SUBSTITUTE_OK;
+}
+
+EXPORT
+void *substitute_sym_to_ptr(struct substitute_image *handle, substitute_sym *sym) {
+ return sym_to_ptr(sym, handle->slide);
+}
+
+#endif /* __APPLE__ */
diff --git a/lib/darwin/interpose.c b/lib/darwin/interpose.c
new file mode 100644
index 0000000..06a357f
--- /dev/null
+++ b/lib/darwin/interpose.c
@@ -0,0 +1,224 @@
+#ifdef __APPLE__
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#include "substitute.h"
+#include "substitute-internal.h"
+
+struct interpose_state {
+ size_t nsegments;
+ segment_command_x **segments;
+ size_t max_segments;
+ uintptr_t slide;
+ const struct substitute_import_hook *hooks;
+ size_t nhooks;
+ segment_command_x *stack_segments[32];
+};
+
+static uintptr_t read_leb128(void **ptr, void *end, bool is_signed) {
+ uintptr_t result = 0;
+ uint8_t *p = *ptr;
+ uint8_t bit;
+ unsigned int shift = 0;
+ do {
+ if (p >= (uint8_t *) end)
+ return 0;
+ bit = *p++;
+ uintptr_t k = bit & 0x7f;
+ if (shift < sizeof(uintptr_t) * 8)
+ result |= k << shift;
+ shift += 7;
+ } while (bit & 0x80);
+ if (is_signed && (bit & 0x40))
+ result |= ~((uintptr_t) 0) << shift;
+ *ptr = p;
+ return result;
+}
+
+static inline char *read_cstring(void **ptr, void *end) {
+ char *s = *ptr;
+ *ptr = s + strnlen(s, (char *) end - s);
+ return s;
+}
+
+static int try_bind_section(void *bind, size_t size, const struct interpose_state *st,
+ bool lazy) {
+ void *ptr = bind, *end = bind + size;
+ const char *sym = NULL;
+ uint8_t type = lazy ? BIND_TYPE_POINTER : 0;
+ intptr_t addend = 0;
+ size_t offset = 0;
+ void *segment = NULL;
+ while (ptr < end) {
+ uint8_t byte = *(uint8_t *) ptr;
+ ptr++;
+ uint8_t immediate = byte & BIND_IMMEDIATE_MASK;
+ uint8_t opcode = byte & BIND_OPCODE_MASK;
+
+ uintptr_t count, stride;
+
+ switch(opcode) {
+ case BIND_OPCODE_DONE:
+ case BIND_OPCODE_SET_DYLIB_ORDINAL_IMM:
+ case BIND_OPCODE_SET_DYLIB_SPECIAL_IMM:
+ break;
+ case BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB:
+ read_leb128(&ptr, end, false);
+ break;
+ case BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM:
+ sym = read_cstring(&ptr, end);
+ /* ignoring flags for now */
+ break;
+ case BIND_OPCODE_SET_TYPE_IMM:
+ type = immediate;
+ break;
+ case BIND_OPCODE_SET_ADDEND_SLEB:
+ addend = read_leb128(&ptr, end, true);
+ break;
+ case BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB:
+ if (immediate < st->nsegments)
+ segment = (void *) (st->segments[immediate]->vmaddr + st->slide);
+ offset = read_leb128(&ptr, end, false);
+ break;
+ case BIND_OPCODE_ADD_ADDR_ULEB:
+ offset += read_leb128(&ptr, end, false);
+ break;
+ case BIND_OPCODE_DO_BIND:
+ count = 1;
+ stride = sizeof(void *);
+ goto bind;
+ case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB:
+ count = 1;
+ stride = read_leb128(&ptr, end, false) + sizeof(void *);
+ goto bind;
+ case BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED:
+ count = 1;
+ stride = immediate * sizeof(void *) + sizeof(void *);
+ goto bind;
+ case BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB:
+ count = read_leb128(&ptr, end, false);
+ stride = read_leb128(&ptr, end, false) + sizeof(void *);
+ goto bind;
+ bind:
+ if (segment && sym) {
+ const struct substitute_import_hook *h;
+ size_t i;
+ for (i = 0; i < st->nhooks; i++) {
+ h = &st->hooks[i];
+ if (!strcmp(sym, h->name))
+ break;
+ }
+ if (i != st->nhooks) {
+ while (count--) {
+ uintptr_t new = (uintptr_t) h->replacement + addend;
+ uintptr_t old;
+ void *p = (void *) (segment + offset);
+ switch (type) {
+ case BIND_TYPE_POINTER: {
+ old = __atomic_exchange_n((uintptr_t *) p, new, __ATOMIC_RELAXED);
+ break;
+ }
+ case BIND_TYPE_TEXT_ABSOLUTE32: {
+ if ((uint32_t) new != new) {
+ /* text rels should only show up on i386, where
+ * this is impossible... */
+ panic("bad TEXT_ABSOLUTE32 rel\n");
+ }
+ old = __atomic_exchange_n((uint32_t *) p, (uint32_t) new, __ATOMIC_RELAXED);
+ break;
+ }
+ case BIND_TYPE_TEXT_PCREL32: {
+ uintptr_t pc = (uintptr_t) p + 4;
+ uintptr_t rel = new - pc;
+ if ((uint32_t) rel != rel) {
+ /* ditto */
+ panic("bad TEXT_ABSOLUTE32 rel\n");
+ }
+ old = __atomic_exchange_n((uint32_t *) p, (uint32_t) rel, __ATOMIC_RELAXED);
+ old += pc;
+ break;
+ }
+ default:
+ panic("unknown relocation type\n");
+ break;
+ }
+ if (h->old_ptr)
+ *(uintptr_t *) h->old_ptr = old - addend;
+ }
+ break;
+ }
+ }
+ offset += count * stride;
+ break;
+ }
+ }
+ return SUBSTITUTE_OK;
+}
+
+static void *off_to_addr(const struct interpose_state *st, uint32_t off) {
+ for (size_t i = 0; i < st->nsegments; i++) {
+ const segment_command_x *sc = st->segments[i];
+ if ((off - sc->fileoff) < sc->filesize)
+ return (void *) (sc->vmaddr + st->slide + off - sc->fileoff);
+ }
+ return NULL;
+}
+
+EXPORT
+int substitute_interpose_imports(const struct substitute_image *image,
+ const struct substitute_import_hook *hooks,
+ size_t nhooks, int options) {
+ int ret = SUBSTITUTE_OK;
+
+ if (options != 0)
+ panic("%s: unrecognized options\n", __func__);
+
+ struct interpose_state st;
+ st.slide = image->slide;
+ st.nsegments = 0;
+ st.hooks = hooks;
+ st.nhooks = nhooks;
+ st.segments = st.stack_segments;
+ st.max_segments = sizeof(st.stack_segments) / sizeof(*st.stack_segments);
+
+ const mach_header_x *mh = image->image_header;
+ const struct load_command *lc = (void *) (mh + 1);
+ for (uint32_t i = 0; i < mh->ncmds; i++) {
+ if (lc->cmd == LC_SEGMENT_X) {
+ segment_command_x *sc = (void *) lc;
+ if (st.nsegments == st.max_segments) {
+ segment_command_x **new = calloc(st.nsegments * 2, sizeof(*st.segments));
+ if (!new)
+ substitute_panic("%s: out of memory\n", __func__);
+ memcpy(new, st.segments, st.nsegments * sizeof(*st.segments));
+ if (st.segments != st.stack_segments)
+ free(st.segments);
+ st.segments = new;
+ }
+ st.segments[st.nsegments++] = sc;
+ }
+ lc = (void *) lc + lc->cmdsize;
+ }
+
+ lc = (void *) (mh + 1);
+ for (uint32_t i = 0; i < mh->ncmds; i++) {
+ if (lc->cmd == LC_DYLD_INFO || lc->cmd == LC_DYLD_INFO_ONLY) {
+ struct dyld_info_command *dc = (void *) lc;
+ int ret;
+ if ((ret = try_bind_section(off_to_addr(&st, dc->bind_off), dc->bind_size, &st, false)) ||
+ (ret = try_bind_section(off_to_addr(&st, dc->weak_bind_off), dc->weak_bind_size, &st, false)) ||
+ (ret = try_bind_section(off_to_addr(&st, dc->lazy_bind_off), dc->lazy_bind_size, &st, true)))
+ goto fail;
+
+ break;
+ }
+ lc = (void *) lc + lc->cmdsize;
+ }
+fail:
+ if (st.segments != st.stack_segments)
+ free(st.segments);
+ return ret;
+}
+
+#endif /* __APPLE__ */
diff --git a/lib/darwin/objc-asm.S b/lib/darwin/objc-asm.S
new file mode 100644
index 0000000..bebce80
--- /dev/null
+++ b/lib/darwin/objc-asm.S
@@ -0,0 +1,72 @@
+#include "objc.h"
+.text
+.align _PAGE_SHIFT
+#ifdef __arm__
+.thumb_func _remap_start
+.thumb
+#endif
+.globl _remap_start
+_remap_start:
+
+.set i, 0
+#define my_rpe (0b + (_PAGE_SIZE - i * TRAMPOLINE_SIZE + i * TRAMP_INFO_PAGE_ENTRY_SIZE))
+.rept TRAMPOLINES_PER_PAGE
+0:
+#if defined(__x86_64__)
+ /* double push for align */
+ push %rdi; push %rsi; push %rdx; push %rcx; push %r8; push %r9; push %r9
+ lea my_rpe(%rip), %rdx
+ mov 8(%rdx), %rdi
+ mov 16(%rdx), %rsi
+ call *(%rdx)
+ pop %r9; pop %r9; pop %r8; pop %rcx; pop %rdx; pop %rsi; pop %rdi
+ jmp *%rax
+#elif defined(__i386__)
+ call 1f
+1:
+ pop %edx
+ lea my_rpe-1b(%edx), %edx
+ push 8(%edx)
+ push 4(%edx)
+ call *(%edx)
+ add $$8, %esp
+ jmp *%eax
+#elif defined(__arm__)
+ push {r0-r4, lr} /* r4 for align */
+ mov r3, #(my_rpe - (1f + 2))
+ add r3, pc
+1:
+ ldr r0, [r3, #4]
+ ldr r1, [r3, #8]
+ ldr r2, [r3]
+ blx r2
+ mov r9, r0
+ pop {r0-r4, lr}
+ bx r9
+#elif defined(__arm64__)
+ stp x30, x8, [sp, #-0x10]!
+ stp x7, x6, [sp, #-0x10]!
+ stp x5, x4, [sp, #-0x10]!
+ stp x3, x2, [sp, #-0x10]!
+ stp x1, x0, [sp, #-0x10]!
+
+ ldr x0, my_rpe+8
+ ldr x1, my_rpe+0x10
+ ldr x2, my_rpe
+ blr x2
+ mov x9, x0
+
+ ldp x1, x0, [sp], #0x10
+ ldp x3, x2, [sp], #0x10
+ ldp x5, x4, [sp], #0x10
+ ldp x7, x6, [sp], #0x10
+ ldp x30, x8, [sp], #0x10
+
+ br x9
+#else
+#error No forwarding assembly definition for this arch
+#endif
+
+.set i, i + 1
+.endr
+
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
diff --git a/lib/darwin/objc.h b/lib/darwin/objc.h
new file mode 100644
index 0000000..9628c35
--- /dev/null
+++ b/lib/darwin/objc.h
@@ -0,0 +1,23 @@
+#pragma once
+/* PAGE_SIZE is not actually a constant on iOS */
+#if defined(__arm64__)
+#define _PAGE_SHIFT 14
+#else
+#define _PAGE_SHIFT 12
+#endif
+#define _PAGE_SIZE (1 << _PAGE_SHIFT)
+#if defined(__x86_64__)
+#define TRAMPOLINE_SIZE 0x27
+#elif defined(__i386__)
+#define TRAMPOLINE_SIZE 0x19
+#elif defined(__arm__)
+#define TRAMPOLINE_SIZE 0x18
+#elif defined(__arm64__)
+#define TRAMPOLINE_SIZE 0x40
+#endif
+#ifdef __LP64__
+#define TRAMP_INFO_PAGE_ENTRY_SIZE 24
+#else
+#define TRAMP_INFO_PAGE_ENTRY_SIZE 12
+#endif
+#define TRAMPOLINES_PER_PAGE (_PAGE_SIZE / TRAMPOLINE_SIZE)
diff --git a/lib/darwin/substrate-compat.c b/lib/darwin/substrate-compat.c
new file mode 100644
index 0000000..3658ac7
--- /dev/null
+++ b/lib/darwin/substrate-compat.c
@@ -0,0 +1,55 @@
+#include "substitute.h"
+#include "substitute-internal.h"
+#include <syslog.h>
+#include <mach-o/dyld.h>
+
+EXPORT
+void *SubGetImageByName(const char *filename) __asm__("SubGetImageByName");
+void *SubGetImageByName(const char *filename) {
+ return substitute_open_image(filename);
+}
+
+EXPORT
+void *SubFindSymbol(void *image, const char *name) __asm__("SubFindSymbol");
+void *SubFindSymbol(void *image, const char *name) {
+ if (!image) {
+ const char *s = "SubFindSymbol: 'any image' specified, which is incredibly slow - like, 2ms on a fast x86. I'm going to do it since it seems to be somewhat common, but you should be ashamed of yourself.";
+ syslog(LOG_WARNING, "%s", s);
+ fprintf(stderr, "%s\n", s);
+ /* and it isn't thread safe, but neither is MS */
+ for(uint32_t i = 0; i < _dyld_image_count(); i++) {
+ const char *im_name = _dyld_get_image_name(i);
+ struct substitute_image *im = substitute_open_image(im_name);
+ if (!im) {
+ fprintf(stderr, "(btw, couldn't open %s?)\n", im_name);
+ continue;
+ }
+ void *r = SubFindSymbol(im, name);
+ substitute_close_image(im);
+ if (r)
+ return r;
+ }
+ return NULL;
+ }
+
+ substitute_sym *sym;
+ if (substitute_find_private_syms(image, &name, &sym, 1) || !sym)
+ return NULL;
+ return substitute_sym_to_ptr(image, sym);
+}
+
+/*
+EXPORT
+void SubHookFunction(void *symbol, void *replace, void **result) __asm__("SubHookFunction");
+void SubHookFunction(void *symbol, void *replace, void **result) {
+ // ...
+}
+*/
+
+#ifdef __APPLE__
+/*void SubHookMessageEx(Class _class, SEL sel, IMP imp, IMP *result) __asm__("SubHookMessageEx");
+void SubHookMessageEx(Class _class, SEL sel, IMP imp, IMP *result) {
+
+}*/
+
+#endif