blob: fe4e39d4c3162e5240e6d03e462a97b61197bd05 [file] [edit]
// Copyright 2016 The Fuchsia Authors
// Copyright (c) 2014 Travis Geiselbrecht
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include <bits.h>
#include <debug.h>
#include <inttypes.h>
#include <lib/arch/arm64/system.h>
#include <lib/arch/intrin.h>
#include <lib/counters.h>
#include <lib/crashlog.h>
#include <platform.h>
#include <stdio.h>
#include <trace.h>
#include <zircon/syscalls/exception.h>
#include <zircon/types.h>
#include <arch/arch_ops.h>
#include <arch/arm64.h>
#include <arch/arm64/uarch.h>
#include <arch/crashlog_regs.h>
#include <arch/exception.h>
#include <arch/regs.h>
#include <arch/thread.h>
#include <arch/user_copy.h>
#include <kernel/interrupt.h>
#include <kernel/thread.h>
#include <pretty/hexdump.h>
#include <vm/fault.h>
#include <vm/vm.h>
#define LOCAL_TRACE 0
namespace {
constexpr uint32_t DFSC_ALIGNMENT_FAULT = 0b100001;
using ESRExceptionClass = ::arch::ArmExceptionSyndromeRegister::ExceptionClass;
uint64_t kernel_addr_from_dfr(uint64_t dfr) {
// Assert that the DFR is a valid kernel address by checking that the bit before the
// ARM64_DFR_RUN_ACCESS_FAULT_HANDLER_BIT (which is before the ARM64_DFR_RUN_FAULT_HANDLER_BIT)
// is a 1.
DEBUG_ASSERT(BIT_SET(dfr, ARM64_DFR_RUN_ACCESS_FAULT_HANDLER_BIT - 1));
return (dfr | (1ull << ARM64_DFR_RUN_ACCESS_FAULT_HANDLER_BIT) |
(1ull << ARM64_DFR_RUN_FAULT_HANDLER_BIT));
}
// clang-format off
const char* dfsc_to_string(uint32_t dfsc) {
switch (dfsc) {
case 0b000000: return "Address Size Fault, Level 0";
case 0b000001: return "Address Size Fault, Level 1";
case 0b000010: return "Address Size Fault, Level 2";
case 0b000011: return "Address Size Fault, Level 3";
case 0b000100: return "Translation Fault, Level 0";
case 0b000101: return "Translation Fault, Level 1";
case 0b000110: return "Translation Fault, Level 2";
case 0b000111: return "Translation Fault, Level 3";
case 0b001001: return "Access Flag Fault, Level 1";
case 0b001010: return "Access Flag Fault, Level 2";
case 0b001011: return "Access Flag Fault, Level 3";
case 0b001101: return "Permission Fault, Level 1";
case 0b001110: return "Permission Fault, Level 2";
case 0b001111: return "Permission Fault, Level 3";
case 0b010000: return "Synchronous External Abort";
case 0b010001: return "Synchronous Tag Check Fail";
case 0b010100: return "Synchronous External Abort, Level 0";
case 0b010101: return "Synchronous External Abort, Level 1";
case 0b010110: return "Synchronous External Abort, Level 2";
case 0b010111: return "Synchronous External Abort, Level 3";
case 0b011000: return "Synchronous Parity or ECC Abort";
case 0b011100: return "Synchronous Parity or ECC Abort, Level 0";
case 0b011101: return "Synchronous Parity or ECC Abort, Level 1";
case 0b011110: return "Synchronous Parity or ECC Abort, Level 2";
case 0b011111: return "Synchronous Parity or ECC Abort, Level 3";
case 0b100001: return "Alignment Fault";
case 0b110000: return "TLB Conflict Abort";
case 0b110100: return "Implementation Defined, Lockdown";
case 0b110101: return "Implementation Defined, Unsupported exclusive or atomic";
case 0b111101: return "Section Domain Fault";
case 0b111110: return "Page Domain Fault";
default: return "Unknown";
}
}
// clang-format on
// Faulting Virtual Address for synchronous exceptions taken to EL1. Exceptions that
// set the FAR_EL1 are Instruction Aborts (EC 0x20 or 0x21), Data Aborts (EC 0x24 or
// 0x25), PC alignment faults (EC 0x22), and Watchpoints (EC 0x34 or 0x35).
// ESR_EL1.EC holds the EC value for the exception.
bool exception_sets_far(ESRExceptionClass ec) {
switch (ec) {
case ESRExceptionClass::kInstructionAbortLowerEl:
case ESRExceptionClass::kInstructionAbortSameEl:
case ESRExceptionClass::kDataAbortLowerEl:
case ESRExceptionClass::kDataAbortSameEl:
case ESRExceptionClass::kPcAlignment:
case ESRExceptionClass::kWatchpointLowerEl:
case ESRExceptionClass::kWatchpointSameEl:
return true;
default:
return false;
}
}
KCOUNTER(exceptions_brkpt, "exceptions.breakpoint")
KCOUNTER(exceptions_hw_brkpt, "exceptions.hw_breakpoint")
KCOUNTER(exceptions_hw_wp, "exceptions.hw_watchpoint")
KCOUNTER(exceptions_fpu, "exceptions.fpu")
KCOUNTER(exceptions_page, "exceptions.page_fault")
KCOUNTER(exceptions_irq, "exceptions.irq")
KCOUNTER(exceptions_unhandled, "exceptions.unhandled")
KCOUNTER(exceptions_user, "exceptions.user")
KCOUNTER(exceptions_unknown, "exceptions.unknown")
KCOUNTER(exceptions_access, "exceptions.access_fault")
KCOUNTER(exceptions_serror, "exceptions.serror")
KCOUNTER(exceptions_mops, "exceptions.mops")
zx_status_t try_dispatch_user_data_fault_exception(zx_excp_type_t type, iframe_t* iframe,
uint32_t esr, uint64_t far,
uint32_t error_code) {
arch_exception_context_t context = {};
DEBUG_ASSERT(iframe != nullptr);
context.frame = iframe;
context.esr = esr;
context.far = far;
context.user_synth_code = error_code;
context.user_synth_data = 0;
arch_enable_ints();
zx_status_t status = dispatch_user_exception(type, &context);
arch_disable_ints();
return status;
}
// Must be called with interrupts disabled from exception entry.
zx_status_t try_dispatch_user_exception(zx_excp_type_t type, iframe_t* iframe, uint32_t esr) {
auto esr_reg = arch::ArmExceptionSyndromeRegister::Get().FromValue(esr);
static_assert(sizeof(esr_reg) <= sizeof(uint64_t) * 4);
uint64_t maybe_far = exception_sets_far(esr_reg.ec()) ? __arm_rsr64("far_el1") : 0;
return try_dispatch_user_data_fault_exception(type, iframe, esr, maybe_far, 0);
}
// Prints exception details and then panics.
[[noreturn]] void exception_die(iframe_t* iframe, uint32_t esr, uint64_t far, const char* format,
...) {
platform_panic_start();
va_list args;
va_start(args, format);
vprintf(format, args);
va_end(args);
uint32_t ec = BITS_SHIFT(esr, 31, 26);
uint32_t il = BIT(esr, 25);
uint32_t iss = BITS(esr, 24, 0);
/* fatal exception, die here */
printf("ESR %#x: ec %#x, il %#x, iss %#x\n", esr, ec, il, iss);
printf("FAR %#" PRIx64 "\n", far);
PrintFrame(*iframe);
g_crashlog.regs.iframe = iframe;
g_crashlog.regs.esr = esr;
g_crashlog.regs.far = far;
g_crashlog.regs.tpidr_el0 = __arm_rsr64("tpidr_el0");
g_crashlog.regs.tpidr_el1 = __arm_rsr64("tpidr_el1");
g_crashlog.regs.tpidrro_el0 = __arm_rsr64("tpidrro_el0");
platform_halt(HALT_ACTION_HALT, ZirconCrashReason::Panic);
}
void arm64_unknown_handler(iframe_t* iframe, uint exception_flags, uint32_t esr) {
/* this is for a lot of reasons, but most of them are undefined instructions */
if (unlikely((exception_flags & ARM64_EXCEPTION_FLAG_LOWER_EL) == 0)) {
/* trapped inside the kernel, this is bad */
exception_die(iframe, esr, __arm_rsr64("far_el1"),
"unknown exception in kernel: PC at %#" PRIx64 "\n", iframe->elr);
}
try_dispatch_user_exception(ZX_EXCP_UNDEFINED_INSTRUCTION, iframe, esr);
}
void arm64_mops_handler(iframe_t* iframe, uint exception_flags, uint32_t esr) {
// This means the PC and the PSTATE don't agree. It's not an undefined
// instruction but it's an illegal instruction.
if (unlikely((exception_flags & ARM64_EXCEPTION_FLAG_LOWER_EL) == 0)) {
/* trapped inside the kernel, this is bad */
exception_die(iframe, esr, __arm_rsr64("far_el1"),
"FEAT_MOPS exception in kernel: PC at %#" PRIx64 "\n", iframe->elr);
}
try_dispatch_user_exception(ZX_EXCP_UNDEFINED_INSTRUCTION, iframe, esr);
}
void arm64_brk_handler(iframe_t* iframe, uint exception_flags, uint32_t esr) {
if (unlikely((exception_flags & ARM64_EXCEPTION_FLAG_LOWER_EL) == 0)) {
/* trapped inside the kernel, this is bad */
exception_die(iframe, esr, __arm_rsr64("far_el1"), "BRK in kernel: PC at %#" PRIx64 "\n",
iframe->elr);
}
// Spectre V2: If we took a BRK exception from EL0, but the ELR address is not a user address,
// invalidate the branch predictor. User code may be attempting to mistrain indirect branch
// prediction structures.
if (unlikely(!is_user_accessible(iframe->elr)) &&
READ_PERCPU_FIELD(should_invalidate_bp_on_el0_exception)) {
arm64_uarch_do_spectre_v2_mitigation();
}
try_dispatch_user_exception(ZX_EXCP_SW_BREAKPOINT, iframe, esr);
}
void arm64_pc_alignment_fault_handler(iframe_t* iframe, uint exception_flags, uint32_t esr) {
if (unlikely((exception_flags & ARM64_EXCEPTION_FLAG_LOWER_EL) == 0)) {
/* trapped inside the kernel, this is bad */
exception_die(iframe, esr, __arm_rsr64("far_el1"),
"PC alignment fault in kernel: PC at %#" PRIx64 "\n", iframe->elr);
}
try_dispatch_user_exception(ZX_EXCP_UNALIGNED_ACCESS, iframe, esr);
}
void arm64_hw_breakpoint_exception_handler(iframe_t* iframe, uint exception_flags, uint32_t esr) {
if (unlikely((exception_flags & ARM64_EXCEPTION_FLAG_LOWER_EL) == 0)) {
/* trapped inside the kernel, this is bad */
exception_die(iframe, esr, __arm_rsr64("far_el1"),
"HW breakpoint in kernel: PC at %#" PRIx64 "\n", iframe->elr);
}
// We don't need to save the debug state because it doesn't change by an exception. The only
// way to change the debug state is through the thread write syscall.
// NOTE: ARM64 Doesn't provide a good way to comunicate exception status (without exposing ESR
// to userspace). This means a debugger will have to compare the registers with the PC
// on the exceptions to find out which breakpoint triggered the exception.
try_dispatch_user_exception(ZX_EXCP_HW_BREAKPOINT, iframe, esr);
}
void arm64_watchpoint_exception_handler(iframe_t* iframe, uint exception_flags, uint32_t esr) {
// Arm64 uses the Fault Address Register to determine which watchpoint triggered the exception.
uint64_t far = __arm_rsr64("far_el1");
if (unlikely((exception_flags & ARM64_EXCEPTION_FLAG_LOWER_EL) == 0)) {
/* trapped inside the kernel, this is bad */
exception_die(iframe, esr, far, "Watchpoint in kernel: PC at %#" PRIx64 "\n", iframe->elr);
}
// We don't need to save the debug state because it doesn't change by an exception. The only
// way to change the debug state is through the thread write syscall.
try_dispatch_user_data_fault_exception(ZX_EXCP_HW_BREAKPOINT, iframe, esr, far, 0);
}
void arm64_step_handler(iframe_t* iframe, uint exception_flags, uint32_t esr) {
if (unlikely((exception_flags & ARM64_EXCEPTION_FLAG_LOWER_EL) == 0)) {
/* trapped inside the kernel, this is bad */
exception_die(iframe, esr, __arm_rsr64("far_el1"),
"software step in kernel: PC at %#" PRIx64 "\n", iframe->elr);
}
// TODO(https://fxbug.dev/42108005): Is it worth separating this into two separate exceptions?
try_dispatch_user_exception(ZX_EXCP_HW_BREAKPOINT, iframe, esr);
}
void arm64_fpu_handler(iframe_t* iframe, uint exception_flags, uint32_t esr) {
if (unlikely((exception_flags & ARM64_EXCEPTION_FLAG_LOWER_EL) == 0)) {
/* we trapped a floating point instruction inside our own EL, this is bad */
exception_die(iframe, esr, __arm_rsr64("far_el1"),
"invalid fpu use in kernel: PC at %#" PRIx64 "\n", iframe->elr);
}
exception_die(iframe, esr, __arm_rsr64("far_el1"), "unhandled fpu exception\n");
}
void arm64_instruction_abort_handler(iframe_t* iframe, uint exception_flags, uint32_t esr) {
uint64_t far = __arm_rsr64("far_el1");
uint32_t ec = BITS_SHIFT(esr, 31, 26);
uint32_t iss = BITS(esr, 24, 0);
uint32_t dfsc = BITS(iss, 5, 0);
bool is_user = !BIT(ec, 0);
// 0b0010XX is access faults.
bool is_access = (dfsc & 0b111100) == 0b001000;
// 0b0001XX is translation faults.
bool is_translation = (dfsc & 0b111100) == 0b000100;
if (unlikely(!is_user)) {
// Any instruction page fault in kernel mode is a bug.
exception_die(iframe, esr, far, "instruction abort in kernel mode\n");
}
// Strip any tag that might be present on the FAR.
far = arch_detag_ptr(far);
// Spectre V2: If we took an instruction abort in EL0 but the faulting address is not a user
// address, invalidate the branch predictor. The $PC may have been updated before the abort is
// delivered, user code may be attempting to mistrain indirect branch prediction structures.
if (unlikely(is_user && !is_user_accessible(far)) &&
READ_PERCPU_FIELD(should_invalidate_bp_on_el0_exception)) {
arm64_uarch_do_spectre_v2_mitigation();
}
uint pf_flags = VMM_PF_FLAG_INSTRUCTION;
pf_flags |= is_user ? VMM_PF_FLAG_USER : 0;
pf_flags |= is_translation ? VMM_PF_FLAG_NOT_PRESENT : 0;
pf_flags |= is_access ? VMM_PF_FLAG_ACCESS : 0;
LTRACEF("instruction abort: PC at %#" PRIx64 ", is_user %d, FAR %" PRIx64 ", esr %#x, iss %#x\n",
iframe->elr, is_user, far, esr, iss);
DEBUG_ASSERT(arch_num_spinlocks_held() == 0);
arch_enable_ints();
zx_status_t err;
// Check for accessed fault and update the counters accordingly.
if (is_access) {
DEBUG_ASSERT((pf_flags & VMM_PF_FLAG_ACCESS) != 0);
kcounter_add(exceptions_access, 1);
} else {
kcounter_add(exceptions_page, 1);
}
err = vmm_page_fault_handler(far, pf_flags);
arch_disable_ints();
if (err == ZX_OK) {
return;
}
// If this is from user space, let the user exception handler
// get a shot at it.
if (is_user) {
kcounter_add(exceptions_user, 1);
if (try_dispatch_user_data_fault_exception(ZX_EXCP_FATAL_PAGE_FAULT, iframe, esr, far,
static_cast<uint32_t>(err)) == ZX_OK) {
return;
}
}
exception_die(iframe, esr, far,
"instruction abort: PC at %#" PRIx64 ", is_user %d, FAR %" PRIx64 "\n", iframe->elr,
is_user, far);
}
void arm64_data_abort_handler(iframe_t* iframe, uint exception_flags, uint32_t esr) {
/* read the FAR register */
uint64_t far = __arm_rsr64("far_el1");
uint32_t ec = BITS_SHIFT(esr, 31, 26);
uint32_t iss = BITS(esr, 24, 0);
uint32_t dfsc = BITS(iss, 5, 0);
bool is_user = !BIT(ec, 0);
// 0b0010XX is access faults.
bool is_access = (dfsc & 0b111100) == 0b001000;
// 0b0001XX is translation faults.
bool is_translation = (dfsc & 0b111100) == 0b000100;
bool WnR = BIT(iss, 6); // Write not Read
bool CM = BIT(iss, 8); // cache maintenance op
uint pf_flags = 0;
// if it was marked Write but the cache maintenance bit was set, treat it as read
pf_flags |= (WnR && !CM) ? VMM_PF_FLAG_WRITE : 0;
pf_flags |= is_user ? VMM_PF_FLAG_USER : 0;
pf_flags |= is_translation ? VMM_PF_FLAG_NOT_PRESENT : 0;
pf_flags |= is_access ? VMM_PF_FLAG_ACCESS : 0;
LTRACEF("data fault: PC at %#" PRIx64 ", is_user %d, FAR %#" PRIx64 ", esr %#x, iss %#x\n",
iframe->elr, is_user, far, esr, iss);
uint64_t dfr = Thread::Current::Get()->arch().data_fault_resume;
if (unlikely(!is_user) && unlikely(!dfr)) {
// Any page fault in kernel mode that's not during user-copy is a bug.
exception_die(iframe, esr, far, "data abort in kernel mode\n");
}
// Check if we want to capture this fault.
bool capture_fault = false;
if (unlikely(dfr)) {
if (unlikely(!BIT_SET(dfr, ARM64_DFR_RUN_ACCESS_FAULT_HANDLER_BIT))) {
// This function does not support capturing only access faults. In other words, if the caller
// wants to capture access faults, they must also capture page faults, so we assert that
// here.
DEBUG_ASSERT(!BIT_SET(dfr, ARM64_DFR_RUN_FAULT_HANDLER_BIT));
capture_fault = true;
} else if (unlikely(!BIT_SET(dfr, ARM64_DFR_RUN_FAULT_HANDLER_BIT))) {
// If the RUN_FAULT_HANDLER_BIT is not set, then we only want to capture this fault if it is
// _not_ an access fault.
capture_fault = !is_access;
}
}
// Only invoke the page fault handler for access, translation, and permission faults. Any other
// kind of fault cannot be resolved by the handler.
// 0b0010XX is access faults.
// 0b0001XX is translation faults.
// 0b0011XX is permission faults.
const bool invoke_page_fault_handler = (dfsc & 0b001100) != 0 && (dfsc & 0b110000) == 0;
if (capture_fault) {
iframe->elr = kernel_addr_from_dfr(dfr);
// Need to inform the user_copy routine whether this is a fault that can be handled by the page
// fault handler or not, which is done by encoding a different status code.
if (likely(invoke_page_fault_handler)) {
// The user_copy routine expects the status and the flags combined into the low and high parts
// of x0 respectively, and since status values are negative we need to construct this
// carefully to control the sign extensions.
iframe->r[0] = static_cast<uint64_t>(static_cast<uint32_t>(ZX_ERR_INVALID_ARGS)) |
(static_cast<uint64_t>(pf_flags) << 32);
// TODO(https://fxbug.dev/42175395): x1 is relayed back to user_copy where it will be stored
// in page fault info. Currently, the only users of this page fault info is
// VmAspace::SoftFault, but the kernel page fault handler shouldn't accept/work with tags. To
// avoid architecture-specific tags reaching the VM layer at all, we can strip it here so it
// never reaches user_copy page fault results.
iframe->r[1] = arch_detag_ptr(far);
} else {
iframe->r[0] = ZX_ERR_BAD_STATE;
}
return;
}
zx_status_t err = ZX_OK;
if (likely(invoke_page_fault_handler)) {
if (is_access) {
DEBUG_ASSERT((pf_flags & VMM_PF_FLAG_ACCESS) != 0);
kcounter_add(exceptions_access, 1);
} else {
kcounter_add(exceptions_page, 1);
}
DEBUG_ASSERT(arch_num_spinlocks_held() == 0);
arch_enable_ints();
err = vmm_page_fault_handler(arch_detag_ptr(far), pf_flags);
arch_disable_ints();
if (err == ZX_OK) {
return;
}
}
// Check if the current thread was expecting a data fault and
// we should return to its handler.
if (dfr && is_user_accessible(far)) {
// Having the ARM64_DFR_RUN_FAULT_HANDLER_BIT and ARM64_DFR_RUN_ACCESS_FAULT_HANDLER_BIT bits
// set should have already resulted in a valid sign extended canonical address. Double check the
// bit before, which should be a one.
DEBUG_ASSERT(BIT_SET(dfr, ARM64_DFR_RUN_ACCESS_FAULT_HANDLER_BIT - 1));
iframe->elr = dfr;
// Set the return status for the user copy routine.
iframe->r[0] = ZX_ERR_INVALID_ARGS;
return;
}
// If this is from user space, let the user exception handler
// get a shot at it.
if (is_user) {
kcounter_add(exceptions_user, 1);
zx_excp_type_t excp_type = ZX_EXCP_FATAL_PAGE_FAULT;
if (unlikely(dfsc == DFSC_ALIGNMENT_FAULT)) {
excp_type = ZX_EXCP_UNALIGNED_ACCESS;
}
if (try_dispatch_user_data_fault_exception(excp_type, iframe, esr, far,
static_cast<uint32_t>(err)) == ZX_OK) {
return;
}
}
// Print the data fault and stop the kernel.
exception_die(iframe, esr, far,
"data fault: PC at %#" PRIx64 ", FAR %#" PRIx64
"\n"
"ISS %#x (WnR %d CM %d)\n"
"DFSC %#x (%s)\n",
iframe->elr, far, iss, WnR, CM, dfsc, dfsc_to_string(dfsc));
}
} // anonymous namespace
/* called from assembly */
extern "C" void arm64_sync_exception(iframe_t* iframe, uint exception_flags, uint32_t esr) {
auto esr_reg = arch::ArmExceptionSyndromeRegister::Get().FromValue(esr);
switch (esr_reg.ec()) {
case ESRExceptionClass::kUnknown:
kcounter_add(exceptions_unknown, 1);
arm64_unknown_handler(iframe, exception_flags, esr);
break;
case ESRExceptionClass::kFp:
kcounter_add(exceptions_fpu, 1);
arm64_fpu_handler(iframe, exception_flags, esr);
break;
case ESRExceptionClass::kSvc32:
case ESRExceptionClass::kSvc64:
exception_die(iframe, esr, __arm_rsr64("far_el1"),
"syscalls should be handled in assembly\n");
break;
case ESRExceptionClass::kInstructionAbortLowerEl:
case ESRExceptionClass::kInstructionAbortSameEl:
CPU_STATS_INC(page_faults);
arm64_instruction_abort_handler(iframe, exception_flags, esr);
break;
case ESRExceptionClass::kDataAbortLowerEl:
case ESRExceptionClass::kDataAbortSameEl:
CPU_STATS_INC(page_faults);
arm64_data_abort_handler(iframe, exception_flags, esr);
break;
case ESRExceptionClass::kBreakpointLowerEl:
case ESRExceptionClass::kBreakpointSameEl:
kcounter_add(exceptions_hw_brkpt, 1);
arm64_hw_breakpoint_exception_handler(iframe, exception_flags, esr);
break;
case ESRExceptionClass::kStepLowerEl:
case ESRExceptionClass::kStepSameEl:
arm64_step_handler(iframe, exception_flags, esr);
break;
case ESRExceptionClass::kWatchpointLowerEl:
case ESRExceptionClass::kWatchpointSameEl:
kcounter_add(exceptions_hw_wp, 1);
arm64_watchpoint_exception_handler(iframe, exception_flags, esr);
break;
case ESRExceptionClass::kBkpt:
case ESRExceptionClass::kBrk:
kcounter_add(exceptions_brkpt, 1);
arm64_brk_handler(iframe, exception_flags, esr);
break;
case ESRExceptionClass::kPcAlignment:
arm64_pc_alignment_fault_handler(iframe, exception_flags, esr);
break;
case ESRExceptionClass::kMops:
kcounter_add(exceptions_mops, 1);
arm64_mops_handler(iframe, exception_flags, esr);
break;
default: {
/* TODO: properly decode more of these */
if (unlikely((exception_flags & ARM64_EXCEPTION_FLAG_LOWER_EL) == 0)) {
/* trapped inside the kernel, this is bad */
exception_die(iframe, esr, __arm_rsr64("far_el1"),
"unhandled exception in kernel: PC at %#" PRIx64 "\n", iframe->elr);
}
/* let the user exception handler get a shot at it */
kcounter_add(exceptions_unhandled, 1);
if (try_dispatch_user_exception(ZX_EXCP_GENERAL, iframe, esr) == ZX_OK) {
break;
}
exception_die(iframe, esr, __arm_rsr64("far_el1"), "unhandled synchronous exception\n");
}
}
/* if we came from user space, check to see if we have any signals to handle */
if (exception_flags & ARM64_EXCEPTION_FLAG_LOWER_EL) {
/* in the case of receiving a kill signal, this function may not return,
* but the scheduler would have been invoked so it's fine.
*/
arch_iframe_process_pending_signals(iframe);
}
}
/* called from assembly */
extern "C" void arm64_irq(iframe_t* iframe, uint exception_flags);
extern "C" void arm64_irq(iframe_t* iframe, uint exception_flags) {
LTRACEF("iframe %p, flags %#x\n", iframe, exception_flags);
bool is_user = exception_flags & ARM64_EXCEPTION_FLAG_LOWER_EL;
// Spectre V2: If we took an interrupt while in EL0 but $PC was not a user address, invalidate
// the branch predictor. User code may be attempting to mistrain an indirect branch predictor.
if (unlikely(is_user && !is_user_accessible(iframe->elr)) &&
READ_PERCPU_FIELD(should_invalidate_bp_on_el0_exception)) {
arm64_uarch_do_spectre_v2_mitigation();
}
int_handler_saved_state_t state;
int_handler_start(&state);
kcounter_add(exceptions_irq, 1);
platform_irq(iframe);
bool do_preempt = int_handler_finish(&state);
/* if we came from user space, check to see if we have any signals to handle */
if (unlikely(is_user)) {
/* in the case of receiving a kill signal, this function may not return,
* but the scheduler would have been invoked so it's fine.
*/
arch_iframe_process_pending_signals(iframe);
}
/* preempt the thread if the interrupt has signaled it */
if (do_preempt) {
Thread::Current::Preempt();
}
}
/* called from assembly */
extern "C" void arm64_serror_exception(iframe_t* iframe, uint exception_flags);
extern "C" void arm64_serror_exception(iframe_t* iframe, uint exception_flags) {
// SError is largely implementation defined and may or may not be fatal. For now, just count the
// occurrences and add a tracer to help analyze possible causes.
ktrace::Scope trace = KTRACE_CPU_BEGIN_SCOPE("kernel:irq", "irq", ("irq #", "SError"));
exceptions_serror.Add(1);
}
/* called from assembly */
extern "C" void arm64_invalid_exception(iframe_t* iframe, unsigned int which);
extern "C" void arm64_invalid_exception(iframe_t* iframe, unsigned int which) {
platform_panic_start();
printf("invalid exception, which %#x\n", which);
PrintFrame(*iframe);
platform_halt(HALT_ACTION_HALT, ZirconCrashReason::Panic);
}
/* called from assembly */
extern "C" void arm64_unrestricted_arm32_exception(iframe_t* iframe, unsigned int which,
uint32_t esr);
extern "C" void arm64_unrestricted_arm32_exception(iframe_t* iframe, unsigned int which,
uint32_t esr) {
platform_panic_start();
// See include/arch/arm64.h to decode the which value
printf("unrestricted exception from arm32, esr %#x which %#x\n", esr, which);
PrintFrame(*iframe);
platform_halt(HALT_ACTION_HALT, ZirconCrashReason::Panic);
}
/* called from assembly */
extern "C" void arch_iframe_process_pending_signals(iframe_t* iframe) {
DEBUG_ASSERT(iframe != nullptr);
Thread::Current::ProcessPendingSignals(GeneralRegsSource::Iframe, iframe);
}
void arch_dump_exception_context(const arch_exception_context_t* context) {
// Print the common fields first.
dump_common_exception_context(context);
// If we don't have a frame, there's nothing more we can print.
if (context->frame == nullptr) {
printf("no frame to dump\n");
return;
}
auto esr = arch::ArmExceptionSyndromeRegister::Get().FromValue(context->esr);
ESRExceptionClass ec = esr.ec();
uint32_t iss = static_cast<uint32_t>(esr.iss());
switch (ec) {
case ESRExceptionClass::kInstructionAbortLowerEl:
case ESRExceptionClass::kInstructionAbortSameEl:
printf("instruction abort: PC at %#" PRIx64 ", address %#" PRIx64 " IFSC %#x %s\n",
context->frame->elr, context->far, BITS(context->esr, 5, 0),
BIT(static_cast<uint32_t>(ec), 0) ? "" : "user ");
break;
case ESRExceptionClass::kDataAbortLowerEl:
case ESRExceptionClass::kDataAbortSameEl:
printf("data abort: PC at %#" PRIx64 ", address %#" PRIx64 " %s%s\n", context->frame->elr,
context->far, BIT(static_cast<uint32_t>(ec), 0) ? "" : "user ",
BIT(iss, 6) ? "write" : "read");
break;
default:
break;
}
PrintFrame(*context->frame);
printf("tpidr_el0 %#35" PRIx64 " tpidrro_el0 %#25" PRIx64 "\n", //
__arm_rsr64("tpidr_el0"), __arm_rsr64("tpidrro_el0"));
// try to dump the user stack
if (is_user_accessible(context->frame->usp)) {
uint8_t buf[256];
if (arch_copy_from_user(buf, reinterpret_cast<void*>(context->frame->usp), sizeof(buf)) ==
ZX_OK) {
printf("bottom of user stack at %#lx:\n", static_cast<vaddr_t>(context->frame->usp));
hexdump_ex(buf, sizeof(buf), context->frame->usp);
}
}
}
void arch_fill_in_exception_context(const arch_exception_context_t* arch_context,
zx_exception_report_t* report) {
zx_exception_context_t* zx_context = &report->context;
zx_context->synth_code = arch_context->user_synth_code;
zx_context->synth_data = arch_context->user_synth_data;
zx_context->arch.u.arm_64.esr = arch_context->esr;
zx_context->arch.u.arm_64.far = arch_context->far;
}
zx_status_t arch_dispatch_user_policy_exception(uint32_t policy_exception_code,
uint32_t policy_exception_data) {
arch_exception_context_t context = {};
context.user_synth_code = policy_exception_code;
context.user_synth_data = policy_exception_data;
return dispatch_user_exception(ZX_EXCP_POLICY_ERROR, &context);
}
bool arch_install_exception_context(Thread* thread, const arch_exception_context_t* context) {
if (!context->frame) {
// TODO(https://fxbug.dev/42105394): Must be a synthetic exception as they don't (yet) provide
// the registers.
return false;
}
arch_set_suspended_general_regs(thread, GeneralRegsSource::Iframe, context->frame);
thread->arch().debug_state.esr = context->esr;
thread->arch().debug_state.far = context->far;
return true;
}
void arch_remove_exception_context(Thread* thread) { arch_reset_suspended_general_regs(thread); }