1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
|
#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 {
struct tramp_info_page_entry *first_free;
size_t nfree;
LIST_ENTRY(tramp_info_page_header) free_pages;
};
struct tramp_info_page_entry {
union {
struct tramp_info_page_entry *next_free;
void *func;
};
void *arg1;
void *arg2;
};
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) {
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->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;
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);
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;
}
int substitute_hook_objc_message(Class class, SEL selector, IMP replacement,
IMP *old_ptr, bool *created_imp_ptr) {
int ret;
Method meth = class_getClassMethod(class, selector);
if (meth == NULL)
return SUBSTITUTE_ERR_NO_SUCH_SELECTOR;
const char *types = method_getTypeEncoding(meth);
/* temporary trampoline just tries again */
IMP temp;
if ((ret = get_trampoline(dereference, old_ptr, NULL, &temp)))
return ret;
*old_ptr = temp;
IMP old = class_replaceMethod(class, selector, replacement, types);
if (old) {
*old_ptr = old;
*created_imp_ptr = false;
} else {
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);
*created_imp_ptr = true;
}
free_trampoline(temp);
return SUBSTITUTE_OK;
}
void substitute_free_created_imp(IMP imp) {
free_trampoline(imp);
}
#endif
|