]>
Commit | Line | Data |
---|---|---|
ada7c492 A |
1 | #include "os/internal.h" |
2 | #include "platform/introspection_private.h" | |
3 | #include "introspection_internal.h" | |
4 | ||
5 | #include <mach/mach_error.h> | |
6 | #include <mach/mach_init.h> | |
7 | #include <mach/mach_port.h> | |
8 | #include <mach/mach_vm.h> | |
9 | #include <mach/task.h> | |
10 | #include <mach/thread_act.h> | |
11 | ||
12 | #include <sys/sysctl.h> | |
13 | ||
14 | #include "libkern/OSAtomic.h" | |
15 | ||
16 | // Returns the number of thread entries that can be stored in a task page. | |
17 | static unsigned int | |
18 | _platform_threads_per_page(void) | |
19 | { | |
20 | // Subtract out the header storage. | |
21 | return (int)(vm_page_size / sizeof(struct platform_thread_s)) - 1; | |
22 | } | |
23 | ||
24 | // Returns the page-aligned base address for a given pthread structure address. | |
25 | static mach_vm_size_t | |
26 | _platform_pthread_addr(mach_vm_address_t addr) { | |
27 | return trunc_page(addr); // XXX approximation | |
28 | } | |
29 | ||
30 | // Returns the page-aligned size for a given pthread structure address. | |
31 | static mach_vm_size_t | |
32 | _platform_pthread_size(mach_vm_address_t addr) { | |
33 | return vm_page_size; // XXX approximation | |
34 | } | |
35 | ||
36 | static kern_return_t | |
37 | _platform_thread_deallocate(platform_thread_t thread) | |
38 | { | |
39 | kern_return_t ret; | |
40 | if (MACH_PORT_VALID(thread->act)) { | |
41 | mach_port_deallocate(mach_task_self(), thread->act); | |
42 | thread->act = MACH_PORT_NULL; | |
43 | } | |
44 | ||
45 | if (thread->pthread_addr != 0) { | |
46 | ret = mach_vm_deallocate(mach_task_self(), | |
47 | _platform_pthread_addr(thread->pthread_addr), | |
48 | _platform_pthread_size(thread->pthread_addr)); | |
49 | thread->pthread_addr = 0; | |
50 | } | |
51 | return ret; | |
52 | } | |
53 | ||
54 | static kern_return_t | |
55 | _platform_task_deallocate(platform_task_t task) | |
56 | { | |
57 | kern_return_t ret; | |
58 | ||
59 | if (!task) { | |
60 | return KERN_INVALID_TASK; | |
61 | } | |
62 | ||
63 | task_t port = task->metadata.port; | |
64 | if (port != MACH_PORT_NULL) { | |
65 | mach_port_deallocate(mach_task_self(), port); | |
66 | } | |
67 | ||
68 | platform_task_t ptr = task; | |
69 | do { | |
70 | mach_vm_address_t addr = (mach_vm_address_t)ptr; | |
71 | ||
72 | // Deallocate threads. | |
73 | int i, start = (ptr == task) ? 1 : 0; // Skip over meta data. | |
74 | for (i = start; i < _platform_threads_per_page() - start; ++i) { | |
75 | _platform_thread_deallocate(&ptr->threads[i]); | |
76 | } | |
77 | ||
78 | ptr = ptr->header.next; | |
79 | ret = mach_vm_deallocate(mach_task_self(), addr, vm_page_size); | |
80 | } while (ptr); | |
81 | ||
82 | return ret; | |
83 | } | |
84 | ||
85 | extern int __sysctl(int *, unsigned int, void *, size_t *, void *, size_t); | |
86 | ||
87 | static kern_return_t | |
88 | _platform_task_query_64_bit(platform_task_t task) | |
89 | { | |
90 | task_flags_info_data_t task_flags_info; | |
91 | mach_msg_type_number_t count = TASK_FLAGS_INFO_COUNT; | |
92 | ||
93 | kern_return_t ret = task_info(task->metadata.port, TASK_FLAGS_INFO, (task_info_t) &task_flags_info, &count); | |
94 | if (ret == KERN_SUCCESS) { | |
95 | task->metadata.is_64_bit = (task_flags_info.flags & TF_LP64) ? true : false; | |
96 | } else if (ret == KERN_INVALID_ARGUMENT) { | |
97 | pid_t pid; | |
98 | kern_return_t ret = pid_for_task(task->metadata.port, &pid); | |
99 | if (ret != KERN_SUCCESS) return ret; | |
100 | ||
101 | struct kinfo_proc info; | |
102 | size_t size = sizeof(info); | |
103 | int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, pid }; | |
104 | unsigned int len = sizeof(mib) / sizeof(*mib); | |
105 | int res = __sysctl(mib, len, &info, &size, NULL, 0); | |
106 | if (res == 0 && size >= sizeof(info)) { | |
107 | task->metadata.is_64_bit = (info.kp_proc.p_flag & P_LP64) != 0; | |
108 | } | |
109 | } | |
110 | ||
111 | return ret; | |
112 | } | |
113 | ||
114 | kern_return_t | |
115 | platform_task_attach(platform_task_t *out_task, task_t port) | |
116 | { | |
117 | kern_return_t ret; | |
118 | ||
119 | // Test some invariants. | |
120 | _Static_assert(sizeof(struct platform_task_header_s) == 32, ""); | |
121 | _Static_assert(sizeof(struct platform_task_metadata_s) == 32, ""); | |
122 | _Static_assert(sizeof(struct platform_thread_s) == 32, ""); | |
123 | ||
124 | // Allocate storage for the returned task handle. | |
125 | mach_vm_address_t addr = 0; | |
126 | mach_vm_size_t size = vm_page_size; | |
127 | ret = mach_vm_allocate(mach_task_self(), &addr, size, VM_FLAGS_ANYWHERE); | |
128 | if (ret != KERN_SUCCESS) return ret; | |
129 | ||
130 | platform_task_t result = (platform_task_t)addr; | |
131 | ||
132 | // Keep a reference to the task port. | |
133 | ret = mach_port_mod_refs(mach_task_self(), port, MACH_PORT_RIGHT_SEND, 1); | |
134 | if (ret != KERN_SUCCESS) { | |
135 | _platform_task_deallocate(result); | |
136 | return ret; | |
137 | } | |
138 | ||
139 | result->header.head = result; | |
140 | result->metadata.port = port; | |
141 | ||
142 | ret = _platform_task_query_64_bit(result); | |
143 | if (ret != KERN_SUCCESS) { | |
144 | _platform_task_deallocate(result); | |
145 | return ret; | |
146 | } | |
147 | ||
148 | *out_task = result; | |
149 | ||
150 | return ret; | |
151 | } | |
152 | ||
153 | kern_return_t | |
154 | platform_task_detach(platform_task_t task) | |
155 | { | |
156 | kern_return_t ret; | |
157 | ret = _platform_task_deallocate(task); | |
158 | return ret; | |
159 | } | |
160 | ||
161 | bool | |
162 | platform_task_is_64_bit(platform_task_t task) | |
163 | { | |
164 | return task->metadata.is_64_bit; | |
165 | } | |
166 | ||
167 | kern_return_t | |
168 | platform_task_suspend_threads(platform_task_t task) | |
169 | { | |
170 | return KERN_NOT_SUPPORTED; | |
171 | } | |
172 | ||
173 | kern_return_t | |
174 | platform_task_resume_threads(platform_task_t task) | |
175 | { | |
176 | return KERN_NOT_SUPPORTED; | |
177 | } | |
178 | ||
179 | kern_return_t | |
180 | platform_task_perform(platform_task_t task, | |
181 | mach_vm_address_t func_addr, | |
182 | mach_vm_address_t data_addr) | |
183 | { | |
184 | return KERN_NOT_SUPPORTED; | |
185 | } | |
186 | ||
187 | static platform_task_t | |
188 | _platform_thread_get_task(platform_thread_t thread) | |
189 | { | |
190 | platform_task_t task = (platform_task_t)trunc_page((uintptr_t)thread); | |
191 | platform_task_t head = task->header.head; | |
192 | if (head) { | |
193 | task = head; | |
194 | } | |
195 | return task; | |
196 | } | |
197 | ||
198 | static kern_return_t | |
199 | _platform_thread_map(platform_task_t task, | |
200 | platform_thread_t thread, | |
201 | mach_vm_address_t thread_handle) | |
202 | { | |
203 | kern_return_t ret; | |
204 | vm_prot_t cur_protection, max_protection; | |
205 | ||
206 | vm_offset_t data = 0; | |
207 | mach_vm_size_t wordsize = task->metadata.is_64_bit ? 8 : 4; | |
208 | mach_msg_type_number_t size; | |
209 | ret = mach_vm_read(_platform_thread_get_task(thread)->metadata.port, | |
210 | thread_handle, // &TSD[0] | |
211 | wordsize, | |
212 | &data, | |
213 | &size); | |
214 | if (ret != KERN_SUCCESS) return ret; | |
215 | ||
216 | mach_vm_address_t pthread_addr = (uintptr_t)*(void **)data; // deref TSD[0] | |
217 | mach_vm_deallocate(mach_task_self(), data, size); | |
218 | ||
219 | mach_vm_address_t src_addr = _platform_pthread_addr(pthread_addr); | |
220 | mach_vm_offset_t offset = pthread_addr - src_addr; | |
221 | mach_vm_address_t dst_addr = 0; | |
222 | ret = mach_vm_remap(mach_task_self(), | |
223 | &dst_addr, | |
224 | _platform_pthread_size(pthread_addr), | |
225 | 0, | |
226 | VM_FLAGS_ANYWHERE, | |
227 | _platform_thread_get_task(thread)->metadata.port, | |
228 | src_addr, | |
229 | 0, // no copy | |
230 | &cur_protection, | |
231 | &max_protection, | |
232 | VM_INHERIT_NONE); | |
233 | if (ret == KERN_SUCCESS) { | |
234 | thread->pthread_addr = dst_addr + offset; | |
235 | } | |
236 | ||
237 | return ret; | |
238 | } | |
239 | ||
240 | // Add a mach thread to the task's thread array. Updates the existing entry | |
241 | // with the same unique id if one exists, otherwise allocates a new entry. | |
242 | // Consumes the reference to the thread act mach port. | |
243 | static kern_return_t | |
244 | _platform_task_add_mach_thread(platform_task_t task, thread_act_t act) | |
245 | { | |
246 | int i; | |
247 | kern_return_t ret; | |
248 | ||
249 | thread_identifier_info_data_t info; | |
250 | mach_msg_type_number_t info_count = THREAD_IDENTIFIER_INFO_COUNT; | |
251 | ret = thread_info(act, | |
252 | THREAD_IDENTIFIER_INFO, | |
253 | (thread_info_t)&info, | |
254 | &info_count); | |
255 | if (ret != KERN_SUCCESS) return ret; | |
256 | ||
257 | // Anything older than the previous generation is a candidate for recycling. | |
258 | uint32_t gen = task->metadata.gen - 1; | |
259 | ||
260 | // Look for an existing slot with this unique ID or the first empty slot. | |
261 | platform_thread_t empty = NULL; | |
262 | platform_thread_t found = NULL; | |
263 | platform_task_t last, ptr = task; | |
264 | do { | |
265 | int start = (ptr == task) ? 1 : 0; // Skip over meta data. | |
266 | for (i = start; i < _platform_threads_per_page() - start; ++i) { | |
267 | platform_thread_t thread = &ptr->threads[i]; | |
268 | if (!empty && | |
269 | thread->refcnt == 0 && | |
270 | (thread->unique_id == 0 || thread->gen < gen)) { | |
271 | empty = &ptr->threads[i]; | |
272 | } else if (task->threads[i].unique_id == info.thread_id) { | |
273 | found = &ptr->threads[i]; | |
274 | break; | |
275 | } | |
276 | } | |
277 | last = ptr; | |
278 | } while (!found && (ptr = ptr->header.next)); | |
279 | ||
280 | if (found) { | |
281 | mach_port_deallocate(mach_task_self(), found->act); | |
282 | found->act = act; | |
283 | found->gen = task->metadata.gen; | |
284 | } else { | |
285 | if (!empty) { | |
286 | // Allocate new storage if necessary. | |
287 | mach_vm_address_t addr = 0; | |
288 | mach_vm_size_t size = vm_page_size; | |
289 | ret = mach_vm_allocate(mach_task_self(), | |
290 | &addr, | |
291 | size, | |
292 | VM_FLAGS_ANYWHERE); | |
293 | if (ret != KERN_SUCCESS) return ret; | |
294 | ||
295 | last = last->header.next = (platform_task_t)addr; | |
296 | last->header.head = task; | |
297 | ||
298 | empty = &last->threads[0]; | |
299 | } else { | |
300 | _platform_thread_deallocate(empty); | |
301 | } | |
302 | ||
303 | empty->act = act; // transfer ownership | |
304 | empty->gen = task->metadata.gen; | |
305 | empty->unique_id = info.thread_id; | |
306 | ret = _platform_thread_map(task, empty, info.thread_handle); | |
307 | } | |
308 | ||
309 | return ret; | |
310 | } | |
311 | ||
312 | kern_return_t | |
313 | platform_task_update_threads(platform_task_t task) | |
314 | { | |
315 | kern_return_t ret; | |
316 | thread_act_array_t array; | |
317 | mach_msg_type_number_t array_count; | |
318 | ret = task_threads(task->metadata.port, &array, &array_count); | |
319 | if (ret != KERN_SUCCESS) return ret; | |
320 | ||
321 | ++task->metadata.gen; | |
322 | task->metadata.cursor = &task->threads[1]; // Reset iteration cursor. | |
323 | ||
324 | unsigned int i; | |
325 | for (i = 0; i < array_count; ++i) { | |
326 | ret = _platform_task_add_mach_thread(task, array[i]); | |
327 | } | |
328 | ||
329 | mach_vm_size_t array_size = array_count * sizeof(*array); | |
330 | mach_vm_deallocate(mach_task_self(), (mach_vm_address_t)array, array_size); | |
331 | ||
332 | return ret; | |
333 | } | |
334 | ||
335 | platform_thread_t | |
336 | platform_task_copy_next_thread(platform_task_t task) | |
337 | { | |
338 | int i; | |
339 | platform_thread_t result = NULL; | |
340 | platform_thread_t cursor = task->metadata.cursor; | |
341 | ||
342 | if (cursor == NULL) { | |
343 | // End of iteration. | |
344 | return NULL; | |
345 | } | |
346 | ||
347 | uint32_t gen = task->metadata.gen; | |
348 | platform_task_t ptr = (platform_task_t)trunc_page((uintptr_t)cursor); | |
349 | ||
350 | do { | |
351 | if (cursor->gen == gen && cursor->unique_id != 0) { | |
352 | result = cursor; | |
353 | } | |
354 | ||
355 | ++cursor; | |
356 | ||
357 | if ((uintptr_t)cursor >= ((uintptr_t)ptr + vm_page_size)) { | |
358 | ptr = ptr->header.next; | |
359 | if (ptr) { | |
360 | cursor = &ptr->threads[0]; | |
361 | } else { | |
362 | cursor = NULL; | |
363 | } | |
364 | } | |
365 | } while (!result && cursor); | |
366 | ||
367 | task->metadata.cursor = cursor; | |
368 | ||
369 | if (result) { | |
370 | OSAtomicIncrement32(&result->refcnt); | |
371 | } | |
372 | ||
373 | return result; | |
374 | } | |
375 | ||
376 | platform_thread_id_t | |
377 | platform_thread_get_unique_id(platform_thread_t thread) | |
378 | { | |
379 | return thread->unique_id; | |
380 | } | |
381 | ||
382 | void | |
383 | platform_thread_release(platform_thread_t thread) | |
384 | { | |
385 | int32_t refcnt = OSAtomicDecrement32(&thread->refcnt); | |
386 | if (refcnt < 0) { | |
387 | __LIBPLATFORM_CLIENT_CRASH__(refcnt, "Over-release of platform thread object"); | |
388 | } | |
389 | } | |
390 | ||
391 | kern_return_t | |
392 | platform_thread_abort_safely(platform_thread_t thread) | |
393 | { | |
394 | kern_return_t ret; | |
395 | ret = thread_abort_safely(thread->act); | |
396 | return ret; | |
397 | } | |
398 | ||
399 | kern_return_t | |
400 | platform_thread_suspend(platform_thread_t thread) | |
401 | { | |
402 | kern_return_t ret; | |
403 | ret = thread_suspend(thread->act); | |
404 | return ret; | |
405 | } | |
406 | ||
407 | kern_return_t | |
408 | platform_thread_resume(platform_thread_t thread) | |
409 | { | |
410 | kern_return_t ret; | |
411 | ret = thread_resume(thread->act); | |
412 | return ret; | |
413 | } | |
414 | ||
415 | kern_return_t | |
416 | platform_thread_info(platform_thread_t thread, | |
417 | thread_flavor_t flavor, | |
418 | void *info, | |
419 | size_t *size) | |
420 | { | |
421 | kern_return_t ret; | |
422 | mach_msg_type_number_t count = (int)*size / sizeof(natural_t); | |
423 | ret = thread_info(thread->act, flavor, info, &count); | |
424 | *size = count * sizeof(natural_t); | |
425 | return ret; | |
426 | } | |
427 | ||
428 | kern_return_t | |
429 | platform_thread_get_state(platform_thread_t thread, | |
430 | thread_state_flavor_t flavor, | |
431 | void *state, | |
432 | size_t *size) | |
433 | { | |
434 | kern_return_t ret; | |
442fbc9d | 435 | mach_msg_type_number_t count = (int)(*size / sizeof(natural_t)); |
ada7c492 A |
436 | ret = thread_get_state(thread->act, flavor, state, &count); |
437 | *size = count * sizeof(natural_t); | |
438 | return ret; | |
439 | } | |
440 | ||
441 | kern_return_t | |
442 | platform_thread_set_state(platform_thread_t thread, | |
443 | thread_state_flavor_t flavor, | |
444 | const void *state, | |
445 | size_t size) | |
446 | { | |
447 | kern_return_t ret; | |
442fbc9d | 448 | mach_msg_type_number_t count = (int)(size / sizeof(natural_t)); |
ada7c492 A |
449 | ret = thread_set_state(thread->act, flavor, (thread_state_t)state, count); |
450 | return ret; | |
451 | } | |
452 | ||
453 | kern_return_t | |
454 | platform_thread_perform(platform_thread_t thread, | |
455 | mach_vm_address_t func_addr, | |
456 | mach_vm_address_t data_addr) | |
457 | { | |
458 | return KERN_NOT_SUPPORTED; | |
459 | } | |
460 | ||
461 | const void * | |
462 | platform_thread_get_pthread(platform_thread_t thread) | |
463 | { | |
464 | return (const void *) thread->pthread_addr; | |
465 | } | |
466 | ||
467 | #ifdef MAIN | |
468 | ||
469 | // cc -DMAIN -I../../include/platform introspection.c | |
470 | ||
471 | #include <stdio.h> | |
472 | #include <unistd.h> | |
473 | ||
474 | int main(int argc, char *argv[]) { | |
475 | kern_return_t ret; | |
476 | ||
477 | task_t port = MACH_PORT_NULL; | |
478 | ret = task_for_pid(mach_task_self(), getppid(), &port); | |
479 | if (ret != KERN_SUCCESS) { | |
480 | mach_error("task_for_pid", ret); | |
481 | return 1; | |
482 | } | |
483 | ||
484 | platform_task_t task = NULL; | |
485 | ret = platform_task_attach(&task, port); | |
486 | if (ret != KERN_SUCCESS) { | |
487 | mach_error("platform_task_attach", ret); | |
488 | return 1; | |
489 | } | |
490 | ||
491 | printf("Task is %s.\n", platform_task_is_64_bit(task) ? "64-bit" : "32-bit"); | |
492 | ||
493 | int i; | |
494 | for (i = 0; i < 3; ++i) { | |
495 | ret = platform_task_update_threads(task); | |
496 | if (ret != KERN_SUCCESS) { | |
497 | mach_error("platform_task_update_threads", ret); | |
498 | return 1; | |
499 | } | |
500 | ||
501 | platform_thread_t thread; | |
502 | while ((thread = platform_task_copy_next_thread(task))) { | |
503 | printf("thread = { .unique_id = 0x%llx, pthread_addr = 0x%llx }\n", | |
504 | thread->unique_id, | |
505 | thread->pthread_addr); | |
506 | printf("pthread = { .sig = %lx, .unique_id = 0x%llx }\n", | |
507 | *(unsigned long *)thread->pthread_addr, | |
508 | *(uint64_t *)((uintptr_t)thread->pthread_addr + 32)); | |
509 | ||
510 | platform_thread_release(thread); | |
511 | } | |
512 | ||
513 | sleep(3); | |
514 | } | |
515 | ||
516 | ret = platform_task_detach(task); | |
517 | if (ret != KERN_SUCCESS) { | |
518 | mach_error("platform_task_detach", ret); | |
519 | return 1; | |
520 | } | |
521 | ||
522 | return 0; | |
523 | } | |
524 | #endif |