2 * Copyright (c) 2015 Apple Inc. All rights reserved.
4 * @APPLE_APACHE_LICENSE_HEADER_START@
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
10 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 * @APPLE_APACHE_LICENSE_HEADER_END@
21 #include <servers/bootstrap.h>
22 #include <sys/ioctl.h>
23 #include <sys/ttycom.h>
26 #include "firehoseServer.h" // MiG
27 #include "firehose_reply.h" // MiG
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");
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
;
43 firehose_snapshot_t fs_snapshot
;
46 firehose_client_t fs_kernel_client
;
48 TAILQ_HEAD(, firehose_client_s
) fs_clients
;
50 .fs_clients
= TAILQ_HEAD_INITIALIZER(server_config
.fs_clients
),
55 #pragma mark firehose client state machine
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
);
64 firehose_client_notify(firehose_client_t fc
, mach_port_t reply_port
)
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
),
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
);
77 if (fc
->fc_is_kernel
) {
78 if (ioctl(server_config
.fs_kernel_fd
, LOGFLUSHED
, &push_reply
) < 0) {
79 dispatch_assume_zero(errno
);
82 if (reply_port
== fc
->fc_sendp
) {
83 kr
= firehose_send_push_notify_async(reply_port
, push_reply
, 0);
85 kr
= firehose_send_push_reply(reply_port
, KERN_SUCCESS
, push_reply
);
87 if (kr
!= MACH_SEND_INVALID_DEST
) {
88 DISPATCH_VERIFY_MIG(kr
);
89 dispatch_assume_zero(kr
);
95 static inline uint16_t
96 firehose_client_acquire_head(firehose_buffer_t fb
, bool for_io
)
100 head
= os_atomic_load2o(&fb
->fb_header
, fbh_ring_io_head
, acquire
);
102 head
= os_atomic_load2o(&fb
->fb_header
, fbh_ring_mem_head
, acquire
);
109 firehose_client_push_async_merge(firehose_client_t fc
, pthread_priority_t pp
,
113 _dispatch_source_merge_data(fc
->fc_io_source
, pp
, 1);
115 _dispatch_source_merge_data(fc
->fc_mem_source
, pp
, 1);
121 firehose_client_mark_corrupted(firehose_client_t fc
, mach_port_t reply_port
)
123 // this client is really confused, do *not* answer to asyncs anymore
124 fc
->fc_memory_corrupted
= true;
125 fc
->fc_use_notifs
= false;
127 // XXX: do not cancel the data sources or a corrupted client could
128 // prevent snapshots from being taken if unlucky with ordering
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
);
140 firehose_client_snapshot_mark_done(firehose_client_t fc
,
141 firehose_snapshot_t snapshot
, bool for_io
)
144 fc
->fc_needs_io_snapshot
= false;
146 fc
->fc_needs_mem_snapshot
= false;
148 dispatch_group_leave(snapshot
->fs_group
);
151 #define DRAIN_BATCH_SIZE 4
152 #define FIREHOSE_DRAIN_FOR_IO 0x1
153 #define FIREHOSE_DRAIN_POLL 0x2
157 firehose_client_drain(firehose_client_t fc
, mach_port_t port
, uint32_t flags
)
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
);
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
;
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
;
190 if (slowpath(fc
->fc_memory_corrupted
)) {
194 client_head
= flushed
;
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
) {
201 if ((uint16_t)(client_head
- sent_flushed
) >=
202 FIREHOSE_BUFFER_CHUNK_COUNT
) {
207 // see firehose_buffer_ring_enqueue
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
);
215 _dispatch_debug("Ignoring invalid page reference in ring: %d", ref
);
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
);
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
);
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.
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
);
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
);
241 if (fc
->fc_is_kernel
) {
242 // will fire firehose_client_notify() because port is MACH_PORT_DEAD
244 } else if (!port
&& client_flushed
== sent_flushed
&& fc
->fc_use_notifs
) {
249 if (slowpath(snapshot
)) {
250 firehose_client_snapshot_finish(fc
, snapshot
, for_io
);
251 firehose_client_snapshot_mark_done(fc
, snapshot
, for_io
);
254 firehose_client_notify(fc
, port
);
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
);
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
267 firehose_client_push_async_merge(fc
, 0, for_io
);
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
);
280 firehose_client_snapshot_mark_done(fc
, snapshot
, for_io
);
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
);
292 firehose_client_drain_io_async(void *ctx
)
294 firehose_client_drain(ctx
, MACH_PORT_NULL
, FIREHOSE_DRAIN_FOR_IO
);
298 firehose_client_drain_mem_async(void *ctx
)
300 firehose_client_drain(ctx
, MACH_PORT_NULL
, 0);
305 firehose_client_finalize(firehose_client_t fc OS_OBJECT_CONSUMED
)
307 firehose_snapshot_t snapshot
= server_config
.fs_snapshot
;
308 firehose_buffer_t fb
= fc
->fc_buffer
;
310 dispatch_assert_queue(server_config
.fs_io_drain_queue
);
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;
319 if (fc
->fc_needs_mem_snapshot
) {
320 dispatch_group_leave(snapshot
->fs_group
);
321 fc
->fc_needs_mem_snapshot
= false;
323 if (fc
->fc_memory_corrupted
) {
324 server_config
.fs_handler(fc
, FIREHOSE_EVENT_CLIENT_CORRUPTED
,
327 server_config
.fs_handler(fc
, FIREHOSE_EVENT_CLIENT_DIED
, NULL
);
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
);
337 firehose_client_handle_death(void *ctxt
)
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
;
344 if (fc
->fc_memory_corrupted
) {
345 return firehose_client_finalize(fc
);
348 dispatch_assert_queue(server_config
.fs_io_drain_queue
);
350 // acquire to match release barriers from threads that died
351 os_atomic_thread_fence(acquire
);
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
;
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
;
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
;
367 if ((uint16_t)(flushed
- tail
) >= FIREHOSE_BUFFER_CHUNK_COUNT
) {
368 fc
->fc_memory_corrupted
= true;
369 return firehose_client_finalize(fc
);
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
;
377 bitmap
&= ~(1ULL << ref
);
381 firehose_snapshot_t snapshot
= server_config
.fs_snapshot
;
383 // Then look at all the allocated pages not seen in the ring
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
;
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
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
400 if (!fbc
->fbc_pos
.fbc_flag_io
) {
401 mem_bitmap
|= 1ULL << ref
;
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
);
411 return firehose_client_finalize(fc
);
414 dispatch_async(server_config
.fs_mem_drain_queue
, ^{
415 uint64_t mem_bitmap_copy
= mem_bitmap
;
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
);
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
);
428 dispatch_async_f(server_config
.fs_io_drain_queue
, fc
,
429 (dispatch_function_t
)firehose_client_finalize
);
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
)
437 mach_msg_header_t
*msg_hdr
;
438 firehose_client_t fc
= ctx
;
439 mach_port_t oldsendp
, oldrecvp
;
442 msg_hdr
= dispatch_mach_msg_get_msg(dmsg
, NULL
);
443 oldsendp
= msg_hdr
->msgh_remote_port
;
444 oldrecvp
= msg_hdr
->msgh_local_port
;
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
);
454 firehose_server_demux(fc
, msg_hdr
);
458 case DISPATCH_MACH_DISCONNECTED
:
460 if (slowpath(oldsendp
!= fc
->fc_sendp
)) {
461 DISPATCH_INTERNAL_CRASH(oldsendp
,
462 "disconnect event about unknown send-right");
464 firehose_mach_port_send_release(fc
->fc_sendp
);
465 fc
->fc_sendp
= MACH_PORT_NULL
;
468 if (slowpath(oldrecvp
!= fc
->fc_recvp
)) {
469 DISPATCH_INTERNAL_CRASH(oldrecvp
,
470 "disconnect event about unknown receive-right");
472 firehose_mach_port_recv_dispose(fc
->fc_recvp
, fc
);
473 fc
->fc_recvp
= MACH_PORT_NULL
;
475 if (fc
->fc_recvp
== MACH_PORT_NULL
&& fc
->fc_sendp
== MACH_PORT_NULL
) {
476 firehose_client_cancel(fc
);
482 #if !TARGET_OS_SIMULATOR
484 firehose_client_kernel_source_handle_event(void *ctxt
)
486 firehose_client_t fc
= ctxt
;
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
);
499 firehose_client_resume(firehose_client_t fc
,
500 const struct firehose_client_connected_info_s
*fcci
)
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
);
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
);
516 firehose_client_cancel(firehose_client_t fc
)
519 dispatch_block_t block
;
521 _dispatch_debug("client died (unique_pid: 0x%llx",
522 firehose_client_get_unique_pid(fc
, NULL
));
524 dm
= fc
->fc_mach_channel
;
525 fc
->fc_mach_channel
= NULL
;
526 dispatch_release(dm
);
528 fc
->fc_use_notifs
= false;
529 dispatch_source_cancel(fc
->fc_io_source
);
530 dispatch_source_cancel(fc
->fc_mem_source
);
532 block
= dispatch_block_create(DISPATCH_BLOCK_DETACHED
, ^{
533 dispatch_async_f(server_config
.fs_io_drain_queue
, fc
,
534 firehose_client_handle_death
);
536 dispatch_async(server_config
.fs_mem_drain_queue
, block
);
537 _Block_release(block
);
540 static firehose_client_t
541 _firehose_client_create(firehose_buffer_t fb
)
543 firehose_client_t fc
;
545 fc
= (firehose_client_t
)_os_object_alloc_realized(FIREHOSE_CLIENT_CLASS
,
546 sizeof(struct firehose_client_s
));
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
;
555 static firehose_client_t
556 firehose_client_create(firehose_buffer_t fb
,
557 mach_port_t comm_recvp
, mach_port_t comm_sendp
)
559 uint64_t unique_pid
= fb
->fb_header
.fbh_uniquepid
;
560 firehose_client_t fc
= _firehose_client_create(fb
);
562 dispatch_source_t ds
;
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
;
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
;
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
;
594 firehose_kernel_client_create(void)
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
;
603 while ((fd
= open("/dev/oslog", O_RDWR
)) < 0) {
604 if (errno
== EINTR
) {
607 if (errno
== ENOENT
) {
610 DISPATCH_INTERNAL_CRASH(errno
, "Unable to open /dev/oslog");
613 while (ioctl(fd
, LOGBUFFERMAP
, &fb_map
) < 0) {
614 if (errno
== EINTR
) {
617 DISPATCH_INTERNAL_CRASH(errno
, "Unable to map kernel buffer");
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");
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,
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
635 fs
->fs_kernel_fd
= fd
;
636 fs
->fs_kernel_client
= fc
;
641 _firehose_client_dispose(firehose_client_t fc
)
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
);
650 _firehose_client_xref_dispose(firehose_client_t fc
)
652 _dispatch_debug("Cleaning up client info for unique_pid 0x%llx",
653 firehose_client_get_unique_pid(fc
, NULL
));
655 dispatch_release(fc
->fc_io_source
);
656 fc
->fc_io_source
= NULL
;
658 dispatch_release(fc
->fc_mem_source
);
659 fc
->fc_mem_source
= NULL
;
663 firehose_client_get_unique_pid(firehose_client_t fc
, pid_t
*pid_out
)
665 firehose_buffer_header_t fbh
= &fc
->fc_buffer
->fb_header
;
666 if (fc
->fc_is_kernel
) {
667 if (pid_out
) *pid_out
= 0;
670 if (pid_out
) *pid_out
= fbh
->fbh_pid
?: ~(pid_t
)0;
671 return fbh
->fbh_uniquepid
?: ~0ull;
675 firehose_client_get_metadata_buffer(firehose_client_t client
, size_t *size
)
677 firehose_buffer_header_t fbh
= &client
->fc_buffer
->fb_header
;
679 *size
= FIREHOSE_BUFFER_LIBTRACE_HEADER_SIZE
;
680 return (void *)((uintptr_t)(fbh
+ 1) - *size
);
684 firehose_client_get_context(firehose_client_t fc
)
686 return os_atomic_load2o(fc
, fc_ctxt
, relaxed
);
690 firehose_client_set_context(firehose_client_t fc
, void *ctxt
)
692 return os_atomic_xchg2o(fc
, fc_ctxt
, ctxt
, relaxed
);
696 #pragma mark firehose server
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.
703 static firehose_client_t cur_client_info
;
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
)
710 mach_msg_header_t
*msg_hdr
= NULL
;
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
);
720 firehose_server_init(mach_port_t comm_port
, firehose_handler_t handler
)
722 struct firehose_server_s
*fs
= &server_config
;
723 dispatch_queue_attr_t attr
;
726 // just reference the string so that it's captured
727 (void)os_atomic_load(&__libfirehose_serverVersionString
[0], relaxed
);
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
);
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();
749 firehose_server_assert_spi_version(uint32_t spi_version
)
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
) ")");
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
) ")");
764 firehose_server_resume(void)
766 struct firehose_server_s
*fs
= &server_config
;
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
,
773 firehose_client_resume(fs
->fs_kernel_client
, &fcci
);
776 dispatch_mach_connect(fs
->fs_mach_channel
, fs
->fs_bootstrap_port
,
777 MACH_PORT_NULL
, NULL
);
781 #pragma mark firehose snapshot and peeking
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
))
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
);
795 if (peek_should_start
&& !peek_should_start()) {
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
;
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
;
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
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
819 if (fbc
->fbc_pos
.fbc_stream
!= firehose_stream_metadata
) {
830 firehose_client_snapshot_finish(firehose_client_t fc
,
831 firehose_snapshot_t snapshot
, bool for_io
)
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
;
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
;
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
;
853 if ((uint16_t)(flushed
- tail
) >= FIREHOSE_BUFFER_CHUNK_COUNT
) {
854 fc
->fc_memory_corrupted
= true;
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
;
863 bitmap
&= ~(1ULL << ref
);
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
;
872 // Then look at all the allocated pages not seen in the ring
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
;
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
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
889 if (fbc
->fbc_pos
.fbc_flag_io
!= for_io
) {
892 snapshot
->handler(fc
, evt
, fbc
);
897 firehose_snapshot_start(void *ctxt
)
899 firehose_snapshot_t snapshot
= ctxt
;
900 firehose_client_t fci
;
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
);
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
915 if (slowpath(fci
->fc_memory_corrupted
)) {
918 fci
->fc_needs_io_snapshot
= true;
919 fci
->fc_needs_mem_snapshot
= true;
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
);
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
;
934 snapshot
->handler(NULL
, FIREHOSE_SNAPSHOT_EVENT_MEM_START
, NULL
);
936 dispatch_async(server_config
.fs_io_drain_queue
, ^{
937 firehose_client_t fcj
;
939 snapshot
->handler(NULL
, FIREHOSE_SNAPSHOT_EVENT_IO_START
, NULL
);
941 // match group_enter from firehose_snapshot() after MEM+IO_START
942 dispatch_group_leave(snapshot
->fs_group
);
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
);
955 dispatch_source_merge_data(fcj
->fc_io_source
, 1);
956 dispatch_source_merge_data(fcj
->fc_mem_source
, 1);
964 firehose_snapshot_finish(void *ctxt
)
966 firehose_snapshot_t fs
= ctxt
;
968 fs
->handler(NULL
, FIREHOSE_SNAPSHOT_EVENT_COMPLETE
, NULL
);
969 server_config
.fs_snapshot
= NULL
;
971 dispatch_release(fs
->fs_group
);
972 Block_release(fs
->handler
);
975 // resume the snapshot gate queue to maybe handle the next snapshot
976 dispatch_resume(server_config
.fs_snapshot_gate_queue
);
980 firehose_snapshot_gate(void *ctxt
)
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
);
989 firehose_snapshot(firehose_snapshot_handler_t handler
)
991 firehose_snapshot_t snapshot
= malloc(sizeof(struct firehose_snapshot_s
));
993 snapshot
->handler
= Block_copy(handler
);
994 snapshot
->fs_group
= dispatch_group_create();
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
);
1002 dispatch_async_f(server_config
.fs_snapshot_gate_queue
, snapshot
,
1003 firehose_snapshot_gate
);
1007 #pragma mark MiG handler routines
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
)
1015 mach_vm_address_t base_addr
= 0;
1016 firehose_client_t fc
= NULL
;
1018 struct firehose_client_connected_info_s fcci
= {
1019 .fcci_version
= FIREHOSE_CLIENT_CONNECTED_INFO_VERSION
,
1022 if (mem_size
!= sizeof(union firehose_buffer_u
)) {
1023 return KERN_INVALID_VALUE
;
1027 * Request a MACH_NOTIFY_NO_SENDERS notification for recvp. That should
1028 * indicate the client going away.
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
;
1038 dispatch_assert(previous
== MACH_PORT_NULL
);
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
;
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
;
1058 fcci
.fcci_data
= (void *)(uintptr_t)addr
;
1059 fcci
.fcci_size
= (size_t)extra_info_size
;
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
,
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
;
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
)
1081 firehose_client_t fc
= cur_client_info
;
1082 pthread_priority_t pp
= _pthread_qos_class_encode(qos
, 0,
1083 _PTHREAD_PRIORITY_ENFORCE_FLAG
);
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;
1091 firehose_client_push_async_merge(fc
, pp
, for_io
);
1093 return KERN_SUCCESS
;
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
)
1101 firehose_client_t fc
= cur_client_info
;
1102 dispatch_block_flags_t flags
= DISPATCH_BLOCK_ENFORCE_QOS_CLASS
;
1103 dispatch_block_t block
;
1106 _dispatch_debug("FIREHOSE_PUSH (unique_pid %llx)",
1107 firehose_client_get_unique_pid(fc
, NULL
));
1109 if (slowpath(fc
->fc_memory_corrupted
)) {
1110 firehose_client_mark_corrupted(fc
, reply_port
);
1111 return MIG_NO_REPLY
;
1115 q
= server_config
.fs_io_drain_queue
;
1117 q
= server_config
.fs_mem_drain_queue
;
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);
1124 dispatch_async(q
, block
);
1125 _Block_release(block
);
1126 return MIG_NO_REPLY
;
1130 firehose_server_demux(firehose_client_t fc
, mach_msg_header_t
*msg_hdr
)
1132 const size_t reply_size
=
1133 sizeof(union __ReplyUnion__firehose_server_firehose_subsystem
);
1135 cur_client_info
= fc
;
1136 firehose_mig_server(firehose_server
, reply_size
, msg_hdr
);