#include <stddef.h>
#include <stdint.h>
#include <kern/assert.h>
#include <kern/backtrace.h>
#include <kern/thread.h>
#include <sys/errno.h>
#include <vm/vm_map.h>
#if defined(__arm__) || defined(__arm64__)
#include <arm/cpu_data.h>
#include <arm/cpu_data_internal.h>
#endif
#if defined(HAS_APPLE_PAC)
#include <ptrauth.h>
#endif
unsigned int __attribute__((noinline))
backtrace(uintptr_t *bt, unsigned int max_frames, bool *was_truncated_out)
{
return backtrace_frame(bt, max_frames, __builtin_frame_address(0),
was_truncated_out);
}
unsigned int __attribute__((noinline, not_tail_called))
backtrace_frame(uintptr_t *bt, unsigned int max_frames, void *start_frame,
bool *was_truncated_out)
{
thread_t thread = current_thread();
uintptr_t *fp;
unsigned int frame_index = 0;
uintptr_t top, bottom;
bool in_valid_stack;
assert(bt != NULL);
assert(max_frames > 0);
fp = start_frame;
bottom = thread->kernel_stack;
top = bottom + kernel_stack_size;
#define IN_STK_BOUNDS(__addr) \
(((uintptr_t)(__addr) >= (uintptr_t)bottom) && \
((uintptr_t)(__addr) < (uintptr_t)top))
in_valid_stack = IN_STK_BOUNDS(fp);
if (!in_valid_stack) {
fp = NULL;
}
while (fp != NULL && frame_index < max_frames) {
uintptr_t *next_fp = (uintptr_t *)*fp;
uintptr_t ret_addr = *(fp + 1);
in_valid_stack = IN_STK_BOUNDS(next_fp);
if (next_fp == NULL || !in_valid_stack) {
break;
}
#if defined(HAS_APPLE_PAC)
bt[frame_index++] = (uintptr_t) ptrauth_strip((void *)ret_addr, ptrauth_key_return_address);
#else
bt[frame_index++] = ret_addr;
#endif
if (next_fp <= fp) {
break;
}
fp = next_fp;
}
if (frame_index != max_frames) {
bt[frame_index] = 0;
}
if (fp != NULL && frame_index == max_frames && was_truncated_out) {
*was_truncated_out = true;
}
return frame_index;
#undef IN_STK_BOUNDS
}
#if defined(__x86_64__)
static kern_return_t
interrupted_kernel_pc_fp(uintptr_t *pc, uintptr_t *fp)
{
x86_saved_state_t *state;
bool state_64;
uint64_t cs;
state = current_cpu_datap()->cpu_int_state;
if (!state) {
return KERN_FAILURE;
}
state_64 = is_saved_state64(state);
if (state_64) {
cs = saved_state64(state)->isf.cs;
} else {
cs = saved_state32(state)->cs;
}
if ((cs & SEL_PL) == SEL_PL_U) {
return KERN_FAILURE;
}
if (state_64) {
*pc = saved_state64(state)->isf.rip;
*fp = saved_state64(state)->rbp;
} else {
*pc = saved_state32(state)->eip;
*fp = saved_state32(state)->ebp;
}
return KERN_SUCCESS;
}
#elif defined(__arm64__)
static kern_return_t
interrupted_kernel_pc_fp(uintptr_t *pc, uintptr_t *fp)
{
struct arm_saved_state *state;
bool state_64;
state = getCpuDatap()->cpu_int_state;
if (!state) {
return KERN_FAILURE;
}
state_64 = is_saved_state64(state);
if (PSR64_IS_USER(get_saved_state_cpsr(state))) {
return KERN_FAILURE;
}
*pc = get_saved_state_pc(state);
*fp = get_saved_state_fp(state);
return KERN_SUCCESS;
}
#elif defined(__arm__)
static kern_return_t
interrupted_kernel_pc_fp(uintptr_t *pc, uintptr_t *fp)
{
struct arm_saved_state *state;
state = getCpuDatap()->cpu_int_state;
if (!state) {
return KERN_FAILURE;
}
if (PSR_IS_USER(get_saved_state_cpsr(state))) {
return KERN_FAILURE;
}
*pc = get_saved_state_pc(state);
*fp = get_saved_state_fp(state);
return KERN_SUCCESS;
}
#else
#error "interrupted_kernel_pc_fp: unsupported architecture"
#endif
unsigned int
backtrace_interrupted(uintptr_t *bt, unsigned int max_frames,
bool *was_truncated_out)
{
uintptr_t pc;
uintptr_t fp;
kern_return_t kr;
assert(bt != NULL);
assert(max_frames > 0);
assert(ml_at_interrupt_context() == TRUE);
kr = interrupted_kernel_pc_fp(&pc, &fp);
if (kr != KERN_SUCCESS) {
return 0;
}
bt[0] = pc;
if (max_frames == 1) {
return 1;
}
return backtrace_frame(bt + 1, max_frames - 1, (void *)fp,
was_truncated_out) + 1;
}
int
backtrace_user(uintptr_t *bt, unsigned int max_frames,
unsigned int *frames_out, bool *user_64_out, bool *was_truncated_out)
{
return backtrace_thread_user(current_thread(), bt, max_frames,
frames_out, user_64_out, was_truncated_out);
}
int
backtrace_thread_user(void *thread, uintptr_t *bt, unsigned int max_frames,
unsigned int *frames_out, bool *user_64_out, bool *was_truncated_out)
{
bool user_64;
uintptr_t pc = 0, fp = 0, next_fp = 0;
vm_map_t map = NULL, old_map = NULL;
unsigned int frame_index = 0;
int err = 0;
size_t frame_size = 0;
assert(bt != NULL);
assert(max_frames > 0);
assert(frames_out != NULL);
#if defined(__x86_64__)
#define INVALID_USER_FP(FP) ((FP) == 0 || !IS_USERADDR64_CANONICAL((FP)))
x86_saved_state_t *state = get_user_regs(thread);
if (!state) {
return EINVAL;
}
user_64 = is_saved_state64(state);
if (user_64) {
pc = saved_state64(state)->isf.rip;
fp = saved_state64(state)->rbp;
} else {
pc = saved_state32(state)->eip;
fp = saved_state32(state)->ebp;
}
#elif defined(__arm64__)
#define INVALID_USER_FP(FP) ((FP) == 0 || ((FP) & 0x3UL) != 0UL)
struct arm_saved_state *state = get_user_regs(thread);
if (!state) {
return EINVAL;
}
user_64 = is_saved_state64(state);
pc = get_saved_state_pc(state);
fp = get_saved_state_fp(state);
#elif defined(__arm__)
#define INVALID_USER_FP(FP) ((FP) == 0 || ((FP) & 0x3UL) != 0UL)
struct arm_saved_state *state = get_user_regs(thread);
if (!state) {
return EINVAL;
}
user_64 = false;
pc = get_saved_state_pc(state);
fp = get_saved_state_fp(state);
#else
#error "backtrace_thread_user: unsupported architecture"
#endif
bt[frame_index++] = pc;
if (frame_index >= max_frames) {
goto out;
}
if (INVALID_USER_FP(fp)) {
goto out;
}
assert(ml_get_interrupts_enabled() == TRUE);
if (!ml_get_interrupts_enabled()) {
goto out;
}
union {
struct {
uint64_t fp;
uint64_t ret;
} u64;
struct {
uint32_t fp;
uint32_t ret;
} u32;
} frame;
frame_size = 2 * (user_64 ? 8 : 4);
if (thread != current_thread()) {
map = get_task_map_reference(get_threadtask(thread));
if (map == NULL) {
goto out;
}
old_map = vm_map_switch(map);
} else {
map = NULL;
}
while (fp != 0 && frame_index < max_frames) {
err = copyin(fp, (char *)&frame, frame_size);
if (err) {
if (was_truncated_out) {
*was_truncated_out = true;
}
goto out;
}
next_fp = user_64 ? frame.u64.fp : frame.u32.fp;
if (INVALID_USER_FP(next_fp)) {
break;
}
uintptr_t ret_addr = user_64 ? frame.u64.ret : frame.u32.ret;
#if defined(HAS_APPLE_PAC)
bt[frame_index++] = (uintptr_t)ptrauth_strip((void *)ret_addr,
ptrauth_key_return_address);
#else
bt[frame_index++] = ret_addr;
#endif
if (next_fp <= fp) {
break;
}
fp = next_fp;
}
out:
if (map) {
(void)vm_map_switch(old_map);
vm_map_deallocate(map);
}
if (frame_index != max_frames) {
bt[frame_index] = 0;
}
if (fp != 0 && frame_index == max_frames && was_truncated_out) {
*was_truncated_out = true;
}
if (user_64_out) {
*user_64_out = user_64;
}
*frames_out = frame_index;
return err;
#undef INVALID_USER_FP
}