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>
35 #include "dyld_process_info.h"
36 #include "dyld_process_info_internal.h"
37 #include "dyld_images.h"
38 #include "dyld_priv.h"
41 #include "AllImages.h"
43 extern "C" int _dyld_func_lookup(const char* name
, void** address
);
45 typedef void (^Notify
)(bool unload
, uint64_t timestamp
, uint64_t machHeader
, const uuid_t uuid
, const char* path
);
46 typedef void (^NotifyExit
)();
47 typedef void (^NotifyMain
)();
50 // Object used for monitoring another processes dyld loads
52 struct __attribute__((visibility("hidden"))) dyld_process_info_notify_base
54 dyld_process_info_notify_base(dispatch_queue_t queue
, Notify notify
, NotifyExit notifyExit
, task_t task
, kern_return_t
* kr
);
55 ~dyld_process_info_notify_base();
60 void setNotifyMain(NotifyMain notifyMain
) const { _notifyMain
= notifyMain
; }
62 // override new and delete so we don't need to link with libc++
63 static void* operator new(size_t sz
) { return malloc(sz
); }
64 static void operator delete(void* p
) { free(p
); }
69 void replyToMonitoredProcess(mach_msg_header_t
& header
);
71 RemoteBuffer _remoteAllImageInfoBuffer
;
72 uint32_t* _notifyMachPorts
;
74 mutable std::atomic
<int32_t> _retainCount
;
75 dispatch_queue_t _queue
;
77 NotifyExit _notifyExit
;
78 mutable NotifyMain _notifyMain
;
80 dispatch_source_t _machSource
;
81 mach_port_t _sendPortInTarget
; // target is process being watched for image loading/unloading
82 mach_port_t _receivePortInMonitor
; // monitor is process being notified of image loading/unloading
83 std::atomic
<bool> _disabled
;
87 dyld_process_info_notify_base::dyld_process_info_notify_base(dispatch_queue_t queue
, Notify notify
, NotifyExit notifyExit
,
88 task_t task
, kern_return_t
* kr
) :
89 _notifyMachPorts(nullptr), _notifySlot(0), _retainCount(1), _queue(queue
), _notify(notify
), _notifyExit(notifyExit
),
90 _notifyMain(nullptr), _targetTask(task
), _machSource(nullptr), _sendPortInTarget(0), _receivePortInMonitor(0),
93 assert(_disabled
== false);
94 dispatch_retain(_queue
);
95 // Allocate a port to listen on in this monitoring task
96 mach_port_options_t options
= { .flags
= MPO_IMPORTANCE_RECEIVER
| MPO_CONTEXT_AS_GUARD
| MPO_STRICT
,
97 .mpl
= { MACH_PORT_QLIMIT_DEFAULT
}};
98 if ((*kr
= mach_port_construct(mach_task_self(), &options
, (mach_port_context_t
)this, &_receivePortInMonitor
))) {
102 if (_targetTask
== mach_task_self()) {
103 _sendPortInTarget
= _receivePortInMonitor
;
104 (void)mach_port_insert_right(_targetTask
, _sendPortInTarget
, _receivePortInMonitor
, MACH_MSG_TYPE_MAKE_SEND
);
106 // Insert a deadname right into the port to trigger notifications
107 kern_return_t r
= KERN_NAME_EXISTS
;
108 while (r
== KERN_NAME_EXISTS
) {
109 _sendPortInTarget
= MACH_PORT_NULL
;
111 r
= mach_port_allocate(_targetTask
, MACH_PORT_RIGHT_DEAD_NAME
, &_sendPortInTarget
);
112 if (r
!= KERN_SUCCESS
) {
116 (void)mach_port_deallocate(_targetTask
, _sendPortInTarget
);
117 r
= mach_port_insert_right(_targetTask
, _sendPortInTarget
, _receivePortInMonitor
, MACH_MSG_TYPE_MAKE_SEND
);
119 if (r
!= KERN_SUCCESS
) {
124 // Notify us if the target dies
125 mach_port_t previous
= MACH_PORT_NULL
;
126 if ((*kr
= mach_port_request_notification(_targetTask
, _sendPortInTarget
, MACH_NOTIFY_DEAD_NAME
, 0, _receivePortInMonitor
, MACH_MSG_TYPE_MAKE_SEND_ONCE
, &previous
))) {
127 (void)mach_port_deallocate(_targetTask
, _sendPortInTarget
);
128 (void)mach_port_destruct(mach_task_self(), _receivePortInMonitor
, 0, (mach_port_context_t
)this);
132 // This is a new port, if there is a previous notifier attached then something is wrong... abort.
133 if (previous
!= MACH_PORT_NULL
) {
134 (void)mach_port_deallocate(mach_task_self(), previous
);
135 (void)mach_port_deallocate(_targetTask
, _sendPortInTarget
);
136 (void)mach_port_destruct(mach_task_self(), _receivePortInMonitor
, 0, (mach_port_context_t
)this);
142 // Setup the event handler for the port
143 _machSource
= dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV
, _receivePortInMonitor
, 0, _queue
);
144 if (_machSource
== nullptr) {
145 (void)mach_port_deallocate(_targetTask
, _sendPortInTarget
);
146 (void)mach_port_destruct(mach_task_self(), _receivePortInMonitor
, 0, (mach_port_context_t
)this);
150 dispatch_source_set_event_handler(_machSource
, ^{
153 dispatch_source_set_cancel_handler(_machSource
, ^{
154 if ( _receivePortInMonitor
!= 0 ) {
155 (void)mach_port_destruct(mach_task_self(), _receivePortInMonitor
, 0, (mach_port_context_t
)this);
156 _receivePortInMonitor
= 0;
159 dispatch_activate(_machSource
);
161 // get location on all_image_infos in the target task
162 task_dyld_info_data_t taskDyldInfo
;
163 mach_msg_type_number_t taskDyldInfoCount
= TASK_DYLD_INFO_COUNT
;
164 if ((*kr
= task_info(_targetTask
, TASK_DYLD_INFO
, (task_info_t
)&taskDyldInfo
, &taskDyldInfoCount
))) {
165 (void)mach_port_deallocate(_targetTask
, _sendPortInTarget
);
169 // Poke the portname of our port into the target task
170 _remoteAllImageInfoBuffer
= RemoteBuffer(_targetTask
, taskDyldInfo
.all_image_info_addr
, taskDyldInfo
.all_image_info_size
, true, false);
171 *kr
= _remoteAllImageInfoBuffer
.getKernelReturn();
173 (void)mach_port_deallocate(_targetTask
, _sendPortInTarget
);
178 static_assert(sizeof(mach_port_t
) == sizeof(uint32_t), "machport size not 32-bits");
179 if ( taskDyldInfo
.all_image_info_format
== TASK_DYLD_ALL_IMAGE_INFO_32
) {
180 _notifyMachPorts
= (uint32_t *)((uint8_t *)_remoteAllImageInfoBuffer
.getLocalAddress() + offsetof(dyld_all_image_infos_32
,notifyMachPorts
));
182 _notifyMachPorts
= (uint32_t *)((uint8_t *)_remoteAllImageInfoBuffer
.getLocalAddress() + offsetof(dyld_all_image_infos_64
,notifyMachPorts
));
186 //If all the slots are filled we will sleep and retry a few times before giving up
187 for (uint32_t i
=0; i
<10; ++i
) {
188 for (_notifySlot
=0; _notifySlot
< DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT
; ++_notifySlot
) {
189 if (OSAtomicCompareAndSwap32(0, _sendPortInTarget
, (volatile int32_t*)&_notifyMachPorts
[_notifySlot
])) {
193 if (_notifySlot
== DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT
) {
194 // all the slots are filled, sleep and try again
195 usleep(1000 * 50); // 50ms
197 // if _notifySlot is set we are done
202 for (_notifySlot
=0; _notifySlot
< DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT
; ++_notifySlot
) {
203 if (OSAtomicCompareAndSwap32(0, _sendPortInTarget
, (volatile int32_t*)&_notifyMachPorts
[_notifySlot
])) {
209 if (_notifySlot
== DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT
) {
210 (void)mach_port_deallocate(_targetTask
, _sendPortInTarget
);
212 *kr
= KERN_UREFS_OVERFLOW
;
219 dyld_process_info_notify_base::~dyld_process_info_notify_base() {
221 fprintf(stderr
, "dyld: ~dyld_process_info_notify_base called while still enabled\n");
223 dispatch_release(_queue
);
226 void dyld_process_info_notify_base::teardown() {
229 // The connection to the target is dead. Clean up ports
230 if ( _remoteAllImageInfoBuffer
.getLocalAddress() != 0 && _notifySlot
< DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT
) {
231 mach_port_t extractedPort
= MACH_PORT_NULL
;
232 mach_msg_type_name_t extractedPortType
;
233 kern_return_t kr
= mach_port_extract_right(_targetTask
, _sendPortInTarget
, MACH_MSG_TYPE_COPY_SEND
, &extractedPort
, &extractedPortType
);
234 if (kr
== KERN_SUCCESS
) {
235 if (extractedPort
== _receivePortInMonitor
) {
236 if (OSAtomicCompareAndSwap32(_sendPortInTarget
, 0, (volatile int32_t*)&_notifyMachPorts
[_notifySlot
])) {
237 (void)mach_port_deallocate(_targetTask
, _sendPortInTarget
);
240 (void)mach_port_deallocate(mach_task_self(), extractedPort
);
243 _sendPortInTarget
= 0;
245 dispatch_source_cancel(_machSource
);
246 dispatch_release(_machSource
);
250 dispatch_async(_queue
, ^{
257 bool dyld_process_info_notify_base::enabled() const
262 void dyld_process_info_notify_base::retain()
267 void dyld_process_info_notify_base::release()
269 uint32_t newCount
= --_retainCount
;
271 if ( newCount
== 0 ) {
274 dispatch_async(_queue
, ^{
279 void dyld_process_info_notify_base::replyToMonitoredProcess(mach_msg_header_t
& header
) {
280 mach_msg_header_t replyHeader
;
281 replyHeader
.msgh_bits
= MACH_MSGH_BITS_SET(MACH_MSGH_BITS_REMOTE(header
.msgh_bits
), 0, 0, 0);
282 replyHeader
.msgh_id
= 0;
283 replyHeader
.msgh_local_port
= MACH_PORT_NULL
;
284 replyHeader
.msgh_remote_port
= header
.msgh_remote_port
;
285 replyHeader
.msgh_reserved
= 0;
286 replyHeader
.msgh_size
= sizeof(replyHeader
);
287 kern_return_t r
= mach_msg(&replyHeader
, MACH_SEND_MSG
, replyHeader
.msgh_size
, 0, MACH_PORT_NULL
, 0, MACH_PORT_NULL
);
288 if (r
== KERN_SUCCESS
) {
289 header
.msgh_remote_port
= MACH_PORT_NULL
;
295 void dyld_process_info_notify_base::handleEvent() {
296 // References object may still exist even after the ports are dead. Disable event dispatching
297 // if the ports have been torn down.
301 // This event handler block has an implicit reference to "this"
302 // if incrementing the count goes to one, that means the object may have already been destroyed
303 uint8_t messageBuffer
[DYLD_PROCESS_INFO_NOTIFY_MAX_BUFFER_SIZE
] = {};
304 mach_msg_header_t
* h
= (mach_msg_header_t
*)messageBuffer
;
306 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
), _receivePortInMonitor
, MACH_MSG_TIMEOUT_NONE
, MACH_PORT_NULL
);
307 if ( r
== KERN_SUCCESS
&& !(h
->msgh_bits
& MACH_MSGH_BITS_COMPLEX
)) {
308 //fprintf(stderr, "received message id=0x%X, size=%d\n", h->msgh_id, h->msgh_size);
310 if ( h
->msgh_id
== DYLD_PROCESS_INFO_NOTIFY_LOAD_ID
|| h
->msgh_id
== DYLD_PROCESS_INFO_NOTIFY_UNLOAD_ID
) {
311 // run notifier block for each [un]load image
312 const dyld_process_info_notify_header
* header
= (dyld_process_info_notify_header
*)messageBuffer
;
313 if (sizeof(*header
) <= h
->msgh_size
314 && header
->imagesOffset
<= h
->msgh_size
315 && header
->stringsOffset
<= h
->msgh_size
316 && (header
->imageCount
* sizeof(dyld_process_info_image_entry
)) <= (h
->msgh_size
- header
->imagesOffset
)) {
317 const dyld_process_info_image_entry
* entries
= (dyld_process_info_image_entry
*)&messageBuffer
[header
->imagesOffset
];
318 const char* const stringPool
= (char*)&messageBuffer
[header
->stringsOffset
];
319 for (unsigned i
=0; i
< header
->imageCount
; ++i
) {
320 bool isUnload
= (h
->msgh_id
== DYLD_PROCESS_INFO_NOTIFY_UNLOAD_ID
);
321 if (entries
[i
].pathStringOffset
<= h
->msgh_size
- header
->stringsOffset
) {
322 //fprintf(stderr, "Notifying about: %s\n", stringPool + entries[i].pathStringOffset);
323 _notify(isUnload
, header
->timestamp
, entries
[i
].loadAddress
, entries
[i
].uuid
, stringPool
+ entries
[i
].pathStringOffset
);
329 // reply to dyld, so it can continue
330 replyToMonitoredProcess(*h
);
335 else if ( h
->msgh_id
== DYLD_PROCESS_INFO_NOTIFY_MAIN_ID
) {
336 if (h
->msgh_size
!= sizeof(mach_msg_header_t
)) {
338 } else if ( _notifyMain
!= NULL
) {
341 replyToMonitoredProcess(*h
);
342 } else if ( h
->msgh_id
== MACH_NOTIFY_PORT_DELETED
) {
343 mach_port_t deadPort
= ((mach_port_deleted_notification_t
*)h
)->not_port
;
344 // Validate this notification came from the kernel
345 const mach_msg_audit_trailer_t
*audit_tlr
= (mach_msg_audit_trailer_t
*)((uint8_t *)h
+ round_msg(h
->msgh_size
));
346 if (audit_tlr
->msgh_trailer_type
== MACH_MSG_TRAILER_FORMAT_0
347 && audit_tlr
->msgh_trailer_size
>= sizeof(mach_msg_audit_trailer_t
)
348 // We cannot link to libbsm, so we are hardcoding the audit token offset (5)
349 // And the value the represents the kernel (0)
350 && audit_tlr
->msgh_audit
.val
[5] == 0
351 && deadPort
== _sendPortInTarget
) {
356 fprintf(stderr
, "dyld: received unknown message id=0x%X, size=%d\n", h
->msgh_id
, h
->msgh_size
);
362 dyld_process_info_notify
_dyld_process_info_notify(task_t task
, dispatch_queue_t queue
,
363 void (^notify
)(bool unload
, uint64_t timestamp
, uint64_t machHeader
, const uuid_t uuid
, const char* path
),
364 void (^notifyExit
)(),
367 kern_return_t krSink
= KERN_SUCCESS
;
373 dyld_process_info_notify result
= new dyld_process_info_notify_base(queue
, notify
, notifyExit
, task
, kr
);
374 if (result
->enabled())
376 const_cast<dyld_process_info_notify_base
*>(result
)->release();
380 void _dyld_process_info_notify_main(dyld_process_info_notify object
, void (^notifyMain
)())
382 object
->setNotifyMain(notifyMain
);
385 void _dyld_process_info_notify_retain(dyld_process_info_notify object
)
387 const_cast<dyld_process_info_notify_base
*>(object
)->retain();
390 void _dyld_process_info_notify_release(dyld_process_info_notify object
)
392 const_cast<dyld_process_info_notify_base
*>(object
)->release();
395 static void (*sNotifyMonitoringDyldMain
)() = nullptr;
396 static void (*sNotifyMonitoringDyld
)(bool unloading
, unsigned imageCount
, const struct mach_header
* loadAddresses
[],
397 const char* imagePaths
[]) = nullptr;
399 void setNotifyMonitoringDyldMain(void (*func
)())
401 sNotifyMonitoringDyldMain
= func
;
404 void setNotifyMonitoringDyld(void (*func
)(bool unloading
, unsigned imageCount
,
405 const struct mach_header
* loadAddresses
[],
406 const char* imagePaths
[]))
408 sNotifyMonitoringDyld
= func
;
413 void AllImages::notifyMonitorMain()
415 assert(sNotifyMonitoringDyldMain
!= nullptr);
416 sNotifyMonitoringDyldMain();
419 void AllImages::notifyMonitorLoads(const Array
<LoadedImage
>& newImages
)
421 assert(sNotifyMonitoringDyld
!= nullptr);
422 const struct mach_header
* loadAddresses
[newImages
.count()];
423 const char* loadPaths
[newImages
.count()];
424 for(uint32_t i
= 0; i
<newImages
.count(); ++i
) {
425 loadAddresses
[i
] = newImages
[i
].loadedAddress();
426 loadPaths
[i
] = newImages
[i
].image()->path();
428 sNotifyMonitoringDyld(false, (unsigned)newImages
.count(), loadAddresses
, loadPaths
);
431 void AllImages::notifyMonitorUnloads(const Array
<LoadedImage
>& unloadingImages
)
433 assert(sNotifyMonitoringDyld
!= nullptr);
434 const struct mach_header
* loadAddresses
[unloadingImages
.count()];
435 const char* loadPaths
[unloadingImages
.count()];
436 for(uint32_t i
= 0; i
<unloadingImages
.count(); ++i
) {
437 loadAddresses
[i
] = unloadingImages
[i
].loadedAddress();
438 loadPaths
[i
] = unloadingImages
[i
].image()->path();
440 sNotifyMonitoringDyld(true, (unsigned)unloadingImages
.count(), loadAddresses
, loadPaths
);