Files
MiniVisorPkg/Sources/Asm.asm
2020-03-08 20:46:45 -07:00

312 lines
9.2 KiB
NASM

;
; @file Asm.asm
;
; @brief Cross platform MASM-written functions.
;
; @author Satoshi Tanda
;
; @copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
;
include AsmCommon.inc
.const
VMX_OK equ 0
VMX_ERROR_WITH_STATUS equ 1
VMX_ERROR_WITHOUT_STATUS equ 2
KTRAP_FRAME_SIZE equ 190h
MACHINE_FRAME_SIZE equ 28h
.code
extern HandleVmExit : proc
extern HandleVmExitFailure : proc
;
; @brief The entry point for the hypervisor.
;
; @details The easiest way to understand this code is to see this as an entry
; point of "VM-exit handler".
;
; Up on VM-exit, the processor starts executing this function as
; configured in the Host RIP field of VMCS. When this function is executed,
; the processor is in the vmx-root mode, which allows the processor to
; execute any instructions without causing VM-exit, and the processor is
; not governed by EPT. The code executed from here most typically emulates
; the instruction caused VM-exit by executing the same instruction on behalf of
; the guest (see HandleCpuid for example), or changing relevant processor
; state and letting the guest retry, for example, handling EPT violation.
;
; What we refer to as hypervisor is basically code executed in this
; context. We also refer those code as a VM-exit handler.
;
AsmHypervisorEntryPoint proc frame
;
; Windows-specific:
;
; Three not-well known techniques are used in this function in oder for
; Windbg to display the stack trace of the guest while the VM-exit
; handlers are being executed. You can skip this comment block and ignore
; the first SUB instruction, .PUSHFRAME, .ALLOCSTACK and .ENDPROLOG if
; not interested in. This is not essential for the hypervisor.
;
; 1) The use of the FRAME (above) attribute. This emits a function table
; entry for this function in the .pdata section. See also:
; https://docs.microsoft.com/en-us/cpp/assembler/masm/proc?view=vs-2017
;
; 2) The use of the .PUSHFRAME pseudo operation. This emits unwind data
; indicating that a machine frame has been pushed on the stack. A machine
; frame is usually pushed by the CPU in response to a trap or fault (
; see: 6.12.1 Exception- or Interrupt-Handler Procedures), hence this
; pseudo operation is often used for their handler code. (In Windows
; kernel, the use of this pseudo operation is often wrapped in the
; GENERATE_TRAP_FRAME macro.) In our case, since VM-exit does not push
; the machine frame, we manually allocate it with the SUB instruction.
; See also:
; https://docs.microsoft.com/en-us/cpp/assembler/masm/dot-pushframe?view=vs-2017
;
; 3) The use of the .ALLOCSTACK pseudo operation. This also emits another
; unwind data indicating how much the function uses stack. (This pseudo
; code is often wrapped by the alloc_stack macro and used within the
; GENERATE_TRAP_FRAME macro.) This function consumes 100h of stack on
; the top of the KTRAP_FRAME size (minus the machine frame size, which
; is already indicated by the .PUSHFRAME). See also:
; https://docs.microsoft.com/en-us/cpp/assembler/masm/dot-allocstack?view=vs-2017
;
.pushframe
sub rsp, KTRAP_FRAME_SIZE
.allocstack KTRAP_FRAME_SIZE - MACHINE_FRAME_SIZE + 100h
;
; Save the general purpose registers as they are not saved to and loaded
; from VMCS. Note that the flag register does not have to be saved as it
; is saved to VMCS on VM-exit and loaded from there on VM-entry.
;
; This operation subtracts RSP 8 * 15.
;
PUSHAQ
;
; Save volatile XMM registers for the same reason as the general purpose
; registers.
;
; 0x60 for XMM registers and 8 for alignment. Remember that those SSE
; SEE instructions has to operate on 0x10 aligned memory.
;
sub rsp, 68h
movaps xmmword ptr [rsp + 0h], xmm0
movaps xmmword ptr [rsp + 10h], xmm1
movaps xmmword ptr [rsp + 20h], xmm2
movaps xmmword ptr [rsp + 30h], xmm3
movaps xmmword ptr [rsp + 40h], xmm4
movaps xmmword ptr [rsp + 50h], xmm5
;
; Save the current stack pointer as an argument of the HandleVmExit
; function.
;
mov rcx, rsp
;
; All stack allocation is done now. Indicate the end of prologue with the
; .ENDPROLOG pseudo operation as required by the FRAME attribute.
;
sub rsp, 20h
.endprolog
;
; BOOLEAN continueVm = HandleVmExit(stack);
;
call HandleVmExit
add rsp, 20h
;
; Restore XMM registers.
;
movaps xmm5, xmmword ptr [rsp + 50h]
movaps xmm4, xmmword ptr [rsp + 40h]
movaps xmm3, xmmword ptr [rsp + 30h]
movaps xmm2, xmmword ptr [rsp + 20h]
movaps xmm1, xmmword ptr [rsp + 10h]
movaps xmm0, xmmword ptr [rsp + 0h]
add rsp, 68h
;
; if (continueVm == 0) goto ExitVm
;
test al, al
jz ExitVm
;
; Otherwise, restore the general purpose registers and resume execution
; of the guest.
;
POPAQ
vmresume
jmp VmxError
ExitVm:
;
; Termination of the VM is requested. Executes VMXOFF and end
; virtualization. At this point, some registers have specific values:
; RAX = Shared processor context
; RCX = Guest RIP for the next instruction
; RDX = Guest RSP
; R8 = Guest RFLAGS
;
; Note that unlike VMRESUME, VMXOFF does not update RIP, RSP etc, and
; just continues the next instruction (but the processor is no longer in
; VMX-root mode). We will check if error occured with VMXOFF subsequently.
;
POPAQ
vmxoff
;
; if (ZF) goto VmxError
; if (CF) goto VmxError
;
jz VmxError
jc VmxError
;
; Restore RFLAGS, RSP, and jump to the next instruction.
;
push r8
popf
mov rsp, rdx
push rcx
ret
VmxError:
;
; VMRESUME or VMXOFF instruction failed. Unrecoverble. The most useful
; thing to do here is to call a C-function to diagnose the issue.
;
pushfq
PUSHAQ
mov rcx, rsp
sub rsp, 20h
call HandleVmExitFailure
jmp $
AsmHypervisorEntryPoint endp
;
; @brief Invalidates translations derived from EPT
;
; @param[in] RCX - The type of invalidation.
;
; @param[in] RDX - The description of translations to invalidate.
;
; @return An appropriate VMX_RESULT value.
;
AsmInvept proc
invept rcx, oword ptr [rdx]
;
; if (ZF) goto ErrorWithCode
; if (CF) goto ErrorWithoutCode
; return VMX_OK
;
jz ErrorWithCode
jc ErrorWithoutCode
xor rax, rax
ret
ErrorWithCode:
mov rax, VMX_ERROR_WITH_STATUS
ret
ErrorWithoutCode:
mov rax, VMX_ERROR_WITHOUT_STATUS
ret
AsmInvept endp
;
; @brief Invalidates translations based on VPID
;
; @param[in] RCX - The type of invalidation.
;
; @param[in] RDX - The description of translations to invalidate.
;
; @return An appropriate VMX_RESULT value.
;
AsmInvvpid proc
invvpid rcx, oword ptr [rdx]
;
; if (ZF) goto ErrorWithCode
; if (CF) goto ErrorWithoutCode
; return VMX_OK
;
jz ErrorWithCode
jc errorWithoutCode
xor rax, rax
ret
ErrorWithCode:
mov rax, VMX_ERROR_WITH_STATUS
ret
errorWithoutCode:
mov rax, VMX_ERROR_WITHOUT_STATUS
ret
AsmInvvpid endp
;
; @brief Reads the access rights byte of the segment.
;
; @details See: LAR-Load Access Rights Byte
;
; @param[in] RCX - The selector of the segment to read.
;
; @return The access rights byte of the segment, or 0 on failure.
;
AsmLoadAccessRightsByte proc
lar rax, rcx
jz Success
xor rax, rax
Success:
ret
AsmLoadAccessRightsByte endp
;
; @brief Issues hypercall.
;
; @param[in] RCX - The hypercall number.
;
; @param[in] RDX - The arbitrary 64bit parameter 1.
;
; @param[in] R8 - The arbitrary 64bit parameter 2.
;
; @param[in] R9 - The arbitrary 64bit parameter 3.
;
; @return The 64bit return value. Meaning is depends on RCX.
;
AsmVmxCall proc
vmcall
ret
AsmVmxCall endp
;
; @brief Returns the return address from this function.
;
; @return The return address from this function.
;
AsmGetCurrentInstructionPointer proc
mov rax, [rsp]
ret
AsmGetCurrentInstructionPointer endp
;
; @brief Returns the current value of RSP.
;
; @return The current value of RSP.
;
AsmGetCurrentStackPointer proc
mov rax, rsp
add rax, 8
ret
AsmGetCurrentStackPointer endp
end