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
|
#if 0
#include "substitute.h"
#include "substitute-internal.h"
#include "darwin/mach-decls.h"
#include <pthread.h>
#include <mach/mach.h>
static void release_port(UNUSED CFAllocatorRef allocator, const void *value) {
mach_port_t thread = (mach_port_t) value;
thread_resume(thread);
mach_port_deallocate(mach_task_self(), thread);
}
static CFSetCallBacks suspend_port_callbacks = {
.version = 0,
.release = release_port,
};
static bool apply_one_pcp(mach_port_t thread,
uintptr_t (*callback)(void *ctx, uintptr_t pc),
void *ctx) {
int flavor;
#if defined(__x86_64__)
struct _x86_thread_state_64 state;
flavor = _x86_thread_state_64_flavor;
#elif defined(__i386__)
struct _x86_thread_state_32 state;
flavor = _x86_thread_state_32_flavor;
#elif defined(__arm__)
struct _arm_thread_state_32 state;
flavor = _arm_thread_state_32_flavor;
#elif defined(__arm64__)
struct _arm_thread_state_64 state;
flavor = _arm_thread_state_64_flavor;
#else
#error ?
#endif
mach_msg_type_number_t real_cnt = sizeof(state) / sizeof(int);
mach_msg_type_number_t cnt = real_cnt;
kern_return_t kr = thread_get_state(thread, flavor, (thread_state_t) &state, &cnt);
if (kr || cnt != real_cnt)
return false;
uintptr_t *pcp;
#if defined(__x86_64__)
pcp = (uintptr_t *) &state.rip;
#elif defined(__i386__)
pcp = (uintptr_t *) &state.eip;
#elif defined(__arm__) || defined(__arm64__)
pcp = (uintptr_t *) &state.pc;
#endif
uintptr_t old = *pcp;
#ifdef __arm__
/* thumb */
if (state.cpsr & 0x20)
old |= 1;
#endif
uintptr_t new = callback(ctx, *pcp);
if (new != old) {
*pcp = new;
#ifdef __arm__
*pcp &= ~1;
state.cpsr = (state.cpsr & ~0x20) | ((new & 1) * 0x20);
#endif
kr = thread_set_state(thread, flavor, (thread_state_t) &state, real_cnt);
if (kr)
return false;
}
return true;
}
int apply_pc_patch_callback(void *token,
uintptr_t (*pc_patch_callback)(void *ctx, uintptr_t pc),
void *ctx) {
CFMutableSetRef suspended_set = token;
CFIndex count = CFSetGetCount(suspended_set);
if (!count)
return SUBSTITUTE_OK;
/* great API there CF */
const void **ports = malloc(sizeof(*ports) * count);
CFSetGetValues(suspended_set, ports);
int ret = SUBSTITUTE_OK;
for (CFIndex i = 0; i < count; i++) {
if (!apply_one_pcp((mach_port_t) ports[i], pc_patch_callback, ctx)) {
ret = SUBSTITUTE_ERR_ADJUSTING_THREADS;
break;
}
}
free(ports);
return ret;
}
int stop_other_threads(void **token_ptr) {
if (!pthread_main_np())
return SUBSTITUTE_ERR_NOT_ON_MAIN_THREAD;
int ret;
mach_port_t self = mach_thread_self();
/* The following shenanigans are for catching any new threads that are
* created while we're looping, without suspending anything twice. Keep
* looping until only threads we already suspended before this loop are
* there. */
CFMutableSetRef suspended_set = CFSetCreateMutable(NULL, 0, &suspend_port_callbacks);
thread_act_array_t ports = 0;
mach_msg_type_number_t nports = 0;
bool got_new = true;
while (got_new) {
got_new = false;
kern_return_t kr = task_threads(mach_task_self(), &ports, &nports);
if (kr) { /* ouch */
ret = SUBSTITUTE_ERR_ADJUSTING_THREADS;
goto fail;
}
for (mach_msg_type_number_t i = 0; i < nports; i++) {
mach_port_t port = ports[i];
void *casted_port = (void *) (uintptr_t) port;
if (port == self ||
CFSetContainsValue(suspended_set, casted_port)) {
/* already suspended, ignore */
mach_port_deallocate(mach_task_self(), port);
} else {
got_new = true;
printf("suspending %d (self=%d)\n", port, self);
kr = thread_suspend(port);
if (kr == KERN_TERMINATED) {
/* too late */
mach_port_deallocate(mach_task_self(), port);
} else if (kr) {
ret = SUBSTITUTE_ERR_ADJUSTING_THREADS;
for (; i < nports; i++)
mach_port_deallocate(mach_task_self(), ports[i]);
vm_deallocate(mach_task_self(), (vm_address_t) ports,
nports * sizeof(*ports));
goto fail;
}
CFSetAddValue(suspended_set, casted_port);
}
}
vm_deallocate(mach_task_self(), (vm_address_t) ports,
nports * sizeof(*ports));
}
/* Success - keep the set around for when we're done. */
*token_ptr = suspended_set;
return SUBSTITUTE_OK;
fail:
CFRelease(suspended_set);
return ret;
}
int resume_other_threads(void *token) {
CFMutableSetRef suspended_set = token;
CFRelease(suspended_set);
return SUBSTITUTE_OK; /* eh */
}
#endif
|