| // Copyright 2017 The Fuchsia Authors |
| // |
| // 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 "constants_priv.h" |
| |
| #define IA32_GS_BASE 0xc0000101 |
| #define X86_MSR_IA32_STAR 0xc0000081 |
| #define X86_MSR_IA32_LSTAR 0xc0000082 |
| #define X86_MSR_IA32_SYSENTER_CS 0x00000174 |
| #define X86_MSR_IA32_SYSENTER_ESP 0x00000175 |
| #define X86_MSR_IA32_SYSENTER_EIP 0x00000176 |
| #define X86_MSR_IA32_EFER 0xc0000080 |
| #define ABSOLUTE_ADDR(base, x) (x - base + GUEST_ENTRY) |
| #define CODE_32_SELECTOR (1<<3) |
| #define CODE_64_SELECTOR (2<<3) |
| #define DATA_SELECTOR (3<<3) |
| #define USER_CODE_32_SELECTOR (4<<3) |
| #define USER_DATA_SELECTOR (5<<3) |
| #define USER_CODE_64_SELECTOR (6<<3) |
| #define CODE_16_SELECTOR (8<<3) |
| #define INVALID_HYPERCALL 0xffffffff |
| |
| .text |
| |
| // Define new function 'name'. |
| // Local label '1:' used by other macros to compute offsets. |
| .macro FUNCTION name |
| .global \name |
| .type \name, STT_FUNC |
| \name: |
| 1: |
| .endm |
| |
| .macro init_gdt start |
| lgdt ABSOLUTE_ADDR(\start, gdtr_for_\start) |
| mov $GUEST_ENTRY + 0x1000, %rsp |
| jmp end_of_init_gdt_for_\start |
| |
| .align 8 |
| gdt_for_\start: |
| // Null entry. |
| .8byte 0 |
| // CODE_32_SELECTOR |
| .2byte 0xffff // Limit 15:00 |
| .2byte 0x0000 // Base 15:00 |
| .byte 0x00 // Base 23:16 |
| .byte 0b10011010 // P(1) DPL(00) S(1) 1 C(0) R(1) A(0) |
| .byte 0b11001111 // G(1) D(1) L(0) AVL(0) Limit 19:16 |
| .byte 0x0 // Base 31:24 |
| // CODE_64_SELECTOR |
| .2byte 0xffff // Limit 15:00 |
| .2byte 0x0000 // Base 15:00 |
| .byte 0x00 // Base 23:16 |
| .byte 0b10011010 // P(1) DPL(00) S(1) 1 C(0) R(1) A(0) |
| .byte 0b10101111 // G(1) D(0) L(1) AVL(0) Limit 19:16 |
| .byte 0x0 // Base 31:24 |
| // DATA_SELECTOR |
| .2byte 0xffff // Limit 15:00 |
| .2byte 0x0000 // Base 15:00 |
| .byte 0x00 // Base 23:16 |
| .byte 0b10010010 // P(1) DPL(00) S(1) 0 E(0) W(1) A(0) |
| .byte 0b11001111 // G(1) B(1) L(0) AVL(0) Limit 19:16 |
| .byte 0x0 // Base 31:24 |
| // USER_CODE_32_SELECTOR |
| .2byte 0xffff // Limit 15:00 |
| .2byte 0x0000 // Base 15:00 |
| .byte 0x00 // Base 23:16 |
| .byte 0b11111010 // P(1) DPL(11) S(1) 1 C(0) R(1) A(0) |
| .byte 0b11001111 // G(1) D(1) L(0) AVL(0) Limit 19:16 |
| .byte 0x0 // Base 31:24 |
| // USER_DATA_SELECTOR |
| .2byte 0xffff // Limit 15:00 |
| .2byte 0x0000 // Base 15:00 |
| .byte 0x00 // Base 23:16 |
| .byte 0b11110010 // P(1) DPL(11) S(1) 0 E(0) W(1) A(0) |
| .byte 0b11001111 // G(1) B(1) 0 0 limit 19:16 |
| .byte 0x0 // Base 31:24 |
| // USER_CODE_64_SELECTOR |
| .2byte 0xffff // Limit 15:00 |
| .2byte 0x0000 // Base 15:00 |
| .byte 0x00 // Base 23:16 |
| .byte 0b11111010 // P(1) DPL(11) S(1) 1 C(0) R(1) A(0) |
| .byte 0b10101111 // G(1) D(0) L(1) AVL(0) Limit 19:16 |
| .byte 0x0 // Base 31:24 |
| // USER_DATA_SELECTOR duplicate for sysexit |
| .2byte 0xffff // Limit 15:00 |
| .2byte 0x0000 // Base 15:00 |
| .byte 0x00 // Base 23:16 |
| .byte 0b11110010 // P(1) DPL(11) S(1) 0 E(0) W(1) A(0) |
| .byte 0b11001111 // G(1) B(1) 0 0 limit 19:16 |
| .byte 0x0 // Base 31:24 |
| // CODE_16_SELECTOR |
| .2byte 0xffff // Limit 15:00 |
| .2byte 0x0000 // Base 15:00 |
| .byte 0x00 // Base 23:16 |
| .byte 0b10011010 // P(1) DPL(00) S(1) 1 C(0) R(1) A(0) |
| .byte 0b10001111 // G(1) D(0) L(0) AVL(0) Limit 19:16 |
| .byte 0x0 // Base 31:24 |
| gdtr_for_\start: |
| .2byte gdtr_for_\start - gdt_for_\start - 1 |
| .8byte ABSOLUTE_ADDR(\start, gdt_for_\start) |
| end_of_init_gdt_for_\start: |
| .endm |
| |
| .macro isr start, n |
| int_\n\()_for_\start: |
| mov $\n, %rax |
| movq $0, (EXIT_TEST_ADDR) |
| .endm |
| |
| .macro idt start, n |
| .2byte ABSOLUTE_ADDR(\start, \ |
| int_\n\()_for_\start) // Offset 15:00 |
| .2byte CODE_64_SELECTOR // Segment selector |
| .byte 0 // IST(000) |
| .byte 0b10001110 // P(1) DPL(00) 0 Type(1110) |
| .2byte 0 // Offset 31:16 |
| .4byte 0 // Offset 63:32 |
| .4byte 0 // Reserved |
| .endm |
| |
| .macro init_interrupt_handling start |
| init_gdt \start |
| lidt ABSOLUTE_ADDR(\start, idtr_for_\start) |
| mov $GUEST_ENTRY + 0x1000, %rsp |
| jmp end_of_\start |
| |
| .irp n, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, \ |
| 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32 |
| isr \start \n |
| .endr |
| |
| .align 8 |
| idt_for_\start: |
| .irp n, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, \ |
| 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32 |
| idt \start \n |
| .endr |
| |
| idtr_for_\start: |
| .2byte idtr_for_\start - idt_for_\start - 1 // Limit |
| .8byte ABSOLUTE_ADDR(\start, idt_for_\start) // Address |
| end_of_\start: |
| .endm |
| |
| // Test vcpu_resume. |
| FUNCTION vcpu_resume_start |
| // Test that we do not exit on load/store of CR3. |
| mov %cr3, %rax |
| mov %rax, %cr3 |
| |
| // Test that we do not exit on store of GS_BASE. |
| xor %eax, %eax |
| xor %edx, %edx |
| mov $IA32_GS_BASE, %ecx |
| wrmsr |
| |
| // Test that we handle CPUID instruction correctly. |
| xor %eax, %eax |
| cpuid |
| |
| movq $0, (EXIT_TEST_ADDR) |
| FUNCTION vcpu_resume_end |
| |
| // Test vcpu_read_state and vcpu_write_state. |
| FUNCTION vcpu_read_write_state_start |
| add $1, %rax |
| add $2, %rcx |
| add $3, %rdx |
| add $4, %rbx |
| add $5, %rsp |
| add $6, %rbp |
| add $7, %rsi |
| add $8, %rdi |
| add $9, %r8 |
| add $10, %r9 |
| add $11, %r10 |
| add $12, %r11 |
| add $13, %r12 |
| add $14, %r13 |
| add $15, %r14 |
| add $16, %r15 |
| |
| stc // Set carry flag (bit 0) |
| stac // Set AC flag (bit 18) |
| |
| movq $0, (EXIT_TEST_ADDR) |
| FUNCTION vcpu_read_write_state_end |
| |
| // Test vcpu_interrupt. |
| FUNCTION vcpu_interrupt_start |
| init_interrupt_handling vcpu_interrupt_start |
| sti |
| movq $0, (EXIT_TEST_ADDR) |
| jmp . |
| FUNCTION vcpu_interrupt_end |
| |
| // Test guest_set_trap using a memory-based trap. |
| FUNCTION guest_set_trap_start |
| movq $0, (TRAP_ADDR) |
| movq $0, (EXIT_TEST_ADDR) |
| FUNCTION guest_set_trap_end |
| |
| // Test guest HLT instruction handling. |
| FUNCTION vcpu_hlt_start |
| init_interrupt_handling vcpu_hlt_start |
| sti |
| hlt |
| FUNCTION vcpu_hlt_end |
| |
| // Test that pause exiting works correctly. |
| FUNCTION vcpu_pause_start |
| pause |
| movq $0, (EXIT_TEST_ADDR) |
| FUNCTION vcpu_pause_end |
| |
| // Test that reading and writing cr0 works correctly. |
| FUNCTION vcpu_write_cr0_start |
| // Read cr0. |
| mov %cr0, %rax |
| |
| // Write cr0 and negate the NE bit. |
| and $~X86_CR0_NE, %rax |
| mov %rax, %cr0 |
| |
| // Test cr0 masking. NE bit should be set. |
| mov %cr0, %rax |
| |
| movq $0, (EXIT_TEST_ADDR) |
| FUNCTION vcpu_write_cr0_end |
| |
| // Test 32 bit compatibility mode (long mode disabled). |
| FUNCTION vcpu_compat_mode_start |
| init_gdt vcpu_compat_mode_start |
| movq $0, %rbx |
| movq $0, %rcx |
| |
| // Drop into compatibility mode |
| pushq $CODE_32_SELECTOR |
| lea vcpu_compat_mode_code(%rip),%rax |
| pushq %rax |
| lretq |
| |
| .code32 |
| vcpu_compat_mode_code: |
| // Hack to check if we're x86 or x64. In x86, 0x41 is `inc %ecx` and loop |
| // falls through. In x64, 0x41 is the REX.B prefix and %ecx is not |
| // incremented so loop jmps to vcpu_compat_mode_exit. |
| xor %ecx,%ecx |
| .byte 0x41 // x86: inc %ecx; x64: rex.B prefix |
| loop vcpu_compat_mode_exit |
| |
| // Write a value to ebx to check in guest.cpp |
| mov $1,%ebx |
| |
| // Go back to long mode |
| pushl $CODE_64_SELECTOR |
| pushl $ABSOLUTE_ADDR(vcpu_compat_mode_start, vcpu_compat_mode_test16) |
| lret |
| .code64 |
| |
| vcpu_compat_mode_test16: |
| // Drop into compatibility mode with 16 bit default operands. |
| pushq $CODE_16_SELECTOR |
| lea vcpu_compat_mode_code16(%rip),%rax |
| pushq %rax |
| lretq |
| |
| .code32 |
| vcpu_compat_mode_code16: |
| // The default operand size should be 16 bits and push should subtract 2 |
| // from the stack pointer. |
| mov %esp,%eax |
| sub $2,%eax |
| push $0 |
| cmp %esp,%eax |
| pop %eax |
| jne vcpu_compat_mode_code16_done |
| |
| // Write a value to ebx to check in guest.cpp |
| mov $2,%ecx |
| |
| // Go back to long mode |
| vcpu_compat_mode_code16_done: |
| pushl $CODE_64_SELECTOR |
| pushl $ABSOLUTE_ADDR(vcpu_compat_mode_start, vcpu_compat_mode_done) |
| lret |
| .code64 |
| |
| vcpu_compat_mode_done: |
| // Fix the data segment selectors |
| mov $DATA_SELECTOR,%rax |
| mov %rax,%ds |
| mov %rax,%es |
| mov %rax,%fs |
| mov %rax,%gs |
| mov %rax,%ss |
| |
| vcpu_compat_mode_exit: |
| movq $0, (EXIT_TEST_ADDR) |
| FUNCTION vcpu_compat_mode_end |
| |
| FUNCTION vcpu_syscall_start |
| init_interrupt_handling vcpu_syscall_start |
| |
| xor %eax, %eax |
| mov $((USER_CODE_32_SELECTOR << 16) | CODE_64_SELECTOR), %edx |
| mov $X86_MSR_IA32_STAR, %ecx |
| wrmsr |
| |
| xor %edx, %edx |
| mov $ABSOLUTE_ADDR(vcpu_syscall_start, vcpu_syscall_done), %eax |
| mov $X86_MSR_IA32_LSTAR, %ecx |
| wrmsr |
| |
| pushfq |
| pop %r11 |
| mov $ABSOLUTE_ADDR(vcpu_syscall_start, vcpu_syscall_ring3), %rcx |
| sysretq |
| |
| vcpu_syscall_ring3: |
| syscall |
| |
| vcpu_syscall_done: |
| movq $0, (EXIT_TEST_ADDR) |
| FUNCTION vcpu_syscall_end |
| |
| FUNCTION vcpu_sysenter_start |
| init_interrupt_handling vcpu_sysenter_start |
| |
| xor %edx, %edx |
| mov $CODE_64_SELECTOR, %eax |
| mov $X86_MSR_IA32_SYSENTER_CS, %ecx |
| wrmsr |
| |
| mov $ABSOLUTE_ADDR(vcpu_sysenter_start, vcpu_sysenter_done), %eax |
| mov $X86_MSR_IA32_SYSENTER_EIP, %ecx |
| wrmsr |
| |
| mov %esp, %eax |
| mov $X86_MSR_IA32_SYSENTER_ESP, %ecx |
| wrmsr |
| |
| mov %rsp, %rdx |
| mov $ABSOLUTE_ADDR(vcpu_sysenter_start, vcpu_sysenter_ring3), %rdx |
| .byte 0x48 // rex.W |
| sysexit |
| |
| vcpu_sysenter_ring3: |
| sysenter |
| |
| vcpu_sysenter_done: |
| movq $0, (EXIT_TEST_ADDR) |
| FUNCTION vcpu_sysenter_end |
| |
| FUNCTION vcpu_sysenter_compat_start |
| init_interrupt_handling vcpu_sysenter_compat_start |
| |
| xor %edx, %edx |
| mov $CODE_64_SELECTOR, %eax |
| mov $X86_MSR_IA32_SYSENTER_CS, %ecx |
| wrmsr |
| |
| mov $ABSOLUTE_ADDR(vcpu_sysenter_compat_start, vcpu_sysenter_compat_done), %eax |
| mov $X86_MSR_IA32_SYSENTER_EIP, %ecx |
| wrmsr |
| |
| mov %esp, %eax |
| mov $X86_MSR_IA32_SYSENTER_ESP, %ecx |
| wrmsr |
| |
| mov %rsp, %rdx |
| mov $ABSOLUTE_ADDR(vcpu_sysenter_compat_start, vcpu_sysenter_compat_ring3), %rdx |
| sysexit |
| |
| .code32 |
| vcpu_sysenter_compat_ring3: |
| sysenter |
| .code64 |
| |
| vcpu_sysenter_compat_done: |
| movq $0, (EXIT_TEST_ADDR) |
| FUNCTION vcpu_sysenter_compat_end |
| |
| FUNCTION vcpu_vmcall_start |
| movq $(INVALID_HYPERCALL), %rax |
| vmcall |
| movq $0, (EXIT_TEST_ADDR) |
| FUNCTION vcpu_vmcall_end |
| |
| FUNCTION vcpu_extended_registers_start |
| // Enable SSE instructions. |
| mov %cr4, %rax |
| or $(X86_CR4_OSFXSR), %rax |
| mov %rax, %cr4 |
| |
| // Store data in xmm0. |
| movdqu xmm_data(%rip), %xmm0 |
| |
| movq $0, (EXIT_TEST_ADDR) |
| |
| // Read data back from xmm0 into rax:rbx. |
| movq %xmm0, %rax |
| movhlps %xmm0, %xmm0 |
| movq %xmm0, %rbx |
| |
| movq $0, (EXIT_TEST_ADDR) |
| |
| // Disable SSE instructions |
| mov %cr4, %rax |
| and $(~(X86_CR4_OSFXSR)), %rax |
| mov %rax, %cr4 |
| |
| movq $0, (EXIT_TEST_ADDR) |
| // If we can get back here then the host correctly handled our sse state becoming reduced |
| movq $0, (EXIT_TEST_ADDR) |
| |
| .align 16 |
| xmm_data: |
| .4byte 0x01234567 |
| .4byte 0x89abcdef |
| .4byte 0xfedcba98 |
| .4byte 0x76543210 |
| FUNCTION vcpu_extended_registers_end |
| |
| // Test guest_set_trap using an IO-based trap. |
| FUNCTION guest_set_trap_with_io_start |
| out %al, $TRAP_PORT |
| movq $0, (EXIT_TEST_ADDR) |
| FUNCTION guest_set_trap_with_io_end |
| |