This is xnu-11215.1.10. See this file in:
/*
* Copyright (c) 2023 Apple Inc. All rights reserved.
*
* @APPLE_OSREFERENCE_LICENSE_HEADER_START@
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apple Public Source License
* Version 2.0 (the 'License'). You may not use this file except in
* compliance with the License. The rights granted to you under the License
* may not be used to create, or enable the creation or redistribution of,
* unlawful or unlicensed copies of an Apple operating system, or to
* circumvent, violate, or enable the circumvention or violation of, any
* terms of an Apple operating system software license agreement.
*
* Please obtain a copy of the License at
* http://www.opensource.apple.com/apsl/ and read it before using this file.
*
* The Original Code and all software distributed under the License are
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
* Please see the License for the specific language governing rights and
* limitations under the License.
*
* @APPLE_OSREFERENCE_LICENSE_HEADER_END@
*/
#if CONFIG_EXCLAVES
#include <kern/exclaves_debug.h>
#include <kern/exclaves_inspection.h>
#include <kern/exclaves_stackshot.h>
#include <kern/exclaves_test_stackshot.h>
#include <kern/exclaves_boot.h>
#include <kern/exclaves.tightbeam.h>
#include <mach/exclaves_l4.h>
#include <vm/pmap.h>
#define EXCLAVES_STACKSHOT_BATCH_SIZE 32
#define EXCLAVES_STACKSHOT_BUFFER_SIZE (16 * PAGE_SIZE)
#include "exclaves_resource.h"
#define EXCLAVES_ID_STACKSHOT_SERVER_EP \
(exclaves_service_lookup(EXCLAVES_DOMAIN_KERNEL, \
"com.apple.service.Stackshot"))
static _Atomic bool exclaves_inspection_initialized;
static stackshot_taker_s tb_client;
static uint8_t ** exclaves_stackshot_buffer_pages;
static uint8_t exclaves_stackshot_buffer[EXCLAVES_STACKSHOT_BUFFER_SIZE];
static integer_t exclaves_collect_priority = MAXPRI_KERNEL;
static thread_t exclaves_collection_thread;
static uint64_t scid_list[EXCLAVES_STACKSHOT_BATCH_SIZE];
static ctid_t ctid_list[EXCLAVES_STACKSHOT_BATCH_SIZE];
static size_t scid_list_count;
bool exclaves_stackshot_raw_addresses;
bool exclaves_stackshot_all_address_spaces;
exclaves_resource_t * stackshot_sharedmem_resource;
exclaves_panic_ss_status_t exclaves_panic_ss_status = EXCLAVES_PANIC_STACKSHOT_UNKNOWN;
static void *exclaves_collect_event = NULL;
static uint8_t exclaves_collect_thread_ready = 0;
queue_head_t exclaves_inspection_queue_stackshot;
queue_head_t exclaves_inspection_queue_kperf;
static LCK_GRP_DECLARE(exclaves_inspection_lck_grp, "exclaves_inspection_lock");
LCK_MTX_DECLARE(exclaves_collect_mtx, &exclaves_inspection_lck_grp);
// Guards initialization to ensure nothing tries to collect before all threads/allocations/etc. are done
LCK_MTX_DECLARE(exclaves_collect_init_mtx, &exclaves_inspection_lck_grp);
static void exclaves_collect_threads_thread(void *arg, wait_result_t __unused wr);
void exclaves_inspection_check_ast(void);
extern kern_return_t
stackshot_exclaves_process_result(kern_return_t collect_kr, const stackshot_stackshotresult_s *result, bool want_raw_addresses);
extern __attribute__((noinline))
void kperf_thread_exclaves_ast_handler(thread_t thread, const stackshot_stackshotentry_s * _Nonnull entry);
typedef kern_return_t (*exclaves_inspection_process_fn)(kern_return_t collect_kr, const stackshot_stackshotresult_s *data, bool want_raw_addresses);
/* Populate provided buffer with a list of scid values of threads from end of the list. */
static size_t
prepare_scid_list_stackshot(queue_t wl, uint64_t *pscid_list, ctid_t *pctid_list, uint64_t max_threads)
{
thread_t thread = NULL;
size_t count = 0;
lck_mtx_assert(&exclaves_collect_mtx, LCK_MTX_ASSERT_OWNED);
for (count = 0; count < max_threads; ++count) {
thread = qe_dequeue_tail(wl, struct thread, th_exclaves_inspection_queue_stackshot);
if (thread == NULL) {
break;
}
pscid_list[count] = thread->th_exclaves_ipc_ctx.scid;
pctid_list[count] = thread_get_ctid(thread);
}
return count;
}
static size_t
prepare_scid_list_kperf(queue_t wl, uint64_t *pscid_list, ctid_t *pctid_list, uint64_t max_threads)
{
thread_t thread = NULL;
size_t count = 0;
lck_mtx_assert(&exclaves_collect_mtx, LCK_MTX_ASSERT_OWNED);
for (count = 0; count < max_threads; ++count) {
thread = qe_dequeue_tail(wl, struct thread, th_exclaves_inspection_queue_kperf);
if (thread == NULL) {
break;
}
pscid_list[count] = thread->th_exclaves_ipc_ctx.scid;
pctid_list[count] = thread_get_ctid(thread);
}
return count;
}
/* Clear flag from the list of pending threads, allowing them to run. */
static void
clear_pending_threads_stackshot(ctid_t *ctids, size_t count, thread_exclaves_inspection_flags_t flag)
{
size_t i;
thread_t thread;
for (i = 0; i < count; ++i) {
thread = ctid_get_thread(ctids[i]);
ctids[i] = 0;
assert(thread);
os_atomic_and(&thread->th_exclaves_inspection_state, ~flag, relaxed);
wakeup_all_with_inheritor((event_t)&thread->th_exclaves_inspection_queue_stackshot, THREAD_AWAKENED);
thread_deallocate_safe(thread);
}
}
static void
clear_pending_threads_kperf(ctid_t *ctids, size_t count, thread_exclaves_inspection_flags_t flag)
{
size_t i;
thread_t thread;
for (i = 0; i < count; ++i) {
thread = ctid_get_thread(ctids[i]);
ctids[i] = 0;
assert(thread);
os_atomic_and(&thread->th_exclaves_inspection_state, ~flag, relaxed);
wakeup_all_with_inheritor((event_t)&thread->th_exclaves_inspection_queue_kperf, THREAD_AWAKENED);
thread_deallocate_safe(thread);
}
}
static void
clear_stackshot_queue(thread_exclaves_inspection_flags_t flag)
{
thread_t thread;
lck_mtx_assert(&exclaves_collect_mtx, LCK_MTX_ASSERT_OWNED);
while (!queue_empty(&exclaves_inspection_queue_stackshot)) {
thread = qe_dequeue_tail(&exclaves_inspection_queue_stackshot, struct thread, th_exclaves_inspection_queue_stackshot);
assert(thread);
os_atomic_and(&thread->th_exclaves_inspection_state, ~flag, relaxed);
wakeup_all_with_inheritor((event_t)&thread->th_exclaves_inspection_queue_stackshot, THREAD_AWAKENED);
thread_deallocate_safe(thread);
}
}
static void
clear_kperf_queue(thread_exclaves_inspection_flags_t flag)
{
thread_t thread;
lck_mtx_assert(&exclaves_collect_mtx, LCK_MTX_ASSERT_OWNED);
while (!queue_empty(&exclaves_inspection_queue_kperf)) {
thread = qe_dequeue_tail(&exclaves_inspection_queue_kperf, struct thread, th_exclaves_inspection_queue_kperf);
assert(thread);
os_atomic_and(&thread->th_exclaves_inspection_state, ~flag, relaxed);
wakeup_all_with_inheritor((event_t)&thread->th_exclaves_inspection_queue_kperf, THREAD_AWAKENED);
thread_deallocate_safe(thread);
}
}
static kern_return_t
process_exclaves_buffer(uint8_t * buffer, size_t output_length, exclaves_inspection_process_fn process_fn, bool want_raw_addresses)
{
__block kern_return_t error = KERN_SUCCESS;
tb_error_t tberr = TB_ERROR_SUCCESS;
if (output_length) {
tberr = stackshot_stackshotresult__unmarshal(buffer, output_length, ^(stackshot_stackshotresult_s result){
error = process_fn(KERN_SUCCESS, &result, want_raw_addresses);
if (error != KERN_SUCCESS) {
exclaves_debug_printf(show_errors, "exclaves stackshot: error processing stackshot result\n");
}
});
if (tberr != TB_ERROR_SUCCESS) {
exclaves_debug_printf(show_errors, "exclaves stackshot: process_exclaves_buffer could not unmarshal stackshot data 0x%x\n", tberr);
error = KERN_FAILURE;
goto error_exit;
}
} else {
error = KERN_FAILURE;
exclaves_debug_printf(show_errors, "exclaves stackshot: exclave stackshot data did not fit into shared memory buffer\n");
}
error_exit:
return error;
}
static kern_return_t
collect_scid_list(exclaves_inspection_process_fn process_fn, bool want_raw_addresses, bool all_address_spaces)
{
__block kern_return_t kr = KERN_SUCCESS;
tb_error_t tberr = 0;
scid_v_s scids = { 0 };
exclaves_debug_printf(show_progress, "exclaves stackshot: starting collection, scid_list_count=%zu\n", scid_list_count);
scid__v_assign_unowned(&scids, scid_list, scid_list_count);
tberr = stackshot_taker_takestackshot(&tb_client, &scids, want_raw_addresses, all_address_spaces, ^(stackshot_outputlength_s output_length) {
assert3u(output_length, <=, EXCLAVES_STACKSHOT_BUFFER_SIZE);
size_t remaining = output_length;
uint8_t * dst = exclaves_stackshot_buffer;
size_t page_index = 0;
if (exclaves_stackshot_buffer_pages) {
/* TODO: remove exclaves_stackshot_buffer_pages and related code when shmv2 adoption is done */
while (remaining >= PAGE_SIZE) {
memcpy(dst, exclaves_stackshot_buffer_pages[page_index], PAGE_SIZE);
dst += PAGE_SIZE;
page_index++;
remaining -= PAGE_SIZE;
}
if (remaining) {
memcpy(dst, exclaves_stackshot_buffer_pages[page_index], remaining);
}
} else {
/* TODO: Do not copy the data when rdar://119329936 lands */
__block size_t dst_offset = 0;
exclaves_resource_shared_memory_io(stackshot_sharedmem_resource, 0, output_length, ^int (char * buf, size_t chunk_size) {
memcpy(exclaves_stackshot_buffer + dst_offset, buf, chunk_size);
dst_offset += chunk_size;
return 0; // continue looping
});
}
kr = process_exclaves_buffer(exclaves_stackshot_buffer, (size_t)output_length, process_fn, want_raw_addresses);
});
if (tberr != TB_ERROR_SUCCESS) {
exclaves_debug_printf(show_errors, "exclaves stackshot: stackshot_taker_takestackshot error 0x%x\n", tberr);
kr = KERN_FAILURE;
goto error_exit;
}
error_exit:
exclaves_debug_printf(show_progress, "exclaves stackshot: collection done with result %d\n", kr);
return kr;
}
static kern_return_t
complete_kperf_ast(kern_return_t collect_kr, const stackshot_stackshotresult_s *result, __unused bool want_raw_addresses)
{
if (collect_kr != KERN_SUCCESS) {
return collect_kr;
}
stackshot_stackshotentry__v_visit(&result->stackshotentries, ^(size_t i, const stackshot_stackshotentry_s * _Nonnull entry) {
assert(i < scid_list_count);
thread_t thread = ctid_get_thread(ctid_list[i]);
assert(thread);
kperf_thread_exclaves_ast_handler(thread, entry);
});
return KERN_SUCCESS;
}
/*
* Kernel thread that will collect, upon event (exclaves_collect_event), data
* on the current activity in the Exclave world of a set of threads registered
* with its waitlist.
*/
__attribute__((noreturn))
static void
exclaves_collect_threads_thread(void __unused *arg, wait_result_t __unused wr)
{
kern_return_t kr = KERN_SUCCESS;
kr = exclaves_allocate_ipc_buffer(NULL);
if (kr != KERN_SUCCESS) {
panic("exclaves stackshot: failed to allocate collect ipcb: %d", kr);
}
os_atomic_store(¤t_thread()->th_exclaves_inspection_state, TH_EXCLAVES_INSPECTION_NOINSPECT, relaxed);
lck_mtx_lock(&exclaves_collect_init_mtx);
exclaves_collect_thread_ready = true;
wakeup_all_with_inheritor(&exclaves_collect_thread_ready, THREAD_AWAKENED);
lck_mtx_unlock(&exclaves_collect_init_mtx);
lck_mtx_lock(&exclaves_collect_mtx);
for (;;) {
while (queue_empty(&exclaves_inspection_queue_stackshot) && queue_empty(&exclaves_inspection_queue_kperf)) {
lck_mtx_sleep(&exclaves_collect_mtx, LCK_SLEEP_DEFAULT, (event_t)&exclaves_collect_event, THREAD_UNINT);
}
if (!queue_empty(&exclaves_inspection_queue_stackshot)) {
// only this thread should manipulate the scid_list
scid_list_count = prepare_scid_list_stackshot(&exclaves_inspection_queue_stackshot, scid_list, ctid_list, EXCLAVES_STACKSHOT_BATCH_SIZE);
while (scid_list_count) {
lck_mtx_unlock(&exclaves_collect_mtx);
kr = collect_scid_list(stackshot_exclaves_process_result, exclaves_stackshot_raw_addresses, exclaves_stackshot_all_address_spaces);
lck_mtx_lock(&exclaves_collect_mtx);
clear_pending_threads_stackshot(ctid_list, scid_list_count, TH_EXCLAVES_INSPECTION_STACKSHOT);
if (kr != KERN_SUCCESS) {
goto stackshot_error;
}
scid_list_count = prepare_scid_list_stackshot(&exclaves_inspection_queue_stackshot, scid_list, ctid_list, EXCLAVES_STACKSHOT_BATCH_SIZE);
}
stackshot_error:
if (!queue_empty(&exclaves_inspection_queue_stackshot)) {
clear_stackshot_queue(TH_EXCLAVES_INSPECTION_STACKSHOT);
}
stackshot_exclaves_process_result(kr, NULL, true);
wakeup_all_with_inheritor(&exclaves_inspection_queue_stackshot, THREAD_AWAKENED);
}
if (!queue_empty(&exclaves_inspection_queue_kperf)) {
scid_list_count = prepare_scid_list_kperf(&exclaves_inspection_queue_kperf, scid_list, ctid_list, EXCLAVES_STACKSHOT_BATCH_SIZE);
while (scid_list_count) {
lck_mtx_unlock(&exclaves_collect_mtx);
kr = collect_scid_list(complete_kperf_ast, false, false);
lck_mtx_lock(&exclaves_collect_mtx);
clear_pending_threads_kperf(ctid_list, scid_list_count, TH_EXCLAVES_INSPECTION_KPERF);
if (kr != KERN_SUCCESS) {
goto kperf_error;
}
scid_list_count = prepare_scid_list_kperf(&exclaves_inspection_queue_kperf, scid_list, ctid_list, EXCLAVES_STACKSHOT_BATCH_SIZE);
}
kperf_error:
if (!queue_empty(&exclaves_inspection_queue_kperf)) {
clear_kperf_queue(TH_EXCLAVES_INSPECTION_KPERF);
}
}
}
}
void
exclaves_inspection_begin_collecting(void)
{
lck_mtx_assert(&exclaves_collect_mtx, LCK_MTX_ASSERT_OWNED);
thread_wakeup_thread((event_t)&exclaves_collect_event, exclaves_collection_thread);
}
void
exclaves_inspection_wait_complete(queue_t queue)
{
lck_mtx_assert(&exclaves_collect_mtx, LCK_MTX_ASSERT_OWNED);
while (!queue_empty(queue)) {
lck_mtx_sleep_with_inheritor(&exclaves_collect_mtx, LCK_SLEEP_DEFAULT, (event_t)queue, exclaves_collection_thread, THREAD_UNINT, TIMEOUT_WAIT_FOREVER);
}
}
static kern_return_t
exclaves_inspection_init(void)
{
__block kern_return_t kr = KERN_SUCCESS;
tb_error_t tberr = 0;
tb_endpoint_t tb_endpoint = { 0 };
assert(!os_atomic_load(&exclaves_inspection_initialized, relaxed));
/*
* If there's no stackshot service available, just return.
*/
if (EXCLAVES_ID_STACKSHOT_SERVER_EP == EXCLAVES_INVALID_ID) {
exclaves_requirement_assert(EXCLAVES_R_STACKSHOT,
"stackshot server not found");
return KERN_SUCCESS;
}
queue_init(&exclaves_inspection_queue_stackshot);
queue_init(&exclaves_inspection_queue_kperf);
tb_endpoint = tb_endpoint_create_with_value(TB_TRANSPORT_TYPE_XNU, EXCLAVES_ID_STACKSHOT_SERVER_EP, TB_ENDPOINT_OPTIONS_NONE);
tberr = stackshot_taker__init(&tb_client, tb_endpoint);
if (tberr != TB_ERROR_SUCCESS) {
exclaves_debug_printf(show_errors, "exclaves stackshot: stackshot_taker_init error 0x%x\n", tberr);
return KERN_FAILURE;
}
tberr = stackshot_taker_allocsharedbuffer(&tb_client, ^(stackshot_sharedbuffer_s tbresult) {
__block size_t page_count = 0;
size_t exclaves_stackshot_buffer_size = 0;
u64__v_visit(&tbresult.physaddr, ^(size_t __unused i, const uint64_t __unused item) {
page_count++;
});
if (!page_count) {
// zero page count means that stackshot server is using shared mem v2
// leaving exclaves_stackshot_buffer_pages uninitialized will trigger shmv2 on xnu side
return;
}
if (os_mul_overflow(page_count, PAGE_SIZE, &exclaves_stackshot_buffer_size)) {
panic("exclaves stackshot: buffer size overflow");
return;
}
assert3u(exclaves_stackshot_buffer_size, ==, EXCLAVES_STACKSHOT_BUFFER_SIZE);
exclaves_stackshot_buffer_pages = kalloc_type(uint8_t*, page_count, Z_WAITOK);
if (!exclaves_stackshot_buffer_pages) {
panic("exclaves stackshot: cannot allocate buffer for exclaves shared memory addresses");
return;
}
u64__v_visit(&tbresult.physaddr, ^(size_t i, const uint64_t item) {
exclaves_stackshot_buffer_pages[i] = (uint8_t*)phystokv((pmap_paddr_t)item);
});
});
if (tberr != TB_ERROR_SUCCESS) {
exclaves_debug_printf(show_errors, "exclaves stackshot: stackshot_taker_allocsharedbuffer error 0x%x\n", tberr);
return KERN_FAILURE;
}
if (!exclaves_stackshot_buffer_pages) {
// initialize sharedmemv2 resource
const char *v2_seg_name = "com.apple.sharedmem.stackshotserver";
kr = exclaves_resource_shared_memory_map(
EXCLAVES_DOMAIN_KERNEL, v2_seg_name,
EXCLAVES_STACKSHOT_BUFFER_SIZE,
EXCLAVES_BUFFER_PERM_READ,
&stackshot_sharedmem_resource);
if (kr != KERN_SUCCESS) {
exclaves_debug_printf(show_errors,
"exclaves_inspection_init: Cannot map shared memory segment '%s': failed with %d\n",
v2_seg_name, kr);
return kr;
}
}
exclaves_debug_printf(show_progress, "exclaves stackshot: exclaves inspection initialized\n");
kr = (kernel_thread_start_priority(
exclaves_collect_threads_thread, NULL, exclaves_collect_priority, &exclaves_collection_thread));
if (kr != KERN_SUCCESS) {
goto error_exit;
}
thread_set_thread_name(exclaves_collection_thread, "exclaves-stackshot");
thread_deallocate(exclaves_collection_thread);
lck_mtx_lock(&exclaves_collect_init_mtx);
while (!exclaves_collect_thread_ready) {
lck_mtx_sleep_with_inheritor(&exclaves_collect_init_mtx, LCK_SLEEP_DEFAULT, (event_t)&exclaves_collect_thread_ready, exclaves_collection_thread, THREAD_UNINT, TIMEOUT_WAIT_FOREVER);
}
os_atomic_store(&exclaves_inspection_initialized, true, release);
lck_mtx_unlock(&exclaves_collect_init_mtx);
error_exit:
return kr;
}
EXCLAVES_BOOT_TASK(exclaves_inspection_init, EXCLAVES_BOOT_RANK_SECOND);
bool
exclaves_inspection_is_initialized()
{
return os_atomic_load(&exclaves_inspection_initialized, acquire);
}
/*
* This function expects preemption and interrupts disabled as
* exclaves_scheduler_request does.
*
* TH_EXCLAVES_STACKSHOT_AST is set when stackshot is running in debug mode
* and adds a thread to waiting list.
*
* TH_EXCLAVES_STACKSHOT_AST is cleaned up by a collection thread which is
* holding exclaves_collect_mtx.
*
* It's guaranteed that th_exclaves_inspection_state & TH_EXCLAVES_STACKSHOT_AST is false
* when it exits.
*/
void
exclaves_inspection_check_ast(void)
{
thread_t thread = current_thread();
assert((os_atomic_load(&thread->th_exclaves_inspection_state, relaxed) & TH_EXCLAVES_INSPECTION_NOINSPECT) == 0);
/* This will unblock exclaves stackshot collection */
STACKSHOT_TESTPOINT(TP_AST);
/* Grab the mutex to prevent cleanup just after next check */
lck_mtx_lock(&exclaves_collect_mtx);
while ((os_atomic_load(&thread->th_exclaves_inspection_state, relaxed) & TH_EXCLAVES_INSPECTION_STACKSHOT) != 0) {
lck_mtx_sleep_with_inheritor(&exclaves_collect_mtx, LCK_SLEEP_DEFAULT,
(event_t)&thread->th_exclaves_inspection_queue_stackshot, exclaves_collection_thread,
THREAD_UNINT, TIMEOUT_WAIT_FOREVER
);
}
if ((os_atomic_load(&thread->th_exclaves_inspection_state, relaxed) & TH_EXCLAVES_INSPECTION_KPERF) != 0) {
exclaves_inspection_queue_add(&exclaves_inspection_queue_kperf, &thread->th_exclaves_inspection_queue_kperf);
thread_reference(thread);
exclaves_inspection_begin_collecting();
lck_mtx_sleep_with_inheritor(&exclaves_collect_mtx, LCK_SLEEP_DEFAULT,
(event_t)&thread->th_exclaves_inspection_queue_kperf, exclaves_collection_thread,
THREAD_UNINT, TIMEOUT_WAIT_FOREVER
);
}
lck_mtx_unlock(&exclaves_collect_mtx);
}
/* this should come from somewhere in EP */
#define STACKSHOT_PANIC_MAGIC 0xdeadcafebeefbabe
typedef struct stackshot_panic_magic {
uint64_t magic;
uint64_t size;
} stackshot_panic_magic_t;
_Static_assert(sizeof(stackshot_panic_magic_t) == 16, "panic magic should be 16 bytes");
void
kdp_read_panic_exclaves_stackshot(struct exclaves_panic_stackshot *eps)
{
assert(debug_mode_active());
*eps = (struct exclaves_panic_stackshot){ 0 };
if (!exclaves_inspection_is_initialized()) {
return;
}
/* copy the entire potential range of the buffer */
if (exclaves_stackshot_buffer_pages) {
size_t remaining = EXCLAVES_STACKSHOT_BUFFER_SIZE;
uint8_t *dst = exclaves_stackshot_buffer;
size_t page_index = 0;
while (remaining >= PAGE_SIZE) {
memcpy(dst, exclaves_stackshot_buffer_pages[page_index], PAGE_SIZE);
dst += PAGE_SIZE;
page_index++;
remaining -= PAGE_SIZE;
}
if (remaining) {
memcpy(dst, exclaves_stackshot_buffer_pages[page_index], remaining);
}
} else {
__block size_t dst_offset = 0;
exclaves_resource_shared_memory_io(stackshot_sharedmem_resource, 0, EXCLAVES_STACKSHOT_BUFFER_SIZE, ^int (char * buf, size_t chunk_size) {
memcpy(exclaves_stackshot_buffer + dst_offset, buf, chunk_size);
dst_offset += chunk_size;
return 0; // continue looping
});
}
/* check for panic magic value in xnu's copy of the region */
stackshot_panic_magic_t *panic_magic = __IGNORE_WCASTALIGN((stackshot_panic_magic_t *)(exclaves_stackshot_buffer + (EXCLAVES_STACKSHOT_BUFFER_SIZE - sizeof(stackshot_panic_magic_t))));
if (panic_magic->magic != STACKSHOT_PANIC_MAGIC) {
return;
}
eps->stackshot_buffer = exclaves_stackshot_buffer;
eps->stackshot_buffer_size = panic_magic->size;
}
#endif /* CONFIG_EXCLAVES */