Publish the files
This commit is contained in:
315
Sources/Asm.asm
Normal file
315
Sources/Asm.asm
Normal file
@@ -0,0 +1,315 @@
|
||||
;
|
||||
; @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 An 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
|
||||
; condifured in the VmcsHostRip field of VMCS. At this time, 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 emulates the instruction caused
|
||||
; VM-exit by, most typically, 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
|
||||
;
|
||||
; 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 xmm0, xmmword ptr [rsp + 0h]
|
||||
movaps xmm1, xmmword ptr [rsp + 10h]
|
||||
movaps xmm2, xmmword ptr [rsp + 20h]
|
||||
movaps xmm3, xmmword ptr [rsp + 30h]
|
||||
movaps xmm4, xmmword ptr [rsp + 40h]
|
||||
movaps xmm5, xmmword ptr [rsp + 50h]
|
||||
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 = VpContexts
|
||||
; 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:
|
||||
;
|
||||
; Any of VMX instructions failed. Unrecoverble. The most useful thing
|
||||
; to do here is probably to call a C-function that does diagnostics
|
||||
; like dumping VMCS.
|
||||
;
|
||||
PUSHAQ
|
||||
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
|
||||
mov rcx, rsp
|
||||
sub rsp, 20h
|
||||
call HandleVmExitFailure
|
||||
AsmHypervisorEntryPoint endp
|
||||
|
||||
;
|
||||
; @brief Invalidate translations derived from EPT
|
||||
;
|
||||
; @param[in] RCX - A type of invalidation.
|
||||
;
|
||||
; @param[in] RDX - A 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 Invalidate translations based on VPID
|
||||
;
|
||||
; @param[in] RCX - A type of invalidation.
|
||||
;
|
||||
; @param[in] RDX - A 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 address of the return address from this function.
|
||||
;
|
||||
; @return The address of 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
|
||||
113
Sources/Asm.h
Normal file
113
Sources/Asm.h
Normal file
@@ -0,0 +1,113 @@
|
||||
/*!
|
||||
@file Asm.h
|
||||
|
||||
@brief MASM-written functions.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#pragma once
|
||||
#include "Common.h"
|
||||
|
||||
#if defined(NTDDI_VERSION)
|
||||
#include "Platform/Windows/WinAsm.h"
|
||||
#else
|
||||
#include "Platform/EFI/EfiAsm.h"
|
||||
#endif
|
||||
|
||||
/*!
|
||||
@brief An entry point for the hypervisor.
|
||||
|
||||
@details See x64.asm.
|
||||
*/
|
||||
VOID
|
||||
AsmHypervisorEntryPoint (
|
||||
VOID
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Invalidate translations derived from EPT.
|
||||
|
||||
@param[in] InvEptType - A type of invalidation.
|
||||
|
||||
@param[in] InvEptDescriptor - A description of translations to invalidate.
|
||||
|
||||
@return An appropriate VMX_RESULT value.
|
||||
*/
|
||||
VMX_RESULT
|
||||
AsmInvept (
|
||||
_In_ INVEPT_TYPE InvEptType,
|
||||
_In_ CONST INVEPT_DESCRIPTOR* InvEptDescriptor
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Invalidate translations based on VPID.
|
||||
|
||||
@param[in] InvVpidType - A type of invalidation.
|
||||
|
||||
@param[in] InvVpidDescriptor - A description of translations to invalidate.
|
||||
|
||||
@return An appropriate VMX_RESULT value.
|
||||
*/
|
||||
VMX_RESULT
|
||||
AsmInvvpid (
|
||||
_In_ INVVPID_TYPE InvVpidType,
|
||||
_In_ CONST INVVPID_DESCRIPTOR* InvVpidDescriptor
|
||||
);
|
||||
|
||||
|
||||
/*!
|
||||
@brief Reads the access rights byte of the segment.
|
||||
|
||||
@details See: LAR-Load Access Rights Byte
|
||||
|
||||
@param[in] SegmentSelector - The selector of the segment to read.
|
||||
|
||||
@return The access rights byte of the segment, or 0 on failure.
|
||||
*/
|
||||
UINT32
|
||||
AsmLoadAccessRightsByte (
|
||||
_In_ UINT16 SegmentSelector
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Issues hypercall.
|
||||
|
||||
@param[in] HyperCallNumber - The hypercall number.
|
||||
|
||||
@param[in] Parameter1 - The arbitrary 64bit parameter 1.
|
||||
|
||||
@param[in] Parameter2 - The arbitrary 64bit parameter 2.
|
||||
|
||||
@param[in] Parameter3 - The arbitrary 64bit parameter 3.
|
||||
|
||||
@return The 64bit return value. Meaning is depends on HyperCallNumber.
|
||||
*/
|
||||
UINT64
|
||||
AsmVmxCall (
|
||||
_In_ UINT64 HyperCallNumber,
|
||||
_In_ UINT64 Parameter1,
|
||||
_In_ UINT64 Parameter2,
|
||||
_In_ UINT64 Parameter3
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Returns the address of the return address from this function.
|
||||
|
||||
@return The address of the return address from this function.
|
||||
*/
|
||||
UINT64
|
||||
AsmGetCurrentInstructionPointer (
|
||||
VOID
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Returns the current value of RSP.
|
||||
|
||||
@return The current value of RSP.
|
||||
*/
|
||||
UINT64
|
||||
AsmGetCurrentStackPointer (
|
||||
VOID
|
||||
);
|
||||
55
Sources/AsmCommon.inc
Normal file
55
Sources/AsmCommon.inc
Normal file
@@ -0,0 +1,55 @@
|
||||
;
|
||||
; @file AsmCommon.inc
|
||||
;
|
||||
; @brief Cross platform MASM-written marcos.
|
||||
;
|
||||
; @author Satoshi Tanda
|
||||
;
|
||||
; @copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
;
|
||||
|
||||
;
|
||||
; @brief Saves all general purpose registers, except for RSP, to the stack.
|
||||
;
|
||||
; @details This macro does not alter the flag register.
|
||||
;
|
||||
PUSHAQ macro
|
||||
push rax
|
||||
push rcx
|
||||
push rdx
|
||||
push rbx
|
||||
push rbp
|
||||
push rsi
|
||||
push rdi
|
||||
push r8
|
||||
push r9
|
||||
push r10
|
||||
push r11
|
||||
push r12
|
||||
push r13
|
||||
push r14
|
||||
push r15
|
||||
endm
|
||||
|
||||
;
|
||||
; @brief Loads all general purpose registers, except for RSP, from the stack.
|
||||
;
|
||||
; @details This macro does not alter the flag register.
|
||||
;
|
||||
POPAQ macro
|
||||
pop r15
|
||||
pop r14
|
||||
pop r13
|
||||
pop r12
|
||||
pop r11
|
||||
pop r10
|
||||
pop r9
|
||||
pop r8
|
||||
pop rdi
|
||||
pop rsi
|
||||
pop rbp
|
||||
pop rbx
|
||||
pop rdx
|
||||
pop rcx
|
||||
pop rax
|
||||
endm
|
||||
72
Sources/Common.h
Normal file
72
Sources/Common.h
Normal file
@@ -0,0 +1,72 @@
|
||||
/*!
|
||||
@file Common.h
|
||||
|
||||
@brief Common things across the project.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#pragma once
|
||||
#if defined(NTDDI_VERSION)
|
||||
#include "Platform/Windows/WinCommon.h"
|
||||
#else
|
||||
#include "Platform/EFI/EfiCommon.h"
|
||||
#endif
|
||||
#include "Ia32.h"
|
||||
|
||||
//
|
||||
// The platform agnostic status type.
|
||||
//
|
||||
typedef _Return_type_success_(return >= 0) long MV_STATUS;
|
||||
|
||||
//
|
||||
// Possible status values.
|
||||
//
|
||||
#define MV_STATUS_SUCCESS ((MV_STATUS)0x00000000L)
|
||||
#define MV_STATUS_UNSUCCESSFUL ((MV_STATUS)0xC0000001L)
|
||||
#define MV_STATUS_ACCESS_DENIED ((MV_STATUS)0xC0000022L)
|
||||
#define MV_STATUS_INSUFFICIENT_RESOURCES ((MV_STATUS)0xC000009AL)
|
||||
#define MV_STATUS_HV_OPERATION_FAILED ((MV_STATUS)0xC0350071L)
|
||||
|
||||
//
|
||||
// The status check macro(s).
|
||||
//
|
||||
#define MV_ERROR(Status) ((UINT32)(Status) >= (UINT32)0xc0000000)
|
||||
|
||||
//
|
||||
// Computes offsets from the given pointer.
|
||||
//
|
||||
#define MV_ADD2PTR(Ptr, Value) ((VOID*)((UINT8*)(Ptr) + (Value)))
|
||||
|
||||
//
|
||||
// Hyper-V Hypervisor Top-Level Functional Specification (TLFS) related.
|
||||
//
|
||||
#define CPUID_HV_VENDOR_AND_MAX_FUNCTIONS ((UINT32)0x40000000)
|
||||
#define CPUID_HV_INTERFACE ((UINT32)0x40000001)
|
||||
#define CPUID_HV_MAX CPUID_HV_INTERFACE
|
||||
|
||||
//
|
||||
// Indicates the invalid physical address.
|
||||
//
|
||||
#define MV_INVALID_PHYSICAL_ADDRESS ((UINT64)-1)
|
||||
|
||||
//
|
||||
// Replacement of BOOLEAN for the flag to indicate whether the operation is read
|
||||
// or write.
|
||||
//
|
||||
typedef enum _OPERATION_TYPE
|
||||
{
|
||||
OperationRead,
|
||||
OperationWrite,
|
||||
} OPERATION_TYPE;
|
||||
|
||||
//
|
||||
// The result type of Microsoft VMX-intrinsic functions.
|
||||
//
|
||||
typedef enum _VMX_RESULT
|
||||
{
|
||||
VmxResultOk = 0, //!< Operation succeeded
|
||||
VmxResultErrorWithStatus = 1, //!< Operation failed with extended status available
|
||||
VmxResultErrorWithoutStatus = 2, //!< Operation failed without status available
|
||||
} VMX_RESULT;
|
||||
480
Sources/ExtendedPageTables.c
Normal file
480
Sources/ExtendedPageTables.c
Normal file
@@ -0,0 +1,480 @@
|
||||
/*!
|
||||
@file ExtendedPageTables.c
|
||||
|
||||
@brief Functions for EPT handling.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#include "ExtendedPageTables.h"
|
||||
#include "Asm.h"
|
||||
#include "Ia32.h"
|
||||
#include "MemoryManager.h"
|
||||
#include "Platform.h"
|
||||
#include "Logger.h"
|
||||
#include "MemoryType.h"
|
||||
#include "Utils.h"
|
||||
|
||||
//
|
||||
// The set of EPT paging structure entries involved with to translate the GPA.
|
||||
//
|
||||
typedef struct _EPT_ENTRIES
|
||||
{
|
||||
EPT_PML4* Pml4e;
|
||||
union
|
||||
{
|
||||
EPDPTE_1GB* AsLargePage;
|
||||
EPDPTE* AsRegularPage;
|
||||
} Pdpte;
|
||||
union
|
||||
{
|
||||
EPDE_2MB* AsLargePage;
|
||||
EPDE* AsRegularPage;
|
||||
} Pde;
|
||||
EPTE* Pte;
|
||||
} EPT_ENTRIES;
|
||||
C_ASSERT(sizeof(EPT_ENTRIES) == sizeof(VOID*) * 4);
|
||||
|
||||
/*!
|
||||
@brief Cleans up all EPT entries and the table recursively.
|
||||
|
||||
@param[in,out] EptTable - A pointer to the EPT table to clean up.
|
||||
|
||||
@param[in] PageMapLevel - A level of the table.
|
||||
*/
|
||||
static
|
||||
VOID
|
||||
CleanupTables (
|
||||
_Inout_ _Pre_notnull_ EPT_ENTRY* EptTable,
|
||||
_In_ UINT32 PageMapLevel
|
||||
)
|
||||
{
|
||||
//
|
||||
// EPT PT does not have any subtables to delete, and so attempting so is
|
||||
// invalid.
|
||||
//
|
||||
MV_ASSERT(PageMapLevel != EPT_LEVEL_PTE);
|
||||
|
||||
//
|
||||
// Go through all 512 entries in the table.
|
||||
//
|
||||
for (UINT32 i = 0; i < 512; ++i)
|
||||
{
|
||||
EPT_ENTRY eptEntry;
|
||||
VOID* subTable;
|
||||
|
||||
eptEntry = EptTable[i];
|
||||
|
||||
//
|
||||
// Go to the next entry of the table if the entry is not initialized or
|
||||
// is a large page entry, which does not point to the next table.
|
||||
//
|
||||
if ((eptEntry.PageFrameNumber == 0) ||
|
||||
(eptEntry.LargePage != FALSE))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
//
|
||||
// Get the address of the subtable. Free it if it is EPT PTE (ie, the
|
||||
// current table is EPT PD) as EPT PTE does not have any more subtables.
|
||||
// Otherwise, perform the same operations against the subtable.
|
||||
//
|
||||
subTable = GetVirtualAddress(eptEntry.PageFrameNumber << PAGE_SHIFT);
|
||||
if (PageMapLevel == EPT_LEVEL_PDE)
|
||||
{
|
||||
MmFreePages(subTable);
|
||||
}
|
||||
else
|
||||
{
|
||||
CleanupTables(subTable, PageMapLevel - 1);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// All subtables referenced from this table were freed. It is OK to free
|
||||
// this table as well.
|
||||
//
|
||||
MmFreePages(EptTable);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Builds EPT paging structures for the range of 512GB managed by the
|
||||
given EPT PML4E.
|
||||
|
||||
@param[in,out] EptPml4 - The pointer to the EPT PML4.
|
||||
|
||||
@param[in] EptPml4Index - The index within the EPT PML4 to build translation.
|
||||
|
||||
@return MV_STATUS_SUCCESS on success; otherwise, an appropriate error code.
|
||||
*/
|
||||
static
|
||||
_Must_inspect_result_
|
||||
MV_STATUS
|
||||
BuildEptEntriesFor512Gb (
|
||||
_Inout_ EPT_PML4* EptPml4,
|
||||
_In_ UINT32 EptPml4Index
|
||||
)
|
||||
{
|
||||
static CONST UINT64 oneGigaByte = (1 * 1024 * 1024 * 1024);
|
||||
static CONST UINT64 twoMegaByte = (2 * 1024 * 1024);
|
||||
|
||||
MV_STATUS status;
|
||||
EPT_ENTRY defaultPermissions;
|
||||
UINT64 hostPhysicalAddress;
|
||||
IA32_MEMORY_TYPE memoryType;
|
||||
EPT_ENTRIES eptEntries;
|
||||
EPDPTE* eptPdpt;
|
||||
EPDE_2MB* eptPd;
|
||||
EPTE* eptPt;
|
||||
|
||||
defaultPermissions.Flags = 0;
|
||||
defaultPermissions.ReadAccess = TRUE;
|
||||
defaultPermissions.WriteAccess = TRUE;
|
||||
defaultPermissions.ExecuteAccess = TRUE;
|
||||
|
||||
//
|
||||
// Allocate the EPT PDPT and fill with the default all allow permissions, and
|
||||
// initialize the EPT PML4 with it.
|
||||
//
|
||||
eptPdpt = MmAllocatePages(1);
|
||||
if (eptPdpt == NULL)
|
||||
{
|
||||
status = MV_STATUS_INSUFFICIENT_RESOURCES;
|
||||
goto Exit;
|
||||
}
|
||||
__stosq((UINT64*)eptPdpt, defaultPermissions.Flags, EPT_PDPTE_ENTRY_COUNT);
|
||||
|
||||
eptEntries.Pml4e = &EptPml4[EptPml4Index];
|
||||
eptEntries.Pml4e->ReadAccess = TRUE;
|
||||
eptEntries.Pml4e->WriteAccess = TRUE;
|
||||
eptEntries.Pml4e->ExecuteAccess = TRUE;
|
||||
eptEntries.Pml4e->PageFrameNumber = (GetPhysicalAddress(eptPdpt) >> PAGE_SHIFT);
|
||||
|
||||
//
|
||||
// Initialize all 512 entries in the EPT PDPT pointed by the EPT PML4E.
|
||||
//
|
||||
for (UINT32 eptPdptIndex = 0; eptPdptIndex < EPT_PDPTE_ENTRY_COUNT; ++eptPdptIndex)
|
||||
{
|
||||
//
|
||||
// Allocate the EPT PD and fill with the default all allow permissions,
|
||||
// and initialize the EPT PDTE with it.
|
||||
//
|
||||
eptPd = MmAllocatePages(1);
|
||||
if (eptPd == NULL)
|
||||
{
|
||||
status = MV_STATUS_INSUFFICIENT_RESOURCES;
|
||||
goto Exit;
|
||||
}
|
||||
__stosq((UINT64*)eptPd, defaultPermissions.Flags, EPT_PDE_ENTRY_COUNT);
|
||||
|
||||
eptEntries.Pdpte.AsRegularPage = &eptPdpt[eptPdptIndex];
|
||||
eptEntries.Pdpte.AsRegularPage->PageFrameNumber = (GetPhysicalAddress(eptPd) >> PAGE_SHIFT);
|
||||
|
||||
//
|
||||
// Initialize all 512 entries in the EPT PD pointed by the EPT PDPTE.
|
||||
//
|
||||
for (UINT32 eptPdIndex = 0; eptPdIndex < EPT_PDE_ENTRY_COUNT; ++eptPdIndex)
|
||||
{
|
||||
eptEntries.Pde.AsLargePage = &eptPd[eptPdIndex];
|
||||
|
||||
//
|
||||
// Use the 2MB translation if the entire 2MB managed by this PDE has
|
||||
// same memory type. Otherwise, this PDE points to the EPT PT.
|
||||
//
|
||||
hostPhysicalAddress = ComputeAddressFromIndexes(EptPml4Index,
|
||||
eptPdptIndex,
|
||||
eptPdIndex,
|
||||
0);
|
||||
memoryType = GetMemoryTypeForRange(hostPhysicalAddress, twoMegaByte);
|
||||
if (memoryType != MEMORY_TYPE_INVALID)
|
||||
{
|
||||
eptEntries.Pde.AsLargePage->LargePage = TRUE;
|
||||
eptEntries.Pde.AsLargePage->MemoryType = memoryType;
|
||||
eptEntries.Pde.AsLargePage->PageFrameNumber = (hostPhysicalAddress >> PAGE_SHIFT_2BM);
|
||||
continue;
|
||||
}
|
||||
|
||||
//
|
||||
// Cannot be the single 2MB page. Allocate the EPT PT and fill with
|
||||
// the default all allow permissions, and initialize the EPT PDE with it.
|
||||
//
|
||||
eptPt = MmAllocatePages(1);
|
||||
if (eptPt == NULL)
|
||||
{
|
||||
status = MV_STATUS_INSUFFICIENT_RESOURCES;
|
||||
goto Exit;
|
||||
}
|
||||
__stosq((UINT64*)eptPt, defaultPermissions.Flags, EPT_PTE_ENTRY_COUNT);
|
||||
eptEntries.Pde.AsRegularPage->PageFrameNumber = (GetPhysicalAddress(eptPt) >> PAGE_SHIFT);
|
||||
|
||||
//
|
||||
// Initialize all 512 entries in the EPT PT pointed by the EPT PDE.
|
||||
//
|
||||
for (UINT32 eptPteIndex = 0; eptPteIndex < EPT_PTE_ENTRY_COUNT; ++eptPteIndex)
|
||||
{
|
||||
hostPhysicalAddress = ComputeAddressFromIndexes(EptPml4Index,
|
||||
eptPdptIndex,
|
||||
eptPdIndex,
|
||||
eptPteIndex);
|
||||
memoryType = GetMemoryTypeForRange(hostPhysicalAddress, PAGE_SIZE);
|
||||
MV_ASSERT(memoryType != MEMORY_TYPE_INVALID);
|
||||
|
||||
eptEntries.Pte = &eptPt[eptPteIndex];
|
||||
eptEntries.Pte->MemoryType = memoryType;
|
||||
eptEntries.Pte->PageFrameNumber = (hostPhysicalAddress >> PAGE_SHIFT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
status = MV_STATUS_SUCCESS;
|
||||
|
||||
Exit:
|
||||
return status;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Split a 2MB EPT PDE to 512 EPT PTEs.
|
||||
|
||||
@param[in,out] EptPdeLarge - The pointer to the 2MB EPT PDE to split.
|
||||
|
||||
@return MV_STATUS_SUCCESS on success; otherwise, an appropriate error code.
|
||||
*/
|
||||
static
|
||||
_Must_inspect_result_
|
||||
MV_STATUS
|
||||
Split2MbPage (
|
||||
_Inout_ EPDE_2MB* EptPdeLarge
|
||||
)
|
||||
{
|
||||
MV_STATUS status;
|
||||
EPDE* eptPde;
|
||||
EPTE* eptPt;
|
||||
UINT64 hostPaBase;
|
||||
UINT64 hostPaToMap;
|
||||
|
||||
MV_ASSERT(EptPdeLarge->LargePage != FALSE);
|
||||
|
||||
//
|
||||
// Allocate the EPT PT as we are going to split one 2MB page to 512 4KB pages.
|
||||
//
|
||||
eptPt = MmAllocatePages(1);
|
||||
if (eptPt == NULL)
|
||||
{
|
||||
status = MV_STATUS_INSUFFICIENT_RESOURCES;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
//
|
||||
// Clear the large page bit, and propagate the current permissions to the
|
||||
// all entries in the EPT PTE.
|
||||
//
|
||||
EptPdeLarge->LargePage = FALSE;
|
||||
__stosq((UINT64*)eptPt, EptPdeLarge->Flags, EPT_PTE_ENTRY_COUNT);
|
||||
|
||||
//
|
||||
// Update the page frame of each EPT PTE.
|
||||
//
|
||||
hostPaBase = (EptPdeLarge->PageFrameNumber << PAGE_SHIFT_2BM);
|
||||
for (UINT32 eptPtIndex = 0; eptPtIndex < EPT_PTE_ENTRY_COUNT; ++eptPtIndex)
|
||||
{
|
||||
hostPaToMap = hostPaBase + ((UINT64)eptPtIndex * PAGE_SIZE);
|
||||
eptPt[eptPtIndex].PageFrameNumber = (hostPaToMap >> PAGE_SHIFT);
|
||||
}
|
||||
|
||||
//
|
||||
// Finally, update the PDE by pointing to the EPT PT.
|
||||
//
|
||||
eptPde = (EPDE*)EptPdeLarge;
|
||||
eptPde->Reserved1 = eptPde->Reserved2 = eptPde->Reserved3 = eptPde->Reserved4 = 0;
|
||||
eptPde->PageFrameNumber = (GetPhysicalAddress(eptPt) >> PAGE_SHIFT);
|
||||
|
||||
status = MV_STATUS_SUCCESS;
|
||||
|
||||
Exit:
|
||||
return status;
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
MV_STATUS
|
||||
InitializeExtendedPageTables (
|
||||
EPT_CONTEXT* EptContext
|
||||
)
|
||||
{
|
||||
MV_STATUS status;
|
||||
EPT_PML4* eptPml4;
|
||||
|
||||
eptPml4 = MmAllocatePages(1);
|
||||
if (eptPml4 == NULL)
|
||||
{
|
||||
status = MV_STATUS_INSUFFICIENT_RESOURCES;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
status = BuildEptEntriesFor512Gb(eptPml4, 0);
|
||||
if (MV_ERROR(status))
|
||||
{
|
||||
LOG_ERROR("BuildEptEntriesFor512Gb failed : %08x", status);
|
||||
goto Exit;
|
||||
}
|
||||
EptContext->EptPml4 = eptPml4;
|
||||
|
||||
//
|
||||
// All EPT initialization completed successfully. Set up the
|
||||
// extended-page-table pointer (EPTP).
|
||||
//
|
||||
// The EPTP is the top level data structure for EPT that governs the vast
|
||||
// majority of EPT related behavior and is written to VMCS. This structure
|
||||
// can be understood as CR3 equivalent in EPT as it holds a physical address
|
||||
// of the EPT PML4 table.
|
||||
// See: 24.6.11 Extended-Page-Table Pointer (EPTP)
|
||||
//
|
||||
|
||||
//
|
||||
// Specify the memory-type used for accessing to any of EPT paging-structures.
|
||||
// We use the memory-type as those structures are allocated, that is the
|
||||
// write-back memory type. We assume that CR0.CD (cache disabled) is 0, which
|
||||
// should always be the case here.
|
||||
// See: 28.2.6.1 Memory Type Used for Accessing EPT Paging Structures
|
||||
//
|
||||
EptContext->EptPointer.MemoryType = MEMORY_TYPE_WRITE_BACK;
|
||||
|
||||
//
|
||||
// "This value is 1 less than the EPT page-walk length."
|
||||
// "The EPT translation mechanism (...) uses a page-walk length of 4".
|
||||
// See: Table 24-8. Format of Extended-Page-Table Pointer
|
||||
// See: 28.2.2 EPT Translation Mechanism
|
||||
//
|
||||
EptContext->EptPointer.PageWalkLength = EPT_PAGE_WALK_LENGTH_4;
|
||||
|
||||
//
|
||||
// PFN of the EPT PML4 table.
|
||||
//
|
||||
EptContext->EptPointer.PageFrameNumber = GetPhysicalAddress(EptContext->EptPml4) >> PAGE_SHIFT;
|
||||
|
||||
Exit:
|
||||
if (MV_ERROR(status))
|
||||
{
|
||||
if (eptPml4 != NULL)
|
||||
{
|
||||
CleanupTables((EPT_ENTRY*)eptPml4, EPT_LEVEL_PML4E);
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
VOID
|
||||
CleanupExtendedPageTables (
|
||||
EPT_CONTEXT* EptContext
|
||||
)
|
||||
{
|
||||
CleanupTables((EPT_ENTRY*)EptContext->EptPml4, EPT_LEVEL_PML4E);
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
MV_STATUS
|
||||
UpdateExtendPageTables (
|
||||
EPT_PML4* EptPml4,
|
||||
UINT64 GuestPhysicalAddress,
|
||||
CONST UINT64* HostPhysicalAddress,
|
||||
CONST EPT_ENTRY* Permissions
|
||||
)
|
||||
{
|
||||
MV_STATUS status;
|
||||
ADDRESS_TRANSLATION_HELPER helper;
|
||||
EPT_ENTRIES eptEntries;
|
||||
EPDPTE_1GB* eptPdpt;
|
||||
EPDE_2MB* eptPd;
|
||||
EPTE* eptPt;
|
||||
|
||||
MV_ASSERT(ARGUMENT_PRESENT(HostPhysicalAddress) ||
|
||||
ARGUMENT_PRESENT(Permissions));
|
||||
|
||||
//
|
||||
// Locate the EPT PML4E for the GPA.
|
||||
//
|
||||
helper.AsUInt64 = GuestPhysicalAddress;
|
||||
eptEntries.Pml4e = &EptPml4[helper.AsIndex.Pml4];
|
||||
MV_ASSERT(MV_IS_EPT_ENTRY_PRESENT(eptEntries.Pml4e) != FALSE);
|
||||
|
||||
//
|
||||
// Locate the EPT PDPTE for the GPA. The entry must not be large page as we
|
||||
// do not use 1GB page.
|
||||
//
|
||||
eptPdpt = GetVirtualAddress(eptEntries.Pml4e->PageFrameNumber << PAGE_SHIFT);
|
||||
eptEntries.Pdpte.AsLargePage = &eptPdpt[helper.AsIndex.Pdpt];
|
||||
MV_ASSERT(MV_IS_EPT_ENTRY_PRESENT(eptEntries.Pdpte.AsRegularPage) != FALSE);
|
||||
MV_ASSERT(eptEntries.Pdpte.AsLargePage->LargePage == FALSE);
|
||||
|
||||
//
|
||||
// Locate the EPT PDE for the GPA. If the entry is the 2MB page, split it.
|
||||
//
|
||||
eptPd = GetVirtualAddress(eptEntries.Pdpte.AsRegularPage->PageFrameNumber << PAGE_SHIFT);
|
||||
eptEntries.Pde.AsLargePage = &eptPd[helper.AsIndex.Pd];
|
||||
MV_ASSERT(MV_IS_EPT_ENTRY_PRESENT(eptEntries.Pde.AsRegularPage) != FALSE);
|
||||
|
||||
if (eptEntries.Pde.AsLargePage->LargePage != FALSE)
|
||||
{
|
||||
status = Split2MbPage(eptEntries.Pde.AsLargePage);
|
||||
if (MV_ERROR(status))
|
||||
{
|
||||
goto Exit;
|
||||
}
|
||||
}
|
||||
|
||||
MV_ASSERT(eptEntries.Pdpte.AsLargePage->LargePage == FALSE);
|
||||
|
||||
//
|
||||
// Locate the EPT PTE for the GPA and update translation and permissions as
|
||||
// requested.
|
||||
//
|
||||
eptPt = GetVirtualAddress(eptEntries.Pde.AsRegularPage->PageFrameNumber << PAGE_SHIFT);
|
||||
eptEntries.Pte = &eptPt[helper.AsIndex.Pt];
|
||||
if (ARGUMENT_PRESENT(HostPhysicalAddress))
|
||||
{
|
||||
eptEntries.Pte->PageFrameNumber = (*HostPhysicalAddress >> PAGE_SHIFT);
|
||||
}
|
||||
if (ARGUMENT_PRESENT(Permissions))
|
||||
{
|
||||
eptEntries.Pte->ReadAccess = Permissions->ReadAccess;
|
||||
eptEntries.Pte->WriteAccess = Permissions->WriteAccess;
|
||||
eptEntries.Pte->ExecuteAccess = Permissions->ExecuteAccess;
|
||||
}
|
||||
|
||||
status = MV_STATUS_SUCCESS;
|
||||
|
||||
Exit:
|
||||
return status;
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
VOID
|
||||
InvalidateEptDerivedCache (
|
||||
UINT64 EptPointer
|
||||
)
|
||||
{
|
||||
INVEPT_DESCRIPTOR descriptor;
|
||||
INVEPT_TYPE type;
|
||||
|
||||
RtlZeroMemory(&descriptor, sizeof(descriptor));
|
||||
descriptor.EptPointer = EptPointer;
|
||||
type = (EptPointer == 0) ? InveptAllContext : InveptSingleContext;
|
||||
MV_VERIFY(AsmInvept(type, &descriptor) == VmxResultOk);
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
VOID
|
||||
InvalidateVpidDerivedCache (
|
||||
UINT16 VirtualProcessorId
|
||||
)
|
||||
{
|
||||
INVVPID_DESCRIPTOR descriptor;
|
||||
INVVPID_TYPE type;
|
||||
|
||||
RtlZeroMemory(&descriptor, sizeof(descriptor));
|
||||
descriptor.Vpid = VirtualProcessorId;
|
||||
type = (VirtualProcessorId == 0) ? InvvpidAllContext : InvvpidSingleContext;
|
||||
MV_VERIFY(AsmInvvpid(type, &descriptor)== VmxResultOk);
|
||||
}
|
||||
132
Sources/ExtendedPageTables.h
Normal file
132
Sources/ExtendedPageTables.h
Normal file
@@ -0,0 +1,132 @@
|
||||
/*!
|
||||
@file ExtendedPageTables.h
|
||||
|
||||
@brief Functions for EPT handling.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 -, Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#pragma once
|
||||
#include "Common.h"
|
||||
|
||||
/*!
|
||||
@brief Checks whether the EPT entry is present.
|
||||
|
||||
@param[in] EptEntry - The pointer to the EPT entry to check.
|
||||
|
||||
@return TRUE when the entry is present.
|
||||
*/
|
||||
#define MV_IS_EPT_ENTRY_PRESENT(EptEntry) \
|
||||
(((EptEntry)->ReadAccess != FALSE) || \
|
||||
((EptEntry)->WriteAccess != FALSE) || \
|
||||
((EptEntry)->ExecuteAccess != FALSE))
|
||||
|
||||
/*!
|
||||
@brief Copies the permission of the EPT entry to the other entry.
|
||||
|
||||
@param[out] Destination - The pointer to the EPT entry to updates its permission.
|
||||
|
||||
@param[in] EptEntry - The pointer to the EPT entry to copy its permission from.
|
||||
*/
|
||||
#define MV_COPY_EPT_ENTRY_PERMISSIONS(Destination, EptEntry) \
|
||||
(Destination)->ReadAccess = (EptEntry)->ReadAccess; \
|
||||
(Destination)->WriteAccess = (EptEntry)->WriteAccess; \
|
||||
(Destination)->ExecuteAccess = (EptEntry)->ExecuteAccess
|
||||
|
||||
/*!
|
||||
@brief Aggregates the permission of the EPT entry to the other entry.
|
||||
|
||||
@param[out] Destination - The pointer to the EPT entry to updates its permission.
|
||||
|
||||
@param[in] EptEntry - The pointer to the EPT entry to aggregate its permission from.
|
||||
*/
|
||||
#define MV_AGGREGATE_EPT_ENTRY_PERMISSIONS(Destination, EptEntry) \
|
||||
(Destination)->ReadAccess &= (EptEntry)->ReadAccess; \
|
||||
(Destination)->WriteAccess &= (EptEntry)->WriteAccess; \
|
||||
(Destination)->ExecuteAccess &= (EptEntry)->ExecuteAccess
|
||||
|
||||
//
|
||||
// Holds the context specific to EPT.
|
||||
//
|
||||
typedef struct _EPT_CONTEXT
|
||||
{
|
||||
//
|
||||
// EPTP written to VMCS.
|
||||
//
|
||||
EPT_POINTER EptPointer;
|
||||
|
||||
//
|
||||
// The virtual address of the EPT PML4.
|
||||
//
|
||||
EPT_PML4* EptPml4;
|
||||
} EPT_CONTEXT;
|
||||
|
||||
/*!
|
||||
@brief Initializes EPT with pass-through style configurations.
|
||||
|
||||
@param[in,out] EptContext - A pointer to the EPT context to initialize.
|
||||
|
||||
@return MV_STATUS_SUCCESS on success; otherwise, an appropriate error code.
|
||||
*/
|
||||
_Must_inspect_result_
|
||||
MV_STATUS
|
||||
InitializeExtendedPageTables (
|
||||
_Inout_ EPT_CONTEXT* EptContext
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Cleans up EPT context.
|
||||
|
||||
@param[in,out] EptContext - A pointer to the EPT context to clean up.
|
||||
*/
|
||||
VOID
|
||||
CleanupExtendedPageTables (
|
||||
_Inout_ EPT_CONTEXT* EptContext
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Updates the EPT PTE for the given GPA with new HPA and permissions.
|
||||
|
||||
@param[in] EptPml4 - The pointer to the EPT PML4.
|
||||
|
||||
@param[in] GuestPhysicalAddress - The GPA to update its EPT PTE.
|
||||
|
||||
@param[in] HostPhysicalAddress - The pointer to the HPA to update to. If NULL
|
||||
is specified, the function does not change the translation.
|
||||
|
||||
@param[in] Permissions - The pointer to the new permission to update to. If
|
||||
NULL is specified, the functions does not change the permissions.
|
||||
|
||||
@return MV_STATUS_SUCCESS on success; otherwise, an appropriate error code.
|
||||
*/
|
||||
_Must_inspect_result_
|
||||
MV_STATUS
|
||||
UpdateExtendPageTables (
|
||||
_In_ EPT_PML4* EptPml4,
|
||||
_In_ UINT64 GuestPhysicalAddress,
|
||||
_In_opt_ CONST UINT64* HostPhysicalAddress,
|
||||
_In_opt_ CONST EPT_ENTRY* Permissions
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Invalidate guest-physical and combined caches.
|
||||
|
||||
@param[in] EptPointer - The EPT pointer to invalidate associated caches. If
|
||||
0 is specified, caches associated with any EPT pointers are invalidated.
|
||||
*/
|
||||
VOID
|
||||
InvalidateEptDerivedCache (
|
||||
_In_ UINT64 EptPointer
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Invalidate liner and combined caches.
|
||||
|
||||
@param[in] VirtualProcessorId - The VPID to invalidate associated caches. If
|
||||
0 is specified, caches associated with any VPID are invalidated.
|
||||
*/
|
||||
VOID
|
||||
InvalidateVpidDerivedCache (
|
||||
_In_ UINT16 VirtualProcessorId
|
||||
);
|
||||
87
Sources/HostInitialization.h
Normal file
87
Sources/HostInitialization.h
Normal file
@@ -0,0 +1,87 @@
|
||||
/*!
|
||||
@file HostInitialization.h
|
||||
|
||||
@brief Functions for host environment initialization.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#pragma once
|
||||
#include "Common.h"
|
||||
|
||||
/*!
|
||||
@brief Initializes the host environment.
|
||||
*/
|
||||
VOID
|
||||
InitializeHostEnvironment (
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Returns the host CR3.
|
||||
|
||||
@return The host CR3.
|
||||
*/
|
||||
CR3
|
||||
GetHostCr3 (
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Returns the pointer to the host IDTR.
|
||||
|
||||
@return The pointer to the host IDTR.
|
||||
*/
|
||||
CONST IDTR*
|
||||
GetHostIdtr (
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Sets up the task state segment (TSS) and the TR register.
|
||||
|
||||
@details On EFI, this functions takes a copy of the existing GDT into NewGdt,
|
||||
adds a new entry into it, which points to NewTss, then, updates the GDTR
|
||||
and TR to point to the NewGdt and the newly added entry. Those updated
|
||||
GDTR and TR may be used as both host and guest GDTR/TR.
|
||||
|
||||
On Windows, this function is no-op.
|
||||
|
||||
@param[in,out] NewTss - The pointer to buffer to be used as the task state
|
||||
segment.
|
||||
|
||||
@param[out] NewGdt - The pointer to the buffet to be used as the new GDT.
|
||||
This will be initialized with the contents of the current GDT with the
|
||||
new entry for TSS.
|
||||
|
||||
@param[in] NewGdtSize - The size of NewGdt in bytes.
|
||||
|
||||
@param[out] OriginalGdtr - The pointer to the GDTR to receive the value before
|
||||
this function updates.
|
||||
*/
|
||||
VOID
|
||||
InitializeGdt (
|
||||
_Inout_ TASK_STATE_SEGMENT_64* NewTss,
|
||||
_Out_writes_bytes_(NewGdtSize) SEGMENT_DESCRIPTOR_64* NewGdt,
|
||||
_In_ UINT64 NewGdtSize,
|
||||
_Out_ GDTR* OriginalGdtr
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Restores the GDTR to the specified value.
|
||||
|
||||
@details On EFI, this function updates the current GDTR, however, does not
|
||||
restore the TR to the original value. This is because the original value
|
||||
is expected to be zero, which cannot write to TR anymore (causes #GP).
|
||||
Because of this, TR will point to an invalid entry in the restored GDT.
|
||||
This is an unsolvable issue unless we reuse the existing GDT instead of
|
||||
creating a copy, which does not work on VMware Workstation due to the
|
||||
physical address hosting the GDT is not modifiable. The only sane
|
||||
workaround would be to disallow unloading of the MiniVisor module.
|
||||
|
||||
On Windows, this function is no-op.
|
||||
|
||||
@param[in] OriginalGdtr - The pointer to the GDTR to restore to.
|
||||
*/
|
||||
VOID
|
||||
CleanupGdt (
|
||||
_In_ CONST GDTR* OriginalGdtr
|
||||
);
|
||||
915
Sources/HostMain.c
Normal file
915
Sources/HostMain.c
Normal file
@@ -0,0 +1,915 @@
|
||||
/*!
|
||||
@file HostMain.c
|
||||
|
||||
@brief Functions for VM-exit handling.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 -, Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#include "HostMain.h"
|
||||
#include "HostUtils.h"
|
||||
#include "Public.h"
|
||||
#include "Logger.h"
|
||||
#include "ExtendedPageTables.h"
|
||||
#include "HostVmcall.h"
|
||||
#include "HostNesting.h"
|
||||
|
||||
//
|
||||
// The trap frame structure for x64 systems. This is structure is used to help
|
||||
// Windbg to construct call stack while VM-exit handlers are being executed.
|
||||
// Since this is for Windbg, this is a Windows specific structure, and its
|
||||
// layout can be found as nt!_KTRAP_FRAME. In our case, only the Rip and Rsp
|
||||
// members are used since those are only fields needed to be set for Windbg to
|
||||
// show proper call stack.
|
||||
//
|
||||
typedef struct _WINDOWS_KTRAP_FRAME
|
||||
{
|
||||
UINT64 Reserved1[45];
|
||||
UINT64 Rip;
|
||||
UINT64 Reserved2[2];
|
||||
UINT64 Rsp;
|
||||
UINT64 Reserved3;
|
||||
} WINDOWS_KTRAP_FRAME;
|
||||
C_ASSERT(sizeof(WINDOWS_KTRAP_FRAME) == 0x190);
|
||||
|
||||
//
|
||||
// The layout of hypervisor stack when the C-handler (HandleVmExit) is executed.
|
||||
// GuestRegisters and TrapFrame are pushed in assembler part.
|
||||
//
|
||||
typedef struct _INITIAL_HYPERVISOR_STACK
|
||||
{
|
||||
GUEST_REGISTERS GuestRegisters;
|
||||
WINDOWS_KTRAP_FRAME TrapFrame;
|
||||
HYPERVISOR_CONTEXT HypervisorContext;
|
||||
} INITIAL_HYPERVISOR_STACK;
|
||||
|
||||
/*!
|
||||
@brief Handles VM-exit due to execution of the RDMSR and WRMSR instruction.
|
||||
|
||||
@param[in,out] GuestContext - A pointer to the guest context.
|
||||
|
||||
@param[in] OperationType - The type of the operation.
|
||||
*/
|
||||
static
|
||||
VOID
|
||||
HandleMsrAccess (
|
||||
_Inout_ GUEST_CONTEXT* GuestContext,
|
||||
_In_ OPERATION_TYPE OperationType
|
||||
)
|
||||
{
|
||||
IA32_MSR_ADDRESS msr;
|
||||
UINT64 value;
|
||||
|
||||
msr = (IA32_MSR_ADDRESS)GuestContext->StackBasedRegisters->Rcx;
|
||||
if (OperationType == OperationRead)
|
||||
{
|
||||
//
|
||||
// Performs the same read on behalf of the guest.
|
||||
//
|
||||
value = __readmsr(msr);
|
||||
GuestContext->StackBasedRegisters->Rax = value & MAXUINT32;
|
||||
GuestContext->StackBasedRegisters->Rdx = (value >> 32) & MAXUINT32;
|
||||
}
|
||||
else
|
||||
{
|
||||
//
|
||||
// Performs the same write on behalf of the guest.
|
||||
//
|
||||
value = (GuestContext->StackBasedRegisters->Rax & MAXUINT32) |
|
||||
((GuestContext->StackBasedRegisters->Rdx & MAXUINT32) << 32);
|
||||
__writemsr(msr, value);
|
||||
}
|
||||
|
||||
AdvanceGuestInstructionPointer(GuestContext);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Handles VM-exit due to execution of the RDMSR instruction.
|
||||
|
||||
@param[in,out] GuestContext - A pointer to the guest context.
|
||||
*/
|
||||
static
|
||||
VOID
|
||||
HandleMsrRead (
|
||||
_Inout_ GUEST_CONTEXT* GuestContext
|
||||
)
|
||||
{
|
||||
HandleMsrAccess(GuestContext, OperationRead);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Handles VM-exit due to execution of the WRMSR instruction.
|
||||
|
||||
@param[in,out] GuestContext - A pointer to the guest context.
|
||||
*/
|
||||
static
|
||||
VOID
|
||||
HandleMsrWrite (
|
||||
_Inout_ GUEST_CONTEXT* GuestContext
|
||||
)
|
||||
{
|
||||
HandleMsrAccess(GuestContext, OperationWrite);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Handles VM-exit due to execution of the CPUID instruction.
|
||||
|
||||
@param[in,out] GuestContext - A pointer to the guest context.
|
||||
*/
|
||||
static
|
||||
VOID
|
||||
HandleCpuid (
|
||||
_Inout_ GUEST_CONTEXT* GuestContext
|
||||
)
|
||||
{
|
||||
int registers[4];
|
||||
int leaf, subLeaf;
|
||||
|
||||
//
|
||||
// Execute the same instruction on behalf of the guest.
|
||||
//
|
||||
leaf = (int)GuestContext->StackBasedRegisters->Rax;
|
||||
subLeaf = (int)GuestContext->StackBasedRegisters->Rcx;
|
||||
__cpuidex(registers, leaf, subLeaf);
|
||||
|
||||
//
|
||||
// Then, modify results when necessary.
|
||||
//
|
||||
switch (leaf)
|
||||
{
|
||||
case CPUID_VERSION_INFORMATION:
|
||||
//
|
||||
// Do not indicate the VMX feature is available on this processor to
|
||||
// prevent other hypervisor tries to use it, as MiniVisor does not
|
||||
// support nesting the hypervisor.
|
||||
//
|
||||
ClearFlag(registers[2], CPUID_FEATURE_INFORMATION_ECX_VIRTUAL_MACHINE_EXTENSIONS_FLAG);
|
||||
break;
|
||||
|
||||
case CPUID_HV_VENDOR_AND_MAX_FUNCTIONS:
|
||||
//
|
||||
// Return a maximum supported hypervisor CPUID leaf range and a vendor
|
||||
// ID signature as required by the spec.
|
||||
//
|
||||
registers[0] = CPUID_HV_MAX;
|
||||
registers[1] = 'iniM'; // "MiniVisor "
|
||||
registers[2] = 'osiV';
|
||||
registers[3] = ' r';
|
||||
break;
|
||||
|
||||
case CPUID_HV_INTERFACE:
|
||||
//
|
||||
// Return non Hv#1 value. This indicate that the MiniVisor does NOT
|
||||
// conform to the Microsoft hypervisor interface.
|
||||
//
|
||||
registers[0] = '0#vH'; // Hv#0
|
||||
registers[1] = registers[2] = registers[3] = 0;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
//
|
||||
// Update guest's GPRs with results.
|
||||
//
|
||||
GuestContext->StackBasedRegisters->Rax = (UINT64)registers[0];
|
||||
GuestContext->StackBasedRegisters->Rbx = (UINT64)registers[1];
|
||||
GuestContext->StackBasedRegisters->Rcx = (UINT64)registers[2];
|
||||
GuestContext->StackBasedRegisters->Rdx = (UINT64)registers[3];
|
||||
|
||||
AdvanceGuestInstructionPointer(GuestContext);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Handles VM-exit due to execution of the VMCALL instruction.
|
||||
|
||||
@param[in,out] GuestContext - A pointer to the guest context.
|
||||
*/
|
||||
static
|
||||
VOID
|
||||
HandleVmCall (
|
||||
_Inout_ GUEST_CONTEXT* GuestContext
|
||||
)
|
||||
{
|
||||
UINT64 hypercallNumber;
|
||||
|
||||
//
|
||||
// Our hypercall takes the hypercall number in RCX.
|
||||
//
|
||||
hypercallNumber = GuestContext->StackBasedRegisters->Rcx;
|
||||
if (hypercallNumber >= VmcallInvalid)
|
||||
{
|
||||
//
|
||||
// Undefined hypercall number. Inject #UD.
|
||||
//
|
||||
InjectInterruption(HardwareException, InvalidOpcode, FALSE, 0);
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
//
|
||||
// Executes the corresponding hypercall handler.
|
||||
//
|
||||
k_VmcallHandlers[hypercallNumber](GuestContext);
|
||||
|
||||
AdvanceGuestInstructionPointer(GuestContext);
|
||||
|
||||
Exit:
|
||||
return;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Handles VM-exit due to execution of the XSETBV instruction.
|
||||
|
||||
@param[in,out] GuestContext - A pointer to the guest context.
|
||||
*/
|
||||
static
|
||||
VOID
|
||||
HandleXsetbv (
|
||||
_Inout_ GUEST_CONTEXT* GuestContext
|
||||
)
|
||||
{
|
||||
UINT64 value;
|
||||
CR4 hostCr4;
|
||||
|
||||
//
|
||||
// Execution of the XSETBV instruction requires the OSXSAVE bit to be set,
|
||||
// and the host CR4 may not have it. Set the bit and execute the instruction.
|
||||
//
|
||||
hostCr4.Flags = __readcr4();
|
||||
hostCr4.OsXsave = TRUE;
|
||||
__writecr4(hostCr4.Flags);
|
||||
|
||||
value = (GuestContext->StackBasedRegisters->Rax & MAXUINT32) |
|
||||
((GuestContext->StackBasedRegisters->Rdx & MAXUINT32) << 32);
|
||||
_xsetbv((UINT32)GuestContext->StackBasedRegisters->Rcx, value);
|
||||
|
||||
AdvanceGuestInstructionPointer(GuestContext);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Returns the address of where the guest general purpose register that
|
||||
corresponds to the given index is stored.
|
||||
|
||||
@param[in,out] GuestContext - A pointer to the guest context.
|
||||
|
||||
@param[in] RegisterIndex - The index provided by VMCS up on VM-exit.
|
||||
|
||||
@return The address of where the guest general purpose register that
|
||||
corresponds to the given index is stored.
|
||||
*/
|
||||
static
|
||||
UINT64*
|
||||
SelectEffectiveRegister (
|
||||
_Inout_ GUEST_CONTEXT* GuestContext,
|
||||
_In_ UINT64 RegisterIndex
|
||||
)
|
||||
{
|
||||
UINT64* effectiveRegister;
|
||||
|
||||
switch (RegisterIndex)
|
||||
{
|
||||
case 0: effectiveRegister = &GuestContext->StackBasedRegisters->Rax; break;
|
||||
case 1: effectiveRegister = &GuestContext->StackBasedRegisters->Rcx; break;
|
||||
case 2: effectiveRegister = &GuestContext->StackBasedRegisters->Rdx; break;
|
||||
case 3: effectiveRegister = &GuestContext->StackBasedRegisters->Rbx; break;
|
||||
case 4: effectiveRegister = &GuestContext->VmcsBasedRegisters.Rsp; break;
|
||||
case 5: effectiveRegister = &GuestContext->StackBasedRegisters->Rbp; break;
|
||||
case 6: effectiveRegister = &GuestContext->StackBasedRegisters->Rsi; break;
|
||||
case 7: effectiveRegister = &GuestContext->StackBasedRegisters->Rdi; break;
|
||||
case 8: effectiveRegister = &GuestContext->StackBasedRegisters->R8; break;
|
||||
case 9: effectiveRegister = &GuestContext->StackBasedRegisters->R9; break;
|
||||
case 10: effectiveRegister = &GuestContext->StackBasedRegisters->R10; break;
|
||||
case 11: effectiveRegister = &GuestContext->StackBasedRegisters->R11; break;
|
||||
case 12: effectiveRegister = &GuestContext->StackBasedRegisters->R12; break;
|
||||
case 13: effectiveRegister = &GuestContext->StackBasedRegisters->R13; break;
|
||||
case 14: effectiveRegister = &GuestContext->StackBasedRegisters->R14; break;
|
||||
case 15: effectiveRegister = &GuestContext->StackBasedRegisters->R15; break;
|
||||
default: MV_PANIC(); break;
|
||||
}
|
||||
|
||||
return effectiveRegister;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Handles VM-exit due to execution of access to the control register.
|
||||
|
||||
@param[in,out] GuestContext - A pointer to the guest context.
|
||||
*/
|
||||
static
|
||||
VOID
|
||||
HandleCrAccess (
|
||||
_Inout_ GUEST_CONTEXT* GuestContext
|
||||
)
|
||||
{
|
||||
VMX_EXIT_QUALIFICATION_MOV_CR qualification;
|
||||
UINT64 newValue;
|
||||
CR0 newCr0, currentCr0;
|
||||
CR4 newCr4;
|
||||
|
||||
qualification.Flags = VmxRead(VMCS_EXIT_QUALIFICATION);
|
||||
newValue = *SelectEffectiveRegister(GuestContext,
|
||||
qualification.GeneralPurposeRegister);
|
||||
|
||||
switch (qualification.AccessType)
|
||||
{
|
||||
case VMX_EXIT_QUALIFICATION_ACCESS_MOV_TO_CR:
|
||||
//
|
||||
// Update what the guest sees (ie, VMCS_CTRL_CRn_READ_SHADOW) exactly
|
||||
// as the guest requested, then update the actual state (VMCS_GUEST_CRn)
|
||||
// after applying the FIXED0 and FIXED1 MSRs. This ensures VMX continues
|
||||
// to function, for example, by keeping the VMXE bit set.
|
||||
//
|
||||
switch (qualification.ControlRegister)
|
||||
{
|
||||
case VMX_EXIT_QUALIFICATION_REGISTER_CR0:
|
||||
newCr0.Flags = newValue;
|
||||
currentCr0.Flags = VmxRead(VMCS_GUEST_CR0);
|
||||
VmxWrite(VMCS_CTRL_CR0_READ_SHADOW, newCr0.Flags);
|
||||
VmxWrite(VMCS_GUEST_CR0, AdjustGuestCr0(newCr0).Flags);
|
||||
if (currentCr0.PagingEnable != newCr0.PagingEnable)
|
||||
{
|
||||
SwitchGuestPagingMode(newCr0);
|
||||
}
|
||||
break;
|
||||
case VMX_EXIT_QUALIFICATION_REGISTER_CR4:
|
||||
newCr4.Flags = newValue;
|
||||
VmxWrite(VMCS_CTRL_CR4_READ_SHADOW, newCr4.Flags);
|
||||
VmxWrite(VMCS_GUEST_CR4, AdjustGuestCr4(newCr4).Flags);
|
||||
break;
|
||||
default:
|
||||
MV_PANIC();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
MV_PANIC();
|
||||
}
|
||||
|
||||
AdvanceGuestInstructionPointer(GuestContext);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Handles VM-exit due to EPT violation.
|
||||
|
||||
@param[in,out] GuestContext - A pointer to the guest context.
|
||||
*/
|
||||
static
|
||||
VOID
|
||||
HandleEptViolation (
|
||||
_Inout_ GUEST_CONTEXT* GuestContext
|
||||
)
|
||||
{
|
||||
UINT64 faultPhysicalAddress;
|
||||
|
||||
//
|
||||
// As of now, this should never happen and can panic here. We inject #GP(0)
|
||||
// instead, because this is what you may want to do once some EPT related logic
|
||||
// such as protecting pages is written.
|
||||
//
|
||||
faultPhysicalAddress = VmxRead(VMCS_GUEST_PHYSICAL_ADDRESS);
|
||||
LOG_WARNING("IP:%016llx PA:%016llx",
|
||||
GuestContext->VmcsBasedRegisters.Rip,
|
||||
faultPhysicalAddress);
|
||||
|
||||
InjectInterruption(HardwareException, GeneralProtection, TRUE, 0);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Handles VM-exit due to EPT misconfiguration.
|
||||
|
||||
@param[in,out] GuestContext - A pointer to the guest context.
|
||||
*/
|
||||
static
|
||||
VOID
|
||||
HandleEptMisconfig (
|
||||
_Inout_ GUEST_CONTEXT* GuestContext
|
||||
)
|
||||
{
|
||||
UINT64 faultPhysicalAddress;
|
||||
ADDRESS_TRANSLATION_HELPER helper;
|
||||
|
||||
//
|
||||
// This is a programming error that should never happen. The most helpful
|
||||
// thing can be done is to dump information for diagnostics.
|
||||
//
|
||||
DumpGuestState();
|
||||
faultPhysicalAddress = VmxRead(VMCS_GUEST_PHYSICAL_ADDRESS);
|
||||
LOG_ERROR("IP:%016llx PA:%016llx",
|
||||
GuestContext->VmcsBasedRegisters.Rip,
|
||||
faultPhysicalAddress);
|
||||
LOG_ERROR("EPT_PML4:%016llx EPTP:%016llx",
|
||||
GuestContext->Contexts->EptContext->EptPml4->Flags,
|
||||
GuestContext->Contexts->EptContext->EptPointer.Flags);
|
||||
helper.AsUInt64 = faultPhysicalAddress;
|
||||
LOG_ERROR("Indexes: %llu %llu %llu %llu",
|
||||
helper.AsIndex.Pml4,
|
||||
helper.AsIndex.Pdpt,
|
||||
helper.AsIndex.Pd,
|
||||
helper.AsIndex.Pt);
|
||||
|
||||
MV_PANIC();
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Handles VM-exit due to interrupt or exception.
|
||||
|
||||
@param[in,out] GuestContext - A pointer to the guest context.
|
||||
*/
|
||||
static
|
||||
VOID
|
||||
HandleExceptionOrNmi (
|
||||
_Inout_ GUEST_CONTEXT* GuestContext
|
||||
)
|
||||
{
|
||||
static BOOLEAN isKeInitAmd64SpecificStateCalled;
|
||||
VMEXIT_INTERRUPT_INFORMATION interruptInfo;
|
||||
|
||||
//
|
||||
// This handler is specialized for skipping main initialization of PatchGuard.
|
||||
// When #DE occurs with the guest state that seems to be during initialization
|
||||
// of PatchGuard, suppress it. Otherwise, just inject the exception (pass-through).
|
||||
//
|
||||
interruptInfo.Flags = (UINT32)VmxRead(VMCS_VMEXIT_INTERRUPTION_INFORMATION);
|
||||
MV_ASSERT(interruptInfo.InterruptionType == HardwareException);
|
||||
MV_ASSERT(interruptInfo.Vector == DivideError);
|
||||
|
||||
//
|
||||
// The below check detects division that will trigger initialization of
|
||||
// PatchGuard. The very instruction is this on all versions of Windows.
|
||||
// idiv r8d
|
||||
// The IDIV instruction in this form performs (int64)edx:eax / (int32)r8d,
|
||||
// and cases #DE, in particular when a positive result is greater than
|
||||
// 0x7fffffff. If the kernel debugger is not attached and disabled, the NT
|
||||
// kernel executes this instruction with the following values, resulting in
|
||||
// the #DE.
|
||||
// ((int64)0xffffffff80000000 / -1) => 0x80000000
|
||||
// When this condition is detected for the first time, we do not inject #DE
|
||||
// to the guest to avoid initialization of main PatchGuard logic.
|
||||
//
|
||||
if ((isKeInitAmd64SpecificStateCalled == FALSE) &&
|
||||
((UINT32)GuestContext->StackBasedRegisters->Rax == (UINT32)0x80000000) &&
|
||||
((UINT32)GuestContext->StackBasedRegisters->Rdx == (UINT32)0xffffffff) &&
|
||||
((UINT32)GuestContext->StackBasedRegisters->R8 == (UINT32)-1))
|
||||
{
|
||||
LOG_DEBUG("KeInitAmd64SpecificState triggered #DE. Ignoring it.");
|
||||
isKeInitAmd64SpecificStateCalled = TRUE;
|
||||
AdvanceGuestInstructionPointer(GuestContext);
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
//
|
||||
// Otherwise, just forward the exception.
|
||||
//
|
||||
InjectInterruption(interruptInfo.InterruptionType, interruptInfo.Vector, FALSE, 0);
|
||||
|
||||
Exit:
|
||||
return;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Handles VM-exit due to the INIT signal.
|
||||
|
||||
@param[in,out] GuestContext - A pointer to the guest context.
|
||||
*/
|
||||
static
|
||||
VOID
|
||||
HandleInitSignal (
|
||||
_Inout_ GUEST_CONTEXT* GuestContext
|
||||
)
|
||||
{
|
||||
UNREFERENCED_PARAMETER(GuestContext);
|
||||
|
||||
//
|
||||
// Simply put the processor into the "wait-for-SIPI" state.
|
||||
//
|
||||
// "All the processors on the system bus (...) execute the multiple processor
|
||||
// (MP) initialization protocol. ... The application (non-BSP) processors
|
||||
// (APs) go into a Wait For Startup IPI (SIPI) state while the BSP is executing
|
||||
// initialization code."
|
||||
//
|
||||
// See: 9.1 INITIALIZATION OVERVIEW
|
||||
//
|
||||
// "Upon receiving an INIT ..., the processor responds by beginning the
|
||||
// initialization process of the processor core and the local APIC. The state
|
||||
// of the local APIC following an INIT reset is the same as it is after a
|
||||
// power-up or hardware reset ... . This state is also referred to at the
|
||||
// "wait-for-SIPI" state."
|
||||
//
|
||||
// See: 10.4.7.3 Local APIC State After an INIT Reset (“Wait-for-SIPI” State)
|
||||
//
|
||||
VmxWrite(VMCS_GUEST_ACTIVITY_STATE, VmxWaitForSipi);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Handles VM-exit due to the Startup-IPI (SIPI) signal.
|
||||
|
||||
@param[in,out] GuestContext - A pointer to the guest context.
|
||||
*/
|
||||
static
|
||||
VOID
|
||||
HandleStartupIpi (
|
||||
_Inout_ GUEST_CONTEXT* GuestContext
|
||||
)
|
||||
{
|
||||
int regs[4];
|
||||
CPUID_EAX_01 cpuVersionInfo;
|
||||
UINT64 extendedModel;
|
||||
UINT64 vector;
|
||||
VMX_SEGMENT_ACCESS_RIGHTS accessRights;
|
||||
IA32_VMX_ENTRY_CTLS_REGISTER vmEntryControls;
|
||||
CR0 newCr0;
|
||||
CR4 newCr4;
|
||||
|
||||
//
|
||||
// Intel SDM suggest to issue SIPI twice. This does not appear to be
|
||||
// implemented by many of kernels such as FreeBSD, Linux and Windows, but
|
||||
// ignore it if this occurs, as we already initialized the processor with
|
||||
// the first SIPI.
|
||||
//
|
||||
if (VmxRead(VMCS_GUEST_ACTIVITY_STATE) == VmxActive)
|
||||
{
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
//
|
||||
// Initializes the processor to the state after INIT as described in the
|
||||
// Intel SDM.
|
||||
//
|
||||
// See: Table 9-1. IA-32 and Intel 64 Processor States Following Power-up,
|
||||
// Reset, or INIT
|
||||
//
|
||||
VmxWrite(VMCS_GUEST_RFLAGS, RFLAGS_READ_AS_1_FLAG);
|
||||
VmxWrite(VMCS_GUEST_RIP, 0xfff0);
|
||||
VmxWrite(VMCS_CTRL_CR0_READ_SHADOW, CR0_EXTENSION_TYPE_FLAG);
|
||||
__writecr2(0);
|
||||
VmxWrite(VMCS_GUEST_CR3, 0);
|
||||
VmxWrite(VMCS_CTRL_CR4_READ_SHADOW, 0);
|
||||
|
||||
//
|
||||
// Actual guest CR0 and CR4 must fulfill requirements for VMX. Apply those.
|
||||
//
|
||||
newCr0.Flags = CR0_EXTENSION_TYPE_FLAG;
|
||||
newCr4.Flags = 0;
|
||||
VmxWrite(VMCS_GUEST_CR0, AdjustGuestCr0(newCr0).Flags);
|
||||
VmxWrite(VMCS_GUEST_CR4, AdjustGuestCr4(newCr4).Flags);
|
||||
|
||||
accessRights.Flags = 0;
|
||||
|
||||
accessRights.Type = SEGMENT_DESCRIPTOR_TYPE_CODE_EXECUTE_READ_ACCESSED;
|
||||
accessRights.DescriptorType = TRUE;
|
||||
accessRights.Present = TRUE;
|
||||
VmxWrite(VMCS_GUEST_CS_SELECTOR, 0xf000);
|
||||
VmxWrite(VMCS_GUEST_CS_BASE, 0xffff0000);
|
||||
VmxWrite(VMCS_GUEST_CS_LIMIT, 0xffff);
|
||||
VmxWrite(VMCS_GUEST_CS_ACCESS_RIGHTS, accessRights.Flags);
|
||||
|
||||
accessRights.Type = SEGMENT_DESCRIPTOR_TYPE_DATA_READ_WRITE_ACCESSED;
|
||||
VmxWrite(VMCS_GUEST_SS_SELECTOR, 0);
|
||||
VmxWrite(VMCS_GUEST_SS_BASE, 0);
|
||||
VmxWrite(VMCS_GUEST_SS_LIMIT, 0xffff);
|
||||
VmxWrite(VMCS_GUEST_SS_ACCESS_RIGHTS, accessRights.Flags);
|
||||
VmxWrite(VMCS_GUEST_DS_SELECTOR, 0);
|
||||
VmxWrite(VMCS_GUEST_DS_BASE, 0);
|
||||
VmxWrite(VMCS_GUEST_DS_LIMIT, 0xffff);
|
||||
VmxWrite(VMCS_GUEST_DS_ACCESS_RIGHTS, accessRights.Flags);
|
||||
VmxWrite(VMCS_GUEST_ES_SELECTOR, 0);
|
||||
VmxWrite(VMCS_GUEST_ES_BASE, 0);
|
||||
VmxWrite(VMCS_GUEST_ES_LIMIT, 0xffff);
|
||||
VmxWrite(VMCS_GUEST_ES_ACCESS_RIGHTS, accessRights.Flags);
|
||||
VmxWrite(VMCS_GUEST_FS_SELECTOR, 0);
|
||||
VmxWrite(VMCS_GUEST_FS_BASE, 0);
|
||||
VmxWrite(VMCS_GUEST_FS_LIMIT, 0xffff);
|
||||
VmxWrite(VMCS_GUEST_FS_ACCESS_RIGHTS, accessRights.Flags);
|
||||
VmxWrite(VMCS_GUEST_GS_SELECTOR, 0);
|
||||
VmxWrite(VMCS_GUEST_GS_BASE, 0);
|
||||
VmxWrite(VMCS_GUEST_GS_LIMIT, 0xffff);
|
||||
VmxWrite(VMCS_GUEST_GS_ACCESS_RIGHTS, accessRights.Flags);
|
||||
|
||||
__cpuid(regs, CPUID_VERSION_INFORMATION);
|
||||
cpuVersionInfo.CpuidVersionInformation.Flags = regs[0];
|
||||
extendedModel = cpuVersionInfo.CpuidVersionInformation.ExtendedModelId;
|
||||
GuestContext->StackBasedRegisters->Rdx = 0x600 | (extendedModel << 16);
|
||||
GuestContext->StackBasedRegisters->Rbx = 0;
|
||||
GuestContext->StackBasedRegisters->Rcx = 0;
|
||||
GuestContext->StackBasedRegisters->Rsi = 0;
|
||||
GuestContext->StackBasedRegisters->Rdi = 0;
|
||||
GuestContext->StackBasedRegisters->Rbp = 0;
|
||||
VmxWrite(VMCS_GUEST_RSP, 0);
|
||||
|
||||
VmxWrite(VMCS_GUEST_GDTR_BASE, 0);
|
||||
VmxWrite(VMCS_GUEST_GDTR_LIMIT, 0xffff);
|
||||
VmxWrite(VMCS_GUEST_IDTR_BASE, 0);
|
||||
VmxWrite(VMCS_GUEST_IDTR_LIMIT, 0xffff);
|
||||
|
||||
accessRights.Type = SEGMENT_DESCRIPTOR_TYPE_LDT;
|
||||
accessRights.DescriptorType = FALSE;
|
||||
VmxWrite(VMCS_GUEST_LDTR_SELECTOR, 0);
|
||||
VmxWrite(VMCS_GUEST_LDTR_BASE, 0);
|
||||
VmxWrite(VMCS_GUEST_LDTR_LIMIT, 0xffff);
|
||||
VmxWrite(VMCS_GUEST_LDTR_ACCESS_RIGHTS, accessRights.Flags);
|
||||
|
||||
accessRights.Type = SEGMENT_DESCRIPTOR_TYPE_TSS_BUSY;
|
||||
VmxWrite(VMCS_GUEST_TR_SELECTOR, 0);
|
||||
VmxWrite(VMCS_GUEST_TR_BASE, 0);
|
||||
VmxWrite(VMCS_GUEST_TR_LIMIT, 0xffff);
|
||||
VmxWrite(VMCS_GUEST_TR_ACCESS_RIGHTS, accessRights.Flags);
|
||||
|
||||
__writedr(0, 0);
|
||||
__writedr(1, 0);
|
||||
__writedr(2, 0);
|
||||
__writedr(3, 0);
|
||||
__writedr(6, 0xffff0ff0);
|
||||
VmxWrite(VMCS_GUEST_DR7, 0x400);
|
||||
|
||||
GuestContext->StackBasedRegisters->R8 = 0;
|
||||
GuestContext->StackBasedRegisters->R9 = 0;
|
||||
GuestContext->StackBasedRegisters->R10 = 0;
|
||||
GuestContext->StackBasedRegisters->R11 = 0;
|
||||
GuestContext->StackBasedRegisters->R12 = 0;
|
||||
GuestContext->StackBasedRegisters->R13 = 0;
|
||||
GuestContext->StackBasedRegisters->R14 = 0;
|
||||
GuestContext->StackBasedRegisters->R15 = 0;
|
||||
|
||||
//
|
||||
// Those registers are supposed to be cleared but that is not implemented here.
|
||||
// - IA32_XSS
|
||||
// - BNDCFGU
|
||||
// - BND0-BND3
|
||||
// - IA32_BNDCFGS
|
||||
//
|
||||
|
||||
VmxWrite(VMCS_GUEST_EFER, 0);
|
||||
VmxWrite(VMCS_GUEST_FS_BASE, 0);
|
||||
VmxWrite(VMCS_GUEST_GS_BASE, 0);
|
||||
|
||||
vmEntryControls.Flags = VmxRead(VMCS_CTRL_VMENTRY_CONTROLS);
|
||||
vmEntryControls.Ia32EModeGuest = FALSE;
|
||||
VmxWrite(VMCS_CTRL_VMENTRY_CONTROLS, vmEntryControls.Flags);
|
||||
|
||||
//
|
||||
// Then, emulate effects of SIPI by making further changes.
|
||||
//
|
||||
// "For a start-up IPI (SIPI), the exit qualification contains the SIPI
|
||||
// vector information in bits 7:0. Bits 63:8 of the exit qualification are
|
||||
// cleared to 0."
|
||||
// See: 27.2.1 Basic VM-Exit Information
|
||||
//
|
||||
vector = VmxRead(VMCS_EXIT_QUALIFICATION);
|
||||
|
||||
//
|
||||
// "At the end of the boot-strap procedure, the BSP sets ... broadcasts a
|
||||
// SIPI message to all the APs in the system. Here, the SIPI message contains
|
||||
// a vector to the BIOS AP initialization code (at 000VV000H, where VV is the
|
||||
// vector contained in the SIPI message)."
|
||||
//
|
||||
// See: 8.4.3 MP Initialization Protocol Algorithm for MP Systems
|
||||
//
|
||||
VmxWrite(VMCS_GUEST_CS_SELECTOR, ((UINT64)vector) << 8);
|
||||
VmxWrite(VMCS_GUEST_CS_BASE, ((UINT64)vector) << 12);
|
||||
VmxWrite(VMCS_GUEST_RIP, 0);
|
||||
|
||||
//
|
||||
// Changing CR0.PG from 1 to 0 *using the MOV instruction* invalidates TLBs.
|
||||
// The case with INIT-SIPI does not seem to be documented but we do so just
|
||||
// in case. Emulate this invalidating combined caches (GVA to HPA translation
|
||||
// caches).
|
||||
//
|
||||
InvalidateVpidDerivedCache((UINT16)VmxRead(VMCS_CTRL_VIRTUAL_PROCESSOR_IDENTIFIER));
|
||||
|
||||
//
|
||||
// Done
|
||||
//
|
||||
VmxWrite(VMCS_GUEST_ACTIVITY_STATE, VmxActive);
|
||||
|
||||
Exit:
|
||||
return;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Handles VM-exit. This is the C-level entry point of the hypervisor.
|
||||
|
||||
@details This function is called the actual entry point of hypervisor, the
|
||||
AsmHypervisorEntryPoint function, after it preserved guest registers to
|
||||
stack as necessary. Such register values can be referenced and updated
|
||||
through the point to the stack location as provided by the Stack
|
||||
parameter. Those values are restored in the AsmHypervisorEntryPoint
|
||||
function after this function is executed and reflected to the guest.
|
||||
|
||||
Any hypervisor code including this and the AsmHypervisorEntryPoint
|
||||
functions are executed while interrupt is disabled via RFLAGS.IF being
|
||||
0 (See: 27.5.3 Loading Host RIP, RSP, and RFLAGS). This means IPI, if
|
||||
requested, is never delivered and causes deadlock. This condition is
|
||||
essentially equal to IRQL being HIGH_LEVEL (i.e., at a higher IRQL than
|
||||
IPI_LEVEL), and so, it is unsafe to call any Windows provided API that
|
||||
is not stated as callable at HIGH_LEVEL.
|
||||
|
||||
@param[in,out] Stack - A pointer to the hypervisor stack containing the
|
||||
guest register values.
|
||||
|
||||
@return TRUE when virtualization should continue and the VMRESUME instruction
|
||||
should be executed. FALSE when it should end and the VMXOFF instruction
|
||||
should be executed.
|
||||
*/
|
||||
_Must_inspect_result_
|
||||
BOOLEAN
|
||||
HandleVmExit (
|
||||
_Inout_ INITIAL_HYPERVISOR_STACK* Stack
|
||||
)
|
||||
{
|
||||
VMX_VMEXIT_REASON vmExitReason;
|
||||
GUEST_CONTEXT guestContext;
|
||||
|
||||
//
|
||||
// "Determine the exit reason through a VMREAD of the exit-reason field in
|
||||
// the working-VMCS."
|
||||
// See: 31.7 HANDLING OF VM EXITS
|
||||
//
|
||||
vmExitReason.Flags = (UINT32)VmxRead(VMCS_EXIT_REASON);
|
||||
|
||||
//
|
||||
// Copy some pointers to a single structure for ease of use.
|
||||
//
|
||||
guestContext.ContinueVm = TRUE;
|
||||
guestContext.Contexts = &Stack->HypervisorContext;
|
||||
guestContext.StackBasedRegisters = &Stack->GuestRegisters;
|
||||
|
||||
//
|
||||
// Read some of commonly used guest registers that are stored in the VMCS
|
||||
// (instead of stack). Reading them are useful for debugging, as we cannot
|
||||
// tell which instruction caused the VM-exit if VMCS_GUEST_RIP is not read,
|
||||
// for example. Note that those values are not automatically written back to
|
||||
// the VMCS. When any of those values should be updated and reflected to the
|
||||
// guest, the VMWRITE instruction (the VmxWrite function) should be used.
|
||||
//
|
||||
guestContext.VmcsBasedRegisters.Rflags.Flags = VmxRead(VMCS_GUEST_RFLAGS);
|
||||
guestContext.VmcsBasedRegisters.Rsp = VmxRead(VMCS_GUEST_RSP);
|
||||
guestContext.VmcsBasedRegisters.Rip = VmxRead(VMCS_GUEST_RIP);
|
||||
|
||||
//
|
||||
// Update the _KTRAP_FRAME structure values in hypervisor stack, so that
|
||||
// Windbg can reconstruct call stack of the guest during debug session.
|
||||
// This is optional but very useful thing to do for debugging.
|
||||
//
|
||||
Stack->TrapFrame.Rsp = guestContext.VmcsBasedRegisters.Rsp;
|
||||
Stack->TrapFrame.Rip = guestContext.VmcsBasedRegisters.Rip +
|
||||
VmxRead(VMCS_VMEXIT_INSTRUCTION_LENGTH);
|
||||
|
||||
//
|
||||
// Comment in this for debugging the handlers below.
|
||||
//
|
||||
//MV_DEBUG_BREAK();
|
||||
|
||||
switch (vmExitReason.BasicExitReason)
|
||||
{
|
||||
case VMX_EXIT_REASON_EXCEPTION_OR_NMI:
|
||||
HandleExceptionOrNmi(&guestContext);
|
||||
break;
|
||||
|
||||
case VMX_EXIT_REASON_INIT_SIGNAL:
|
||||
HandleInitSignal(&guestContext);
|
||||
break;
|
||||
|
||||
case VMX_EXIT_REASON_STARTUP_IPI:
|
||||
HandleStartupIpi(&guestContext);
|
||||
break;
|
||||
|
||||
case VMX_EXIT_REASON_EXECUTE_CPUID:
|
||||
HandleCpuid(&guestContext);
|
||||
break;
|
||||
|
||||
case VMX_EXIT_REASON_EXECUTE_VMCALL:
|
||||
HandleVmCall(&guestContext);
|
||||
break;
|
||||
|
||||
case VMX_EXIT_REASON_MOV_CR:
|
||||
HandleCrAccess(&guestContext);
|
||||
break;
|
||||
|
||||
case VMX_EXIT_REASON_EXECUTE_RDMSR:
|
||||
HandleMsrRead(&guestContext);
|
||||
break;
|
||||
|
||||
case VMX_EXIT_REASON_EXECUTE_WRMSR:
|
||||
HandleMsrWrite(&guestContext);
|
||||
break;
|
||||
|
||||
case VMX_EXIT_REASON_EPT_VIOLATION:
|
||||
HandleEptViolation(&guestContext);
|
||||
break;
|
||||
|
||||
case VMX_EXIT_REASON_EPT_MISCONFIGURATION:
|
||||
HandleEptMisconfig(&guestContext);
|
||||
break;
|
||||
|
||||
case VMX_EXIT_REASON_EXECUTE_XSETBV:
|
||||
HandleXsetbv(&guestContext);
|
||||
break;
|
||||
|
||||
default:
|
||||
DumpGuestState();
|
||||
DumpHostState();
|
||||
DumpControl();
|
||||
LOG_DEBUG("VM-exit reason (Full) = %x", vmExitReason.Flags);
|
||||
MV_PANIC();
|
||||
}
|
||||
|
||||
if (guestContext.ContinueVm == FALSE)
|
||||
{
|
||||
//
|
||||
// End of virtualization is requested. prevent undesired retention of
|
||||
// cache.
|
||||
//
|
||||
// "Software can use the INVVPID instruction with the "all-context" INVVPID
|
||||
// type (...) immediately prior to execution of the VMXOFF instruction."
|
||||
// "Software can use the INVEPT instruction with the "all-context" INVEPT
|
||||
// type (...) immediately prior to execution of the VMXOFF instruction."
|
||||
// See: 28.3.3.3 Guidelines for Use of the INVVPID Instruction
|
||||
// See: 28.3.3.4 Guidelines for Use of the INVEPT Instruction
|
||||
//
|
||||
InvalidateEptDerivedCache(0);
|
||||
InvalidateVpidDerivedCache(0);
|
||||
}
|
||||
|
||||
return guestContext.ContinueVm;
|
||||
}
|
||||
|
||||
typedef struct _EXCEPTION_STACK
|
||||
{
|
||||
UINT64 R15;
|
||||
UINT64 R14;
|
||||
UINT64 R13;
|
||||
UINT64 R12;
|
||||
UINT64 R11;
|
||||
UINT64 R10;
|
||||
UINT64 R9;
|
||||
UINT64 R8;
|
||||
UINT64 Rdi;
|
||||
UINT64 Rsi;
|
||||
UINT64 Rbp;
|
||||
UINT64 Rbx;
|
||||
UINT64 Rdx;
|
||||
UINT64 Rcx;
|
||||
UINT64 Rax;
|
||||
UINT64 InterruptNumber;
|
||||
UINT64 ErrorCode;
|
||||
UINT64 Rip;
|
||||
UINT64 Cs;
|
||||
UINT64 Rflags;
|
||||
} EXCEPTION_STACK;
|
||||
|
||||
/*!
|
||||
@brief Handles the interrupt and exception occurred during execution of the
|
||||
host.
|
||||
|
||||
@details On Windows, this function is unused because the host uses the same
|
||||
IDT as that of the guest. All interrupts and exceptions are handled by
|
||||
the NT kernel.
|
||||
|
||||
@param[in] Stack - The pointer to the hypervisor stack containing the
|
||||
guest register values.
|
||||
*/
|
||||
VOID
|
||||
HandleHostException (
|
||||
_In_ CONST EXCEPTION_STACK* Stack
|
||||
)
|
||||
{
|
||||
DumpGuestState();
|
||||
DumpHostState();
|
||||
DumpControl();
|
||||
LOG_ERROR("Exception or interrupt 0x%llx(0x%llx)", Stack->InterruptNumber, Stack->ErrorCode);
|
||||
LOG_ERROR("RIP - %016llx, CS - %016llx, RFLAGS - %016llx", Stack->Rip, Stack->Cs, Stack->Rflags);
|
||||
LOG_ERROR("RAX - %016llx, RCX - %016llx, RDX - %016llx", Stack->Rax, Stack->Rcx, Stack->Rdx);
|
||||
LOG_ERROR("RBX - %016llx, RSP - %016llx, RBP - %016llx", Stack->Rbx, 0ull, Stack->Rbp);
|
||||
LOG_ERROR("RSI - %016llx, RDI - %016llx", Stack->Rsi, Stack->Rdi);
|
||||
LOG_ERROR("R8 - %016llx, R9 - %016llx, R10 - %016llx", Stack->R8, Stack->R9, Stack->R10);
|
||||
LOG_ERROR("R11 - %016llx, R12 - %016llx, R13 - %016llx", Stack->R11, Stack->R12, Stack->R13);
|
||||
LOG_ERROR("R14 - %016llx, R15 - %016llx", Stack->R14, Stack->R15);
|
||||
MV_PANIC();
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Handles error occurred on attempt to exit to the guest.
|
||||
|
||||
@param[in] Stack - The pointer to the hypervisor stack containing the
|
||||
guest register values.
|
||||
*/
|
||||
VOID
|
||||
HandleVmExitFailure (
|
||||
_In_ CONST INITIAL_HYPERVISOR_STACK* Stack
|
||||
)
|
||||
{
|
||||
VMX_ERROR_NUMBER vmxErrorNumber;
|
||||
VMX_VMEXIT_REASON vmExitReason;
|
||||
|
||||
UNREFERENCED_PARAMETER(Stack);
|
||||
|
||||
vmxErrorNumber = (VMX_ERROR_NUMBER)VmxRead(VMCS_VM_INSTRUCTION_ERROR);
|
||||
vmExitReason.Flags = (UINT32)VmxRead(VMCS_EXIT_REASON);
|
||||
|
||||
DumpGuestState();
|
||||
DumpHostState();
|
||||
DumpControl();
|
||||
LOG_ERROR("VM-exit reason (full) = %x, Error = %ul", vmExitReason.Flags, vmxErrorNumber);
|
||||
MV_PANIC();
|
||||
}
|
||||
11
Sources/HostMain.h
Normal file
11
Sources/HostMain.h
Normal file
@@ -0,0 +1,11 @@
|
||||
/*!
|
||||
@file HostMain.h
|
||||
|
||||
@brief Functions for VM-exit handling.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 -, Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#pragma once
|
||||
#include "Common.h"
|
||||
79
Sources/HostNesting.h
Normal file
79
Sources/HostNesting.h
Normal file
@@ -0,0 +1,79 @@
|
||||
/*!
|
||||
@file HostNesting.h
|
||||
|
||||
@brief Incomplete nesting related code. Do not study.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#pragma once
|
||||
#include "Common.h"
|
||||
#include "Ia32.h"
|
||||
#include "HostUtils.h"
|
||||
#include "ExtendedPageTables.h"
|
||||
|
||||
typedef enum _VMX_OPERATION
|
||||
{
|
||||
VmxOperationNotInVmxOperation,
|
||||
VmxOperationRoot,
|
||||
VmxOperationNonRoot,
|
||||
} VMX_OPERATION;
|
||||
|
||||
typedef enum _VMCS_LAUNCH_STATE
|
||||
{
|
||||
LaunchStateUnintialized,
|
||||
LaunchStateClear,
|
||||
LaunchStateLaunched,
|
||||
} VMCS_LAUNCH_STATE;
|
||||
|
||||
typedef struct _NEXTED_VMX_CONTEXT
|
||||
{
|
||||
VMX_OPERATION VmxOperation;
|
||||
VMCS_LAUNCH_STATE VmcsLaunchState;
|
||||
|
||||
//
|
||||
// The physical address of the VMXON region (used by L1 for L2)
|
||||
//
|
||||
UINT64 Vmxon12Pa;
|
||||
|
||||
//
|
||||
// The physical addresses of VMCSs (used by L0 for L1 and L2)
|
||||
//
|
||||
UINT64 Vmcs01Pa;
|
||||
UINT64 Vmcs02Pa;
|
||||
|
||||
//
|
||||
// Current VMCS from the point of view of the nested VMM (used by L1 for L2)
|
||||
//
|
||||
UINT64 Vmcs12Pa;
|
||||
|
||||
//
|
||||
// EPT related data (used by L0 for L2)
|
||||
//
|
||||
EPT_CONTEXT Ept02Context;
|
||||
EPT_PML4* EptPml4_02;
|
||||
|
||||
//
|
||||
// VMCS (used by L0 for L2).
|
||||
//
|
||||
DECLSPEC_ALIGN(PAGE_SIZE) VMCS Vmcs02;
|
||||
} NEXTED_VMX_CONTEXT;
|
||||
|
||||
VOID
|
||||
HandleVmx (
|
||||
_Inout_ GUEST_CONTEXT* GuestContext,
|
||||
_In_ UINT32 ExitReason
|
||||
);
|
||||
|
||||
VOID
|
||||
EmulateVmExitForL1Vmm (
|
||||
_Inout_ GUEST_CONTEXT* GuestContext,
|
||||
_In_ UINT32 ExitReason
|
||||
);
|
||||
|
||||
BOOLEAN
|
||||
IsVmExitForL1 (
|
||||
CONST GUEST_CONTEXT* GuestContext,
|
||||
VMX_VMEXIT_REASON VmExitReason
|
||||
);
|
||||
415
Sources/HostUtils.c
Normal file
415
Sources/HostUtils.c
Normal file
@@ -0,0 +1,415 @@
|
||||
/*!
|
||||
@file HostUtils.c
|
||||
|
||||
@brief Utility functions and structures for the host.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#include "HostUtils.h"
|
||||
#include "Logger.h"
|
||||
#include "ExtendedPageTables.h"
|
||||
#include "Utils.h"
|
||||
|
||||
/*!
|
||||
@brief Dumps the segment access rights value.
|
||||
|
||||
@param[in] AccessRights - The segment access rights value to dump.
|
||||
*/
|
||||
static
|
||||
VOID
|
||||
DumpAccessRights (
|
||||
_In_ UINT64 AccessRights
|
||||
)
|
||||
{
|
||||
VMX_SEGMENT_ACCESS_RIGHTS rights;
|
||||
|
||||
rights.Flags = (UINT32)AccessRights;
|
||||
LOG_ERROR(" - Type = %ul", rights.Type);
|
||||
LOG_ERROR(" - S = %ul", rights.DescriptorType);
|
||||
LOG_ERROR(" - DPL = %ul", rights.DescriptorPrivilegeLevel);
|
||||
LOG_ERROR(" - P = %ul", rights.Present);
|
||||
LOG_ERROR(" - Reserved1 = %ul", rights.Reserved1);
|
||||
LOG_ERROR(" - Available = %ul", rights.AvailableBit);
|
||||
LOG_ERROR(" - L = %ul", rights.LongMode);
|
||||
LOG_ERROR(" - D/B = %ul", rights.DefaultBig);
|
||||
LOG_ERROR(" - G = %ul", rights.Granularity);
|
||||
LOG_ERROR(" - Unusable = %ul", rights.Unusable);
|
||||
LOG_ERROR(" - Reserved2 = %ul", rights.Reserved2);
|
||||
}
|
||||
|
||||
VOID
|
||||
DumpHostState (
|
||||
)
|
||||
{
|
||||
//
|
||||
// 16-Bit Host-State Fields
|
||||
//
|
||||
LOG_ERROR("Host ES Selector = %016llx", VmxRead(VMCS_HOST_ES_SELECTOR));
|
||||
LOG_ERROR("Host CS Selector = %016llx", VmxRead(VMCS_HOST_CS_SELECTOR));
|
||||
LOG_ERROR("Host SS Selector = %016llx", VmxRead(VMCS_HOST_SS_SELECTOR));
|
||||
LOG_ERROR("Host DS Selector = %016llx", VmxRead(VMCS_HOST_DS_SELECTOR));
|
||||
LOG_ERROR("Host FS Selector = %016llx", VmxRead(VMCS_HOST_FS_SELECTOR));
|
||||
LOG_ERROR("Host GS Selector = %016llx", VmxRead(VMCS_HOST_GS_SELECTOR));
|
||||
LOG_ERROR("Host TR Selector = %016llx", VmxRead(VMCS_HOST_TR_SELECTOR));
|
||||
|
||||
//
|
||||
// 64-Bit Host-State Fields
|
||||
//
|
||||
LOG_ERROR("Host IA32_PAT = %016llx", VmxRead(VMCS_HOST_PAT));
|
||||
LOG_ERROR("Host IA32_EFER = %016llx", VmxRead(VMCS_HOST_EFER));
|
||||
LOG_ERROR("Host IA32_PERF_GLOBAL_CTRL = %016llx", VmxRead(VMCS_HOST_PERF_GLOBAL_CTRL));
|
||||
|
||||
//
|
||||
// 32-Bit Host-State Fields
|
||||
//
|
||||
LOG_ERROR("Host IA32_SYSENTER_CS = %016llx", VmxRead(VMCS_HOST_SYSENTER_CS));
|
||||
|
||||
//
|
||||
// Natural-Width Host-State Fields
|
||||
//
|
||||
LOG_ERROR("Host CR0 = %016llx", VmxRead(VMCS_HOST_CR0));
|
||||
LOG_ERROR("Host CR3 = %016llx", VmxRead(VMCS_HOST_CR3));
|
||||
LOG_ERROR("Host CR4 = %016llx", VmxRead(VMCS_HOST_CR4));
|
||||
LOG_ERROR("Host FS Base = %016llx", VmxRead(VMCS_HOST_FS_BASE));
|
||||
LOG_ERROR("Host GS Base = %016llx", VmxRead(VMCS_HOST_GS_BASE));
|
||||
LOG_ERROR("Host TR base = %016llx", VmxRead(VMCS_HOST_TR_BASE));
|
||||
LOG_ERROR("Host GDTR base = %016llx", VmxRead(VMCS_HOST_GDTR_BASE));
|
||||
LOG_ERROR("Host IDTR base = %016llx", VmxRead(VMCS_HOST_IDTR_BASE));
|
||||
LOG_ERROR("Host IA32_SYSENTER_ESP = %016llx", VmxRead(VMCS_HOST_SYSENTER_ESP));
|
||||
LOG_ERROR("Host IA32_SYSENTER_EIP = %016llx", VmxRead(VMCS_HOST_SYSENTER_EIP));
|
||||
LOG_ERROR("Host RSP = %016llx", VmxRead(VMCS_HOST_RSP));
|
||||
LOG_ERROR("Host RIP = %016llx", VmxRead(VMCS_HOST_RIP));
|
||||
}
|
||||
|
||||
VOID
|
||||
DumpGuestState (
|
||||
)
|
||||
{
|
||||
//
|
||||
// 16-Bit Guest-State Fields
|
||||
//
|
||||
LOG_ERROR("Guest ES Selector = %016llx", VmxRead(VMCS_GUEST_ES_SELECTOR));
|
||||
LOG_ERROR("Guest CS Selector = %016llx", VmxRead(VMCS_GUEST_CS_SELECTOR));
|
||||
LOG_ERROR("Guest SS Selector = %016llx", VmxRead(VMCS_GUEST_SS_SELECTOR));
|
||||
LOG_ERROR("Guest DS Selector = %016llx", VmxRead(VMCS_GUEST_DS_SELECTOR));
|
||||
LOG_ERROR("Guest FS Selector = %016llx", VmxRead(VMCS_GUEST_FS_SELECTOR));
|
||||
LOG_ERROR("Guest GS Selector = %016llx", VmxRead(VMCS_GUEST_GS_SELECTOR));
|
||||
LOG_ERROR("Guest LDTR Selector = %016llx", VmxRead(VMCS_GUEST_LDTR_SELECTOR));
|
||||
LOG_ERROR("Guest TR Selector = %016llx", VmxRead(VMCS_GUEST_TR_SELECTOR));
|
||||
LOG_ERROR("Guest interrupt status = %016llx", VmxRead(VMCS_GUEST_INTERRUPT_STATUS));
|
||||
LOG_ERROR("PML index = %016llx", VmxRead(VMCS_GUEST_PML_INDEX));
|
||||
|
||||
//
|
||||
// 64-Bit Guest-State Fields
|
||||
//
|
||||
LOG_ERROR("VMCS link pointer = %016llx", VmxRead(VMCS_GUEST_VMCS_LINK_POINTER));
|
||||
LOG_ERROR("Guest IA32_DEBUGCTL = %016llx", VmxRead(VMCS_GUEST_DEBUGCTL));
|
||||
LOG_ERROR("Guest IA32_PAT = %016llx", VmxRead(VMCS_GUEST_PAT));
|
||||
LOG_ERROR("Guest IA32_EFER = %016llx", VmxRead(VMCS_GUEST_EFER));
|
||||
LOG_ERROR("Guest IA32_PERF_GLOBAL_CTRL = %016llx", VmxRead(VMCS_GUEST_PERF_GLOBAL_CTRL));
|
||||
LOG_ERROR("Guest PDPTE0 = %016llx", VmxRead(VMCS_GUEST_PDPTE0));
|
||||
LOG_ERROR("Guest PDPTE1 = %016llx", VmxRead(VMCS_GUEST_PDPTE1));
|
||||
LOG_ERROR("Guest PDPTE2 = %016llx", VmxRead(VMCS_GUEST_PDPTE2));
|
||||
LOG_ERROR("Guest PDPTE3 = %016llx", VmxRead(VMCS_GUEST_PDPTE3));
|
||||
LOG_ERROR("Guest IA32_BNDCFGS = %016llx", VmxRead(VMCS_GUEST_BNDCFGS));
|
||||
LOG_ERROR("Guest IA32_RTIT_CTL = %016llx", VmxRead(VMCS_GUEST_RTIT_CTL));
|
||||
|
||||
//
|
||||
// 32-Bit Guest-State Fields
|
||||
//
|
||||
LOG_ERROR("Guest ES Limit = %016llx", VmxRead(VMCS_GUEST_ES_LIMIT));
|
||||
LOG_ERROR("Guest CS Limit = %016llx", VmxRead(VMCS_GUEST_CS_LIMIT));
|
||||
LOG_ERROR("Guest SS Limit = %016llx", VmxRead(VMCS_GUEST_SS_LIMIT));
|
||||
LOG_ERROR("Guest DS Limit = %016llx", VmxRead(VMCS_GUEST_DS_LIMIT));
|
||||
LOG_ERROR("Guest FS Limit = %016llx", VmxRead(VMCS_GUEST_FS_LIMIT));
|
||||
LOG_ERROR("Guest GS Limit = %016llx", VmxRead(VMCS_GUEST_GS_LIMIT));
|
||||
LOG_ERROR("Guest LDTR Limit = %016llx", VmxRead(VMCS_GUEST_LDTR_LIMIT));
|
||||
LOG_ERROR("Guest TR Limit = %016llx", VmxRead(VMCS_GUEST_TR_LIMIT));
|
||||
LOG_ERROR("Guest GDTR limit = %016llx", VmxRead(VMCS_GUEST_GDTR_LIMIT));
|
||||
LOG_ERROR("Guest IDTR limit = %016llx", VmxRead(VMCS_GUEST_IDTR_LIMIT));
|
||||
LOG_ERROR("Guest ES access rights = %016llx", VmxRead(VMCS_GUEST_ES_ACCESS_RIGHTS));
|
||||
LOG_ERROR("Guest CS access rights = %016llx", VmxRead(VMCS_GUEST_CS_ACCESS_RIGHTS));
|
||||
LOG_ERROR("Guest SS access rights = %016llx", VmxRead(VMCS_GUEST_SS_ACCESS_RIGHTS));
|
||||
LOG_ERROR("Guest DS access rights = %016llx", VmxRead(VMCS_GUEST_DS_ACCESS_RIGHTS));
|
||||
LOG_ERROR("Guest FS access rights = %016llx", VmxRead(VMCS_GUEST_FS_ACCESS_RIGHTS));
|
||||
LOG_ERROR("Guest GS access rights = %016llx", VmxRead(VMCS_GUEST_GS_ACCESS_RIGHTS));
|
||||
LOG_ERROR("Guest LDTR access rights = %016llx", VmxRead(VMCS_GUEST_LDTR_ACCESS_RIGHTS));
|
||||
LOG_ERROR("Guest TR access rights = %016llx", VmxRead(VMCS_GUEST_TR_ACCESS_RIGHTS));
|
||||
LOG_ERROR("Guest interruptibility state = %016llx", VmxRead(VMCS_GUEST_INTERRUPTIBILITY_STATE));
|
||||
LOG_ERROR("Guest activity state = %016llx", VmxRead(VMCS_GUEST_ACTIVITY_STATE));
|
||||
LOG_ERROR("Guest SMBASE = %016llx", VmxRead(VMCS_GUEST_SMBASE));
|
||||
LOG_ERROR("Guest IA32_SYSENTER_CS = %016llx", VmxRead(VMCS_GUEST_SYSENTER_CS));
|
||||
LOG_ERROR("VMX-preemption timer value = %016llx", VmxRead(VMCS_GUEST_VMX_PREEMPTION_TIMER_VALUE));
|
||||
|
||||
//
|
||||
// Natural-Width Guest-State Fields
|
||||
//
|
||||
LOG_ERROR("Guest CR0 = %016llx", VmxRead(VMCS_GUEST_CR0));
|
||||
LOG_ERROR("Guest CR3 = %016llx", VmxRead(VMCS_GUEST_CR3));
|
||||
LOG_ERROR("Guest CR4 = %016llx", VmxRead(VMCS_GUEST_CR4));
|
||||
LOG_ERROR("Guest ES Base = %016llx", VmxRead(VMCS_GUEST_ES_BASE));
|
||||
LOG_ERROR("Guest CS Base = %016llx", VmxRead(VMCS_GUEST_CS_BASE));
|
||||
LOG_ERROR("Guest SS Base = %016llx", VmxRead(VMCS_GUEST_SS_BASE));
|
||||
LOG_ERROR("Guest DS Base = %016llx", VmxRead(VMCS_GUEST_DS_BASE));
|
||||
LOG_ERROR("Guest FS Base = %016llx", VmxRead(VMCS_GUEST_FS_BASE));
|
||||
LOG_ERROR("Guest GS Base = %016llx", VmxRead(VMCS_GUEST_GS_BASE));
|
||||
LOG_ERROR("Guest LDTR base = %016llx", VmxRead(VMCS_GUEST_LDTR_BASE));
|
||||
LOG_ERROR("Guest TR base = %016llx", VmxRead(VMCS_GUEST_TR_BASE));
|
||||
LOG_ERROR("Guest GDTR base = %016llx", VmxRead(VMCS_GUEST_GDTR_BASE));
|
||||
LOG_ERROR("Guest IDTR base = %016llx", VmxRead(VMCS_GUEST_IDTR_BASE));
|
||||
LOG_ERROR("Guest DR7 = %016llx", VmxRead(VMCS_GUEST_DR7));
|
||||
LOG_ERROR("Guest RSP = %016llx", VmxRead(VMCS_GUEST_RSP));
|
||||
LOG_ERROR("Guest RIP = %016llx", VmxRead(VMCS_GUEST_RIP));
|
||||
LOG_ERROR("Guest RFLAGS = %016llx", VmxRead(VMCS_GUEST_RFLAGS));
|
||||
LOG_ERROR("Guest pending debug exceptions = %016llx", VmxRead(VMCS_GUEST_PENDING_DEBUG_EXCEPTIONS));
|
||||
LOG_ERROR("Guest IA32_SYSENTER_ESP = %016llx", VmxRead(VMCS_GUEST_SYSENTER_ESP));
|
||||
LOG_ERROR("Guest IA32_SYSENTER_EIP = %016llx", VmxRead(VMCS_GUEST_SYSENTER_EIP));
|
||||
}
|
||||
|
||||
VOID
|
||||
DumpControl (
|
||||
)
|
||||
{
|
||||
//
|
||||
// 16-Bit Control Fields
|
||||
//
|
||||
LOG_ERROR("Virtual-processor identifier = %016llx", VmxRead(VMCS_CTRL_VIRTUAL_PROCESSOR_IDENTIFIER));
|
||||
LOG_ERROR("Posted-interrupt notification vector = %016llx", VmxRead(VMCS_CTRL_POSTED_INTERRUPT_NOTIFICATION_VECTOR));
|
||||
LOG_ERROR("EPTP index = %016llx", VmxRead(VMCS_CTRL_EPTP_INDEX));
|
||||
|
||||
//
|
||||
// 64-Bit Control Fields
|
||||
//
|
||||
LOG_ERROR("Address of I/O bitmap A = %016llx", VmxRead(VMCS_CTRL_IO_BITMAP_A_ADDRESS));
|
||||
LOG_ERROR("Address of I/O bitmap B = %016llx", VmxRead(VMCS_CTRL_IO_BITMAP_B_ADDRESS));
|
||||
LOG_ERROR("Address of MSR bitmaps = %016llx", VmxRead(VMCS_CTRL_MSR_BITMAP_ADDRESS));
|
||||
LOG_ERROR("VM-exit MSR-store address = %016llx", VmxRead(VMCS_CTRL_VMEXIT_MSR_STORE_ADDRESS));
|
||||
LOG_ERROR("VM-exit MSR-load address = %016llx", VmxRead(VMCS_CTRL_VMEXIT_MSR_LOAD_ADDRESS));
|
||||
LOG_ERROR("VM-entry MSR-load address = %016llx", VmxRead(VMCS_CTRL_VMENTRY_MSR_LOAD_ADDRESS));
|
||||
LOG_ERROR("Executive-VMCS pointer = %016llx", VmxRead(VMCS_CTRL_EXECUTIVE_VMCS_POINTER));
|
||||
LOG_ERROR("PML address = %016llx", VmxRead(VMCS_CTRL_PML_ADDRESS));
|
||||
LOG_ERROR("TSC offset = %016llx", VmxRead(VMCS_CTRL_TSC_OFFSET));
|
||||
LOG_ERROR("Virtual-APIC address = %016llx", VmxRead(VMCS_CTRL_VIRTUAL_APIC_ADDRESS));
|
||||
LOG_ERROR("APIC-access address = %016llx", VmxRead(VMCS_CTRL_APIC_ACCESS_ADDRESS));
|
||||
LOG_ERROR("Posted-interrupt descriptor address = %016llx", VmxRead(VMCS_CTRL_POSTED_INTERRUPT_DESCRIPTOR_ADDRESS));
|
||||
LOG_ERROR("VM-function controls = %016llx", VmxRead(VMCS_CTRL_VMFUNC_CONTROLS));
|
||||
LOG_ERROR("EPT pointer = %016llx", VmxRead(VMCS_CTRL_EPT_POINTER));
|
||||
LOG_ERROR("EOI-exit bitmap 0 = %016llx", VmxRead(VMCS_CTRL_EOI_EXIT_BITMAP_0));
|
||||
LOG_ERROR("EOI-exit bitmap 1 = %016llx", VmxRead(VMCS_CTRL_EOI_EXIT_BITMAP_1));
|
||||
LOG_ERROR("EOI-exit bitmap 2 = %016llx", VmxRead(VMCS_CTRL_EOI_EXIT_BITMAP_2));
|
||||
LOG_ERROR("EOI-exit bitmap 3 = %016llx", VmxRead(VMCS_CTRL_EOI_EXIT_BITMAP_3));
|
||||
LOG_ERROR("EPTP-list address = %016llx", VmxRead(VMCS_CTRL_EPT_POINTER_LIST_ADDRESS));
|
||||
LOG_ERROR("VMREAD-bitmap address = %016llx", VmxRead(VMCS_CTRL_VMREAD_BITMAP_ADDRESS));
|
||||
LOG_ERROR("VMWRITE-bitmap address = %016llx", VmxRead(VMCS_CTRL_VMWRITE_BITMAP_ADDRESS));
|
||||
LOG_ERROR("Virtualization-exception information address = %016llx", VmxRead(VMCS_CTRL_VIRTUALIZATION_EXCEPTION_INFORMATION_ADDRESS));
|
||||
LOG_ERROR("XSS-exiting bitmap = %016llx", VmxRead(VMCS_CTRL_XSS_EXITING_BITMAP));
|
||||
LOG_ERROR("ENCLS-exiting bitmap = %016llx", VmxRead(VMCS_CTRL_ENCLS_EXITING_BITMAP));
|
||||
LOG_ERROR("TSC multiplier = %016llx", VmxRead(VMCS_CTRL_TSC_MULTIPLIER));
|
||||
|
||||
//
|
||||
// 32-Bit Control Fields
|
||||
//
|
||||
LOG_ERROR("Pin-based VM-execution controls = %016llx", VmxRead(VMCS_CTRL_PIN_BASED_VM_EXECUTION_CONTROLS));
|
||||
LOG_ERROR("Primary processor-based VM-execution controls = %016llx", VmxRead(VMCS_CTRL_PROCESSOR_BASED_VM_EXECUTION_CONTROLS));
|
||||
LOG_ERROR("Exception bitmap = %016llx", VmxRead(VMCS_CTRL_EXCEPTION_BITMAP));
|
||||
LOG_ERROR("Page-fault error-code mask = %016llx", VmxRead(VMCS_CTRL_PAGEFAULT_ERROR_CODE_MASK));
|
||||
LOG_ERROR("Page-fault error-code match = %016llx", VmxRead(VMCS_CTRL_PAGEFAULT_ERROR_CODE_MATCH));
|
||||
LOG_ERROR("CR3-target count = %016llx", VmxRead(VMCS_CTRL_CR3_TARGET_COUNT));
|
||||
LOG_ERROR("VM-exit controls = %016llx", VmxRead(VMCS_CTRL_VMEXIT_CONTROLS));
|
||||
LOG_ERROR("VM-exit MSR-store count = %016llx", VmxRead(VMCS_CTRL_VMEXIT_MSR_STORE_COUNT));
|
||||
LOG_ERROR("VM-exit MSR-load count = %016llx", VmxRead(VMCS_CTRL_VMEXIT_MSR_LOAD_COUNT));
|
||||
LOG_ERROR("VM-entry controls = %016llx", VmxRead(VMCS_CTRL_VMENTRY_CONTROLS));
|
||||
LOG_ERROR("VM-entry MSR-load count = %016llx", VmxRead(VMCS_CTRL_VMENTRY_MSR_LOAD_COUNT));
|
||||
LOG_ERROR("VM-entry interruption-information field = %016llx", VmxRead(VMCS_CTRL_VMENTRY_INTERRUPTION_INFORMATION_FIELD));
|
||||
LOG_ERROR("VM-entry exception error code = %016llx", VmxRead(VMCS_CTRL_VMENTRY_EXCEPTION_ERROR_CODE));
|
||||
LOG_ERROR("VM-entry instruction length = %016llx", VmxRead(VMCS_CTRL_VMENTRY_INSTRUCTION_LENGTH));
|
||||
LOG_ERROR("TPR threshold = %016llx", VmxRead(VMCS_CTRL_TPR_THRESHOLD));
|
||||
LOG_ERROR("Secondary processor-based VM-execution controls = %016llx", VmxRead(VMCS_CTRL_SECONDARY_PROCESSOR_BASED_VM_EXECUTION_CONTROLS));
|
||||
LOG_ERROR("PLE_Gap = %016llx", VmxRead(VMCS_CTRL_PLE_GAP));
|
||||
LOG_ERROR("PLE_Window = %016llx", VmxRead(VMCS_CTRL_PLE_WINDOW));
|
||||
|
||||
//
|
||||
// Natural-Width Control Fields
|
||||
//
|
||||
LOG_ERROR("CR0 guest/host mask = %016llx", VmxRead(VMCS_CTRL_CR0_GUEST_HOST_MASK));
|
||||
LOG_ERROR("CR4 guest/host mask = %016llx", VmxRead(VMCS_CTRL_CR4_GUEST_HOST_MASK));
|
||||
LOG_ERROR("CR0 read shadow = %016llx", VmxRead(VMCS_CTRL_CR0_READ_SHADOW));
|
||||
LOG_ERROR("CR4 read shadow = %016llx", VmxRead(VMCS_CTRL_CR4_READ_SHADOW));
|
||||
LOG_ERROR("CR3-target value 0 = %016llx", VmxRead(VMCS_CTRL_CR3_TARGET_VALUE_0));
|
||||
LOG_ERROR("CR3-target value 1 = %016llx", VmxRead(VMCS_CTRL_CR3_TARGET_VALUE_1));
|
||||
LOG_ERROR("CR3-target value 2 = %016llx", VmxRead(VMCS_CTRL_CR3_TARGET_VALUE_2));
|
||||
LOG_ERROR("CR3-target value 3 = %016llx", VmxRead(VMCS_CTRL_CR3_TARGET_VALUE_3));
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
VOID
|
||||
VmxWrite (
|
||||
VMCS_FIELD Field,
|
||||
UINT64 FieldValue
|
||||
)
|
||||
{
|
||||
VMX_RESULT result;
|
||||
|
||||
result = __vmx_vmwrite(Field, FieldValue);
|
||||
if (result != VmxResultOk)
|
||||
{
|
||||
VMX_ERROR_NUMBER vmxErrorStatus;
|
||||
|
||||
vmxErrorStatus = (result == VmxResultErrorWithStatus) ?
|
||||
(VMX_ERROR_NUMBER)VmxRead(VMCS_VM_INSTRUCTION_ERROR) : 0;
|
||||
if (vmxErrorStatus != VMX_ERROR_VMREAD_VMWRITE_INVALID_COMPONENT)
|
||||
{
|
||||
MV_PANIC();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
UINT64
|
||||
VmxRead (
|
||||
VMCS_FIELD Field
|
||||
)
|
||||
{
|
||||
VMX_RESULT result;
|
||||
UINT64 fieldValue;
|
||||
|
||||
result = __vmx_vmread(Field, &fieldValue);
|
||||
if (result != VmxResultOk)
|
||||
{
|
||||
VMX_ERROR_NUMBER vmxErrorStatus;
|
||||
|
||||
vmxErrorStatus = (result == VmxResultErrorWithStatus) ?
|
||||
(VMX_ERROR_NUMBER)VmxRead(VMCS_VM_INSTRUCTION_ERROR) : 0;
|
||||
if (vmxErrorStatus != VMX_ERROR_VMREAD_VMWRITE_INVALID_COMPONENT)
|
||||
{
|
||||
MV_PANIC();
|
||||
}
|
||||
fieldValue = MAXUINT64;
|
||||
}
|
||||
return fieldValue;
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
VOID
|
||||
AdvanceGuestInstructionPointer (
|
||||
GUEST_CONTEXT* GuestContext
|
||||
)
|
||||
{
|
||||
UINT64 exitInstructionLength;
|
||||
|
||||
exitInstructionLength = VmxRead(VMCS_VMEXIT_INSTRUCTION_LENGTH);
|
||||
GuestContext->VmcsBasedRegisters.Rip += exitInstructionLength;
|
||||
VmxWrite(VMCS_GUEST_RIP, GuestContext->VmcsBasedRegisters.Rip);
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
BOOLEAN
|
||||
IsGuestInKernelMode (
|
||||
VOID
|
||||
)
|
||||
{
|
||||
VMX_SEGMENT_ACCESS_RIGHTS accessRight;
|
||||
|
||||
accessRight.Flags = (UINT32)VmxRead(VMCS_GUEST_SS_ACCESS_RIGHTS);
|
||||
return (accessRight.DescriptorPrivilegeLevel == 0);
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
VOID
|
||||
InjectInterruption (
|
||||
INTERRUPTION_TYPE InterruptionType,
|
||||
EXCEPTION_VECTOR Vector,
|
||||
BOOLEAN DeliverErrorCode,
|
||||
UINT32 ErrorCode
|
||||
)
|
||||
{
|
||||
VMENTRY_INTERRUPT_INFORMATION interruptToInject;
|
||||
|
||||
interruptToInject.Flags = 0;
|
||||
interruptToInject.Valid = TRUE;
|
||||
interruptToInject.InterruptionType = (UINT32)InterruptionType;
|
||||
interruptToInject.Vector = (UINT32)Vector;
|
||||
interruptToInject.DeliverErrorCode = DeliverErrorCode;
|
||||
VmxWrite(VMCS_CTRL_VMENTRY_INTERRUPTION_INFORMATION_FIELD, interruptToInject.Flags);
|
||||
|
||||
if (DeliverErrorCode != FALSE)
|
||||
{
|
||||
VmxWrite(VMCS_CTRL_VMENTRY_EXCEPTION_ERROR_CODE, ErrorCode);
|
||||
}
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
VOID
|
||||
SwitchGuestPagingMode (
|
||||
CR0 NewGuestCr0
|
||||
)
|
||||
{
|
||||
IA32_EFER_REGISTER guestEfer;
|
||||
IA32_VMX_ENTRY_CTLS_REGISTER vmEntryControls;
|
||||
|
||||
//
|
||||
// "Enable paging by setting CR0.PG = 1. This causes the processor to set the
|
||||
// IA32_EFER.LMA bit to 1."
|
||||
// See: 9.8.5 Initializing IA-32e Mode
|
||||
//
|
||||
// "The processor always sets IA32_EFER.LMA to CR0.PG & IA32_EFER.LME.
|
||||
// Software cannot directly modify IA32_EFER.LMA; an execution of WRMSR to
|
||||
// the IA32_EFER MSR ignores bit 10 of its source operand."
|
||||
// See: 4.1.1 Three Paging Modes
|
||||
//
|
||||
guestEfer.Flags = VmxRead(VMCS_GUEST_EFER);
|
||||
guestEfer.Ia32EModeActive = (NewGuestCr0.PagingEnable & guestEfer.Ia32EModeEnable);
|
||||
VmxWrite(VMCS_GUEST_EFER, guestEfer.Flags);
|
||||
|
||||
//
|
||||
// Apply the paging mode change in the VM-entry control VMCS field too.
|
||||
//
|
||||
vmEntryControls.Flags = VmxRead(VMCS_CTRL_VMENTRY_CONTROLS);
|
||||
vmEntryControls.Ia32EModeGuest = guestEfer.Ia32EModeActive;
|
||||
VmxWrite(VMCS_CTRL_VMENTRY_CONTROLS, vmEntryControls.Flags);
|
||||
|
||||
//
|
||||
// Changing the paging mode results in invalidating TLB. Emulate this by
|
||||
// invalidating combined caches (GVA to HPA translation caches).
|
||||
//
|
||||
InvalidateVpidDerivedCache((UINT16)VmxRead(VMCS_CTRL_VIRTUAL_PROCESSOR_IDENTIFIER));
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
CR0
|
||||
AdjustGuestCr0 (
|
||||
CR0 Cr0
|
||||
)
|
||||
{
|
||||
CR0 newCr0;
|
||||
IA32_VMX_PROCBASED_CTLS2_REGISTER secondaryProcBasedControls;
|
||||
|
||||
newCr0 = AdjustCr0(Cr0);
|
||||
|
||||
//
|
||||
// When the UnrestrictedGuest bit is set, ProtectionEnable and PagingEnable
|
||||
// bits are allowed to be zero. Make this adjustment, by setting them 1 only
|
||||
// when the guest did indeed requested them to be 1 (ie,
|
||||
// Cr0.ProtectionEnable == 1) and the FIXED0 MSR indicated them to be 1 (ie,
|
||||
// newCr0.ProtectionEnable == 1).
|
||||
//
|
||||
secondaryProcBasedControls.Flags = VmxRead(
|
||||
VMCS_CTRL_SECONDARY_PROCESSOR_BASED_VM_EXECUTION_CONTROLS);
|
||||
if (secondaryProcBasedControls.UnrestrictedGuest != FALSE)
|
||||
{
|
||||
newCr0.ProtectionEnable &= Cr0.ProtectionEnable;
|
||||
newCr0.PagingEnable &= Cr0.PagingEnable;
|
||||
}
|
||||
return newCr0;
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
CR4
|
||||
AdjustGuestCr4 (
|
||||
CR4 Cr4
|
||||
)
|
||||
{
|
||||
return AdjustCr4(Cr4);
|
||||
}
|
||||
217
Sources/HostUtils.h
Normal file
217
Sources/HostUtils.h
Normal file
@@ -0,0 +1,217 @@
|
||||
/*!
|
||||
@file HostUtils.h
|
||||
|
||||
@brief Utility functions and structures for the host.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#pragma once
|
||||
#include "Common.h"
|
||||
#include "Public.h"
|
||||
|
||||
//
|
||||
// 128bit XMM register (ie, equivalent to __m128 on MSVC).
|
||||
//
|
||||
typedef struct _XMM
|
||||
{
|
||||
UINT8 Value[16];
|
||||
} XMM;
|
||||
|
||||
//
|
||||
// Guest General Purpose Registers (GPRs) created on VM-exit from the guest
|
||||
// state and write back to the guest on VM-entry.
|
||||
//
|
||||
typedef struct _GUEST_REGISTERS
|
||||
{
|
||||
XMM Xmm[6];
|
||||
VOID* Alignment;
|
||||
UINT64 R15;
|
||||
UINT64 R14;
|
||||
UINT64 R13;
|
||||
UINT64 R12;
|
||||
UINT64 R11;
|
||||
UINT64 R10;
|
||||
UINT64 R9;
|
||||
UINT64 R8;
|
||||
UINT64 Rdi;
|
||||
UINT64 Rsi;
|
||||
UINT64 Rbp;
|
||||
UINT64 Rbx;
|
||||
UINT64 Rdx;
|
||||
UINT64 Rcx;
|
||||
UINT64 Rax;
|
||||
} GUEST_REGISTERS;
|
||||
|
||||
//
|
||||
// The guest registers that are stored in the VMCS as opposed to stack like
|
||||
// ones in the GUEST_REGISTERS structure.
|
||||
//
|
||||
typedef struct _VMCS_BASED_REGISTERS
|
||||
{
|
||||
UINT64 Rip;
|
||||
UINT64 Rsp;
|
||||
RFLAGS Rflags;
|
||||
} VMCS_BASED_REGISTERS;
|
||||
|
||||
//
|
||||
// State of the guest.
|
||||
//
|
||||
typedef struct _GUEST_CONTEXT
|
||||
{
|
||||
//
|
||||
// Indicates that the processor should continue virtualization. FALSE of
|
||||
// results in disablement of hypervisor with the VMXOFF instruction. See
|
||||
// x64.asm. This value is used as a return value of the HandleVmExit function.
|
||||
//
|
||||
BOOLEAN ContinueVm;
|
||||
|
||||
//
|
||||
// Collection of pointers passed from the kernel via the host stack.
|
||||
//
|
||||
HYPERVISOR_CONTEXT* Contexts;
|
||||
|
||||
//
|
||||
// The guest states stored in hypervisor stack.
|
||||
//
|
||||
GUEST_REGISTERS* StackBasedRegisters;
|
||||
|
||||
//
|
||||
// The guest states stored in the VMCS.
|
||||
//
|
||||
VMCS_BASED_REGISTERS VmcsBasedRegisters;
|
||||
} GUEST_CONTEXT;
|
||||
|
||||
/*!
|
||||
@brief Dumps host state VMCS fields.
|
||||
*/
|
||||
VOID
|
||||
DumpHostState (
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Dumps guest state VMCS fields.
|
||||
*/
|
||||
VOID
|
||||
DumpGuestState (
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Dumps control VMCS fields.
|
||||
*/
|
||||
VOID
|
||||
DumpControl (
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Writes the value to the VMCS.
|
||||
|
||||
@param[in] Field - A VMCS field to write the value to.
|
||||
|
||||
@param[in] FieldValue - A value to write.
|
||||
*/
|
||||
VOID
|
||||
VmxWrite (
|
||||
_In_ VMCS_FIELD Field,
|
||||
_In_ UINT64 FieldValue
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Read a value from the VMCS.
|
||||
|
||||
@param[in] Field - A VMCS field to read a value from.
|
||||
|
||||
@return A value read from the VMCS. MAXUINT64 is returned when a non-existent
|
||||
VMCS field is requested for read.
|
||||
*/
|
||||
UINT64
|
||||
VmxRead (
|
||||
_In_ VMCS_FIELD Field
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Advances the guest's RIP to the address of the next instruction. This
|
||||
implies that the hypervisor completed emulation of the instruction.
|
||||
|
||||
@param[in,out] GuestContext - A pointer to the guest context.
|
||||
*/
|
||||
VOID
|
||||
AdvanceGuestInstructionPointer (
|
||||
_Inout_ GUEST_CONTEXT* GuestContext
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Tests whether the guest was at the CPL 0 (kernel-mode) when VM-exit
|
||||
happened.
|
||||
|
||||
@return TRUE when the guest was at the CPL 0, otherwise FALSE.
|
||||
*/
|
||||
_Must_inspect_result_
|
||||
BOOLEAN
|
||||
IsGuestInKernelMode (
|
||||
VOID
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Queues interrupt to occur to the VMCS.
|
||||
|
||||
@details Generally, this interrupt fires on VM-entry and the guests runs a
|
||||
corresponding exception handler before executing the instruction pointed
|
||||
by Rip.
|
||||
|
||||
@param[in] InterruptionType - A type of interrupt to inject.
|
||||
|
||||
@param[in] Vector - A vector number of interrupt to inject.
|
||||
|
||||
@param[in] DeliverErrorCode - TRUE when the interrupt should have an error
|
||||
code. Whether the interrupt should have an error code is defined by the
|
||||
Intel SDM. See comments in the EXCEPTION_VECTOR definitions for a quick
|
||||
reference.
|
||||
|
||||
@param[in] ErrorCode - An error code. Not used when DeliverErrorCode is FALSE.
|
||||
*/
|
||||
VOID
|
||||
InjectInterruption (
|
||||
_In_ INTERRUPTION_TYPE InterruptionType,
|
||||
_In_ EXCEPTION_VECTOR Vector,
|
||||
_In_ BOOLEAN DeliverErrorCode,
|
||||
_In_ UINT32 ErrorCode
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Switches the guest paging mode between 32 and 64bit modes according
|
||||
with CR0 and EFER.
|
||||
|
||||
@param[in] NewGuestCr0 - The guest CR0 value to check the mode to switch to.
|
||||
*/
|
||||
VOID
|
||||
SwitchGuestPagingMode (
|
||||
_In_ CR0 NewGuestCr0
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Returns the CR0 value after the FIXED0 and FIXED1 MSR values are applied
|
||||
for the guest.
|
||||
|
||||
@param[in] Cr0 - The CR0 value to apply the FIXED0 and FIXED1 MSR values.
|
||||
|
||||
@return The CR0 value where the FIXED0 and FIXED1 MSR values are applied.
|
||||
*/
|
||||
CR0
|
||||
AdjustGuestCr0 (
|
||||
_In_ CR0 Cr0
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Returns the CR4 value after the FIXED0 and FIXED1 MSR values are applied
|
||||
for the guest.
|
||||
|
||||
@param[in] Cr4 - The CR4 value to apply the FIXED0 and FIXED1 MSR values.
|
||||
|
||||
@return The CR4 value where the FIXED0 and FIXED1 MSR values are applied.
|
||||
*/
|
||||
CR4
|
||||
AdjustGuestCr4 (
|
||||
_In_ CR4 Cr4
|
||||
);
|
||||
89
Sources/HostVmcall.c
Normal file
89
Sources/HostVmcall.c
Normal file
@@ -0,0 +1,89 @@
|
||||
/*!
|
||||
@file HostVmcall.c
|
||||
|
||||
@brief Implementation of hypercall functions.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#include "HostVmcall.h"
|
||||
|
||||
_Use_decl_annotations_
|
||||
VOID
|
||||
HandleVmcallUninstall (
|
||||
GUEST_CONTEXT* GuestContext
|
||||
)
|
||||
{
|
||||
GDTR gdtr;
|
||||
IDTR idtr;
|
||||
|
||||
//
|
||||
// This hypercall is not allowed for ring 3.
|
||||
//
|
||||
if (IsGuestInKernelMode() == FALSE)
|
||||
{
|
||||
GuestContext->StackBasedRegisters->Rax = (UINT64)MV_STATUS_ACCESS_DENIED;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
//
|
||||
// On VM-exit, the processor loads registers according with the Host state
|
||||
// fields in the VMCS. Some registers are changed, e.g, GPRs, and some
|
||||
// others are changed with hard-coded values. The limits of GDTR and IDTR
|
||||
// are such example, and updated to 0xFFFF. When the VMRESUME instruction
|
||||
// is executed, this is not an issue as VM-entry reloads the proper values
|
||||
// from the guest state fields of the VMCS. However, it is not the case when
|
||||
// the VMRESUME is not called, like here. In such a case those values must
|
||||
// be restored with normal value manually, or PatchGuard will report
|
||||
// integrity violation.
|
||||
//
|
||||
// "The GDTR and IDTR limits are each set to FFFFH."
|
||||
// See: 27.5.2 Loading Host Segment and Descriptor-Table Registers
|
||||
//
|
||||
gdtr.BaseAddress = VmxRead(VMCS_GUEST_GDTR_BASE);
|
||||
gdtr.Limit = (UINT16)VmxRead(VMCS_GUEST_GDTR_LIMIT);
|
||||
_sgdt(&gdtr);
|
||||
|
||||
idtr.BaseAddress = VmxRead(VMCS_GUEST_IDTR_BASE);
|
||||
idtr.Limit = (UINT16)VmxRead(VMCS_GUEST_IDTR_LIMIT);
|
||||
__lidt(&idtr);
|
||||
|
||||
//
|
||||
// The host may use a different CR3 than that of the guest. This is the case
|
||||
// on EFI. Apply the guest one. This assumes that translation both the host
|
||||
// CR3 and the guest CR3 has the same translation. Otherwise, the system will
|
||||
// crash immediately after updating CR3.
|
||||
//
|
||||
__writecr3(VmxRead(VMCS_GUEST_CR3));
|
||||
|
||||
//
|
||||
// Save some values needed for clean up in the volatile registers.
|
||||
// RAX = The address of the all-processors context. This is used as a
|
||||
// return value of the AsmVmxCall function.
|
||||
// RCX = The address to continue execution after the execution of the VMXOFF
|
||||
// instruction. This value is needed because we have to manually
|
||||
// transfer execution instead of doing so automatically with the
|
||||
// VMRESUME instruction in this pass.
|
||||
// RDX = The RSP value to be restored. Same as the case of RIP, the RSP is
|
||||
// not automatically restored in this pass, and so, has to be updated
|
||||
// by the original value (not host's RSP).
|
||||
// Param2 = The RFLAGS value to be restored. Also same as the case of RIP and
|
||||
// RSP. Recall that RFLAGS is also updated automatically on VM-exit.
|
||||
// "RFLAGS is cleared, except bit 1, which is always set."
|
||||
// See: 27.5.3 Loading Host RIP, RSP, and RFLAGS
|
||||
//
|
||||
GuestContext->StackBasedRegisters->Rax = (UINT64)GuestContext->Contexts->VpContexts;
|
||||
GuestContext->StackBasedRegisters->Rcx = GuestContext->VmcsBasedRegisters.Rip +
|
||||
VmxRead(VMCS_VMEXIT_INSTRUCTION_LENGTH);
|
||||
GuestContext->StackBasedRegisters->Rdx = GuestContext->VmcsBasedRegisters.Rsp;
|
||||
GuestContext->StackBasedRegisters->R8 = GuestContext->VmcsBasedRegisters.Rflags.Flags;
|
||||
|
||||
//
|
||||
// Finally, indicates that virtualization should be terminated.
|
||||
//
|
||||
GuestContext->ContinueVm = FALSE;
|
||||
|
||||
Exit:
|
||||
return;
|
||||
}
|
||||
38
Sources/HostVmcall.h
Normal file
38
Sources/HostVmcall.h
Normal file
@@ -0,0 +1,38 @@
|
||||
/*!
|
||||
@file HostVmcall.h
|
||||
|
||||
@brief Implementation of hypercall functions.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#pragma once
|
||||
#include "Common.h"
|
||||
#include "HostUtils.h"
|
||||
#include "Public.h"
|
||||
|
||||
//
|
||||
// The VMCALL handler type.
|
||||
//
|
||||
typedef
|
||||
VOID
|
||||
VMCALL_HANDLER (
|
||||
_Inout_ GUEST_CONTEXT* GuestContext
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Handles hypercall for uninstalling the hypervisor.
|
||||
|
||||
@param[in,out] GuestContext - A pointer to the guest context.
|
||||
*/
|
||||
VMCALL_HANDLER HandleVmcallUninstall;
|
||||
|
||||
//
|
||||
// VMCALL handlers and mapping.
|
||||
//
|
||||
static VMCALL_HANDLER* k_VmcallHandlers[] =
|
||||
{
|
||||
HandleVmcallUninstall,
|
||||
};
|
||||
C_ASSERT(RTL_NUMBER_OF(k_VmcallHandlers) == VmcallInvalid);
|
||||
139
Sources/Ia32.h
Normal file
139
Sources/Ia32.h
Normal file
@@ -0,0 +1,139 @@
|
||||
/*!
|
||||
@file Ia32.h
|
||||
|
||||
@brief Intel SDM defined constants and structures.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
//
|
||||
// "nonstandard extension used: bit field types other than int"
|
||||
//
|
||||
#pragma warning(disable: 4214)
|
||||
|
||||
//
|
||||
// "nonstandard extension used: nameless struct/union"
|
||||
//
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4201)
|
||||
#include "ia32-doc/out/ia32.h"
|
||||
#pragma warning(pop)
|
||||
|
||||
//
|
||||
// The entry count within an EPT page table.
|
||||
//
|
||||
#define EPT_PTE_ENTRY_COUNT 512
|
||||
|
||||
//
|
||||
// The entry counts within paging structures.
|
||||
//
|
||||
#define PML4_ENTRY_COUNT 512
|
||||
#define PDPT_ENTRY_COUNT 512
|
||||
#define PDT_ENTRY_COUNT 512
|
||||
#define PT_ENTRY_COUNT 512
|
||||
|
||||
//
|
||||
// The entry count within the IDT.
|
||||
//
|
||||
#define IDT_ENTRY_COUNT 256
|
||||
|
||||
//
|
||||
// The levels of paging structures.
|
||||
//
|
||||
#define PT_LEVEL_PML4E 4
|
||||
#define PT_LEVEL_PDPTE 3
|
||||
#define PT_LEVEL_PDE 2
|
||||
#define PT_LEVEL_PTE 1
|
||||
|
||||
//
|
||||
// Bits useful for working with paging structures and EPTs.
|
||||
//
|
||||
#ifndef PAGE_SHIFT
|
||||
#define PAGE_SHIFT 12
|
||||
#endif
|
||||
#define PAGE_SHIFT_2BM 21
|
||||
#define PAGE_SHIFT_1GB 30
|
||||
#define PAGE_MASK (PAGE_SIZE - 1)
|
||||
|
||||
//
|
||||
// See: 11.11.2.2 Fixed Range MTRRs
|
||||
//
|
||||
typedef union _IA32_MTRR_FIXED_RANGE_MSR
|
||||
{
|
||||
struct
|
||||
{
|
||||
UINT8 Types[8];
|
||||
} u;
|
||||
UINT64 Flags;
|
||||
} IA32_MTRR_FIXED_RANGE_MSR;
|
||||
|
||||
//
|
||||
// See: Table 11-10. Memory Ranges That Can Be Encoded With PAT
|
||||
//
|
||||
typedef UINT32 IA32_MEMORY_TYPE;
|
||||
|
||||
typedef UINT64 VMCS_FIELD;
|
||||
|
||||
typedef SEGMENT_DESCRIPTOR_REGISTER_64 GDTR, IDTR;
|
||||
|
||||
typedef UINT32 IA32_MSR_ADDRESS;
|
||||
|
||||
//
|
||||
// See: Table 30-1. VM-Instruction Error Numbers
|
||||
//
|
||||
typedef UINT32 VMX_ERROR_NUMBER;
|
||||
|
||||
//
|
||||
// The helper structure for translating the guest physical address to the
|
||||
// host physical address.
|
||||
//
|
||||
typedef union _ADDRESS_TRANSLATION_HELPER
|
||||
{
|
||||
//
|
||||
// Indexes to locate paging-structure entries corresponds to this virtual
|
||||
// address.
|
||||
//
|
||||
struct
|
||||
{
|
||||
UINT64 Unused : 12; //< [11:0]
|
||||
UINT64 Pt : 9; //< [20:12]
|
||||
UINT64 Pd : 9; //< [29:21]
|
||||
UINT64 Pdpt : 9; //< [38:30]
|
||||
UINT64 Pml4 : 9; //< [47:39]
|
||||
} AsIndex;
|
||||
|
||||
//
|
||||
// The page offset for each type of pages. For example, for 4KB pages, bits
|
||||
// [11:0] are treated as the page offset and Mapping4Kb can be used for it.
|
||||
//
|
||||
union
|
||||
{
|
||||
UINT64 Mapping4Kb : 12; //< [11:0]
|
||||
UINT64 Mapping2Mb : 21; //< [20:0]
|
||||
UINT64 Mapping1Gb : 30; //< [29:0]
|
||||
} AsPageOffset;
|
||||
|
||||
UINT64 AsUInt64;
|
||||
} ADDRESS_TRANSLATION_HELPER;
|
||||
|
||||
//
|
||||
// See: Figure 7-11. 64-Bit TSS Format
|
||||
//
|
||||
#pragma pack(push, 1)
|
||||
typedef struct _TASK_STATE_SEGMENT_64
|
||||
{
|
||||
UINT32 Reserved0;
|
||||
UINT64 Rsp0;
|
||||
UINT64 Rsp1;
|
||||
UINT64 Rsp2;
|
||||
UINT64 Reserved1;
|
||||
UINT64 Ist[7];
|
||||
UINT64 Reserved3;
|
||||
UINT16 Reserved4;
|
||||
UINT16 IoMapBaseAddress;
|
||||
} TASK_STATE_SEGMENT_64;
|
||||
C_ASSERT(sizeof(TASK_STATE_SEGMENT_64) == 104);
|
||||
#pragma pack(pop)
|
||||
109
Sources/Logger.h
Normal file
109
Sources/Logger.h
Normal file
@@ -0,0 +1,109 @@
|
||||
/*!
|
||||
@file Logger.h
|
||||
|
||||
@brief Declarations of functions and structures for logging.
|
||||
|
||||
@details Strings provided for the LOG_* macros are NOT removed from the
|
||||
release build. If you wish so, wrap them with preprocessor and make them
|
||||
no-op.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#pragma once
|
||||
#include "Common.h"
|
||||
|
||||
//
|
||||
// Logging chars and wide-chars require different format strings because of the
|
||||
// difference of the formatting functions. Use them like the standard's PRIx macro
|
||||
// family.
|
||||
//
|
||||
#if defined(NTDDI_VERSION)
|
||||
#define LOG_PRIANSI "s"
|
||||
#define LOG_PRIUNICODE "S"
|
||||
#else
|
||||
#define LOG_PRIANSI "a"
|
||||
#define LOG_PRIUNICODE "s"
|
||||
#endif
|
||||
|
||||
//
|
||||
// Log levels.
|
||||
//
|
||||
typedef enum _LOG_LEVEL
|
||||
{
|
||||
LogLevelNone,
|
||||
LogLevelError,
|
||||
LogLevelWarning,
|
||||
LogLevelInfo,
|
||||
LogLevelDebug,
|
||||
LogLevelReserved,
|
||||
} LOG_LEVEL;
|
||||
|
||||
/*!
|
||||
@brief Logs the error message without depending on the logger to be initialized.
|
||||
|
||||
@param[in] Format - The format string.
|
||||
*/
|
||||
#define LOG_EARLY_ERROR(Format, ...) \
|
||||
LogEarlyErrorMessage(Format ## "\n", __VA_ARGS__)
|
||||
|
||||
/*!
|
||||
@brief Logs the error-level message.
|
||||
|
||||
@param[in] Format - The format string.
|
||||
*/
|
||||
#define LOG_ERROR(Format, ...) \
|
||||
LogMessage(LogLevelError, __FUNCTION__, (Format), __VA_ARGS__)
|
||||
|
||||
/*!
|
||||
@brief Logs the warning-level message.
|
||||
|
||||
@param[in] Format - The format string.
|
||||
*/
|
||||
#define LOG_WARNING(Format, ...) \
|
||||
LogMessage(LogLevelWarning, __FUNCTION__, (Format), __VA_ARGS__)
|
||||
|
||||
/*!
|
||||
@brief Logs the information-level message.
|
||||
|
||||
@param[in] Format - The format string.
|
||||
*/
|
||||
#define LOG_INFO(Format, ...) \
|
||||
LogMessage(LogLevelInfo, __FUNCTION__, (Format), __VA_ARGS__)
|
||||
|
||||
/*!
|
||||
@brief Logs the debug-level message.
|
||||
|
||||
@param[in] Format - The format string.
|
||||
*/
|
||||
#define LOG_DEBUG(Format, ...) \
|
||||
LogMessage(LogLevelDebug, __FUNCTION__, (Format), __VA_ARGS__)
|
||||
|
||||
/*!
|
||||
@brief Logs the log message.
|
||||
|
||||
@param[in] Level - The level of the message.
|
||||
|
||||
@param[in] FunctionName - The name of the function initiated this logging.
|
||||
|
||||
@param[in] Format - The format string.
|
||||
*/
|
||||
VOID
|
||||
LogMessage (
|
||||
_In_ LOG_LEVEL Level,
|
||||
_In_ CONST CHAR* FunctionName,
|
||||
_In_ _Printf_format_string_ CONST CHAR* Format,
|
||||
...
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Logs the error log message immediately.
|
||||
|
||||
@param[in] Format - The format string.
|
||||
*/
|
||||
VOID
|
||||
LogEarlyErrorMessage (
|
||||
_In_ _Printf_format_string_ CONST CHAR* Format,
|
||||
...
|
||||
);
|
||||
712
Sources/MemoryAccess.c
Normal file
712
Sources/MemoryAccess.c
Normal file
@@ -0,0 +1,712 @@
|
||||
/*!
|
||||
@file MemoryAccess.c
|
||||
|
||||
@brief Functions for guest virtual memory access from the hypervisor.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#include "MemoryAccess.h"
|
||||
#include "HostUtils.h"
|
||||
#include "Platform.h"
|
||||
#include "MemoryManager.h"
|
||||
|
||||
/*!
|
||||
@brief Split a 2MB EPT PDE to 512 EPT PTEs.
|
||||
|
||||
@param[in,out] PdeLarge - The pointer to the 2MB EPT PDE to split.
|
||||
|
||||
@return MV_STATUS_SUCCESS on success; otherwise, an appropriate error code.
|
||||
*/
|
||||
static
|
||||
_Must_inspect_result_
|
||||
MV_STATUS
|
||||
Split2MbPage (
|
||||
_Inout_ PDE_2MB_64* PdeLarge
|
||||
)
|
||||
{
|
||||
MV_STATUS status;
|
||||
PDE_64* pde;
|
||||
PTE_64* pt;
|
||||
UINT64 paBase;
|
||||
UINT64 paToMap;
|
||||
|
||||
MV_ASSERT(PdeLarge->LargePage != FALSE);
|
||||
|
||||
//
|
||||
// Allocate the PT as we are going to split one 2MB page to 512 4KB pages.
|
||||
//
|
||||
pt = MmAllocatePages(1);
|
||||
if (pt == NULL)
|
||||
{
|
||||
status = MV_STATUS_INSUFFICIENT_RESOURCES;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
//
|
||||
// Clear the large page bit, and propagate the current permissions to the
|
||||
// all entries in the PT.
|
||||
//
|
||||
PdeLarge->LargePage = FALSE;
|
||||
__stosq((UINT64*)pt, PdeLarge->Flags, PT_ENTRY_COUNT);
|
||||
|
||||
//
|
||||
// Update the page frame of each PTE.
|
||||
//
|
||||
paBase = (PdeLarge->PageFrameNumber << PAGE_SHIFT_2BM);
|
||||
for (UINT32 ptIndex = 0; ptIndex < PT_ENTRY_COUNT; ++ptIndex)
|
||||
{
|
||||
paToMap = paBase + ((UINT64)ptIndex * PAGE_SIZE);
|
||||
pt[ptIndex].PageFrameNumber = (paToMap >> PAGE_SHIFT);
|
||||
}
|
||||
|
||||
//
|
||||
// Finally, update the PDE by pointing to the PT.
|
||||
//
|
||||
pde = (PDE_64*)PdeLarge;
|
||||
pde->Reserved1 = pde->Reserved2 = 0;
|
||||
pde->PageFrameNumber = (GetPhysicalAddress(pt) >> PAGE_SHIFT);
|
||||
|
||||
status = MV_STATUS_SUCCESS;
|
||||
|
||||
Exit:
|
||||
return status;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Returns the pointer to the final paging structure entry used to
|
||||
translate the given virtual address in the current CR3.
|
||||
|
||||
@param[in] VirtualAddress - The virtual address to retrieve its PTE.
|
||||
|
||||
@param[in] HostCr3 - The host CR3.
|
||||
|
||||
@param[out] PageMapLevel - The pointer to the integer to receive the level of
|
||||
paging structures of the returned entry.
|
||||
|
||||
@return The pointer to the final paging structure when the virtual address
|
||||
is not mapped in the physical address. If not, returns the pointer to the
|
||||
paging structure entry that indicated that the page is not present (ie,
|
||||
the Present bit is cleared).
|
||||
*/
|
||||
static
|
||||
_Must_inspect_result_
|
||||
PT_ENTRY_64*
|
||||
GetPteForVa (
|
||||
_In_ VOID* VirtualAddress,
|
||||
_In_ CR3 HostCr3,
|
||||
_Out_opt_ UINT32* PageMapLevel
|
||||
)
|
||||
{
|
||||
ADDRESS_TRANSLATION_HELPER helper;
|
||||
UINT32 level;
|
||||
PT_ENTRY_64* finalEntry;
|
||||
PML4E_64* pml4;
|
||||
PML4E_64* pml4e;
|
||||
PDPTE_64* pdpt;
|
||||
PDPTE_64* pdpte;
|
||||
PDE_64* pd;
|
||||
PDE_64* pde;
|
||||
PTE_64* pt;
|
||||
PTE_64* pte;
|
||||
|
||||
helper.AsUInt64 = (UINT64)VirtualAddress;
|
||||
|
||||
//
|
||||
// Locate PML4E from CR3.
|
||||
//
|
||||
pml4 = (PML4E_64*)GetVirtualAddress(HostCr3.AddressOfPageDirectory << PAGE_SHIFT);
|
||||
pml4e = &pml4[helper.AsIndex.Pml4];
|
||||
if (pml4e->Present == FALSE)
|
||||
{
|
||||
finalEntry = (PT_ENTRY_64*)pml4e;
|
||||
level = PT_LEVEL_PML4E;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
//
|
||||
// Locate PDPTE from PML4E. If the located entry indicates this is the 1GB
|
||||
// page, return the entry.
|
||||
//
|
||||
pdpt = (PDPTE_64*)GetVirtualAddress(pml4e->PageFrameNumber << PAGE_SHIFT);
|
||||
pdpte = &pdpt[helper.AsIndex.Pdpt];
|
||||
if ((pdpte->Present == FALSE) || (pdpte->LargePage != FALSE))
|
||||
{
|
||||
finalEntry = (PT_ENTRY_64*)pdpte;
|
||||
level = PT_LEVEL_PDPTE;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
//
|
||||
// Locate PDE from PDPTE. If the located entry indicates this is the 2MB
|
||||
// page, return the entry.
|
||||
//
|
||||
pd = (PDE_64*)GetVirtualAddress(pdpte->PageFrameNumber << PAGE_SHIFT);
|
||||
pde = &pd[helper.AsIndex.Pd];
|
||||
if ((pde->Present == FALSE) || (pde->LargePage != FALSE))
|
||||
{
|
||||
finalEntry = (PT_ENTRY_64*)pde;
|
||||
level = PT_LEVEL_PDE;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
//
|
||||
// Locate PTE from PDE and return it.
|
||||
//
|
||||
pt = (PTE_64*)GetVirtualAddress(pde->PageFrameNumber << PAGE_SHIFT);
|
||||
pte = &pt[helper.AsIndex.Pt];
|
||||
finalEntry = (PT_ENTRY_64*)pte;
|
||||
level = PT_LEVEL_PTE;
|
||||
|
||||
Exit:
|
||||
if (ARGUMENT_PRESENT(PageMapLevel))
|
||||
{
|
||||
*PageMapLevel = level;
|
||||
}
|
||||
return finalEntry;
|
||||
}
|
||||
|
||||
MV_SECTION_PAGED
|
||||
_Use_decl_annotations_
|
||||
MV_STATUS
|
||||
InitializeMemoryAccess (
|
||||
MEMORY_ACCESS_CONTEXT* Context,
|
||||
CR3 HostCr3
|
||||
)
|
||||
{
|
||||
MV_STATUS status;
|
||||
UINT32 level;
|
||||
VOID* reservedPage;
|
||||
PT_ENTRY_64* reservedPagePte;
|
||||
PTE_64* allocatedPageTable;
|
||||
|
||||
PAGED_CODE();
|
||||
|
||||
allocatedPageTable = NULL;
|
||||
|
||||
//
|
||||
// Reserve a single page that will map the guest's memory to access it from
|
||||
// the hypervisor. At this point, this page is not mapped to anywhere and not
|
||||
// accessible. MapPa() will do this job.
|
||||
//
|
||||
reservedPage = ReserveVirtualAddress(1);
|
||||
if (reservedPage == NULL)
|
||||
{
|
||||
status = MV_STATUS_INSUFFICIENT_RESOURCES;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
//
|
||||
// Get the address of the paging structures entry that has the translation
|
||||
// for the virtual address. If the resulted entry is a PDE, it means the
|
||||
// virtual address is within a large (2MB) page and cannot safely modify its
|
||||
// contents. Split the PDE into PTEs in this case. This is the case on EFI
|
||||
// because we built our own identity mapping using large pages.
|
||||
//
|
||||
reservedPagePte = GetPteForVa(reservedPage, HostCr3, &level);
|
||||
if (level == PT_LEVEL_PDE)
|
||||
{
|
||||
MV_ASSERT(reservedPagePte->LargePage != FALSE);
|
||||
status = Split2MbPage((PDE_2MB_64*)reservedPagePte);
|
||||
if (MV_ERROR(status))
|
||||
{
|
||||
goto Exit;
|
||||
}
|
||||
reservedPagePte = GetPteForVa(reservedPage, HostCr3, &level);
|
||||
allocatedPageTable = PAGE_ALIGN(reservedPagePte);
|
||||
MV_ASSERT(level == PT_LEVEL_PTE);
|
||||
MV_ASSERT(reservedPagePte->LargePage == FALSE);
|
||||
}
|
||||
|
||||
//
|
||||
// Drop the translation of the virtual address. This is not required and done
|
||||
// to track map/unmap state of the reserved page. The entry may already not
|
||||
// have translation. This is the case on Windows because of underneath API.
|
||||
//
|
||||
reservedPagePte->Flags = 0;
|
||||
|
||||
//
|
||||
// We are good. Fill out the context structure.
|
||||
//
|
||||
status = MV_STATUS_SUCCESS;
|
||||
Context->ReservedPage = reservedPage;
|
||||
Context->Pte = (PTE_64*)reservedPagePte;
|
||||
Context->AllocatedPageTable = allocatedPageTable;
|
||||
|
||||
Exit:
|
||||
if (MV_ERROR(status))
|
||||
{
|
||||
if (reservedPage != NULL)
|
||||
{
|
||||
FreeReservedVirtualAddress(reservedPage, 1);
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
MV_SECTION_PAGED
|
||||
_Use_decl_annotations_
|
||||
VOID
|
||||
CleanupMemoryAccess (
|
||||
MEMORY_ACCESS_CONTEXT* Context
|
||||
)
|
||||
{
|
||||
PAGED_CODE();
|
||||
|
||||
//
|
||||
// Mapping should not be active; otherwise, FreeReservedVirtualAddress()
|
||||
// will bug checks.
|
||||
//
|
||||
MV_ASSERT(Context->Pte->Present == FALSE);
|
||||
|
||||
if (Context->AllocatedPageTable != NULL)
|
||||
{
|
||||
MmFreePages(Context->AllocatedPageTable);
|
||||
}
|
||||
|
||||
FreeReservedVirtualAddress(Context->ReservedPage, 1);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Maps the given physical address to the reserved page.
|
||||
|
||||
@details This function modifies the PTE of the reserved page to map the given
|
||||
physical address to the virtual address. This function maps the virtual
|
||||
address as writable regardless of the permission of the virtual address
|
||||
used by the guest.
|
||||
|
||||
@param[in,out] Context - The pointer to the memory access context.
|
||||
|
||||
@param[in] PhysicalAddress - The physical address to map to the reserved
|
||||
virtual address.
|
||||
|
||||
@return The virtual address that maps the specified physical address. The
|
||||
caller must unmap this using UnmapPa() when mapping is no longer needed.
|
||||
*/
|
||||
static
|
||||
_Must_inspect_result_
|
||||
VOID*
|
||||
MapPa (
|
||||
_Inout_ MEMORY_ACCESS_CONTEXT* Context,
|
||||
_In_ UINT64 PhysicalAddress
|
||||
)
|
||||
{
|
||||
//
|
||||
// Make sure the caller called UnmapPa(). This is purely for easier state
|
||||
// tracking.
|
||||
//
|
||||
MV_ASSERT(Context->Pte->Flags == 0);
|
||||
|
||||
//
|
||||
// Make the page present and writable, change the page frame, then flush TLB.
|
||||
//
|
||||
Context->Pte->Present = TRUE;
|
||||
Context->Pte->Write = TRUE;
|
||||
Context->Pte->PageFrameNumber = (PhysicalAddress >> PAGE_SHIFT);
|
||||
__invlpg(Context->ReservedPage);
|
||||
|
||||
//
|
||||
// Return the pointer within the reserved page with the page offset.
|
||||
//
|
||||
return MV_ADD2PTR(Context->ReservedPage, (PhysicalAddress & PAGE_MASK));
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Unmaps the physical address that is currently mapped to the reserved
|
||||
page.
|
||||
|
||||
@param[in,out] Context - The pointer to the memory access context.
|
||||
*/
|
||||
static
|
||||
VOID
|
||||
UnmapPa (
|
||||
_Inout_ MEMORY_ACCESS_CONTEXT* Context
|
||||
)
|
||||
{
|
||||
MV_ASSERT(Context->Pte->Flags != 0);
|
||||
|
||||
//
|
||||
// Invalidates the reserved page.
|
||||
//
|
||||
Context->Pte->Flags = 0;
|
||||
__invlpg(Context->ReservedPage);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Reads or writes memory from or to the location specified as the
|
||||
physical address.
|
||||
|
||||
@param[in] Context - The pointer to the memory access context.
|
||||
|
||||
@param[in] OperationType - Indicates whether this is read or write operation.
|
||||
|
||||
@param[in] PhysicalAddress - The physical address to read or write memory.
|
||||
|
||||
@param[in,out] Buffer - The pointer to buffer to store the read memory,
|
||||
or the pointer to buffer containing data to write.
|
||||
|
||||
@param[in] BytesToCopy - The size to read or write in bytes.
|
||||
*/
|
||||
static
|
||||
VOID
|
||||
ReadOrWriteOnPhysicalAddress (
|
||||
_In_ MEMORY_ACCESS_CONTEXT* Context,
|
||||
_In_ OPERATION_TYPE OperationType,
|
||||
_In_ UINT64 PhysicalAddress,
|
||||
_When_(OperationType == OperationRead, _Out_writes_bytes_(BytesToCopy))
|
||||
_When_(OperationType == OperationWrite, _In_reads_bytes_(BytesToCopy)) VOID* Buffer,
|
||||
_In_ UINT64 BytesToCopy
|
||||
)
|
||||
{
|
||||
VOID* mappedVa;
|
||||
|
||||
//
|
||||
// BytesToCopy should be more than one and within the range or the page.
|
||||
//
|
||||
MV_ASSERT(BytesToCopy != 0);
|
||||
MV_ASSERT(BytesToCopy <= (PAGE_SIZE - (PhysicalAddress & PAGE_MASK)));
|
||||
|
||||
//
|
||||
// Map the physical address to this address space and copy to or from it.
|
||||
//
|
||||
mappedVa = MapPa(Context, PhysicalAddress);
|
||||
if (OperationType == OperationRead)
|
||||
{
|
||||
RtlCopyMemory(Buffer, mappedVa, BytesToCopy);
|
||||
}
|
||||
else
|
||||
{
|
||||
RtlCopyMemory(mappedVa, Buffer, BytesToCopy);
|
||||
}
|
||||
UnmapPa(Context);
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
UINT64
|
||||
GetPhysicalAddressForGuest (
|
||||
MEMORY_ACCESS_CONTEXT* Context,
|
||||
UINT64 GuestVirtualAddress,
|
||||
PT_ENTRY_64* AggregatedPagePermissions
|
||||
)
|
||||
{
|
||||
UINT64 pa;
|
||||
ADDRESS_TRANSLATION_HELPER helper;
|
||||
PT_ENTRY_64 permission;
|
||||
CR3 guestCr3;
|
||||
UINT64 tableBasePa;
|
||||
UINT64 tableEntryPa;
|
||||
PML4E_64 pml4e;
|
||||
PDPTE_64 pdpte;
|
||||
PDE_64 pde;
|
||||
PTE_64 pte;
|
||||
|
||||
//
|
||||
// Return MV_INVALID_PHYSICAL_ADDRESS if the virtual address is not mapped into
|
||||
// the guest address space (ie, there is no associated physical memory).
|
||||
//
|
||||
pa = MV_INVALID_PHYSICAL_ADDRESS;
|
||||
helper.AsUInt64 = GuestVirtualAddress;
|
||||
permission.Flags = 0;
|
||||
guestCr3.Flags = VmxRead(VMCS_GUEST_CR3);
|
||||
|
||||
//
|
||||
// Read the guest PML4E from the guest CR3. If the page is present, save the
|
||||
// permission bits.
|
||||
//
|
||||
tableBasePa = (guestCr3.AddressOfPageDirectory << PAGE_SHIFT);
|
||||
tableEntryPa = tableBasePa + (helper.AsIndex.Pml4 * sizeof(PML4E_64));
|
||||
ReadOrWriteOnPhysicalAddress(Context, OperationRead, tableEntryPa, &pml4e, sizeof(pml4e));
|
||||
if (pml4e.Present == FALSE)
|
||||
{
|
||||
goto Exit;
|
||||
}
|
||||
permission.Write = pml4e.Write;
|
||||
permission.Supervisor = pml4e.Supervisor;
|
||||
permission.ExecuteDisable = pml4e.ExecuteDisable;
|
||||
|
||||
//
|
||||
// Read the guest PDPTE from the guest PML4E. If the page is present,
|
||||
// aggregate the permission bits.
|
||||
//
|
||||
tableBasePa = (pml4e.PageFrameNumber << PAGE_SHIFT);
|
||||
tableEntryPa = tableBasePa + (helper.AsIndex.Pdpt * sizeof(PDPTE_64));
|
||||
ReadOrWriteOnPhysicalAddress(Context, OperationRead, tableEntryPa, &pdpte, sizeof(pdpte));
|
||||
if (pdpte.Present == FALSE)
|
||||
{
|
||||
goto Exit;
|
||||
}
|
||||
permission.Write &= pdpte.Write;
|
||||
permission.Supervisor &= pdpte.Supervisor;
|
||||
permission.ExecuteDisable |= pdpte.ExecuteDisable;
|
||||
|
||||
//
|
||||
// In case of the 1GB page, compute the physical address and exit.
|
||||
//
|
||||
if (pdpte.LargePage != FALSE)
|
||||
{
|
||||
PDPTE_1GB_64 pdpte1Gb;
|
||||
|
||||
pdpte1Gb.Flags = pdpte.Flags;
|
||||
pa = (pdpte1Gb.PageFrameNumber << PAGE_SHIFT_1GB) | helper.AsPageOffset.Mapping1Gb;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
//
|
||||
// Same. Read the guest PDE from the guest PDPTE. If the page is present,
|
||||
// aggregate the permission bits.
|
||||
//
|
||||
tableBasePa = (pdpte.PageFrameNumber << PAGE_SHIFT);
|
||||
tableEntryPa = tableBasePa + (helper.AsIndex.Pd * sizeof(PDE_64));
|
||||
ReadOrWriteOnPhysicalAddress(Context, OperationRead, tableEntryPa, &pde, sizeof(pde));
|
||||
if (pde.Present == FALSE)
|
||||
{
|
||||
goto Exit;
|
||||
}
|
||||
permission.Write &= pde.Write;
|
||||
permission.Supervisor &= pde.Supervisor;
|
||||
permission.ExecuteDisable |= pde.ExecuteDisable;
|
||||
|
||||
//
|
||||
// Same. If the page is the 2MB page, exit here.
|
||||
//
|
||||
if (pde.LargePage != FALSE)
|
||||
{
|
||||
PDE_2MB_64 pde2Mb;
|
||||
|
||||
pde2Mb.Flags = pde.Flags;
|
||||
pa = (pde2Mb.PageFrameNumber << PAGE_SHIFT_2BM) | helper.AsPageOffset.Mapping2Mb;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
//
|
||||
// Same. Read the guest PTE from the guest PDE. If the page is present,
|
||||
// aggregate the permission bits. Finally, compute the physical address.
|
||||
//
|
||||
tableBasePa = (pde.PageFrameNumber << PAGE_SHIFT);
|
||||
tableEntryPa = tableBasePa + (helper.AsIndex.Pt * sizeof(PTE_64));
|
||||
ReadOrWriteOnPhysicalAddress(Context, OperationRead, tableEntryPa, &pte, sizeof(pte));
|
||||
if (pte.Present == FALSE)
|
||||
{
|
||||
goto Exit;
|
||||
}
|
||||
permission.Write &= pte.Write;
|
||||
permission.Supervisor &= pte.Supervisor;
|
||||
permission.ExecuteDisable |= pte.ExecuteDisable;
|
||||
|
||||
pa = (pte.PageFrameNumber << PAGE_SHIFT) | helper.AsPageOffset.Mapping4Kb;
|
||||
|
||||
Exit:
|
||||
//
|
||||
// Return the collected permission bits on success.
|
||||
//
|
||||
if ((pa != MV_INVALID_PHYSICAL_ADDRESS) &&
|
||||
ARGUMENT_PRESENT(AggregatedPagePermissions))
|
||||
{
|
||||
*AggregatedPagePermissions = permission;
|
||||
}
|
||||
return pa;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Reads or writes memory from or the location specified as the guest
|
||||
virtual address.
|
||||
|
||||
@param[in] Context - The pointer to the memory access context.
|
||||
|
||||
@param[in] OperationType - Whether this is a read or write operation.
|
||||
|
||||
@param[in] KernelMode - Whether this is kernel-mode access.
|
||||
|
||||
@param[in] GuestVirtualAddress - The guest virtual address to work on.
|
||||
|
||||
@param[in,out] Buffer - The pointer to buffer to store the read memory,
|
||||
or the pointer to buffer containing data to write.
|
||||
|
||||
@param[in] BytesToCopy - The size to read or write in bytes.
|
||||
|
||||
@param[out] ErrorInformation - The pointer to the structure to receive error
|
||||
information on failure. On success, this structure is cleared to zero.
|
||||
|
||||
@return TRUE when the requested operation is completed. Otherwise, for example,
|
||||
when it encountered page permission violation in the middle, FALSE.
|
||||
*/
|
||||
static
|
||||
_Success_(return != FALSE)
|
||||
_Must_inspect_result_
|
||||
BOOLEAN
|
||||
ReadOrWriteGuestVirtualAddress (
|
||||
_In_ MEMORY_ACCESS_CONTEXT* Context,
|
||||
_In_ OPERATION_TYPE OperationType,
|
||||
_In_ BOOLEAN KernelMode,
|
||||
_In_ UINT64 GuestVirtualAddress,
|
||||
_When_(OperationType == OperationRead, _Out_writes_bytes_(BytesToCopy))
|
||||
_When_(OperationType == OperationWrite, _In_reads_bytes_(BytesToCopy)) VOID* Buffer,
|
||||
_In_ UINT64 BytesToCopy,
|
||||
_Out_ MEMORY_ACCESS_ERROR_INFORMATION* ErrorInformation
|
||||
)
|
||||
{
|
||||
BOOLEAN successful;
|
||||
UINT64 physicalAddress;
|
||||
VOID* failedVa;
|
||||
UINT64 guestVaToOperate;
|
||||
UINT8* currentBuffer;
|
||||
UINT64 remainingBytesToCopy;
|
||||
|
||||
//
|
||||
// Likely a programming error. Catch it.
|
||||
//
|
||||
MV_ASSERT(BytesToCopy != 0);
|
||||
|
||||
RtlZeroMemory(ErrorInformation, sizeof(*ErrorInformation));
|
||||
|
||||
successful = FALSE;
|
||||
|
||||
//
|
||||
// Start iterating memory access until all request bytes are processed.
|
||||
// Each iteration is at most 4KB-length.
|
||||
//
|
||||
// Note that this is broken in that it does not guarantee atomicity of memory
|
||||
// access. Consider the case where a single memory access is performed on the
|
||||
// page boundary, and only the 2nd page is paged out. This logic will access
|
||||
// to the first page, then injects #PF to complete access to the 2nd page.
|
||||
// As this lets the guest to execute the #PF handler and there is a relatively
|
||||
// larger window that allows other core to modify the 2nd page meanwhile.
|
||||
//
|
||||
failedVa = NULL;
|
||||
currentBuffer = Buffer;
|
||||
guestVaToOperate = GuestVirtualAddress;
|
||||
remainingBytesToCopy = BytesToCopy;
|
||||
while (remainingBytesToCopy > 0)
|
||||
{
|
||||
UINT64 bytesToOperate;
|
||||
UINT64 accessibleBytes;
|
||||
PT_ENTRY_64 permissions;
|
||||
|
||||
//
|
||||
// Round down the operation length to the page-boundary.
|
||||
//
|
||||
accessibleBytes = PAGE_SIZE - (guestVaToOperate & PAGE_MASK);
|
||||
bytesToOperate = MV_MIN(accessibleBytes, remainingBytesToCopy);
|
||||
|
||||
//
|
||||
// Try to get the physical address.
|
||||
//
|
||||
physicalAddress = GetPhysicalAddressForGuest(Context,
|
||||
guestVaToOperate,
|
||||
&permissions);
|
||||
if ((physicalAddress == MV_INVALID_PHYSICAL_ADDRESS) ||
|
||||
((permissions.Write == FALSE) && (OperationType == OperationWrite)) ||
|
||||
((permissions.Supervisor == FALSE) && (KernelMode == FALSE)))
|
||||
{
|
||||
//
|
||||
// Either the page not present, write access to non-writable page, or
|
||||
// kernel address access from the user-mode. Inject #PF(ErrorCode).
|
||||
// See: Interrupt 14-Page-Fault Exception (#PF)
|
||||
//
|
||||
ErrorInformation->ErrorType = PageFault;
|
||||
ErrorInformation->u.PageFault.FaultAddress = guestVaToOperate;
|
||||
ErrorInformation->u.PageFault.ErrorCode.Present = (physicalAddress != MV_INVALID_PHYSICAL_ADDRESS);
|
||||
ErrorInformation->u.PageFault.ErrorCode.Write = (OperationType == OperationWrite);
|
||||
ErrorInformation->u.PageFault.ErrorCode.UserModeAccess = (KernelMode == FALSE);
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
//
|
||||
// Copy bytes from or to the physical address as requested.
|
||||
//
|
||||
ReadOrWriteOnPhysicalAddress(Context,
|
||||
OperationType,
|
||||
physicalAddress,
|
||||
currentBuffer,
|
||||
bytesToOperate);
|
||||
|
||||
currentBuffer += bytesToOperate;
|
||||
guestVaToOperate += bytesToOperate;
|
||||
remainingBytesToCopy -= bytesToOperate;
|
||||
}
|
||||
|
||||
successful = TRUE;
|
||||
|
||||
Exit:
|
||||
return successful;
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
BOOLEAN
|
||||
ReadGuestVirtualAddress (
|
||||
MEMORY_ACCESS_CONTEXT* Context,
|
||||
BOOLEAN KernelMode,
|
||||
UINT64 GuestVirtualAddress,
|
||||
VOID* Buffer,
|
||||
UINT64 BytesToRead,
|
||||
MEMORY_ACCESS_ERROR_INFORMATION* ErrorInformation
|
||||
)
|
||||
{
|
||||
return ReadOrWriteGuestVirtualAddress(Context,
|
||||
OperationRead,
|
||||
KernelMode,
|
||||
GuestVirtualAddress,
|
||||
Buffer,
|
||||
BytesToRead,
|
||||
ErrorInformation);
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
BOOLEAN
|
||||
WriteGuestVirtualAddress (
|
||||
MEMORY_ACCESS_CONTEXT* Context,
|
||||
BOOLEAN KernelMode,
|
||||
UINT64 GuestVirtualAddress,
|
||||
CONST VOID* Data,
|
||||
UINT64 BytesToWrite,
|
||||
MEMORY_ACCESS_ERROR_INFORMATION* ErrorInformation
|
||||
)
|
||||
{
|
||||
return ReadOrWriteGuestVirtualAddress(Context,
|
||||
OperationWrite,
|
||||
KernelMode,
|
||||
GuestVirtualAddress,
|
||||
(VOID*)Data,
|
||||
BytesToWrite,
|
||||
ErrorInformation);
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
VOID*
|
||||
MapGuestPage (
|
||||
MEMORY_ACCESS_CONTEXT* Context,
|
||||
UINT64 GuestPageNumber
|
||||
)
|
||||
{
|
||||
UINT64 physicalAddress;
|
||||
VOID* mappedVa;
|
||||
|
||||
mappedVa = NULL;
|
||||
|
||||
physicalAddress = GetPhysicalAddressForGuest(Context,
|
||||
(GuestPageNumber << PAGE_SHIFT),
|
||||
NULL);
|
||||
if (physicalAddress == MV_INVALID_PHYSICAL_ADDRESS)
|
||||
{
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
mappedVa = MapPa(Context, physicalAddress);
|
||||
|
||||
Exit:
|
||||
return mappedVa;
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
VOID
|
||||
UnmapGuestPage (
|
||||
MEMORY_ACCESS_CONTEXT* Context,
|
||||
VOID* MappedVa
|
||||
)
|
||||
{
|
||||
MV_ASSERT(MappedVa == Context->ReservedPage);
|
||||
DBG_UNREFERENCED_PARAMETER(MappedVa);
|
||||
|
||||
UnmapPa(Context);
|
||||
}
|
||||
202
Sources/MemoryAccess.h
Normal file
202
Sources/MemoryAccess.h
Normal file
@@ -0,0 +1,202 @@
|
||||
/*!
|
||||
@file MemoryAccess.h
|
||||
|
||||
@brief Functions for guest virtual memory access from the hypervisor.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#pragma once
|
||||
#include "Common.h"
|
||||
#include "Ia32.h"
|
||||
|
||||
typedef struct _MEMORY_ACCESS_CONTEXT
|
||||
{
|
||||
//
|
||||
// Reserved virtual address (page) for access to the guest virtual memory
|
||||
// from the hypervisor.
|
||||
//
|
||||
VOID* ReservedPage;
|
||||
|
||||
//
|
||||
// The pointer to the PTE of the reserved page.
|
||||
//
|
||||
PTE_64* Pte;
|
||||
|
||||
//
|
||||
// The address of the page table that is dynamically allocated to translate
|
||||
// ReservedPage with the 4KB page (and not the large page).
|
||||
//
|
||||
PTE_64* AllocatedPageTable;
|
||||
} MEMORY_ACCESS_CONTEXT;
|
||||
|
||||
//
|
||||
// Error information can be filled by (Read|Write)GuestVirtualAddress().
|
||||
//
|
||||
typedef struct _MEMORY_ACCESS_ERROR_INFORMATION
|
||||
{
|
||||
EXCEPTION_VECTOR ErrorType;
|
||||
union
|
||||
{
|
||||
struct
|
||||
{
|
||||
PAGE_FAULT_EXCEPTION ErrorCode;
|
||||
UINT64 FaultAddress;
|
||||
} PageFault;
|
||||
} u;
|
||||
} MEMORY_ACCESS_ERROR_INFORMATION;
|
||||
|
||||
/*!
|
||||
@brief Initializes the memory access context.
|
||||
|
||||
@param[out] Context - The pointer to the context to initialize. On success,
|
||||
the caller must clean up this context with CleanupMemoryAccess().
|
||||
|
||||
@param[in] HostCr3 - The host CR3.
|
||||
|
||||
@return MV_STATUS_SUCCESS on success; otherwise, an appropriate error code.
|
||||
*/
|
||||
_IRQL_requires_max_(APC_LEVEL)
|
||||
_Must_inspect_result_
|
||||
MV_STATUS
|
||||
InitializeMemoryAccess (
|
||||
_Out_ MEMORY_ACCESS_CONTEXT* Context,
|
||||
_In_ CR3 HostCr3
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Cleans up the memory access context initialized with
|
||||
InitializeMemoryAccess().
|
||||
|
||||
@param[in,out] Context - The pointer to the context to clean up.
|
||||
*/
|
||||
_IRQL_requires_max_(APC_LEVEL)
|
||||
VOID
|
||||
CleanupMemoryAccess (
|
||||
_Inout_ MEMORY_ACCESS_CONTEXT* Context
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Retrieves the physical address associated with the guest virtual
|
||||
address.
|
||||
|
||||
@details This function walks through the guest paging structures and for the
|
||||
given virtual address and retrieves the guest physical address of it.
|
||||
This is equivalent to changing the current CR3 with the guest CR3 and
|
||||
calling GetPhysicalAddress(). This function, however, exists to avoid
|
||||
problems associated with CR3 update, for example, updating the CR3 crashes
|
||||
the system immediately if the KVA Shadow is enabled and the guest CR3
|
||||
contains the USER CR3, as it does not map our driver.
|
||||
|
||||
@param[in] Context - The pointer to the memory access context.
|
||||
|
||||
@param[in] GuestVirtualAddress - The guest virtual address to look for its
|
||||
physical address.
|
||||
|
||||
@param[out] AggregatedPagePermissions - The pointer to the paging-structure
|
||||
entry to receive aggregated copy of the page permissions specified in
|
||||
the guest paging structure entries used to translate the guest virtual
|
||||
address. If the guest physical address is mapped to the physical address,
|
||||
Write, Supervisor, and ExecuteDisable bits are updated accordingly, and
|
||||
the rest of bits are cleared.
|
||||
|
||||
@return The physical address associated with the specified virtual address if
|
||||
exists. Otherwise, MV_INVALID_PHYSICAL_ADDRESS.
|
||||
*/
|
||||
_Must_inspect_result_
|
||||
_Success_(return != MV_INVALID_PHYSICAL_ADDRESS)
|
||||
UINT64
|
||||
GetPhysicalAddressForGuest (
|
||||
_In_ MEMORY_ACCESS_CONTEXT* Context,
|
||||
_In_ UINT64 GuestVirtualAddress,
|
||||
_Out_opt_ PT_ENTRY_64* AggregatedPagePermissions
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Reads memory from the location specified as the guest virtual address.
|
||||
|
||||
@param[in] Context - See ReadOrWriteGuestVirtualAddress().
|
||||
|
||||
@param[in] KernelMode - See ReadOrWriteGuestVirtualAddress().
|
||||
|
||||
@param[in] GuestVirtualAddress - See ReadOrWriteGuestVirtualAddress().
|
||||
|
||||
@param[out] Buffer - See ReadOrWriteGuestVirtualAddress().
|
||||
|
||||
@param[in] BytesToRead - See ReadOrWriteGuestVirtualAddress().
|
||||
|
||||
@param[out] ErrorInformation - See ReadOrWriteGuestVirtualAddress().
|
||||
|
||||
@return See ReadOrWriteGuestVirtualAddress().
|
||||
*/
|
||||
_Must_inspect_result_
|
||||
BOOLEAN
|
||||
ReadGuestVirtualAddress (
|
||||
_In_ MEMORY_ACCESS_CONTEXT* Context,
|
||||
_In_ BOOLEAN KernelMode,
|
||||
_In_ UINT64 GuestVirtualAddress,
|
||||
_Out_writes_bytes_(BytesToRead) VOID* Buffer,
|
||||
_In_ UINT64 BytesToRead,
|
||||
_Out_ MEMORY_ACCESS_ERROR_INFORMATION* ErrorInformation
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Write memory to the location specified as the guest virtual address.
|
||||
|
||||
@param[in] Context - See ReadOrWriteGuestVirtualAddress().
|
||||
|
||||
@param[in] KernelMode - See ReadOrWriteGuestVirtualAddress().
|
||||
|
||||
@param[in] GuestVirtualAddress - See ReadOrWriteGuestVirtualAddress().
|
||||
|
||||
@param[out] Data - See ReadOrWriteGuestVirtualAddress().
|
||||
|
||||
@param[in] BytesToWrite - See ReadOrWriteGuestVirtualAddress().
|
||||
|
||||
@param[out] ErrorInformation - See ReadOrWriteGuestVirtualAddress().
|
||||
|
||||
@return See ReadOrWriteGuestVirtualAddress().
|
||||
*/
|
||||
_Must_inspect_result_
|
||||
BOOLEAN
|
||||
WriteGuestVirtualAddress (
|
||||
_In_ MEMORY_ACCESS_CONTEXT* Context,
|
||||
_In_ BOOLEAN KernelMode,
|
||||
_In_ UINT64 GuestVirtualAddress,
|
||||
_In_reads_bytes_(BytesToWrite) CONST VOID* Data,
|
||||
_In_ UINT64 BytesToWrite,
|
||||
_Out_ MEMORY_ACCESS_ERROR_INFORMATION* ErrorInformation
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Maps the specified guest page number to the current address space.
|
||||
|
||||
@param[in] Context - The pointer to the memory access context.
|
||||
|
||||
@param[in] GuestPageNumber - See ReadOrWriteGuestVirtualAddress().
|
||||
|
||||
@return The virtual address mapping the same physical page as specified as
|
||||
the page number, or NULL if the specified page number does not have
|
||||
a corresponding physical page. The caller must unmap the return value
|
||||
with UnmapGuestPage() when it is no longer needed.
|
||||
*/
|
||||
_Must_inspect_result_
|
||||
VOID*
|
||||
MapGuestPage (
|
||||
_Inout_ MEMORY_ACCESS_CONTEXT* Context,
|
||||
_In_ UINT64 GuestPageNumber
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Unmaps the address mapped with MapGuestPage().
|
||||
|
||||
@param[in] Context - The pointer to the memory access context.
|
||||
|
||||
@param[in] MappedVa - The pointer returned by MapGuestPage().
|
||||
*/
|
||||
VOID
|
||||
UnmapGuestPage (
|
||||
_Inout_ MEMORY_ACCESS_CONTEXT* Context,
|
||||
_In_ VOID* MappedVa
|
||||
);
|
||||
298
Sources/MemoryManager.c
Normal file
298
Sources/MemoryManager.c
Normal file
@@ -0,0 +1,298 @@
|
||||
/*!
|
||||
@file MemoryManager.c
|
||||
|
||||
@brief Functions for memory management.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#include "MemoryManager.h"
|
||||
#include "Platform.h"
|
||||
#include "Logger.h"
|
||||
#if !defined(NTDDI_VERSION)
|
||||
#include "Platform/EFI/EfiBitmap.h"
|
||||
#endif
|
||||
|
||||
#if !defined(CHAR_BIT)
|
||||
#define CHAR_BIT (8)
|
||||
#endif
|
||||
|
||||
typedef struct _MEMORY_MANAGER_CONTEXT
|
||||
{
|
||||
//
|
||||
// Lock for concurrent access to the this memory manager instance.
|
||||
//
|
||||
SPIN_LOCK SpinLock;
|
||||
|
||||
//
|
||||
// The number of pages reserved for use by the memory manager, and the
|
||||
// pointer to the reserved pages.
|
||||
//
|
||||
UINT32 PageCount;
|
||||
VOID* AllocatedPages;
|
||||
|
||||
//
|
||||
// The bit index pointing to the bit found to be clear and used by the latest
|
||||
// allocation within AllocatedPages. The memory manager will start look for
|
||||
// the next clear bit from this index as optimization.
|
||||
//
|
||||
UINT32 LastUsedBitIndex;
|
||||
|
||||
//
|
||||
// The bitmap header and actual bitmap buffer. The memory manager tracks
|
||||
// which pages within AllocatedPages are allocated for caller by setting a
|
||||
// bit to the corresponding offset in this bitmap. For example, if
|
||||
// AllocatedPages[0] to AllocatedPages[3] are allocated, bit 0-3 of the
|
||||
// bitmap are set.
|
||||
//
|
||||
RTL_BITMAP BitmapHeader;
|
||||
VOID* AllocationBitmap;
|
||||
|
||||
//
|
||||
// The array of the allocated page lengths for callers. The memory manager
|
||||
// tracks how many pages are allocated by the single request by setting the
|
||||
// length in a corresponding entry in this array. For example, if the caller
|
||||
// requests 3 pages, and the memory manager finds 3 contiguous free pages at
|
||||
// AllocatedPages[0], the memory manager sets 3 to AllocationLengthMap[0].
|
||||
// This is used to know the page length from the pointer on FreePages().
|
||||
//
|
||||
UINT8* AllocationLengthMap;
|
||||
} MEMORY_MANAGER_CONTEXT;
|
||||
|
||||
//
|
||||
// The singleton instance of the memory manager.
|
||||
//
|
||||
static MEMORY_MANAGER_CONTEXT g_MemoryManager;
|
||||
|
||||
MV_SECTION_PAGED
|
||||
_Use_decl_annotations_
|
||||
MV_STATUS
|
||||
MmInitializeMemoryManager (
|
||||
UINT32 PageCount
|
||||
)
|
||||
{
|
||||
MV_STATUS status;
|
||||
VOID* pages;
|
||||
UINT32 bitmapBytesCount;
|
||||
UINT32 bitmapPagesCount;
|
||||
VOID* bitmap;
|
||||
UINT8* lengthMap;
|
||||
UINT32 lengthMapBytesCount;
|
||||
UINT32 lengthMapPagesCount;
|
||||
MEMORY_MANAGER_CONTEXT* memoryManager;
|
||||
|
||||
PAGED_CODE()
|
||||
|
||||
memoryManager = &g_MemoryManager;
|
||||
lengthMapPagesCount = 0;
|
||||
lengthMap = NULL;
|
||||
bitmapPagesCount = 0;
|
||||
bitmap = NULL;
|
||||
pages = NULL;
|
||||
|
||||
MV_ASSERT(PageCount > 0);
|
||||
MV_ASSERT(memoryManager->PageCount == 0);
|
||||
|
||||
//
|
||||
// Allocate the memory pool for the memory manager. This can be VERY large
|
||||
// memory allocation request and fail on system with little RAM.
|
||||
//
|
||||
pages = AllocateSystemMemory(PageCount);
|
||||
if (pages == NULL)
|
||||
{
|
||||
status = MV_STATUS_INSUFFICIENT_RESOURCES;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
//
|
||||
// Computes how many bytes are required to cover the PageCount bits,
|
||||
// round it up to the page count (as we do not have API to allocate smaller
|
||||
// granularity), then allocate the bitmap.
|
||||
//
|
||||
bitmapBytesCount = (PageCount / CHAR_BIT) +
|
||||
((PageCount % CHAR_BIT) != 0);
|
||||
bitmapPagesCount = BYTES_TO_PAGES(bitmapBytesCount);
|
||||
bitmap = AllocateSystemMemory(bitmapPagesCount);
|
||||
if (bitmap == NULL)
|
||||
{
|
||||
status = MV_STATUS_INSUFFICIENT_RESOURCES;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
//
|
||||
// Compute the how many bytes are required to make the array of UINT8s
|
||||
// (lengths) for "PageCount" entries. Then, round it up to the page count
|
||||
// and allocate it.
|
||||
//
|
||||
lengthMapBytesCount = (PageCount * sizeof(UINT8));
|
||||
lengthMapPagesCount = BYTES_TO_PAGES(lengthMapBytesCount);
|
||||
lengthMap = AllocateSystemMemory(lengthMapPagesCount);
|
||||
if (lengthMap == NULL)
|
||||
{
|
||||
status = MV_STATUS_INSUFFICIENT_RESOURCES;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
//
|
||||
// All good. Initialize the memory manager instance.
|
||||
//
|
||||
status = MV_STATUS_SUCCESS;
|
||||
|
||||
InitializeSystemSpinLock(&memoryManager->SpinLock);
|
||||
memoryManager->PageCount = PageCount;
|
||||
memoryManager->AllocatedPages = pages;
|
||||
memoryManager->LastUsedBitIndex = 0;
|
||||
RtlInitializeBitMap(&memoryManager->BitmapHeader, bitmap, PageCount);
|
||||
memoryManager->AllocationBitmap = bitmap;
|
||||
memoryManager->AllocationLengthMap = lengthMap;
|
||||
|
||||
Exit:
|
||||
if (MV_ERROR(status))
|
||||
{
|
||||
if (lengthMap != NULL)
|
||||
{
|
||||
FreeSystemMemory(lengthMap, lengthMapPagesCount);
|
||||
}
|
||||
if (bitmap != NULL)
|
||||
{
|
||||
FreeSystemMemory(bitmap, bitmapPagesCount);
|
||||
}
|
||||
if (pages != NULL)
|
||||
{
|
||||
FreeSystemMemory(pages, PageCount);
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
MV_SECTION_PAGED
|
||||
_Use_decl_annotations_
|
||||
VOID
|
||||
MmCleanupMemoryManager (
|
||||
)
|
||||
{
|
||||
UINT32 bitmapBytesCount;
|
||||
UINT32 bitmapPagesCount;
|
||||
UINT32 lengthMapBytesCount;
|
||||
UINT32 lengthMapPagesCount;
|
||||
MEMORY_MANAGER_CONTEXT* memoryManager;
|
||||
|
||||
PAGED_CODE()
|
||||
|
||||
memoryManager = &g_MemoryManager;
|
||||
|
||||
//
|
||||
// The memory manager must be initialized already.
|
||||
//
|
||||
MV_ASSERT(memoryManager->PageCount != 0);
|
||||
MV_ASSERT(memoryManager->AllocatedPages != NULL);
|
||||
MV_ASSERT(memoryManager->AllocationLengthMap != NULL);
|
||||
MV_ASSERT(memoryManager->AllocatedPages != NULL);
|
||||
|
||||
//
|
||||
// All memory allocated for the callers must be freed.
|
||||
//
|
||||
MV_ASSERT(RtlAreBitsClear(&memoryManager->BitmapHeader,
|
||||
0,
|
||||
memoryManager->PageCount) != FALSE);
|
||||
|
||||
bitmapBytesCount = (memoryManager->PageCount / CHAR_BIT) +
|
||||
((memoryManager->PageCount % CHAR_BIT) != 0);
|
||||
bitmapPagesCount = BYTES_TO_PAGES(bitmapBytesCount);
|
||||
|
||||
lengthMapBytesCount = (memoryManager->PageCount * sizeof(UINT8));
|
||||
lengthMapPagesCount = BYTES_TO_PAGES(lengthMapBytesCount);
|
||||
|
||||
FreeSystemMemory(memoryManager->AllocationBitmap,
|
||||
bitmapPagesCount);
|
||||
FreeSystemMemory(memoryManager->AllocationLengthMap,
|
||||
lengthMapPagesCount);
|
||||
FreeSystemMemory(memoryManager->AllocatedPages,
|
||||
memoryManager->PageCount);
|
||||
|
||||
RtlZeroMemory(memoryManager, sizeof(*memoryManager));
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
VOID*
|
||||
MmAllocatePages (
|
||||
UINT8 PageCount
|
||||
)
|
||||
{
|
||||
VOID* pages;
|
||||
UINT32 bitIndex;
|
||||
MEMORY_MANAGER_CONTEXT* memoryManager;
|
||||
UINT8 oldIrql;
|
||||
|
||||
memoryManager = &g_MemoryManager;
|
||||
|
||||
//
|
||||
// Search the contiguous free page(s) that suffices the request.
|
||||
//
|
||||
oldIrql = AcquireSystemSpinLock(&memoryManager->SpinLock);
|
||||
bitIndex = RtlFindClearBitsAndSet(&memoryManager->BitmapHeader,
|
||||
PageCount,
|
||||
memoryManager->LastUsedBitIndex);
|
||||
ReleaseSystemSpinLock(&memoryManager->SpinLock, oldIrql);
|
||||
|
||||
if (bitIndex == MAXUINT32)
|
||||
{
|
||||
MV_DEBUG_BREAK();
|
||||
LOG_ERROR("Memory allocation failed : %lu", (UINT32)PageCount * PAGE_SIZE);
|
||||
pages = NULL;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
//
|
||||
// Return the page(s) from the pool, and update the book keeping fields.
|
||||
//
|
||||
pages = MV_ADD2PTR(memoryManager->AllocatedPages, ((UINT64)bitIndex * PAGE_SIZE));
|
||||
memoryManager->AllocationLengthMap[bitIndex] = PageCount;
|
||||
memoryManager->LastUsedBitIndex = bitIndex;
|
||||
|
||||
Exit:
|
||||
return pages;
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
VOID
|
||||
MmFreePages (
|
||||
VOID* Pages
|
||||
)
|
||||
{
|
||||
UINT64 offsetInBytes;
|
||||
UINT32 bitIndex;
|
||||
MEMORY_MANAGER_CONTEXT* memoryManager;
|
||||
UINT8 oldIrql;
|
||||
UINT8 pageLength;
|
||||
|
||||
memoryManager = &g_MemoryManager;
|
||||
|
||||
//
|
||||
// The pointer must be page aligned, within the range of
|
||||
// [AllocatedPages, AllocatedPages + PageCount).
|
||||
//
|
||||
MV_ASSERT(Pages == PAGE_ALIGN(Pages));
|
||||
MV_ASSERT((UINT64)Pages >= (UINT64)memoryManager->AllocatedPages);
|
||||
MV_ASSERT((UINT64)Pages <
|
||||
(UINT64)memoryManager->AllocatedPages + ((UINT64)memoryManager->PageCount * PAGE_SIZE));
|
||||
|
||||
//
|
||||
// Compute the bit index corresponds to the pointer requested for freeing,
|
||||
// and look up its length with it. The length must be more than zero, meaning
|
||||
// that the page allocated for the caller.
|
||||
//
|
||||
offsetInBytes = ((UINT64)Pages - (UINT64)memoryManager->AllocatedPages);
|
||||
bitIndex = (UINT32)(offsetInBytes / PAGE_SIZE);
|
||||
pageLength = memoryManager->AllocationLengthMap[bitIndex];
|
||||
MV_ASSERT(pageLength > 0);
|
||||
|
||||
//
|
||||
// Clears the bitmap and the length to "free" the page.
|
||||
//
|
||||
oldIrql = AcquireSystemSpinLock(&memoryManager->SpinLock);
|
||||
RtlClearBits(&memoryManager->BitmapHeader, bitIndex, pageLength);
|
||||
memoryManager->AllocationLengthMap[bitIndex] = 0;
|
||||
ReleaseSystemSpinLock(&memoryManager->SpinLock, oldIrql);
|
||||
}
|
||||
61
Sources/MemoryManager.h
Normal file
61
Sources/MemoryManager.h
Normal file
@@ -0,0 +1,61 @@
|
||||
/*!
|
||||
@file MemoryManager.h
|
||||
|
||||
@brief Functions for memory management.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#pragma once
|
||||
#include "Common.h"
|
||||
|
||||
/*!
|
||||
@brief Allocates the requested size of memory and initialize the singleton
|
||||
memory manager instance with it.
|
||||
|
||||
@param[in] PageCount - The page count to allocate for the use by the
|
||||
memory manager.
|
||||
|
||||
@return MV_STATUS_SUCCESS on success; otherwise, an appropriate error code.
|
||||
*/
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
_Must_inspect_result_
|
||||
MV_STATUS
|
||||
MmInitializeMemoryManager (
|
||||
_In_ UINT32 PageCount
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Cleans up the memory manager.
|
||||
*/
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
VOID
|
||||
MmCleanupMemoryManager (
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Allocates page-aligned, zero-initialized physical page backed pool.
|
||||
|
||||
@param[in] PageCount - The page count to allocate.
|
||||
|
||||
@return The base of allocated pointer, or NULL on failure. The caller must
|
||||
free the return value with FreeSystemMemory().
|
||||
*/
|
||||
_Post_maybenull_
|
||||
_Post_writable_byte_size_(PageCount * PAGE_SIZE)
|
||||
_Must_inspect_result_
|
||||
VOID*
|
||||
MmAllocatePages (
|
||||
_In_ UINT8 PageCount
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Frees the memory allocated by AllocatePages().
|
||||
|
||||
@param[in] Pages - The pointer to free.
|
||||
*/
|
||||
VOID
|
||||
MmFreePages (
|
||||
_Pre_notnull_ VOID* Pages
|
||||
);
|
||||
383
Sources/MemoryType.c
Normal file
383
Sources/MemoryType.c
Normal file
@@ -0,0 +1,383 @@
|
||||
/*!
|
||||
@file MemoryType.c
|
||||
|
||||
@brief Functions for MTRR (memory type range registers) handling.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#include "MemoryType.h"
|
||||
#include "Logger.h"
|
||||
|
||||
//
|
||||
// A physical address range and its memory type.
|
||||
//
|
||||
typedef struct _MEMORY_TYPE_RANGE
|
||||
{
|
||||
BOOLEAN FixedMtrr;
|
||||
IA32_MEMORY_TYPE MemoryType;
|
||||
UINT64 RangeBase; // Inclusive
|
||||
UINT64 RangeEnd; // Exclusive
|
||||
} MEMORY_TYPE_RANGE;
|
||||
|
||||
//
|
||||
// Represents MTRR configurations on this system.
|
||||
//
|
||||
typedef struct _MTRR_CONTEXT
|
||||
{
|
||||
//
|
||||
// The memory type should be used for physical addresses whose memory types
|
||||
// are not specified by MTRR.
|
||||
//
|
||||
IA32_MEMORY_TYPE DefaultMemoryType;
|
||||
|
||||
//
|
||||
// The collection of physical memory address ranges and their memory types
|
||||
// collected from MTRR.
|
||||
//
|
||||
MEMORY_TYPE_RANGE MemoryTypeRanges[IA32_MTRR_COUNT];
|
||||
} MTRR_CONTEXT;
|
||||
|
||||
//
|
||||
// Mapping of physical addresses to memory types.
|
||||
//
|
||||
static MTRR_CONTEXT g_MtrrDatabase;
|
||||
|
||||
|
||||
typedef struct _FIXED_MTRR_RANGE_INFORMATION
|
||||
{
|
||||
//
|
||||
// The fixed-range MTRR MSR.
|
||||
//
|
||||
IA32_MSR_ADDRESS Msr;
|
||||
|
||||
//
|
||||
// The start physical address where the fixed-range MTRR MSR manages.
|
||||
//
|
||||
UINT64 BaseAddress;
|
||||
|
||||
//
|
||||
// The size where a single "range" of the fixed-range MTRR MSR manages.
|
||||
// A single fixed-range MTRR contains 8 ranges, and this field represents
|
||||
// the size a single range manages.
|
||||
// See: 11.11.2.2 Fixed Range MTRRs
|
||||
//
|
||||
UINT64 ManagedSize;
|
||||
} FIXED_MTRR_RANGE_INFORMATION;
|
||||
|
||||
MV_SECTION_INIT
|
||||
VOID
|
||||
InitializeMemoryTypeMapping (
|
||||
)
|
||||
{
|
||||
//
|
||||
// This array defines all fixed-range MTRRs.
|
||||
// See: 11.11.2.2 Fixed Range MTRRs
|
||||
//
|
||||
static CONST FIXED_MTRR_RANGE_INFORMATION rangeInformation[] =
|
||||
{
|
||||
{ IA32_MTRR_FIX64K_00000, 0x0, 0x10000, },
|
||||
{ IA32_MTRR_FIX16K_80000, 0x80000, 0x4000, },
|
||||
{ IA32_MTRR_FIX16K_A0000, 0xA0000, 0x4000, },
|
||||
{ IA32_MTRR_FIX4K_C0000, 0xC0000, 0x1000, },
|
||||
{ IA32_MTRR_FIX4K_C8000, 0xC8000, 0x1000, },
|
||||
{ IA32_MTRR_FIX4K_D0000, 0xD0000, 0x1000, },
|
||||
{ IA32_MTRR_FIX4K_D8000, 0xD8000, 0x1000, },
|
||||
{ IA32_MTRR_FIX4K_E0000, 0xE0000, 0x1000, },
|
||||
{ IA32_MTRR_FIX4K_E8000, 0xE8000, 0x1000, },
|
||||
{ IA32_MTRR_FIX4K_F0000, 0xF0000, 0x1000, },
|
||||
{ IA32_MTRR_FIX4K_F8000, 0xF8000, 0x1000, },
|
||||
};
|
||||
|
||||
UINT32 index;
|
||||
IA32_MTRR_DEF_TYPE_REGISTER defaultType;
|
||||
IA32_MTRR_CAPABILITIES_REGISTER capabilities;
|
||||
MEMORY_TYPE_RANGE rangeType;
|
||||
MTRR_CONTEXT* mtrr;
|
||||
IA32_MEMORY_TYPE memoryType;
|
||||
UINT64 baseForRange;
|
||||
|
||||
mtrr = &g_MtrrDatabase;
|
||||
index = 0;
|
||||
defaultType.Flags = __readmsr(IA32_MTRR_DEF_TYPE);
|
||||
capabilities.Flags = __readmsr(IA32_MTRR_CAPABILITIES);
|
||||
|
||||
RtlZeroMemory(&rangeType, sizeof(rangeType));
|
||||
rangeType.MemoryType = MEMORY_TYPE_INVALID;
|
||||
|
||||
//
|
||||
// We assume MTRRs are enabled. This should always be the case.
|
||||
//
|
||||
MV_ASSERT(defaultType.MtrrEnable != FALSE);
|
||||
|
||||
//
|
||||
// Use the fixed-range MTRRs when supported and enabled.
|
||||
//
|
||||
if ((capabilities.FixedRangeSupported != FALSE) &&
|
||||
(defaultType.FixedRangeMtrrEnable != FALSE))
|
||||
{
|
||||
//
|
||||
// Parse all fixed-range MTRRs.
|
||||
//
|
||||
for (UINT32 i = 0; i < RTL_NUMBER_OF(rangeInformation); ++i)
|
||||
{
|
||||
CONST FIXED_MTRR_RANGE_INFORMATION* range;
|
||||
IA32_MTRR_FIXED_RANGE_MSR fixedRange;
|
||||
|
||||
range = &rangeInformation[i];
|
||||
fixedRange.Flags = __readmsr(range->Msr);
|
||||
|
||||
//
|
||||
// The fixed-range MTRR consists of 8 memory type fields. Each of
|
||||
// them specifies the memory type of the range computed as follows:
|
||||
// start address + (managed size * index of the range).
|
||||
// Go through each of them and save pairs of ranges and memory types.
|
||||
// See: 11.11.2.2 Fixed Range MTRRs
|
||||
// See: Table 11-9. Address Mapping for Fixed-Range MTRRs
|
||||
//
|
||||
for (UINT32 j = 0; j < RTL_NUMBER_OF(fixedRange.u.Types); ++j)
|
||||
{
|
||||
memoryType = fixedRange.u.Types[j];
|
||||
baseForRange = range->BaseAddress + (range->ManagedSize * j);
|
||||
|
||||
//
|
||||
// Combine this entry if it is contiguous from the previous entry
|
||||
// with the same memory type.
|
||||
//
|
||||
if ((rangeType.MemoryType == memoryType) &&
|
||||
(rangeType.RangeEnd == baseForRange))
|
||||
{
|
||||
rangeType.RangeEnd += range->ManagedSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
//
|
||||
// Otherwise, save the previous (possibly combined) entry to
|
||||
// the database if exists. Then, keep the current entry for
|
||||
// combining with any following contiguous entries.
|
||||
//
|
||||
if (rangeType.MemoryType != MEMORY_TYPE_INVALID)
|
||||
{
|
||||
mtrr->MemoryTypeRanges[index] = rangeType;
|
||||
index++;
|
||||
}
|
||||
rangeType.FixedMtrr = TRUE;
|
||||
rangeType.MemoryType = memoryType;
|
||||
rangeType.RangeBase = baseForRange;
|
||||
rangeType.RangeEnd = baseForRange + (range->ManagedSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Go through all variable-range MTRRs.
|
||||
// See: 11.11.2.3 Variable Range MTRRs
|
||||
//
|
||||
for (UINT32 i = 0; i < capabilities.VariableRangeCount; ++i)
|
||||
{
|
||||
IA32_MSR_ADDRESS physMaskMsr, physBaseMsr;
|
||||
IA32_MTRR_PHYSMASK_REGISTER physMaskValue;
|
||||
IA32_MTRR_PHYSBASE_REGISTER physBaseValue;
|
||||
UINT32 length;
|
||||
UINT64 sizeInPages;
|
||||
|
||||
//
|
||||
// The variable-range MTRR is described with a pair of MSRs:
|
||||
// IA32_MTRR_PHYSBASEn indicating the memory type and the starting
|
||||
// address and IA32_MTRR_PHYSMASKn indicating the size.
|
||||
//
|
||||
physMaskMsr = IA32_MTRR_PHYSMASK0 + (i * 2);
|
||||
physMaskValue.Flags = __readmsr(physMaskMsr);
|
||||
|
||||
//
|
||||
// Skip if the IA32_MTRR_PHYSMASKn and IA32_MTRR_PHYSBASEn pair is
|
||||
// disabled.
|
||||
//
|
||||
if (physMaskValue.Valid == FALSE)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
//
|
||||
// Compute the size of the range.
|
||||
//
|
||||
MV_VERIFY(_BitScanForward64((unsigned long*)&length, physMaskValue.PageFrameNumber) != 0);
|
||||
sizeInPages = (1ull << length);
|
||||
|
||||
//
|
||||
// Get the starting address (in pages) and the memory type, then save
|
||||
// them.
|
||||
//
|
||||
physBaseMsr = IA32_MTRR_PHYSBASE0 + (i * 2);
|
||||
physBaseValue.Flags = __readmsr(physBaseMsr);
|
||||
|
||||
memoryType = (IA32_MEMORY_TYPE)physBaseValue.Type;
|
||||
baseForRange = (physBaseValue.PageFrameNumber << PAGE_SHIFT);
|
||||
|
||||
//
|
||||
// The same logic as above. Combine if contiguous, else save the entry
|
||||
// to the database and keep the current entry for combining with subsequent
|
||||
// entries.
|
||||
//
|
||||
if ((rangeType.FixedMtrr == FALSE) &&
|
||||
(rangeType.MemoryType == memoryType) &&
|
||||
(rangeType.RangeEnd == baseForRange))
|
||||
{
|
||||
rangeType.RangeEnd += (sizeInPages << PAGE_SHIFT);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (rangeType.MemoryType != MEMORY_TYPE_INVALID)
|
||||
{
|
||||
mtrr->MemoryTypeRanges[index] = rangeType;
|
||||
index++;
|
||||
}
|
||||
rangeType.FixedMtrr = FALSE;
|
||||
rangeType.MemoryType = memoryType;
|
||||
rangeType.RangeBase = baseForRange;
|
||||
rangeType.RangeEnd = baseForRange + (sizeInPages << PAGE_SHIFT);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Add the last entry to the database.
|
||||
//
|
||||
if (rangeType.MemoryType != MEMORY_TYPE_INVALID)
|
||||
{
|
||||
mtrr->MemoryTypeRanges[index] = rangeType;
|
||||
}
|
||||
|
||||
mtrr->DefaultMemoryType = (IA32_MEMORY_TYPE)defaultType.DefaultMemoryType;
|
||||
|
||||
//
|
||||
// Dump configured ranges.
|
||||
//
|
||||
LOG_DEBUG("Type=%u (Default)", mtrr->DefaultMemoryType);
|
||||
for (UINT32 i = 0; i <= index; ++i)
|
||||
{
|
||||
LOG_DEBUG("Type=%u Fixed=%u %016llx - %016llx",
|
||||
mtrr->MemoryTypeRanges[i].MemoryType,
|
||||
mtrr->MemoryTypeRanges[i].FixedMtrr,
|
||||
mtrr->MemoryTypeRanges[i].RangeBase,
|
||||
mtrr->MemoryTypeRanges[i].RangeEnd);
|
||||
}
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
IA32_MEMORY_TYPE
|
||||
GetMemoryTypeForRange (
|
||||
UINT64 PhysicalAddress,
|
||||
UINT64 RangeSize
|
||||
)
|
||||
{
|
||||
IA32_MEMORY_TYPE memoryType;
|
||||
MTRR_CONTEXT* mtrr;
|
||||
|
||||
mtrr = &g_MtrrDatabase;
|
||||
|
||||
memoryType = MEMORY_TYPE_INVALID;
|
||||
|
||||
for (UINT32 i = 0; i < RTL_NUMBER_OF(mtrr->MemoryTypeRanges); ++i)
|
||||
{
|
||||
CONST MEMORY_TYPE_RANGE* range;
|
||||
|
||||
range = &mtrr->MemoryTypeRanges[i];
|
||||
|
||||
if ((range->RangeBase == 0) &&
|
||||
(range->RangeEnd == 0))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
//
|
||||
// Look for the next range information if the base address is outside
|
||||
// the range this entry manages.
|
||||
//
|
||||
if ((PhysicalAddress < range->RangeBase) ||
|
||||
(PhysicalAddress >= range->RangeEnd))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
//
|
||||
// The first page is managed by the current MTRR entry. Then, all other
|
||||
// pages in the given range must fit in the current MTRR entry; otherwise
|
||||
// the given range has more than one MTRRs either explicitly or
|
||||
// implicitly. Bail out and report error in this case.
|
||||
//
|
||||
if ((PhysicalAddress + RangeSize - 1) >= range->RangeEnd)
|
||||
{
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
//
|
||||
// The fixed-range MTRR takes precedence.
|
||||
//
|
||||
// "If the physical address falls within the first 1 MByte of physical
|
||||
// memory and fixed MTRRs are enabled, the processor uses the memory
|
||||
// type stored for the appropriate fixed-range MTRR."
|
||||
// See: 11.11.4.1 MTRR Precedences
|
||||
//
|
||||
if (range->FixedMtrr != FALSE)
|
||||
{
|
||||
memoryType = range->MemoryType;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
//
|
||||
// The UC memory type takes precedence.
|
||||
//
|
||||
// "If two or more variable memory ranges match and one of the memory
|
||||
// types is UC, the UC memory type used."
|
||||
// See: 11.11.4.1 MTRR Precedences
|
||||
//
|
||||
if (range->MemoryType == MEMORY_TYPE_UNCACHEABLE)
|
||||
{
|
||||
memoryType = range->MemoryType;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
//
|
||||
// The WT memory type takes precedence over the WB memory type.
|
||||
//
|
||||
// "If two or more variable memory ranges match and the memory types are
|
||||
// WT and WB, the WT memory type is used."
|
||||
// See: 11.11.4.1 MTRR Precedences
|
||||
//
|
||||
if (((range->MemoryType == MEMORY_TYPE_WRITE_THROUGH) &&
|
||||
(memoryType == MEMORY_TYPE_WRITE_BACK)) ||
|
||||
((range->MemoryType == MEMORY_TYPE_WRITE_BACK) &&
|
||||
(memoryType == MEMORY_TYPE_WRITE_THROUGH)))
|
||||
{
|
||||
memoryType = MEMORY_TYPE_WRITE_THROUGH;
|
||||
}
|
||||
else
|
||||
{
|
||||
//
|
||||
// Use the last matching MTRR (even with multiple entries overlap).
|
||||
//
|
||||
// "For overlaps not defined by the above rules, processor behavior
|
||||
// is undefined."
|
||||
// See: 11.11.4.1 MTRR Precedences
|
||||
//
|
||||
memoryType = range->MemoryType;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Use the default type if none of MTRRs controls any page in this range.
|
||||
//
|
||||
// "If no fixed or variable memory range matches, the processor uses the
|
||||
// default memory type."
|
||||
// See: 11.11.4.1 MTRR Precedences
|
||||
//
|
||||
if (memoryType == MEMORY_TYPE_INVALID)
|
||||
{
|
||||
memoryType = mtrr->DefaultMemoryType;
|
||||
}
|
||||
|
||||
Exit:
|
||||
return memoryType;
|
||||
}
|
||||
35
Sources/MemoryType.h
Normal file
35
Sources/MemoryType.h
Normal file
@@ -0,0 +1,35 @@
|
||||
/*!
|
||||
@file MemoryType.h
|
||||
|
||||
@brief Functions for MTRR (memory type range registers) handling.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#pragma once
|
||||
#include "Common.h"
|
||||
#include "Ia32.h"
|
||||
|
||||
/*!
|
||||
@brief Initializes the MTRR context.
|
||||
*/
|
||||
VOID
|
||||
InitializeMemoryTypeMapping (
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Returns a memory type for the given physical address range.
|
||||
|
||||
@param[in] PhysicalAddress - A physical address to retrieve its memory type.
|
||||
|
||||
@param[in] RangeSize - The size of the range to check.
|
||||
|
||||
@return The memory type for the given physical address. If the range contains
|
||||
more than one memory type, return MEMORY_TYPE_INVALID.
|
||||
*/
|
||||
IA32_MEMORY_TYPE
|
||||
GetMemoryTypeForRange (
|
||||
_In_ UINT64 PhysicalAddress,
|
||||
_In_ UINT64 RangeSize
|
||||
);
|
||||
1381
Sources/MiniVisor.c
Normal file
1381
Sources/MiniVisor.c
Normal file
File diff suppressed because it is too large
Load Diff
30
Sources/MiniVisor.h
Normal file
30
Sources/MiniVisor.h
Normal file
@@ -0,0 +1,30 @@
|
||||
/*!
|
||||
@file MiniVisor.h
|
||||
|
||||
@brief MiniVisor initialization.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#pragma once
|
||||
#include "Common.h"
|
||||
|
||||
/*!
|
||||
@brief Cross platform entry point. Initializes MiniVisor.
|
||||
|
||||
@return MV_STATUS_SUCCESS on success; otherwise, an appropriate error code.
|
||||
*/
|
||||
MV_STATUS
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
_Must_inspect_result_
|
||||
InitializeMiniVisor (
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Cross platform clean up entry callback entry point. Cleans up MiniVisor.
|
||||
*/
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
VOID
|
||||
CleanupMiniVisor (
|
||||
);
|
||||
175
Sources/MiniVisor.vcxproj
Normal file
175
Sources/MiniVisor.vcxproj
Normal file
@@ -0,0 +1,175 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="UEFI|x64">
|
||||
<Configuration>UEFI</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{B94B175C-8D18-47E2-800C-1AFBAAC7AC73}</ProjectGuid>
|
||||
<TemplateGuid>{dd38f7fc-d7bd-488b-9242-7d8754cde80d}</TemplateGuid>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<MinimumVisualStudioVersion>12.0</MinimumVisualStudioVersion>
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform Condition="'$(Platform)' == ''">Win32</Platform>
|
||||
<RootNamespace>MiniVisor</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>$(LatestTargetPlatformVersion)</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Label="Configuration">
|
||||
<TargetVersion>Windows7</TargetVersion>
|
||||
<UseDebugLibraries Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</UseDebugLibraries>
|
||||
<UseDebugLibraries Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</UseDebugLibraries>
|
||||
<PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset>
|
||||
<ConfigurationType>Driver</ConfigurationType>
|
||||
<DriverType>WDM</DriverType>
|
||||
<DriverTargetPlatform>Desktop</DriverTargetPlatform>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Label="Configuration" Condition="'$(Configuration)'=='UEFI'">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>NotSet</CharacterSet>
|
||||
<WholeProgramOptimization>false</WholeProgramOptimization>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
<PropertyGroup Label="UserMacros">
|
||||
<Edk2Dir>$(SolutionDir)..\..\</Edk2Dir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)'!='UEFI'">
|
||||
<DebuggerFlavor>DbgengKernelDebugger</DebuggerFlavor>
|
||||
<IncludePath>$(VC_IncludePath);$(IncludePath)</IncludePath>
|
||||
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
|
||||
<PostBuildEventUseInBuild>false</PostBuildEventUseInBuild>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)'=='UEFI'">
|
||||
<IncludePath>$(SolutionDir)Include;$(Edk2Dir)MdePkg\Include;$(Edk2Dir)MdePkg\Include\X64</IncludePath>
|
||||
<LibraryPath>$(SolutionDir)Libs</LibraryPath>
|
||||
<TargetExt>.efi</TargetExt>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)'=='UEFI'">
|
||||
<TargetName>$(ProjectName)Dxe</TargetName>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<PreprocessorDefinitions>POOL_NX_OPTIN=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)'=='UEFI'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<BufferSecurityCheck>false</BufferSecurityCheck>
|
||||
<BasicRuntimeChecks>Default</BasicRuntimeChecks>
|
||||
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
|
||||
<SupportJustMyCode>false</SupportJustMyCode>
|
||||
<Optimization Condition="'$(Configuration)'=='UEFI'">Disabled</Optimization>
|
||||
<OmitFramePointers Condition="'$(Configuration)'=='UEFI'">false</OmitFramePointers>
|
||||
<WholeProgramOptimization Condition="'$(Configuration)'=='UEFI'">false</WholeProgramOptimization>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>EFI Runtime</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<IgnoreAllDefaultLibraries>true</IgnoreAllDefaultLibraries>
|
||||
<AdditionalDependencies>BaseDebugLibSerialPort.lib;BaseDebugPrintErrorLevelLib.lib;BaseIoLibIntrinsicSev.lib;BaseLib.lib;BaseMemoryLibOptDxe.lib;BasePrintLib.lib;BaseSynchronizationLib.lib;BaseTimerLibNullTemplate.lib;DxePcdLib.lib;UefiBootServicesTableLib.lib;UefiDevicePathLibDevicePathProtocol.lib;UefiDriverEntryPoint.lib;UefiLib.lib;UefiMemoryAllocationLib.lib;UefiRuntimeServicesTableLib.lib;MiniVisorDxe.lib;PcAtSerialPortLib.lib</AdditionalDependencies>
|
||||
<EntryPointSymbol>_ModuleEntryPoint</EntryPointSymbol>
|
||||
<EnableUAC>false</EnableUAC>
|
||||
<RandomizedBaseAddress>false</RandomizedBaseAddress>
|
||||
<DataExecutionPrevention>false</DataExecutionPrevention>
|
||||
</Link>
|
||||
<PreLinkEvent>
|
||||
<Command>python $(SolutionDir)PreLinkEvent.py $(Edk2Dir)Build\MiniVisor\NOOPT_VS2019\X64 $(SolutionDir)Libs</Command>
|
||||
</PreLinkEvent>
|
||||
<PostBuildEvent>
|
||||
<Command Condition="'$(Configuration)'=='UEFI'">copy /y $(OutDir)$(TargetName)$(TargetExt) D:\</Command>
|
||||
</PostBuildEvent>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<FilesToPackage Include="$(TargetPath)" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<MASM Include="Asm.asm" />
|
||||
<MASM Include="Platform\EFI\EfiAsm.asm">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)'!='UEFI'">true</ExcludedFromBuild>
|
||||
</MASM>
|
||||
<MASM Include="Platform\Windows\WinAsm.asm">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)'=='UEFI'">true</ExcludedFromBuild>
|
||||
</MASM>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="Asm.h" />
|
||||
<ClInclude Include="Common.h" />
|
||||
<ClInclude Include="ExtendedPageTables.h" />
|
||||
<ClInclude Include="HostNesting.h" />
|
||||
<ClInclude Include="HostUtils.h" />
|
||||
<ClInclude Include="HostInitialization.h" />
|
||||
<ClInclude Include="HostMain.h" />
|
||||
<ClInclude Include="HostVmcall.h" />
|
||||
<ClInclude Include="Ia32.h" />
|
||||
<ClInclude Include="Logger.h" />
|
||||
<ClInclude Include="MemoryAccess.h" />
|
||||
<ClInclude Include="MemoryManager.h" />
|
||||
<ClInclude Include="MemoryType.h" />
|
||||
<ClInclude Include="MiniVisor.h" />
|
||||
<ClInclude Include="Platform.h" />
|
||||
<ClInclude Include="Platform\EFI\EfiAsm.h" />
|
||||
<ClInclude Include="Platform\EFI\EfiBitmap.h" />
|
||||
<ClInclude Include="Platform\EFI\EfiCommon.h" />
|
||||
<ClInclude Include="Platform\EFI\EfiHostInitialization.h" />
|
||||
<ClInclude Include="Platform\EFI\EfiLogger.h" />
|
||||
<ClInclude Include="Platform\EFI\EfiPlatform.h" />
|
||||
<ClInclude Include="Platform\Windows\WinAsm.h" />
|
||||
<ClInclude Include="Platform\Windows\WinCommon.h" />
|
||||
<ClInclude Include="Platform\Windows\WinHostInitialization.h" />
|
||||
<ClInclude Include="Platform\Windows\WinLogger.h" />
|
||||
<ClInclude Include="Platform\Windows\WinPlatform.h" />
|
||||
<ClInclude Include="Public.h" />
|
||||
<ClInclude Include="Utils.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="ExtendedPageTables.c" />
|
||||
<ClCompile Include="HostUtils.c" />
|
||||
<ClCompile Include="HostMain.c" />
|
||||
<ClCompile Include="HostVmcall.c" />
|
||||
<ClCompile Include="MemoryAccess.c" />
|
||||
<ClCompile Include="MemoryManager.c" />
|
||||
<ClCompile Include="MemoryType.c" />
|
||||
<ClCompile Include="MiniVisor.c" />
|
||||
<ClCompile Include="Platform\EFI\EfiBitmap.c">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)'!='UEFI'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Platform\EFI\EfiHostInitialization.c">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)'!='UEFI'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Platform\EFI\EfiLogger.c">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)'!='UEFI'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Platform\EFI\EfiPlatform.c">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)'!='UEFI'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Platform\Windows\WinHostInitialization.c">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)'=='UEFI'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Platform\Windows\WinLogger.c">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)'=='UEFI'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Platform\Windows\WinPlatform.c">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)'=='UEFI'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Utils.c" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="AsmCommon.inc" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets" />
|
||||
</Project>
|
||||
187
Sources/MiniVisor.vcxproj.filters
Normal file
187
Sources/MiniVisor.vcxproj.filters
Normal file
@@ -0,0 +1,187 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Source Files\Platform">
|
||||
<UniqueIdentifier>{e293d76d-1cb2-498e-865d-1408d410a323}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Source Files\Platform\Windows">
|
||||
<UniqueIdentifier>{89b31625-554c-4a64-adb9-53d69633d23e}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Source Files\Platform\EFI">
|
||||
<UniqueIdentifier>{0666652f-cc2f-4a3b-a0c5-0eb8b57f2ef5}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Header Files\Platform">
|
||||
<UniqueIdentifier>{e9cab091-74b4-4aa0-b8ea-0526186a92ba}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Header Files\Platform\Windows">
|
||||
<UniqueIdentifier>{377c4141-5e61-4508-ad18-231451501b85}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Header Files\Platform\EFI">
|
||||
<UniqueIdentifier>{39c270e1-5eba-4499-b137-e359597058c3}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="Common.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ExtendedPageTables.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="HostInitialization.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="HostMain.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Ia32.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Logger.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="MemoryManager.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="MemoryType.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="MiniVisor.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Platform.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Public.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Utils.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="HostVmcall.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="HostUtils.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Platform\Windows\WinCommon.h">
|
||||
<Filter>Header Files\Platform\Windows</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Platform\EFI\EfiCommon.h">
|
||||
<Filter>Header Files\Platform\EFI</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Platform\EFI\EfiAsm.h">
|
||||
<Filter>Header Files\Platform\EFI</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Platform\Windows\WinAsm.h">
|
||||
<Filter>Header Files\Platform\Windows</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Asm.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Platform\EFI\EfiBitmap.h">
|
||||
<Filter>Header Files\Platform\EFI</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Platform\EFI\EfiLogger.h">
|
||||
<Filter>Header Files\Platform\EFI</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Platform\Windows\WinLogger.h">
|
||||
<Filter>Header Files\Platform\Windows</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Platform\Windows\WinPlatform.h">
|
||||
<Filter>Header Files\Platform\Windows</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Platform\EFI\EfiPlatform.h">
|
||||
<Filter>Header Files\Platform\EFI</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Platform\EFI\EfiHostInitialization.h">
|
||||
<Filter>Header Files\Platform\EFI</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Platform\Windows\WinHostInitialization.h">
|
||||
<Filter>Header Files\Platform\Windows</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="MemoryAccess.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="HostNesting.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="ExtendedPageTables.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="HostMain.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MemoryManager.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MemoryType.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MiniVisor.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Utils.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="HostVmcall.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="HostUtils.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Platform\Windows\WinLogger.c">
|
||||
<Filter>Source Files\Platform\Windows</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Platform\Windows\WinPlatform.c">
|
||||
<Filter>Source Files\Platform\Windows</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Platform\EFI\EfiPlatform.c">
|
||||
<Filter>Source Files\Platform\EFI</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Platform\EFI\EfiLogger.c">
|
||||
<Filter>Source Files\Platform\EFI</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Platform\EFI\EfiBitmap.c">
|
||||
<Filter>Source Files\Platform\EFI</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Platform\EFI\EfiHostInitialization.c">
|
||||
<Filter>Source Files\Platform\EFI</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Platform\Windows\WinHostInitialization.c">
|
||||
<Filter>Source Files\Platform\Windows</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MemoryAccess.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<MASM Include="Asm.asm">
|
||||
<Filter>Source Files</Filter>
|
||||
</MASM>
|
||||
<MASM Include="Platform\EFI\EfiAsm.asm">
|
||||
<Filter>Source Files\Platform\EFI</Filter>
|
||||
</MASM>
|
||||
<MASM Include="Platform\Windows\WinAsm.asm">
|
||||
<Filter>Source Files\Platform\Windows</Filter>
|
||||
</MASM>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="AsmCommon.inc">
|
||||
<Filter>Header Files</Filter>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
247
Sources/Platform.h
Normal file
247
Sources/Platform.h
Normal file
@@ -0,0 +1,247 @@
|
||||
/*!
|
||||
@file Platform.h
|
||||
|
||||
@brief Platform specific API.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#pragma once
|
||||
#include "Common.h"
|
||||
|
||||
//
|
||||
// Spin lock type and state names.
|
||||
//
|
||||
#if defined(NTDDI_VERSION)
|
||||
typedef volatile LONG64 SPIN_LOCK;
|
||||
typedef enum _SPIN_LOCK_STATE
|
||||
{
|
||||
SpinLockReleased,
|
||||
SpinLockAcquired,
|
||||
} SPIN_LOCK_STATE;
|
||||
#else
|
||||
#include <Library/SynchronizationLib.h>
|
||||
#endif
|
||||
|
||||
/*!
|
||||
@brief Initializes platform specific bits.
|
||||
|
||||
@return MV_STATUS_SUCCESS on success; otherwise, an appropriate error code.
|
||||
*/
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
_Must_inspect_result_
|
||||
MV_STATUS
|
||||
InitializePlatform (
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Cleans up platform specific bits.
|
||||
*/
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
VOID
|
||||
CleanupPlatform (
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Stalls execution of the current processor.
|
||||
|
||||
@details Use of this API from the host is not allowed.
|
||||
|
||||
@param[in] Milliseconds - The time to stall in milliseconds.
|
||||
*/
|
||||
_IRQL_requires_max_(APC_LEVEL)
|
||||
VOID
|
||||
Sleep (
|
||||
_In_ UINT64 Milliseconds
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Returns the active logical processor count.
|
||||
|
||||
@details Use of this API from the host is not allowed.
|
||||
|
||||
@return The active logical processor count.
|
||||
*/
|
||||
UINT32
|
||||
GetActiveProcessorCount (
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Returns the current processor number. The BSP will return 0.
|
||||
|
||||
@details Use of this API from the host is not allowed.
|
||||
|
||||
@return The current processor number. 0 for BSP.
|
||||
*/
|
||||
UINT32
|
||||
GetCurrentProcessorNumber (
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Returns the physical address of the given virtual address.
|
||||
|
||||
@param[in] VirualAddress - A virtual address to retrieve its physical
|
||||
address for the current CR3. This must be non paged pool, otherwise the
|
||||
result is undefined.
|
||||
|
||||
@return The physical address of the given virtual address.
|
||||
*/
|
||||
UINT64
|
||||
GetPhysicalAddress (
|
||||
_In_ VOID* VirualAddress
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Returns the virtual address of the given physical address.
|
||||
|
||||
@param[in] PhysicalAddress - The physical address to retrieve its virtual
|
||||
address for the current CR3.
|
||||
|
||||
@return The virtual address of the given physical address.
|
||||
*/
|
||||
VOID*
|
||||
GetVirtualAddress (
|
||||
_In_ UINT64 PhysicalAddress
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Allocates page-aligned, zero-initialized physical memory resident pages.
|
||||
|
||||
@details Use of this API from the host is not allowed.
|
||||
|
||||
@param[in] PageCount - The page count to allocate.
|
||||
|
||||
@return The base of allocated pointer, or NULL on failure. The caller must
|
||||
free the return value with FreeSystemMemory().
|
||||
*/
|
||||
__drv_allocatesMem(Mem)
|
||||
_IRQL_requires_max_(DISPATCH_LEVEL)
|
||||
_Post_maybenull_
|
||||
_Post_writable_byte_size_(PageCount * PAGE_SIZE)
|
||||
_Must_inspect_result_
|
||||
VOID*
|
||||
AllocateSystemMemory (
|
||||
_In_ UINT64 PageCount
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Frees the memory allocated by AllocateSystemMemory().
|
||||
|
||||
@details Use of this API from the host is not allowed.
|
||||
|
||||
@param[in] Pages - The pointer to free.
|
||||
|
||||
@param[in] PageCount - Unused.
|
||||
*/
|
||||
_IRQL_requires_max_(DISPATCH_LEVEL)
|
||||
VOID
|
||||
FreeSystemMemory (
|
||||
_Pre_notnull_ __drv_freesMem(Mem) VOID* Pages,
|
||||
_In_ UINT64 PageCount
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Reserves the virtual address. The returned address is not accessible.
|
||||
|
||||
@details Use of this API from the host is not allowed.
|
||||
|
||||
@param[in] PageCount - The page count to reserve.
|
||||
|
||||
@return The address of reserved region on success or NULL. The caller must
|
||||
free this value with FreeReservedVirtualAddress().
|
||||
*/
|
||||
_IRQL_requires_max_(APC_LEVEL)
|
||||
_Must_inspect_result_
|
||||
VOID*
|
||||
ReserveVirtualAddress (
|
||||
_In_ UINT64 PageCount
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Frees the address reserved with ReserveVirtualAddress().
|
||||
|
||||
@details Use of this API from the host is not allowed.
|
||||
|
||||
@param[in] Pages - The pointer returned from ReserveVirtualAddress().
|
||||
|
||||
@param[in] PageCount - Unused.
|
||||
*/
|
||||
_IRQL_requires_max_(APC_LEVEL)
|
||||
VOID
|
||||
FreeReservedVirtualAddress (
|
||||
_In_ VOID* Pages,
|
||||
_In_ UINT64 PageCount
|
||||
);
|
||||
|
||||
typedef
|
||||
VOID
|
||||
USER_PASSIVE_CALLBACK (
|
||||
_Inout_ VOID* Context
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Executes the callback at the PASSIVE_LEVEL on each processor one by one.
|
||||
|
||||
@details Use of this API from the host is not allowed.
|
||||
|
||||
@param[in] Callback - The pointer to the function to execute.
|
||||
|
||||
@param[in,out] Context - The pointer to arbitrary context data.
|
||||
*/
|
||||
_IRQL_requires_max_(APC_LEVEL)
|
||||
VOID
|
||||
RunOnAllProcessors (
|
||||
_In_ USER_PASSIVE_CALLBACK* Callback,
|
||||
_Inout_ VOID* Context
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Initializes the spin lock.
|
||||
|
||||
@param[out] SpinLock - The pointer to spin lock to initialize.
|
||||
*/
|
||||
VOID
|
||||
InitializeSystemSpinLock (
|
||||
_Out_ SPIN_LOCK* SpinLock
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Acquires the spin lock.
|
||||
|
||||
@details The custom spin lock is used because NT provided spin lock API is
|
||||
not compatible with Driver Verifier when they are used from hypervisor.
|
||||
|
||||
@param[in,out] SpinLock - The pointer to the spin lock to acquire.
|
||||
|
||||
@return The opaque previous context.
|
||||
*/
|
||||
_Requires_lock_not_held_(*SpinLock)
|
||||
_Acquires_lock_(*SpinLock)
|
||||
_IRQL_requires_max_(HIGH_LEVEL)
|
||||
_IRQL_saves_
|
||||
_IRQL_raises_(DISPATCH_LEVEL)
|
||||
UINT8
|
||||
AcquireSystemSpinLock (
|
||||
_Inout_ SPIN_LOCK* SpinLock
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Release the spin lock and lowers IRQL if necessary.
|
||||
|
||||
@details The custom spin lock is used because NT provided spin lock API is
|
||||
not compatible with Driver Verifier when they are used from hypervisor.
|
||||
|
||||
@param[in,out] SpinLock - The spin lock to release.
|
||||
|
||||
@param[in] PreviousContext - The opaque previous context returned by the
|
||||
AcquireSpinLock function.
|
||||
*/
|
||||
_Requires_lock_held_(*SpinLock)
|
||||
_Releases_lock_(*SpinLock)
|
||||
_IRQL_requires_max_(HIGH_LEVEL)
|
||||
VOID
|
||||
ReleaseSystemSpinLock (
|
||||
_Inout_ SPIN_LOCK* SpinLock,
|
||||
_In_ _IRQL_restores_ UINT8 PreviousContext
|
||||
);
|
||||
105
Sources/Platform/EFI/EfiAsm.asm
Normal file
105
Sources/Platform/EFI/EfiAsm.asm
Normal file
@@ -0,0 +1,105 @@
|
||||
;
|
||||
; @file EfiAsm.asm
|
||||
;
|
||||
; @brief EFI specific MASM-written functions.
|
||||
;
|
||||
; @author Satoshi Tanda
|
||||
;
|
||||
; @copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
;
|
||||
include AsmCommon.inc
|
||||
.code
|
||||
|
||||
extern HandleHostException : proc
|
||||
|
||||
;
|
||||
; The index to track an interrupt number for generating AsmDefaultExceptionHandlers.
|
||||
;
|
||||
Index = 0
|
||||
|
||||
;
|
||||
; Generates the default exception handler code for the given interrupt/exception
|
||||
; number. The generated code assumes that the interrupt/exception does not push
|
||||
; error code.
|
||||
;
|
||||
; Index is incremented whenever this macro is used.
|
||||
;
|
||||
INTERRUPT_HANDLER macro InterruptNumber
|
||||
push 0 ; Push dummy error code for consistent stack layout.
|
||||
push InterruptNumber
|
||||
jmp AsmCommonExceptionHandler
|
||||
Index = Index + 1
|
||||
endm
|
||||
|
||||
;
|
||||
; Generates the default exception handler code for the given interrupt/exception
|
||||
; number. The generated code assumes that the interrupt/exception pushes error code.
|
||||
;
|
||||
; Index is incremented whenever this macro is used.
|
||||
;
|
||||
INTERRUPT_HANDLER_WITH_CODE macro InterruptNumber
|
||||
nop ; Error code is expected to be pushed by the processor.
|
||||
nop
|
||||
push InterruptNumber
|
||||
jmp AsmCommonExceptionHandler
|
||||
Index = Index + 1
|
||||
endm
|
||||
|
||||
;
|
||||
; @brief The default host exception handlers.
|
||||
;
|
||||
; @details This is the function containing actually 256 stub functions generated
|
||||
; with the INTERRUPT_HANDLER and INTERRUPT_HANDLER_WITH_CODE macros. Each function
|
||||
; works as a hendler of the corresponding interrupt/exception in the host.
|
||||
;
|
||||
AsmDefaultExceptionHandlers proc
|
||||
repeat 8
|
||||
INTERRUPT_HANDLER Index ; INT0-7
|
||||
endm
|
||||
|
||||
INTERRUPT_HANDLER_WITH_CODE Index ; INT8
|
||||
INTERRUPT_HANDLER Index ; INT9
|
||||
|
||||
repeat 5
|
||||
INTERRUPT_HANDLER_WITH_CODE Index ; INT10-14
|
||||
endm
|
||||
|
||||
repeat 2
|
||||
INTERRUPT_HANDLER Index ; INT15-16
|
||||
endm
|
||||
|
||||
INTERRUPT_HANDLER_WITH_CODE Index ; INT17
|
||||
|
||||
repeat 238
|
||||
INTERRUPT_HANDLER Index ; INT18-255
|
||||
endm
|
||||
AsmDefaultExceptionHandlers endp
|
||||
|
||||
;
|
||||
; @brief The common logic for the exception handlers.
|
||||
;
|
||||
; @details This function pushes register values into the stack and calls the
|
||||
; high-level handler written in C.
|
||||
;
|
||||
AsmCommonExceptionHandler proc
|
||||
PUSHAQ
|
||||
mov rcx, rsp
|
||||
sub rsp, 20h
|
||||
call HandleHostException
|
||||
add rsp, 20h
|
||||
POPAQ
|
||||
add rsp, 10h ; Remove the error code and interrupt number.
|
||||
iretq
|
||||
AsmCommonExceptionHandler endp
|
||||
|
||||
;
|
||||
; @brief The NMI handler for the host.
|
||||
;
|
||||
; @details This implementation is incomplete. When NMI occurs while the host is
|
||||
; executed, it should be injected to the guest.
|
||||
;
|
||||
AsmNmiExceptionHandler proc
|
||||
iretq
|
||||
AsmNmiExceptionHandler endp
|
||||
|
||||
end
|
||||
27
Sources/Platform/EFI/EfiAsm.h
Normal file
27
Sources/Platform/EFI/EfiAsm.h
Normal file
@@ -0,0 +1,27 @@
|
||||
/*!
|
||||
@file EfiAsm.h
|
||||
|
||||
@brief EFI specific MASM-written functions.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#pragma once
|
||||
#include "EfiCommon.h"
|
||||
|
||||
/*!
|
||||
@brief The array of the default host exception handlers.
|
||||
*/
|
||||
VOID
|
||||
AsmDefaultExceptionHandlers (
|
||||
VOID
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief The host NMI handler.
|
||||
*/
|
||||
VOID
|
||||
AsmNmiExceptionHandler (
|
||||
VOID
|
||||
);
|
||||
98
Sources/Platform/EFI/EfiBitmap.c
Normal file
98
Sources/Platform/EFI/EfiBitmap.c
Normal file
@@ -0,0 +1,98 @@
|
||||
/*!
|
||||
@file EfiBitmap.c
|
||||
|
||||
@brief EFI specific implementation of bitmap algorithm.
|
||||
|
||||
@details Implementation of algorithm is good enough for the current use of
|
||||
those API but is incomplete and broken, for example, bits are NEVER
|
||||
reused once they are set, even after they are "cleared".
|
||||
|
||||
For complete implementation, one can copy ReactOS's implementation if
|
||||
licensing the project under GPL is acceptable. hvpp by wbenny has its own
|
||||
implementation of bitmap but is actually influenced by ReactOS
|
||||
implementation and such should be treated as GPL.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#include "EfiBitmap.h"
|
||||
|
||||
VOID
|
||||
RtlInitializeBitMap (
|
||||
RTL_BITMAP* BitMapHeader,
|
||||
UINT32* BitMapBuffer,
|
||||
UINT32 SizeOfBitMap
|
||||
)
|
||||
{
|
||||
BitMapHeader->SizeOfBitMap = SizeOfBitMap;
|
||||
BitMapHeader->Buffer = BitMapBuffer;
|
||||
BitMapHeader->NextAvailableBitIndex = 0;
|
||||
BitMapHeader->SetBitCount = 0;
|
||||
}
|
||||
|
||||
UINT32
|
||||
RtlFindClearBitsAndSet (
|
||||
RTL_BITMAP* BitMapHeader,
|
||||
UINT32 NumberToFind,
|
||||
UINT32 HintIndex
|
||||
)
|
||||
{
|
||||
UINT32 clearBitIndex;
|
||||
|
||||
//
|
||||
// Return error if the bitmap does not have enough bits after the current
|
||||
// index. In other words, it never search from the index 0 because implementation
|
||||
// never clears bits.
|
||||
//
|
||||
if (BitMapHeader->NextAvailableBitIndex + NumberToFind > BitMapHeader->SizeOfBitMap)
|
||||
{
|
||||
clearBitIndex = MAXUINT32;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
//
|
||||
// "Find" clear bits, which is just using bits from the current position.
|
||||
//
|
||||
clearBitIndex = BitMapHeader->NextAvailableBitIndex;
|
||||
|
||||
//
|
||||
// "Set" requested bits, which is just moving the index further.
|
||||
//
|
||||
BitMapHeader->SetBitCount += NumberToFind;
|
||||
BitMapHeader->NextAvailableBitIndex += NumberToFind;
|
||||
|
||||
Exit:
|
||||
return clearBitIndex;
|
||||
}
|
||||
|
||||
BOOLEAN
|
||||
RtlAreBitsClear (
|
||||
RTL_BITMAP* BitMapHeader,
|
||||
UINT32 StartingIndex,
|
||||
UINT32 Length
|
||||
)
|
||||
{
|
||||
//
|
||||
// This implementation support checking only whether an entire bitmap is
|
||||
// cleared.
|
||||
//
|
||||
ASSERT(StartingIndex == 0);
|
||||
ASSERT(Length == BitMapHeader->SizeOfBitMap);
|
||||
|
||||
return (BitMapHeader->SetBitCount == 0);
|
||||
}
|
||||
|
||||
VOID
|
||||
RtlClearBits (
|
||||
RTL_BITMAP* BitMapHeader,
|
||||
UINT32 StartingIndex,
|
||||
UINT32 NumberToClear
|
||||
)
|
||||
{
|
||||
//
|
||||
// This implementation only change this counter, and never actually clear
|
||||
// bits and let them to be re-set.
|
||||
//
|
||||
BitMapHeader->SetBitCount -= NumberToClear;
|
||||
}
|
||||
96
Sources/Platform/EFI/EfiBitmap.h
Normal file
96
Sources/Platform/EFI/EfiBitmap.h
Normal file
@@ -0,0 +1,96 @@
|
||||
/*!
|
||||
@file EfiBitmap.h
|
||||
|
||||
@brief EFI specific implementation of bitmap algorithm.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#pragma once
|
||||
#include "EfiCommon.h"
|
||||
|
||||
typedef struct _RTL_BITMAP
|
||||
{
|
||||
UINT32 SizeOfBitMap; // Number of bits in bit map
|
||||
UINT32* Buffer; // Pointer to the bit map itself
|
||||
UINT32 NextAvailableBitIndex; // Index of the next cleared bit
|
||||
UINT32 SetBitCount; // Number of bits currently set
|
||||
} RTL_BITMAP;
|
||||
|
||||
/*!
|
||||
@brief Initializes the header of a bitmap variable.
|
||||
|
||||
@param[out] BitMapHeader - The pointer to the bitmap variable to initialize.
|
||||
|
||||
@param[in] BitMapBuffer - The pointer to caller-allocated memory for the bitmap
|
||||
itself.
|
||||
|
||||
@param[in] SizeOfBitMap - The number of bits in the bitmap.
|
||||
*/
|
||||
VOID
|
||||
RtlInitializeBitMap (
|
||||
RTL_BITMAP* BitMapHeader,
|
||||
UINT32* BitMapBuffer,
|
||||
UINT32 SizeOfBitMap
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Searches for a range of clear bits of a requested size within a bitmap
|
||||
and sets all bits in the range when it has been located.
|
||||
|
||||
@param[out] BitMapHeader - The pointer to the RTL_BITMAP structure that
|
||||
describes the bitmap.
|
||||
|
||||
@param[in] NumberToFind - How many contiguous clear bits will satisfy this
|
||||
request.
|
||||
|
||||
@param[in] HintIndex - Unused.
|
||||
|
||||
@return The zero-based starting bit index for a clear bit range of the
|
||||
requested size that it set, or it returns 0xFFFFFFFF if it cannot find
|
||||
such a range within the given bitmap variable.
|
||||
*/
|
||||
UINT32
|
||||
RtlFindClearBitsAndSet (
|
||||
RTL_BITMAP* BitMapHeader,
|
||||
UINT32 NumberToFind,
|
||||
UINT32 HintIndex
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Determines whether a given range of bits within a bitmap variable is
|
||||
clear.
|
||||
|
||||
@param[in] BitMapHeader - The pointer to the RTL_BITMAP structure that
|
||||
describes the bitmap.
|
||||
|
||||
@param[in] StartingIndex - The start of the bit range to be tested.
|
||||
|
||||
@param[in] Length - How many bits to test.
|
||||
|
||||
@return Whether a given range of bits within a bitmap variable is clear.
|
||||
*/
|
||||
BOOLEAN
|
||||
RtlAreBitsClear (
|
||||
RTL_BITMAP* BitMapHeader,
|
||||
UINT32 StartingIndex,
|
||||
UINT32 Length
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Sets all bits in the specified range of bits in the bitmap to zero.
|
||||
|
||||
@param[out] BitMapHeader - The pointer to the RTL_BITMAP structure that
|
||||
describes the bitmap.
|
||||
|
||||
@param[in] StartingIndex - Unused.
|
||||
|
||||
@param[in] NumberToClear - How many bits to clear.
|
||||
*/
|
||||
VOID
|
||||
RtlClearBits (
|
||||
RTL_BITMAP* BitMapHeader,
|
||||
UINT32 StartingIndex,
|
||||
UINT32 NumberToClear
|
||||
);
|
||||
135
Sources/Platform/EFI/EfiCommon.h
Normal file
135
Sources/Platform/EFI/EfiCommon.h
Normal file
@@ -0,0 +1,135 @@
|
||||
/*!
|
||||
@file EfiCommon.h
|
||||
|
||||
@brief EFI specific implementation of common things across the project.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#pragma once
|
||||
#include <Uefi.h>
|
||||
#include <Library/UefiLib.h>
|
||||
#include <Library/DebugLib.h>
|
||||
#include <Library/BaseMemoryLib.h>
|
||||
|
||||
//
|
||||
// "structure was padded due to alignment specifier"
|
||||
//
|
||||
#pragma warning(disable: 4324)
|
||||
|
||||
|
||||
/*!
|
||||
@brief Freezes execution of the processor by entering infinite busy loop.
|
||||
*/
|
||||
#define MV_PANIC() do { CpuDeadLoop(); } while (TRUE)
|
||||
#define MV_DEBUG_BREAK()
|
||||
#define MV_SECTION_INIT
|
||||
#define MV_SECTION_PAGED
|
||||
#define MV_ASSERT(x) ASSERT(x)
|
||||
#if !defined(MDEPKG_NDEBUG)
|
||||
#define MV_VERIFY(x) ASSERT(x)
|
||||
#else
|
||||
#define MV_VERIFY(x) (x)
|
||||
#endif
|
||||
#define MV_MAX(x, y) MAX((x), (y))
|
||||
#define MV_MIN(x, y) MIN((x), (y))
|
||||
|
||||
//
|
||||
// MSVC compatibility type definitions.
|
||||
//
|
||||
typedef CHAR8 CHAR;
|
||||
typedef CHAR16 WCHAR;
|
||||
|
||||
//
|
||||
// MSVC intrinsics.
|
||||
//
|
||||
unsigned __int64 __readcr0(void);
|
||||
unsigned __int64 __readcr2(void);
|
||||
unsigned __int64 __readcr3(void);
|
||||
unsigned __int64 __readcr4(void);
|
||||
unsigned __int64 __readdr(unsigned int);
|
||||
unsigned __int64 __readeflags(void);
|
||||
unsigned __int64 __readmsr(unsigned long);
|
||||
unsigned char __vmx_on(unsigned __int64 *);
|
||||
unsigned char __vmx_vmclear(unsigned __int64 *);
|
||||
unsigned char __vmx_vmlaunch(void);
|
||||
unsigned char __vmx_vmptrld(unsigned __int64 *);
|
||||
unsigned char __vmx_vmread(unsigned __int64, unsigned __int64 *);
|
||||
unsigned char __vmx_vmresume(void);
|
||||
unsigned char __vmx_vmwrite(unsigned __int64, unsigned __int64);
|
||||
unsigned long __segmentlimit(unsigned long);
|
||||
void __cpuid(int[4], int);
|
||||
void __cpuidex(int[4], int, int);
|
||||
void __invlpg(void *);
|
||||
void __lidt(void *);
|
||||
void __sidt(void *);
|
||||
void __stosq(unsigned __int64 *, unsigned __int64, unsigned __int64);
|
||||
void __vmx_off(void);
|
||||
void __vmx_vmptrst(unsigned __int64 *);
|
||||
void __writecr0(unsigned __int64);
|
||||
void __writecr2(unsigned __int64);
|
||||
void __writecr3(unsigned __int64);
|
||||
void __writecr4(unsigned __int64);
|
||||
void __writedr(unsigned int, unsigned __int64);
|
||||
void __writemsr(unsigned long, unsigned __int64);
|
||||
void _lgdt(void *);
|
||||
void _sgdt(void *);
|
||||
|
||||
//
|
||||
// MSVC compatibility macro definitions.
|
||||
//
|
||||
#define __drv_aliasesMem
|
||||
#define __drv_allocatesMem(x)
|
||||
#define __drv_freesMem(x)
|
||||
#define _Acquires_lock_(x)
|
||||
#define _In_
|
||||
#define _In_opt_
|
||||
#define _In_range_(x, y)
|
||||
#define _In_reads_bytes_(x)
|
||||
#define _Inout_
|
||||
#define _IRQL_raises_(x)
|
||||
#define _IRQL_requires_max_(x)
|
||||
#define _IRQL_restores_
|
||||
#define _IRQL_saves_
|
||||
#define _Must_inspect_result_
|
||||
#define _Out_
|
||||
#define _Out_opt_
|
||||
#define _Out_writes_bytes_(x)
|
||||
#define _Post_maybenull_
|
||||
#define _Post_writable_byte_size_(x)
|
||||
#define _Pre_notnull_
|
||||
#define _Printf_format_string_
|
||||
#define _Releases_lock_(x)
|
||||
#define _Requires_lock_held_(x)
|
||||
#define _Requires_lock_not_held_(x)
|
||||
#define _Return_type_success_(x)
|
||||
#define _Success_(x)
|
||||
#define _Use_decl_annotations_
|
||||
#define _When_(x, y)
|
||||
|
||||
#define ANSI_NULL ((CHAR)0)
|
||||
#define ANYSIZE_ARRAY (1)
|
||||
#define ARGUMENT_PRESENT(x) ((x) != NULL)
|
||||
#define BooleanFlagOn(F,SF) ((BOOLEAN)(((F) & (SF)) != 0))
|
||||
#define BYTES_TO_PAGES(x) EFI_SIZE_TO_PAGES(x)
|
||||
#define C_ASSERT(x) STATIC_ASSERT(x, #x)
|
||||
#define ClearFlag(_F,_SF) ((_F) &= ~(_SF))
|
||||
#define DBG_UNREFERENCED_PARAMETER(x)
|
||||
#define DECLSPEC_ALIGN(x) __declspec(align(x))
|
||||
#define FlagOn(_F,_SF) ((_F) & (_SF))
|
||||
#define KERNEL_STACK_SIZE (0x6000)
|
||||
#define MAXUINT16 MAX_UINT16
|
||||
#define MAXUINT32 MAX_UINT32
|
||||
#define MAXUINT64 MAX_UINT64
|
||||
#define MAXUINT8 MAX_UINT8
|
||||
#define NOTHING
|
||||
#define PAGE_ALIGN(Va) ((VOID*)((UINT64)(Va) & ~(PAGE_SIZE - 1)))
|
||||
#define PAGE_SIZE EFI_PAGE_SIZE
|
||||
#define PAGED_CODE()
|
||||
#define RTL_NUMBER_OF(x) ARRAY_SIZE(x)
|
||||
#define RtlCopyMemory CopyMem
|
||||
#define RtlZeroMemory ZeroMem
|
||||
#define SetFlag(_F,_SF) ((_F) |= (_SF))
|
||||
#define strcmp(x, y) AsciiStrCmp((x), (y))
|
||||
#define UNREFERENCED_PARAMETER(x)
|
||||
330
Sources/Platform/EFI/EfiHostInitialization.c
Normal file
330
Sources/Platform/EFI/EfiHostInitialization.c
Normal file
@@ -0,0 +1,330 @@
|
||||
/*!
|
||||
@file EfiHostInitialization.c
|
||||
|
||||
@brief EFI specific implementation of host environment initialization.
|
||||
|
||||
@details On EFI, the host uses its own paging structures (CR3) and interrupt
|
||||
descriptor table (IDT).
|
||||
|
||||
Its own paging structures is preferable and the most straightforward
|
||||
approach to void impact from the physical mode to the virtual mode
|
||||
transition happens during OS startup time. After this transition (ie,
|
||||
SetVirtualAddressMap is called from a boot loader), the paging structures
|
||||
that is used at the physical mode and the host would be using it becomes
|
||||
invalid, as nothing runs on the physical mode anymore. This results in
|
||||
crash (triple fault) when VM-exit occurs. One solution could be to
|
||||
subscribe the SetVirtualAddressMap event and notify the host to switch
|
||||
to the new CR3 for the virtual mode, but this has to be done for all
|
||||
logical processors requiring some inter processor calls. The MP protocol
|
||||
could do the job but is no longer available at the moment of the transition
|
||||
notification because the system is already switched from the boot time to
|
||||
the run time.
|
||||
|
||||
Its own interrupt descriptor table is required for the same reason. After
|
||||
transitioning to the virtual mode, the existing IDT becomes invalid. One
|
||||
might think the host IDT is not relevant as interrupts are disabled. The
|
||||
fact is that NMI still occurs while the host is running, and also, having
|
||||
basic diagnose handlers are useful in case of access violation, for example.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#include "EfiHostInitialization.h"
|
||||
#include "EfiAsm.h"
|
||||
#include "EfiPlatform.h"
|
||||
#include "../../Utils.h"
|
||||
|
||||
//
|
||||
// The format of the IDT.
|
||||
//
|
||||
typedef struct _INTERRUPT_GATE_DESCRIPTOR
|
||||
{
|
||||
UINT16 Offset15To0 : 16;
|
||||
UINT16 SegmentSelector : 16;
|
||||
UINT8 Reserved0;
|
||||
UINT8 GateType;
|
||||
UINT16 Offset31To16;
|
||||
UINT32 Offset63To32;
|
||||
UINT32 Reserved1;
|
||||
} INTERRUPT_GATE_DESCRIPTOR;
|
||||
C_ASSERT(sizeof(INTERRUPT_GATE_DESCRIPTOR) == 16);
|
||||
|
||||
//
|
||||
// Collections of paging structures. Only the single PDPT is accommodated to
|
||||
// handle only up to 512GB of physical memory.
|
||||
//
|
||||
typedef struct _PAGING_STRUCTURES
|
||||
{
|
||||
//
|
||||
// There is only one PML4, unless 5-level page mapping is enabled.
|
||||
//
|
||||
DECLSPEC_ALIGN(PAGE_SIZE) PML4E_64 Pml4[PML4_ENTRY_COUNT];
|
||||
|
||||
//
|
||||
// Only one PDPT is used for PML4[0]. This covers 512GB of the physical memory
|
||||
// range and is sufficient for our purpose.
|
||||
//
|
||||
DECLSPEC_ALIGN(PAGE_SIZE) PDPTE_64 Pdpt[1][PDPT_ENTRY_COUNT];
|
||||
|
||||
//
|
||||
// PDs are assigned for each PDPT entry, meaning that 512 (PDPTEs) multiplied
|
||||
// by the PDT entry count.
|
||||
//
|
||||
DECLSPEC_ALIGN(PAGE_SIZE) PDE_2MB_64 Pdt[1][PDPT_ENTRY_COUNT][PDT_ENTRY_COUNT];
|
||||
} PAGING_STRUCTURES;
|
||||
|
||||
//
|
||||
// Paging related.
|
||||
//
|
||||
static PAGING_STRUCTURES g_HostPagingStructures;
|
||||
static CR3 g_HostCr3;
|
||||
|
||||
//
|
||||
// IDT related.
|
||||
//
|
||||
static INTERRUPT_GATE_DESCRIPTOR g_HostIdt[IDT_ENTRY_COUNT];
|
||||
static IDTR g_HostIdtr;
|
||||
|
||||
/*!
|
||||
@brief Initializes the host paging structures.
|
||||
|
||||
@details This function fills out the statically allocated paging structures
|
||||
and builds the identity mapping. All translation is done with 2MB pages
|
||||
since not 4KB granularity configuration is not needed.
|
||||
|
||||
The identity mapping works because when the host is loaded, the EFI system
|
||||
also uses the identity mapping, meaning that it is essentially making a
|
||||
clone of existing paging structures.
|
||||
|
||||
Note that page protections are all writable and executable. One may drop
|
||||
the executable attribute for outside the range of the .text section of
|
||||
this module and drop writable for the same range to be W^X.
|
||||
*/
|
||||
static
|
||||
VOID
|
||||
InitializeHostPagingStructures (
|
||||
)
|
||||
{
|
||||
PML4E_64* pml4;
|
||||
PDPTE_64* pdpt;
|
||||
PDE_2MB_64* pdt;
|
||||
UINT32 pml4Index;
|
||||
|
||||
pml4Index = 0;
|
||||
pml4 = g_HostPagingStructures.Pml4;
|
||||
pdpt = g_HostPagingStructures.Pdpt[pml4Index];
|
||||
|
||||
//
|
||||
// Fill out PML4, PDPT, PDT.
|
||||
//
|
||||
pml4[0].Present = TRUE;
|
||||
pml4[0].Write = TRUE;
|
||||
pml4[0].PageFrameNumber = GetPhysicalAddress(pdpt) >> PAGE_SHIFT;
|
||||
|
||||
for (UINT32 pdptIndex = 0; pdptIndex < PDPT_ENTRY_COUNT; ++pdptIndex)
|
||||
{
|
||||
pdt = g_HostPagingStructures.Pdt[pml4Index][pdptIndex];
|
||||
|
||||
pdpt[pdptIndex].Present = TRUE;
|
||||
pdpt[pdptIndex].Write = TRUE;
|
||||
pdpt[pdptIndex].PageFrameNumber = GetPhysicalAddress(pdt) >> PAGE_SHIFT;
|
||||
|
||||
for (UINT32 pdIndex = 0; pdIndex < PDT_ENTRY_COUNT; ++pdIndex)
|
||||
{
|
||||
UINT64 physicalAddress;
|
||||
|
||||
physicalAddress = ComputeAddressFromIndexes(pml4Index,
|
||||
pdptIndex,
|
||||
pdIndex,
|
||||
0);
|
||||
pdt[pdIndex].Present = TRUE;
|
||||
pdt[pdIndex].Write = TRUE;
|
||||
pdt[pdIndex].LargePage = TRUE;
|
||||
pdt[pdIndex].PageFrameNumber = physicalAddress >> PAGE_SHIFT_2BM;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Then initialize the CR3 to point to the PML4.
|
||||
//
|
||||
g_HostCr3.AddressOfPageDirectory = GetPhysicalAddress(pml4) >> PAGE_SHIFT;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Initializes the host IDT.
|
||||
|
||||
@details This function fills out the IDT with AsmDefaultExceptionHandlers[N]
|
||||
where N is the interrupt number, updates IDT[2] with AsmNmiExceptionHandler,
|
||||
and initializes IDTR to point to the IDT.
|
||||
|
||||
AsmDefaultExceptionHandlers is the array of stub functions to transfer
|
||||
execution to the main common logic in AsmCommonExceptionHandler.
|
||||
*/
|
||||
static
|
||||
VOID
|
||||
InitializeHostIdt (
|
||||
)
|
||||
{
|
||||
UINT64 handlerBase;
|
||||
|
||||
//
|
||||
// Get the beginning of the AsmDefaultExceptionHandlers to index.
|
||||
//
|
||||
handlerBase = (UINT64)&AsmDefaultExceptionHandlers;
|
||||
|
||||
//
|
||||
// Fill out all IDT entries.
|
||||
//
|
||||
for (UINT32 i = 0; i < IDT_ENTRY_COUNT; ++i)
|
||||
{
|
||||
static const UINT64 sizeOfHandlerTill0x7f = 9;
|
||||
static const UINT64 sizeOfHandlerFrom0x80 = 12;
|
||||
UINT64 sizeOfHandler;
|
||||
UINT64 handlerAddress;
|
||||
|
||||
//
|
||||
// Compute the address of AsmDefaultExceptionHandlers[i]. Each stub
|
||||
// function is 9 bytes up to 0x7f, and 12 bytes after that.
|
||||
//
|
||||
if (i < 0x80)
|
||||
{
|
||||
sizeOfHandler = sizeOfHandlerTill0x7f;
|
||||
}
|
||||
else
|
||||
{
|
||||
sizeOfHandler = sizeOfHandlerFrom0x80;
|
||||
}
|
||||
handlerAddress = (handlerBase + i * sizeOfHandler);
|
||||
|
||||
//
|
||||
// Fill out the IDT entry. The type is 32-bit Interrupt gate: 0x8E
|
||||
// P=1, DPL=00b, S=0, type=1110b => type_attr=1000_1110b=0x8E)
|
||||
//
|
||||
g_HostIdt[i].Offset15To0 = (UINT16)handlerAddress;
|
||||
g_HostIdt[i].Offset31To16 = (UINT16)(handlerAddress >> 16);
|
||||
g_HostIdt[i].Offset63To32 = (UINT32)(handlerAddress >> 32);
|
||||
g_HostIdt[i].SegmentSelector = AsmReadCs();
|
||||
g_HostIdt[i].GateType = 0x8E;
|
||||
}
|
||||
|
||||
//
|
||||
// Interrupt 0x2 is NMI. This needs special handling.
|
||||
//
|
||||
g_HostIdt[2].Offset15To0 = (UINT16)((UINT64)&AsmNmiExceptionHandler);
|
||||
g_HostIdt[2].Offset31To16 = (UINT16)((UINT64)&AsmNmiExceptionHandler >> 16);
|
||||
g_HostIdt[2].Offset63To32 = (UINT32)((UINT64)&AsmNmiExceptionHandler >> 32);
|
||||
|
||||
//
|
||||
// Finally initialize the IDTR to point to the IDT.
|
||||
//
|
||||
g_HostIdtr.Limit = sizeof(g_HostIdt) - 1;
|
||||
g_HostIdtr.BaseAddress = (UINT64)(&g_HostIdt[0]);
|
||||
}
|
||||
|
||||
VOID
|
||||
InitializeHostEnvironment (
|
||||
)
|
||||
{
|
||||
InitializeHostPagingStructures();
|
||||
InitializeHostIdt();
|
||||
}
|
||||
|
||||
CR3
|
||||
GetHostCr3 (
|
||||
)
|
||||
{
|
||||
return g_HostCr3;
|
||||
}
|
||||
|
||||
CONST IDTR*
|
||||
GetHostIdtr (
|
||||
)
|
||||
{
|
||||
return &g_HostIdtr;
|
||||
}
|
||||
|
||||
VOID
|
||||
InitializeGdt (
|
||||
TASK_STATE_SEGMENT_64* NewTss,
|
||||
SEGMENT_DESCRIPTOR_64* NewGdt,
|
||||
UINT64 NewGdtSize,
|
||||
GDTR* OriginalGdtr
|
||||
)
|
||||
{
|
||||
GDTR newGdtr;
|
||||
SEGMENT_SELECTOR taskRegister;
|
||||
SEGMENT_DESCRIPTOR_64 tssDescriptor;
|
||||
SEGMENT_DESCRIPTOR_64* tssDescriptorInGdt;
|
||||
UINT64 tssAddress;
|
||||
SEGMENT_DESCRIPTOR_32* newGdt32;
|
||||
|
||||
//
|
||||
// Get the current GDTR.
|
||||
//
|
||||
_sgdt(&newGdtr);
|
||||
*OriginalGdtr = newGdtr;
|
||||
|
||||
//
|
||||
// Copy contents of the existing GDT to the new GDT in the processor context.
|
||||
//
|
||||
RtlCopyMemory(NewGdt, (VOID*)newGdtr.BaseAddress, newGdtr.Limit);
|
||||
|
||||
//
|
||||
// Set up TR pointing to the entry going to be added below in the GDT. Divide
|
||||
// by the size of SEGMENT_DESCRIPTOR_32 because the limit field is in bytes
|
||||
// while the index is index in the entry count.
|
||||
//
|
||||
taskRegister.Flags = 0;
|
||||
taskRegister.Index = (newGdtr.Limit + 1ull) / sizeof(SEGMENT_DESCRIPTOR_32);
|
||||
|
||||
//
|
||||
// Update the GDTR. Change the base to the new location and increase the
|
||||
// limit to add one more entry for TR. Make sure we have enough space
|
||||
// in the processor context to copy the contents of GDT.
|
||||
//
|
||||
newGdtr.BaseAddress = (UINT64)NewGdt;
|
||||
newGdtr.Limit += sizeof(SEGMENT_DESCRIPTOR_64);
|
||||
MV_ASSERT(newGdtr.Limit < NewGdtSize);
|
||||
|
||||
//
|
||||
// At this point, the TR points to uninitialized entry in the GDT. Set up
|
||||
// the Task State Segment Descriptor to be written to GDT.
|
||||
//
|
||||
tssAddress = (UINT64)NewTss;
|
||||
RtlZeroMemory(&tssDescriptor, sizeof(tssDescriptor));
|
||||
tssDescriptor.SegmentLimitLow = sizeof(*NewTss) - 1;
|
||||
tssDescriptor.BaseAddressLow = (tssAddress & MAXUINT16);
|
||||
tssDescriptor.BaseAddressMiddle = ((tssAddress >> 16) & MAXUINT8);
|
||||
tssDescriptor.BaseAddressHigh = ((tssAddress >> 24) & MAXUINT8);
|
||||
tssDescriptor.BaseAddressUpper = ((tssAddress >> 32) & MAXUINT32);
|
||||
tssDescriptor.Type = SEGMENT_DESCRIPTOR_TYPE_TSS_AVAILABLE;
|
||||
tssDescriptor.Present = TRUE;
|
||||
|
||||
//
|
||||
// Update the GDT by writing entry for TSS, which is pointed by the TR.
|
||||
//
|
||||
newGdt32 = (SEGMENT_DESCRIPTOR_32*)NewGdt;
|
||||
tssDescriptorInGdt = (SEGMENT_DESCRIPTOR_64*)(&newGdt32[taskRegister.Index]);
|
||||
*tssDescriptorInGdt = tssDescriptor;
|
||||
|
||||
//
|
||||
// Finally, update the GDTR and TR of the current processor. The VT-x
|
||||
// requires the guest task segment register to be configured correctly
|
||||
// and the UEFI platform typically does not (ie, TR being zero). Update TR
|
||||
// to point to the task segment just set up.
|
||||
//
|
||||
// See: 26.3.1.2 Checks on Guest Segment Registers
|
||||
//
|
||||
_lgdt(&newGdtr);
|
||||
AsmWriteTr(taskRegister.Flags);
|
||||
}
|
||||
|
||||
VOID
|
||||
CleanupGdt (
|
||||
CONST GDTR* OriginalGdtr
|
||||
)
|
||||
{
|
||||
MV_ASSERT(OriginalGdtr->BaseAddress != 0);
|
||||
_lgdt((VOID*)OriginalGdtr);
|
||||
}
|
||||
11
Sources/Platform/EFI/EfiHostInitialization.h
Normal file
11
Sources/Platform/EFI/EfiHostInitialization.h
Normal file
@@ -0,0 +1,11 @@
|
||||
/*!
|
||||
@file EfiHostInitialization.h
|
||||
|
||||
@brief EFI specific implementation of host environment initialization.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#pragma once
|
||||
#include "../../HostInitialization.h"
|
||||
151
Sources/Platform/EFI/EfiLogger.c
Normal file
151
Sources/Platform/EFI/EfiLogger.c
Normal file
@@ -0,0 +1,151 @@
|
||||
/*!
|
||||
@file EfiLogger.c
|
||||
|
||||
@brief EFI specific implementation of the logger.
|
||||
|
||||
@details Logging becomes no-op at the runtime when UefiDebugLibConOut is used,
|
||||
ie, -D DEBUG_ON_SERIAL_PORT is not set. See use of mPostEBS in
|
||||
edk2/MdePkg/Library/UefiDebugLibConOut/DebugLib.c for this behavior.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#include "EfiLogger.h"
|
||||
#include <Guid/EventGroup.h>
|
||||
#include <Library/UefiBootServicesTableLib.h>
|
||||
#include <Library/PrintLib.h>
|
||||
|
||||
//
|
||||
// The event handle for ExitBootServices event subscription.
|
||||
//
|
||||
static EFI_EVENT g_EfiExitBootServicesEvent;
|
||||
|
||||
//
|
||||
// FALSE during boot time. Once the system is transition to the run time, any
|
||||
// EFI API that depends on boot services directly or indirectly cannot be called.
|
||||
// The most significant implication is the console output cannot be used anymore.
|
||||
//
|
||||
static BOOLEAN g_AtRuntime;
|
||||
|
||||
/*!
|
||||
@brief Handles the ExitBootServices notification.
|
||||
|
||||
@details The solo purpose of this handler is to report the end of console
|
||||
debug output.
|
||||
|
||||
@param[in] Event - Unused.
|
||||
|
||||
@param[in] Context - Unused.
|
||||
*/
|
||||
static
|
||||
VOID
|
||||
EFIAPI
|
||||
ExitBootServicesHandler (
|
||||
EFI_EVENT Event,
|
||||
VOID* Context
|
||||
)
|
||||
{
|
||||
LOG_INFO("ExitBootServices was called. Ending console logging if used.");
|
||||
gBS->CloseEvent(g_EfiExitBootServicesEvent);
|
||||
g_AtRuntime = TRUE;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Registers ExitBootServices notification.
|
||||
|
||||
@return EFI_SUCCESS on success; otherwise, an appropriate error code.
|
||||
*/
|
||||
static
|
||||
EFI_STATUS
|
||||
RegisterNotification (
|
||||
)
|
||||
{
|
||||
EFI_STATUS status;
|
||||
|
||||
status = gBS->CreateEventEx(EVT_NOTIFY_SIGNAL,
|
||||
TPL_NOTIFY,
|
||||
ExitBootServicesHandler,
|
||||
NULL,
|
||||
&gEfiEventExitBootServicesGuid,
|
||||
&g_EfiExitBootServicesEvent);
|
||||
if (EFI_ERROR(status))
|
||||
{
|
||||
LOG_ERROR("CreateEventEx failed : %r", status);
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
Exit:
|
||||
return status;
|
||||
}
|
||||
|
||||
EFI_STATUS
|
||||
InitializeLogger (
|
||||
)
|
||||
{
|
||||
EFI_STATUS status;
|
||||
|
||||
status = RegisterNotification();
|
||||
if (EFI_ERROR(status))
|
||||
{
|
||||
LOG_ERROR("RegisterNotifications failed : %r", status);
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
Exit:
|
||||
return status;
|
||||
}
|
||||
|
||||
VOID
|
||||
CleanupLogger (
|
||||
)
|
||||
{
|
||||
if (g_AtRuntime == FALSE)
|
||||
{
|
||||
gBS->CloseEvent(g_EfiExitBootServicesEvent);
|
||||
}
|
||||
}
|
||||
|
||||
VOID
|
||||
LogMessage (
|
||||
LOG_LEVEL Level,
|
||||
CONST CHAR* FunctionName,
|
||||
CONST CHAR* Format,
|
||||
...
|
||||
)
|
||||
{
|
||||
//
|
||||
// Mapping from LOG_LEVEL to the EFI log level.
|
||||
//
|
||||
static CONST UINT64 debugLevelMapping[] =
|
||||
{
|
||||
0,
|
||||
DEBUG_ERROR,
|
||||
DEBUG_WARN,
|
||||
DEBUG_INFO,
|
||||
DEBUG_VERBOSE,
|
||||
};
|
||||
C_ASSERT(RTL_NUMBER_OF(debugLevelMapping) == LogLevelReserved);
|
||||
|
||||
VA_LIST args;
|
||||
CHAR8 message[400];
|
||||
|
||||
VA_START(args, Format);
|
||||
(VOID)AsciiVSPrint(message, sizeof(message), Format, args);
|
||||
VA_END(args);
|
||||
|
||||
DebugPrint(debugLevelMapping[Level], "%a: %a\n", FunctionName, message);
|
||||
}
|
||||
|
||||
VOID
|
||||
LogEarlyErrorMessage (
|
||||
CONST CHAR* Format,
|
||||
...
|
||||
)
|
||||
{
|
||||
VA_LIST args;
|
||||
|
||||
VA_START(args, Format);
|
||||
(VOID)DebugVPrint(DEBUG_ERROR, Format, args);
|
||||
VA_END(args);
|
||||
}
|
||||
26
Sources/Platform/EFI/EfiLogger.h
Normal file
26
Sources/Platform/EFI/EfiLogger.h
Normal file
@@ -0,0 +1,26 @@
|
||||
/*!
|
||||
@file EfiLogger.h
|
||||
|
||||
@brief EFI specific implementation of the logger.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#include "../../Logger.h"
|
||||
|
||||
/*!
|
||||
@brief Initializes the global logger.
|
||||
|
||||
@return EFI_SUCCESS on success; otherwise, an appropriate error code.
|
||||
*/
|
||||
EFI_STATUS
|
||||
InitializeLogger (
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Clean up the logger.
|
||||
*/
|
||||
VOID
|
||||
CleanupLogger (
|
||||
);
|
||||
398
Sources/Platform/EFI/EfiPlatform.c
Normal file
398
Sources/Platform/EFI/EfiPlatform.c
Normal file
@@ -0,0 +1,398 @@
|
||||
/*!
|
||||
@file EfiPlatform.c
|
||||
|
||||
@details Some of API in this module can be called from the host. See the
|
||||
description of each API.
|
||||
|
||||
@brief EFI specific platform API.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#include "EfiPlatform.h"
|
||||
#include <Guid/EventGroup.h>
|
||||
#include <Library/DevicePathLib.h>
|
||||
#include <Library/MemoryAllocationLib.h>
|
||||
#include <Library/UefiBootServicesTableLib.h>
|
||||
#include <Library/UefiRuntimeServicesTableLib.h>
|
||||
#include <Pi/PiDxeCis.h>
|
||||
#include <Protocol/MpService.h>
|
||||
#include <Protocol/LoadedImage.h>
|
||||
#include "EfiLogger.h"
|
||||
#include "../../MiniVisor.h"
|
||||
|
||||
//
|
||||
// Maps conversion between MV_STATUS and NTSTATUS.
|
||||
//
|
||||
typedef struct _STATUS_MAPPING
|
||||
{
|
||||
MV_STATUS MvStatus;
|
||||
EFI_STATUS EfiStatus;
|
||||
} STATUS_MAPPING;
|
||||
|
||||
static CONST STATUS_MAPPING k_StatusMapping[] =
|
||||
{
|
||||
{ MV_STATUS_SUCCESS, EFI_SUCCESS, },
|
||||
{ MV_STATUS_UNSUCCESSFUL, EFI_ABORTED, },
|
||||
{ MV_STATUS_ACCESS_DENIED, EFI_ACCESS_DENIED, },
|
||||
{ MV_STATUS_INSUFFICIENT_RESOURCES, EFI_OUT_OF_RESOURCES, },
|
||||
{ MV_STATUS_HV_OPERATION_FAILED, EFI_UNSUPPORTED, },
|
||||
};
|
||||
|
||||
//
|
||||
// The multi-processor protocol. Only available during the boot-time.
|
||||
//
|
||||
static EFI_MP_SERVICES_PROTOCOL* g_MpServices;
|
||||
|
||||
/*!
|
||||
@brief Converts MV_STATUS to EFI_STATUS.
|
||||
|
||||
@param[in] Status - The MV_STATUS to convert from.
|
||||
|
||||
@return The converted EFI_STATUS.
|
||||
*/
|
||||
static
|
||||
EFI_STATUS
|
||||
ConvertMvToEfiStatus (
|
||||
MV_STATUS Status
|
||||
)
|
||||
{
|
||||
for (UINT32 i = 0; i < RTL_NUMBER_OF(k_StatusMapping); ++i)
|
||||
{
|
||||
if (Status == k_StatusMapping[i].MvStatus)
|
||||
{
|
||||
return k_StatusMapping[i].EfiStatus;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Update the mapping when this assert hits.
|
||||
//
|
||||
MV_ASSERT(FALSE);
|
||||
return EFI_ABORTED;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Converts EFI_STATUS to MV_STATUS.
|
||||
|
||||
@param[in] Status - The EFI_STATUS to convert from.
|
||||
|
||||
@return The converted MV_STATUS.
|
||||
*/
|
||||
static
|
||||
MV_STATUS
|
||||
ConvertEfiToMvStatus (
|
||||
EFI_STATUS Status
|
||||
)
|
||||
{
|
||||
for (UINT32 i = 0; i < RTL_NUMBER_OF(k_StatusMapping); ++i)
|
||||
{
|
||||
if (Status == k_StatusMapping[i].EfiStatus)
|
||||
{
|
||||
return k_StatusMapping[i].MvStatus;
|
||||
}
|
||||
}
|
||||
return MV_STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Displays information about the current module.
|
||||
|
||||
@details Use of this API at the run-time is not allowed.
|
||||
|
||||
@return EFI_SUCCESS on success; otherwise, an appropriate error code.
|
||||
*/
|
||||
static
|
||||
EFI_STATUS
|
||||
PrintLoadedImageInformation (
|
||||
)
|
||||
{
|
||||
EFI_STATUS status;
|
||||
EFI_LOADED_IMAGE_PROTOCOL* loadedImageInfo;
|
||||
CHAR16* devicePath;
|
||||
|
||||
devicePath = NULL;
|
||||
|
||||
status = gBS->OpenProtocol(gImageHandle,
|
||||
&gEfiLoadedImageProtocolGuid,
|
||||
(VOID**)&loadedImageInfo,
|
||||
gImageHandle,
|
||||
NULL,
|
||||
EFI_OPEN_PROTOCOL_GET_PROTOCOL);
|
||||
if (EFI_ERROR(status))
|
||||
{
|
||||
LOG_ERROR("OpenProtocol failed : %r", status);
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
devicePath = ConvertDevicePathToText(loadedImageInfo->FilePath, TRUE, TRUE);
|
||||
if (devicePath == NULL)
|
||||
{
|
||||
LOG_ERROR("ConvertDevicePathToText failed");
|
||||
status = EFI_OUT_OF_RESOURCES;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
LOG_INFO("%s - %llx:%llx",
|
||||
devicePath,
|
||||
loadedImageInfo->ImageBase,
|
||||
MV_ADD2PTR(loadedImageInfo->ImageBase, loadedImageInfo->ImageSize));
|
||||
|
||||
Exit:
|
||||
if (devicePath != NULL)
|
||||
{
|
||||
FreePool(devicePath);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief The platform specific module entry point.
|
||||
|
||||
@param[in] ImageHandle - The handle of this module.
|
||||
|
||||
@param[in] SystemTable - The boot service table pointer.
|
||||
|
||||
@return EFI_SUCCESS on success; otherwise, an appropriate error code.
|
||||
*/
|
||||
EFI_STATUS
|
||||
EFIAPI
|
||||
DriverEntry (
|
||||
EFI_HANDLE ImageHandle,
|
||||
EFI_SYSTEM_TABLE* SystemTable
|
||||
)
|
||||
{
|
||||
ASSERT(ImageHandle == gImageHandle);
|
||||
ASSERT(SystemTable->BootServices == gBS);
|
||||
|
||||
return ConvertMvToEfiStatus(InitializeMiniVisor());
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief The platform specific module unload callback.
|
||||
|
||||
@param[in] ImageHandle - The handle of this module.
|
||||
|
||||
@return Always EFI_SUCCESS.
|
||||
*/
|
||||
EFI_STATUS
|
||||
EFIAPI
|
||||
DriverUnload (
|
||||
EFI_HANDLE ImageHandle
|
||||
)
|
||||
{
|
||||
CleanupMiniVisor();
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
|
||||
MV_STATUS
|
||||
InitializePlatform (
|
||||
)
|
||||
{
|
||||
EFI_STATUS status;
|
||||
BOOLEAN isLoggerInitialized;
|
||||
|
||||
status = InitializeLogger();
|
||||
if (EFI_ERROR(status))
|
||||
{
|
||||
LOG_EARLY_ERROR("InitializeLogger failed : %r", status);
|
||||
goto Exit;
|
||||
}
|
||||
isLoggerInitialized = TRUE;
|
||||
|
||||
PrintLoadedImageInformation();
|
||||
|
||||
//
|
||||
// Locate the protocol for multi-processor handling. UEFI on a Hyper-V VM
|
||||
// does not implement this and fails.
|
||||
//
|
||||
status = gBS->LocateProtocol(&gEfiMpServiceProtocolGuid,
|
||||
NULL,
|
||||
&g_MpServices);
|
||||
if (EFI_ERROR(status))
|
||||
{
|
||||
LOG_ERROR("LocateProtocol failed : %r", status);
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
Exit:
|
||||
if (EFI_ERROR(status))
|
||||
{
|
||||
if (isLoggerInitialized != FALSE)
|
||||
{
|
||||
CleanupLogger();
|
||||
}
|
||||
}
|
||||
return ConvertEfiToMvStatus(status);
|
||||
}
|
||||
|
||||
VOID
|
||||
CleanupPlatform (
|
||||
)
|
||||
{
|
||||
CleanupLogger();
|
||||
}
|
||||
|
||||
UINT32
|
||||
GetActiveProcessorCount (
|
||||
)
|
||||
{
|
||||
EFI_STATUS status;
|
||||
UINTN numberOfProcessors;
|
||||
UINTN numberOfEnabledProcessors;
|
||||
|
||||
status = g_MpServices->GetNumberOfProcessors(g_MpServices,
|
||||
&numberOfProcessors,
|
||||
&numberOfEnabledProcessors);
|
||||
if (EFI_ERROR(status))
|
||||
{
|
||||
MV_PANIC();
|
||||
}
|
||||
|
||||
return (UINT32)numberOfEnabledProcessors;
|
||||
}
|
||||
|
||||
UINT32
|
||||
GetCurrentProcessorNumber (
|
||||
)
|
||||
{
|
||||
EFI_STATUS status;
|
||||
UINTN processorNumber;
|
||||
|
||||
status = g_MpServices->WhoAmI(g_MpServices, &processorNumber);
|
||||
if (EFI_ERROR(status))
|
||||
{
|
||||
MV_PANIC();
|
||||
}
|
||||
|
||||
return (UINT32)processorNumber;
|
||||
}
|
||||
|
||||
UINT64
|
||||
GetPhysicalAddress (
|
||||
VOID* VirualAddress
|
||||
)
|
||||
{
|
||||
//
|
||||
// Assume the current CR3 uses the identity mapping. This is the case during
|
||||
// the boot time or execution of the host.
|
||||
//
|
||||
return (UINT64)VirualAddress;
|
||||
}
|
||||
|
||||
VOID*
|
||||
GetVirtualAddress (
|
||||
UINT64 PhysicalAddress
|
||||
)
|
||||
{
|
||||
//
|
||||
// This function assume the current CR3 uses the identity mapping. This is
|
||||
// the case during the boot time or execution of the host.
|
||||
//
|
||||
return (VOID*)PhysicalAddress;
|
||||
}
|
||||
|
||||
VOID*
|
||||
AllocateSystemMemory (
|
||||
UINT64 PageCount
|
||||
)
|
||||
{
|
||||
VOID* pages;
|
||||
|
||||
pages = AllocateRuntimePages(PageCount);
|
||||
if (pages == NULL)
|
||||
{
|
||||
goto Exit;
|
||||
}
|
||||
ZeroMem(pages, PageCount * EFI_PAGE_SIZE);
|
||||
|
||||
Exit:
|
||||
return pages;
|
||||
}
|
||||
|
||||
VOID
|
||||
FreeSystemMemory (
|
||||
VOID* Pages,
|
||||
UINT64 PageCount
|
||||
)
|
||||
{
|
||||
FreePages(Pages, PageCount);
|
||||
}
|
||||
|
||||
VOID*
|
||||
ReserveVirtualAddress (
|
||||
UINT64 PageCount
|
||||
)
|
||||
{
|
||||
return AllocateSystemMemory(PageCount);
|
||||
}
|
||||
|
||||
VOID
|
||||
FreeReservedVirtualAddress (
|
||||
VOID* BaseAddress,
|
||||
UINT64 PageCount
|
||||
)
|
||||
{
|
||||
FreeSystemMemory(BaseAddress, PageCount);
|
||||
}
|
||||
|
||||
VOID
|
||||
RunOnAllProcessors (
|
||||
USER_PASSIVE_CALLBACK* Callback,
|
||||
VOID* Context
|
||||
)
|
||||
{
|
||||
EFI_STATUS status;
|
||||
|
||||
Callback(Context);
|
||||
if (GetActiveProcessorCount() == 1)
|
||||
{
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
status = g_MpServices->StartupAllAPs(g_MpServices,
|
||||
Callback,
|
||||
TRUE,
|
||||
NULL,
|
||||
0,
|
||||
Context,
|
||||
NULL);
|
||||
if (EFI_ERROR(status))
|
||||
{
|
||||
MV_PANIC();
|
||||
}
|
||||
|
||||
Exit:
|
||||
return;
|
||||
}
|
||||
|
||||
VOID
|
||||
InitializeSystemSpinLock (
|
||||
SPIN_LOCK* SpinLock
|
||||
)
|
||||
{
|
||||
(VOID)InitializeSpinLock(SpinLock);
|
||||
}
|
||||
|
||||
UINT8
|
||||
AcquireSystemSpinLock (
|
||||
SPIN_LOCK* SpinLock
|
||||
)
|
||||
{
|
||||
//
|
||||
// This function does not raise TPL as it is not available at the run time.
|
||||
//
|
||||
(VOID)AcquireSpinLock(SpinLock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
VOID
|
||||
ReleaseSystemSpinLock (
|
||||
SPIN_LOCK* SpinLock,
|
||||
UINT8 PreviousContext
|
||||
)
|
||||
{
|
||||
ASSERT(PreviousContext == 0);
|
||||
(VOID)ReleaseSpinLock(SpinLock);
|
||||
}
|
||||
11
Sources/Platform/EFI/EfiPlatform.h
Normal file
11
Sources/Platform/EFI/EfiPlatform.h
Normal file
@@ -0,0 +1,11 @@
|
||||
/*!
|
||||
@file EfiPlatform.h
|
||||
|
||||
@brief EFI specific platform API.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#pragma once
|
||||
#include "../../Platform.h"
|
||||
102
Sources/Platform/Windows/WinAsm.asm
Normal file
102
Sources/Platform/Windows/WinAsm.asm
Normal file
@@ -0,0 +1,102 @@
|
||||
;
|
||||
; @file WinAsm.asm
|
||||
;
|
||||
; @brief Windows specific MASM-written functions.
|
||||
;
|
||||
; @author Satoshi Tanda
|
||||
;
|
||||
; @copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
;
|
||||
.code
|
||||
|
||||
;
|
||||
; @brief Read the value from LDTR.
|
||||
;
|
||||
; @return The value of LDTR.
|
||||
;
|
||||
AsmReadLdtr proc
|
||||
sldt ax
|
||||
ret
|
||||
AsmReadLdtr endp
|
||||
|
||||
;
|
||||
; @brief Read the value from TR.
|
||||
;
|
||||
; @return The value of TR.
|
||||
;
|
||||
AsmReadTr proc
|
||||
str ax
|
||||
ret
|
||||
AsmReadTr endp
|
||||
|
||||
;
|
||||
; @brief Read the value from ES.
|
||||
;
|
||||
; @return The value of ES.
|
||||
;
|
||||
AsmReadEs proc
|
||||
mov ax, es
|
||||
ret
|
||||
AsmReadEs endp
|
||||
|
||||
;
|
||||
; @brief Read the value from CS.
|
||||
;
|
||||
; @return The value of CS.
|
||||
;
|
||||
AsmReadCs proc
|
||||
mov ax, cs
|
||||
ret
|
||||
AsmReadCs endp
|
||||
|
||||
;
|
||||
; @brief Read the value from SS.
|
||||
;
|
||||
; @return The value of SS.
|
||||
;
|
||||
AsmReadSs proc
|
||||
mov ax, ss
|
||||
ret
|
||||
AsmReadSs endp
|
||||
|
||||
;
|
||||
; @brief Read the value from DS.
|
||||
;
|
||||
; @return The value of DS.
|
||||
;
|
||||
AsmReadDs proc
|
||||
mov ax, ds
|
||||
ret
|
||||
AsmReadDs endp
|
||||
|
||||
;
|
||||
; @brief Read the value from FS.
|
||||
;
|
||||
; @return The value of FS.
|
||||
;
|
||||
AsmReadFs proc
|
||||
mov ax, fs
|
||||
ret
|
||||
AsmReadFs endp
|
||||
|
||||
;
|
||||
; @brief Read the value from GS.
|
||||
;
|
||||
; @return The value of GS.
|
||||
;
|
||||
AsmReadGs proc
|
||||
mov ax, gs
|
||||
ret
|
||||
AsmReadGs endp
|
||||
|
||||
;
|
||||
; @brief Write the value to TR.
|
||||
;
|
||||
; @param[in] RCX - The new TR value to write.
|
||||
;
|
||||
AsmWriteTr proc
|
||||
ltr cx
|
||||
ret
|
||||
AsmWriteTr endp
|
||||
|
||||
end
|
||||
101
Sources/Platform/Windows/WinAsm.h
Normal file
101
Sources/Platform/Windows/WinAsm.h
Normal file
@@ -0,0 +1,101 @@
|
||||
/*!
|
||||
@file WinAsm.h
|
||||
|
||||
@brief Windows specific MASM-written functions.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#pragma once
|
||||
#include "WinCommon.h"
|
||||
|
||||
/*!
|
||||
@brief Reads the value of LDTR.
|
||||
|
||||
@return The value of LDTR.
|
||||
*/
|
||||
UINT16
|
||||
AsmReadLdtr (
|
||||
VOID
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Reads the value of TR.
|
||||
|
||||
@return The value of TR.
|
||||
*/
|
||||
UINT16
|
||||
AsmReadTr (
|
||||
VOID
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Reads the value of ES.
|
||||
|
||||
@return The value of ES.
|
||||
*/
|
||||
UINT16
|
||||
AsmReadEs (
|
||||
VOID
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Reads the value of CS.
|
||||
|
||||
@return The value of CS.
|
||||
*/
|
||||
UINT16
|
||||
AsmReadCs (
|
||||
VOID
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Reads the value of SS.
|
||||
|
||||
@return The value of SS.
|
||||
*/
|
||||
UINT16
|
||||
AsmReadSs (
|
||||
VOID
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Reads the value of DS.
|
||||
|
||||
@return The value of DS.
|
||||
*/
|
||||
UINT16
|
||||
AsmReadDs (
|
||||
VOID
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Reads the value of FS.
|
||||
|
||||
@return The value of FS.
|
||||
*/
|
||||
UINT16
|
||||
AsmReadFs (
|
||||
VOID
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Reads the value of GS.
|
||||
|
||||
@return The value of GS.
|
||||
*/
|
||||
UINT16
|
||||
AsmReadGs (
|
||||
VOID
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Writes the value to TR.
|
||||
|
||||
@param[in] TaskSelector - The value to write to TR.
|
||||
*/
|
||||
VOID
|
||||
AsmWriteTr (
|
||||
_In_ UINT16 TaskSelector
|
||||
);
|
||||
60
Sources/Platform/Windows/WinCommon.h
Normal file
60
Sources/Platform/Windows/WinCommon.h
Normal file
@@ -0,0 +1,60 @@
|
||||
/*!
|
||||
@file WinCommon.h
|
||||
|
||||
@brief Windows specific implementation of common things across the project.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#pragma once
|
||||
#include <intrin.h>
|
||||
#include <ntifs.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
//
|
||||
// "Error annotation: Must succeed pool allocations are forbidden. Allocation
|
||||
// failures cause a system crash."
|
||||
//
|
||||
#pragma warning(disable: __WARNING_ERROR)
|
||||
|
||||
/*!
|
||||
@brief Breaks into a debugger if present, and then triggers bug check.
|
||||
*/
|
||||
#define MV_PANIC() \
|
||||
MV_DEBUG_BREAK(); \
|
||||
__pragma(warning(push)) \
|
||||
__pragma(warning(disable: __WARNING_USE_OTHER_FUNCTION)) \
|
||||
KeBugCheckEx(MANUALLY_INITIATED_CRASH, 0, 0, 0, 0) \
|
||||
__pragma(warning(pop))
|
||||
|
||||
/*!
|
||||
@brief Breaks into a kernel debugger if present.
|
||||
|
||||
@details This macro is emits software breakpoint that only hits when a
|
||||
kernel debugger is present. This macro is useful because it does not
|
||||
change the current frame unlike the DbgBreakPoint function, and
|
||||
breakpoint by this macro can be overwritten with NOP without impacting
|
||||
other breakpoints.
|
||||
*/
|
||||
#define MV_DEBUG_BREAK() \
|
||||
if (KD_DEBUGGER_NOT_PRESENT) \
|
||||
{ \
|
||||
NOTHING; \
|
||||
} \
|
||||
else \
|
||||
{ \
|
||||
__debugbreak(); \
|
||||
} \
|
||||
(VOID*)(0)
|
||||
|
||||
//
|
||||
// The handy macros to specify in which section the code should be placed.
|
||||
//
|
||||
#define MV_SECTION_INIT __declspec(code_seg("INIT"))
|
||||
#define MV_SECTION_PAGED __declspec(code_seg("PAGE"))
|
||||
|
||||
#define MV_ASSERT(x) NT_ASSERT(x)
|
||||
#define MV_VERIFY(x) NT_VERIFY(x)
|
||||
#define MV_MAX(x, y) max((x), (y))
|
||||
#define MV_MIN(x, y) min((x), (y))
|
||||
67
Sources/Platform/Windows/WinHostInitialization.c
Normal file
67
Sources/Platform/Windows/WinHostInitialization.c
Normal file
@@ -0,0 +1,67 @@
|
||||
/*!
|
||||
@file WinHostInitialization.c
|
||||
|
||||
@brief Windows specific implementation of host environment initialization.
|
||||
|
||||
@details On Windows, no special set up is done because the host shares the
|
||||
System process CR3 and IDTR for ease of debugging, and other interactions
|
||||
with the guest as demanded.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#include "WinHostInitialization.h"
|
||||
|
||||
//
|
||||
// The host CR3 and IDTR on Windows are the same as that of the System process.
|
||||
// This allows the host to be debugged with Windbg.
|
||||
//
|
||||
static CR3 g_HostCr3;
|
||||
static IDTR g_HostIdtr;
|
||||
|
||||
VOID
|
||||
InitializeHostEnvironment (
|
||||
)
|
||||
{
|
||||
MV_ASSERT(PsGetCurrentProcess() == PsInitialSystemProcess);
|
||||
|
||||
g_HostCr3.Flags = __readcr3();
|
||||
__sidt(&g_HostIdtr);
|
||||
}
|
||||
|
||||
CR3
|
||||
GetHostCr3 (
|
||||
)
|
||||
{
|
||||
return g_HostCr3;
|
||||
}
|
||||
|
||||
CONST IDTR*
|
||||
GetHostIdtr (
|
||||
)
|
||||
{
|
||||
return &g_HostIdtr;
|
||||
}
|
||||
|
||||
VOID
|
||||
InitializeGdt (
|
||||
TASK_STATE_SEGMENT_64* NewTss,
|
||||
SEGMENT_DESCRIPTOR_64* NewGdt,
|
||||
UINT64 NewGdtSize,
|
||||
GDTR* OriginalGdtr
|
||||
)
|
||||
{
|
||||
UNREFERENCED_PARAMETER(NewTss);
|
||||
UNREFERENCED_PARAMETER(NewGdt);
|
||||
UNREFERENCED_PARAMETER(NewGdtSize);
|
||||
UNREFERENCED_PARAMETER(OriginalGdtr);
|
||||
}
|
||||
|
||||
VOID
|
||||
CleanupGdt (
|
||||
CONST GDTR* OriginalGdtr
|
||||
)
|
||||
{
|
||||
UNREFERENCED_PARAMETER(OriginalGdtr);
|
||||
}
|
||||
11
Sources/Platform/Windows/WinHostInitialization.h
Normal file
11
Sources/Platform/Windows/WinHostInitialization.h
Normal file
@@ -0,0 +1,11 @@
|
||||
/*!
|
||||
@file WinHostInitialization.h
|
||||
|
||||
@brief Windows specific implementation of host environment initialization.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#pragma once
|
||||
#include "../../Platform.h"
|
||||
977
Sources/Platform/Windows/WinLogger.c
Normal file
977
Sources/Platform/Windows/WinLogger.c
Normal file
@@ -0,0 +1,977 @@
|
||||
/*!
|
||||
@file WinLogger.c
|
||||
|
||||
@brief Windows specific implementation of the logger.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#include "WinLogger.h"
|
||||
#include "WinPlatform.h"
|
||||
|
||||
//
|
||||
// Tells the CRT not to use a inline version of CRT functions, which use
|
||||
// internal functions that lead to linker errors.
|
||||
//
|
||||
#define _NO_CRT_STDIO_INLINE
|
||||
|
||||
#define NTSTRSAFE_NO_CB_FUNCTIONS
|
||||
#include <ntstrsafe.h>
|
||||
#include <ntintsafe.h>
|
||||
|
||||
//
|
||||
// "Error annotation: Must succeed pool allocations are forbidden. Allocation
|
||||
// failures cause a system crash."
|
||||
//
|
||||
#pragma warning(disable: __WARNING_ERROR)
|
||||
|
||||
//
|
||||
// The pool tag for logging.
|
||||
//
|
||||
#define LOGGER_POOL_TAG ((ULONG)'rgoL')
|
||||
|
||||
NTKERNELAPI
|
||||
PCHAR
|
||||
NTAPI
|
||||
PsGetProcessImageFileName (
|
||||
_In_ PEPROCESS Process
|
||||
);
|
||||
|
||||
//
|
||||
// The maximum characters the DbgPrint family can handle at once.
|
||||
//
|
||||
#define LOGGER_MAX_DBGPRINT_LENGTH 512
|
||||
|
||||
//
|
||||
// The format of a single debug log message stored in DEBUG_LOG_BUFFER::LogEntries.
|
||||
//
|
||||
#include <pshpack1.h>
|
||||
typedef struct _DEBUG_LOG_ENTRY
|
||||
{
|
||||
//
|
||||
// The system time of when this message is seen in the debug print callback.
|
||||
//
|
||||
LARGE_INTEGER Timestamp;
|
||||
|
||||
//
|
||||
// The level of this message.
|
||||
//
|
||||
LOG_LEVEL Level;
|
||||
|
||||
//
|
||||
// The number of the processor which generated this message.
|
||||
//
|
||||
ULONG ProcessorNumber;
|
||||
|
||||
//
|
||||
// The process and thread IDs which generated this message.
|
||||
//
|
||||
CLIENT_ID ClientId;
|
||||
|
||||
//
|
||||
// The name of the process which generated this message.
|
||||
//
|
||||
CHAR ProcessName[16];
|
||||
|
||||
//
|
||||
// The name of the function where generated this message.
|
||||
//
|
||||
CHAR FunctionName[32];
|
||||
|
||||
//
|
||||
// The length of the message stored in LogMessage in characters.
|
||||
//
|
||||
USHORT LogMessageLength;
|
||||
|
||||
//
|
||||
// The debug log message, not including terminating null.
|
||||
//
|
||||
CHAR LogMessage[ANYSIZE_ARRAY];
|
||||
} DEBUG_LOG_ENTRY;
|
||||
#include <poppack.h>
|
||||
|
||||
//
|
||||
// The active and inactive buffer layout.
|
||||
//
|
||||
typedef struct _DEBUG_LOG_BUFFER
|
||||
{
|
||||
//
|
||||
// The pointer to the buffer storing the sequence of DEBUG_LOG_ENTRYs (it is
|
||||
// not a pointer to a single entry or an array of entries either).
|
||||
//
|
||||
DEBUG_LOG_ENTRY* LogEntries;
|
||||
|
||||
//
|
||||
// The offset to the address where the next DEBUG_LOG_ENTRY should be saved,
|
||||
// counted from LogEntries.
|
||||
//
|
||||
UINT64 NextLogOffset;
|
||||
|
||||
//
|
||||
// How many bytes are not saved into LogEntries due to lack of space.
|
||||
//
|
||||
SIZE_T OverflowedLogSize;
|
||||
} DEBUG_LOG_BUFFER;
|
||||
|
||||
//
|
||||
// The pair of log buffers used to save log messages in memory.
|
||||
//
|
||||
typedef struct _PAIRED_DEBUG_LOG_BUFFER
|
||||
{
|
||||
//
|
||||
// Indicates whether ActiveLogBuffer and InactiveLogBuffer are usable.
|
||||
//
|
||||
BOOLEAN BufferValid;
|
||||
|
||||
//
|
||||
// The lock must be held before accessing any other fields of this structure.
|
||||
//
|
||||
SPIN_LOCK ActiveLogBufferLock;
|
||||
|
||||
//
|
||||
// The size of ActiveLogBuffer and InactiveLogBuffer.
|
||||
//
|
||||
SIZE_T BufferSize;
|
||||
|
||||
//
|
||||
// The maximum size of overflow observed during use of this
|
||||
// PAIRED_DEBUG_LOG_BUFFER. Useful to know how much BufferSize should be
|
||||
// increased.
|
||||
//
|
||||
SIZE_T MaxOverflowedLogSize;
|
||||
|
||||
//
|
||||
// The pointers to two buffers: active and inactive. Active buffer is used
|
||||
// to save new messages as they comes in. Inactive buffer is buffer accessed
|
||||
// and cleared up by the flush buffer thread. The flush buffer thread switches
|
||||
// them before flushing so that duration lock is held remains minimum.
|
||||
//
|
||||
DEBUG_LOG_BUFFER* ActiveLogBuffer;
|
||||
DEBUG_LOG_BUFFER* InactiveLogBuffer;
|
||||
|
||||
//
|
||||
// Actual log buffers. Those are pointed by ActiveLogBuffer and
|
||||
// InactiveLogBuffer.
|
||||
//
|
||||
DEBUG_LOG_BUFFER LogBuffers[2];
|
||||
} PAIRED_DEBUG_LOG_BUFFER;
|
||||
|
||||
//
|
||||
// The logger instance.
|
||||
//
|
||||
typedef struct _LOGGER_CONTEXT
|
||||
{
|
||||
LOG_LEVEL Level; // See LOGGER_CONFIGURATION.
|
||||
LOGGER_CONFIGURATION_FLAGS Flags; // See LOGGER_CONFIGURATION.
|
||||
ULONG FlushIntervalInMs; // See LOGGER_CONFIGURATION.
|
||||
|
||||
//
|
||||
// The log file handle. NULL if a log file is not used.
|
||||
//
|
||||
HANDLE LogFileHandle;
|
||||
|
||||
//
|
||||
// The flush buffer thread.
|
||||
//
|
||||
PKTHREAD FlushBufferThread;
|
||||
|
||||
//
|
||||
// The event to tell the flush buffer thread to exit.
|
||||
//
|
||||
KEVENT ThreadExitEvent;
|
||||
|
||||
//
|
||||
// The log buffers.
|
||||
//
|
||||
PAIRED_DEBUG_LOG_BUFFER PairedLogBuffer;
|
||||
} LOGGER_CONTEXT;
|
||||
|
||||
//
|
||||
// The empty logger instance. Used when the logger is initialized with
|
||||
// LogLevelNone. This is never "allocated" and "freed".
|
||||
//
|
||||
static LOGGER_CONTEXT k_EmptyLogger = { LogLevelNone, };
|
||||
|
||||
//
|
||||
// The string representation of the log levels.
|
||||
//
|
||||
static CONST PCSTR k_LogLevelStrings[] =
|
||||
{
|
||||
"NON",
|
||||
"ERR",
|
||||
"WRN",
|
||||
"INF",
|
||||
"DBG",
|
||||
};
|
||||
|
||||
//
|
||||
// The global logger instance.
|
||||
//
|
||||
static LOGGER_CONTEXT* g_Logger;
|
||||
|
||||
/*!
|
||||
@brief Flushes all save log messages.
|
||||
|
||||
@param[in,out] Logger - The logger instance.
|
||||
*/
|
||||
static
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
VOID
|
||||
FlushDebugLogEntries (
|
||||
_Inout_ LOGGER_CONTEXT* Logger
|
||||
)
|
||||
{
|
||||
NTSTATUS status;
|
||||
KIRQL oldIrql;
|
||||
DEBUG_LOG_BUFFER* logBufferToFlush;
|
||||
IO_STATUS_BLOCK ioStatusBlock;
|
||||
|
||||
status = STATUS_SUCCESS;
|
||||
|
||||
//
|
||||
// Swap active and inactive buffer.
|
||||
//
|
||||
oldIrql = AcquireSystemSpinLock(&Logger->PairedLogBuffer.ActiveLogBufferLock);
|
||||
logBufferToFlush = Logger->PairedLogBuffer.ActiveLogBuffer;
|
||||
Logger->PairedLogBuffer.ActiveLogBuffer = Logger->PairedLogBuffer.InactiveLogBuffer;
|
||||
Logger->PairedLogBuffer.InactiveLogBuffer = logBufferToFlush;
|
||||
ReleaseSystemSpinLock(&Logger->PairedLogBuffer.ActiveLogBufferLock, oldIrql);
|
||||
MV_ASSERT(Logger->PairedLogBuffer.ActiveLogBuffer !=
|
||||
Logger->PairedLogBuffer.InactiveLogBuffer);
|
||||
|
||||
//
|
||||
// Iterate all saved debug log messages (if exist).
|
||||
//
|
||||
for (ULONG offset = 0; offset < logBufferToFlush->NextLogOffset; /**/)
|
||||
{
|
||||
DEBUG_LOG_ENTRY* logEntry;
|
||||
CHAR logMessage[LOGGER_MAX_DBGPRINT_LENGTH];
|
||||
CHAR logTimestamp[20];
|
||||
CHAR logLevel[5];
|
||||
CHAR logProcessorNumber[5];
|
||||
CHAR logPidTid[13];
|
||||
CHAR logProcessName[17];
|
||||
CHAR logFunctionName[34];
|
||||
ANSI_STRING tmpLogLine;
|
||||
TIME_FIELDS timeFields;
|
||||
LARGE_INTEGER localTime;
|
||||
ULONG logMessageLength;
|
||||
|
||||
logTimestamp[0] = ANSI_NULL;
|
||||
logLevel[0] = ANSI_NULL;
|
||||
logProcessorNumber[0] = ANSI_NULL;
|
||||
logPidTid[0] = ANSI_NULL;
|
||||
logProcessName[0] = ANSI_NULL;
|
||||
logFunctionName[0] = ANSI_NULL;
|
||||
|
||||
logEntry = (DEBUG_LOG_ENTRY*)MV_ADD2PTR(logBufferToFlush->LogEntries, offset);
|
||||
|
||||
//
|
||||
// Build a temporal ANSI_STRING to stringify a non-null terminated string.
|
||||
//
|
||||
tmpLogLine.Buffer = logEntry->LogMessage;
|
||||
tmpLogLine.Length = logEntry->LogMessageLength;
|
||||
tmpLogLine.MaximumLength = logEntry->LogMessageLength;
|
||||
|
||||
if (Logger->Flags.u.EnableTimestamp != FALSE)
|
||||
{
|
||||
//
|
||||
// Convert the time stamp to the local time in the human readable format.
|
||||
//
|
||||
ExSystemTimeToLocalTime(&logEntry->Timestamp, &localTime);
|
||||
RtlTimeToTimeFields(&localTime, &timeFields);
|
||||
status = RtlStringCchPrintfA(logTimestamp,
|
||||
RTL_NUMBER_OF(logTimestamp),
|
||||
"%02hd-%02hd %02hd:%02hd:%02hd.%03hd\t",
|
||||
timeFields.Month,
|
||||
timeFields.Day,
|
||||
timeFields.Hour,
|
||||
timeFields.Minute,
|
||||
timeFields.Second,
|
||||
timeFields.Milliseconds);
|
||||
if (NT_ERROR(status))
|
||||
{
|
||||
MV_ASSERT(FALSE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (Logger->Flags.u.EnableTimestamp != FALSE)
|
||||
{
|
||||
status = RtlStringCchPrintfA(logLevel,
|
||||
RTL_NUMBER_OF(logLevel),
|
||||
"%s\t",
|
||||
k_LogLevelStrings[logEntry->Level]);
|
||||
if (NT_ERROR(status))
|
||||
{
|
||||
MV_ASSERT(FALSE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (Logger->Flags.u.EnableProcessorNumber != FALSE)
|
||||
{
|
||||
status = RtlStringCchPrintfA(logProcessorNumber,
|
||||
RTL_NUMBER_OF(logProcessorNumber),
|
||||
"%lu\t",
|
||||
logEntry->ProcessorNumber);
|
||||
if (NT_ERROR(status))
|
||||
{
|
||||
MV_ASSERT(FALSE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (Logger->Flags.u.EnablePidTid != FALSE)
|
||||
{
|
||||
status = RtlStringCchPrintfA(logPidTid,
|
||||
RTL_NUMBER_OF(logPidTid),
|
||||
"%5lu\t%5lu\t",
|
||||
HandleToULong(logEntry->ClientId.UniqueProcess),
|
||||
HandleToULong(logEntry->ClientId.UniqueThread));
|
||||
if (NT_ERROR(status))
|
||||
{
|
||||
MV_ASSERT(FALSE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (Logger->Flags.u.EnableProcessName != FALSE)
|
||||
{
|
||||
status = RtlStringCchPrintfA(logProcessName,
|
||||
RTL_NUMBER_OF(logProcessName),
|
||||
"%-15s\t",
|
||||
logEntry->ProcessName);
|
||||
if (NT_ERROR(status))
|
||||
{
|
||||
MV_ASSERT(FALSE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (Logger->Flags.u.EnableFunctionName != FALSE)
|
||||
{
|
||||
status = RtlStringCchPrintfA(logFunctionName,
|
||||
RTL_NUMBER_OF(logFunctionName),
|
||||
"%-32s\t",
|
||||
logEntry->FunctionName);
|
||||
if (NT_ERROR(status))
|
||||
{
|
||||
MV_ASSERT(FALSE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
status = RtlStringCchPrintfA(logMessage,
|
||||
RTL_NUMBER_OF(logMessage),
|
||||
"%s%s%s%s%s%s%Z\r\n",
|
||||
logTimestamp,
|
||||
logLevel,
|
||||
logProcessorNumber,
|
||||
logPidTid,
|
||||
logProcessName,
|
||||
logFunctionName,
|
||||
&tmpLogLine);
|
||||
if (NT_ERROR(status))
|
||||
{
|
||||
//
|
||||
// This should not happen, but if it does, just discard all log
|
||||
// messages. The next attempt will very likely fail too.
|
||||
//
|
||||
MV_ASSERT(FALSE);
|
||||
break;
|
||||
}
|
||||
|
||||
logMessageLength = (ULONG)strlen(logMessage);
|
||||
|
||||
if (Logger->LogFileHandle != NULL)
|
||||
{
|
||||
status = ZwWriteFile(Logger->LogFileHandle,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
&ioStatusBlock,
|
||||
logMessage,
|
||||
logMessageLength,
|
||||
NULL,
|
||||
NULL);
|
||||
if (NT_ERROR(status))
|
||||
{
|
||||
//
|
||||
// This can happen when the system is shutting down and the file
|
||||
// system was already unmounted. Nothing we can do here.
|
||||
//
|
||||
NOTHING;
|
||||
}
|
||||
}
|
||||
|
||||
logMessage[logMessageLength - 2] = '\n';
|
||||
logMessage[logMessageLength - 1] = ANSI_NULL;
|
||||
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "%s", logMessage);
|
||||
|
||||
//
|
||||
// Compute the offset to the next entry by adding the size of the current
|
||||
// entry.
|
||||
//
|
||||
offset += RTL_SIZEOF_THROUGH_FIELD(DEBUG_LOG_ENTRY, LogMessageLength) +
|
||||
logEntry->LogMessageLength;
|
||||
}
|
||||
|
||||
//
|
||||
// If the debug log messages exist, and no error happened before, flush the
|
||||
// log file. This may fail if the file system is unmounted after the last
|
||||
// successful write..
|
||||
//
|
||||
if ((Logger->LogFileHandle != NULL) &&
|
||||
(logBufferToFlush->NextLogOffset != 0) &&
|
||||
NT_SUCCESS(status))
|
||||
{
|
||||
(VOID)ZwFlushBuffersFile(Logger->LogFileHandle, &ioStatusBlock);
|
||||
}
|
||||
|
||||
//
|
||||
// Update the maximum overflow size as necessary.
|
||||
//
|
||||
Logger->PairedLogBuffer.MaxOverflowedLogSize = max(
|
||||
Logger->PairedLogBuffer.MaxOverflowedLogSize,
|
||||
logBufferToFlush->OverflowedLogSize);
|
||||
|
||||
//
|
||||
// Finally, clear the previously active buffer.
|
||||
//
|
||||
logBufferToFlush->NextLogOffset = 0;
|
||||
logBufferToFlush->OverflowedLogSize = 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief The entry point of the flush buffer thread. Flushes logs at interval.
|
||||
|
||||
@param[in] StartContext - The logger instance.
|
||||
*/
|
||||
LOGGER_PAGED
|
||||
static
|
||||
_Function_class_(KSTART_ROUTINE)
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
VOID
|
||||
LogFlushThread (
|
||||
_In_ VOID* StartContext
|
||||
)
|
||||
{
|
||||
NTSTATUS status;
|
||||
LOGGER_CONTEXT* logger;
|
||||
LARGE_INTEGER interval;
|
||||
|
||||
PAGED_CODE()
|
||||
|
||||
logger = (LOGGER_CONTEXT*)StartContext;
|
||||
interval.QuadPart = -(10000ll * logger->FlushIntervalInMs);
|
||||
|
||||
do
|
||||
{
|
||||
//
|
||||
// Flush log buffer with interval, or exit when it is requested.
|
||||
//
|
||||
status = KeWaitForSingleObject(&logger->ThreadExitEvent,
|
||||
Executive,
|
||||
KernelMode,
|
||||
FALSE,
|
||||
&interval);
|
||||
FlushDebugLogEntries(logger);
|
||||
} while (status == STATUS_TIMEOUT);
|
||||
|
||||
//
|
||||
// It is probably a programming error if non STATUS_SUCCESS is returned. Let
|
||||
// us catch that.
|
||||
//
|
||||
MV_ASSERT(status == STATUS_SUCCESS);
|
||||
PsTerminateSystemThread(status);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Initializes paired log buffers.
|
||||
|
||||
@param[in] BufferSize - The size of each buffer to allocate.
|
||||
|
||||
@param[out] PairedLogBuffer - The pointer to the paired log buffers.
|
||||
|
||||
@return STATUS_SUCCESS on success; otherwise, an appropriate error code.
|
||||
*/
|
||||
LOGGER_INIT
|
||||
static
|
||||
_Must_inspect_result_
|
||||
NTSTATUS
|
||||
InitializePairedLogBuffer (
|
||||
_In_ SIZE_T BufferSize,
|
||||
_Out_ PAIRED_DEBUG_LOG_BUFFER* PairedLogBuffer
|
||||
)
|
||||
{
|
||||
NTSTATUS status;
|
||||
DEBUG_LOG_ENTRY* logEntries1;
|
||||
DEBUG_LOG_ENTRY* logEntries2;
|
||||
|
||||
RtlZeroMemory(PairedLogBuffer, sizeof(*PairedLogBuffer));
|
||||
|
||||
//
|
||||
// Create paired log buffer.
|
||||
//
|
||||
logEntries1 = ExAllocatePoolWithTag(NonPagedPool, BufferSize, LOGGER_POOL_TAG);
|
||||
logEntries2 = ExAllocatePoolWithTag(NonPagedPool, BufferSize, LOGGER_POOL_TAG);
|
||||
if ((logEntries1 == NULL) || (logEntries2 == NULL))
|
||||
{
|
||||
status = STATUS_INSUFFICIENT_RESOURCES;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
//
|
||||
// Initialize buffer variables, and mark the paired buffer as valid. This
|
||||
// lets the debug print callback use this paired buffer.
|
||||
//
|
||||
PairedLogBuffer->LogBuffers[0].LogEntries = logEntries1;
|
||||
PairedLogBuffer->LogBuffers[1].LogEntries = logEntries2;
|
||||
PairedLogBuffer->ActiveLogBuffer = &PairedLogBuffer->LogBuffers[0];
|
||||
PairedLogBuffer->InactiveLogBuffer = &PairedLogBuffer->LogBuffers[1];
|
||||
PairedLogBuffer->BufferSize = BufferSize;
|
||||
PairedLogBuffer->BufferValid = TRUE;
|
||||
|
||||
status = STATUS_SUCCESS;
|
||||
|
||||
Exit:
|
||||
if (NT_ERROR(status))
|
||||
{
|
||||
if (logEntries2 != NULL)
|
||||
{
|
||||
ExFreePoolWithTag(logEntries2, LOGGER_POOL_TAG);
|
||||
}
|
||||
if (logEntries1 != NULL)
|
||||
{
|
||||
ExFreePoolWithTag(logEntries1, LOGGER_POOL_TAG);
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Cleans up paired log buffers.
|
||||
|
||||
@param[in,out] PairedLogBuffer - The pointer to the paired log buffers to
|
||||
clean up.
|
||||
*/
|
||||
static
|
||||
VOID
|
||||
CleanupPairedLogBuffer (
|
||||
_Inout_ PAIRED_DEBUG_LOG_BUFFER* PairedLogBuffer
|
||||
)
|
||||
{
|
||||
ExFreePoolWithTag(PairedLogBuffer->ActiveLogBuffer->LogEntries, LOGGER_POOL_TAG);
|
||||
ExFreePoolWithTag(PairedLogBuffer->InactiveLogBuffer->LogEntries, LOGGER_POOL_TAG);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Initializes the global logger.
|
||||
|
||||
@param[in] Configuration - The configuration for initialization.
|
||||
|
||||
@return STATUS_SUCCESS on success; otherwise, an appropriate error code.
|
||||
*/
|
||||
LOGGER_INIT
|
||||
_Use_decl_annotations_
|
||||
NTSTATUS
|
||||
InitializeLogger (
|
||||
CONST LOGGER_CONFIGURATION* Configuration
|
||||
)
|
||||
{
|
||||
NTSTATUS status;
|
||||
LOGGER_CONTEXT* logger;
|
||||
HANDLE fileHandle;
|
||||
HANDLE threadHandle;
|
||||
|
||||
PAGED_CODE()
|
||||
|
||||
MV_ASSERT(g_Logger == NULL);
|
||||
|
||||
logger = NULL;
|
||||
fileHandle = NULL;
|
||||
|
||||
//
|
||||
// Return the empty logger without any initialization if LogLevelNone is
|
||||
// specified.
|
||||
//
|
||||
if (Configuration->Level == LogLevelNone)
|
||||
{
|
||||
g_Logger = &k_EmptyLogger;
|
||||
status = STATUS_SUCCESS;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
MV_ASSERT(Configuration->BufferSize != 0);
|
||||
|
||||
//
|
||||
// Open the log file handle if requested.
|
||||
//
|
||||
if (Configuration->FilePath != NULL)
|
||||
{
|
||||
UNICODE_STRING filePath;
|
||||
OBJECT_ATTRIBUTES objectAttributes;
|
||||
IO_STATUS_BLOCK ioStatusBlock;
|
||||
|
||||
RtlInitUnicodeString(&filePath, Configuration->FilePath);
|
||||
InitializeObjectAttributes(&objectAttributes,
|
||||
&filePath,
|
||||
OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE,
|
||||
NULL,
|
||||
NULL)
|
||||
status = ZwCreateFile(&fileHandle,
|
||||
FILE_APPEND_DATA | SYNCHRONIZE,
|
||||
&objectAttributes,
|
||||
&ioStatusBlock,
|
||||
NULL,
|
||||
FILE_ATTRIBUTE_NORMAL,
|
||||
FILE_SHARE_READ,
|
||||
FILE_OPEN_IF,
|
||||
FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE,
|
||||
NULL,
|
||||
0);
|
||||
if (NT_ERROR(status))
|
||||
{
|
||||
DbgPrintEx(DPFLTR_IHVDRIVER_ID,
|
||||
DPFLTR_ERROR_LEVEL,
|
||||
"ZwCreateFile failed : %08x\n",
|
||||
status);
|
||||
goto Exit;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Create the logger instance.
|
||||
//
|
||||
#pragma prefast(suppress: __WARNING_MEMORY_LEAK, "Ownership taken on success.")
|
||||
logger = ExAllocatePoolWithTag(NonPagedPool, sizeof(*logger), LOGGER_POOL_TAG);
|
||||
if (logger == NULL)
|
||||
{
|
||||
DbgPrintEx(DPFLTR_IHVDRIVER_ID,
|
||||
DPFLTR_ERROR_LEVEL,
|
||||
"Memory allocation failed : %Iu\n",
|
||||
sizeof(*logger));
|
||||
status = STATUS_INSUFFICIENT_RESOURCES;
|
||||
goto Exit;
|
||||
}
|
||||
RtlZeroMemory(logger, sizeof(*logger));
|
||||
|
||||
//
|
||||
// Initialize the created logger instance.
|
||||
//
|
||||
status = InitializePairedLogBuffer(Configuration->BufferSize,
|
||||
&logger->PairedLogBuffer);
|
||||
if (NT_ERROR(status))
|
||||
{
|
||||
DbgPrintEx(DPFLTR_IHVDRIVER_ID,
|
||||
DPFLTR_ERROR_LEVEL,
|
||||
"InitializePairedLogBuffer failed : %08x\n",
|
||||
status);
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
logger->Level = Configuration->Level;
|
||||
logger->Flags.AsUInt32 = Configuration->Flags.AsUInt32;
|
||||
logger->FlushIntervalInMs = Configuration->FlushIntervalInMs;
|
||||
logger->LogFileHandle = fileHandle;
|
||||
KeInitializeEvent(&logger->ThreadExitEvent, SynchronizationEvent, FALSE);
|
||||
|
||||
//
|
||||
// Create the log flush thread for this logger.
|
||||
//
|
||||
status = PsCreateSystemThread(&threadHandle,
|
||||
THREAD_ALL_ACCESS,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
LogFlushThread,
|
||||
logger);
|
||||
if (NT_ERROR(status))
|
||||
{
|
||||
DbgPrintEx(DPFLTR_IHVDRIVER_ID,
|
||||
DPFLTR_ERROR_LEVEL,
|
||||
"PsCreateSystemThread failed : %08x\n",
|
||||
status);
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
//
|
||||
// Get the created thread object. This code should not fail.
|
||||
//
|
||||
status = ObReferenceObjectByHandle(threadHandle,
|
||||
THREAD_ALL_ACCESS,
|
||||
*PsThreadType,
|
||||
KernelMode,
|
||||
(VOID**)&logger->FlushBufferThread,
|
||||
NULL);
|
||||
MV_ASSERT(NT_SUCCESS(status));
|
||||
MV_VERIFY(NT_SUCCESS(ZwClose(threadHandle)));
|
||||
|
||||
//
|
||||
// We are good. Return the handle.
|
||||
//
|
||||
g_Logger = logger;
|
||||
|
||||
|
||||
Exit:
|
||||
if (NT_ERROR(status))
|
||||
{
|
||||
if (fileHandle != NULL)
|
||||
{
|
||||
MV_VERIFY(ZwClose(fileHandle));
|
||||
}
|
||||
if (logger != NULL)
|
||||
{
|
||||
if (logger->PairedLogBuffer.BufferValid != FALSE)
|
||||
{
|
||||
CleanupPairedLogBuffer(&logger->PairedLogBuffer);
|
||||
}
|
||||
ExFreePoolWithTag(logger, LOGGER_POOL_TAG);
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Clean up the logger.
|
||||
*/
|
||||
LOGGER_PAGED
|
||||
_Use_decl_annotations_
|
||||
VOID
|
||||
CleanupLogger (
|
||||
)
|
||||
{
|
||||
NTSTATUS status;
|
||||
LOGGER_CONTEXT* logger;
|
||||
SIZE_T maxOverflowedLogSize;
|
||||
|
||||
PAGED_CODE()
|
||||
|
||||
MV_ASSERT(g_Logger != NULL);
|
||||
|
||||
logger = g_Logger;
|
||||
|
||||
//
|
||||
// No need to do anything if the logger is an empty logger.
|
||||
//
|
||||
if (logger == &k_EmptyLogger)
|
||||
{
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
//
|
||||
// Signal the event to exit the flush buffer thread, and wait for termination.
|
||||
//
|
||||
(VOID)KeSetEvent(&logger->ThreadExitEvent, IO_NO_INCREMENT, FALSE);
|
||||
status = KeWaitForSingleObject(logger->FlushBufferThread,
|
||||
Executive,
|
||||
KernelMode,
|
||||
FALSE,
|
||||
NULL);
|
||||
MV_ASSERT(status == STATUS_SUCCESS);
|
||||
ObDereferenceObject(logger->FlushBufferThread);
|
||||
|
||||
maxOverflowedLogSize = logger->PairedLogBuffer.MaxOverflowedLogSize;
|
||||
|
||||
//
|
||||
// No one should be touching the log file now. Close it.
|
||||
//
|
||||
if (logger->LogFileHandle != NULL)
|
||||
{
|
||||
MV_VERIFY(NT_SUCCESS(ZwClose(logger->LogFileHandle)));
|
||||
}
|
||||
|
||||
//
|
||||
// Free resources and the logger itself.
|
||||
//
|
||||
CleanupPairedLogBuffer(&logger->PairedLogBuffer);
|
||||
ExFreePoolWithTag(logger, LOGGER_POOL_TAG);
|
||||
|
||||
if (maxOverflowedLogSize != 0)
|
||||
{
|
||||
DbgPrintEx(DPFLTR_IHVDRIVER_ID,
|
||||
DPFLTR_ERROR_LEVEL,
|
||||
"Cleaning up the logger. Max overflowed logs during the"
|
||||
" session is %llu bytes.\n",
|
||||
maxOverflowedLogSize);
|
||||
}
|
||||
|
||||
Exit:
|
||||
g_Logger = NULL;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Buffers the debug-level message to the paired log buffer.
|
||||
|
||||
@param[in,out] Logger - The current logger instance.
|
||||
|
||||
@param[in] Level - The level of the message.
|
||||
|
||||
@param[in] FunctionName - The name of the function initiated this logging.
|
||||
|
||||
@param[in] LogMessage - The message to save.
|
||||
|
||||
@return STATUS_SUCCESS on success; otherwise, an appropriate error code.
|
||||
*/
|
||||
static
|
||||
_Must_inspect_result_
|
||||
NTSTATUS
|
||||
BufferLog (
|
||||
_Inout_ LOGGER_CONTEXT* Logger,
|
||||
_In_ LOG_LEVEL Level,
|
||||
_In_ PCSTR FunctionName,
|
||||
_In_ PCSTR LogMessage
|
||||
)
|
||||
{
|
||||
NTSTATUS status;
|
||||
USHORT logMessageLength;
|
||||
SIZE_T logEntrySize;
|
||||
BOOLEAN lockAcquired;
|
||||
DEBUG_LOG_ENTRY* logEntry;
|
||||
LARGE_INTEGER timestamp;
|
||||
KIRQL oldIrql;
|
||||
|
||||
KeQuerySystemTime(×tamp);
|
||||
|
||||
oldIrql = 0; // Suppress compiler false positive warning.
|
||||
lockAcquired = FALSE;
|
||||
|
||||
//
|
||||
// Get the length of the message in characters.
|
||||
//
|
||||
status = RtlSizeTToUShort(strlen(LogMessage), &logMessageLength);
|
||||
if (NT_ERROR(status))
|
||||
{
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
logEntrySize = RTL_SIZEOF_THROUGH_FIELD(DEBUG_LOG_ENTRY, LogMessageLength) +
|
||||
logMessageLength;
|
||||
|
||||
//
|
||||
// Acquire the lock to safely modify active buffer.
|
||||
//
|
||||
oldIrql = AcquireSystemSpinLock(&Logger->PairedLogBuffer.ActiveLogBufferLock);
|
||||
lockAcquired = TRUE;
|
||||
|
||||
//
|
||||
// Bail out if a concurrent thread invalidated buffer.
|
||||
//
|
||||
if (Logger->PairedLogBuffer.BufferValid == FALSE)
|
||||
{
|
||||
status = STATUS_TOO_LATE;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
//
|
||||
// If the remaining buffer is not large enough to save this message, count
|
||||
// up the overflowed size and bail out.
|
||||
//
|
||||
if (Logger->PairedLogBuffer.ActiveLogBuffer->NextLogOffset + logEntrySize >
|
||||
Logger->PairedLogBuffer.BufferSize)
|
||||
{
|
||||
Logger->PairedLogBuffer.ActiveLogBuffer->OverflowedLogSize += logEntrySize;
|
||||
status = STATUS_BUFFER_TOO_SMALL;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
//
|
||||
// There are sufficient room to save the message. Get the address to save
|
||||
// the message within active buffer. On debug build, the address should be
|
||||
// filled with 0xff, indicating no one has yet touched there.
|
||||
//
|
||||
logEntry = (DEBUG_LOG_ENTRY*)MV_ADD2PTR(
|
||||
Logger->PairedLogBuffer.ActiveLogBuffer->LogEntries,
|
||||
Logger->PairedLogBuffer.ActiveLogBuffer->NextLogOffset);
|
||||
|
||||
//
|
||||
// Save this message and update the offset to the address to save the next
|
||||
// message.
|
||||
//
|
||||
logEntry->Timestamp = timestamp;
|
||||
logEntry->Level = Level;
|
||||
logEntry->ProcessorNumber = KeGetCurrentProcessorNumberEx(NULL);
|
||||
logEntry->ClientId.UniqueProcess = PsGetCurrentProcessId();
|
||||
logEntry->ClientId.UniqueThread = PsGetCurrentThreadId();
|
||||
(VOID)RtlStringCchCopyA(logEntry->ProcessName,
|
||||
RTL_NUMBER_OF_FIELD(DEBUG_LOG_ENTRY, ProcessName),
|
||||
PsGetProcessImageFileName(PsGetCurrentProcess()));
|
||||
(VOID)RtlStringCchCopyA(logEntry->FunctionName,
|
||||
RTL_NUMBER_OF_FIELD(DEBUG_LOG_ENTRY, FunctionName),
|
||||
FunctionName);
|
||||
logEntry->LogMessageLength = logMessageLength;
|
||||
RtlCopyMemory(logEntry->LogMessage, LogMessage, logMessageLength);
|
||||
Logger->PairedLogBuffer.ActiveLogBuffer->NextLogOffset += logEntrySize;
|
||||
|
||||
status = STATUS_SUCCESS;
|
||||
|
||||
Exit:
|
||||
if (lockAcquired != FALSE)
|
||||
{
|
||||
ReleaseSystemSpinLock(&Logger->PairedLogBuffer.ActiveLogBufferLock, oldIrql);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
VOID
|
||||
LogMessage (
|
||||
LOG_LEVEL Level,
|
||||
CONST CHAR* FunctionName,
|
||||
CONST CHAR* Format,
|
||||
...
|
||||
)
|
||||
{
|
||||
NTSTATUS status;
|
||||
LOGGER_CONTEXT* logger;
|
||||
va_list args;
|
||||
CHAR logMessage[400];
|
||||
|
||||
MV_ASSERT(Level != LogLevelNone);
|
||||
|
||||
logger = g_Logger;
|
||||
|
||||
//
|
||||
// Skip if the log is more verbose than the requested level.
|
||||
//
|
||||
if (logger->Level < Level)
|
||||
{
|
||||
status = STATUS_SUCCESS;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
//
|
||||
// Build a log message string and buffer it.
|
||||
//
|
||||
va_start(args, Format);
|
||||
status = RtlStringCchVPrintfA(logMessage,
|
||||
RTL_NUMBER_OF(logMessage),
|
||||
Format,
|
||||
args);
|
||||
va_end(args);
|
||||
if (NT_ERROR(status))
|
||||
{
|
||||
MV_ASSERT(FALSE);
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
status = BufferLog(logger, Level, FunctionName, logMessage);
|
||||
if (NT_ERROR(status))
|
||||
{
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
Exit:
|
||||
return;
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
VOID
|
||||
LogEarlyErrorMessage (
|
||||
CONST CHAR* Format,
|
||||
...
|
||||
)
|
||||
{
|
||||
va_list args;
|
||||
|
||||
va_start(args, Format);
|
||||
(VOID)vDbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, Format, args);
|
||||
va_end(args);
|
||||
}
|
||||
82
Sources/Platform/Windows/WinLogger.h
Normal file
82
Sources/Platform/Windows/WinLogger.h
Normal file
@@ -0,0 +1,82 @@
|
||||
/*!
|
||||
@file WinLogger.h
|
||||
|
||||
@brief Windows specific implementation of the logger.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#pragma once
|
||||
#include "../../Logger.h"
|
||||
|
||||
//
|
||||
// The handy macros to specify in which section the code should be placed.
|
||||
//
|
||||
#define LOGGER_INIT __declspec(code_seg("INIT"))
|
||||
#define LOGGER_PAGED __declspec(code_seg("PAGE"))
|
||||
|
||||
//
|
||||
// Extended configuration flags.
|
||||
//
|
||||
typedef union _LOGGER_CONFIGURATION_FLAGS
|
||||
{
|
||||
struct
|
||||
{
|
||||
UINT32 EnableTimestamp : 1;
|
||||
UINT32 EnableLevel : 1;
|
||||
UINT32 EnableProcessorNumber : 1;
|
||||
UINT32 EnablePidTid : 1;
|
||||
UINT32 EnableProcessName : 1;
|
||||
UINT32 EnableFunctionName : 1;
|
||||
} u;
|
||||
|
||||
UINT32 AsUInt32;
|
||||
} LOGGER_CONFIGURATION_FLAGS;
|
||||
|
||||
//
|
||||
// The configurations of the logger to initialize.
|
||||
//
|
||||
typedef struct _LOGGER_CONFIGURATION
|
||||
{
|
||||
//
|
||||
// The maximum level of the log this logger will log. For example, the
|
||||
// information-level logs are discarded when LogLevelWarning is specified.
|
||||
// If LogLevelNone is set, the logger is disabled and none of logs are logged.
|
||||
//
|
||||
LOG_LEVEL Level;
|
||||
|
||||
//
|
||||
// Extended configuration flags.
|
||||
//
|
||||
LOGGER_CONFIGURATION_FLAGS Flags;
|
||||
|
||||
//
|
||||
// An interval to flush logs saved into log message buffer.
|
||||
//
|
||||
UINT32 FlushIntervalInMs;
|
||||
|
||||
//
|
||||
// A size of log message buffer. The logger internally allocates two buffers
|
||||
// with this size.
|
||||
//
|
||||
SIZE_T BufferSize;
|
||||
|
||||
//
|
||||
// The path to the file to save logs. The logger do not save logs into a file
|
||||
// when NULL is specified.
|
||||
//
|
||||
PCWSTR FilePath;
|
||||
} LOGGER_CONFIGURATION;
|
||||
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
_Must_inspect_result_
|
||||
NTSTATUS
|
||||
InitializeLogger (
|
||||
_In_ CONST LOGGER_CONFIGURATION* Configuration
|
||||
);
|
||||
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
VOID
|
||||
CleanupLogger (
|
||||
);
|
||||
437
Sources/Platform/Windows/WinPlatform.c
Normal file
437
Sources/Platform/Windows/WinPlatform.c
Normal file
@@ -0,0 +1,437 @@
|
||||
/*!
|
||||
@file WinPlatform.c
|
||||
|
||||
@brief Windows specific platform API.
|
||||
|
||||
@details Some of API in this module can be called from the host. See the
|
||||
description of each API.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#include "WinPlatform.h"
|
||||
#include "WinLogger.h"
|
||||
#include "../../MiniVisor.h"
|
||||
|
||||
MV_SECTION_INIT DRIVER_INITIALIZE DriverEntry;
|
||||
|
||||
MV_SECTION_PAGED static DRIVER_UNLOAD DriverUnload;
|
||||
|
||||
//
|
||||
// The pool tag value used across the project.
|
||||
//
|
||||
#define MV_POOL_TAG ((UINT32)'vniM')
|
||||
|
||||
//
|
||||
// Maps conversion between MV_STATUS and NTSTATUS.
|
||||
//
|
||||
typedef struct _STATUS_MAPPING
|
||||
{
|
||||
MV_STATUS MvStatus;
|
||||
NTSTATUS NtStatus;
|
||||
} STATUS_MAPPING;
|
||||
|
||||
static CONST STATUS_MAPPING k_StatusMapping[] =
|
||||
{
|
||||
{ MV_STATUS_SUCCESS, STATUS_SUCCESS, },
|
||||
{ MV_STATUS_UNSUCCESSFUL, STATUS_UNSUCCESSFUL, },
|
||||
{ MV_STATUS_ACCESS_DENIED, STATUS_ACCESS_DENIED, },
|
||||
{ MV_STATUS_INSUFFICIENT_RESOURCES, STATUS_INSUFFICIENT_RESOURCES, },
|
||||
{ MV_STATUS_HV_OPERATION_FAILED, STATUS_HV_OPERATION_FAILED, },
|
||||
};
|
||||
|
||||
/*!
|
||||
@brief Converts MV_STATUS to NTSTATUS.
|
||||
|
||||
@param[in] Status - The MV_STATUS to convert from.
|
||||
|
||||
@return The converted NTSTATUS.
|
||||
*/
|
||||
static
|
||||
NTSTATUS
|
||||
ConvertMvToNtStatus (
|
||||
_In_ MV_STATUS Status
|
||||
)
|
||||
{
|
||||
for (UINT32 i = 0; i < RTL_NUMBER_OF(k_StatusMapping); ++i)
|
||||
{
|
||||
if (Status == k_StatusMapping[i].MvStatus)
|
||||
{
|
||||
return k_StatusMapping[i].NtStatus;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Update the mapping when this assert hits.
|
||||
//
|
||||
MV_ASSERT(FALSE);
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Converts NTSTATUS to MV_STATUS.
|
||||
|
||||
@param[in] Status - The NTSTATUS to convert from.
|
||||
|
||||
@return The converted MV_STATUS.
|
||||
*/
|
||||
static
|
||||
MV_STATUS
|
||||
ConvertNtToMvStatus (
|
||||
_In_ NTSTATUS Status
|
||||
)
|
||||
{
|
||||
for (UINT32 i = 0; i < RTL_NUMBER_OF(k_StatusMapping); ++i)
|
||||
{
|
||||
if (Status == k_StatusMapping[i].NtStatus)
|
||||
{
|
||||
return k_StatusMapping[i].MvStatus;
|
||||
}
|
||||
}
|
||||
return MV_STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief The platform specific module entry point.
|
||||
|
||||
@param[in] DriverObject - The driver's driver object.
|
||||
|
||||
@param[in] RegistryPath - A path to the driver's registry key.
|
||||
|
||||
@return STATUS_SUCCESS on success; otherwise, an appropriate error code.
|
||||
*/
|
||||
MV_SECTION_INIT
|
||||
_Use_decl_annotations_
|
||||
NTSTATUS
|
||||
DriverEntry (
|
||||
PDRIVER_OBJECT DriverObject,
|
||||
PUNICODE_STRING RegistryPath
|
||||
)
|
||||
{
|
||||
UNREFERENCED_PARAMETER(RegistryPath);
|
||||
|
||||
DriverObject->DriverUnload = DriverUnload;
|
||||
|
||||
//
|
||||
// Opts-in no-execute (NX) non-paged pool for security when available.
|
||||
//
|
||||
// By defining POOL_NX_OPTIN as 1 and calling this function, non-paged pool
|
||||
// allocation by the ExAllocatePool family with the NonPagedPool flag
|
||||
// automatically allocates NX non-paged pool on Windows 8 and later versions
|
||||
// of Windows, while on Windows 7 where NX non-paged pool is unsupported,
|
||||
// executable non-paged pool is returned as usual. The merit of this is that
|
||||
// the NonPagedPoolNx flag does not have to be used. Since the flag is
|
||||
// unsupported on Windows 7, being able to stick with the NonPagedPool flag
|
||||
// help keep code concise.
|
||||
//
|
||||
ExInitializeDriverRuntime(DrvRtPoolNxOptIn);
|
||||
|
||||
//
|
||||
// Start cross-platform initialization.
|
||||
//
|
||||
return ConvertMvToNtStatus(InitializeMiniVisor());
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief The platform specific module unload callback.
|
||||
|
||||
@param[in] DriverObject - The driver's driver object.
|
||||
*/
|
||||
MV_SECTION_PAGED
|
||||
_Use_decl_annotations_
|
||||
static
|
||||
VOID
|
||||
DriverUnload (
|
||||
PDRIVER_OBJECT DriverObject
|
||||
)
|
||||
{
|
||||
UNREFERENCED_PARAMETER(DriverObject);
|
||||
|
||||
PAGED_CODE()
|
||||
|
||||
//
|
||||
// Start cross-platform clean up.
|
||||
//
|
||||
CleanupMiniVisor();
|
||||
}
|
||||
|
||||
MV_SECTION_INIT
|
||||
_Use_decl_annotations_
|
||||
MV_STATUS
|
||||
InitializePlatform (
|
||||
)
|
||||
{
|
||||
NTSTATUS status;
|
||||
LOGGER_CONFIGURATION loggerConfig;
|
||||
|
||||
PAGED_CODE()
|
||||
|
||||
//
|
||||
// Initialize in-house logger. Enable all flags.
|
||||
//
|
||||
loggerConfig.Level = LogLevelDebug;
|
||||
loggerConfig.Flags.AsUInt32 = MAXUINT32;
|
||||
loggerConfig.FlushIntervalInMs = 500;
|
||||
loggerConfig.BufferSize = (SIZE_T)(32 * PAGE_SIZE) * GetActiveProcessorCount();
|
||||
loggerConfig.FilePath = L"\\SystemRoot\\Minivisor.log";
|
||||
status = InitializeLogger(&loggerConfig);
|
||||
if (NT_ERROR(status))
|
||||
{
|
||||
LOG_EARLY_ERROR("InitializeLogger failed : %08x", status);
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
Exit:
|
||||
return ConvertNtToMvStatus(status);
|
||||
}
|
||||
|
||||
MV_SECTION_PAGED
|
||||
_Use_decl_annotations_
|
||||
VOID
|
||||
CleanupPlatform (
|
||||
)
|
||||
{
|
||||
PAGED_CODE()
|
||||
|
||||
CleanupLogger();
|
||||
}
|
||||
|
||||
MV_SECTION_PAGED
|
||||
_Use_decl_annotations_
|
||||
VOID
|
||||
Sleep (
|
||||
UINT64 Milliseconds
|
||||
)
|
||||
{
|
||||
LARGE_INTEGER interval;
|
||||
|
||||
PAGED_CODE()
|
||||
|
||||
interval.QuadPart = -(LONGLONG)(10000 * Milliseconds);
|
||||
(VOID)KeDelayExecutionThread(KernelMode, FALSE, &interval);
|
||||
}
|
||||
|
||||
UINT32
|
||||
GetActiveProcessorCount (
|
||||
)
|
||||
{
|
||||
return KeQueryActiveProcessorCountEx(ALL_PROCESSOR_GROUPS);
|
||||
}
|
||||
|
||||
UINT32
|
||||
GetCurrentProcessorNumber (
|
||||
)
|
||||
{
|
||||
return KeGetCurrentProcessorNumberEx(NULL);
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
UINT64
|
||||
GetPhysicalAddress (
|
||||
VOID* VirualAddress
|
||||
)
|
||||
{
|
||||
return (UINT64)MmGetPhysicalAddress(VirualAddress).QuadPart;
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
VOID*
|
||||
GetVirtualAddress (
|
||||
UINT64 PhysicalAddress
|
||||
)
|
||||
{
|
||||
PHYSICAL_ADDRESS pa;
|
||||
|
||||
pa.QuadPart = (LONGLONG)PhysicalAddress;
|
||||
return MmGetVirtualForPhysical(pa);
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
VOID*
|
||||
AllocateSystemMemory (
|
||||
UINT64 PageCount
|
||||
)
|
||||
{
|
||||
VOID* pages;
|
||||
SIZE_T allocationBytes;
|
||||
|
||||
MV_ASSERT(PageCount > 0);
|
||||
|
||||
allocationBytes = (SIZE_T)PageCount * PAGE_SIZE;
|
||||
|
||||
//
|
||||
// This is bogus.
|
||||
// "The current function is permitted to run at an IRQ level above the
|
||||
// maximum permitted for 'ExAllocatePoolWithTag' (1)."
|
||||
//
|
||||
#pragma warning(suppress: 28118)
|
||||
pages = ExAllocatePoolWithTag(NonPagedPool, allocationBytes, MV_POOL_TAG);
|
||||
if (pages == NULL)
|
||||
{
|
||||
goto Exit;
|
||||
}
|
||||
RtlZeroMemory(pages, allocationBytes);
|
||||
|
||||
Exit:
|
||||
return pages;
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
VOID
|
||||
FreeSystemMemory (
|
||||
VOID* Pages,
|
||||
UINT64 PageCount
|
||||
)
|
||||
{
|
||||
UNREFERENCED_PARAMETER(PageCount);
|
||||
|
||||
ExFreePoolWithTag(Pages, MV_POOL_TAG);
|
||||
}
|
||||
|
||||
MV_SECTION_PAGED
|
||||
_Use_decl_annotations_
|
||||
VOID*
|
||||
ReserveVirtualAddress (
|
||||
UINT64 PageCount
|
||||
)
|
||||
{
|
||||
PAGED_CODE()
|
||||
|
||||
return MmAllocateMappingAddress(PageCount * PAGE_SIZE, MV_POOL_TAG);
|
||||
}
|
||||
|
||||
MV_SECTION_PAGED
|
||||
_Use_decl_annotations_
|
||||
VOID
|
||||
FreeReservedVirtualAddress (
|
||||
VOID* Pages,
|
||||
UINT64 PageCount
|
||||
)
|
||||
{
|
||||
PAGED_CODE()
|
||||
|
||||
UNREFERENCED_PARAMETER(PageCount);
|
||||
|
||||
MmFreeMappingAddress(Pages, MV_POOL_TAG);
|
||||
}
|
||||
|
||||
MV_SECTION_PAGED
|
||||
_Use_decl_annotations_
|
||||
VOID
|
||||
RunOnAllProcessors (
|
||||
USER_PASSIVE_CALLBACK* Callback,
|
||||
VOID* Context
|
||||
)
|
||||
{
|
||||
UINT32 numberOfProcessors;
|
||||
|
||||
PAGED_CODE()
|
||||
|
||||
numberOfProcessors = KeQueryActiveProcessorCountEx(ALL_PROCESSOR_GROUPS);
|
||||
for (UINT32 index = 0; index < numberOfProcessors; ++index)
|
||||
{
|
||||
NTSTATUS status;
|
||||
PROCESSOR_NUMBER processorNumber;
|
||||
GROUP_AFFINITY newAffinity, prevAffinity;
|
||||
|
||||
status = KeGetProcessorNumberFromIndex(index, &processorNumber);
|
||||
if (NT_ERROR(status))
|
||||
{
|
||||
MV_ASSERT(FALSE);
|
||||
continue;
|
||||
}
|
||||
|
||||
RtlZeroMemory(&newAffinity, sizeof(newAffinity));
|
||||
newAffinity.Group = processorNumber.Group;
|
||||
newAffinity.Mask = 1ull << processorNumber.Number;
|
||||
KeSetSystemGroupAffinityThread(&newAffinity, &prevAffinity);
|
||||
Callback(Context);
|
||||
KeRevertToUserGroupAffinityThread(&prevAffinity);
|
||||
}
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
VOID
|
||||
InitializeSystemSpinLock (
|
||||
SPIN_LOCK* SpinLock
|
||||
)
|
||||
{
|
||||
*SpinLock = SpinLockReleased;
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
UINT8
|
||||
AcquireSystemSpinLock (
|
||||
SPIN_LOCK* SpinLock
|
||||
)
|
||||
{
|
||||
KIRQL oldIrql;
|
||||
|
||||
//
|
||||
// Raise IRQL if the current is lower than DISPATCH_LEVEL.
|
||||
//
|
||||
oldIrql = KeGetCurrentIrql();
|
||||
if (oldIrql < DISPATCH_LEVEL)
|
||||
{
|
||||
oldIrql = KeRaiseIrqlToDpcLevel();
|
||||
}
|
||||
|
||||
for (;;)
|
||||
{
|
||||
//
|
||||
// Attempt to acquire the lock.
|
||||
//
|
||||
if (InterlockedBitTestAndSet64(SpinLock, 0) == SpinLockReleased)
|
||||
{
|
||||
//
|
||||
// Acquired the lock.
|
||||
//
|
||||
MV_ASSERT(*SpinLock == SpinLockAcquired);
|
||||
_Analysis_assume_lock_acquired_(*SpinLock);
|
||||
break;
|
||||
}
|
||||
|
||||
while (*SpinLock == SpinLockAcquired)
|
||||
{
|
||||
//
|
||||
// Someone already acquired it. Spin unless the some of release it.
|
||||
//
|
||||
YieldProcessor();
|
||||
}
|
||||
}
|
||||
|
||||
return oldIrql;
|
||||
}
|
||||
|
||||
//
|
||||
// "The IRQL in 'PreviousContext' was never restored."
|
||||
//
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: __WARNING_IRQL_NOT_USED)
|
||||
|
||||
_Use_decl_annotations_
|
||||
VOID
|
||||
ReleaseSystemSpinLock (
|
||||
SPIN_LOCK* SpinLock,
|
||||
UINT8 PreviousContext
|
||||
)
|
||||
{
|
||||
//
|
||||
// Prevent CPU and compiler re-ordering, and make sure any operations are
|
||||
// done before releasing the spin lock.
|
||||
//
|
||||
MemoryBarrier();
|
||||
*SpinLock = SpinLockReleased;
|
||||
_Analysis_assume_lock_released_(*SpinLock);
|
||||
|
||||
//
|
||||
// Lowers IRQL if necessary.
|
||||
//
|
||||
if (PreviousContext < DISPATCH_LEVEL)
|
||||
{
|
||||
KeLowerIrql(PreviousContext);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning(pop)
|
||||
11
Sources/Platform/Windows/WinPlatform.h
Normal file
11
Sources/Platform/Windows/WinPlatform.h
Normal file
@@ -0,0 +1,11 @@
|
||||
/*!
|
||||
@file WinPlatform.h
|
||||
|
||||
@brief Windows specific platform API.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#pragma once
|
||||
#include "../../Platform.h"
|
||||
68
Sources/Public.h
Normal file
68
Sources/Public.h
Normal file
@@ -0,0 +1,68 @@
|
||||
/*!
|
||||
@file Public.h
|
||||
|
||||
@brief Interfaces to communicate with our hypervisor.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#pragma once
|
||||
#include "Common.h"
|
||||
|
||||
//
|
||||
// The VMCALL numbers our hypervisor provides.
|
||||
//
|
||||
typedef enum _HYPERVISOR_VMCALL_NUMBER
|
||||
{
|
||||
//
|
||||
// Uninstall the hypervisor.
|
||||
//
|
||||
VmcallUninstall,
|
||||
|
||||
//
|
||||
// The maximum valid VMCALL number (exclusive).
|
||||
//
|
||||
VmcallInvalid,
|
||||
} HYPERVISOR_VMCALL_NUMBER;
|
||||
|
||||
//
|
||||
// The arbitrary collection of data passed to our hypervisor from kernel-mode
|
||||
// code through stack of the hypervisor.
|
||||
//
|
||||
// The structure must be 16-byte aligned so that hypervisor's stack pointer is
|
||||
// always 16-byte aligned and SSE instructions can be used to save XMM registers.
|
||||
// See Asm.asm for relevant code.
|
||||
//
|
||||
typedef struct _HYPERVISOR_CONTEXT
|
||||
{
|
||||
//
|
||||
// The processor number associated with this context. 0 for BSP.
|
||||
//
|
||||
UINT32 ProcessorNumber;
|
||||
UINT32 Padding1;
|
||||
UINT64 Padding2;
|
||||
|
||||
//
|
||||
// A pointer to the all-processors context. This value is not used by the
|
||||
// hypervisor, and the hypervisor doe not know its layout. It is stored here
|
||||
// so that it can be returned and freed when hypervisor is being disabled.
|
||||
//
|
||||
struct _SHARED_PROCESSOR_CONTEXT* VpContexts;
|
||||
|
||||
//
|
||||
// A pointer to the EPT context. Needed to handle EPT violation VM-exit.
|
||||
//
|
||||
struct _EPT_CONTEXT* EptContext;
|
||||
|
||||
//
|
||||
// A pointer to the memory access context. Used to access guest's memory.
|
||||
//
|
||||
struct _MEMORY_ACCESS_CONTEXT* MemoryAccessContext;
|
||||
|
||||
//
|
||||
// The state of the nested hypervisor if any.
|
||||
//
|
||||
struct _NEXTED_VMX_CONTEXT* NestedVmxContext;
|
||||
} HYPERVISOR_CONTEXT;
|
||||
C_ASSERT((sizeof(HYPERVISOR_CONTEXT) % 0x10) == 0);
|
||||
222
Sources/Utils.c
Normal file
222
Sources/Utils.c
Normal file
@@ -0,0 +1,222 @@
|
||||
/*!
|
||||
@file Utils.c
|
||||
|
||||
@brief Utility functions that could be used by both on root and non-root
|
||||
operations.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#include "Utils.h"
|
||||
#include "Asm.h"
|
||||
|
||||
_Use_decl_annotations_
|
||||
UINT64
|
||||
ComputeAddressFromIndexes (
|
||||
UINT32 Pml4Index,
|
||||
UINT32 PdptIndex,
|
||||
UINT32 PdIndex,
|
||||
UINT32 PtIndex
|
||||
)
|
||||
{
|
||||
ADDRESS_TRANSLATION_HELPER helper;
|
||||
|
||||
helper.AsUInt64 = 0;
|
||||
helper.AsIndex.Pml4 = Pml4Index;
|
||||
helper.AsIndex.Pdpt = PdptIndex;
|
||||
helper.AsIndex.Pd = PdIndex;
|
||||
helper.AsIndex.Pt = PtIndex;
|
||||
return helper.AsUInt64;
|
||||
}
|
||||
|
||||
UINT32
|
||||
GetSegmentAccessRight (
|
||||
_In_ UINT16 SegmentSelector
|
||||
)
|
||||
{
|
||||
SEGMENT_SELECTOR segmentSelector;
|
||||
UINT32 nativeAccessRight;
|
||||
VMX_SEGMENT_ACCESS_RIGHTS accessRight;
|
||||
|
||||
segmentSelector.Flags = SegmentSelector;
|
||||
|
||||
//
|
||||
// "In general, a segment register is unusable if it has been loaded with a
|
||||
// null selector."
|
||||
// See: 24.4.1 Guest Register State
|
||||
//
|
||||
if ((segmentSelector.Table == 0) &&
|
||||
(segmentSelector.Index == 0))
|
||||
{
|
||||
accessRight.Flags = 0;
|
||||
accessRight.Unusable = TRUE;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
//
|
||||
// Convert the native access right to the format for VMX. Those two formats
|
||||
// are almost identical except that first 8 bits of the native format does
|
||||
// not exist in the VMX format, and that few fields are undefined in the
|
||||
// native format but reserved to be zero in the VMX format.
|
||||
//
|
||||
nativeAccessRight = AsmLoadAccessRightsByte(SegmentSelector);
|
||||
MV_ASSERT(nativeAccessRight);
|
||||
accessRight.Flags = (nativeAccessRight >> 8);
|
||||
accessRight.Reserved1 = 0;
|
||||
accessRight.Reserved2 = 0;
|
||||
accessRight.Unusable = FALSE;
|
||||
|
||||
Exit:
|
||||
return accessRight.Flags;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Returns the segment descriptor corresponds to the SegmentSelector.
|
||||
|
||||
@param[in] DescriptorTableBase - An address of the base of the descriptor
|
||||
table.
|
||||
|
||||
@param[in] SegmentSelector - A segment selector value.
|
||||
|
||||
@return The segment descriptor corresponds to the SegmentSelector.
|
||||
*/
|
||||
static
|
||||
SEGMENT_DESCRIPTOR_32*
|
||||
GetSegmentDescriptor (
|
||||
_In_ UINT64 DescriptorTableBase,
|
||||
_In_ UINT16 SegmentSelector
|
||||
)
|
||||
{
|
||||
SEGMENT_SELECTOR segmentSelector;
|
||||
SEGMENT_DESCRIPTOR_32* segmentDescriptors;
|
||||
|
||||
//
|
||||
// "Selects one of 8192 descriptors in the GDT or LDT. The processor multiplies
|
||||
// the index value by 8 (the number of bytes in a segment descriptor) and
|
||||
// adds the result to the base address of the GDT or LDT (from the GDTR or
|
||||
// LDTR register, respectively)."
|
||||
// See: 3.4.2 Segment Selectors
|
||||
//
|
||||
segmentSelector.Flags = SegmentSelector;
|
||||
segmentDescriptors = (SEGMENT_DESCRIPTOR_32*)DescriptorTableBase;
|
||||
return &segmentDescriptors[segmentSelector.Index];
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Returns the base address of SegmentDescriptor.
|
||||
|
||||
@param[in] SegmentDescriptor - The segment descriptor from which retrieve
|
||||
the base address.
|
||||
|
||||
@return The base address of SegmentDescriptor.
|
||||
*/
|
||||
static
|
||||
UINT64
|
||||
GetSegmentBaseByDescriptor (
|
||||
_In_ CONST SEGMENT_DESCRIPTOR_32* SegmentDescriptor
|
||||
)
|
||||
{
|
||||
UINT64 segmentBase;
|
||||
UINT64 baseHigh, baseMiddle, baseLow;
|
||||
|
||||
baseHigh = ((UINT64)SegmentDescriptor->BaseAddressHigh) << (6 * 4);
|
||||
baseMiddle = ((UINT64)SegmentDescriptor->BaseAddressMiddle) << (4 * 4);
|
||||
baseLow = SegmentDescriptor->BaseAddressLow;
|
||||
segmentBase = (baseHigh | baseMiddle | baseLow) & MAXUINT32;
|
||||
|
||||
//
|
||||
// Few system descriptors are expanded to 16 bytes on x64. For practical
|
||||
// reasons, we only detect TSS descriptors (that is the System field is
|
||||
// cleared, and the Type field has either one of specific values).
|
||||
//
|
||||
// See: 3.5.2 Segment Descriptor Tables in IA-32e Mode
|
||||
//
|
||||
if ((SegmentDescriptor->System == 0) &&
|
||||
((SegmentDescriptor->Type == SEGMENT_DESCRIPTOR_TYPE_TSS_AVAILABLE) ||
|
||||
(SegmentDescriptor->Type == SEGMENT_DESCRIPTOR_TYPE_TSS_BUSY)))
|
||||
{
|
||||
CONST SEGMENT_DESCRIPTOR_64* descriptor64;
|
||||
|
||||
descriptor64 = (CONST SEGMENT_DESCRIPTOR_64*)SegmentDescriptor;
|
||||
segmentBase |= ((UINT64)descriptor64->BaseAddressUpper << 32);
|
||||
}
|
||||
return segmentBase;
|
||||
}
|
||||
|
||||
UINT64
|
||||
GetSegmentBase (
|
||||
_In_ UINT64 DescriptorTableBase,
|
||||
_In_ UINT16 SegmentSelector
|
||||
)
|
||||
{
|
||||
UINT64 segmentBase;
|
||||
SEGMENT_SELECTOR segmentSelector;
|
||||
|
||||
segmentSelector.Flags = SegmentSelector;
|
||||
|
||||
if ((segmentSelector.Table == 0) &&
|
||||
(segmentSelector.Index == 0))
|
||||
{
|
||||
//
|
||||
// The null segment selectors technically does not point to a valid
|
||||
// segment descriptor, hence no valid base address either. We return
|
||||
// 0 for convenience, however.
|
||||
//
|
||||
// "The first entry of the GDT is not used by the processor. A segment
|
||||
// selector that points to this entry of the GDT (that is, a segment
|
||||
// selector with an index of 0 and the TI flag set to 0) is used as a
|
||||
// "null segment selector."".
|
||||
// 3.4.2 Segment Selectors
|
||||
//
|
||||
segmentBase = 0;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
//
|
||||
// For practical reasons, we do not support LDT. This will not be an issue
|
||||
// as we are running as a SYSTEM which will not use LDT.
|
||||
//
|
||||
// "Specifies the descriptor table to use: clearing this flag selects the GDT;
|
||||
// setting this flag selects the current LDT."
|
||||
// See: 3.4.2 Segment Selectors
|
||||
//
|
||||
MV_ASSERT(segmentSelector.Table == 0);
|
||||
segmentBase = GetSegmentBaseByDescriptor(GetSegmentDescriptor(DescriptorTableBase,
|
||||
SegmentSelector));
|
||||
|
||||
Exit:
|
||||
return segmentBase;
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
CR0
|
||||
AdjustCr0 (
|
||||
CR0 Cr0
|
||||
)
|
||||
{
|
||||
CR0 newCr0, fixed0Cr0, fixed1Cr0;
|
||||
|
||||
newCr0 = Cr0;
|
||||
fixed0Cr0.Flags = __readmsr(IA32_VMX_CR0_FIXED0);
|
||||
fixed1Cr0.Flags = __readmsr(IA32_VMX_CR0_FIXED1);
|
||||
newCr0.Flags &= fixed1Cr0.Flags;
|
||||
newCr0.Flags |= fixed0Cr0.Flags;
|
||||
return newCr0;
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
CR4
|
||||
AdjustCr4 (
|
||||
CR4 Cr4
|
||||
)
|
||||
{
|
||||
CR4 newCr4, fixed0Cr4, fixed1Cr4;
|
||||
|
||||
newCr4 = Cr4;
|
||||
fixed0Cr4.Flags = __readmsr(IA32_VMX_CR4_FIXED0);
|
||||
fixed1Cr4.Flags = __readmsr(IA32_VMX_CR4_FIXED1);
|
||||
newCr4.Flags &= fixed1Cr4.Flags;
|
||||
newCr4.Flags |= fixed0Cr4.Flags;
|
||||
return newCr4;
|
||||
}
|
||||
87
Sources/Utils.h
Normal file
87
Sources/Utils.h
Normal file
@@ -0,0 +1,87 @@
|
||||
/*!
|
||||
@file Utils.h
|
||||
|
||||
@brief Utility functions that could be used by both on root and non-root
|
||||
operations.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#pragma once
|
||||
#include "Common.h"
|
||||
|
||||
/*!
|
||||
@brief Computes the address from the four page table indexes.
|
||||
|
||||
@param[in] Pml4Index - The index for PML4.
|
||||
|
||||
@param[in] PdptIndex - The index for PDPT.
|
||||
|
||||
@param[in] PdIndex - The index for PE.
|
||||
|
||||
@param[in] PtIndex - The index for PE.
|
||||
|
||||
@return The resulted address.
|
||||
*/
|
||||
UINT64
|
||||
ComputeAddressFromIndexes (
|
||||
_In_ UINT32 Pml4Index,
|
||||
_In_ UINT32 PdptIndex,
|
||||
_In_ UINT32 PdIndex,
|
||||
_In_ UINT32 PtIndex
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Returns the access right of the segment specified by the SegmentSelector
|
||||
for VMX.
|
||||
|
||||
@param[in] SegmentSelector - A segment selector value.
|
||||
|
||||
@return The access right of the segment for VMX.
|
||||
*/
|
||||
UINT32
|
||||
GetSegmentAccessRight (
|
||||
_In_ UINT16 SegmentSelector
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Returns the base address of the segment specified by SegmentSelector.
|
||||
|
||||
@param[in] DescriptorTableBase - An address of the base of the descriptor
|
||||
table.
|
||||
|
||||
@param[in] SegmentSelector - The segment selector which points to the
|
||||
segment descriptor to retrieve the base address from.
|
||||
|
||||
@return The base address of the segment specified by SegmentSelector.
|
||||
*/
|
||||
UINT64
|
||||
GetSegmentBase (
|
||||
_In_ UINT64 DescriptorTableBase,
|
||||
_In_ UINT16 SegmentSelector
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Returns the CR0 value after the FIXED0 and FIXED1 MSR values are applied.
|
||||
|
||||
@param[in] Cr0 - The CR0 value to apply the FIXED0 and FIXED1 MSR values.
|
||||
|
||||
@return The CR0 value where the FIXED0 and FIXED1 MSR values are applied.
|
||||
*/
|
||||
CR0
|
||||
AdjustCr0 (
|
||||
_In_ CR0 Cr0
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Returns the CR4 value after the FIXED0 and FIXED1 MSR values are applied.
|
||||
|
||||
@param[in] Cr4 - The CR4 value to apply the FIXED0 and FIXED1 MSR values.
|
||||
|
||||
@return The CR4 value where the FIXED0 and FIXED1 MSR values are applied.
|
||||
*/
|
||||
CR4
|
||||
AdjustCr4 (
|
||||
_In_ CR4 Cr4
|
||||
);
|
||||
21433
Sources/ia32-doc/out/ia32.h
Normal file
21433
Sources/ia32-doc/out/ia32.h
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user