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
)();
43 // Object used for monitoring another processes dyld loads
45 struct __attribute__((visibility("hidden"))) dyld_process_info_notify_base
47 static dyld_process_info_notify_base
* make(task_t task
, dispatch_queue_t queue
, Notify notify
, NotifyExit notifyExit
, kern_return_t
* kr
);
48 ~dyld_process_info_notify_base();
50 uint32_t& retainCount() const { return _retainCount
; }
53 dyld_process_info_notify_base(dispatch_queue_t queue
, Notify notify
, NotifyExit notifyExit
, task_t task
);
54 kern_return_t
makePorts();
55 kern_return_t
pokeSendPortIntoTarget();
56 kern_return_t
unpokeSendPortInTarget();
57 void setMachSourceOnQueue();
58 void* operator new (size_t, void* buf
) { return buf
; }
60 mutable uint32_t _retainCount
;
61 dispatch_queue_t _queue
;
63 NotifyExit _notifyExit
;
65 dispatch_source_t _machSource
;
66 uint64_t _portAddressInTarget
;
67 mach_port_t _sendPortInTarget
; // target is process being watched for image loading/unloading
68 mach_port_t _receivePortInMonitor
; // monitor is process being notified of image loading/unloading
72 dyld_process_info_notify_base::dyld_process_info_notify_base(dispatch_queue_t queue
, Notify notify
, NotifyExit notifyExit
, task_t task
)
73 : _retainCount(1), _queue(queue
), _notify(notify
), _notifyExit(notifyExit
), _targetTask(task
), _machSource(NULL
), _portAddressInTarget(0), _sendPortInTarget(0), _receivePortInMonitor(0)
75 dispatch_retain(_queue
);
78 dyld_process_info_notify_base::~dyld_process_info_notify_base()
81 dispatch_release(_machSource
);
84 if ( _portAddressInTarget
) {
85 unpokeSendPortInTarget();
86 _portAddressInTarget
= 0;
88 if ( _sendPortInTarget
) {
89 _sendPortInTarget
= 0;
91 dispatch_release(_queue
);
92 if ( _receivePortInMonitor
!= 0 ) {
93 mach_port_deallocate(mach_task_self(), _receivePortInMonitor
);
94 _receivePortInMonitor
= 0;
99 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
)
101 void* storage
= malloc(sizeof(dyld_process_info_notify_base
));
102 dyld_process_info_notify_base
* obj
= new (storage
) dyld_process_info_notify_base(queue
, notify
, notifyExit
, task
);
104 if ( kern_return_t r
= obj
->makePorts() ) {
110 obj
->setMachSourceOnQueue();
112 if ( kern_return_t r
= obj
->pokeSendPortIntoTarget() ) {
128 kern_return_t
dyld_process_info_notify_base::makePorts()
130 // Allocate a port to listen on in this monitoring task
131 if ( kern_return_t r
= mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE
, &_receivePortInMonitor
) )
134 // Add send rights for replying
135 if ( kern_return_t r
= mach_port_insert_right(mach_task_self(), _receivePortInMonitor
, _receivePortInMonitor
, MACH_MSG_TYPE_MAKE_SEND
) )
138 // Allocate a name in the target. We need a new name to add send rights to
139 if ( kern_return_t r
= mach_port_allocate(_targetTask
, MACH_PORT_RIGHT_DEAD_NAME
, &_sendPortInTarget
) )
142 // Deallocate the dead name
143 if ( kern_return_t r
= mach_port_mod_refs(_targetTask
, _sendPortInTarget
, MACH_PORT_RIGHT_DEAD_NAME
, -1) )
146 // Make the dead name a send right to our listening port
147 if ( kern_return_t r
= mach_port_insert_right(_targetTask
, _sendPortInTarget
, _receivePortInMonitor
, MACH_MSG_TYPE_MAKE_SEND
) )
150 // Notify us if the target dies
151 mach_port_t previous
= MACH_PORT_NULL
;
152 if ( kern_return_t r
= mach_port_request_notification(_targetTask
, _sendPortInTarget
, MACH_NOTIFY_DEAD_NAME
, 0, _receivePortInMonitor
, MACH_MSG_TYPE_MAKE_SEND_ONCE
, &previous
))
155 //fprintf(stderr, "_sendPortInTarget=%d, _receivePortInMonitor=%d\n", _sendPortInTarget, _receivePortInMonitor);
161 void dyld_process_info_notify_base::setMachSourceOnQueue()
163 NotifyExit exitHandler
= _notifyExit
;
164 _machSource
= dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV
, _receivePortInMonitor
, 0, _queue
);
165 dispatch_source_set_event_handler(_machSource
, ^{
166 uint8_t messageBuffer
[DYLD_PROCESS_INFO_NOTIFY_MAX_BUFFER_SIZE
];
167 mach_msg_header_t
* h
= (mach_msg_header_t
*)messageBuffer
;
169 kern_return_t r
= mach_msg(h
, MACH_RCV_MSG
, 0, sizeof(messageBuffer
), _receivePortInMonitor
, MACH_MSG_TIMEOUT_NONE
, MACH_PORT_NULL
);
170 if ( r
== KERN_SUCCESS
) {
171 //fprintf(stderr, "received message id=0x%X, size=%d\n", h->msgh_id, h->msgh_size);
172 if ( h
->msgh_id
== DYLD_PROCESS_INFO_NOTIFY_LOAD_ID
|| h
->msgh_id
== DYLD_PROCESS_INFO_NOTIFY_UNLOAD_ID
) {
173 // run notifier block for each [un]load image
174 const dyld_process_info_notify_header
* header
= (dyld_process_info_notify_header
*)messageBuffer
;
175 const dyld_process_info_image_entry
* entries
= (dyld_process_info_image_entry
*)&messageBuffer
[header
->imagesOffset
];
176 const char* const stringPool
= (char*)&messageBuffer
[header
->stringsOffset
];
177 for (unsigned i
=0; i
< header
->imageCount
; ++i
) {
178 bool isUnload
= (h
->msgh_id
== DYLD_PROCESS_INFO_NOTIFY_UNLOAD_ID
);
179 _notify(isUnload
, header
->timestamp
, entries
[i
].loadAddress
, entries
[i
].uuid
, stringPool
+ entries
[i
].pathStringOffset
);
181 // reply to dyld, so it can continue
182 mach_msg_header_t replyHeader
;
183 replyHeader
.msgh_bits
= MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND
, MACH_MSG_TYPE_MAKE_SEND
);
184 replyHeader
.msgh_id
= 0;
185 replyHeader
.msgh_local_port
= MACH_PORT_NULL
;
186 replyHeader
.msgh_remote_port
= h
->msgh_remote_port
;
187 replyHeader
.msgh_reserved
= 0;
188 replyHeader
.msgh_size
= sizeof(replyHeader
);
189 mach_msg(&replyHeader
, MACH_SEND_MSG
| MACH_SEND_TIMEOUT
, replyHeader
.msgh_size
, 0, MACH_PORT_NULL
, 100, MACH_PORT_NULL
);
191 else if ( h
->msgh_id
== MACH_NOTIFY_PORT_DELETED
) {
192 mach_port_t deadPort
= ((mach_port_deleted_notification_t
*)h
)->not_port
;
193 //fprintf(stderr, "received message id=MACH_NOTIFY_PORT_DELETED, size=%d, deadPort=%d\n", h->msgh_size, deadPort);
194 if ( deadPort
== _sendPortInTarget
) {
195 // target process died. Clean up ports
196 _sendPortInTarget
= 0;
197 mach_port_deallocate(mach_task_self(), _receivePortInMonitor
);
198 _receivePortInMonitor
= 0;
199 _portAddressInTarget
= 0;
200 // notify that target is gone
205 fprintf(stderr
, "received unknown message id=0x%X, size=%d\n", h
->msgh_id
, h
->msgh_size
);
209 dispatch_resume(_machSource
);
213 kern_return_t
dyld_process_info_notify_base::pokeSendPortIntoTarget()
215 // get location on all_image_infos in target task
216 task_dyld_info_data_t taskDyldInfo
;
217 mach_msg_type_number_t count
= TASK_DYLD_INFO_COUNT
;
218 kern_return_t r
= task_info(_targetTask
, TASK_DYLD_INFO
, (task_info_t
)&taskDyldInfo
, &count
);
222 // remap the page containing all_image_infos into this process r/w
223 mach_vm_address_t mappedAddress
= 0;
224 mach_vm_size_t mappedSize
= taskDyldInfo
.all_image_info_size
;
225 vm_prot_t curProt
= VM_PROT_NONE
;
226 vm_prot_t maxProt
= VM_PROT_NONE
;
227 r
= mach_vm_remap(mach_task_self(), &mappedAddress
, mappedSize
, 0, VM_FLAGS_ANYWHERE
| VM_FLAGS_RETURN_DATA_ADDR
,
228 _targetTask
, taskDyldInfo
.all_image_info_addr
, false, &curProt
, &maxProt
, VM_INHERIT_NONE
);
231 if ( curProt
!= (VM_PROT_READ
|VM_PROT_WRITE
) )
232 return KERN_PROTECTION_FAILURE
;
234 // atomically set port into all_image_info_struct
235 static_assert(sizeof(mach_port_t
) == sizeof(uint32_t), "machport size not 32-bits");
237 mach_vm_address_t mappedAddressToPokePort
= 0;
238 if ( taskDyldInfo
.all_image_info_format
== TASK_DYLD_ALL_IMAGE_INFO_32
)
239 mappedAddressToPokePort
= mappedAddress
+ offsetof(dyld_all_image_infos_32
,notifyMachPorts
);
241 mappedAddressToPokePort
= mappedAddress
+ offsetof(dyld_all_image_infos_64
,notifyMachPorts
);
243 // use first available slot
244 bool slotFound
= false;
245 for (int slotIndex
=0; slotIndex
< DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT
; ++slotIndex
) {
246 if ( OSAtomicCompareAndSwap32Barrier(0, _sendPortInTarget
, (volatile int32_t*)mappedAddressToPokePort
) ) {
250 mappedAddressToPokePort
+= sizeof(uint32_t);
253 mach_vm_deallocate(mach_task_self(), mappedAddress
, mappedSize
);
254 return KERN_UREFS_OVERFLOW
;
256 _portAddressInTarget
= taskDyldInfo
.all_image_info_addr
+ mappedAddressToPokePort
- mappedAddress
;
257 //fprintf(stderr, "poked port %d into target at address 0x%llX\n", _sendPortInTarget, _portAddressInTarget);
258 mach_vm_deallocate(mach_task_self(), mappedAddress
, mappedSize
);
264 kern_return_t
dyld_process_info_notify_base::unpokeSendPortInTarget()
266 // remap the page containing all_image_infos into this process r/w
267 mach_vm_address_t mappedAddress
= 0;
268 mach_vm_size_t mappedSize
= sizeof(mach_port_t
);
269 vm_prot_t curProt
= VM_PROT_NONE
;
270 vm_prot_t maxProt
= VM_PROT_NONE
;
271 kern_return_t r
= mach_vm_remap(mach_task_self(), &mappedAddress
, mappedSize
, 0, VM_FLAGS_ANYWHERE
| VM_FLAGS_RETURN_DATA_ADDR
,
272 _targetTask
, _portAddressInTarget
, false, &curProt
, &maxProt
, VM_INHERIT_NONE
);
275 if ( curProt
!= (VM_PROT_READ
|VM_PROT_WRITE
) )
276 return KERN_PROTECTION_FAILURE
;
278 OSAtomicCompareAndSwap32Barrier(_sendPortInTarget
, 0, (volatile int32_t*)mappedAddress
);
280 //fprintf(stderr, "cleared port %d from target\n", _sendPortInTarget);
281 mach_vm_deallocate(mach_task_self(), mappedAddress
, mappedSize
);
287 dyld_process_info_notify
_dyld_process_info_notify(task_t task
, dispatch_queue_t queue
,
288 void (^notify
)(bool unload
, uint64_t timestamp
, uint64_t machHeader
, const uuid_t uuid
, const char* path
),
289 void (^notifyExit
)(),
292 return dyld_process_info_notify_base::make(task
, queue
, notify
, notifyExit
, kr
);
295 void _dyld_process_info_notify_retain(dyld_process_info_notify object
)
297 object
->retainCount() += 1;
300 void _dyld_process_info_notify_release(dyld_process_info_notify object
)
302 object
->retainCount() -= 1;
303 if ( object
->retainCount() == 0 ) {
304 object
->~dyld_process_info_notify_base();