x86: Handle double faults
authorBarret Rhoden <brho@cs.berkeley.edu>
Mon, 28 Nov 2016 18:56:48 +0000 (13:56 -0500)
committerBarret Rhoden <brho@cs.berkeley.edu>
Tue, 29 Nov 2016 16:27:40 +0000 (11:27 -0500)
Otherwise, when the kernel stack runs into its guard page, the core will
get stuck in an infinite loop, attempting to double fault.

Signed-off-by: Barret Rhoden <brho@cs.berkeley.edu>
kern/arch/x86/smp_boot.c
kern/arch/x86/trap.c
kern/arch/x86/trapentry64.S

index a72f943..133b9d6 100644 (file)
@@ -295,6 +295,11 @@ static void pcpu_init_nmi(struct per_cpu_info *pcpui)
        pcpui->nmi_worker_stacktop = get_kstack();
 }
 
+static void pcpu_init_doublefault(struct per_cpu_info *pcpui)
+{
+       pcpui->tss->ts_ist2 = get_kstack();
+}
+
 /* Perform any initialization needed by per_cpu_info.  Make sure every core
  * calls this at some point in the smp_boot process.  If you don't smp_boot, you
  * must still call this for core 0.  This must NOT be called from smp_main,
@@ -346,6 +351,7 @@ void __arch_pcpu_init(uint32_t coreid)
        x86_sysenter_init();
        x86_set_sysenter_stacktop(x86_get_stacktop_tss(pcpui->tss));
        pcpu_init_nmi(pcpui);
+       pcpu_init_doublefault(pcpui);
        /* need to init perfctr before potentially using it in timer handler */
        perfmon_pcpu_init();
        vmm_pcpu_init();
index e9b92b0..2a6134c 100644 (file)
@@ -150,6 +150,8 @@ void idt_init(void)
        idt[T_BRKPT].gd_dpl = 3;
        /* Send NMIs to their own stack (IST1 in every core's TSS) */
        idt[T_NMI].gd_ist = 1;
+       /* Send double faults to their own stack (IST2 in every core's TSS) */
+       idt[T_DBLFLT].gd_ist = 2;
 
        /* The sooner we set this, the sooner we can use set/get_stack_top. */
        per_cpu_info[0].tss = &ts;
@@ -536,6 +538,13 @@ void handle_nmi(struct hw_trapframe *hw_tf)
        assert(0);
 }
 
+void handle_double_fault(struct hw_trapframe *hw_tf)
+{
+       print_trapframe(hw_tf);
+       backtrace_hwtf(hw_tf);
+       panic("Double fault!  Check the kernel stack pointer; you likely ran off the end of the stack.");
+}
+
 /* Certain traps want IRQs enabled, such as the syscall.  Others can't handle
  * it, like the page fault handler.  Turn them on on a case-by-case basis. */
 static void trap_dispatch(struct hw_trapframe *hw_tf)
index 7c4175d..e7aa859 100644 (file)
        .quad name;                                                     \
        .long num
 
+#define DOUBLEFAULT_HANDLER(name, num) \
+       .text;                                                          \
+       .globl name;                                            \
+       .type name, @function;                          \
+       .align 2;                                                       \
+       name:                                                           \
+       pushq $(num);                                           \
+       jmp _dblf_entry;                                        \
+       .data;                                                          \
+       .quad name;                                                     \
+       .long num
+
 /* Only used in the kernel during SMP boot.  Send a LAPIC_EOI and iret. */
 #define POKE_HANDLER(name, num)                        \
        .text;                                                          \
@@ -122,11 +134,7 @@ TRAPHANDLER_NOEC(ISR_overflow, T_OFLOW)
 TRAPHANDLER_NOEC(ISR_bounds_check, T_BOUND)
 TRAPHANDLER_NOEC(ISR_invalid_opcode, T_ILLOP)
 TRAPHANDLER_NOEC(ISR_device_not_available, T_DEVICE)
-/* supposedly, DF generates an error code, but the one time we've had a DF so
- * far, it didn't.  eventually, this should probably be handled with a task gate
- * it might have pushed a 0, but just the rest of the stack was corrupt
- */
-TRAPHANDLER_NOEC(ISR_double_fault, T_DBLFLT)
+DOUBLEFAULT_HANDLER(ISR_double_fault, T_DBLFLT)
 /* 9 reserved */
 TRAPHANDLER(ISR_invalid_TSS, T_TSS)
 TRAPHANDLER(ISR_segment_not_present, T_SEGNP)
@@ -474,6 +482,47 @@ irq_all_tf:
        addq $0x10, %rsp                        # skip trapno and err
        iretq
 
+# Similar to the NMI handler, we come in on a special stack, but we can trust
+# the contents of GS for kernel contexts.  This is mostly true.  If we double
+# faulted in this file, for instance, then GS could be garbage.  But that should
+# never happen.  Most double faults will be for running into a guard page on a
+# stack.
+#
+# Since the double fault is so bad, we just want to get into the kernel's C code
+# where we can run some diagnostics.  We (currently) won't try to recover, since
+# it's probably a kernel bug.
+_dblf_entry:
+       cld
+       pushq %r15
+       pushq %r14
+       pushq %r13
+       pushq %r12
+       pushq %r11
+       pushq %r10
+       pushq %r9
+       pushq %r8
+       pushq %rdi
+       pushq %rsi
+       pushq %rbp
+       pushq %rdx
+       pushq %rcx
+       pushq %rbx
+       pushq %rax
+       cmpw $GD_KT, 0x90(%rsp) # 0x90 - diff btw tf_cs and tf_rax
+       je dblf_all_tf
+       # this is a user TF.  we need to swapgs to get the kernel's gs and mark the
+       # context as partial
+       swapgs                                  # user's GS is now in MSR_KERNEL_GS_BASE
+       movl $0x1, 0xac(%rsp)   # 0xac - diff btw tf_padding0 and tf_rax
+dblf_all_tf:
+       pushq $0                                # fsbase space
+       pushq $0                                # gsbase space
+       movq $0, %rbp                   # so we can backtrace to this point
+       movq %rsp, %rdi
+       call handle_double_fault
+dblf_spin:
+       jmp dblf_spin
+
 # Unlike normal trap and IRQ handlers, both user and kernel TFs are handled the
 # similarly.  Both come in here and return from here.  We cannot do anything
 # fancy like proc_restartcore() from NMI context.