1 /* -*- mode: C++; c-basic-offset: 4; tab-width: 4 -*-
3 * Copyright (c) 2016 Apple Inc. All rights reserved.
5 * @APPLE_LICENSE_HEADER_START@
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
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.
22 * @APPLE_LICENSE_HEADER_END@
29 #include <mach/shared_region.h>
30 #include <mach/mach_vm.h>
31 #include <libkern/OSAtomic.h>
33 #include <mach-o/dyld_priv.h>
34 #include <mach-o/dyld_process_info.h>
35 #include <mach-o/dyld_images.h>
39 #include "dyld_process_info_internal.h"
43 #include "AllImages.h"
45 extern "C" int _dyld_func_lookup(const char* name
, void** address
);
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
)();
52 // Object used for monitoring another processes dyld loads
54 struct __attribute__((visibility("hidden"))) dyld_process_info_notify_base
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();
62 void setNotifyMain(NotifyMain notifyMain
) const {
63 if (_notifyMain
== notifyMain
) { return; }
64 Block_release(_notifyMain
);
65 _notifyMain
= Block_copy(notifyMain
);
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
); }
75 void teardownMachPorts();
76 void replyToMonitoredProcess(mach_msg_header_t
& header
);
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
);
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
;
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
;
96 #if TARGET_OS_SIMULATOR
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
) {
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
);
115 kr
= mach_vm_remap(mach_task_self(),
119 VM_FLAGS_ANYWHERE
| VM_FLAGS_RETURN_DATA_ADDR
| VM_FLAGS_RESILIENT_CODESIGN
| VM_FLAGS_RESILIENT_MEDIA
,
121 taskDyldInfo
.all_image_info_addr
,
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
));
132 notifyMachPorts
= (uint32_t *)((uint8_t *)localAddress
+ offsetof(dyld_all_image_infos_64
,notifyMachPorts
));
134 kr
= f(notifyMachPorts
);
135 (void)vm_deallocate(target_task
, localAddress
, size
);
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");
149 return tdpinr(target_task
, notify
);
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
;
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
) {
162 (void)mach_port_deallocate(target_task
, portInTarget
);
163 kr
= mach_port_insert_right(target_task
, portInTarget
, notify
, MACH_MSG_TYPE_MAKE_SEND
);
165 // The call is not succesfull return
166 if (kr
!= KERN_SUCCESS
) {
167 (void)mach_port_deallocate(target_task
, portInTarget
);
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
;
177 // The array was full, we need to fail
178 (void)mach_port_deallocate(target_task
, portInTarget
);
179 return KERN_UREFS_OVERFLOW
;
182 return ::task_dyld_process_info_notify_register(target_task
, notify
);
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");
194 return tdpind(target_task
, notify
);
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
])) {
207 // Our libsystem does not have task_dyld_process_info_notify_deregister, emulate
208 return ::task_dyld_process_info_notify_deregister(target_task
, notify
);
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
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
) {
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
) {
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
) {
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) {
250 dispatch_source_set_event_handler(_machSource
, ^{ handleEvent(); });
251 dispatch_source_set_cancel_handler(_machSource
, ^{ teardownMachPorts(); });
252 dispatch_activate(_machSource
);
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
);
264 void dyld_process_info_notify_base::teardownMachPorts() {
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);
273 void dyld_process_info_notify_base::disconnect() {
276 // The connection to the target is dead. Clean up ports
278 dispatch_source_cancel(_machSource
);
279 dispatch_release(_machSource
);
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
291 bool dyld_process_info_notify_base::enabled() const
296 void dyld_process_info_notify_base::retain()
298 _retainCount
.fetch_add(1, std::memory_order_relaxed
);
301 void dyld_process_info_notify_base::release()
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
306 dispatch_async(_queue
, ^{
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
;
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; }
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
;
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);
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
);
361 // reply to dyld, so it can continue
362 replyToMonitoredProcess(*h
);
367 else if ( h
->msgh_id
== DYLD_PROCESS_INFO_NOTIFY_MAIN_ID
) {
368 if (h
->msgh_size
!= sizeof(mach_msg_header_t
)) {
370 } else if ( _notifyMain
!= NULL
) {
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) {
386 fprintf(stderr
, "dyld: received unknown message id=0x%X, size=%d\n", h
->msgh_id
, h
->msgh_size
);
389 fprintf(stderr
, "dyld: received unknown message id=0x%X, size=%d\n", h
->msgh_id
, h
->msgh_size
);
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
)(),
399 kern_return_t krSink
= KERN_SUCCESS
;
405 dyld_process_info_notify result
= new dyld_process_info_notify_base(queue
, notify
, notifyExit
, task
, kr
);
406 if (result
->enabled())
408 const_cast<dyld_process_info_notify_base
*>(result
)->release();
412 void _dyld_process_info_notify_main(dyld_process_info_notify object
, void (^notifyMain
)())
414 object
->setNotifyMain(notifyMain
);
417 void _dyld_process_info_notify_retain(dyld_process_info_notify object
)
419 const_cast<dyld_process_info_notify_base
*>(object
)->retain();
422 void _dyld_process_info_notify_release(dyld_process_info_notify object
)
424 const_cast<dyld_process_info_notify_base
*>(object
)->release();
427 static void (*sNotifyMonitoringDyldMain
)() = nullptr;
428 static void (*sNotifyMonitoringDyld
)(bool unloading
, unsigned imageCount
, const struct mach_header
* loadAddresses
[],
429 const char* imagePaths
[]) = nullptr;
431 void setNotifyMonitoringDyldMain(void (*func
)())
433 sNotifyMonitoringDyldMain
= func
;
436 void setNotifyMonitoringDyld(void (*func
)(bool unloading
, unsigned imageCount
,
437 const struct mach_header
* loadAddresses
[],
438 const char* imagePaths
[]))
440 sNotifyMonitoringDyld
= func
;
445 void AllImages::notifyMonitorMain()
447 #if !TARGET_OS_DRIVERKIT
448 assert(sNotifyMonitoringDyldMain
!= nullptr);
449 sNotifyMonitoringDyldMain();
453 void AllImages::notifyMonitorLoads(const Array
<LoadedImage
>& newImages
)
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();
463 sNotifyMonitoringDyld(false, (unsigned)newImages
.count(), loadAddresses
, loadPaths
);
467 void AllImages::notifyMonitorUnloads(const Array
<LoadedImage
>& unloadingImages
)
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();
477 sNotifyMonitoringDyld(true, (unsigned)unloadingImages
.count(), loadAddresses
, loadPaths
);