This is xnu-11215.1.10. See this file in:
/*
* Copyright (c) 2012-2021 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@
*/
#include <kern/assert.h>
#include <kern/backtrace.h>
#include <kern/btlog.h>
#include <kern/smr.h>
#include <kern/startup.h>
#include <kern/thread_call.h>
#include <os/hash.h>
#include <mach/vm_map.h>
#include <mach/vm_param.h>
#include <vm/vm_kern_xnu.h>
#include <vm/vm_map_xnu.h>
#include <vm/vm_memtag.h>
#include <vm/pmap.h>
#pragma mark btref & helpers
static LCK_GRP_DECLARE(bt_library_lck_grp, "bt_library");
static SMR_DEFINE(bt_library_smr, "bt library");
#define BTS_FRAMES_MAX 13
#define BTS_FRAMES_REF_MASK 0xfffffff0
#define BTS_FRAMES_REF_INC 0x00000010
#define BTS_FRAMES_LEN_MASK 0x0000000f
typedef SMR_POINTER(btref_t) btref_smr_t;
typedef union bt_stack {
struct {
btref_smr_t bts_next;
uint32_t bts_ref_len;
uint32_t bts_hash;
uint32_t bts_frames[BTS_FRAMES_MAX];
};
struct {
uint32_t bts_padding[3 + BTS_FRAMES_MAX - 1 - sizeof(long) / 4];
uint32_t bts_free_next;
smr_seq_t bts_free_seq;
};
} *bt_stack_t;
static_assert(sizeof(union bt_stack) == 64); /* allocation scheme needs it */
#define BTREF_PERMANENT_BIT 0x80000000u
#define BTREF_OP_MASK 0x0000003fu
#define BTREF_VALID_MASK 0xc000003fu
#define BTL_SIZE_INIT (1u << 20)
#define BTL_SIZE_MAX (1u << 30)
#define BTL_SLABS 9
#define BTL_PARAM_INIT 0x00000020u
#define BTL_PARAM_PARITY(p) ((p) >> 31)
#define BTL_PARAM_SHIFT(p) (32 - ((p) & 0x3f))
#define BTL_PARAM_IDX(p, h) ((uint64_t)(h) >> ((p) & 0x3f))
#define BTL_PARAM_NEXT(p) ((p) - 0x80000001u)
#define BTL_HASH_SHIFT 8
#define BTL_HASH_COUNT (1u << BTL_HASH_SHIFT)
#define BTL_HASH_MASK (BTL_HASH_COUNT - 1)
static_assert((BTL_SIZE_INIT << BTL_SLABS) == BTL_SIZE_MAX / 2);
typedef struct bt_hash {
btref_smr_t bth_array[BTL_HASH_COUNT];
} *bt_hash_t;
#if DEBUG || DEVELOPMENT
#define BTLIB_VALIDATE 1
#else
#define BTLIB_VALIDATE 0
#endif
/*!
* @typedef bt_library_t
*
* @brief
* Describes a backtrace library.
*
* @discussion
* A backtrace library is a scalable hash table of backtraces
* used for debugging purposes.
*
* By default there is a single singleton one, but the code
* is amenable to have several instances.
*
*
* <h2>Data structure design</h2>
*
* Its hash table is structured like this:
*
* par = BTL_PARAM_PARITY(btl->btl_param);
* sz = 1u << BTL_PARAM_SHIFT(btl->btl_param);
*
* btl->btl_hash[par]
* │
* │ ╭─────── array of size "sz" buckets ───────╮
* ╰───> │ │
* ╰──────────────────────────────────┼───────╯
* │
* ╭─────── struct bt_hash ───────╮ │
* │ │ <───╯
* ╰──┼───────────────────────────╯
* │
* ╰──> Stack ──> Stack ──> Stack ──> X
*
*
* The "btl_hash" two entries are used with the "btl_param" switch in order
* to swap the outer array while growing the hash without perturbating
* readers.
*
* The lists of stacks are also maintained in "hash" order which allows
* for the rehashing to be a clean split of the lists.
*
* All stack pointers are "references" which are a smaller 32bit offset
* within the library backing store (slabs).
*
*/
typedef struct bt_library {
lck_ticket_t btl_lock;
SMR_POINTER(uint32_t) btl_param;
bt_hash_t *btl_hash[2];
thread_call_t btl_call;
thread_t btl_grower;
btref_t *btl_free_tail;
btref_t btl_free_head;
btref_t btl_deferred_head;
bool btl_waiters;
bool btl_in_callout;
bool btl_rehashing;
uint8_t btl_slab_cur;
uint32_t btl_alloc_pos;
uint32_t btl_faulted_pos;
uint32_t btl_max_pos;
vm_address_t btl_slabs[BTL_SLABS];
} *bt_library_t;
static struct bt_library bt_library;
static size_t
__btstack_len(bt_stack_t bts)
{
return bts->bts_ref_len & BTS_FRAMES_LEN_MASK;
}
static size_t
__btstack_size(bt_stack_t bts)
{
return sizeof(uint32_t) * __btstack_len(bts);
}
static bool
__btstack_same(bt_stack_t a, bt_stack_t b)
{
return a->bts_hash == b->bts_hash &&
__btstack_len(a) == __btstack_len(b) &&
memcmp(a->bts_frames, b->bts_frames, __btstack_size(a)) == 0;
}
static uint32_t
__btstack_capture(bt_stack_t bts, void *fp, bool permanent)
{
struct backtrace_control ctl = {
.btc_frame_addr = (vm_offset_t)fp,
};
size_t size;
size = backtrace_packed(BTP_KERN_OFFSET_32, (uint8_t *)bts->bts_frames,
sizeof(bts->bts_frames), &ctl, NULL);
bts->bts_ref_len = (size / sizeof(uint32_t)) +
(permanent ? BTS_FRAMES_REF_MASK : BTS_FRAMES_REF_INC);
return bts->bts_hash = os_hash_jenkins(bts->bts_frames, size);
}
static btref_t
__btstack_try_retain(btref_t btref, bt_stack_t bts, btref_get_flags_t flags)
{
uint32_t oref, nref;
oref = bts->bts_ref_len;
do {
switch (oref & BTS_FRAMES_REF_MASK) {
case 0:
return 0;
case BTS_FRAMES_REF_MASK:
return btref | BTREF_PERMANENT_BIT;
}
nref = oref + BTS_FRAMES_REF_INC;
if (flags & BTREF_GET_PERMANENT) {
nref |= BTS_FRAMES_REF_MASK;
}
} while (!os_atomic_cmpxchgv(&bts->bts_ref_len,
oref, nref, &oref, relaxed));
if ((nref & BTS_FRAMES_REF_MASK) == BTS_FRAMES_REF_MASK) {
btref |= BTREF_PERMANENT_BIT;
}
return btref;
}
__abortlike
static void
__btstack_resurrect_panic(bt_stack_t bts)
{
panic("trying to resurrect bt stack %p", bts);
}
static btref_t
__btstack_retain(btref_t btref, bt_stack_t bts, btref_get_flags_t flags)
{
uint32_t oref, nref;
oref = bts->bts_ref_len;
do {
switch (oref & BTS_FRAMES_REF_MASK) {
case 0:
__btstack_resurrect_panic(bts);
case BTS_FRAMES_REF_MASK:
return btref | BTREF_PERMANENT_BIT;
}
nref = oref + BTS_FRAMES_REF_INC;
if (flags & BTREF_GET_PERMANENT) {
nref |= BTS_FRAMES_REF_MASK;
}
} while (!os_atomic_cmpxchgv(&bts->bts_ref_len,
oref, nref, &oref, relaxed));
if ((nref & BTS_FRAMES_REF_MASK) == BTS_FRAMES_REF_MASK) {
btref |= BTREF_PERMANENT_BIT;
}
return btref;
}
__abortlike
static void
__btstack_over_release_panic(bt_stack_t bts)
{
panic("trying to over-release bt stack %p", bts);
}
static bool
__btstack_release(bt_stack_t bts)
{
uint32_t oref, nref;
oref = bts->bts_ref_len;
do {
switch (oref & BTS_FRAMES_REF_MASK) {
case 0:
__btstack_over_release_panic(bts);
case BTS_FRAMES_REF_MASK:
return false;
}
nref = oref - BTS_FRAMES_REF_INC;
} while (!os_atomic_cmpxchgv(&bts->bts_ref_len,
oref, nref, &oref, relaxed));
return nref < BTS_FRAMES_REF_INC;
}
static bt_stack_t
__btlib_deref(bt_library_t btl, btref_t ref)
{
uint32_t slab = 0;
if (ref >= BTL_SIZE_INIT) {
slab = __builtin_clz(BTL_SIZE_INIT) - __builtin_clz(ref) + 1;
}
return (bt_stack_t)(btl->btl_slabs[slab] + ref);
}
static void
__btlib_lock(bt_library_t btl)
{
lck_ticket_lock(&btl->btl_lock, &bt_library_lck_grp);
}
static void
__btlib_unlock(bt_library_t btl)
{
lck_ticket_unlock(&btl->btl_lock);
}
static inline btref_smr_t *
__btlib_head(bt_library_t btl, uint32_t param, uint32_t hash)
{
uint32_t par = BTL_PARAM_PARITY(param);
uint32_t idx = BTL_PARAM_IDX(param, hash);
return &btl->btl_hash[par][idx]->bth_array[hash & BTL_HASH_MASK];
}
#pragma mark btref growth & rehashing
static void __btlib_remove_deferred_locked(bt_library_t btl);
static bool
__btlib_growth_needed(bt_library_t btl)
{
if (btl->btl_faulted_pos >= btl->btl_alloc_pos + PAGE_SIZE / 2) {
return false;
}
if (btl->btl_faulted_pos == btl->btl_max_pos &&
btl->btl_slab_cur + 1 == BTL_SLABS) {
return false;
}
return true;
}
static bool
__btlib_rehash_needed(bt_library_t btl)
{
uint32_t param = smr_serialized_load(&btl->btl_param);
uint32_t shift = BTL_HASH_SHIFT + BTL_PARAM_SHIFT(param);
return (btl->btl_faulted_pos >> (3 + shift)) >= sizeof(union bt_stack);
}
static void
__btlib_callout_wakeup(bt_library_t btl)
{
if (startup_phase >= STARTUP_SUB_THREAD_CALL &&
!btl->btl_in_callout) {
thread_call_enter(btl->btl_call);
}
}
__attribute__((noinline))
static void
__btlib_grow(bt_library_t btl)
{
kern_return_t kr = KERN_SUCCESS;
vm_address_t addr;
while (btl->btl_grower) {
btl->btl_waiters = true;
lck_ticket_sleep_with_inheritor(&btl->btl_lock,
&bt_library_lck_grp, LCK_SLEEP_DEFAULT,
&btl->btl_grower, btl->btl_grower,
THREAD_UNINT, TIMEOUT_WAIT_FOREVER);
if (!__btlib_growth_needed(btl)) {
return;
}
}
btl->btl_grower = current_thread();
__btlib_unlock(btl);
if (btl->btl_faulted_pos == btl->btl_max_pos) {
uint8_t slab = btl->btl_slab_cur + 1;
vm_size_t size = btl->btl_max_pos;
kr = kmem_alloc(kernel_map, &addr, size,
KMA_KOBJECT | KMA_ZERO | KMA_VAONLY | KMA_DATA,
VM_KERN_MEMORY_DIAG);
if (kr != KERN_SUCCESS) {
goto done;
}
btl->btl_slab_cur = slab;
btl->btl_slabs[slab] = addr - size;
btl->btl_max_pos += size;
}
if (btl->btl_faulted_pos < btl->btl_alloc_pos + PAGE_SIZE / 2) {
uint8_t slab = btl->btl_slab_cur;
addr = btl->btl_slabs[slab] + btl->btl_faulted_pos;
kr = kernel_memory_populate(addr, PAGE_SIZE,
KMA_KOBJECT | KMA_ZERO | KMA_SPRAYQTN, VM_KERN_MEMORY_DIAG);
}
done:
__btlib_lock(btl);
if (kr == KERN_SUCCESS) {
btl->btl_faulted_pos += PAGE_SIZE;
}
btl->btl_grower = NULL;
if (btl->btl_waiters) {
btl->btl_waiters = false;
wakeup_all_with_inheritor(&btl->btl_grower, THREAD_AWAKENED);
}
if (__btlib_rehash_needed(btl)) {
__btlib_callout_wakeup(btl);
}
}
static void
__btlib_split_step(
bt_library_t btl,
bt_hash_t *bthp,
uint32_t idx,
uint32_t mask)
{
btref_smr_t *head, *prev;
bt_stack_t bts;
btref_t ref;
__btlib_lock(btl);
if (__btlib_growth_needed(btl)) {
__btlib_grow(btl);
}
for (uint32_t i = 0; i < BTL_HASH_COUNT; i++) {
prev = head = &bthp[idx]->bth_array[i];
while ((ref = smr_serialized_load(prev)) != BTREF_NULL) {
bts = __btlib_deref(btl, ref);
if (bts->bts_hash & mask) {
break;
}
prev = &bts->bts_next;
}
if (idx & 1) {
smr_init_store(head, ref);
} else {
smr_clear_store(prev);
}
}
__btlib_unlock(btl);
}
#if BTLIB_VALIDATE
static void
__btlib_validate(
bt_library_t btl,
bt_hash_t *bthp,
uint32_t size,
uint32_t __assert_only param)
{
bt_stack_t bts;
btref_t ref;
for (uint32_t i = 0; i < size; i++) {
for (uint32_t j = 0; j < BTL_HASH_COUNT; j++) {
ref = smr_serialized_load(&bthp[i]->bth_array[j]);
if (ref == 0) {
continue;
}
bts = __btlib_deref(btl, ref);
assert3u(BTL_PARAM_IDX(param, bts->bts_hash), ==, i);
assert3u(bts->bts_hash & BTL_HASH_MASK, ==, j);
}
}
}
#endif /* BTLIB_VALIDATE */
__attribute__((noinline))
static void
__btlib_rehash_and_lock(bt_library_t btl)
{
uint32_t param_old, size_old, mask;
bt_hash_t *bthp_old;
bt_hash_t *bthp;
smr_seq_t s1, s2;
/*
* Step 1: compute all the right sizes and parameters
* and allocate the new hash table elements.
*/
param_old = smr_serialized_load(&btl->btl_param);
bthp_old = btl->btl_hash[BTL_PARAM_PARITY(param_old)];
size_old = 1u << BTL_PARAM_SHIFT(param_old);
bthp = kalloc_type(bt_hash_t, 2 * size_old, Z_WAITOK_ZERO);
mask = 1u << (BTL_PARAM_NEXT(param_old) & 0x1f);
if (bthp == NULL) {
return;
}
for (uint32_t i = 0; i < size_old; i++) {
bthp[2 * i] = bthp_old[i];
bthp[2 * i + 1] = kalloc_type(struct bt_hash,
Z_WAITOK_ZERO_NOFAIL);
}
/*
* Step 2: Copy all the hash table buckets in one go.
* And publish the new array.
*
* TODO: consider if we want to let go of the lock sometimes.
*/
__btlib_lock(btl);
btl->btl_rehashing = true;
for (uint32_t i = 0; i < size_old; i++) {
memcpy(bthp[2 * i + 1], bthp[2 * i], sizeof(struct bt_hash));
}
btl->btl_hash[!BTL_PARAM_PARITY(param_old)] = bthp;
smr_serialized_store(&btl->btl_param, BTL_PARAM_NEXT(param_old));
__btlib_unlock(btl);
smr_synchronize(&bt_library_smr);
/*
* Step 3: Compute the "odd" lists
*
* When we arrive here, we have 2 buckets per list working this way,
* assumnig the hash bit that we are interested in changes on "C -> D":
*
* [ even ] -> A -> B -> C -> D -> E -> 0
* [ odd ] ---^
*
* We will now build:
*
* [ even ] -> A -> B -> C -> D -> E -> 0
* [ odd ] ------------------^
*
* Note: we try to advance the SMR clock twice,
* in the hope that for larger hashes it will
* help smr_wait() not to spin.
*/
for (uint32_t i = 0; i < size_old; i += 2) {
__btlib_split_step(btl, bthp, i + 1, mask);
}
s1 = smr_advance(&bt_library_smr);
if (size_old >= 2) {
for (uint32_t i = size_old; i < 2 * size_old; i += 2) {
__btlib_split_step(btl, bthp, i + 1, mask);
}
s2 = smr_advance(&bt_library_smr);
}
/*
* It's now possible to free the old array, do it,
* in a feeble attempt to give SMR readers more time before
* the next smr_wait().
*/
btl->btl_hash[BTL_PARAM_PARITY(param_old)] = NULL;
kfree_type(bt_hash_t, size_old, bthp_old);
/*
* Step 4: Split the "even" lists
*
* We will now cut the "C -> D" link in the even bucket, ending up with:
*
* [ even ] -> A -> B -> C -> 0
* [ odd ] ----------------> D -> E -> 0
*/
smr_wait(&bt_library_smr, s1);
for (uint32_t i = 0; i < size_old; i += 2) {
__btlib_split_step(btl, bthp, i, mask);
}
if (size_old >= 2) {
smr_wait(&bt_library_smr, s2);
for (uint32_t i = size_old; i < 2 * size_old; i += 2) {
__btlib_split_step(btl, bthp, i, mask);
}
}
/*
* Help readers see the cuts.
*/
(void)smr_advance(&bt_library_smr);
__btlib_lock(btl);
btl->btl_rehashing = false;
#if BTLIB_VALIDATE
__btlib_validate(btl, bthp, size_old * 2, BTL_PARAM_NEXT(param_old));
#endif /* BTLIB_VALIDATE */
__btlib_remove_deferred_locked(btl);
}
static void
__btlib_callout(thread_call_param_t arg0, thread_call_param_t __unused arg1)
{
bt_library_t btl = arg0;
__btlib_lock(btl);
btl->btl_in_callout = true;
if (__btlib_growth_needed(btl)) {
__btlib_grow(btl);
}
while (__btlib_rehash_needed(btl)) {
__btlib_unlock(btl);
__btlib_rehash_and_lock(btl);
}
btl->btl_in_callout = false;
__btlib_unlock(btl);
}
static void
__btlib_init(bt_library_t btl)
{
kern_return_t kr;
vm_address_t addr;
bt_hash_t *bthp;
lck_ticket_init(&btl->btl_lock, &bt_library_lck_grp);
btl->btl_free_tail = &btl->btl_free_head;
btl->btl_call = thread_call_allocate_with_options(__btlib_callout, btl,
THREAD_CALL_PRIORITY_KERNEL, THREAD_CALL_OPTIONS_ONCE);
kr = kmem_alloc(kernel_map, &addr, BTL_SIZE_INIT,
KMA_KOBJECT | KMA_ZERO | KMA_VAONLY | KMA_DATA,
VM_KERN_MEMORY_DIAG);
if (kr != KERN_SUCCESS) {
panic("unable to allocate initial VA: %d", kr);
}
bthp = kalloc_type(bt_hash_t, 1, Z_WAITOK_ZERO_NOFAIL);
bthp[0] = kalloc_type(struct bt_hash, Z_WAITOK_ZERO_NOFAIL);
btl->btl_slabs[0] = addr;
btl->btl_max_pos = BTL_SIZE_INIT;
btl->btl_alloc_pos = sizeof(union bt_stack);
btl->btl_hash[0] = bthp;
smr_init_store(&btl->btl_param, BTL_PARAM_INIT);
}
STARTUP_ARG(ZALLOC, STARTUP_RANK_LAST, __btlib_init, &bt_library);
#pragma mark btref insertion/removal fastpaths
__attribute__((noinline))
static btref_t
__btlib_insert(
bt_library_t btl,
bt_stack_t needle,
btref_get_flags_t flags,
uint32_t hash)
{
bt_stack_t bts;
btref_smr_t *prev;
btref_t ref;
__btlib_lock(btl);
if (__btlib_growth_needed(btl)) {
/*
* Do this first so that we keep the lock held
* while we insert.
*/
if ((flags & BTREF_GET_NOWAIT) == 0) {
__btlib_grow(btl);
} else {
__btlib_callout_wakeup(btl);
}
}
prev = __btlib_head(btl, smr_serialized_load(&btl->btl_param), hash);
while ((ref = smr_serialized_load(prev)) != BTREF_NULL) {
bts = __btlib_deref(btl, ref);
#if BTLIB_VALIDATE
assert3u(bts->bts_hash & BTL_HASH_MASK, ==,
hash & BTL_HASH_MASK);
#endif /* BTLIB_VALIDATE */
if (needle->bts_hash < bts->bts_hash) {
break;
}
if (__btstack_same(needle, bts)) {
ref = __btstack_try_retain(ref, bts, flags);
if (ref) {
__btlib_unlock(btl);
return ref;
}
break;
}
prev = &bts->bts_next;
}
if (btl->btl_free_head) {
ref = btl->btl_free_head;
bts = __btlib_deref(btl, btl->btl_free_head);
if (smr_poll(&bt_library_smr, bts->bts_free_seq)) {
if ((btl->btl_free_head = bts->bts_free_next) == 0) {
btl->btl_free_tail = &btl->btl_free_head;
}
goto allocated;
}
}
if (__improbable(btl->btl_alloc_pos + sizeof(union bt_stack) >
btl->btl_faulted_pos)) {
__btlib_unlock(btl);
return BTREF_NULL;
}
ref = btl->btl_alloc_pos;
btl->btl_alloc_pos = ref + sizeof(union bt_stack);
bts = __btlib_deref(btl, ref);
allocated:
*bts = *needle;
smr_serialized_store(&bts->bts_next, smr_serialized_load(prev));
smr_serialized_store(prev, ref);
__btlib_unlock(btl);
return ref | ((flags & BTREF_GET_PERMANENT) != 0);
}
__abortlike
static void
__btlib_remove_notfound_panic(bt_library_t btl, bt_stack_t bts)
{
panic("couldn't find stack %p in library %p", bts, btl);
}
static void
__btlib_remove_locked(bt_library_t btl, btref_t ref, bt_stack_t bts)
{
uint32_t hash = bts->bts_hash;
uint32_t param = smr_serialized_load(&btl->btl_param);
btref_smr_t *prev;
if (btl->btl_rehashing) {
/*
* We can't really delete things during rehash.
* put them on the deferred list.
*/
bts->bts_free_next = btl->btl_deferred_head;
btl->btl_deferred_head = ref;
return;
}
prev = __btlib_head(btl, param, hash);
for (;;) {
btref_t tmp = smr_serialized_load(prev);
if (tmp == ref) {
break;
}
if (tmp == 0) {
__btlib_remove_notfound_panic(btl, bts);
}
prev = &__btlib_deref(btl, tmp)->bts_next;
}
smr_serialized_store(prev, smr_serialized_load(&bts->bts_next));
bts->bts_free_next = 0;
*btl->btl_free_tail = ref;
btl->btl_free_tail = &bts->bts_free_next;
bts->bts_free_seq = smr_advance(&bt_library_smr);
}
static void
__btlib_remove_deferred_locked(bt_library_t btl)
{
btref_t ref, next;
bt_stack_t bts;
next = btl->btl_deferred_head;
btl->btl_deferred_head = 0;
while ((ref = next)) {
bts = __btlib_deref(btl, ref);
next = bts->bts_free_next;
__btlib_remove_locked(btl, ref, bts);
}
}
__attribute__((noinline))
static void
__btlib_remove(bt_library_t btl, btref_t ref, bt_stack_t bts)
{
__btlib_lock(btl);
__btlib_remove_locked(btl, ref, bts);
__btlib_unlock(btl);
}
static btref_t
__btlib_get(bt_library_t btl, void *fp, btref_get_flags_t flags)
{
union bt_stack needle;
btref_smr_t *head;
uint32_t hash, param;
btref_t ref;
if (bt_library.btl_alloc_pos == 0) {
return BTREF_NULL;
}
hash = __btstack_capture(&needle, fp, (flags & BTREF_GET_PERMANENT));
smr_enter(&bt_library_smr);
/*
* The hash "params" have a single bit to select the btl_hash[]
* pointer that is used.
*
* The compiler knows enough about this code to break
* the dependency chains that we would like, generating code like this:
*
* bthp = btl->btl_hash[0];
* if (BTL_PARAM_PARITY(param)) {
* bthp = btl->btl_hash[1];
* }
*
* We could try to play tricks but this would be brittle, so instead,
* use a proper acquire barrier on param, which pairs with
* smr_serialized_store(&btl->btl_param, ...)
* in __btlib_rehash_and_lock().
*
*
* Similarly, because the `bts_next` fields are not dereferenced
* right away but used as part of complicated arithmetics,
* trusting the compiler's maintaining of dependencies
* is a tall order, sometimes, an acquire barrier is best.
*/
param = smr_entered_load_acquire(&btl->btl_param);
head = __btlib_head(btl, param, hash);
ref = smr_entered_load(head);
while (ref) {
bt_stack_t bts = __btlib_deref(btl, ref);
#if BTLIB_VALIDATE
assert3u(bts->bts_hash & BTL_HASH_MASK, ==,
hash & BTL_HASH_MASK);
#endif /* BTLIB_VALIDATE */
if (needle.bts_hash < bts->bts_hash) {
break;
}
if (__btstack_same(&needle, bts) &&
(ref = __btstack_try_retain(ref, bts, flags))) {
smr_leave(&bt_library_smr);
return ref;
}
ref = smr_entered_load(&bts->bts_next);
}
smr_leave(&bt_library_smr);
return __btlib_insert(btl, &needle, flags, hash);
}
btref_t
btref_get(void *fp, btref_get_flags_t flags)
{
return __btlib_get(&bt_library, fp, flags);
}
__abortlike
static void
__btref_invalid(btref_t btref)
{
panic("trying to manipulate invalid backtrace ref: 0x%08x", btref);
}
static inline bool
__btref_isvalid(btref_t btref)
{
return ((btref & BTREF_VALID_MASK) & ~BTREF_GET_PERMANENT) == 0;
}
btref_t
btref_retain(btref_t btref)
{
uint32_t sig = btref & BTREF_VALID_MASK;
if (btref && sig == 0) {
bt_stack_t bts = __btlib_deref(&bt_library, btref);
btref = __btstack_retain(btref, bts, 0);
} else if (sig & ~BTREF_PERMANENT_BIT) {
__btref_invalid(btref);
}
return btref;
}
void
btref_put(btref_t btref)
{
uint32_t sig = btref & BTREF_VALID_MASK;
if (btref && sig == 0) {
bt_library_t btl = &bt_library;
bt_stack_t bts = __btlib_deref(btl, btref);
if (__improbable(__btstack_release(bts))) {
__btlib_remove(btl, btref, bts);
}
} else if (sig & ~BTREF_PERMANENT_BIT) {
__btref_invalid(btref);
}
}
uint32_t
btref_decode_unslide(btref_t btref, mach_vm_address_t bt_out[])
{
static_assert(sizeof(mach_vm_address_t) == sizeof(uintptr_t));
if (__btref_isvalid(btref)) {
bt_stack_t bts = __btlib_deref(&bt_library, btref);
uint32_t len = __btstack_len(bts);
backtrace_unpack(BTP_KERN_OFFSET_32, (uintptr_t *)bt_out,
BTLOG_MAX_DEPTH, (uint8_t *)bts->bts_frames,
sizeof(uint32_t) * len);
for (uint32_t i = 0; i < len; i++) {
bt_out[i] = VM_KERNEL_UNSLIDE(bt_out[i]);
}
return len;
}
__btref_invalid(btref);
}
#pragma mark btlog types and helpers
struct btlog {
btlog_type_t btl_type;
uint32_t btl_disabled : 1;
uint32_t btl_sample_max : 23;
#define BTL_SAMPLE_LIMIT 0x007fffffu
uint32_t btl_count;
lck_ticket_t btl_lock;
uint32_t *__zpercpu btl_sample;
};
struct bt_log_entry {
vm_address_t btle_addr;
btref_t btle_where;
} __attribute__((packed, aligned(4)));
struct btlog_log {
struct btlog btll_hdr;
#define btll_count btll_hdr.btl_count
uint32_t btll_pos;
struct bt_log_entry btll_entries[__counted_by(btll_count)];
};
#define BT_HASH_END_MARKER UINT32_MAX
struct bt_hash_entry {
vm_address_t bthe_addr;
uint32_t bthe_next;
btref_t bthe_where;
};
struct bt_hash_head {
uint32_t bthh_first;
uint32_t bthh_last;
};
struct btlog_hash {
struct btlog btlh_hdr;
#define btlh_count btlh_hdr.btl_count
uint32_t btlh_pos;
struct bt_hash_head btlh_free;
struct bt_hash_entry btlh_entries[__counted_by(btlh_count)];
};
typedef union {
vm_address_t bta;
struct btlog *btl;
struct btlog_log *btll;
struct btlog_hash *btlh;
} __attribute__((transparent_union)) btlogu_t;
static LCK_GRP_DECLARE(btlog_lck_grp, "btlog");
static void
__btlog_lock(btlogu_t btlu)
{
lck_ticket_lock(&btlu.btl->btl_lock, &btlog_lck_grp);
}
static void
__btlog_unlock(btlogu_t btlu)
{
lck_ticket_unlock(&btlu.btl->btl_lock);
}
static void *
__btlog_elem_normalize(void *addr)
{
addr = (void *)vm_memtag_canonicalize_address((vm_offset_t)addr);
return addr;
}
static long
__btlog_elem_encode(void *addr)
{
return ~(long)__btlog_elem_normalize(addr);
}
static void *
__btlog_elem_decode(long addr)
{
return (void *)~addr;
}
static struct bt_hash_head *
__btlog_hash_hash(struct btlog_hash *btlh)
{
return (struct bt_hash_head *)(btlh->btlh_entries + btlh->btlh_count);
}
static uint32_t
__btlog_hash_count(struct btlog_hash *btlh)
{
return btlh->btlh_count >> 2;
}
static struct bt_hash_head *
__btlog_hash_head(struct btlog_hash *btlh, void *addr)
{
uint32_t h = os_hash_kernel_pointer(__btlog_elem_normalize(addr));
h &= (__btlog_hash_count(btlh) - 1);
return &__btlog_hash_hash(btlh)[h];
}
__attribute__((overloadable))
static struct btlog_size_pair {
vm_size_t btsp_size;
uint32_t btsp_count;
}
__btlog_size(btlog_type_t type, uint32_t count)
{
struct btlog_size_pair pair = {0};
switch (type) {
case BTLOG_LOG:
pair.btsp_size = round_page(sizeof(struct btlog_log) +
count * sizeof(struct bt_log_entry));
pair.btsp_count = (pair.btsp_size - sizeof(struct btlog_log)) /
sizeof(struct bt_log_entry);
break;
case BTLOG_HASH:
pair.btsp_count = MAX(1u << fls(count - 1), 128u);
pair.btsp_size = round_page(sizeof(struct btlog_hash) +
pair.btsp_count * sizeof(struct bt_log_entry) +
(pair.btsp_count >> 2) * sizeof(struct btlog_hash));
break;
}
return pair;
}
__attribute__((overloadable))
static struct btlog_size_pair
__btlog_size(btlogu_t btlu)
{
return __btlog_size(btlu.btl->btl_type, btlu.btl->btl_count);
}
static inline btref_t
__bt_ref(uint32_t stack_and_op)
{
return stack_and_op & ~BTREF_OP_MASK;
}
static inline btref_t
__bt_op(uint32_t stack_and_op)
{
return stack_and_op & BTREF_OP_MASK;
}
#pragma mark btlog_log
static void
__btlog_log_destroy(struct btlog_log *btll)
{
for (uint32_t i = 0; i < btll->btll_count; i++) {
btref_put(__bt_ref(btll->btll_entries[i].btle_where));
}
}
static void
__btlog_log_record(struct btlog_log *btll, void *addr, uint8_t op, btref_t btref)
{
struct bt_log_entry *btle;
btref_t old = BTREF_NULL;
uint32_t pos;
__btlog_lock(btll);
if (__improbable(btll->btll_hdr.btl_disabled)) {
goto disabled;
}
pos = btll->btll_pos;
if (pos + 1 == btll->btll_count) {
btll->btll_pos = 0;
} else {
btll->btll_pos = pos + 1;
}
btle = &btll->btll_entries[pos];
old = __bt_ref(btle->btle_where);
*btle = (struct bt_log_entry){
.btle_addr = __btlog_elem_encode(addr),
.btle_where = btref | (op & BTREF_OP_MASK),
};
disabled:
__btlog_unlock(btll);
btref_put(old);
}
#pragma mark btlog_hash
static void
__btlog_hash_init(struct btlog_hash *btlh)
{
struct bt_hash_head *hash = __btlog_hash_hash(btlh);
btlh->btlh_free.bthh_first = BT_HASH_END_MARKER;
btlh->btlh_free.bthh_last = BT_HASH_END_MARKER;
for (size_t i = 0; i < __btlog_hash_count(btlh); i++) {
hash[i].bthh_first = BT_HASH_END_MARKER;
hash[i].bthh_last = BT_HASH_END_MARKER;
}
}
static void
__btlog_hash_destroy(struct btlog_hash *btlh)
{
for (uint32_t i = 0; i < btlh->btlh_count; i++) {
btref_put(__bt_ref(btlh->btlh_entries[i].bthe_where));
}
}
static uint32_t
__btlog_hash_stailq_pop_first(
struct btlog_hash *btlh,
struct bt_hash_head *head)
{
struct bt_hash_entry *bthe;
uint32_t pos = head->bthh_first;
bthe = &btlh->btlh_entries[pos];
btlh->btlh_free.bthh_first = bthe->bthe_next;
if (bthe->bthe_next == BT_HASH_END_MARKER) {
btlh->btlh_free.bthh_last = BT_HASH_END_MARKER;
} else {
bthe->bthe_next = BT_HASH_END_MARKER;
}
return pos;
}
static void
__btlog_hash_stailq_remove(
struct bt_hash_head *head,
struct bt_hash_entry *bthe,
uint32_t *prev,
uint32_t ppos)
{
*prev = bthe->bthe_next;
if (bthe->bthe_next == BT_HASH_END_MARKER) {
head->bthh_last = ppos;
} else {
bthe->bthe_next = BT_HASH_END_MARKER;
}
}
static void
__btlog_hash_stailq_append(
struct btlog_hash *btlh,
struct bt_hash_head *head,
uint32_t pos)
{
if (head->bthh_last == BT_HASH_END_MARKER) {
head->bthh_first = head->bthh_last = pos;
} else {
btlh->btlh_entries[head->bthh_last].bthe_next = pos;
head->bthh_last = pos;
}
}
static void
__btlog_hash_remove(
struct btlog_hash *btlh,
struct bt_hash_entry *bthe)
{
struct bt_hash_head *head;
uint32_t *prev;
uint32_t ppos;
head = __btlog_hash_head(btlh, __btlog_elem_decode(bthe->bthe_addr));
prev = &head->bthh_first;
ppos = BT_HASH_END_MARKER;
while (bthe != &btlh->btlh_entries[*prev]) {
ppos = *prev;
prev = &btlh->btlh_entries[ppos].bthe_next;
}
__btlog_hash_stailq_remove(head, bthe, prev, ppos);
}
static void
__btlog_hash_record(struct btlog_hash *btlh, void *addr, uint8_t op, btref_t btref)
{
struct bt_hash_head *head;
struct bt_hash_entry *bthe;
btref_t old = BTREF_NULL;
uint32_t pos;
head = __btlog_hash_head(btlh, __btlog_elem_normalize(addr));
__btlog_lock(btlh);
if (__improbable(btlh->btlh_hdr.btl_disabled)) {
goto disabled;
}
if (btlh->btlh_free.bthh_first != BT_HASH_END_MARKER) {
pos = __btlog_hash_stailq_pop_first(btlh, &btlh->btlh_free);
bthe = &btlh->btlh_entries[pos];
} else {
pos = btlh->btlh_pos;
if (pos + 1 == btlh->btlh_count) {
btlh->btlh_pos = 0;
} else {
btlh->btlh_pos = pos + 1;
}
bthe = &btlh->btlh_entries[pos];
if (bthe->bthe_addr) {
__btlog_hash_remove(btlh, bthe);
}
}
old = __bt_ref(bthe->bthe_where);
*bthe = (struct bt_hash_entry){
.bthe_addr = __btlog_elem_encode(addr),
.bthe_where = btref | (op & BTREF_OP_MASK),
.bthe_next = BT_HASH_END_MARKER,
};
if (btref & BTREF_VALID_MASK) {
assert(__btlib_deref(&bt_library,
btref & BTREF_VALID_MASK)->bts_ref_len >= BTS_FRAMES_REF_INC);
}
__btlog_hash_stailq_append(btlh, head, pos);
disabled:
__btlog_unlock(btlh);
btref_put(old);
}
static void
__btlog_hash_erase(struct btlog_hash *btlh, void *addr)
{
struct bt_hash_head *head;
struct bt_hash_entry *bthe;
uint32_t *prev;
uint32_t pos, ppos;
addr = __btlog_elem_normalize(addr);
head = __btlog_hash_head(btlh, addr);
prev = &head->bthh_first;
ppos = BT_HASH_END_MARKER;
__btlog_lock(btlh);
if (__improbable(btlh->btlh_hdr.btl_disabled)) {
goto disabled;
}
while ((pos = *prev) != BT_HASH_END_MARKER) {
bthe = &btlh->btlh_entries[pos];
if (__btlog_elem_decode(bthe->bthe_addr) == addr) {
bthe->bthe_addr = 0;
__btlog_hash_stailq_remove(head, bthe, prev, ppos);
__btlog_hash_stailq_append(btlh, &btlh->btlh_free, pos);
} else {
ppos = *prev;
prev = &btlh->btlh_entries[ppos].bthe_next;
}
}
disabled:
__btlog_unlock(btlh);
}
#pragma mark btlog APIs
static void
__btlog_init(btlogu_t btlu)
{
switch (btlu.btl->btl_type) {
case BTLOG_HASH:
__btlog_hash_init(btlu.btlh);
break;
case BTLOG_LOG:
break;
}
}
btlog_t
btlog_create(btlog_type_t type, uint32_t count, uint32_t sample)
{
struct btlog_size_pair pair = __btlog_size(type, count);
kern_return_t kr;
btlogu_t btlu;
kr = kmem_alloc(kernel_map, &btlu.bta, pair.btsp_size,
KMA_KOBJECT | KMA_ZERO | KMA_SPRAYQTN, VM_KERN_MEMORY_DIAG);
if (kr != KERN_SUCCESS) {
return NULL;
}
if (sample > BTL_SAMPLE_LIMIT) {
sample = BTL_SAMPLE_LIMIT;
}
btlu.btl->btl_type = type;
btlu.btl->btl_sample_max = sample;
btlu.btl->btl_count = pair.btsp_count;
lck_ticket_init(&btlu.btl->btl_lock, &btlog_lck_grp);
assert3u(btlu.btl->btl_count, !=, 0);
if (sample > 1) {
btlu.btl->btl_sample = zalloc_percpu(percpu_u64_zone,
Z_WAITOK | Z_ZERO | Z_NOFAIL);
zpercpu_foreach_cpu(cpu) {
uint32_t *counter;
counter = zpercpu_get_cpu(btlu.btl->btl_sample, cpu);
*counter = (cpu + 1) * sample / zpercpu_count();
}
}
__btlog_init(btlu);
return btlu.btl;
}
static void
__btlog_destroy(btlogu_t btlu)
{
switch (btlu.btl->btl_type) {
case BTLOG_LOG:
__btlog_log_destroy(btlu.btll);
break;
case BTLOG_HASH:
__btlog_hash_destroy(btlu.btlh);
break;
}
}
void
btlog_destroy(btlogu_t btlu)
{
if (!btlu.btl->btl_disabled) {
__btlog_destroy(btlu);
}
if (btlu.btl->btl_sample) {
zfree_percpu(percpu_u64_zone, btlu.btl->btl_sample);
}
lck_ticket_destroy(&btlu.btl->btl_lock, &btlog_lck_grp);
kmem_free(kernel_map, btlu.bta, __btlog_size(btlu).btsp_size);
}
kern_return_t
btlog_enable(btlogu_t btlu)
{
vm_size_t size;
kern_return_t kr = KERN_SUCCESS;
size = __btlog_size(btlu).btsp_size;
if (size > PAGE_SIZE) {
kr = kernel_memory_populate(btlu.bta + PAGE_SIZE,
size - PAGE_SIZE, KMA_KOBJECT | KMA_ZERO | KMA_SPRAYQTN,
VM_KERN_MEMORY_DIAG);
}
if (kr == KERN_SUCCESS) {
__btlog_init(btlu);
__btlog_lock(btlu);
assert(btlu.btl->btl_disabled);
btlu.btl->btl_disabled = false;
__btlog_unlock(btlu);
}
return kr;
}
void
btlog_disable(btlogu_t btlu)
{
vm_size_t size;
__btlog_lock(btlu);
assert(!btlu.btl->btl_disabled);
btlu.btl->btl_disabled = true;
__btlog_unlock(btlu);
__btlog_destroy(btlu);
size = __btlog_size(btlu).btsp_size;
bzero((char *)btlu.bta + sizeof(*btlu.btl),
PAGE_SIZE - sizeof(*btlu.btl));
if (size > PAGE_SIZE) {
kernel_memory_depopulate(btlu.bta + PAGE_SIZE,
size - PAGE_SIZE, KMA_KOBJECT, VM_KERN_MEMORY_DIAG);
}
}
btlog_type_t
btlog_get_type(btlog_t btlog)
{
return btlog->btl_type;
}
uint32_t
btlog_get_count(btlog_t btlog)
{
return btlog->btl_count;
}
bool
btlog_sample(btlog_t btlog)
{
uint32_t *counter;
if (btlog->btl_sample == NULL) {
return true;
}
counter = zpercpu_get(btlog->btl_sample);
if (os_atomic_dec_orig(counter, relaxed) != 0) {
return false;
}
os_atomic_store(counter, btlog->btl_sample_max - 1, relaxed);
return true;
}
void
btlog_record(btlogu_t btlu, void *addr, uint8_t op, btref_t btref)
{
if (btlu.btl->btl_disabled) {
return;
}
switch (btlu.btl->btl_type) {
case BTLOG_LOG:
__btlog_log_record(btlu.btll, addr, op, btref);
break;
case BTLOG_HASH:
__btlog_hash_record(btlu.btlh, addr, op, btref);
break;
}
}
void
btlog_erase(btlogu_t btlu, void *addr)
{
if (btlu.btl->btl_disabled) {
return;
}
switch (btlu.btl->btl_type) {
case BTLOG_HASH:
__btlog_hash_erase(btlu.btlh, addr);
break;
case BTLOG_LOG:
break;
}
}
extern void
qsort(void *a, size_t n, size_t es, int (*cmp)(const void *, const void *));
struct btlog_record {
uint32_t btr_where;
uint32_t btr_count;
};
static int
btlog_record_cmp_where(const void *e1, const void *e2)
{
const struct btlog_record *a = e1;
const struct btlog_record *b = e2;
if (a->btr_where == b->btr_where) {
return 0;
}
return a->btr_where > b->btr_where ? 1 : -1;
}
static bool
btlog_records_pack(struct btlog_record *array, uint32_t *countp)
{
uint32_t r, w, count = *countp;
qsort(array, count, sizeof(struct btlog_record), btlog_record_cmp_where);
for (r = 1, w = 1; r < count; r++) {
if (array[w - 1].btr_where == array[r].btr_where) {
array[w - 1].btr_count += array[r].btr_count;
} else {
array[w++] = array[r];
}
}
if (w == count) {
return false;
}
*countp = w;
return true;
}
static int
btlog_record_cmp_rev_count(const void *e1, const void *e2)
{
const struct btlog_record *a = e1;
const struct btlog_record *b = e2;
if (a->btr_count == b->btr_count) {
return 0;
}
return a->btr_count > b->btr_count ? -1 : 1;
}
kern_return_t
btlog_get_records(
btlogu_t btl,
zone_btrecord_t **records,
unsigned int *numrecs)
{
struct btlog_record *btr_array;
struct btlog_record btr;
zone_btrecord_t *rec_array;
vm_offset_t addr, end, size, ipc_map_size;
kern_return_t kr;
uint32_t count = 0;
/*
* Step 1: collect all the backtraces in the logs in wired memory
*
* note that the ipc_kernel_map is small, and we might have
* too little space.
*
* In order to accomodate, we will deduplicate as we go.
* If we still overflow space, we return KERN_NO_SPACE.
*/
ipc_map_size = (vm_offset_t)(vm_map_max(ipc_kernel_map) -
vm_map_min(ipc_kernel_map));
size = round_page(btlog_get_count(btl.btl) * sizeof(struct btlog_record));
if (size > ipc_map_size) {
size = ipc_map_size / 4;
}
for (;;) {
kr = kmem_alloc(ipc_kernel_map, &addr, size,
KMA_DATA, VM_KERN_MEMORY_IPC);
if (kr == KERN_SUCCESS) {
break;
}
if (size < (1U << 19)) {
return kr;
}
size /= 2;
}
btr_array = (struct btlog_record *)addr;
rec_array = (zone_btrecord_t *)addr;
kr = KERN_NOT_FOUND;
__btlog_lock(btl);
if (btl.btl->btl_disabled) {
goto disabled;
}
switch (btl.btl->btl_type) {
case BTLOG_LOG:
for (uint32_t i = 0; i < btl.btl->btl_count; i++) {
struct bt_log_entry *btle = &btl.btll->btll_entries[i];
if (!btle->btle_addr) {
break;
}
if ((count + 1) * sizeof(struct btlog_record) > size) {
if (!btlog_records_pack(btr_array, &count)) {
kr = KERN_NO_SPACE;
count = 0;
break;
}
}
btr_array[count].btr_where = btle->btle_where;
btr_array[count].btr_count = 1;
count++;
}
break;
case BTLOG_HASH:
for (uint32_t i = 0; i < btl.btl->btl_count; i++) {
struct bt_hash_entry *bthe = &btl.btlh->btlh_entries[i];
if (!bthe->bthe_addr) {
continue;
}
if ((count + 1) * sizeof(struct btlog_record) > size) {
if (!btlog_records_pack(btr_array, &count)) {
kr = KERN_NO_SPACE;
count = 0;
break;
}
}
btr_array[count].btr_where = bthe->bthe_where;
btr_array[count].btr_count = 1;
count++;
}
break;
}
/*
* Step 2: unique all the records, and retain them
*/
if (count) {
btlog_records_pack(btr_array, &count);
/*
* If the backtraces won't fit,
* sort them in reverse popularity order and clip.
*/
if (count > size / sizeof(zone_btrecord_t)) {
qsort(btr_array, count, sizeof(struct btlog_record),
btlog_record_cmp_rev_count);
count = size / sizeof(zone_btrecord_t);
}
for (uint32_t i = 0; i < count; i++) {
btref_retain(__bt_ref(btr_array[i].btr_where));
}
}
disabled:
__btlog_unlock(btl);
if (count == 0) {
kmem_free(ipc_kernel_map, addr, size);
return kr;
}
/*
* Step 3: Expand the backtraces in place, in reverse order.
*/
for (uint32_t i = count; i-- > 0;) {
btr = *(volatile struct btlog_record *)&btr_array[i];
rec_array[i] = (zone_btrecord_t){
.ref_count = btr.btr_count,
.operation_type = __bt_op(btr.btr_where),
};
btref_decode_unslide(__bt_ref(btr.btr_where), rec_array[i].bt);
btref_put(__bt_ref(btr.btr_where));
}
/*
* Step 4: Free the excess memory, zero padding, and unwire the buffer.
*/
end = round_page((vm_offset_t)(rec_array + count));
bzero(rec_array + count, end - (vm_address_t)(rec_array + count));
if (end < addr + size) {
kmem_free(ipc_kernel_map, end, addr + size - end);
}
kr = vm_map_unwire(ipc_kernel_map, addr, end, FALSE);
assert(kr == KERN_SUCCESS);
*records = rec_array;
*numrecs = count;
return KERN_SUCCESS;
}
uint32_t
btlog_guess_top(btlogu_t btlu, vm_address_t bt[], uint32_t *len)
{
struct btlog_hash *btlh = btlu.btlh;
const unsigned RECS = 8;
struct btlog_record recs[RECS] = {0};
bt_stack_t bts;
if (btlu.btl->btl_type != BTLOG_HASH) {
return 0;
}
if (!lck_ticket_lock_try(&btlu.btl->btl_lock, &btlog_lck_grp)) {
return 0;
}
if (btlu.btl->btl_disabled || btlh->btlh_count == 0) {
goto disabled;
}
/*
* This is called from panic context, and can't really
* do what btlog_get_records() do and allocate memory.
*
* Instead, we use the refcounts in the bt library
* as a proxy for counts (of course those backtraces
* can be inflated due to being shared with other logs,
* which is why we use `RECS` slots in the array to find
* the RECS more popular stacks at all).
*
* Note: this will break down if permanent backtraces get used.
* if we ever go there for performance reasons,
* then we'll want to find another way to do this.
*/
for (uint32_t i = 0; i < btlh->btlh_count; i++) {
struct bt_hash_entry *bthe = &btlh->btlh_entries[i];
btref_t ref;
if (!bthe->bthe_addr) {
continue;
}
ref = __bt_ref(bthe->bthe_where);
bts = __btlib_deref(&bt_library, ref);
for (uint32_t j = 0; j < RECS; j++) {
if (ref == recs[j].btr_where) {
break;
}
if (bts->bts_ref_len > recs[j].btr_count) {
for (uint32_t k = j + 1; k < RECS; k++) {
recs[k] = recs[k - 1];
}
recs[j].btr_count = bts->bts_ref_len;
recs[j].btr_where = ref;
break;
}
}
}
/*
* Then correct what we sampled by counting how many times
* the backtrace _actually_ exists in that one log.
*/
for (uint32_t j = 0; j < RECS; j++) {
recs[j].btr_count = 0;
}
for (uint32_t i = 0; i < btlh->btlh_count; i++) {
struct bt_hash_entry *bthe = &btlh->btlh_entries[i];
btref_t ref;
if (!bthe->bthe_addr) {
continue;
}
ref = __bt_ref(bthe->bthe_where);
for (uint32_t j = 0; j < RECS; j++) {
if (recs[j].btr_where == ref) {
recs[j].btr_count++;
break;
}
}
}
for (uint32_t j = 1; j < RECS; j++) {
if (recs[0].btr_count < recs[j].btr_count) {
recs[0] = recs[j];
}
}
bts = __btlib_deref(&bt_library, recs[0].btr_where);
*len = __btstack_len(bts);
backtrace_unpack(BTP_KERN_OFFSET_32, (uintptr_t *)bt, BTLOG_MAX_DEPTH,
(uint8_t *)bts->bts_frames, sizeof(uint32_t) * *len);
disabled:
__btlog_unlock(btlu);
return recs[0].btr_count;
}
#if DEBUG || DEVELOPMENT
void
btlog_copy_backtraces_for_elements(
btlogu_t btlu,
vm_address_t *instances,
uint32_t *countp,
uint32_t elem_size,
leak_site_proc proc)
{
struct btlog_hash *btlh = btlu.btlh;
struct bt_hash_head *head;
uint32_t count = *countp;
uint32_t num_sites = 0;
if (btlu.btl->btl_type != BTLOG_HASH) {
return;
}
__btlog_lock(btlh);
if (btlu.btl->btl_disabled) {
goto disabled;
}
for (uint32_t i = 0; i < count; i++) {
vm_offset_t element = instances[i];
void *addr = __btlog_elem_normalize((void *)element);
btref_t ref = BTREF_NULL;
uint32_t pos;
if (kInstanceFlagReferenced & element) {
continue;
}
element = INSTANCE_PUT(element) & ~kInstanceFlags;
head = __btlog_hash_head(btlh, addr);
pos = head->bthh_first;
while (pos != BT_HASH_END_MARKER) {
struct bt_hash_entry *bthe = &btlh->btlh_entries[pos];
if (__btlog_elem_decode(bthe->bthe_addr) == addr) {
ref = __bt_ref(bthe->bthe_where);
break;
}
pos = bthe->bthe_next;
}
if (ref != BTREF_NULL) {
element = (ref | kInstanceFlagReferenced);
}
instances[num_sites++] = INSTANCE_PUT(element);
}
for (uint32_t i = 0; i < num_sites; i++) {
vm_offset_t btref = instances[i];
uint32_t site_count, dups;
if (!(btref & kInstanceFlagReferenced)) {
continue;
}
for (site_count = 1, dups = i + 1; dups < num_sites; dups++) {
if (instances[dups] == btref) {
site_count++;
instances[dups] = 0;
}
}
btref = INSTANCE_PUT(btref) & ~kInstanceFlags;
proc(site_count, elem_size, (btref_t)btref);
}
disabled:
__btlog_unlock(btlh);
*countp = num_sites;
}
#endif /* DEBUG || DEVELOPMENT */