]> git.saurik.com Git - apple/dyld.git/blob - src/dyld_process_info_notify.cpp
760f06c558eed83e21b3004802db76b68b7c3746
[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
33 #include "dyld_process_info.h"
34 #include "dyld_process_info_internal.h"
35 #include "dyld_images.h"
36 #include "dyld_priv.h"
37
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)();
41
42
43 //
44 // Object used for monitoring another processes dyld loads
45 //
46 struct __attribute__((visibility("hidden"))) dyld_process_info_notify_base
47 {
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();
50
51 uint32_t& retainCount() const { return _retainCount; }
52 void setNotifyMain(NotifyMain notifyMain) const { _notifyMain = notifyMain; }
53
54 private:
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; }
61
62 mutable uint32_t _retainCount;
63 dispatch_queue_t _queue;
64 Notify _notify;
65 NotifyExit _notifyExit;
66 mutable NotifyMain _notifyMain;
67 task_t _targetTask;
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
72 };
73
74
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)
77 {
78 dispatch_retain(_queue);
79 }
80
81 dyld_process_info_notify_base::~dyld_process_info_notify_base()
82 {
83 if ( _machSource ) {
84 dispatch_release(_machSource);
85 _machSource = NULL;
86 }
87 if ( _portAddressInTarget ) {
88 unpokeSendPortInTarget();
89 _portAddressInTarget = 0;
90 }
91 if ( _sendPortInTarget ) {
92 _sendPortInTarget = 0;
93 }
94 dispatch_release(_queue);
95 if ( _receivePortInMonitor != 0 ) {
96 mach_port_deallocate(mach_task_self(), _receivePortInMonitor);
97 _receivePortInMonitor = 0;
98 }
99 }
100
101
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)
103 {
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);
106
107 if ( kern_return_t r = obj->makePorts() ) {
108 if ( kr != NULL )
109 *kr = r;
110 goto fail;
111 }
112
113 obj->setMachSourceOnQueue();
114
115 if ( kern_return_t r = obj->pokeSendPortIntoTarget() ) {
116 if ( kr != NULL )
117 *kr = r;
118 goto fail;
119 }
120
121 if ( kr != NULL )
122 *kr = KERN_SUCCESS;
123 return obj;
124
125 fail:
126 free(obj);
127 return NULL;
128 }
129
130
131 kern_return_t dyld_process_info_notify_base::makePorts()
132 {
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) )
135 return r;
136
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) )
139 return r;
140
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) )
143 return r;
144
145 // Deallocate the dead name
146 if ( kern_return_t r = mach_port_mod_refs(_targetTask, _sendPortInTarget, MACH_PORT_RIGHT_DEAD_NAME, -1) )
147 return r;
148
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) )
151 return r;
152
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))
156 return r;
157
158 //fprintf(stderr, "_sendPortInTarget=%d, _receivePortInMonitor=%d\n", _sendPortInTarget, _receivePortInMonitor);
159 return KERN_SUCCESS;
160 }
161
162
163
164 void dyld_process_info_notify_base::setMachSourceOnQueue()
165 {
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;
171
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);
183 }
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);
193 }
194 else if ( h->msgh_id == DYLD_PROCESS_INFO_NOTIFY_MAIN_ID ) {
195 if ( _notifyMain != NULL ) {
196 _notifyMain();
197 }
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);
207 }
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
218 exitHandler();
219 }
220 }
221 else {
222 fprintf(stderr, "received unknown message id=0x%X, size=%d\n", h->msgh_id, h->msgh_size);
223 }
224 }
225 });
226 dispatch_resume(_machSource);
227 }
228
229
230 kern_return_t dyld_process_info_notify_base::pokeSendPortIntoTarget()
231 {
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);
236 if ( r )
237 return r;
238
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);
246 if ( r )
247 return r;
248 if ( curProt != (VM_PROT_READ|VM_PROT_WRITE) )
249 return KERN_PROTECTION_FAILURE;
250
251 // atomically set port into all_image_info_struct
252 static_assert(sizeof(mach_port_t) == sizeof(uint32_t), "machport size not 32-bits");
253
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);
257 else
258 mappedAddressToPokePort = mappedAddress + offsetof(dyld_all_image_infos_64,notifyMachPorts);
259
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) ) {
264 slotFound = true;
265 break;
266 }
267 mappedAddressToPokePort += sizeof(uint32_t);
268 }
269 if ( !slotFound ) {
270 mach_vm_deallocate(mach_task_self(), mappedAddress, mappedSize);
271 return KERN_UREFS_OVERFLOW;
272 }
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);
276 return r;
277 }
278
279
280
281 kern_return_t dyld_process_info_notify_base::unpokeSendPortInTarget()
282 {
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);
290 if ( r )
291 return r;
292 if ( curProt != (VM_PROT_READ|VM_PROT_WRITE) )
293 return KERN_PROTECTION_FAILURE;
294
295 OSAtomicCompareAndSwap32Barrier(_sendPortInTarget, 0, (volatile int32_t*)mappedAddress);
296
297 //fprintf(stderr, "cleared port %d from target\n", _sendPortInTarget);
298 mach_vm_deallocate(mach_task_self(), mappedAddress, mappedSize);
299 return r;
300 }
301
302
303
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)(),
307 kern_return_t* kr)
308 {
309 return dyld_process_info_notify_base::make(task, queue, notify, notifyExit, kr);
310 }
311
312 void _dyld_process_info_notify_main(dyld_process_info_notify object, void (^notifyMain)())
313 {
314 object->setNotifyMain(notifyMain);
315 }
316
317 void _dyld_process_info_notify_retain(dyld_process_info_notify object)
318 {
319 object->retainCount() += 1;
320 }
321
322 void _dyld_process_info_notify_release(dyld_process_info_notify object)
323 {
324 object->retainCount() -= 1;
325 if ( object->retainCount() == 0 ) {
326 object->~dyld_process_info_notify_base();
327 free((void*)object);
328 }
329 }
330
331
332