dyld-851.27.tar.gz
[apple/dyld.git] / src / dyld_process_info_notify.cpp
1 /* -*- mode: C++; c-basic-offset: 4; tab-width: 4 -*-
2 *
3 * Copyright (c) 2016 Apple Inc. All rights reserved.
4 *
5 * @APPLE_LICENSE_HEADER_START@
6 *
7 * This file contains Original Code and/or Modifications of Original Code
8 * as defined in and that are subject to the Apple Public Source License
9 * Version 2.0 (the 'License'). You may not use this file except in
10 * compliance with the License. Please obtain a copy of the License at
11 * http://www.opensource.apple.com/apsl/ and read it before using this
12 * file.
13 *
14 * The Original Code and all software distributed under the License are
15 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
16 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
17 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
19 * Please see the License for the specific language governing rights and
20 * limitations under the License.
21 *
22 * @APPLE_LICENSE_HEADER_END@
23 */
24
25 #include <stdlib.h>
26 #include <string.h>
27 #include <limits.h>
28 #include <stdio.h>
29 #include <mach/shared_region.h>
30 #include <mach/mach_vm.h>
31 #include <libkern/OSAtomic.h>
32 #include <execinfo.h>
33 #include <mach-o/dyld_priv.h>
34 #include <mach-o/dyld_process_info.h>
35 #include <mach-o/dyld_images.h>
36 #include <Block.h>
37 #include <dlfcn.h>
38
39 #include "dyld_process_info_internal.h"
40
41 #include "Loading.h"
42 #include "Tracing.h"
43 #include "AllImages.h"
44
45 extern "C" int _dyld_func_lookup(const char* name, void** address);
46
47 typedef void (^Notify)(bool unload, uint64_t timestamp, uint64_t machHeader, const uuid_t uuid, const char* path);
48 typedef void (^NotifyExit)();
49 typedef void (^NotifyMain)();
50
51 //
52 // Object used for monitoring another processes dyld loads
53 //
54 struct __attribute__((visibility("hidden"))) dyld_process_info_notify_base
55 {
56 dyld_process_info_notify_base(dispatch_queue_t queue, Notify notify, NotifyExit notifyExit, task_t task, kern_return_t* kr);
57 ~dyld_process_info_notify_base();
58 bool enabled() const;
59 void retain();
60 void release();
61
62 void setNotifyMain(NotifyMain notifyMain) const {
63 if (_notifyMain == notifyMain) { return; }
64 Block_release(_notifyMain);
65 _notifyMain = Block_copy(notifyMain);
66 }
67
68 // override new and delete so we don't need to link with libc++
69 static void* operator new(size_t sz) { return malloc(sz); }
70 static void operator delete(void* p) { free(p); }
71
72 private:
73 void handleEvent();
74 void disconnect();
75 void teardownMachPorts();
76 void replyToMonitoredProcess(mach_msg_header_t& header);
77
78 kern_return_t task_dyld_process_info_notify_register(task_read_t target_task, mach_port_t notify);
79 kern_return_t task_dyld_process_info_notify_deregister(task_read_t target_task, mach_port_t notify);
80
81 RemoteBuffer _remoteAllImageInfoBuffer;
82 mutable std::atomic<uint32_t> _retainCount;
83 dispatch_queue_t _queue;
84 mutable Notify _notify;
85 mutable NotifyExit _notifyExit;
86 mutable NotifyMain _notifyMain;
87 dispatch_source_t _machSource;
88 task_t _task;
89 mach_port_t _port; // monitor is process being notified of image loading/unloading
90 std::atomic<bool> _connected;
91 #if TARGET_OS_SIMULATOR
92 uint32_t _portInTarget;
93 #endif
94 };
95
96 #if TARGET_OS_SIMULATOR
97
98 template<typename F>
99 kern_return_t withRemotePortArray(task_t target_task, F f) {
100 // Get the all image info
101 task_dyld_info_data_t taskDyldInfo;
102 mach_msg_type_number_t taskDyldInfoCount = TASK_DYLD_INFO_COUNT;
103 auto kr = task_info(target_task, TASK_DYLD_INFO, (task_info_t)&taskDyldInfo, &taskDyldInfoCount);
104 if (kr != KERN_SUCCESS) {
105 return kr;
106 }
107
108 vm_prot_t cur_protection = VM_PROT_NONE;
109 vm_prot_t max_protection = VM_PROT_NONE;
110 mach_vm_address_t localAddress = 0;
111 mach_vm_size_t size = sizeof(dyld_all_image_infos_64);
112 if ( taskDyldInfo.all_image_info_format == TASK_DYLD_ALL_IMAGE_INFO_32 ) {
113 size = sizeof(dyld_all_image_infos_32);
114 }
115 kr = mach_vm_remap(mach_task_self(),
116 &localAddress,
117 size,
118 0, // mask
119 VM_FLAGS_ANYWHERE | VM_FLAGS_RETURN_DATA_ADDR| VM_FLAGS_RESILIENT_CODESIGN | VM_FLAGS_RESILIENT_MEDIA,
120 target_task,
121 taskDyldInfo.all_image_info_addr,
122 false,
123 &cur_protection,
124 &max_protection,
125 VM_INHERIT_NONE);
126
127 static_assert(sizeof(mach_port_t) == sizeof(uint32_t), "machport size not 32-bits");
128 uint32_t* notifyMachPorts;
129 if ( taskDyldInfo.all_image_info_format == TASK_DYLD_ALL_IMAGE_INFO_32 ) {
130 notifyMachPorts = (uint32_t *)((uint8_t *)localAddress + offsetof(dyld_all_image_infos_32,notifyMachPorts));
131 } else {
132 notifyMachPorts = (uint32_t *)((uint8_t *)localAddress + offsetof(dyld_all_image_infos_64,notifyMachPorts));
133 }
134 kr = f(notifyMachPorts);
135 (void)vm_deallocate(target_task, localAddress, size);
136 return kr;
137 }
138
139 #endif
140
141 kern_return_t dyld_process_info_notify_base::task_dyld_process_info_notify_register(task_t target_task, mach_port_t notify) {
142 #if TARGET_OS_SIMULATOR
143 static dispatch_once_t onceToken;
144 static kern_return_t (*tdpinr)(task_t, mach_port_t) = nullptr;
145 dispatch_once(&onceToken, ^{
146 tdpinr = (kern_return_t (*)(task_t, mach_port_t))dlsym(RTLD_DEFAULT, "task_dyld_process_info_notify_register");
147 });
148 if (tdpinr) {
149 return tdpinr(target_task, notify);
150 }
151 // Our libsystem does not have task_dyld_process_info_notify_register, emulate
152 return withRemotePortArray(target_task, [this,target_task,notify](uint32_t* portArray){
153 mach_port_t portInTarget = MACH_PORT_NULL;
154 // Insert the right
155 kern_return_t kr = KERN_NAME_EXISTS;
156 while (kr == KERN_NAME_EXISTS) {
157 portInTarget = MACH_PORT_NULL;
158 kr = mach_port_allocate(target_task, MACH_PORT_RIGHT_DEAD_NAME, &portInTarget);
159 if (kr != KERN_SUCCESS) {
160 return kr;
161 }
162 (void)mach_port_deallocate(target_task, portInTarget);
163 kr = mach_port_insert_right(target_task, portInTarget, notify, MACH_MSG_TYPE_MAKE_SEND);
164 }
165 // The call is not succesfull return
166 if (kr != KERN_SUCCESS) {
167 (void)mach_port_deallocate(target_task, portInTarget);
168 return kr;
169 }
170 // Find a slot for the right
171 for (uint8_t notifySlot=0; notifySlot < DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT; ++notifySlot) {
172 if (OSAtomicCompareAndSwap32(0, portInTarget, (volatile int32_t*)&portArray[notifySlot])) {
173 _portInTarget = portInTarget;
174 return KERN_SUCCESS;
175 }
176 }
177 // The array was full, we need to fail
178 (void)mach_port_deallocate(target_task, portInTarget);
179 return KERN_UREFS_OVERFLOW;
180 });
181 #else
182 return ::task_dyld_process_info_notify_register(target_task, notify);
183 #endif
184 }
185
186 kern_return_t dyld_process_info_notify_base::task_dyld_process_info_notify_deregister(task_t target_task, mach_port_t notify) {
187 #if TARGET_OS_SIMULATOR
188 static dispatch_once_t onceToken;
189 static kern_return_t (*tdpind)(task_t, mach_port_t) = nullptr;
190 dispatch_once(&onceToken, ^{
191 tdpind = (kern_return_t (*)(task_t, mach_port_t))dlsym(RTLD_DEFAULT, "task_dyld_process_info_notify_deregister");
192 });
193 if (tdpind) {
194 return tdpind(target_task, notify);
195 }
196 // Our libsystem does not have task_dyld_process_info_notify_deregister, emulate
197 return withRemotePortArray(target_task, [this](uint32_t* portArray){
198 // Find a slot for the right
199 for (uint8_t notifySlot=0; notifySlot < DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT; ++notifySlot) {
200 if (OSAtomicCompareAndSwap32(0, _portInTarget, (volatile int32_t*)&portArray[notifySlot])) {
201 return KERN_SUCCESS;
202 }
203 }
204 return KERN_FAILURE;
205 });
206 #else
207 // Our libsystem does not have task_dyld_process_info_notify_deregister, emulate
208 return ::task_dyld_process_info_notify_deregister(target_task, notify);
209 #endif
210 }
211
212 dyld_process_info_notify_base::dyld_process_info_notify_base(dispatch_queue_t queue, Notify notify, NotifyExit notifyExit,
213 task_t task, kern_return_t* kr) :
214 _retainCount(0), _queue(queue), _notify(Block_copy(notify)), _notifyExit(Block_copy(notifyExit)),
215 _notifyMain(nullptr), _machSource(nullptr), _task(task), _port(MACH_PORT_NULL), _connected(false)
216 #if TARGET_OS_SIMULATOR
217 , _portInTarget(0)
218 #endif
219 {
220 assert(kr != NULL);
221 dispatch_retain(_queue);
222 // Allocate a port to listen on in this monitoring task
223 mach_port_options_t options = { .flags = MPO_IMPORTANCE_RECEIVER | MPO_CONTEXT_AS_GUARD | MPO_STRICT, .mpl = { MACH_PORT_QLIMIT_DEFAULT }};
224 *kr = mach_port_construct(mach_task_self(), &options, (mach_port_context_t)this, &_port);
225 if (*kr != KERN_SUCCESS) {
226 teardownMachPorts();
227 return;
228 }
229
230 mach_port_t previous = MACH_PORT_NULL;
231 *kr = mach_port_request_notification(mach_task_self(), _port, MACH_NOTIFY_NO_SENDERS, 1, _port, MACH_MSG_TYPE_MAKE_SEND_ONCE, &previous);
232 if ((*kr != KERN_SUCCESS) || previous != MACH_PORT_NULL) {
233 teardownMachPorts();
234 return;
235 }
236 //FIXME: Should we retry here if we fail?
237 *kr = task_dyld_process_info_notify_register(_task, _port);
238 dyld3::kdebug_trace_dyld_marker(DBG_DYLD_TASK_NOTIFY_REGISTER, (uint64_t)_task, (uint64_t)_port, *kr, 0);
239 if (*kr != KERN_SUCCESS) {
240 teardownMachPorts();
241 return;
242 }
243
244 // Setup the event handler for the port
245 _machSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV, _port, 0, _queue);
246 if (_machSource == nullptr) {
247 teardownMachPorts();
248 return;
249 }
250 dispatch_source_set_event_handler(_machSource, ^{ handleEvent(); });
251 dispatch_source_set_cancel_handler(_machSource, ^{ teardownMachPorts(); });
252 dispatch_activate(_machSource);
253 _connected = true;
254 }
255
256 dyld_process_info_notify_base::~dyld_process_info_notify_base() {
257 if (_connected) { fprintf(stderr, "dyld: ~dyld_process_info_notify_base called while still connected\n"); }
258 Block_release(_notify);
259 Block_release(_notifyMain);
260 Block_release(_notifyExit);
261 dispatch_release(_queue);
262 }
263
264 void dyld_process_info_notify_base::teardownMachPorts() {
265 if ( _port != 0 ) {
266 kern_return_t kr = task_dyld_process_info_notify_deregister(_task, _port);
267 dyld3::kdebug_trace_dyld_marker(DBG_DYLD_TASK_NOTIFY_DEREGISTER, (uint64_t)_task, (uint64_t)_port, kr, 0);
268 (void)mach_port_destruct(mach_task_self(), _port, 0, (mach_port_context_t)this);
269 _port = 0;
270 }
271 }
272
273 void dyld_process_info_notify_base::disconnect() {
274 if (_connected) {
275 _connected = false;
276 // The connection to the target is dead. Clean up ports
277 if ( _machSource ) {
278 dispatch_source_cancel(_machSource);
279 dispatch_release(_machSource);
280 _machSource = NULL;
281 }
282 if (_notifyExit) {
283 dispatch_async(_queue, ^{
284 // There was a not a mach source, so if we have any ports they will not get torn down by its cancel handler
285 _notifyExit();
286 });
287 }
288 }
289 }
290
291 bool dyld_process_info_notify_base::enabled() const
292 {
293 return _connected;
294 }
295
296 void dyld_process_info_notify_base::retain()
297 {
298 _retainCount.fetch_add(1, std::memory_order_relaxed);
299 }
300
301 void dyld_process_info_notify_base::release()
302 {
303 if (_retainCount.fetch_sub(1, std::memory_order_acq_rel) == 0) {
304 // When we subtracted the ref count was 0, which means it was the last reference
305 disconnect();
306 dispatch_async(_queue, ^{
307 delete this;
308 });
309 }
310 }
311
312 void dyld_process_info_notify_base::replyToMonitoredProcess(mach_msg_header_t& header) {
313 mach_msg_header_t replyHeader;
314 replyHeader.msgh_bits = MACH_MSGH_BITS_SET(MACH_MSGH_BITS_REMOTE(header.msgh_bits), 0, 0, 0);
315 replyHeader.msgh_id = 0;
316 replyHeader.msgh_local_port = MACH_PORT_NULL;
317 replyHeader.msgh_remote_port = header.msgh_remote_port;
318 replyHeader.msgh_reserved = 0;
319 replyHeader.msgh_size = sizeof(replyHeader);
320 kern_return_t r = mach_msg(&replyHeader, MACH_SEND_MSG, replyHeader.msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
321 if (r == KERN_SUCCESS) {
322 header.msgh_remote_port = MACH_PORT_NULL;
323 } else {
324 disconnect();
325 }
326 }
327
328 void dyld_process_info_notify_base::handleEvent() {
329 // References object may still exist even after the ports are dead. Disable event dispatching
330 // if the ports have been torn down.
331 if (!_connected) { return; }
332
333 // This event handler block has an implicit reference to "this"
334 // if incrementing the count goes to one, that means the object may have already been destroyed
335 uint8_t messageBuffer[DYLD_PROCESS_INFO_NOTIFY_MAX_BUFFER_SIZE] = {};
336 mach_msg_header_t* h = (mach_msg_header_t*)messageBuffer;
337
338 kern_return_t r = mach_msg(h, MACH_RCV_MSG | MACH_RCV_VOUCHER| MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT) | MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0), 0, sizeof(messageBuffer)-sizeof(mach_msg_audit_trailer_t), _port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
339 if ( r == KERN_SUCCESS && !(h->msgh_bits & MACH_MSGH_BITS_COMPLEX)) {
340 //fprintf(stderr, "received message id=0x%X, size=%d\n", h->msgh_id, h->msgh_size);
341
342 if ( h->msgh_id == DYLD_PROCESS_INFO_NOTIFY_LOAD_ID || h->msgh_id == DYLD_PROCESS_INFO_NOTIFY_UNLOAD_ID ) {
343 // run notifier block for each [un]load image
344 const dyld_process_info_notify_header* header = (dyld_process_info_notify_header*)messageBuffer;
345 if (sizeof(*header) <= h->msgh_size
346 && header->imagesOffset <= h->msgh_size
347 && header->stringsOffset <= h->msgh_size
348 && (header->imageCount * sizeof(dyld_process_info_image_entry)) <= (h->msgh_size - header->imagesOffset)) {
349 const dyld_process_info_image_entry* entries = (dyld_process_info_image_entry*)&messageBuffer[header->imagesOffset];
350 const char* const stringPool = (char*)&messageBuffer[header->stringsOffset];
351 for (unsigned i=0; i < header->imageCount; ++i) {
352 bool isUnload = (h->msgh_id == DYLD_PROCESS_INFO_NOTIFY_UNLOAD_ID);
353 if (entries[i].pathStringOffset <= h->msgh_size - header->stringsOffset) {
354 //fprintf(stderr, "Notifying about: %s\n", stringPool + entries[i].pathStringOffset);
355 _notify(isUnload, header->timestamp, entries[i].loadAddress, entries[i].uuid, stringPool + entries[i].pathStringOffset);
356 } else {
357 disconnect();
358 break;
359 }
360 }
361 // reply to dyld, so it can continue
362 replyToMonitoredProcess(*h);
363 } else {
364 disconnect();
365 }
366 }
367 else if ( h->msgh_id == DYLD_PROCESS_INFO_NOTIFY_MAIN_ID ) {
368 if (h->msgh_size != sizeof(mach_msg_header_t)) {
369 disconnect();
370 } else if ( _notifyMain != NULL ) {
371 _notifyMain();
372 }
373 replyToMonitoredProcess(*h);
374 } else if ( h->msgh_id == MACH_NOTIFY_NO_SENDERS ) {
375 // Validate this notification came from the kernel
376 const mach_msg_audit_trailer_t *audit_tlr = (mach_msg_audit_trailer_t *)((uint8_t *)h + round_msg(h->msgh_size));
377 if (audit_tlr->msgh_trailer_type == MACH_MSG_TRAILER_FORMAT_0
378 && audit_tlr->msgh_trailer_size >= sizeof(mach_msg_audit_trailer_t)
379 // We cannot link to libbsm, so we are hardcoding the audit token offset (5)
380 // And the value the represents the kernel (0)
381 && audit_tlr->msgh_audit.val[5] == 0) {
382 disconnect();
383 }
384 }
385 else {
386 fprintf(stderr, "dyld: received unknown message id=0x%X, size=%d\n", h->msgh_id, h->msgh_size);
387 }
388 } else {
389 fprintf(stderr, "dyld: received unknown message id=0x%X, size=%d\n", h->msgh_id, h->msgh_size);
390 }
391 mach_msg_destroy(h);
392 }
393
394 dyld_process_info_notify _dyld_process_info_notify(task_t task, dispatch_queue_t queue,
395 void (^notify)(bool unload, uint64_t timestamp, uint64_t machHeader, const uuid_t uuid, const char* path),
396 void (^notifyExit)(),
397 kern_return_t* kr)
398 {
399 kern_return_t krSink = KERN_SUCCESS;
400 if (kr == nullptr) {
401 kr = &krSink;
402 }
403 *kr = KERN_SUCCESS;
404
405 dyld_process_info_notify result = new dyld_process_info_notify_base(queue, notify, notifyExit, task, kr);
406 if (result->enabled())
407 return result;
408 const_cast<dyld_process_info_notify_base*>(result)->release();
409 return nullptr;
410 }
411
412 void _dyld_process_info_notify_main(dyld_process_info_notify object, void (^notifyMain)())
413 {
414 object->setNotifyMain(notifyMain);
415 }
416
417 void _dyld_process_info_notify_retain(dyld_process_info_notify object)
418 {
419 const_cast<dyld_process_info_notify_base*>(object)->retain();
420 }
421
422 void _dyld_process_info_notify_release(dyld_process_info_notify object)
423 {
424 const_cast<dyld_process_info_notify_base*>(object)->release();
425 }
426
427 static void (*sNotifyMonitoringDyldMain)() = nullptr;
428 static void (*sNotifyMonitoringDyld)(bool unloading, unsigned imageCount, const struct mach_header* loadAddresses[],
429 const char* imagePaths[]) = nullptr;
430
431 void setNotifyMonitoringDyldMain(void (*func)())
432 {
433 sNotifyMonitoringDyldMain = func;
434 }
435
436 void setNotifyMonitoringDyld(void (*func)(bool unloading, unsigned imageCount,
437 const struct mach_header* loadAddresses[],
438 const char* imagePaths[]))
439 {
440 sNotifyMonitoringDyld = func;
441 }
442
443 namespace dyld3 {
444
445 void AllImages::notifyMonitorMain()
446 {
447 #if !TARGET_OS_DRIVERKIT
448 assert(sNotifyMonitoringDyldMain != nullptr);
449 sNotifyMonitoringDyldMain();
450 #endif
451 }
452
453 void AllImages::notifyMonitorLoads(const Array<LoadedImage>& newImages)
454 {
455 #if !TARGET_OS_DRIVERKIT
456 assert(sNotifyMonitoringDyld != nullptr);
457 const struct mach_header* loadAddresses[newImages.count()];
458 const char* loadPaths[newImages.count()];
459 for(uint32_t i = 0; i<newImages.count(); ++i) {
460 loadAddresses[i] = newImages[i].loadedAddress();
461 loadPaths[i] = newImages[i].image()->path();
462 }
463 sNotifyMonitoringDyld(false, (unsigned)newImages.count(), loadAddresses, loadPaths);
464 #endif
465 }
466
467 void AllImages::notifyMonitorUnloads(const Array<LoadedImage>& unloadingImages)
468 {
469 #if !TARGET_OS_DRIVERKIT
470 assert(sNotifyMonitoringDyld != nullptr);
471 const struct mach_header* loadAddresses[unloadingImages.count()];
472 const char* loadPaths[unloadingImages.count()];
473 for(uint32_t i = 0; i<unloadingImages.count(); ++i) {
474 loadAddresses[i] = unloadingImages[i].loadedAddress();
475 loadPaths[i] = unloadingImages[i].image()->path();
476 }
477 sNotifyMonitoringDyld(true, (unsigned)unloadingImages.count(), loadAddresses, loadPaths);
478 #endif
479 }
480
481 } // namespace dyld3
482
483
484
485