561 lines
15 KiB
C
561 lines
15 KiB
C
/*
|
|
* Copyright (C) 2007-2010 Apple Inc. All rights reserved.
|
|
* Copyright (C) 2006 Apple Computer, Inc. All rights reserved.
|
|
*
|
|
* This document is the property of Apple Inc.
|
|
* It is considered confidential and proprietary.
|
|
*
|
|
* This document may not be reproduced or transmitted in any form,
|
|
* in whole or in part, without the express written permission of
|
|
* Apple Inc.
|
|
*/
|
|
#include <debug.h>
|
|
#include <arch.h>
|
|
#include <arch/arch_task.h>
|
|
#include <arch/arm/arm.h>
|
|
#include <sys.h>
|
|
#include <lib/libc.h>
|
|
|
|
extern void platform_irq(void);
|
|
extern void platform_fiq(void);
|
|
|
|
static const char *arm_abort_decode_status(unsigned int dfsr);
|
|
static const char *arm_decode_mode(unsigned int status_reg);
|
|
static void arm_exception_abort(struct arm_exception_frame *frame,
|
|
const char *kind,
|
|
unsigned int fsr,
|
|
unsigned int far) __noreturn;
|
|
|
|
static struct arm_exception_frame *arm_get_exception_frame(void) __pure;
|
|
static struct arm_exception_frame *arm_get_interrupt_frame(void) __pure;
|
|
static struct arm_exception_frame *arm_get_fiq_frame(void) __pure;
|
|
static void arm_print_frame_registers(struct arm_exception_frame *frame, const char *prefix);
|
|
static void arm_backtrace(const char *prefix, u_int32_t pc, u_int32_t fp, u_int32_t stack_base, u_int32_t stack_len);
|
|
static void arm_stack_for_status_reg(u_int32_t status_reg, u_int32_t *stack_base, u_int32_t *stack_len);
|
|
|
|
static bool exception_dump_near_pc;
|
|
|
|
#if WITH_MMU
|
|
static char status_str[32];
|
|
|
|
static const char *
|
|
arm_abort_decode_status(unsigned int fsr)
|
|
{
|
|
/* mask bit 10 & low 4 bits for cause decoding */
|
|
fsr &= FSR_CAUSE_MASK;
|
|
|
|
/* ARMv4v5 architecture reference B4-20 */
|
|
switch (fsr) {
|
|
case 0x01:
|
|
case 0x03:
|
|
return("alignment error");
|
|
case 0x04:
|
|
return("icache operation error");
|
|
case 0x0c:
|
|
case 0x0e:
|
|
return("external abort on translation");
|
|
case 0x05:
|
|
case 0x07:
|
|
return("translation error");
|
|
case 0x09:
|
|
case 0x0b:
|
|
return("domain violation");
|
|
case 0x0d:
|
|
case 0x0f:
|
|
return("permission violation");
|
|
case 0x08:
|
|
case 0x0a: /* this code is deprecated in ARMv7*/
|
|
return("precise external abort");
|
|
case 0x406:
|
|
return("imprecise external abort"); /* ARMv7 */
|
|
case 0x40a:
|
|
return("coprocessor data abort");
|
|
case 0x408:
|
|
return("data cache parity error"); /* ARMv7 */
|
|
case 0x409:
|
|
return("instruction cache parity error"); /* ARMv7 */
|
|
case 0x02:
|
|
return("debug event");
|
|
}
|
|
|
|
snprintf(status_str, sizeof(status_str), "<unknown cause 0x%03x>", fsr);
|
|
|
|
return(status_str);
|
|
}
|
|
#endif
|
|
|
|
static const char *
|
|
arm_decode_mode(unsigned int status_reg)
|
|
{
|
|
switch (CPSR_MODE(status_reg)) {
|
|
case CPSR_MODE_USER:
|
|
return("user");
|
|
case CPSR_MODE_FIQ:
|
|
return("FIQ");
|
|
case CPSR_MODE_IRQ:
|
|
return("IRQ");
|
|
case CPSR_MODE_SUPERVISOR:
|
|
return("supervisor");
|
|
case CPSR_MODE_ABORT:
|
|
return("abort");
|
|
case CPSR_MODE_UNDEFINED:
|
|
return("undefined");
|
|
case CPSR_MODE_SYSTEM:
|
|
return("system");
|
|
}
|
|
return("<unknown>");
|
|
}
|
|
|
|
void
|
|
arm_data_abort(struct arm_exception_frame *frame)
|
|
{
|
|
unsigned int fsr = arm_read_dfsr();
|
|
unsigned int far = arm_read_dfar();
|
|
|
|
frame->pc -= 8;
|
|
if (fsr == 0x408) {
|
|
arm_l1cache_dump(1, far);
|
|
}
|
|
arm_exception_abort(frame, "data abort", fsr, far);
|
|
}
|
|
|
|
void
|
|
arm_prefetch_abort(struct arm_exception_frame *frame)
|
|
{
|
|
unsigned int fsr = arm_read_ifsr();
|
|
unsigned int far = arm_read_ifar();
|
|
|
|
frame->pc -= 4;
|
|
if (fsr == 0x409) {
|
|
arm_l1cache_dump(0, far);
|
|
}
|
|
arm_exception_abort(frame, "prefetch abort", fsr, far);
|
|
}
|
|
|
|
void
|
|
arm_undefined(struct arm_exception_frame *frame)
|
|
{
|
|
exception_dump_near_pc = true;
|
|
frame->pc -= (frame->spsr & CPSR_STATE_THUMB) ? 2: 4;
|
|
arm_exception_abort(frame, "undefined instruction", ~0, frame->pc);
|
|
}
|
|
|
|
void
|
|
arm_syscall(struct arm_exception_frame *frame)
|
|
{
|
|
exception_dump_near_pc = true;
|
|
frame->pc -= (frame->spsr & CPSR_STATE_THUMB) ? 2: 4;
|
|
arm_exception_abort(frame, "syscall", arm_read_ifsr(), frame->pc);
|
|
}
|
|
|
|
static void
|
|
arm_exception_abort(struct arm_exception_frame *frame, const char *kind, unsigned int fsr, unsigned int far)
|
|
{
|
|
#if WITH_MMU
|
|
panic("ARM %s abort in %s mode at 0x%08x due to %s:\n"
|
|
" far 0x%08x fsr 0x%08x\n"
|
|
" r0 0x%08x 0x%08x 0x%08x 0x%08x\n"
|
|
" r4 0x%08x 0x%08x 0x%08x 0x%08x\n"
|
|
" r8 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x\n"
|
|
" sp 0x%08x lr 0x%08x spsr 0x%08x\n",
|
|
kind, arm_decode_mode(frame->spsr), frame->pc, arm_abort_decode_status(fsr), far, fsr,
|
|
frame->r[0], frame->r[1], frame->r[2], frame->r[3],
|
|
frame->r[4], frame->r[5], frame->r[6], frame->r[7],
|
|
frame->r[8], frame->r[9], frame->r[10], frame->r[11], frame->r[12],
|
|
frame->sp, frame->lr, frame->spsr);
|
|
#else
|
|
panic("ARM %s abort in %s mode at 0x%08x\n"
|
|
" r0 0x%08x 0x%08x 0x%08x 0x%08x\n"
|
|
" r4 0x%08x 0x%08x 0x%08x 0x%08x\n"
|
|
" r8 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x\n"
|
|
" sp 0x%08x lr 0x%08x spsr 0x%08x\n",
|
|
kind, arm_decode_mode(frame->spsr), frame->pc,
|
|
frame->r[0], frame->r[1], frame->r[2], frame->r[3],
|
|
frame->r[4], frame->r[5], frame->r[6], frame->r[7],
|
|
frame->r[8], frame->r[9], frame->r[10], frame->r[11], frame->r[12],
|
|
frame->sp, frame->lr, frame->spsr);
|
|
#endif
|
|
}
|
|
|
|
void
|
|
arm_irq() /* __attribute__ ((naked)) */
|
|
{
|
|
#if WITH_FIQ_TIMER
|
|
// Currently only defined on M7, M7 uses a FIQ for thetimer, and we need to hold off timer interrupts
|
|
// until we are done running ISR code to prevent unwanted preemption (e.g. <rdar://problem/16757466>)
|
|
arm_disable_fiqs();
|
|
#endif
|
|
_irq_enter_critical_section();
|
|
platform_irq();
|
|
_irq_exit_critical_section();
|
|
#if WITH_FIQ_TIMER
|
|
arm_enable_fiqs();
|
|
#endif
|
|
}
|
|
|
|
void
|
|
arm_fiq() /* __attribute__ ((naked)) */
|
|
{
|
|
_irq_enter_critical_section();
|
|
platform_fiq();
|
|
_irq_exit_critical_section();
|
|
}
|
|
|
|
/*
|
|
* Accessors for exception frame information.
|
|
*
|
|
* These may return NULL if we don't believe the frame requested is currently valid.
|
|
*
|
|
* Note that not all possible cases of nesting are necessarily handled here; only those
|
|
* that are likely to be interesting in the case of a fatality.
|
|
*/
|
|
static struct arm_exception_frame *
|
|
arm_get_exception_frame(void)
|
|
{
|
|
/*
|
|
* As exceptions are considered fatal, we don't expect to be asked for
|
|
* the exception frame in any other context.
|
|
*/
|
|
switch (CPSR_MODE(arm_read_cpsr())) {
|
|
case CPSR_MODE_ABORT:
|
|
case CPSR_MODE_UNDEFINED:
|
|
return((struct arm_exception_frame *)exc_stack_top - 1);
|
|
default:
|
|
return(NULL);
|
|
}
|
|
}
|
|
|
|
static struct arm_exception_frame *
|
|
arm_get_interrupt_frame(void)
|
|
{
|
|
switch (CPSR_MODE(arm_read_cpsr())) {
|
|
case CPSR_MODE_ABORT:
|
|
case CPSR_MODE_UNDEFINED:
|
|
/*
|
|
* In some configurations the IRQ and exception stacks share a top,
|
|
* in which case we can't return the IRQ frame because the exception
|
|
* frame has overwritten it.
|
|
*/
|
|
if (&irq_stack_top == &exc_stack_top)
|
|
return(NULL);
|
|
|
|
/*
|
|
* If we took an exception while servicing an interrupt, the IRQ frame
|
|
* will be valid.
|
|
*/
|
|
if (CPSR_MODE_IRQ != arm_get_exception_frame()->spsr)
|
|
return(NULL);
|
|
break;
|
|
|
|
case CPSR_MODE_IRQ:
|
|
/* we're in interrupt mode, so the frame is valid */
|
|
break;
|
|
|
|
case CPSR_MODE_FIQ:
|
|
/*
|
|
* If we took a FIQ while in interrupt mode, the IRQ frame will be
|
|
* valid.
|
|
*/
|
|
if (CPSR_MODE_IRQ != CPSR_MODE(arm_get_fiq_frame()->spsr))
|
|
return(NULL);
|
|
break;
|
|
|
|
default:
|
|
/* the interrupt frame is invalid in other modes */
|
|
return(NULL);
|
|
}
|
|
return ((struct arm_exception_frame *)irq_stack_top - 1);
|
|
}
|
|
|
|
static struct arm_exception_frame *
|
|
arm_get_fiq_frame(void)
|
|
{
|
|
switch (CPSR_MODE(arm_read_cpsr())) {
|
|
case CPSR_MODE_ABORT:
|
|
case CPSR_MODE_UNDEFINED:
|
|
/*
|
|
* If we took an exception while servicing a FIQ, the frame
|
|
* will be valid.
|
|
*/
|
|
if (CPSR_MODE_FIQ != CPSR_MODE(arm_get_exception_frame()->spsr))
|
|
return(NULL);
|
|
break;
|
|
|
|
case CPSR_MODE_FIQ:
|
|
/* we're in FIQ mode, so the frame is valid */
|
|
break;
|
|
|
|
default:
|
|
/* the FIQ frame is invalid in other modes */
|
|
return(NULL);
|
|
}
|
|
return((struct arm_exception_frame *)fiq_stack_top - 1);
|
|
}
|
|
|
|
/*
|
|
* arm_print_frame_registers
|
|
*
|
|
* Print the registers from an exception frame.
|
|
*/
|
|
static void
|
|
arm_print_frame_registers(struct arm_exception_frame *frame, const char *prefix)
|
|
{
|
|
dprintf(DEBUG_CRITICAL,
|
|
"%sr0 0x%08x 0x%08x 0x%08x 0x%08x\n"
|
|
"%sr4 0x%08x 0x%08x 0x%08x 0x%08x\n"
|
|
"%sr8 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x\n"
|
|
"%spc 0x%08x sp 0x%08x lr 0x%08x spsr 0x%08x\n",
|
|
prefix, frame->r[0], frame->r[1], frame->r[2], frame->r[3],
|
|
prefix, frame->r[4], frame->r[5], frame->r[6], frame->r[7],
|
|
prefix, frame->r[8], frame->r[9], frame->r[10], frame->r[11], frame->r[12],
|
|
prefix, frame->pc, frame->sp, frame->lr, frame->spsr);
|
|
}
|
|
|
|
/*
|
|
* arm_backtrace
|
|
*
|
|
* Print a backtrace until we run out of frames on the stack.
|
|
*/
|
|
static void
|
|
arm_backtrace(const char *prefix, u_int32_t pc, u_int32_t fp, u_int32_t stack_base, u_int32_t stack_len)
|
|
{
|
|
u_int32_t prev_fp;
|
|
u_int32_t stack_top;
|
|
u_int32_t *fpp;
|
|
|
|
/* print the mode prefix and current pc for starters */
|
|
dprintf(DEBUG_CRITICAL, " %10s 0x%08x 0x%08x\n", prefix, pc, fp);
|
|
|
|
/* sanity check our arguments as much as we can */
|
|
if ((stack_base % 4) || /* bump these to CPU_CACHELINE_SIZE if/when stack allocation gets smarter */
|
|
(stack_len % 4) ||
|
|
(stack_len < 16) ||
|
|
(stack_len > 100 * 1024)) {
|
|
dprintf(DEBUG_CRITICAL, "improbable stack, base %p size 0x%08x (task smashed?)\n", (void *)stack_base, stack_len);
|
|
return;
|
|
}
|
|
|
|
/* loop unwinding frames until we leave the stack region */
|
|
prev_fp = stack_base;
|
|
stack_top = stack_base + stack_len;
|
|
for (;;) {
|
|
/*
|
|
* Frame pointer must be aligned, must be above the previous frame, must not point outside the stack.
|
|
*
|
|
* It would be nice to have a sentinel to allow differentiating clean termination from a smashed
|
|
* stack, but that would require support in the interrupt and exception handlers.
|
|
*/
|
|
if ((fp % 4) || (fp <= prev_fp) || (fp > stack_top))
|
|
return;
|
|
|
|
/*
|
|
* Looks like it's inside the stack, so go ahead and deref it.
|
|
*/
|
|
fpp = (u_int32_t *)fp;
|
|
prev_fp = fp;
|
|
fp = fpp[0];
|
|
|
|
/*
|
|
* Print the return address for this frame.
|
|
*/
|
|
dprintf(DEBUG_CRITICAL, " 0x%08x 0x%08x\n", fpp[1], fp);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* arm_stack_for_status_reg
|
|
*
|
|
* Returns the base and size of the stack for the given status register value if it's "special".
|
|
*/
|
|
static void
|
|
arm_stack_for_status_reg(u_int32_t status_reg, u_int32_t *stack_base, u_int32_t *stack_len)
|
|
{
|
|
*stack_base = 0;
|
|
*stack_len = 0;
|
|
|
|
#ifdef STACK_BASE
|
|
*stack_base = (void *)STACK_BASE;
|
|
switch (CPSR_MODE(status_reg)) {
|
|
case CPSR_MODE_FIQ:
|
|
*stack_len = FIQ_STACK_SIZE;
|
|
break;
|
|
case CPSR_MODE_IRQ:
|
|
*stack_len = IRQ_STACK_SIZE;
|
|
break;
|
|
case CPSR_MODE_ABORT:
|
|
case CPSR_MODE_UNDEFINED:
|
|
*stack_len = EXCEPTION_STACK_SIZE;
|
|
break;
|
|
|
|
}
|
|
#else
|
|
switch (CPSR_MODE(status_reg)) {
|
|
case CPSR_MODE_FIQ:
|
|
*stack_base = (u_int32_t)fiq_stack_top - FIQ_STACK_SIZE;
|
|
*stack_len = FIQ_STACK_SIZE;
|
|
break;
|
|
case CPSR_MODE_IRQ:
|
|
*stack_base = (u_int32_t)irq_stack_top - IRQ_STACK_SIZE;
|
|
*stack_len = IRQ_STACK_SIZE;
|
|
break;
|
|
case CPSR_MODE_ABORT:
|
|
case CPSR_MODE_UNDEFINED:
|
|
*stack_base = (u_int32_t)exc_stack_top - EXCEPTION_STACK_SIZE;
|
|
*stack_len = EXCEPTION_STACK_SIZE;
|
|
break;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* arch_backtrace_current_task
|
|
*
|
|
* Print a backtrace for the current task.
|
|
*
|
|
* We support being called with the following context nesting:
|
|
*
|
|
* supervisor
|
|
* supervisor interrupt
|
|
* supervisor FIQ
|
|
* supervisor exception
|
|
* supervisor interrupt FIQ
|
|
* supervisor interrupt exception
|
|
* supervisor FIQ exception
|
|
* supervisor interrupt FIQ exception
|
|
*
|
|
*/
|
|
|
|
void
|
|
arch_backtrace_current_task(void *stack_base, size_t stack_len)
|
|
{
|
|
struct arm_exception_frame *exception_frame, *fiq_frame, *interrupt_frame, *outer_frame;
|
|
u_int32_t esb, esl;
|
|
u_int32_t base, count, *insn, offending_pc;
|
|
|
|
uintptr_t btext = (uintptr_t)__segment_start(__TEXT);
|
|
uintptr_t etext = (uintptr_t)__segment_end(__TEXT);
|
|
|
|
/*
|
|
* Find the stack limits for the current context and then backtrace it.
|
|
*/
|
|
arm_stack_for_status_reg(arm_read_cpsr(), &esb, &esl);
|
|
if (0 == esl) {
|
|
esb = (u_int32_t)stack_base;
|
|
esl = stack_len;
|
|
}
|
|
arm_backtrace(arm_decode_mode(arm_read_cpsr()),
|
|
(u_int32_t)&arch_backtrace_current_task, (u_int32_t)__builtin_frame_address(0),
|
|
esb, esl);
|
|
|
|
/*
|
|
* Find valid saved contexts, for the case where we may be in an exception state.
|
|
*/
|
|
exception_frame = arm_get_exception_frame();
|
|
fiq_frame = arm_get_fiq_frame();
|
|
interrupt_frame = arm_get_interrupt_frame();
|
|
outer_frame = NULL;
|
|
|
|
/*
|
|
* If we have a valid exception context, backtrace the context it was called from first.
|
|
* That context may be supervisor, FIQ or interrupt mode. If it was FIQ mode, we will
|
|
* dump the context the FIQ was called from next.
|
|
*/
|
|
if (NULL != exception_frame) {
|
|
|
|
/* dump registers at entry to exception mode */
|
|
arm_print_frame_registers(exception_frame, " ");
|
|
|
|
/*
|
|
* If the exception handler thought that the data around the PC was worth
|
|
* displaying, and we think the PC was inside the text section, display it here.
|
|
*/
|
|
if (true == exception_dump_near_pc) {
|
|
offending_pc = exception_frame->pc;
|
|
if ((offending_pc >= btext) && (offending_pc <= etext)) {
|
|
if ((offending_pc - btext) < 8) {
|
|
base = btext;
|
|
} else {
|
|
base = offending_pc - 8;
|
|
}
|
|
dprintf(DEBUG_CRITICAL, " [0x%08x:", base);
|
|
for (count = 0; count < 4; count++) {
|
|
insn = (u_int32_t *)base + count;
|
|
dprintf(DEBUG_CRITICAL, " 0x%08x", *insn);
|
|
}
|
|
dprintf(DEBUG_CRITICAL, "]\n");
|
|
} else {
|
|
dprintf(DEBUG_CRITICAL, " [pc 0x%08x not in text %p-%p]\n",
|
|
offending_pc, (void *)btext, (void *)etext);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If the exception occurred in a context with a special stack (FIQ or interrupt)
|
|
* backtrace that stack here.
|
|
*/
|
|
arm_stack_for_status_reg(exception_frame->spsr, &esb, &esl);
|
|
if (0 != esl) {
|
|
arm_backtrace(arm_decode_mode(exception_frame->spsr),
|
|
exception_frame->pc, exception_frame->r[7],
|
|
esb, esl);
|
|
} else {
|
|
/* the outer context is in the exception frame */
|
|
outer_frame = exception_frame;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If we have a valid FIQ context, backtrace the context it was invoked from now.
|
|
*/
|
|
if (NULL != fiq_frame) {
|
|
|
|
/* dump registers at entry to FIQ mode */
|
|
arm_print_frame_registers(fiq_frame, " ");
|
|
|
|
/*
|
|
* If the FIQ occurred from interrupt mode, backtrace the interrupt mode stack here.
|
|
*/
|
|
arm_stack_for_status_reg(fiq_frame->spsr, &esb, &esl);
|
|
if (0 != esl) {
|
|
arm_backtrace(arm_decode_mode(fiq_frame->spsr),
|
|
fiq_frame->pc, fiq_frame->r[7],
|
|
esb, esl);
|
|
} else {
|
|
/* the outer context is in the FIQ frame */
|
|
outer_frame = fiq_frame;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If we have an interrupt-saved context, fetch the task state from it.
|
|
*/
|
|
if (NULL != interrupt_frame) {
|
|
|
|
/* dump registers at entry to interrupt mode */
|
|
arm_print_frame_registers(interrupt_frame, " ");
|
|
|
|
outer_frame = interrupt_frame;
|
|
}
|
|
|
|
/*
|
|
* Print the task context backtrace from the outer frame.
|
|
*/
|
|
if (NULL != outer_frame) {
|
|
arm_backtrace(arm_decode_mode(outer_frame->spsr),
|
|
outer_frame->pc, outer_frame->r[7],
|
|
(u_int32_t)stack_base, (u_int32_t)stack_len);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* arch_backtrace_task
|
|
*
|
|
* Print a backtrace from a saved task context.
|
|
*/
|
|
void
|
|
arch_backtrace_task(struct arch_task *arch_task, void *stack_base, size_t stack_len)
|
|
{
|
|
arm_backtrace("supervisor", arch_task->regs[ARM_ARCH_TASK_LR], arch_task->regs[ARM_ARCH_TASK_FP],
|
|
(u_int32_t)stack_base, (u_int32_t)stack_len);
|
|
}
|