Publish the files
This commit is contained in:
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"
|
||||
Reference in New Issue
Block a user