Publish the files

This commit is contained in:
Satoshi Tanda
2020-02-22 13:54:50 -08:00
parent 83bd8d5f19
commit 791486327d
79 changed files with 36078 additions and 47 deletions

View 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

View 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
);

View 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;
}

View 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
);

View 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)

View 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);
}

View 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"

View 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);
}

View 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 (
);

View 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);
}

View 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"

View 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

View 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
);

View 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))

View 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);
}

View 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"

View 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(&timestamp);
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);
}

View 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 (
);

View 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)

View 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"