#include "libcflat.h" #include "processor.h" #include "vm.h" #include "desc.h" #include "isr.h" #include "apic.h" #include "apic-defs.h" #ifdef __x86_64__ # define R "r" #else # define R "e" #endif static inline void io_delay(void) { } void apic_self_ipi(u8 v) { apic_icr_write(APIC_DEST_SELF | APIC_DEST_PHYSICAL | APIC_DM_FIXED | APIC_INT_ASSERT | v, 0); } void apic_self_nmi(void) { apic_icr_write(APIC_DEST_PHYSICAL | APIC_DM_NMI | APIC_INT_ASSERT, 0); } #define flush_phys_addr(__s) outl(__s, 0xe4) #define flush_stack() do { \ int __l; \ flush_phys_addr(virt_to_phys(&__l)); \ } while (0) extern char isr_iret_ip[]; static void flush_idt_page() { struct descriptor_table_ptr ptr; sidt(&ptr); flush_phys_addr(virt_to_phys((void*)ptr.base)); } static volatile unsigned int test_divider; static volatile int test_count; ulong stack_phys; void *stack_va; void do_pf_tss(void) { printf("PF running\n"); install_pte(phys_to_virt(read_cr3()), 1, stack_va, stack_phys | PT_PRESENT_MASK | PT_WRITABLE_MASK, 0); invlpg(stack_va); } extern void pf_tss(void); asm ("pf_tss: \n\t" #ifdef __x86_64__ // no task on x86_64, save/restore caller-save regs "push %rax; push %rcx; push %rdx; push %rsi; push %rdi\n" "push %r8; push %r9; push %r10; push %r11\n" #endif "call do_pf_tss \n\t" #ifdef __x86_64__ "pop %r11; pop %r10; pop %r9; pop %r8\n" "pop %rdi; pop %rsi; pop %rdx; pop %rcx; pop %rax\n" #endif "add $"S", %"R "sp\n\t" // discard error code "iret"W" \n\t" "jmp pf_tss\n\t" ); #ifndef __x86_64__ static void of_isr(struct ex_regs *r) { printf("OF isr running\n"); test_count++; } #endif static void np_isr(struct ex_regs *r) { printf("NP isr running %lx err=%lx\n", r->rip, r->error_code); set_idt_sel(33, read_cs()); test_count++; } static void de_isr(struct ex_regs *r) { printf("DE isr running divider is %d\n", test_divider); test_divider = 10; } static void bp_isr(struct ex_regs *r) { printf("BP isr running\n"); test_count++; } static void nested_nmi_isr(struct ex_regs *r) { printf("Nested NMI isr running rip=%lx\n", r->rip); if (r->rip != (ulong)&isr_iret_ip) test_count++; } static void nmi_isr(struct ex_regs *r) { printf("NMI isr running %p\n", &isr_iret_ip); test_count++; handle_exception(2, nested_nmi_isr); printf("Sending nested NMI to self\n"); apic_self_nmi(); io_delay(); printf("After nested NMI to self\n"); } unsigned long *iret_stack; static void nested_nmi_iret_isr(struct ex_regs *r) { printf("Nested NMI isr running rip=%lx\n", r->rip); if (r->rip == iret_stack[-3]) test_count++; } extern void do_iret(ulong phys_stack, void *virt_stack); // Return to same privilege level won't pop SS or SP, so // save it in RDX while we run on the nested stack asm("do_iret:" #ifdef __x86_64__ "mov %rdi, %rax \n\t" // phys_stack "mov %rsi, %rdx \n\t" // virt_stack #else "mov 4(%esp), %eax \n\t" // phys_stack "mov 8(%esp), %edx \n\t" // virt_stack #endif "xchg %"R "dx, %"R "sp \n\t" // point to new stack "pushf"W" \n\t" "mov %cs, %ecx \n\t" "push"W" %"R "cx \n\t" "push"W" $1f \n\t" "outl %eax, $0xe4 \n\t" // flush page "iret"W" \n\t" "1: xchg %"R "dx, %"R "sp \n\t" // point to old stack "ret\n\t" ); static void nmi_iret_isr(struct ex_regs *r) { unsigned long *s = alloc_page(); test_count++; printf("NMI isr running stack %p\n", s); handle_exception(2, nested_nmi_iret_isr); printf("Sending nested NMI to self\n"); apic_self_nmi(); printf("After nested NMI to self\n"); iret_stack = &s[128]; do_iret(virt_to_phys(s), iret_stack); printf("After iret\n"); } static void tirq0(isr_regs_t *r) { printf("irq0 running\n"); if (test_count == 1) test_count++; eoi(); } static void tirq1(isr_regs_t *r) { printf("irq1 running\n"); test_count++; eoi(); } ulong saved_stack; #define switch_stack(S) do { \ asm volatile ("mov %%" R "sp, %0":"=r"(saved_stack)); \ asm volatile ("mov %0, %%" R "sp"::"r"(S)); \ } while(0) #define restore_stack() do { \ asm volatile ("mov %0, %%" R "sp"::"r"(saved_stack)); \ } while(0) int main() { unsigned int res; ulong *pt, *cr3, i; setup_vm(); setup_idt(); setup_alt_stack(); handle_irq(32, tirq0); handle_irq(33, tirq1); /* generate HW exception that will fault on IDT and stack */ handle_exception(0, de_isr); printf("Try to divide by 0\n"); flush_idt_page(); flush_stack(); asm volatile ("divl %3": "=a"(res) : "d"(0), "a"(1500), "m"(test_divider)); printf("Result is %d\n", res); report("DE exception", res == 150); /* generate soft exception (BP) that will fault on IDT and stack */ test_count = 0; handle_exception(3, bp_isr); printf("Try int 3\n"); flush_idt_page(); flush_stack(); asm volatile ("int $3"); printf("After int 3\n"); report("BP exception", test_count == 1); #ifndef __x86_64__ /* generate soft exception (OF) that will fault on IDT */ test_count = 0; handle_exception(4, of_isr); flush_idt_page(); printf("Try into\n"); asm volatile ("addb $127, %b0\ninto"::"a"(127)); printf("After into\n"); report("OF exception", test_count == 1); /* generate soft exception (OF) using two bit instruction that will fault on IDT */ test_count = 0; handle_exception(4, of_isr); flush_idt_page(); printf("Try into\n"); asm volatile ("addb $127, %b0\naddr16 into"::"a"(127)); printf("After into\n"); report("2 byte OF exception", test_count == 1); #endif /* generate HW interrupt that will fault on IDT */ test_count = 0; flush_idt_page(); printf("Sending vec 33 to self\n"); irq_enable(); apic_self_ipi(33); io_delay(); irq_disable(); printf("After vec 33 to self\n"); report("vec 33", test_count == 1); /* generate soft interrupt that will fault on IDT and stack */ test_count = 0; flush_idt_page(); printf("Try int $33\n"); flush_stack(); asm volatile ("int $33"); printf("After int $33\n"); report("int $33", test_count == 1); /* Inject two HW interrupt than open iterrupt windows. Both interrupt will fault on IDT access */ test_count = 0; flush_idt_page(); printf("Sending vec 32 and 33 to self\n"); apic_self_ipi(32); apic_self_ipi(33); io_delay(); irq_enable(); asm volatile("nop"); irq_disable(); printf("After vec 32 and 33 to self\n"); report("vec 32/33", test_count == 2); /* Inject HW interrupt, do sti and than (while in irq shadow) inject soft interrupt. Fault during soft interrupt. Soft interrup shoud be handled before HW interrupt */ test_count = 0; flush_idt_page(); printf("Sending vec 32 and int $33\n"); apic_self_ipi(32); flush_stack(); io_delay(); asm volatile ("sti; int $33"); irq_disable(); printf("After vec 32 and int $33\n"); report("vec 32/int $33", test_count == 2); /* test that TPR is honored */ test_count = 0; handle_irq(62, tirq1); flush_idt_page(); printf("Sending vec 33 and 62 and mask one with TPR\n"); apic_write(APIC_TASKPRI, 0xf << 4); irq_enable(); apic_self_ipi(32); apic_self_ipi(62); io_delay(); apic_write(APIC_TASKPRI, 0x2 << 4); printf("After 33/62 TPR test\n"); report("TPR", test_count == 1); apic_write(APIC_TASKPRI, 0x0); while(test_count != 2); /* wait for second irq */ irq_disable(); /* test fault durint NP delivery */ printf("Before NP test\n"); test_count = 0; handle_exception(11, np_isr); set_idt_sel(33, NP_SEL); flush_idt_page(); flush_stack(); asm volatile ("int $33"); printf("After int33\n"); report("NP exception", test_count == 2); /* generate NMI that will fault on IDT */ test_count = 0; handle_exception(2, nmi_isr); flush_idt_page(); printf("Sending NMI to self\n"); apic_self_nmi(); printf("After NMI to self\n"); /* this is needed on VMX without NMI window notification. Interrupt windows is used instead, so let pending NMI to be injected */ irq_enable(); asm volatile ("nop"); irq_disable(); report("NMI", test_count == 2); /* generate NMI that will fault on IRET */ printf("Before NMI IRET test\n"); test_count = 0; handle_exception(2, nmi_iret_isr); printf("Sending NMI to self\n"); apic_self_nmi(); /* this is needed on VMX without NMI window notification. Interrupt windows is used instead, so let pending NMI to be injected */ irq_enable(); asm volatile ("nop"); irq_disable(); printf("After NMI to self\n"); report("NMI", test_count == 2); stack_phys = (ulong)virt_to_phys(alloc_page()); stack_va = alloc_vpage(); /* Generate DE and PF exceptions serially */ test_divider = 0; set_intr_alt_stack(14, pf_tss); handle_exception(0, de_isr); printf("Try to divide by 0\n"); /* install read only pte */ install_pte(phys_to_virt(read_cr3()), 1, stack_va, stack_phys | PT_PRESENT_MASK, 0); invlpg(stack_va); flush_phys_addr(stack_phys); switch_stack(stack_va + 4095); flush_idt_page(); asm volatile ("divl %3": "=a"(res) : "d"(0), "a"(1500), "m"(test_divider)); restore_stack(); printf("Result is %d\n", res); report("DE PF exceptions", res == 150); /* Generate NP and PF exceptions serially */ printf("Before NP test\n"); test_count = 0; set_intr_alt_stack(14, pf_tss); handle_exception(11, np_isr); set_idt_sel(33, NP_SEL); /* install read only pte */ install_pte(phys_to_virt(read_cr3()), 1, stack_va, stack_phys | PT_PRESENT_MASK, 0); invlpg(stack_va); flush_idt_page(); flush_phys_addr(stack_phys); switch_stack(stack_va + 4095); asm volatile ("int $33"); restore_stack(); printf("After int33\n"); report("NP PF exceptions", test_count == 2); pt = alloc_page(); cr3 = (void*)read_cr3(); memset(pt, 0, 4096); /* use shadowed stack during interrupt delivery */ for (i = 0; i < 4096/sizeof(ulong); i++) { if (!cr3[i]) { cr3[i] = virt_to_phys(pt) | PT_PRESENT_MASK | PT_WRITABLE_MASK; pt[0] = virt_to_phys(pt) | PT_PRESENT_MASK | PT_WRITABLE_MASK; #ifndef __x86_64__ ((ulong*)(i<<22))[1] = 0; #else ((ulong*)(i<<39))[1] = 0; #endif write_cr3(virt_to_phys(cr3)); break; } } test_count = 0; printf("Try int 33 with shadowed stack\n"); switch_stack(((char*)pt) + 4095); asm volatile("int $33"); restore_stack(); printf("After int 33 with shadowed stack\n"); report("int 33 with shadowed stack", test_count == 1); return report_summary(); }