blob: 322e5f8d2fb13a31f40c69d60771db6b0314c9d5 [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors
// Copyright (c) 2009 Corey Tabaka
// Copyright (c) 2015 Intel Corporation
//
// 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 <debug.h>
#include <trace.h>
#include <arch/exception.h>
#include <arch/user_copy.h>
#include <arch/x86.h>
#include <arch/x86/apic.h>
#include <arch/x86/feature.h>
#include <arch/x86/interrupts.h>
#include <arch/x86/descriptor.h>
#include <kernel/thread.h>
#include <kernel/stats.h>
#include <kernel/vm.h>
#include <platform.h>
#include <vm/fault.h>
#include <fbl/auto_call.h>
#include <zircon/syscalls/exception.h>
#include <lib/ktrace.h>
static void dump_fault_frame(x86_iframe_t *frame)
{
dprintf(CRITICAL, " CS: %#18" PRIx64 " RIP: %#18" PRIx64 " EFL: %#18" PRIx64 " CR2: %#18lx\n",
frame->cs, frame->ip, frame->flags, x86_get_cr2());
dprintf(CRITICAL, " RAX: %#18" PRIx64 " RBX: %#18" PRIx64 " RCX: %#18" PRIx64 " RDX: %#18" PRIx64 "\n",
frame->rax, frame->rbx, frame->rcx, frame->rdx);
dprintf(CRITICAL, " RSI: %#18" PRIx64 " RDI: %#18" PRIx64 " RBP: %#18" PRIx64 " RSP: %#18" PRIx64 "\n",
frame->rsi, frame->rdi, frame->rbp, frame->user_sp);
dprintf(CRITICAL, " R8: %#18" PRIx64 " R9: %#18" PRIx64 " R10: %#18" PRIx64 " R11: %#18" PRIx64 "\n",
frame->r8, frame->r9, frame->r10, frame->r11);
dprintf(CRITICAL, " R12: %#18" PRIx64 " R13: %#18" PRIx64 " R14: %#18" PRIx64 " R15: %#18" PRIx64 "\n",
frame->r12, frame->r13, frame->r14, frame->r15);
dprintf(CRITICAL, "errc: %#18" PRIx64 "\n",
frame->err_code);
// dump the bottom of the current stack
void *stack = frame;
if (frame->cs == CODE_64_SELECTOR) {
dprintf(CRITICAL, "bottom of kernel stack at %p:\n", stack);
hexdump(stack, 128);
}
}
__NO_RETURN static void exception_die(x86_iframe_t *frame, const char *msg)
{
platform_panic_start();
printf("vector %lu\n", (ulong) frame->vector);
dprintf(CRITICAL, "%s", msg);
dump_fault_frame(frame);
// try to dump the user stack
if (is_user_address(frame->user_sp)) {
uint8_t buf[256];
if (arch_copy_from_user(buf, (void *)frame->user_sp, sizeof(buf)) == ZX_OK) {
printf("bottom of user stack at 0x%lx:\n", (vaddr_t)frame->user_sp);
hexdump_ex(buf, sizeof(buf), frame->user_sp);
}
}
platform_halt(HALT_ACTION_HALT, HALT_REASON_SW_PANIC);
}
static status_t call_dispatch_user_exception(uint kind,
struct arch_exception_context *context,
x86_iframe_t *frame)
{
thread_t *thread = get_current_thread();
x86_set_suspended_general_regs(&thread->arch, X86_GENERAL_REGS_IFRAME, frame);
status_t status = dispatch_user_exception(kind, context);
x86_reset_suspended_general_regs(&thread->arch);
return status;
}
static bool try_dispatch_user_exception(x86_iframe_t *frame, uint kind)
{
bool from_user = SELECTOR_PL(frame->cs) != 0;
if (from_user) {
struct arch_exception_context context = { false, frame, 0 };
arch_set_in_int_handler(false);
arch_enable_ints();
status_t erc = call_dispatch_user_exception(kind, &context, frame);
arch_disable_ints();
arch_set_in_int_handler(true);
if (erc == ZX_OK)
return true;
}
return false;
}
static void x86_debug_handler(x86_iframe_t *frame)
{
if (try_dispatch_user_exception(frame, ZX_EXCP_HW_BREAKPOINT))
return;
exception_die(frame, "unhandled hw breakpoint, halting\n");
}
static void x86_nmi_handler(x86_iframe_t *frame)
{
}
static void x86_breakpoint_handler(x86_iframe_t *frame)
{
if (try_dispatch_user_exception(frame, ZX_EXCP_SW_BREAKPOINT))
return;
exception_die(frame, "unhandled sw breakpoint, halting\n");
}
static void x86_gpf_handler(x86_iframe_t *frame)
{
DEBUG_ASSERT(arch_ints_disabled());
// Check if we were doing a GPF test, e.g. to check if an MSR exists.
struct x86_percpu *percpu = x86_get_percpu();
if (unlikely(percpu->gpf_return_target)) {
ASSERT(SELECTOR_PL(frame->cs) == 0);
// Set up return to new address
frame->ip = percpu->gpf_return_target;
percpu->gpf_return_target = 0;
return;
}
if (try_dispatch_user_exception(frame, ZX_EXCP_GENERAL))
return;
exception_die(frame, "unhandled gpf, halting\n");
}
static void x86_invop_handler(x86_iframe_t *frame)
{
if (try_dispatch_user_exception(frame, ZX_EXCP_UNDEFINED_INSTRUCTION))
return;
exception_die(frame, "invalid opcode, halting\n");
}
static void x86_df_handler(x86_iframe_t *frame)
{
// Do not give the user exception handler the opportunity to handle double
// faults, since they indicate an unexpected system state and cannot be
// recovered from.
exception_die(frame, "double fault, halting\n");
}
static void x86_unhandled_exception(x86_iframe_t *frame)
{
if (try_dispatch_user_exception(frame, ZX_EXCP_GENERAL))
return;
exception_die(frame, "unhandled exception, halting\n");
}
static void x86_dump_pfe(x86_iframe_t *frame, ulong cr2)
{
uint64_t error_code = frame->err_code;
addr_t v_addr = cr2;
addr_t ssp = frame->user_ss & X86_8BYTE_MASK;
addr_t sp = frame->user_sp;
addr_t cs = frame->cs & X86_8BYTE_MASK;
addr_t ip = frame->ip;
dprintf(CRITICAL, "<PAGE FAULT> Instruction Pointer = 0x%lx:0x%lx\n",
(ulong)cs,
(ulong)ip);
dprintf(CRITICAL, "<PAGE FAULT> Stack Pointer = 0x%lx:0x%lx\n",
(ulong)ssp,
(ulong)sp);
dprintf(CRITICAL, "<PAGE FAULT> Fault Linear Address = 0x%lx\n",
(ulong)v_addr);
dprintf(CRITICAL, "<PAGE FAULT> Error Code Value = 0x%lx\n",
(ulong)error_code);
dprintf(CRITICAL, "<PAGE FAULT> Error Code Type = %s %s %s%s, %s\n",
error_code & PFEX_U ? "user" : "supervisor",
error_code & PFEX_W ? "write" : "read",
error_code & PFEX_I ? "instruction" : "data",
error_code & PFEX_RSV ? " rsv" : "",
error_code & PFEX_P ? "protection violation" : "page not present");
}
__NO_RETURN static void x86_fatal_pfe_handler(x86_iframe_t *frame, ulong cr2)
{
x86_dump_pfe(frame, cr2);
uint64_t error_code = frame->err_code;
dump_thread(get_current_thread(), true);
if (error_code & PFEX_U) {
// User mode page fault
switch (error_code) {
case 4:
case 5:
case 6:
case 7:
exception_die(frame, "User Page Fault exception, halting\n");
break;
}
} else {
// Supervisor mode page fault
switch (error_code) {
case 0:
case 1:
case 2:
case 3:
exception_die(frame, "Supervisor Page Fault exception, halting\n");
break;
}
}
exception_die(frame, "unhandled page fault, halting\n");
}
static status_t x86_pfe_handler(x86_iframe_t *frame)
{
/* Handle a page fault exception */
uint64_t error_code = frame->err_code;
vaddr_t va = x86_get_cr2();
/* reenable interrupts */
arch_set_in_int_handler(false);
arch_enable_ints();
/* make sure we put interrupts back as we exit */
auto ac = fbl::MakeAutoCall([]() {
arch_disable_ints();
arch_set_in_int_handler(true);
});
/* check for flags we're not prepared to handle */
if (unlikely(error_code & ~(PFEX_I | PFEX_U | PFEX_W | PFEX_P))) {
printf("x86_pfe_handler: unhandled error code bits set, error code %#" PRIx64 "\n", error_code);
return ZX_ERR_NOT_SUPPORTED;
}
/* check for a potential SMAP failure */
if (unlikely(
!(error_code & PFEX_U) &&
(error_code & PFEX_P) &&
x86_feature_test(X86_FEATURE_SMAP) &&
!(frame->flags & X86_FLAGS_AC) &&
is_user_address(va))) {
/* supervisor mode page-present access failure with the AC bit clear (SMAP enabled) */
printf("x86_pfe_handler: potential SMAP failure, supervisor access at address %#" PRIxPTR "\n", va);
return ZX_ERR_ACCESS_DENIED;
}
/* convert the PF error codes to page fault flags */
uint flags = 0;
flags |= (error_code & PFEX_W) ? VMM_PF_FLAG_WRITE : 0;
flags |= (error_code & PFEX_U) ? VMM_PF_FLAG_USER : 0;
flags |= (error_code & PFEX_I) ? VMM_PF_FLAG_INSTRUCTION : 0;
flags |= (error_code & PFEX_P) ? 0 : VMM_PF_FLAG_NOT_PRESENT;
/* call the high level page fault handler */
status_t pf_err = vmm_page_fault_handler(va, flags);
if (likely(pf_err == ZX_OK))
return ZX_OK;
/* if the high level page fault handler can't deal with it,
* resort to trying to recover first, before bailing */
/* Check if a resume address is specified, and just return to it if so */
thread_t *current_thread = get_current_thread();
if (unlikely(current_thread->arch.page_fault_resume)) {
frame->ip = (uintptr_t)current_thread->arch.page_fault_resume;
return ZX_OK;
}
/* let high level code deal with this */
bool from_user = SELECTOR_PL(frame->cs) != 0;
if (from_user) {
CPU_STATS_INC(exceptions);
struct arch_exception_context context = { true, frame, va };
return call_dispatch_user_exception(ZX_EXCP_FATAL_PAGE_FAULT,
&context, frame);
}
/* fall through to fatal path */
return ZX_ERR_NOT_SUPPORTED;
}
static void x86_iframe_process_pending_signals(x86_iframe_t *frame)
{
thread_t *thread = get_current_thread();
if (unlikely(thread_is_signaled(thread))) {
x86_set_suspended_general_regs(&thread->arch, X86_GENERAL_REGS_IFRAME, frame);
thread_process_pending_signals();
x86_reset_suspended_general_regs(&thread->arch);
}
}
/* top level x86 exception handler for most exceptions and irqs */
void x86_exception_handler(x86_iframe_t *frame)
{
// are we recursing?
if (unlikely(arch_in_int_handler()) && frame->vector != X86_INT_NMI) {
exception_die(frame, "recursion in interrupt handler\n");
}
arch_set_in_int_handler(true);
// did we come from user or kernel space?
bool from_user = SELECTOR_PL(frame->cs) != 0;
// deliver the interrupt
enum handler_return ret = INT_NO_RESCHEDULE;
ktrace_tiny(TAG_IRQ_ENTER, ((uint32_t)frame->vector << 8) | arch_curr_cpu_num());
switch (frame->vector) {
case X86_INT_DEBUG:
CPU_STATS_INC(exceptions);
x86_debug_handler(frame);
break;
case X86_INT_NMI:
CPU_STATS_INC(exceptions);
x86_nmi_handler(frame);
break;
case X86_INT_BREAKPOINT:
CPU_STATS_INC(exceptions);
x86_breakpoint_handler(frame);
break;
case X86_INT_INVALID_OP:
CPU_STATS_INC(exceptions);
x86_invop_handler(frame);
break;
case X86_INT_DEVICE_NA:
CPU_STATS_INC(exceptions);
exception_die(frame, "device na fault\n");
break;
case X86_INT_DOUBLE_FAULT:
x86_df_handler(frame);
break;
case X86_INT_FPU_FP_ERROR: {
CPU_STATS_INC(exceptions);
uint16_t fsw;
__asm__ __volatile__("fnstsw %0" : "=m" (fsw));
TRACEF("fsw 0x%hx\n", fsw);
exception_die(frame, "x87 math fault\n");
break;
}
case X86_INT_SIMD_FP_ERROR: {
CPU_STATS_INC(exceptions);
uint32_t mxcsr;
__asm__ __volatile__("stmxcsr %0" : "=m" (mxcsr));
TRACEF("mxcsr 0x%x\n", mxcsr);
exception_die(frame, "simd math fault\n");
break;
}
case X86_INT_GP_FAULT:
CPU_STATS_INC(exceptions);
x86_gpf_handler(frame);
break;
case X86_INT_PAGE_FAULT:
CPU_STATS_INC(page_faults);
if (x86_pfe_handler(frame) != ZX_OK)
x86_fatal_pfe_handler(frame, x86_get_cr2());
break;
/* ignore spurious APIC irqs */
case X86_INT_APIC_SPURIOUS:
break;
case X86_INT_APIC_ERROR: {
ret = apic_error_interrupt_handler();
apic_issue_eoi();
break;
}
case X86_INT_APIC_TIMER: {
ret = apic_timer_interrupt_handler();
apic_issue_eoi();
break;
}
case X86_INT_IPI_GENERIC: {
ret = x86_ipi_generic_handler();
apic_issue_eoi();
break;
}
case X86_INT_IPI_RESCHEDULE: {
ret = x86_ipi_reschedule_handler();
apic_issue_eoi();
break;
}
case X86_INT_IPI_HALT: {
x86_ipi_halt_handler();
/* no return */
break;
}
/* pass all other non-Intel defined irq vectors to the platform */
case X86_INT_PLATFORM_BASE ... X86_INT_PLATFORM_MAX: {
CPU_STATS_INC(interrupts);
ret = platform_irq(frame);
break;
}
default:
x86_unhandled_exception(frame);
break;
}
/* at this point we're able to be rescheduled, so we're 'outside' of the int handler */
arch_set_in_int_handler(false);
/* if we came from user space, check to see if we have any signals to handle */
if (unlikely(from_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.
*/
x86_iframe_process_pending_signals(frame);
}
if (ret != INT_NO_RESCHEDULE)
thread_preempt();
ktrace_tiny(TAG_IRQ_EXIT, ((uint)frame->vector << 8) | arch_curr_cpu_num());
DEBUG_ASSERT_MSG(arch_ints_disabled(),
"ints disabled on way out of exception, vector %" PRIu64 " IP %#" PRIx64 "\n",
frame->vector, frame->ip);
}
__WEAK x86_64_syscall_result unknown_syscall(uint64_t syscall_num, uint64_t ip)
{
PANIC_UNIMPLEMENTED;
}
void x86_syscall_process_pending_signals(x86_syscall_general_regs_t *gregs)
{
thread_t *thread = get_current_thread();
x86_set_suspended_general_regs(&thread->arch, X86_GENERAL_REGS_SYSCALL, gregs);
thread_process_pending_signals();
x86_reset_suspended_general_regs(&thread->arch);
}
void arch_dump_exception_context(const arch_exception_context_t *context)
{
if (context->is_page_fault) {
x86_dump_pfe(context->frame, context->cr2);
}
dump_fault_frame(context->frame);
// try to dump the user stack
if (context->frame->cs != CODE_64_SELECTOR && is_user_address(context->frame->user_sp)) {
uint8_t buf[256];
if (arch_copy_from_user(buf, (void *)context->frame->user_sp, sizeof(buf)) == ZX_OK) {
printf("bottom of user stack at 0x%lx:\n", (vaddr_t)context->frame->user_sp);
hexdump_ex(buf, sizeof(buf), context->frame->user_sp);
}
}
}
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->arch.u.x86_64.vector = arch_context->frame->vector;
zx_context->arch.u.x86_64.err_code = arch_context->frame->err_code;
zx_context->arch.u.x86_64.cr2 = arch_context->cr2;
}
status_t arch_dispatch_user_policy_exception(void)
{
x86_iframe_t frame = {};
arch_exception_context_t context = {};
context.frame = &frame;
return dispatch_user_exception(ZX_EXCP_POLICY_ERROR, &context);
}