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 "dyld_process_info.h"
34 #include "dyld_process_info_internal.h"
35 #include "dyld_images.h"
36 #include "dyld_priv.h"
38 typedef void (^Notify
)(bool unload
, uint64_t timestamp
, uint64_t machHeader
, const uuid_t uuid
, const char* path
);
39 typedef void (^NotifyExit
)();
40 typedef void (^NotifyMain
)();
44 // Object used for monitoring another processes dyld loads
46 struct __attribute__((visibility("hidden"))) dyld_process_info_notify_base
48 static dyld_process_info_notify_base
* make(task_t task
, dispatch_queue_t queue
, Notify notify
, NotifyExit notifyExit
, kern_return_t
* kr
);
49 ~dyld_process_info_notify_base();
51 uint32_t& retainCount() const { return _retainCount
; }
52 void setNotifyMain(NotifyMain notifyMain
) const { _notifyMain
= notifyMain
; }
55 dyld_process_info_notify_base(dispatch_queue_t queue
, Notify notify
, NotifyExit notifyExit
, task_t task
);
56 kern_return_t
makePorts();
57 kern_return_t
pokeSendPortIntoTarget();
58 kern_return_t
unpokeSendPortInTarget();
59 void setMachSourceOnQueue();
60 void* operator new (size_t, void* buf
) { return buf
; }
62 mutable uint32_t _retainCount
;
63 dispatch_queue_t _queue
;
65 NotifyExit _notifyExit
;
66 mutable NotifyMain _notifyMain
;
68 dispatch_source_t _machSource
;
69 uint64_t _portAddressInTarget
;
70 mach_port_t _sendPortInTarget
; // target is process being watched for image loading/unloading
71 mach_port_t _receivePortInMonitor
; // monitor is process being notified of image loading/unloading
75 dyld_process_info_notify_base::dyld_process_info_notify_base(dispatch_queue_t queue
, Notify notify
, NotifyExit notifyExit
, task_t task
)
76 : _retainCount(1), _queue(queue
), _notify(notify
), _notifyExit(notifyExit
), _notifyMain(NULL
), _targetTask(task
), _machSource(NULL
), _portAddressInTarget(0), _sendPortInTarget(0), _receivePortInMonitor(0)
78 dispatch_retain(_queue
);
81 dyld_process_info_notify_base::~dyld_process_info_notify_base()
84 dispatch_release(_machSource
);
87 if ( _portAddressInTarget
) {
88 unpokeSendPortInTarget();
89 _portAddressInTarget
= 0;
91 if ( _sendPortInTarget
) {
92 _sendPortInTarget
= 0;
94 dispatch_release(_queue
);
95 if ( _receivePortInMonitor
!= 0 ) {
96 mach_port_deallocate(mach_task_self(), _receivePortInMonitor
);
97 _receivePortInMonitor
= 0;
102 dyld_process_info_notify_base
* dyld_process_info_notify_base::make(task_t task
, dispatch_queue_t queue
, Notify notify
, NotifyExit notifyExit
, kern_return_t
* kr
)
104 void* storage
= malloc(sizeof(dyld_process_info_notify_base
));
105 dyld_process_info_notify_base
* obj
= new (storage
) dyld_process_info_notify_base(queue
, notify
, notifyExit
, task
);
107 if ( kern_return_t r
= obj
->makePorts() ) {
113 obj
->setMachSourceOnQueue();
115 if ( kern_return_t r
= obj
->pokeSendPortIntoTarget() ) {
131 kern_return_t
dyld_process_info_notify_base::makePorts()
133 // Allocate a port to listen on in this monitoring task
134 if ( kern_return_t r
= mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE
, &_receivePortInMonitor
) )
137 // Add send rights for replying
138 if ( kern_return_t r
= mach_port_insert_right(mach_task_self(), _receivePortInMonitor
, _receivePortInMonitor
, MACH_MSG_TYPE_MAKE_SEND
) )
141 // Allocate a name in the target. We need a new name to add send rights to
142 if ( kern_return_t r
= mach_port_allocate(_targetTask
, MACH_PORT_RIGHT_DEAD_NAME
, &_sendPortInTarget
) )
145 // Deallocate the dead name
146 if ( kern_return_t r
= mach_port_mod_refs(_targetTask
, _sendPortInTarget
, MACH_PORT_RIGHT_DEAD_NAME
, -1) )
149 // Make the dead name a send right to our listening port
150 if ( kern_return_t r
= mach_port_insert_right(_targetTask
, _sendPortInTarget
, _receivePortInMonitor
, MACH_MSG_TYPE_MAKE_SEND
) )
153 // Notify us if the target dies
154 mach_port_t previous
= MACH_PORT_NULL
;
155 if ( kern_return_t r
= mach_port_request_notification(_targetTask
, _sendPortInTarget
, MACH_NOTIFY_DEAD_NAME
, 0, _receivePortInMonitor
, MACH_MSG_TYPE_MAKE_SEND_ONCE
, &previous
))
158 //fprintf(stderr, "_sendPortInTarget=%d, _receivePortInMonitor=%d\n", _sendPortInTarget, _receivePortInMonitor);
164 void dyld_process_info_notify_base::setMachSourceOnQueue()
166 NotifyExit exitHandler
= _notifyExit
;
167 _machSource
= dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV
, _receivePortInMonitor
, 0, _queue
);
168 dispatch_source_set_event_handler(_machSource
, ^{
169 uint8_t messageBuffer
[DYLD_PROCESS_INFO_NOTIFY_MAX_BUFFER_SIZE
];
170 mach_msg_header_t
* h
= (mach_msg_header_t
*)messageBuffer
;
172 kern_return_t r
= mach_msg(h
, MACH_RCV_MSG
, 0, sizeof(messageBuffer
), _receivePortInMonitor
, MACH_MSG_TIMEOUT_NONE
, MACH_PORT_NULL
);
173 if ( r
== KERN_SUCCESS
) {
174 //fprintf(stderr, "received message id=0x%X, size=%d\n", h->msgh_id, h->msgh_size);
175 if ( h
->msgh_id
== DYLD_PROCESS_INFO_NOTIFY_LOAD_ID
|| h
->msgh_id
== DYLD_PROCESS_INFO_NOTIFY_UNLOAD_ID
) {
176 // run notifier block for each [un]load image
177 const dyld_process_info_notify_header
* header
= (dyld_process_info_notify_header
*)messageBuffer
;
178 const dyld_process_info_image_entry
* entries
= (dyld_process_info_image_entry
*)&messageBuffer
[header
->imagesOffset
];
179 const char* const stringPool
= (char*)&messageBuffer
[header
->stringsOffset
];
180 for (unsigned i
=0; i
< header
->imageCount
; ++i
) {
181 bool isUnload
= (h
->msgh_id
== DYLD_PROCESS_INFO_NOTIFY_UNLOAD_ID
);
182 _notify(isUnload
, header
->timestamp
, entries
[i
].loadAddress
, entries
[i
].uuid
, stringPool
+ entries
[i
].pathStringOffset
);
184 // reply to dyld, so it can continue
185 mach_msg_header_t replyHeader
;
186 replyHeader
.msgh_bits
= MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND
, MACH_MSG_TYPE_MAKE_SEND
);
187 replyHeader
.msgh_id
= 0;
188 replyHeader
.msgh_local_port
= MACH_PORT_NULL
;
189 replyHeader
.msgh_remote_port
= h
->msgh_remote_port
;
190 replyHeader
.msgh_reserved
= 0;
191 replyHeader
.msgh_size
= sizeof(replyHeader
);
192 mach_msg(&replyHeader
, MACH_SEND_MSG
| MACH_SEND_TIMEOUT
, replyHeader
.msgh_size
, 0, MACH_PORT_NULL
, 100, MACH_PORT_NULL
);
194 else if ( h
->msgh_id
== DYLD_PROCESS_INFO_NOTIFY_MAIN_ID
) {
195 if ( _notifyMain
!= NULL
) {
198 // reply to dyld, so it can continue
199 mach_msg_header_t replyHeader
;
200 replyHeader
.msgh_bits
= MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND
, MACH_MSG_TYPE_MAKE_SEND
);
201 replyHeader
.msgh_id
= 0;
202 replyHeader
.msgh_local_port
= MACH_PORT_NULL
;
203 replyHeader
.msgh_remote_port
= h
->msgh_remote_port
;
204 replyHeader
.msgh_reserved
= 0;
205 replyHeader
.msgh_size
= sizeof(replyHeader
);
206 mach_msg(&replyHeader
, MACH_SEND_MSG
| MACH_SEND_TIMEOUT
, replyHeader
.msgh_size
, 0, MACH_PORT_NULL
, 100, MACH_PORT_NULL
);
208 else if ( h
->msgh_id
== MACH_NOTIFY_PORT_DELETED
) {
209 mach_port_t deadPort
= ((mach_port_deleted_notification_t
*)h
)->not_port
;
210 //fprintf(stderr, "received message id=MACH_NOTIFY_PORT_DELETED, size=%d, deadPort=%d\n", h->msgh_size, deadPort);
211 if ( deadPort
== _sendPortInTarget
) {
212 // target process died. Clean up ports
213 _sendPortInTarget
= 0;
214 mach_port_deallocate(mach_task_self(), _receivePortInMonitor
);
215 _receivePortInMonitor
= 0;
216 _portAddressInTarget
= 0;
217 // notify that target is gone
222 fprintf(stderr
, "received unknown message id=0x%X, size=%d\n", h
->msgh_id
, h
->msgh_size
);
226 dispatch_resume(_machSource
);
230 kern_return_t
dyld_process_info_notify_base::pokeSendPortIntoTarget()
232 // get location on all_image_infos in target task
233 task_dyld_info_data_t taskDyldInfo
;
234 mach_msg_type_number_t count
= TASK_DYLD_INFO_COUNT
;
235 kern_return_t r
= task_info(_targetTask
, TASK_DYLD_INFO
, (task_info_t
)&taskDyldInfo
, &count
);
239 // remap the page containing all_image_infos into this process r/w
240 mach_vm_address_t mappedAddress
= 0;
241 mach_vm_size_t mappedSize
= taskDyldInfo
.all_image_info_size
;
242 vm_prot_t curProt
= VM_PROT_NONE
;
243 vm_prot_t maxProt
= VM_PROT_NONE
;
244 r
= mach_vm_remap(mach_task_self(), &mappedAddress
, mappedSize
, 0, VM_FLAGS_ANYWHERE
| VM_FLAGS_RETURN_DATA_ADDR
,
245 _targetTask
, taskDyldInfo
.all_image_info_addr
, false, &curProt
, &maxProt
, VM_INHERIT_NONE
);
248 if ( curProt
!= (VM_PROT_READ
|VM_PROT_WRITE
) )
249 return KERN_PROTECTION_FAILURE
;
251 // atomically set port into all_image_info_struct
252 static_assert(sizeof(mach_port_t
) == sizeof(uint32_t), "machport size not 32-bits");
254 mach_vm_address_t mappedAddressToPokePort
= 0;
255 if ( taskDyldInfo
.all_image_info_format
== TASK_DYLD_ALL_IMAGE_INFO_32
)
256 mappedAddressToPokePort
= mappedAddress
+ offsetof(dyld_all_image_infos_32
,notifyMachPorts
);
258 mappedAddressToPokePort
= mappedAddress
+ offsetof(dyld_all_image_infos_64
,notifyMachPorts
);
260 // use first available slot
261 bool slotFound
= false;
262 for (int slotIndex
=0; slotIndex
< DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT
; ++slotIndex
) {
263 if ( OSAtomicCompareAndSwap32Barrier(0, _sendPortInTarget
, (volatile int32_t*)mappedAddressToPokePort
) ) {
267 mappedAddressToPokePort
+= sizeof(uint32_t);
270 mach_vm_deallocate(mach_task_self(), mappedAddress
, mappedSize
);
271 return KERN_UREFS_OVERFLOW
;
273 _portAddressInTarget
= taskDyldInfo
.all_image_info_addr
+ mappedAddressToPokePort
- mappedAddress
;
274 //fprintf(stderr, "poked port %d into target at address 0x%llX\n", _sendPortInTarget, _portAddressInTarget);
275 mach_vm_deallocate(mach_task_self(), mappedAddress
, mappedSize
);
281 kern_return_t
dyld_process_info_notify_base::unpokeSendPortInTarget()
283 // remap the page containing all_image_infos into this process r/w
284 mach_vm_address_t mappedAddress
= 0;
285 mach_vm_size_t mappedSize
= sizeof(mach_port_t
);
286 vm_prot_t curProt
= VM_PROT_NONE
;
287 vm_prot_t maxProt
= VM_PROT_NONE
;
288 kern_return_t r
= mach_vm_remap(mach_task_self(), &mappedAddress
, mappedSize
, 0, VM_FLAGS_ANYWHERE
| VM_FLAGS_RETURN_DATA_ADDR
,
289 _targetTask
, _portAddressInTarget
, false, &curProt
, &maxProt
, VM_INHERIT_NONE
);
292 if ( curProt
!= (VM_PROT_READ
|VM_PROT_WRITE
) )
293 return KERN_PROTECTION_FAILURE
;
295 OSAtomicCompareAndSwap32Barrier(_sendPortInTarget
, 0, (volatile int32_t*)mappedAddress
);
297 //fprintf(stderr, "cleared port %d from target\n", _sendPortInTarget);
298 mach_vm_deallocate(mach_task_self(), mappedAddress
, mappedSize
);
304 dyld_process_info_notify
_dyld_process_info_notify(task_t task
, dispatch_queue_t queue
,
305 void (^notify
)(bool unload
, uint64_t timestamp
, uint64_t machHeader
, const uuid_t uuid
, const char* path
),
306 void (^notifyExit
)(),
309 return dyld_process_info_notify_base::make(task
, queue
, notify
, notifyExit
, kr
);
312 void _dyld_process_info_notify_main(dyld_process_info_notify object
, void (^notifyMain
)())
314 object
->setNotifyMain(notifyMain
);
317 void _dyld_process_info_notify_retain(dyld_process_info_notify object
)
319 object
->retainCount() += 1;
322 void _dyld_process_info_notify_release(dyld_process_info_notify object
)
324 object
->retainCount() -= 1;
325 if ( object
->retainCount() == 0 ) {
326 object
->~dyld_process_info_notify_base();