]> git.saurik.com Git - apple/libdispatch.git/blob - src/firehose/firehose_server.c
libdispatch-703.1.4.tar.gz
[apple/libdispatch.git] / src / firehose / firehose_server.c
1 /*
2 * Copyright (c) 2015 Apple Inc. All rights reserved.
3 *
4 * @APPLE_APACHE_LICENSE_HEADER_START@
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 *
18 * @APPLE_APACHE_LICENSE_HEADER_END@
19 */
20
21 #include <servers/bootstrap.h>
22 #include <sys/ioctl.h>
23 #include <sys/ttycom.h>
24 #include <sys/uio.h>
25 #include "internal.h"
26 #include "firehoseServer.h" // MiG
27 #include "firehose_reply.h" // MiG
28
29 #if __has_feature(c_static_assert)
30 _Static_assert(offsetof(struct firehose_client_s, fc_mem_sent_flushed_pos)
31 % 8 == 0, "Make sure atomic fields are properly aligned");
32 #endif
33
34 static struct firehose_server_s {
35 mach_port_t fs_bootstrap_port;
36 dispatch_mach_t fs_mach_channel;
37 dispatch_queue_t fs_ipc_queue;
38 dispatch_queue_t fs_snapshot_gate_queue;
39 dispatch_queue_t fs_io_drain_queue;
40 dispatch_queue_t fs_mem_drain_queue;
41 firehose_handler_t fs_handler;
42
43 firehose_snapshot_t fs_snapshot;
44
45 int fs_kernel_fd;
46 firehose_client_t fs_kernel_client;
47
48 TAILQ_HEAD(, firehose_client_s) fs_clients;
49 } server_config = {
50 .fs_clients = TAILQ_HEAD_INITIALIZER(server_config.fs_clients),
51 .fs_kernel_fd = -1,
52 };
53
54 #pragma mark -
55 #pragma mark firehose client state machine
56
57 static void firehose_server_demux(firehose_client_t fc,
58 mach_msg_header_t *msg_hdr);
59 static void firehose_client_cancel(firehose_client_t fc);
60 static void firehose_client_snapshot_finish(firehose_client_t fc,
61 firehose_snapshot_t snapshot, bool for_io);
62
63 static void
64 firehose_client_notify(firehose_client_t fc, mach_port_t reply_port)
65 {
66 firehose_push_reply_t push_reply = {
67 .fpr_mem_flushed_pos = os_atomic_load2o(fc, fc_mem_flushed_pos,relaxed),
68 .fpr_io_flushed_pos = os_atomic_load2o(fc, fc_io_flushed_pos, relaxed),
69 };
70 kern_return_t kr;
71
72 firehose_atomic_max2o(fc, fc_mem_sent_flushed_pos,
73 push_reply.fpr_mem_flushed_pos, relaxed);
74 firehose_atomic_max2o(fc, fc_io_sent_flushed_pos,
75 push_reply.fpr_io_flushed_pos, relaxed);
76
77 if (fc->fc_is_kernel) {
78 if (ioctl(server_config.fs_kernel_fd, LOGFLUSHED, &push_reply) < 0) {
79 dispatch_assume_zero(errno);
80 }
81 } else {
82 if (reply_port == fc->fc_sendp) {
83 kr = firehose_send_push_notify_async(reply_port, push_reply, 0);
84 } else {
85 kr = firehose_send_push_reply(reply_port, KERN_SUCCESS, push_reply);
86 }
87 if (kr != MACH_SEND_INVALID_DEST) {
88 DISPATCH_VERIFY_MIG(kr);
89 dispatch_assume_zero(kr);
90 }
91 }
92 }
93
94 OS_ALWAYS_INLINE
95 static inline uint16_t
96 firehose_client_acquire_head(firehose_buffer_t fb, bool for_io)
97 {
98 uint16_t head;
99 if (for_io) {
100 head = os_atomic_load2o(&fb->fb_header, fbh_ring_io_head, acquire);
101 } else {
102 head = os_atomic_load2o(&fb->fb_header, fbh_ring_mem_head, acquire);
103 }
104 return head;
105 }
106
107 OS_ALWAYS_INLINE
108 static inline void
109 firehose_client_push_async_merge(firehose_client_t fc, pthread_priority_t pp,
110 bool for_io)
111 {
112 if (for_io) {
113 _dispatch_source_merge_data(fc->fc_io_source, pp, 1);
114 } else {
115 _dispatch_source_merge_data(fc->fc_mem_source, pp, 1);
116 }
117 }
118
119 OS_NOINLINE OS_COLD
120 static void
121 firehose_client_mark_corrupted(firehose_client_t fc, mach_port_t reply_port)
122 {
123 // this client is really confused, do *not* answer to asyncs anymore
124 fc->fc_memory_corrupted = true;
125 fc->fc_use_notifs = false;
126
127 // XXX: do not cancel the data sources or a corrupted client could
128 // prevent snapshots from being taken if unlucky with ordering
129
130 if (reply_port) {
131 kern_return_t kr = firehose_send_push_reply(reply_port, 0,
132 FIREHOSE_PUSH_REPLY_CORRUPTED);
133 DISPATCH_VERIFY_MIG(kr);
134 dispatch_assume_zero(kr);
135 }
136 }
137
138 OS_ALWAYS_INLINE
139 static inline void
140 firehose_client_snapshot_mark_done(firehose_client_t fc,
141 firehose_snapshot_t snapshot, bool for_io)
142 {
143 if (for_io) {
144 fc->fc_needs_io_snapshot = false;
145 } else {
146 fc->fc_needs_mem_snapshot = false;
147 }
148 dispatch_group_leave(snapshot->fs_group);
149 }
150
151 #define DRAIN_BATCH_SIZE 4
152 #define FIREHOSE_DRAIN_FOR_IO 0x1
153 #define FIREHOSE_DRAIN_POLL 0x2
154
155 OS_NOINLINE
156 static void
157 firehose_client_drain(firehose_client_t fc, mach_port_t port, uint32_t flags)
158 {
159 firehose_buffer_t fb = fc->fc_buffer;
160 firehose_buffer_chunk_t fbc;
161 firehose_event_t evt;
162 uint16_t volatile *fbh_ring;
163 uint16_t flushed, ref, count = 0;
164 uint16_t client_head, client_flushed, sent_flushed;
165 firehose_snapshot_t snapshot = NULL;
166 bool for_io = (flags & FIREHOSE_DRAIN_FOR_IO);
167
168 if (for_io) {
169 evt = FIREHOSE_EVENT_IO_BUFFER_RECEIVED;
170 _Static_assert(FIREHOSE_EVENT_IO_BUFFER_RECEIVED ==
171 FIREHOSE_SNAPSHOT_EVENT_IO_BUFFER, "");
172 fbh_ring = fb->fb_header.fbh_io_ring;
173 sent_flushed = (uint16_t)fc->fc_io_sent_flushed_pos;
174 flushed = (uint16_t)fc->fc_io_flushed_pos;
175 if (fc->fc_needs_io_snapshot) {
176 snapshot = server_config.fs_snapshot;
177 }
178 } else {
179 evt = FIREHOSE_EVENT_MEM_BUFFER_RECEIVED;
180 _Static_assert(FIREHOSE_EVENT_MEM_BUFFER_RECEIVED ==
181 FIREHOSE_SNAPSHOT_EVENT_MEM_BUFFER, "");
182 fbh_ring = fb->fb_header.fbh_mem_ring;
183 sent_flushed = (uint16_t)fc->fc_mem_sent_flushed_pos;
184 flushed = (uint16_t)fc->fc_mem_flushed_pos;
185 if (fc->fc_needs_mem_snapshot) {
186 snapshot = server_config.fs_snapshot;
187 }
188 }
189
190 if (slowpath(fc->fc_memory_corrupted)) {
191 goto corrupt;
192 }
193
194 client_head = flushed;
195 do {
196 if ((uint16_t)(flushed + count) == client_head) {
197 client_head = firehose_client_acquire_head(fb, for_io);
198 if ((uint16_t)(flushed + count) == client_head) {
199 break;
200 }
201 if ((uint16_t)(client_head - sent_flushed) >=
202 FIREHOSE_BUFFER_CHUNK_COUNT) {
203 goto corrupt;
204 }
205 }
206
207 // see firehose_buffer_ring_enqueue
208 do {
209 ref = (flushed + count) & FIREHOSE_RING_POS_IDX_MASK;
210 ref = os_atomic_load(&fbh_ring[ref], relaxed);
211 ref &= FIREHOSE_RING_POS_IDX_MASK;
212 } while (fc->fc_is_kernel && !ref);
213 count++;
214 if (!ref) {
215 _dispatch_debug("Ignoring invalid page reference in ring: %d", ref);
216 continue;
217 }
218
219 fbc = firehose_buffer_ref_to_chunk(fb, ref);
220 server_config.fs_handler(fc, evt, fbc);
221 if (slowpath(snapshot)) {
222 snapshot->handler(fc, evt, fbc);
223 }
224 // clients not using notifications (single threaded) always drain fully
225 // because they use all their limit, always
226 } while (!fc->fc_use_notifs || count < DRAIN_BATCH_SIZE || snapshot);
227
228 if (count) {
229 // we don't load the full fbh_ring_tail because that is a 64bit quantity
230 // and we only need 16bits from it. and on 32bit arm, there's no way to
231 // perform an atomic load of a 64bit quantity on read-only memory.
232 if (for_io) {
233 os_atomic_add2o(fc, fc_io_flushed_pos, count, relaxed);
234 client_flushed = os_atomic_load2o(&fb->fb_header,
235 fbh_ring_tail.frp_io_flushed, relaxed);
236 } else {
237 os_atomic_add2o(fc, fc_mem_flushed_pos, count, relaxed);
238 client_flushed = os_atomic_load2o(&fb->fb_header,
239 fbh_ring_tail.frp_mem_flushed, relaxed);
240 }
241 if (fc->fc_is_kernel) {
242 // will fire firehose_client_notify() because port is MACH_PORT_DEAD
243 port = fc->fc_sendp;
244 } else if (!port && client_flushed == sent_flushed && fc->fc_use_notifs) {
245 port = fc->fc_sendp;
246 }
247 }
248
249 if (slowpath(snapshot)) {
250 firehose_client_snapshot_finish(fc, snapshot, for_io);
251 firehose_client_snapshot_mark_done(fc, snapshot, for_io);
252 }
253 if (port) {
254 firehose_client_notify(fc, port);
255 }
256 if (fc->fc_is_kernel) {
257 if (!(flags & FIREHOSE_DRAIN_POLL)) {
258 // see firehose_client_kernel_source_handle_event
259 dispatch_resume(fc->fc_kernel_source);
260 }
261 } else {
262 if (fc->fc_use_notifs && count >= DRAIN_BATCH_SIZE) {
263 // if we hit the drain batch size, the client probably logs a lot
264 // and there's more to drain, so optimistically schedule draining
265 // again this is cheap since the queue is hot, and is fair for other
266 // clients
267 firehose_client_push_async_merge(fc, 0, for_io);
268 }
269 if (count && server_config.fs_kernel_client) {
270 // the kernel is special because it can drop messages, so if we're
271 // draining, poll the kernel each time while we're bound to a thread
272 firehose_client_drain(server_config.fs_kernel_client,
273 MACH_PORT_NULL, flags | FIREHOSE_DRAIN_POLL);
274 }
275 }
276 return;
277
278 corrupt:
279 if (snapshot) {
280 firehose_client_snapshot_mark_done(fc, snapshot, for_io);
281 }
282 firehose_client_mark_corrupted(fc, port);
283 // from now on all IO/mem drains depending on `for_io` will be no-op
284 // (needs_<for_io>_snapshot: false, memory_corrupted: true). we can safely
285 // silence the corresponding source of drain wake-ups.
286 if (!fc->fc_is_kernel) {
287 dispatch_source_cancel(for_io ? fc->fc_io_source : fc->fc_mem_source);
288 }
289 }
290
291 static void
292 firehose_client_drain_io_async(void *ctx)
293 {
294 firehose_client_drain(ctx, MACH_PORT_NULL, FIREHOSE_DRAIN_FOR_IO);
295 }
296
297 static void
298 firehose_client_drain_mem_async(void *ctx)
299 {
300 firehose_client_drain(ctx, MACH_PORT_NULL, 0);
301 }
302
303 OS_NOINLINE
304 static void
305 firehose_client_finalize(firehose_client_t fc OS_OBJECT_CONSUMED)
306 {
307 firehose_snapshot_t snapshot = server_config.fs_snapshot;
308 firehose_buffer_t fb = fc->fc_buffer;
309
310 dispatch_assert_queue(server_config.fs_io_drain_queue);
311
312 // if a client dies between phase 1 and 2 of starting the snapshot
313 // (see firehose_snapshot_start)) there's no handler to call, but the
314 // dispatch group has to be adjusted for this client going away.
315 if (fc->fc_needs_io_snapshot) {
316 dispatch_group_leave(snapshot->fs_group);
317 fc->fc_needs_io_snapshot = false;
318 }
319 if (fc->fc_needs_mem_snapshot) {
320 dispatch_group_leave(snapshot->fs_group);
321 fc->fc_needs_mem_snapshot = false;
322 }
323 if (fc->fc_memory_corrupted) {
324 server_config.fs_handler(fc, FIREHOSE_EVENT_CLIENT_CORRUPTED,
325 &fb->fb_chunks[0]);
326 }
327 server_config.fs_handler(fc, FIREHOSE_EVENT_CLIENT_DIED, NULL);
328
329 TAILQ_REMOVE(&server_config.fs_clients, fc, fc_entry);
330 fc->fc_entry.tqe_next = DISPATCH_OBJECT_LISTLESS;
331 fc->fc_entry.tqe_prev = DISPATCH_OBJECT_LISTLESS;
332 _os_object_release(&fc->fc_as_os_object);
333 }
334
335 OS_NOINLINE
336 static void
337 firehose_client_handle_death(void *ctxt)
338 {
339 firehose_client_t fc = ctxt;
340 firehose_buffer_t fb = fc->fc_buffer;
341 firehose_buffer_header_t fbh = &fb->fb_header;
342 uint64_t mem_bitmap = 0, bitmap;
343
344 if (fc->fc_memory_corrupted) {
345 return firehose_client_finalize(fc);
346 }
347
348 dispatch_assert_queue(server_config.fs_io_drain_queue);
349
350 // acquire to match release barriers from threads that died
351 os_atomic_thread_fence(acquire);
352
353 bitmap = fbh->fbh_bank.fbb_bitmap & ~1ULL;
354 for (int for_io = 0; for_io < 2; for_io++) {
355 uint16_t volatile *fbh_ring;
356 uint16_t tail, flushed;
357
358 if (for_io) {
359 fbh_ring = fbh->fbh_io_ring;
360 tail = fbh->fbh_ring_tail.frp_io_tail;
361 flushed = (uint16_t)fc->fc_io_flushed_pos;
362 } else {
363 fbh_ring = fbh->fbh_mem_ring;
364 tail = fbh->fbh_ring_tail.frp_mem_tail;
365 flushed = (uint16_t)fc->fc_mem_flushed_pos;
366 }
367 if ((uint16_t)(flushed - tail) >= FIREHOSE_BUFFER_CHUNK_COUNT) {
368 fc->fc_memory_corrupted = true;
369 return firehose_client_finalize(fc);
370 }
371
372 // remove the pages that we flushed already from the bitmap
373 for (; tail != flushed; tail++) {
374 uint16_t ring_pos = tail & FIREHOSE_RING_POS_IDX_MASK;
375 uint16_t ref = fbh_ring[ring_pos] & FIREHOSE_RING_POS_IDX_MASK;
376
377 bitmap &= ~(1ULL << ref);
378 }
379 }
380
381 firehose_snapshot_t snapshot = server_config.fs_snapshot;
382
383 // Then look at all the allocated pages not seen in the ring
384 while (bitmap) {
385 uint16_t ref = firehose_bitmap_first_set(bitmap);
386 firehose_buffer_chunk_t fbc = firehose_buffer_ref_to_chunk(fb, ref);
387 uint16_t fbc_length = fbc->fbc_pos.fbc_next_entry_offs;
388
389 bitmap &= ~(1ULL << ref);
390 if (fbc->fbc_start + fbc_length <= fbc->fbc_data) {
391 // this page has its "recycle-requeue" done, but hasn't gone
392 // through "recycle-reuse", or it has no data, ditch it
393 continue;
394 }
395 if (!((firehose_tracepoint_t)fbc->fbc_data)->ft_length) {
396 // this thing has data, but the first tracepoint is unreadable
397 // so also just ditch it
398 continue;
399 }
400 if (!fbc->fbc_pos.fbc_flag_io) {
401 mem_bitmap |= 1ULL << ref;
402 continue;
403 }
404 server_config.fs_handler(fc, FIREHOSE_EVENT_IO_BUFFER_RECEIVED, fbc);
405 if (fc->fc_needs_io_snapshot && snapshot) {
406 snapshot->handler(fc, FIREHOSE_SNAPSHOT_EVENT_IO_BUFFER, fbc);
407 }
408 }
409
410 if (!mem_bitmap) {
411 return firehose_client_finalize(fc);
412 }
413
414 dispatch_async(server_config.fs_mem_drain_queue, ^{
415 uint64_t mem_bitmap_copy = mem_bitmap;
416
417 while (mem_bitmap_copy) {
418 uint16_t ref = firehose_bitmap_first_set(mem_bitmap_copy);
419 firehose_buffer_chunk_t fbc = firehose_buffer_ref_to_chunk(fb, ref);
420
421 mem_bitmap_copy &= ~(1ULL << ref);
422 server_config.fs_handler(fc, FIREHOSE_EVENT_MEM_BUFFER_RECEIVED, fbc);
423 if (fc->fc_needs_mem_snapshot && snapshot) {
424 snapshot->handler(fc, FIREHOSE_SNAPSHOT_EVENT_MEM_BUFFER, fbc);
425 }
426 }
427
428 dispatch_async_f(server_config.fs_io_drain_queue, fc,
429 (dispatch_function_t)firehose_client_finalize);
430 });
431 }
432
433 static void
434 firehose_client_handle_mach_event(void *ctx, dispatch_mach_reason_t reason,
435 dispatch_mach_msg_t dmsg, mach_error_t error OS_UNUSED)
436 {
437 mach_msg_header_t *msg_hdr;
438 firehose_client_t fc = ctx;
439 mach_port_t oldsendp, oldrecvp;
440
441 if (dmsg) {
442 msg_hdr = dispatch_mach_msg_get_msg(dmsg, NULL);
443 oldsendp = msg_hdr->msgh_remote_port;
444 oldrecvp = msg_hdr->msgh_local_port;
445 }
446
447 switch (reason) {
448 case DISPATCH_MACH_MESSAGE_RECEIVED:
449 if (msg_hdr->msgh_id == MACH_NOTIFY_NO_SENDERS) {
450 _dispatch_debug("FIREHOSE NO_SENDERS (unique_pid: 0x%llx)",
451 firehose_client_get_unique_pid(fc, NULL));
452 dispatch_mach_cancel(fc->fc_mach_channel);
453 } else {
454 firehose_server_demux(fc, msg_hdr);
455 }
456 break;
457
458 case DISPATCH_MACH_DISCONNECTED:
459 if (oldsendp) {
460 if (slowpath(oldsendp != fc->fc_sendp)) {
461 DISPATCH_INTERNAL_CRASH(oldsendp,
462 "disconnect event about unknown send-right");
463 }
464 firehose_mach_port_send_release(fc->fc_sendp);
465 fc->fc_sendp = MACH_PORT_NULL;
466 }
467 if (oldrecvp) {
468 if (slowpath(oldrecvp != fc->fc_recvp)) {
469 DISPATCH_INTERNAL_CRASH(oldrecvp,
470 "disconnect event about unknown receive-right");
471 }
472 firehose_mach_port_recv_dispose(fc->fc_recvp, fc);
473 fc->fc_recvp = MACH_PORT_NULL;
474 }
475 if (fc->fc_recvp == MACH_PORT_NULL && fc->fc_sendp == MACH_PORT_NULL) {
476 firehose_client_cancel(fc);
477 }
478 break;
479 }
480 }
481
482 #if !TARGET_OS_SIMULATOR
483 static void
484 firehose_client_kernel_source_handle_event(void *ctxt)
485 {
486 firehose_client_t fc = ctxt;
487
488 // resumed in firehose_client_drain for both memory and I/O
489 dispatch_suspend(fc->fc_kernel_source);
490 dispatch_suspend(fc->fc_kernel_source);
491 dispatch_async_f(server_config.fs_mem_drain_queue,
492 fc, firehose_client_drain_mem_async);
493 dispatch_async_f(server_config.fs_io_drain_queue,
494 fc, firehose_client_drain_io_async);
495 }
496 #endif
497
498 static inline void
499 firehose_client_resume(firehose_client_t fc,
500 const struct firehose_client_connected_info_s *fcci)
501 {
502 dispatch_assert_queue(server_config.fs_io_drain_queue);
503 TAILQ_INSERT_TAIL(&server_config.fs_clients, fc, fc_entry);
504 server_config.fs_handler(fc, FIREHOSE_EVENT_CLIENT_CONNECTED, (void *)fcci);
505 if (fc->fc_is_kernel) {
506 dispatch_activate(fc->fc_kernel_source);
507 } else {
508 dispatch_mach_connect(fc->fc_mach_channel,
509 fc->fc_recvp, fc->fc_sendp, NULL);
510 dispatch_activate(fc->fc_io_source);
511 dispatch_activate(fc->fc_mem_source);
512 }
513 }
514
515 static void
516 firehose_client_cancel(firehose_client_t fc)
517 {
518 dispatch_mach_t dm;
519 dispatch_block_t block;
520
521 _dispatch_debug("client died (unique_pid: 0x%llx",
522 firehose_client_get_unique_pid(fc, NULL));
523
524 dm = fc->fc_mach_channel;
525 fc->fc_mach_channel = NULL;
526 dispatch_release(dm);
527
528 fc->fc_use_notifs = false;
529 dispatch_source_cancel(fc->fc_io_source);
530 dispatch_source_cancel(fc->fc_mem_source);
531
532 block = dispatch_block_create(DISPATCH_BLOCK_DETACHED, ^{
533 dispatch_async_f(server_config.fs_io_drain_queue, fc,
534 firehose_client_handle_death);
535 });
536 dispatch_async(server_config.fs_mem_drain_queue, block);
537 _Block_release(block);
538 }
539
540 static firehose_client_t
541 _firehose_client_create(firehose_buffer_t fb)
542 {
543 firehose_client_t fc;
544
545 fc = (firehose_client_t)_os_object_alloc_realized(FIREHOSE_CLIENT_CLASS,
546 sizeof(struct firehose_client_s));
547 fc->fc_buffer = fb;
548 fc->fc_mem_flushed_pos = fb->fb_header.fbh_bank.fbb_mem_flushed;
549 fc->fc_mem_sent_flushed_pos = fc->fc_mem_flushed_pos;
550 fc->fc_io_flushed_pos = fb->fb_header.fbh_bank.fbb_io_flushed;
551 fc->fc_io_sent_flushed_pos = fc->fc_io_flushed_pos;
552 return fc;
553 }
554
555 static firehose_client_t
556 firehose_client_create(firehose_buffer_t fb,
557 mach_port_t comm_recvp, mach_port_t comm_sendp)
558 {
559 uint64_t unique_pid = fb->fb_header.fbh_uniquepid;
560 firehose_client_t fc = _firehose_client_create(fb);
561 dispatch_mach_t dm;
562 dispatch_source_t ds;
563
564 ds = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_OR, 0, 0,
565 server_config.fs_mem_drain_queue);
566 _os_object_retain_internal_inline(&fc->fc_as_os_object);
567 dispatch_set_context(ds, fc);
568 dispatch_set_finalizer_f(ds,
569 (dispatch_function_t)_os_object_release_internal);
570 dispatch_source_set_event_handler_f(ds, firehose_client_drain_mem_async);
571 fc->fc_mem_source = ds;
572
573 ds = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_OR, 0, 0,
574 server_config.fs_io_drain_queue);
575 _os_object_retain_internal_inline(&fc->fc_as_os_object);
576 dispatch_set_context(ds, fc);
577 dispatch_set_finalizer_f(ds,
578 (dispatch_function_t)_os_object_release_internal);
579 dispatch_source_set_event_handler_f(ds, firehose_client_drain_io_async);
580 fc->fc_io_source = ds;
581
582 _dispatch_debug("FIREHOSE_REGISTER (unique_pid: 0x%llx)", unique_pid);
583 fc->fc_recvp = comm_recvp;
584 fc->fc_sendp = comm_sendp;
585 firehose_mach_port_guard(comm_recvp, true, fc);
586 dm = dispatch_mach_create_f("com.apple.firehose.peer",
587 server_config.fs_ipc_queue,
588 fc, firehose_client_handle_mach_event);
589 fc->fc_mach_channel = dm;
590 return fc;
591 }
592
593 static void
594 firehose_kernel_client_create(void)
595 {
596 #if !TARGET_OS_SIMULATOR
597 struct firehose_server_s *fs = &server_config;
598 firehose_buffer_map_info_t fb_map;
599 firehose_client_t fc;
600 dispatch_source_t ds;
601 int fd;
602
603 while ((fd = open("/dev/oslog", O_RDWR)) < 0) {
604 if (errno == EINTR) {
605 continue;
606 }
607 if (errno == ENOENT) {
608 return;
609 }
610 DISPATCH_INTERNAL_CRASH(errno, "Unable to open /dev/oslog");
611 }
612
613 while (ioctl(fd, LOGBUFFERMAP, &fb_map) < 0) {
614 if (errno == EINTR) {
615 continue;
616 }
617 DISPATCH_INTERNAL_CRASH(errno, "Unable to map kernel buffer");
618 }
619 if (fb_map.fbmi_size !=
620 FIREHOSE_BUFFER_KERNEL_CHUNK_COUNT * FIREHOSE_BUFFER_CHUNK_SIZE) {
621 DISPATCH_INTERNAL_CRASH(fb_map.fbmi_size, "Unexpected kernel buffer size");
622 }
623
624 fc = _firehose_client_create((firehose_buffer_t)(uintptr_t)fb_map.fbmi_addr);
625 fc->fc_is_kernel = true;
626 ds = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, (uintptr_t)fd, 0,
627 fs->fs_ipc_queue);
628 dispatch_set_context(ds, fc);
629 dispatch_source_set_event_handler_f(ds,
630 firehose_client_kernel_source_handle_event);
631 fc->fc_kernel_source = ds;
632 fc->fc_use_notifs = true;
633 fc->fc_sendp = MACH_PORT_DEAD; // causes drain() to call notify
634
635 fs->fs_kernel_fd = fd;
636 fs->fs_kernel_client = fc;
637 #endif
638 }
639
640 void
641 _firehose_client_dispose(firehose_client_t fc)
642 {
643 vm_deallocate(mach_task_self(), (vm_address_t)fc->fc_buffer,
644 sizeof(*fc->fc_buffer));
645 fc->fc_buffer = NULL;
646 server_config.fs_handler(fc, FIREHOSE_EVENT_CLIENT_FINALIZE, NULL);
647 }
648
649 void
650 _firehose_client_xref_dispose(firehose_client_t fc)
651 {
652 _dispatch_debug("Cleaning up client info for unique_pid 0x%llx",
653 firehose_client_get_unique_pid(fc, NULL));
654
655 dispatch_release(fc->fc_io_source);
656 fc->fc_io_source = NULL;
657
658 dispatch_release(fc->fc_mem_source);
659 fc->fc_mem_source = NULL;
660 }
661
662 uint64_t
663 firehose_client_get_unique_pid(firehose_client_t fc, pid_t *pid_out)
664 {
665 firehose_buffer_header_t fbh = &fc->fc_buffer->fb_header;
666 if (fc->fc_is_kernel) {
667 if (pid_out) *pid_out = 0;
668 return 0;
669 }
670 if (pid_out) *pid_out = fbh->fbh_pid ?: ~(pid_t)0;
671 return fbh->fbh_uniquepid ?: ~0ull;
672 }
673
674 void *
675 firehose_client_get_metadata_buffer(firehose_client_t client, size_t *size)
676 {
677 firehose_buffer_header_t fbh = &client->fc_buffer->fb_header;
678
679 *size = FIREHOSE_BUFFER_LIBTRACE_HEADER_SIZE;
680 return (void *)((uintptr_t)(fbh + 1) - *size);
681 }
682
683 void *
684 firehose_client_get_context(firehose_client_t fc)
685 {
686 return os_atomic_load2o(fc, fc_ctxt, relaxed);
687 }
688
689 void *
690 firehose_client_set_context(firehose_client_t fc, void *ctxt)
691 {
692 return os_atomic_xchg2o(fc, fc_ctxt, ctxt, relaxed);
693 }
694
695 #pragma mark -
696 #pragma mark firehose server
697
698 /*
699 * The current_message context stores the client info for the current message
700 * being handled. The only reason this works is because currently the message
701 * processing is serial. If that changes, this would not work.
702 */
703 static firehose_client_t cur_client_info;
704
705 static void
706 firehose_server_handle_mach_event(void *ctx OS_UNUSED,
707 dispatch_mach_reason_t reason, dispatch_mach_msg_t dmsg,
708 mach_error_t error OS_UNUSED)
709 {
710 mach_msg_header_t *msg_hdr = NULL;
711
712 if (reason == DISPATCH_MACH_MESSAGE_RECEIVED) {
713 msg_hdr = dispatch_mach_msg_get_msg(dmsg, NULL);
714 /* TODO: Assert this should be a register message */
715 firehose_server_demux(NULL, msg_hdr);
716 }
717 }
718
719 void
720 firehose_server_init(mach_port_t comm_port, firehose_handler_t handler)
721 {
722 struct firehose_server_s *fs = &server_config;
723 dispatch_queue_attr_t attr;
724 dispatch_mach_t dm;
725
726 // just reference the string so that it's captured
727 (void)os_atomic_load(&__libfirehose_serverVersionString[0], relaxed);
728
729 attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL,
730 QOS_CLASS_USER_INITIATED, 0);
731 fs->fs_ipc_queue = dispatch_queue_create_with_target(
732 "com.apple.firehose.ipc", attr, NULL);
733 fs->fs_snapshot_gate_queue = dispatch_queue_create_with_target(
734 "com.apple.firehose.snapshot-gate", DISPATCH_QUEUE_SERIAL, NULL);
735 fs->fs_io_drain_queue = dispatch_queue_create_with_target(
736 "com.apple.firehose.drain-io", DISPATCH_QUEUE_SERIAL, NULL);
737 fs->fs_mem_drain_queue = dispatch_queue_create_with_target(
738 "com.apple.firehose.drain-mem", DISPATCH_QUEUE_SERIAL, NULL);
739
740 dm = dispatch_mach_create_f("com.apple.firehose.listener",
741 fs->fs_ipc_queue, NULL, firehose_server_handle_mach_event);
742 fs->fs_bootstrap_port = comm_port;
743 fs->fs_mach_channel = dm;
744 fs->fs_handler = _Block_copy(handler);
745 firehose_kernel_client_create();
746 }
747
748 void
749 firehose_server_assert_spi_version(uint32_t spi_version)
750 {
751 if (spi_version != OS_FIREHOSE_SPI_VERSION) {
752 DISPATCH_CLIENT_CRASH(spi_version, "firehose server version mismatch ("
753 OS_STRINGIFY(OS_FIREHOSE_SPI_VERSION) ")");
754 }
755 if (_firehose_spi_version != OS_FIREHOSE_SPI_VERSION) {
756 DISPATCH_CLIENT_CRASH(_firehose_spi_version,
757 "firehose libdispatch version mismatch ("
758 OS_STRINGIFY(OS_FIREHOSE_SPI_VERSION) ")");
759
760 }
761 }
762
763 void
764 firehose_server_resume(void)
765 {
766 struct firehose_server_s *fs = &server_config;
767
768 if (fs->fs_kernel_client) {
769 dispatch_async(fs->fs_io_drain_queue, ^{
770 struct firehose_client_connected_info_s fcci = {
771 .fcci_version = FIREHOSE_CLIENT_CONNECTED_INFO_VERSION,
772 };
773 firehose_client_resume(fs->fs_kernel_client, &fcci);
774 });
775 }
776 dispatch_mach_connect(fs->fs_mach_channel, fs->fs_bootstrap_port,
777 MACH_PORT_NULL, NULL);
778 }
779
780 #pragma mark -
781 #pragma mark firehose snapshot and peeking
782
783 void
784 firehose_client_metadata_stream_peek(firehose_client_t fc,
785 firehose_event_t context, bool (^peek_should_start)(void),
786 bool (^peek)(firehose_buffer_chunk_t fbc))
787 {
788 if (context != FIREHOSE_EVENT_MEM_BUFFER_RECEIVED) {
789 return dispatch_sync(server_config.fs_mem_drain_queue, ^{
790 firehose_client_metadata_stream_peek(fc,
791 FIREHOSE_EVENT_MEM_BUFFER_RECEIVED, peek_should_start, peek);
792 });
793 }
794
795 if (peek_should_start && !peek_should_start()) {
796 return;
797 }
798
799 firehose_buffer_t fb = fc->fc_buffer;
800 firehose_buffer_header_t fbh = &fb->fb_header;
801 uint64_t bitmap = fbh->fbh_bank.fbb_metadata_bitmap;
802
803 while (bitmap) {
804 uint16_t ref = firehose_bitmap_first_set(bitmap);
805 firehose_buffer_chunk_t fbc = firehose_buffer_ref_to_chunk(fb, ref);
806 uint16_t fbc_length = fbc->fbc_pos.fbc_next_entry_offs;
807
808 bitmap &= ~(1ULL << ref);
809 if (fbc->fbc_start + fbc_length <= fbc->fbc_data) {
810 // this page has its "recycle-requeue" done, but hasn't gone
811 // through "recycle-reuse", or it has no data, ditch it
812 continue;
813 }
814 if (!((firehose_tracepoint_t)fbc->fbc_data)->ft_length) {
815 // this thing has data, but the first tracepoint is unreadable
816 // so also just ditch it
817 continue;
818 }
819 if (fbc->fbc_pos.fbc_stream != firehose_stream_metadata) {
820 continue;
821 }
822 if (!peek(fbc)) {
823 break;
824 }
825 }
826 }
827
828 OS_NOINLINE OS_COLD
829 static void
830 firehose_client_snapshot_finish(firehose_client_t fc,
831 firehose_snapshot_t snapshot, bool for_io)
832 {
833 firehose_buffer_t fb = fc->fc_buffer;
834 firehose_buffer_header_t fbh = &fb->fb_header;
835 firehose_snapshot_event_t evt;
836 uint16_t volatile *fbh_ring;
837 uint16_t tail, flushed;
838 uint64_t bitmap;
839
840 bitmap = ~1ULL;
841
842 if (for_io) {
843 fbh_ring = fbh->fbh_io_ring;
844 tail = fbh->fbh_ring_tail.frp_io_tail;
845 flushed = (uint16_t)fc->fc_io_flushed_pos;
846 evt = FIREHOSE_SNAPSHOT_EVENT_IO_BUFFER;
847 } else {
848 fbh_ring = fbh->fbh_mem_ring;
849 tail = fbh->fbh_ring_tail.frp_mem_tail;
850 flushed = (uint16_t)fc->fc_mem_flushed_pos;
851 evt = FIREHOSE_SNAPSHOT_EVENT_MEM_BUFFER;
852 }
853 if ((uint16_t)(flushed - tail) >= FIREHOSE_BUFFER_CHUNK_COUNT) {
854 fc->fc_memory_corrupted = true;
855 return;
856 }
857
858 // remove the pages that we flushed already from the bitmap
859 for (; tail != flushed; tail++) {
860 uint16_t idx = tail & FIREHOSE_RING_POS_IDX_MASK;
861 uint16_t ref = fbh_ring[idx] & FIREHOSE_RING_POS_IDX_MASK;
862
863 bitmap &= ~(1ULL << ref);
864 }
865
866 // Remove pages that are free by AND-ing with the allocating bitmap.
867 // The load of fbb_bitmap may not be atomic, but it's ok because bits
868 // being flipped are pages we don't care about snapshotting. The worst thing
869 // that can happen is that we go peek at an unmapped page and we fault it in
870 bitmap &= fbh->fbh_bank.fbb_bitmap;
871
872 // Then look at all the allocated pages not seen in the ring
873 while (bitmap) {
874 uint16_t ref = firehose_bitmap_first_set(bitmap);
875 firehose_buffer_chunk_t fbc = firehose_buffer_ref_to_chunk(fb, ref);
876 uint16_t fbc_length = fbc->fbc_pos.fbc_next_entry_offs;
877
878 bitmap &= ~(1ULL << ref);
879 if (fbc->fbc_start + fbc_length <= fbc->fbc_data) {
880 // this page has its "recycle-requeue" done, but hasn't gone
881 // through "recycle-reuse", or it has no data, ditch it
882 continue;
883 }
884 if (!((firehose_tracepoint_t)fbc->fbc_data)->ft_length) {
885 // this thing has data, but the first tracepoint is unreadable
886 // so also just ditch it
887 continue;
888 }
889 if (fbc->fbc_pos.fbc_flag_io != for_io) {
890 continue;
891 }
892 snapshot->handler(fc, evt, fbc);
893 }
894 }
895
896 static void
897 firehose_snapshot_start(void *ctxt)
898 {
899 firehose_snapshot_t snapshot = ctxt;
900 firehose_client_t fci;
901 long n = 0;
902
903 // 0. we need to be on the IO queue so that client connection and/or death
904 // cannot happen concurrently
905 dispatch_assert_queue(server_config.fs_io_drain_queue);
906
907 // 1. mark all the clients participating in the current snapshot
908 // and enter the group for each bit set
909 TAILQ_FOREACH(fci, &server_config.fs_clients, fc_entry) {
910 if (fci->fc_is_kernel) {
911 #if TARGET_OS_SIMULATOR
912 continue;
913 #endif
914 }
915 if (slowpath(fci->fc_memory_corrupted)) {
916 continue;
917 }
918 fci->fc_needs_io_snapshot = true;
919 fci->fc_needs_mem_snapshot = true;
920 n += 2;
921 }
922 if (n) {
923 // cheating: equivalent to dispatch_group_enter() n times
924 // without the acquire barriers that we don't need
925 os_atomic_add2o(snapshot->fs_group, dg_value, n, relaxed);
926 }
927
928 dispatch_async(server_config.fs_mem_drain_queue, ^{
929 // 2. make fs_snapshot visible, this is what triggers the snapshot
930 // logic from _drain() or handle_death(). until fs_snapshot is
931 // published, the bits set above are mostly ignored
932 server_config.fs_snapshot = snapshot;
933
934 snapshot->handler(NULL, FIREHOSE_SNAPSHOT_EVENT_MEM_START, NULL);
935
936 dispatch_async(server_config.fs_io_drain_queue, ^{
937 firehose_client_t fcj;
938
939 snapshot->handler(NULL, FIREHOSE_SNAPSHOT_EVENT_IO_START, NULL);
940
941 // match group_enter from firehose_snapshot() after MEM+IO_START
942 dispatch_group_leave(snapshot->fs_group);
943
944 // 3. tickle all the clients. the list of clients may have changed
945 // since step 1, but worry not - new clients don't have
946 // fc_needs_*_snapshot set so drain is harmless; clients that
947 // were removed from the list have already left the group
948 // (see firehose_client_finalize())
949 TAILQ_FOREACH(fcj, &server_config.fs_clients, fc_entry) {
950 if (fcj->fc_is_kernel) {
951 #if !TARGET_OS_SIMULATOR
952 firehose_client_kernel_source_handle_event(fcj);
953 #endif
954 } else {
955 dispatch_source_merge_data(fcj->fc_io_source, 1);
956 dispatch_source_merge_data(fcj->fc_mem_source, 1);
957 }
958 }
959 });
960 });
961 }
962
963 static void
964 firehose_snapshot_finish(void *ctxt)
965 {
966 firehose_snapshot_t fs = ctxt;
967
968 fs->handler(NULL, FIREHOSE_SNAPSHOT_EVENT_COMPLETE, NULL);
969 server_config.fs_snapshot = NULL;
970
971 dispatch_release(fs->fs_group);
972 Block_release(fs->handler);
973 free(fs);
974
975 // resume the snapshot gate queue to maybe handle the next snapshot
976 dispatch_resume(server_config.fs_snapshot_gate_queue);
977 }
978
979 static void
980 firehose_snapshot_gate(void *ctxt)
981 {
982 // prevent other snapshots from running until done
983 dispatch_suspend(server_config.fs_snapshot_gate_queue);
984 dispatch_async_f(server_config.fs_io_drain_queue, ctxt,
985 firehose_snapshot_start);
986 }
987
988 void
989 firehose_snapshot(firehose_snapshot_handler_t handler)
990 {
991 firehose_snapshot_t snapshot = malloc(sizeof(struct firehose_snapshot_s));
992
993 snapshot->handler = Block_copy(handler);
994 snapshot->fs_group = dispatch_group_create();
995
996 // keep the group entered until IO_START and MEM_START have been sent
997 // See firehose_snapshot_start()
998 dispatch_group_enter(snapshot->fs_group);
999 dispatch_group_notify_f(snapshot->fs_group, server_config.fs_io_drain_queue,
1000 snapshot, firehose_snapshot_finish);
1001
1002 dispatch_async_f(server_config.fs_snapshot_gate_queue, snapshot,
1003 firehose_snapshot_gate);
1004 }
1005
1006 #pragma mark -
1007 #pragma mark MiG handler routines
1008
1009 kern_return_t
1010 firehose_server_register(mach_port_t server_port OS_UNUSED,
1011 mach_port_t mem_port, mach_vm_size_t mem_size,
1012 mach_port_t comm_recvp, mach_port_t comm_sendp,
1013 mach_port_t extra_info_port, mach_vm_size_t extra_info_size)
1014 {
1015 mach_vm_address_t base_addr = 0;
1016 firehose_client_t fc = NULL;
1017 kern_return_t kr;
1018 struct firehose_client_connected_info_s fcci = {
1019 .fcci_version = FIREHOSE_CLIENT_CONNECTED_INFO_VERSION,
1020 };
1021
1022 if (mem_size != sizeof(union firehose_buffer_u)) {
1023 return KERN_INVALID_VALUE;
1024 }
1025
1026 /*
1027 * Request a MACH_NOTIFY_NO_SENDERS notification for recvp. That should
1028 * indicate the client going away.
1029 */
1030 mach_port_t previous = MACH_PORT_NULL;
1031 kr = mach_port_request_notification(mach_task_self(), comm_recvp,
1032 MACH_NOTIFY_NO_SENDERS, 0, comm_recvp,
1033 MACH_MSG_TYPE_MAKE_SEND_ONCE, &previous);
1034 DISPATCH_VERIFY_MIG(kr);
1035 if (dispatch_assume_zero(kr)) {
1036 return KERN_FAILURE;
1037 }
1038 dispatch_assert(previous == MACH_PORT_NULL);
1039
1040 /* Map the memory handle into the server address space */
1041 kr = mach_vm_map(mach_task_self(), &base_addr, mem_size, 0,
1042 VM_FLAGS_ANYWHERE, mem_port, 0, FALSE,
1043 VM_PROT_READ, VM_PROT_READ, VM_INHERIT_NONE);
1044 DISPATCH_VERIFY_MIG(kr);
1045 if (dispatch_assume_zero(kr)) {
1046 return KERN_NO_SPACE;
1047 }
1048
1049 if (extra_info_port && extra_info_size) {
1050 mach_vm_address_t addr = 0;
1051 kr = mach_vm_map(mach_task_self(), &addr, extra_info_size, 0,
1052 VM_FLAGS_ANYWHERE, extra_info_port, 0, FALSE,
1053 VM_PROT_READ, VM_PROT_READ, VM_INHERIT_NONE);
1054 if (dispatch_assume_zero(kr)) {
1055 mach_vm_deallocate(mach_task_self(), base_addr, mem_size);
1056 return KERN_NO_SPACE;
1057 }
1058 fcci.fcci_data = (void *)(uintptr_t)addr;
1059 fcci.fcci_size = (size_t)extra_info_size;
1060 }
1061
1062 fc = firehose_client_create((firehose_buffer_t)base_addr,
1063 comm_recvp, comm_sendp);
1064 dispatch_async(server_config.fs_io_drain_queue, ^{
1065 firehose_client_resume(fc, &fcci);
1066 if (fcci.fcci_size) {
1067 vm_deallocate(mach_task_self(), (vm_address_t)fcci.fcci_data,
1068 fcci.fcci_size);
1069 }
1070 });
1071
1072 if (extra_info_port) firehose_mach_port_send_release(extra_info_port);
1073 firehose_mach_port_send_release(mem_port);
1074 return KERN_SUCCESS;
1075 }
1076
1077 kern_return_t
1078 firehose_server_push_async(mach_port_t server_port OS_UNUSED,
1079 qos_class_t qos, boolean_t for_io, boolean_t expects_notifs)
1080 {
1081 firehose_client_t fc = cur_client_info;
1082 pthread_priority_t pp = _pthread_qos_class_encode(qos, 0,
1083 _PTHREAD_PRIORITY_ENFORCE_FLAG);
1084
1085 _dispatch_debug("FIREHOSE_PUSH_ASYNC (unique_pid %llx)",
1086 firehose_client_get_unique_pid(fc, NULL));
1087 if (!slowpath(fc->fc_memory_corrupted)) {
1088 if (expects_notifs && !fc->fc_use_notifs) {
1089 fc->fc_use_notifs = true;
1090 }
1091 firehose_client_push_async_merge(fc, pp, for_io);
1092 }
1093 return KERN_SUCCESS;
1094 }
1095
1096 kern_return_t
1097 firehose_server_push(mach_port_t server_port OS_UNUSED,
1098 mach_port_t reply_port, qos_class_t qos, boolean_t for_io,
1099 firehose_push_reply_t *push_reply OS_UNUSED)
1100 {
1101 firehose_client_t fc = cur_client_info;
1102 dispatch_block_flags_t flags = DISPATCH_BLOCK_ENFORCE_QOS_CLASS;
1103 dispatch_block_t block;
1104 dispatch_queue_t q;
1105
1106 _dispatch_debug("FIREHOSE_PUSH (unique_pid %llx)",
1107 firehose_client_get_unique_pid(fc, NULL));
1108
1109 if (slowpath(fc->fc_memory_corrupted)) {
1110 firehose_client_mark_corrupted(fc, reply_port);
1111 return MIG_NO_REPLY;
1112 }
1113
1114 if (for_io) {
1115 q = server_config.fs_io_drain_queue;
1116 } else {
1117 q = server_config.fs_mem_drain_queue;
1118 }
1119
1120 block = dispatch_block_create_with_qos_class(flags, qos, 0, ^{
1121 firehose_client_drain(fc, reply_port,
1122 for_io ? FIREHOSE_DRAIN_FOR_IO : 0);
1123 });
1124 dispatch_async(q, block);
1125 _Block_release(block);
1126 return MIG_NO_REPLY;
1127 }
1128
1129 static void
1130 firehose_server_demux(firehose_client_t fc, mach_msg_header_t *msg_hdr)
1131 {
1132 const size_t reply_size =
1133 sizeof(union __ReplyUnion__firehose_server_firehose_subsystem);
1134
1135 cur_client_info = fc;
1136 firehose_mig_server(firehose_server, reply_size, msg_hdr);
1137 }