| // 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); |
| } |