]> git.saurik.com Git - apple/dyld.git/blob - src/dyld_process_info_notify.cpp
dyld-421.1.tar.gz
[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
41
42 //
43 // Object used for monitoring another processes dyld loads
44 //
45 struct __attribute__((visibility("hidden"))) dyld_process_info_notify_base
46 {
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();
49
50 uint32_t& retainCount() const { return _retainCount; }
51
52 private:
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; }
59
60 mutable uint32_t _retainCount;
61 dispatch_queue_t _queue;
62 Notify _notify;
63 NotifyExit _notifyExit;
64 task_t _targetTask;
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
69 };
70
71
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)
74 {
75 dispatch_retain(_queue);
76 }
77
78 dyld_process_info_notify_base::~dyld_process_info_notify_base()
79 {
80 if ( _machSource ) {
81 dispatch_release(_machSource);
82 _machSource = NULL;
83 }
84 if ( _portAddressInTarget ) {
85 unpokeSendPortInTarget();
86 _portAddressInTarget = 0;
87 }
88 if ( _sendPortInTarget ) {
89 _sendPortInTarget = 0;
90 }
91 dispatch_release(_queue);
92 if ( _receivePortInMonitor != 0 ) {
93 mach_port_deallocate(mach_task_self(), _receivePortInMonitor);
94 _receivePortInMonitor = 0;
95 }
96 }
97
98
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)
100 {
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);
103
104 if ( kern_return_t r = obj->makePorts() ) {
105 if ( kr != NULL )
106 *kr = r;
107 goto fail;
108 }
109
110 obj->setMachSourceOnQueue();
111
112 if ( kern_return_t r = obj->pokeSendPortIntoTarget() ) {
113 if ( kr != NULL )
114 *kr = r;
115 goto fail;
116 }
117
118 if ( kr != NULL )
119 *kr = KERN_SUCCESS;
120 return obj;
121
122 fail:
123 free(obj);
124 return NULL;
125 }
126
127
128 kern_return_t dyld_process_info_notify_base::makePorts()
129 {
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) )
132 return r;
133
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) )
136 return r;
137
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) )
140 return r;
141
142 // Deallocate the dead name
143 if ( kern_return_t r = mach_port_mod_refs(_targetTask, _sendPortInTarget, MACH_PORT_RIGHT_DEAD_NAME, -1) )
144 return r;
145
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) )
148 return r;
149
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))
153 return r;
154
155 //fprintf(stderr, "_sendPortInTarget=%d, _receivePortInMonitor=%d\n", _sendPortInTarget, _receivePortInMonitor);
156 return KERN_SUCCESS;
157 }
158
159
160
161 void dyld_process_info_notify_base::setMachSourceOnQueue()
162 {
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;
168
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);
180 }
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);
190 }
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
201 exitHandler();
202 }
203 }
204 else {
205 fprintf(stderr, "received unknown message id=0x%X, size=%d\n", h->msgh_id, h->msgh_size);
206 }
207 }
208 });
209 dispatch_resume(_machSource);
210 }
211
212
213 kern_return_t dyld_process_info_notify_base::pokeSendPortIntoTarget()
214 {
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);
219 if ( r )
220 return r;
221
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);
229 if ( r )
230 return r;
231 if ( curProt != (VM_PROT_READ|VM_PROT_WRITE) )
232 return KERN_PROTECTION_FAILURE;
233
234 // atomically set port into all_image_info_struct
235 static_assert(sizeof(mach_port_t) == sizeof(uint32_t), "machport size not 32-bits");
236
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);
240 else
241 mappedAddressToPokePort = mappedAddress + offsetof(dyld_all_image_infos_64,notifyMachPorts);
242
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) ) {
247 slotFound = true;
248 break;
249 }
250 mappedAddressToPokePort += sizeof(uint32_t);
251 }
252 if ( !slotFound ) {
253 mach_vm_deallocate(mach_task_self(), mappedAddress, mappedSize);
254 return KERN_UREFS_OVERFLOW;
255 }
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);
259 return r;
260 }
261
262
263
264 kern_return_t dyld_process_info_notify_base::unpokeSendPortInTarget()
265 {
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);
273 if ( r )
274 return r;
275 if ( curProt != (VM_PROT_READ|VM_PROT_WRITE) )
276 return KERN_PROTECTION_FAILURE;
277
278 OSAtomicCompareAndSwap32Barrier(_sendPortInTarget, 0, (volatile int32_t*)mappedAddress);
279
280 //fprintf(stderr, "cleared port %d from target\n", _sendPortInTarget);
281 mach_vm_deallocate(mach_task_self(), mappedAddress, mappedSize);
282 return r;
283 }
284
285
286
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)(),
290 kern_return_t* kr)
291 {
292 return dyld_process_info_notify_base::make(task, queue, notify, notifyExit, kr);
293 }
294
295 void _dyld_process_info_notify_retain(dyld_process_info_notify object)
296 {
297 object->retainCount() += 1;
298 }
299
300 void _dyld_process_info_notify_release(dyld_process_info_notify object)
301 {
302 object->retainCount() -= 1;
303 if ( object->retainCount() == 0 ) {
304 object->~dyld_process_info_notify_base();
305 free((void*)object);
306 }
307 }
308
309
310