; $Id: AMD64andLegacy.mac 16859 2009-02-17 16:19:51Z vboxsync $ ;; @file ; VMM - World Switchers, template for AMD64 to PAE and 32-bit. ; ; ; Copyright (C) 2006-2007 Sun Microsystems, Inc. ; ; This file is part of VirtualBox Open Source Edition (OSE), as ; available from http://www.virtualbox.org. This file is free software; ; you can redistribute it and/or modify it under the terms of the GNU ; General Public License (GPL) as published by the Free Software ; Foundation, in version 2 as it comes in the "COPYING" file of the ; VirtualBox OSE distribution. VirtualBox OSE is distributed in the ; hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. ; ; Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa ; Clara, CA 95054 USA or visit http://www.sun.com if you need ; additional information or have any questions. ; ;%define DEBUG_STUFF 1 ;%define STRICT_IF 1 ;******************************************************************************* ;* Header Files * ;******************************************************************************* %include "VBox/asmdefs.mac" %include "VBox/x86.mac" %include "VBox/cpum.mac" %include "VBox/stam.mac" %include "VBox/vm.mac" %include "CPUMInternal.mac" %include "VMMSwitcher/VMMSwitcher.mac" ; ; Start the fixup records ; We collect the fixups in the .data section as we go along ; It is therefore VITAL that no-one is using the .data section ; for anything else between 'Start' and 'End'. ; BEGINDATA GLOBALNAME Fixups BEGINCODE GLOBALNAME Start %ifndef VBOX_WITH_HYBRID_32BIT_KERNEL BITS 64 ;; ; The C interface. ; ; @param pVM GCC: rdi MSC:rcx The VM handle. ; BEGINPROC vmmR0HostToGuest %ifdef DEBUG_STUFF COM64_S_NEWLINE COM64_S_CHAR '^' %endif ; ; The ordinary version of the code. ; %ifdef STRICT_IF pushf pop rax test eax, X86_EFL_IF jz .if_clear_in mov eax, 0c0ffee00h ret .if_clear_in: %endif ; ; make r9 = pVM and rdx = pCpum. ; rax, rcx and r8 are scratch here after. %ifdef RT_OS_WINDOWS mov r9, rcx %else mov r9, rdi %endif lea rdx, [r9 + VM.cpum] %ifdef VBOX_WITH_STATISTICS ; ; Switcher stats. ; lea r8, [r9 + VM.StatSwitcherToGC] STAM64_PROFILE_ADV_START r8 %endif ; ; Call worker (far return). ; mov eax, cs push rax call NAME(vmmR0HostToGuestAsm) %ifdef VBOX_WITH_STATISTICS ; ; Switcher stats. ; lea r8, [r9 + VM.StatSwitcherToGC] STAM64_PROFILE_ADV_STOP r8 %endif ret ENDPROC vmmR0HostToGuest %else ; VBOX_WITH_HYBRID_32BIT_KERNEL BITS 32 ;; ; The C interface. ; BEGINPROC vmmR0HostToGuest %ifdef DEBUG_STUFF COM32_S_NEWLINE COM32_S_CHAR '^' %endif %ifdef VBOX_WITH_STATISTICS ; ; Switcher stats. ; FIXUP FIX_HC_VM_OFF, 1, VM.StatSwitcherToGC mov edx, 0ffffffffh STAM_PROFILE_ADV_START edx %endif ; Thunk to/from 64 bit when invoking the worker routine. ; FIXUP FIX_HC_VM_OFF, 1, VM.cpum mov edx, 0ffffffffh push 0 push cs push 0 FIXUP FIX_HC_32BIT, 1, .vmmR0HostToGuestReturn - NAME(Start) push 0ffffffffh FIXUP FIX_HC_64BIT_CS, 1 push 0ffffh FIXUP FIX_HC_32BIT, 1, NAME(vmmR0HostToGuestAsm) - NAME(Start) push 0ffffffffh retf .vmmR0HostToGuestReturn: ; ; This selector reloading is probably not necessary, but we do it anyway to be quite sure ; the CPU has the right idea about the selectors. ; mov edx, ds mov ds, edx mov ecx, es mov es, ecx mov edx, ss mov ss, edx %ifdef VBOX_WITH_STATISTICS ; ; Switcher stats. ; FIXUP FIX_HC_VM_OFF, 1, VM.StatSwitcherToHC mov edx, 0ffffffffh STAM_PROFILE_ADV_STOP edx %endif ret ENDPROC vmmR0HostToGuest BITS 64 %endif ;!VBOX_WITH_HYBRID_32BIT_KERNEL ; ***************************************************************************** ; vmmR0HostToGuestAsm ; ; Phase one of the switch from host to guest context (host MMU context) ; ; INPUT: ; - edx virtual address of CPUM structure (valid in host context) ; ; USES/DESTROYS: ; - eax, ecx, edx, r8 ; ; ASSUMPTION: ; - current CS and DS selectors are wide open ; ; ***************************************************************************** ALIGNCODE(16) BEGINPROC vmmR0HostToGuestAsm ;; Store the offset from CPUM to CPUMCPU in r8 mov r8, [rdx + CPUM.ulOffCPUMCPU] ;; ;; Save CPU host context ;; Skip eax, edx and ecx as these are not preserved over calls. ;; ; general registers. ; mov [rdx + r8 + CPUMCPU.Host.rax], rax - scratch mov [rdx + r8 + CPUMCPU.Host.rbx], rbx ; mov [rdx + r8 + CPUMCPU.Host.rcx], rcx - scratch ; mov [rdx + r8 + CPUMCPU.Host.rdx], rdx - scratch mov [rdx + r8 + CPUMCPU.Host.rdi], rdi mov [rdx + r8 + CPUMCPU.Host.rsi], rsi mov [rdx + r8 + CPUMCPU.Host.rsp], rsp mov [rdx + r8 + CPUMCPU.Host.rbp], rbp ; mov [rdx + r8 + CPUMCPU.Host.r8 ], r8 - scratch ; mov [rdx + r8 + CPUMCPU.Host.r9 ], r9 - scratch mov [rdx + r8 + CPUMCPU.Host.r10], r10 mov [rdx + r8 + CPUMCPU.Host.r11], r11 mov [rdx + r8 + CPUMCPU.Host.r12], r12 mov [rdx + r8 + CPUMCPU.Host.r13], r13 mov [rdx + r8 + CPUMCPU.Host.r14], r14 mov [rdx + r8 + CPUMCPU.Host.r15], r15 ; selectors. mov [rdx + r8 + CPUMCPU.Host.ds], ds mov [rdx + r8 + CPUMCPU.Host.es], es mov [rdx + r8 + CPUMCPU.Host.fs], fs mov [rdx + r8 + CPUMCPU.Host.gs], gs mov [rdx + r8 + CPUMCPU.Host.ss], ss ; MSRs mov rbx, rdx mov ecx, MSR_K8_FS_BASE rdmsr mov [rbx + r8 + CPUMCPU.Host.FSbase], eax mov [rbx + r8 + CPUMCPU.Host.FSbase + 4], edx mov ecx, MSR_K8_GS_BASE rdmsr mov [rbx + r8 + CPUMCPU.Host.GSbase], eax mov [rbx + r8 + CPUMCPU.Host.GSbase + 4], edx mov ecx, MSR_K6_EFER rdmsr mov [rbx + r8 + CPUMCPU.Host.efer], eax mov [rbx + r8 + CPUMCPU.Host.efer + 4], edx mov ecx, MSR_K6_EFER mov rdx, rbx ; special registers. sldt [rdx + r8 + CPUMCPU.Host.ldtr] sidt [rdx + r8 + CPUMCPU.Host.idtr] sgdt [rdx + r8 + CPUMCPU.Host.gdtr] str [rdx + r8 + CPUMCPU.Host.tr] ; yasm BUG, generates sldt. YASMCHECK! ; flags pushf pop qword [rdx + r8 + CPUMCPU.Host.rflags] FIXUP FIX_NO_SYSENTER_JMP, 0, htg_no_sysenter - NAME(Start) ; this will insert a jmp htg_no_sysenter if host doesn't use sysenter. ; save MSR_IA32_SYSENTER_CS register. mov ecx, MSR_IA32_SYSENTER_CS mov rbx, rdx ; save edx rdmsr ; edx:eax <- MSR[ecx] mov [rbx + r8 + CPUMCPU.Host.SysEnter.cs], rax mov [rbx + r8 + CPUMCPU.Host.SysEnter.cs + 4], rdx xor rax, rax ; load 0:0 to cause #GP upon sysenter xor rdx, rdx wrmsr mov rdx, rbx ; restore edx jmp short htg_no_sysenter ALIGNCODE(16) htg_no_sysenter: ;; handle use flags. mov esi, [rdx + r8 + CPUMCPU.fUseFlags] ; esi == use flags. and esi, ~CPUM_USED_FPU ; Clear CPUM_USED_* flags. ;;@todo FPU check can be optimized to use cr0 flags! mov [rdx + r8 + CPUMCPU.fUseFlags], esi ; debug registers. test esi, CPUM_USE_DEBUG_REGS | CPUM_USE_DEBUG_REGS_HOST jz htg_debug_regs_no jmp htg_debug_regs_save htg_debug_regs_no: DEBUG_CHAR('a') ; trashes esi ; control registers. mov rax, cr0 mov [rdx + r8 + CPUMCPU.Host.cr0], rax ;mov rax, cr2 ; assume host os don't stuff things in cr2. (safe) ;mov [rdx + r8 + CPUMCPU.Host.cr2], rax mov rax, cr3 mov [rdx + r8 + CPUMCPU.Host.cr3], rax mov rax, cr4 mov [rdx + r8 + CPUMCPU.Host.cr4], rax ;; ;; Start switching to VMM context. ;; ; ; Change CR0 and CR4 so we can correctly emulate FPU/MMX/SSE[23] exceptions ; Also disable WP. (eax==cr4 now) ; Note! X86_CR4_PSE and X86_CR4_PAE are important if the host thinks so :-) ; and rax, X86_CR4_MCE | X86_CR4_PSE | X86_CR4_PAE mov ecx, [rdx + r8 + CPUMCPU.Guest.cr4] DEBUG_CHAR('b') ; trashes esi ;; @todo Switcher cleanup: Determine base CR4 during CPUMR0Init / VMMR3SelectSwitcher putting it ; in CPUM.Hyper.cr4 (which isn't currently being used). That should ; simplify this operation a bit (and improve locality of the data). ; ; CR4.AndMask and CR4.OrMask are set in CPUMR3Init based on the presence of ; FXSAVE support on the host CPU ; and ecx, [rdx + CPUM.CR4.AndMask] or eax, ecx or eax, [rdx + CPUM.CR4.OrMask] mov cr4, rax DEBUG_CHAR('c') ; trashes esi mov eax, [rdx + r8 + CPUMCPU.Guest.cr0] and eax, X86_CR0_EM or eax, X86_CR0_PE | X86_CR0_PG | X86_CR0_TS | X86_CR0_ET | X86_CR0_NE | X86_CR0_MP mov cr0, rax DEBUG_CHAR('0') ; trashes esi ; Load new gdt so we can do far jump to guest code after cr3 reload. lgdt [rdx + CPUM.Hyper.gdtr] DEBUG_CHAR('1') ; trashes esi ; Store the hypervisor cr3 for later loading mov ebp, [rdx + CPUM.Hyper.cr3] ;; ;; Load Intermediate memory context. ;; FIXUP FIX_INTER_AMD64_CR3, 1 mov eax, 0ffffffffh mov cr3, rax DEBUG_CHAR('2') ; trashes esi ;; ;; 1. Switch to compatibility mode, placing ourselves in identity mapped code. ;; jmp far [NAME(fpIDEnterTarget) wrt rip] ; 16:32 Pointer to IDEnterTarget. NAME(fpIDEnterTarget): FIXUP FIX_ID_32BIT, 0, NAME(IDEnterTarget) - NAME(Start) dd 0 FIXUP FIX_HYPER_CS, 0 dd 0 ;; ; Detour for saving the host DR7 and DR6. ; esi and rdx must be preserved. htg_debug_regs_save: DEBUG_S_CHAR('s'); mov rax, dr7 ; not sure, but if I read the docs right this will trap if GD is set. FIXME!!! mov [rdx + r8 + CPUMCPU.Host.dr7], rax xor eax, eax ; clear everything. (bit 12? is read as 1...) mov dr7, rax mov rax, dr6 ; just in case we save the state register too. mov [rdx + r8 + CPUMCPU.Host.dr6], rax ; save host DR0-3? test esi, CPUM_USE_DEBUG_REGS jz near htg_debug_regs_no DEBUG_S_CHAR('S'); mov rax, dr0 mov [rdx + r8 + CPUMCPU.Host.dr0], rax mov rbx, dr1 mov [rdx + r8 + CPUMCPU.Host.dr1], rbx mov rcx, dr2 mov [rdx + r8 + CPUMCPU.Host.dr2], rcx mov rax, dr3 mov [rdx + r8 + CPUMCPU.Host.dr3], rax jmp htg_debug_regs_no ; We're now on an identity mapped pages! in 32-bit compatability mode. BITS 32 ALIGNCODE(16) GLOBALNAME IDEnterTarget DEBUG_CHAR('3') ; 2. Deactivate long mode by turning off paging. mov ebx, cr0 and ebx, ~X86_CR0_PG mov cr0, ebx DEBUG_CHAR('4') ; 3. Load intermediate page table. FIXUP SWITCHER_FIX_INTER_CR3_GC, 1 mov edx, 0ffffffffh mov cr3, edx ; 4. Disable long mode. ; We also use the chance to disable syscall/sysret and fast fxsave/fxrstor. mov ecx, MSR_K6_EFER rdmsr DEBUG_CHAR('5') and eax, ~(MSR_K6_EFER_LME | MSR_K6_EFER_SCE | MSR_K6_EFER_FFXSR) wrmsr DEBUG_CHAR('6') %ifndef SWITCHER_TO_PAE ; 4b. Disable PAE. mov eax, cr4 and eax, ~X86_CR4_PAE mov cr4, eax %else %endif ; 5. Enable paging. or ebx, X86_CR0_PG mov cr0, ebx jmp short just_a_jump just_a_jump: DEBUG_CHAR('7') ;; ;; 6. Jump to guest code mapping of the code and load the Hypervisor CS. ;; FIXUP FIX_ID_2_GC_NEAR_REL, 1, NAME(JmpGCTarget) - NAME(Start) jmp near NAME(JmpGCTarget) ;; ;; When we arrive at this label we're at the ;; guest code mapping of the switching code. ;; ALIGNCODE(16) GLOBALNAME JmpGCTarget DEBUG_CHAR('-') ;mov eax, 0ffff0000h ;.delay_loop: ;nop ;dec eax ;nop ;jnz .delay_loop ; load final cr3 and do far jump to load cs. mov cr3, ebp ; ebp set above DEBUG_CHAR('0') ;; ;; We're in VMM MMU context and VMM CS is loaded. ;; Setup the rest of the VMM state. ;; ; Load selectors DEBUG_CHAR('1') FIXUP FIX_HYPER_DS, 1 mov eax, 0ffffh mov ds, eax mov es, eax xor eax, eax mov gs, eax mov fs, eax ; Load pCpum into EDX FIXUP FIX_GC_CPUM_OFF, 1, 0 mov edx, 0ffffffffh ; Activate guest IDT DEBUG_CHAR('2') lidt [edx + CPUM.Hyper.idtr] ; Setup stack; use the lss_esp, ss pair for lss DEBUG_CHAR('3') mov eax, [edx + CPUM.Hyper.esp] mov [edx + CPUM.Hyper.lss_esp], eax lss esp, [edx + CPUM.Hyper.lss_esp] ; Restore TSS selector; must mark it as not busy before using ltr (!) DEBUG_CHAR('4') FIXUP FIX_GC_TSS_GDTE_DW2, 2 and dword [0ffffffffh], ~0200h ; clear busy flag (2nd type2 bit) DEBUG_CHAR('5') ltr word [edx + CPUM.Hyper.tr] DEBUG_CHAR('6') ; Activate the ldt (now we can safely crash). lldt [edx + CPUM.Hyper.ldtr] DEBUG_CHAR('7') ;; use flags. mov esi, [edx + CPUM.ulOffCPUMCPU] mov esi, [edx + esi + CPUMCPU.fUseFlags] ; debug registers test esi, CPUM_USE_DEBUG_REGS jz htg_debug_regs_guest_no jmp htg_debug_regs_guest htg_debug_regs_guest_no: DEBUG_CHAR('9') ; General registers. mov ebx, [edx + CPUM.Hyper.ebx] mov ebp, [edx + CPUM.Hyper.ebp] mov esi, [edx + CPUM.Hyper.esi] mov edi, [edx + CPUM.Hyper.edi] push dword [edx + CPUM.Hyper.eflags] popfd DEBUG_CHAR('!') ;; ;; Return to the VMM code which either called the switcher or ;; the code set up to run by HC. ;; %ifdef DEBUG_STUFF COM32_S_PRINT ';eip=' mov eax, [edx + CPUM.Hyper.eip] COM32_S_DWORD_REG eax COM32_S_CHAR ';' %endif mov eax, [edx + CPUM.Hyper.eip] %ifdef VBOX_WITH_STATISTICS FIXUP FIX_GC_VM_OFF, 1, VM.StatSwitcherToGC mov edx, 0ffffffffh STAM32_PROFILE_ADV_STOP edx FIXUP FIX_GC_CPUM_OFF, 1, 0 mov edx, 0ffffffffh %endif jmp eax ;; ; Detour for saving host DR0-3 and loading hypervisor debug registers. ; esi and edx must be preserved. htg_debug_regs_guest: DEBUG_S_CHAR('D') DEBUG_S_CHAR('R') DEBUG_S_CHAR('x') ; load hyper DR0-7 mov ebx, [edx + CPUM.Hyper.dr] mov dr0, ebx mov ecx, [edx + CPUM.Hyper.dr + 8*1] mov dr1, ecx mov eax, [edx + CPUM.Hyper.dr + 8*2] mov dr2, eax mov ebx, [edx + CPUM.Hyper.dr + 8*3] mov dr3, ebx ;mov eax, [edx + CPUM.Hyper.dr + 8*6] mov ecx, 0ffff0ff0h mov dr6, ecx mov eax, [edx + CPUM.Hyper.dr + 8*7] mov dr7, eax jmp htg_debug_regs_guest_no ENDPROC vmmR0HostToGuestAsm ;; ; Trampoline for doing a call when starting the hyper visor execution. ; ; Push any arguments to the routine. ; Push the argument frame size (cArg * 4). ; Push the call target (_cdecl convention). ; Push the address of this routine. ; ; ALIGNCODE(16) BEGINPROC vmmGCCallTrampoline %ifdef DEBUG_STUFF COM32_S_CHAR 'c' COM32_S_CHAR 't' COM32_S_CHAR '!' %endif ; call routine pop eax ; call address mov esi, edx ; save edx pop edi ; argument count. %ifdef DEBUG_STUFF COM32_S_PRINT ';eax=' COM32_S_DWORD_REG eax COM32_S_CHAR ';' %endif call eax ; do call add esp, edi ; cleanup stack ; return to the host context. push byte 0 ; eip mov edx, esi ; CPUM pointer %ifdef DEBUG_STUFF COM32_S_CHAR '`' %endif jmp NAME(VMMGCGuestToHostAsm) ; eax = returncode. ENDPROC vmmGCCallTrampoline ;; ; The C interface. ; ALIGNCODE(16) BEGINPROC vmmGCGuestToHost %ifdef DEBUG_STUFF push esi COM_NEWLINE DEBUG_CHAR('b') DEBUG_CHAR('a') DEBUG_CHAR('c') DEBUG_CHAR('k') DEBUG_CHAR('!') COM_NEWLINE pop esi %endif mov eax, [esp + 4] jmp NAME(VMMGCGuestToHostAsm) ENDPROC vmmGCGuestToHost ;; ; VMMGCGuestToHostAsmGuestCtx ; ; Switches from Guest Context to Host Context. ; Of course it's only called from within the GC. ; ; @param eax Return code. ; @param esp + 4 Pointer to CPUMCTXCORE. ; ; @remark ASSUMES interrupts disabled. ; ALIGNCODE(16) BEGINPROC VMMGCGuestToHostAsmGuestCtx DEBUG_CHAR('~') %ifdef VBOX_WITH_STATISTICS FIXUP FIX_GC_VM_OFF, 1, VM.StatTotalInGC mov edx, 0ffffffffh STAM32_PROFILE_ADV_STOP edx FIXUP FIX_GC_VM_OFF, 1, VM.StatTotalGCToQemu mov edx, 0ffffffffh STAM32_PROFILE_ADV_START edx FIXUP FIX_GC_VM_OFF, 1, VM.StatSwitcherToHC mov edx, 0ffffffffh STAM32_PROFILE_ADV_START edx %endif ; ; Load the CPUM pointer. ; FIXUP FIX_GC_CPUM_OFF, 1, 0 mov edx, 0ffffffffh ; Convert to CPUMCPU pointer add edx, [edx + CPUM.ulOffCPUMCPU] ; Skip return address (assumes called!) lea esp, [esp + 4] ; ; Guest Context (assumes esp now points to CPUMCTXCORE structure). ; ; general purpose registers push eax ; save return code. mov eax, [esp + 4 + CPUMCTXCORE.edi] mov [edx + CPUMCPU.Guest.edi], eax mov eax, [esp + 4 + CPUMCTXCORE.esi] mov [edx + CPUMCPU.Guest.esi], eax mov eax, [esp + 4 + CPUMCTXCORE.ebp] mov [edx + CPUMCPU.Guest.ebp], eax mov eax, [esp + 4 + CPUMCTXCORE.eax] mov [edx + CPUMCPU.Guest.eax], eax mov eax, [esp + 4 + CPUMCTXCORE.ebx] mov [edx + CPUMCPU.Guest.ebx], eax mov eax, [esp + 4 + CPUMCTXCORE.edx] mov [edx + CPUMCPU.Guest.edx], eax mov eax, [esp + 4 + CPUMCTXCORE.ecx] mov [edx + CPUMCPU.Guest.ecx], eax mov eax, [esp + 4 + CPUMCTXCORE.esp] mov [edx + CPUMCPU.Guest.esp], eax ; selectors mov eax, [esp + 4 + CPUMCTXCORE.ss] mov [edx + CPUMCPU.Guest.ss], eax mov eax, [esp + 4 + CPUMCTXCORE.gs] mov [edx + CPUMCPU.Guest.gs], eax mov eax, [esp + 4 + CPUMCTXCORE.fs] mov [edx + CPUMCPU.Guest.fs], eax mov eax, [esp + 4 + CPUMCTXCORE.es] mov [edx + CPUMCPU.Guest.es], eax mov eax, [esp + 4 + CPUMCTXCORE.ds] mov [edx + CPUMCPU.Guest.ds], eax mov eax, [esp + 4 + CPUMCTXCORE.cs] mov [edx + CPUMCPU.Guest.cs], eax ; flags mov eax, [esp + 4 + CPUMCTXCORE.eflags] mov [edx + CPUMCPU.Guest.eflags], eax ; eip mov eax, [esp + 4 + CPUMCTXCORE.eip] mov [edx + CPUMCPU.Guest.eip], eax ; jump to common worker code. pop eax ; restore return code. ; Load CPUM into edx again sub edx, [edx + CPUMCPU.ulOffCPUM] add esp, CPUMCTXCORE_size ; skip CPUMCTXCORE structure jmp vmmGCGuestToHostAsm_EIPDone ENDPROC VMMGCGuestToHostAsmGuestCtx ;; ; VMMGCGuestToHostAsmHyperCtx ; ; This is an alternative entry point which we'll be using ; when the we have the hypervisor context and need to save ; that before going to the host. ; ; This is typically useful when abandoning the hypervisor ; because of a trap and want the trap state to be saved. ; ; @param eax Return code. ; @param ecx Points to CPUMCTXCORE. ; @uses eax,edx,ecx ALIGNCODE(16) BEGINPROC VMMGCGuestToHostAsmHyperCtx DEBUG_CHAR('#') %ifdef VBOX_WITH_STATISTICS FIXUP FIX_GC_VM_OFF, 1, VM.StatTotalInGC mov edx, 0ffffffffh STAM32_PROFILE_ADV_STOP edx FIXUP FIX_GC_VM_OFF, 1, VM.StatTotalGCToQemu mov edx, 0ffffffffh STAM32_PROFILE_ADV_START edx FIXUP FIX_GC_VM_OFF, 1, VM.StatSwitcherToHC mov edx, 0ffffffffh STAM32_PROFILE_ADV_START edx %endif ; ; Load the CPUM pointer. ; FIXUP FIX_GC_CPUM_OFF, 1, 0 mov edx, 0ffffffffh push eax ; save return code. ; general purpose registers mov eax, [ecx + CPUMCTXCORE.edi] mov [edx + CPUM.Hyper.edi], eax mov eax, [ecx + CPUMCTXCORE.esi] mov [edx + CPUM.Hyper.esi], eax mov eax, [ecx + CPUMCTXCORE.ebp] mov [edx + CPUM.Hyper.ebp], eax mov eax, [ecx + CPUMCTXCORE.eax] mov [edx + CPUM.Hyper.eax], eax mov eax, [ecx + CPUMCTXCORE.ebx] mov [edx + CPUM.Hyper.ebx], eax mov eax, [ecx + CPUMCTXCORE.edx] mov [edx + CPUM.Hyper.edx], eax mov eax, [ecx + CPUMCTXCORE.ecx] mov [edx + CPUM.Hyper.ecx], eax mov eax, [ecx + CPUMCTXCORE.esp] mov [edx + CPUM.Hyper.esp], eax ; selectors mov eax, [ecx + CPUMCTXCORE.ss] mov [edx + CPUM.Hyper.ss], eax mov eax, [ecx + CPUMCTXCORE.gs] mov [edx + CPUM.Hyper.gs], eax mov eax, [ecx + CPUMCTXCORE.fs] mov [edx + CPUM.Hyper.fs], eax mov eax, [ecx + CPUMCTXCORE.es] mov [edx + CPUM.Hyper.es], eax mov eax, [ecx + CPUMCTXCORE.ds] mov [edx + CPUM.Hyper.ds], eax mov eax, [ecx + CPUMCTXCORE.cs] mov [edx + CPUM.Hyper.cs], eax ; flags mov eax, [ecx + CPUMCTXCORE.eflags] mov [edx + CPUM.Hyper.eflags], eax ; eip mov eax, [ecx + CPUMCTXCORE.eip] mov [edx + CPUM.Hyper.eip], eax ; jump to common worker code. pop eax ; restore return code. jmp vmmGCGuestToHostAsm_SkipHyperRegs ENDPROC VMMGCGuestToHostAsmHyperCtx ;; ; VMMGCGuestToHostAsm ; ; This is an alternative entry point which we'll be using ; when the we have saved the guest state already or we haven't ; been messing with the guest at all. ; ; @param eax Return code. ; @uses eax, edx, ecx (or it may use them in the future) ; ALIGNCODE(16) BEGINPROC VMMGCGuestToHostAsm DEBUG_CHAR('%') %ifdef VBOX_WITH_STATISTICS FIXUP FIX_GC_VM_OFF, 1, VM.StatTotalInGC mov edx, 0ffffffffh STAM32_PROFILE_ADV_STOP edx FIXUP FIX_GC_VM_OFF, 1, VM.StatTotalGCToQemu mov edx, 0ffffffffh STAM32_PROFILE_ADV_START edx FIXUP FIX_GC_VM_OFF, 1, VM.StatSwitcherToHC mov edx, 0ffffffffh STAM32_PROFILE_ADV_START edx %endif ; ; Load the CPUM pointer. ; FIXUP FIX_GC_CPUM_OFF, 1, 0 mov edx, 0ffffffffh pop dword [edx + CPUM.Hyper.eip] ; call return from stack jmp short vmmGCGuestToHostAsm_EIPDone ALIGNCODE(16) vmmGCGuestToHostAsm_EIPDone: ; general registers which we care about. mov dword [edx + CPUM.Hyper.ebx], ebx mov dword [edx + CPUM.Hyper.esi], esi mov dword [edx + CPUM.Hyper.edi], edi mov dword [edx + CPUM.Hyper.ebp], ebp mov dword [edx + CPUM.Hyper.esp], esp ; special registers which may change. vmmGCGuestToHostAsm_SkipHyperRegs: %ifdef STRICT_IF pushf pop ecx test ecx, X86_EFL_IF jz .if_clear_out mov eax, 0c0ffee01h cli .if_clear_out: %endif ; str [edx + CPUM.Hyper.tr] - double fault only, and it won't be right then either. sldt [edx + CPUM.Hyper.ldtr] ; No need to save CRx here. They are set dynamically according to Guest/Host requirements. ; FPU context is saved before restore of host saving (another) branch. ;; ;; Load Intermediate memory context. ;; mov edi, eax ; save return code in EDI (careful with COM_DWORD_REG from here on!) FIXUP SWITCHER_FIX_INTER_CR3_GC, 1 mov eax, 0ffffffffh mov cr3, eax DEBUG_CHAR('?') ;; We're now in intermediate memory context! ;; ;; 0. Jump to identity mapped location ;; FIXUP FIX_GC_2_ID_NEAR_REL, 1, NAME(IDExitTarget) - NAME(Start) jmp near NAME(IDExitTarget) ; We're now on identity mapped pages! ALIGNCODE(16) GLOBALNAME IDExitTarget DEBUG_CHAR('1') ; 1. Disable paging. mov ebx, cr0 and ebx, ~X86_CR0_PG mov cr0, ebx DEBUG_CHAR('2') ; 2. Enable PAE. %ifdef SWITCHER_TO_PAE ; - already enabled %else mov ecx, cr4 or ecx, X86_CR4_PAE mov cr4, ecx %endif ; 3. Load long mode intermediate CR3. FIXUP FIX_INTER_AMD64_CR3, 1 mov ecx, 0ffffffffh mov cr3, ecx DEBUG_CHAR('3') ; 4. Enable long mode. mov ebp, edx mov ecx, MSR_K6_EFER rdmsr or eax, MSR_K6_EFER_LME wrmsr mov edx, ebp DEBUG_CHAR('4') ; 5. Enable paging. or ebx, X86_CR0_PG mov cr0, ebx DEBUG_CHAR('5') ; Jump from compatability mode to 64-bit mode. FIXUP FIX_ID_FAR32_TO_64BIT_MODE, 1, NAME(IDExit64Mode) - NAME(Start) jmp 0ffffh:0fffffffeh ; ; We're in 64-bit mode (ds, ss, es, fs, gs are all bogus). ; Move on to the HC mapping. ; BITS 64 ALIGNCODE(16) NAME(IDExit64Mode): DEBUG_CHAR('6') jmp [NAME(pHCExitTarget) wrt rip] ; 64-bit jump target NAME(pHCExitTarget): FIXUP FIX_HC_64BIT, 0, NAME(HCExitTarget) - NAME(Start) dq 0ffffffffffffffffh ; 64-bit pCpum address. NAME(pCpumHC): FIXUP FIX_HC_64BIT_CPUM, 0 dq 0ffffffffffffffffh ; ; When we arrive here we're at the host context ; mapping of the switcher code. ; ALIGNCODE(16) GLOBALNAME HCExitTarget DEBUG_CHAR('9') ; Clear high dword of the CPUM pointer and rdx, 0ffffffffh ; Load the CPUMCPU offset. mov r8, [rdx + CPUM.ulOffCPUMCPU] ; load final cr3 mov rsi, [rdx + r8 + CPUMCPU.Host.cr3] mov cr3, rsi DEBUG_CHAR('@') ;; ;; Restore Host context. ;; ; Load CPUM pointer into edx mov rdx, [NAME(pCpumHC) wrt rip] ; Load the CPUMCPU offset. mov r8, [rdx + CPUM.ulOffCPUMCPU] ; activate host gdt and idt lgdt [rdx + r8 + CPUMCPU.Host.gdtr] DEBUG_CHAR('0') lidt [rdx + r8 + CPUMCPU.Host.idtr] DEBUG_CHAR('1') ; Restore TSS selector; must mark it as not busy before using ltr (!) %if 1 ; ASSUME that this is supposed to be 'BUSY'. (saves 20-30 ticks on the T42p) movzx eax, word [rdx + r8 + CPUMCPU.Host.tr] ; eax <- TR and al, 0F8h ; mask away TI and RPL bits, get descriptor offset. add rax, [rdx + r8 + CPUMCPU.Host.gdtr + 2] ; eax <- GDTR.address + descriptor offset. and dword [rax + 4], ~0200h ; clear busy flag (2nd type2 bit) ltr word [rdx + r8 + CPUMCPU.Host.tr] %else movzx eax, word [rdx + r8 + CPUMCPU.Host.tr] ; eax <- TR and al, 0F8h ; mask away TI and RPL bits, get descriptor offset. add rax, [rdx + r8 + CPUMCPU.Host.gdtr + 2] ; eax <- GDTR.address + descriptor offset. mov ecx, [rax + 4] ; ecx <- 2nd descriptor dword mov ebx, ecx ; save orginal value and ecx, ~0200h ; clear busy flag (2nd type2 bit) mov [rax + 4], ccx ; not using xchg here is paranoia.. ltr word [rdx + r8 + CPUMCPU.Host.tr] xchg [rax + 4], ebx ; using xchg is paranoia too... %endif ; activate ldt DEBUG_CHAR('2') lldt [rdx + r8 + CPUMCPU.Host.ldtr] ; Restore segment registers mov eax, [rdx + r8 + CPUMCPU.Host.ds] mov ds, eax mov eax, [rdx + r8 + CPUMCPU.Host.es] mov es, eax mov eax, [rdx + r8 + CPUMCPU.Host.fs] mov fs, eax mov eax, [rdx + r8 + CPUMCPU.Host.gs] mov gs, eax ; restore stack mov eax, [rdx + r8 + CPUMCPU.Host.ss] mov ss, eax mov rsp, [rdx + r8 + CPUMCPU.Host.rsp] FIXUP FIX_NO_SYSENTER_JMP, 0, gth_sysenter_no - NAME(Start) ; this will insert a jmp gth_sysenter_no if host doesn't use sysenter. ; restore MSR_IA32_SYSENTER_CS register. mov ecx, MSR_IA32_SYSENTER_CS mov eax, [rdx + r8 + CPUMCPU.Host.SysEnter.cs] mov ebx, [rdx + r8 + CPUMCPU.Host.SysEnter.cs + 4] mov rbx, rdx ; save/load edx wrmsr ; MSR[ecx] <- edx:eax mov rdx, rbx ; restore edx jmp short gth_sysenter_no ALIGNCODE(16) gth_sysenter_no: ;; @todo AMD syscall ; Restore FPU if guest has used it. ; Using fxrstor should ensure that we're not causing unwanted exception on the host. mov esi, [rdx + r8 + CPUMCPU.fUseFlags] ; esi == use flags. test esi, CPUM_USED_FPU jz short gth_fpu_no mov rcx, cr0 and rcx, ~(X86_CR0_TS | X86_CR0_EM) mov cr0, rcx fxsave [rdx + r8 + CPUMCPU.Guest.fpu] fxrstor [rdx + r8 + CPUMCPU.Host.fpu] jmp short gth_fpu_no ALIGNCODE(16) gth_fpu_no: ; Control registers. ; Would've liked to have these highere up in case of crashes, but ; the fpu stuff must be done before we restore cr0. mov rcx, [rdx + r8 + CPUMCPU.Host.cr4] mov cr4, rcx mov rcx, [rdx + r8 + CPUMCPU.Host.cr0] mov cr0, rcx ;mov rcx, [rdx + r8 + CPUMCPU.Host.cr2] ; assumes this is waste of time. ;mov cr2, rcx ; restore debug registers (if modified) (esi must still be fUseFlags!) ; (must be done after cr4 reload because of the debug extension.) test esi, CPUM_USE_DEBUG_REGS | CPUM_USE_DEBUG_REGS_HOST jz short gth_debug_regs_no jmp gth_debug_regs_restore gth_debug_regs_no: ; Restore MSRs mov rbx, rdx mov ecx, MSR_K8_FS_BASE mov eax, [rbx + r8 + CPUMCPU.Host.FSbase] mov edx, [rbx + r8 + CPUMCPU.Host.FSbase + 4] wrmsr mov ecx, MSR_K8_GS_BASE mov eax, [rbx + r8 + CPUMCPU.Host.GSbase] mov edx, [rbx + r8 + CPUMCPU.Host.GSbase + 4] wrmsr mov ecx, MSR_K6_EFER mov eax, [rbx + r8 + CPUMCPU.Host.efer] mov edx, [rbx + r8 + CPUMCPU.Host.efer + 4] wrmsr mov rdx, rbx ; restore general registers. mov eax, edi ; restore return code. eax = return code !! ; mov rax, [rdx + r8 + CPUMCPU.Host.rax] - scratch + return code mov rbx, [rdx + r8 + CPUMCPU.Host.rbx] ; mov rcx, [rdx + r8 + CPUMCPU.Host.rcx] - scratch ; mov rdx, [rdx + r8 + CPUMCPU.Host.rdx] - scratch mov rdi, [rdx + r8 + CPUMCPU.Host.rdi] mov rsi, [rdx + r8 + CPUMCPU.Host.rsi] mov rsp, [rdx + r8 + CPUMCPU.Host.rsp] mov rbp, [rdx + r8 + CPUMCPU.Host.rbp] ; mov r8, [rdx + r8 + CPUMCPU.Host.r8 ] - scratch ; mov r9, [rdx + r8 + CPUMCPU.Host.r9 ] - scratch mov r10, [rdx + r8 + CPUMCPU.Host.r10] mov r11, [rdx + r8 + CPUMCPU.Host.r11] mov r12, [rdx + r8 + CPUMCPU.Host.r12] mov r13, [rdx + r8 + CPUMCPU.Host.r13] mov r14, [rdx + r8 + CPUMCPU.Host.r14] mov r15, [rdx + r8 + CPUMCPU.Host.r15] ; finally restore flags. (probably not required) push qword [rdx + r8 + CPUMCPU.Host.rflags] popf %ifdef DEBUG_STUFF COM64_S_CHAR '4' %endif db 048h retf ;; ; Detour for restoring the host debug registers. ; edx and edi must be preserved. gth_debug_regs_restore: DEBUG_S_CHAR('d') xor eax, eax mov dr7, rax ; paranoia or not? test esi, CPUM_USE_DEBUG_REGS jz short gth_debug_regs_dr7 DEBUG_S_CHAR('r') mov rax, [rdx + r8 + CPUMCPU.Host.dr0] mov dr0, rax mov rbx, [rdx + r8 + CPUMCPU.Host.dr1] mov dr1, rbx mov rcx, [rdx + r8 + CPUMCPU.Host.dr2] mov dr2, rcx mov rax, [rdx + r8 + CPUMCPU.Host.dr3] mov dr3, rax gth_debug_regs_dr7: mov rbx, [rdx + r8 + CPUMCPU.Host.dr6] mov dr6, rbx mov rcx, [rdx + r8 + CPUMCPU.Host.dr7] mov dr7, rcx jmp gth_debug_regs_no ENDPROC VMMGCGuestToHostAsm GLOBALNAME End ; ; The description string (in the text section). ; NAME(Description): db SWITCHER_DESCRIPTION db 0 extern NAME(Relocate) ; ; End the fixup records. ; BEGINDATA db FIX_THE_END ; final entry. GLOBALNAME FixupsEnd ;; ; The switcher definition structure. ALIGNDATA(16) GLOBALNAME Def istruc VMMSWITCHERDEF at VMMSWITCHERDEF.pvCode, RTCCPTR_DEF NAME(Start) at VMMSWITCHERDEF.pvFixups, RTCCPTR_DEF NAME(Fixups) at VMMSWITCHERDEF.pszDesc, RTCCPTR_DEF NAME(Description) at VMMSWITCHERDEF.pfnRelocate, RTCCPTR_DEF NAME(Relocate) at VMMSWITCHERDEF.enmType, dd SWITCHER_TYPE at VMMSWITCHERDEF.cbCode, dd NAME(End) - NAME(Start) at VMMSWITCHERDEF.offR0HostToGuest, dd NAME(vmmR0HostToGuest) - NAME(Start) at VMMSWITCHERDEF.offGCGuestToHost, dd NAME(vmmGCGuestToHost) - NAME(Start) at VMMSWITCHERDEF.offGCCallTrampoline, dd NAME(vmmGCCallTrampoline) - NAME(Start) at VMMSWITCHERDEF.offGCGuestToHostAsm, dd NAME(VMMGCGuestToHostAsm) - NAME(Start) at VMMSWITCHERDEF.offGCGuestToHostAsmHyperCtx, dd NAME(VMMGCGuestToHostAsmHyperCtx)- NAME(Start) at VMMSWITCHERDEF.offGCGuestToHostAsmGuestCtx, dd NAME(VMMGCGuestToHostAsmGuestCtx)- NAME(Start) ; disasm help at VMMSWITCHERDEF.offHCCode0, dd 0 at VMMSWITCHERDEF.cbHCCode0, dd NAME(IDEnterTarget) - NAME(Start) at VMMSWITCHERDEF.offHCCode1, dd NAME(HCExitTarget) - NAME(Start) at VMMSWITCHERDEF.cbHCCode1, dd NAME(End) - NAME(HCExitTarget) at VMMSWITCHERDEF.offIDCode0, dd NAME(IDEnterTarget) - NAME(Start) at VMMSWITCHERDEF.cbIDCode0, dd NAME(JmpGCTarget) - NAME(IDEnterTarget) at VMMSWITCHERDEF.offIDCode1, dd NAME(IDExitTarget) - NAME(Start) at VMMSWITCHERDEF.cbIDCode1, dd NAME(HCExitTarget) - NAME(IDExitTarget) at VMMSWITCHERDEF.offGCCode, dd NAME(JmpGCTarget) - NAME(Start) at VMMSWITCHERDEF.cbGCCode, dd NAME(IDExitTarget) - NAME(JmpGCTarget) iend