aboutsummaryrefslogtreecommitdiff
path: root/lib/darwin/interpose.c
diff options
context:
space:
mode:
authorcomex2015-01-19 19:12:32 -0500
committercomex2015-01-19 19:12:32 -0500
commitc25b9e2337aad02073a199619f6f754f15cccd38 (patch)
tree603f3bcdc6eb687c11c077f350cde53fc1700898 /lib/darwin/interpose.c
parentsome reorganization (diff)
downloadsubstitute-c25b9e2337aad02073a199619f6f754f15cccd38.tar.gz
more reorganization - move OS X/iOS specific stuff into its own directory
Diffstat (limited to 'lib/darwin/interpose.c')
-rw-r--r--lib/darwin/interpose.c224
1 files changed, 224 insertions, 0 deletions
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__ */