Publish the files
18
.editorconfig
Normal file
@@ -0,0 +1,18 @@
|
||||
# This file how and what files are formatted with EditorConfig. The specified
|
||||
# formatting is applied when an user explicitly requires formatting with
|
||||
# [Edit] > [Advanced] > [Format Document] or makes changes in a file.
|
||||
#
|
||||
# To learn more about .editorconfig see https://aka.ms/editorconfigdocs
|
||||
|
||||
# This is the top-level settings. EditorConfig will not look for further settings
|
||||
# in any parent directory that may override this settings unintentionally.
|
||||
root = true
|
||||
|
||||
# All C and assembly files
|
||||
[*.{c,cpp,cc,cxx,h,hpp,rc,manifest,asm,nasm,s,inc}]
|
||||
charset = utf-8
|
||||
end_of_line = crlf
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
63
.gitattributes
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
###############################################################################
|
||||
# Set default behavior to automatically normalize line endings.
|
||||
###############################################################################
|
||||
* text=auto
|
||||
|
||||
###############################################################################
|
||||
# Set default behavior for command prompt diff.
|
||||
#
|
||||
# This is need for earlier builds of msysgit that does not have it on by
|
||||
# default for csharp files.
|
||||
# Note: This is only used by command line
|
||||
###############################################################################
|
||||
#*.cs diff=csharp
|
||||
|
||||
###############################################################################
|
||||
# Set the merge driver for project and solution files
|
||||
#
|
||||
# Merging from the command prompt will add diff markers to the files if there
|
||||
# are conflicts (Merging from VS is not affected by the settings below, in VS
|
||||
# the diff markers are never inserted). Diff markers may cause the following
|
||||
# file extensions to fail to load in VS. An alternative would be to treat
|
||||
# these files as binary and thus will always conflict and require user
|
||||
# intervention with every merge. To do so, just uncomment the entries below
|
||||
###############################################################################
|
||||
#*.sln merge=binary
|
||||
#*.csproj merge=binary
|
||||
#*.vbproj merge=binary
|
||||
#*.vcxproj merge=binary
|
||||
#*.vcproj merge=binary
|
||||
#*.dbproj merge=binary
|
||||
#*.fsproj merge=binary
|
||||
#*.lsproj merge=binary
|
||||
#*.wixproj merge=binary
|
||||
#*.modelproj merge=binary
|
||||
#*.sqlproj merge=binary
|
||||
#*.wwaproj merge=binary
|
||||
|
||||
###############################################################################
|
||||
# behavior for image files
|
||||
#
|
||||
# image files are treated as binary by default.
|
||||
###############################################################################
|
||||
#*.jpg binary
|
||||
#*.png binary
|
||||
#*.gif binary
|
||||
|
||||
###############################################################################
|
||||
# diff behavior for common document formats
|
||||
#
|
||||
# Convert binary document formats to text before diffing them. This feature
|
||||
# is only available from the command line. Turn it on by uncommenting the
|
||||
# entries below.
|
||||
###############################################################################
|
||||
#*.doc diff=astextplain
|
||||
#*.DOC diff=astextplain
|
||||
#*.docx diff=astextplain
|
||||
#*.DOCX diff=astextplain
|
||||
#*.dot diff=astextplain
|
||||
#*.DOT diff=astextplain
|
||||
#*.pdf diff=astextplain
|
||||
#*.PDF diff=astextplain
|
||||
#*.rtf diff=astextplain
|
||||
#*.RTF diff=astextplain
|
||||
314
.gitignore
vendored
@@ -1,52 +1,274 @@
|
||||
# Prerequisites
|
||||
*.d
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
|
||||
# Object files
|
||||
*.o
|
||||
*.ko
|
||||
*.obj
|
||||
*.elf
|
||||
# User-specific files
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# Linker output
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
|
||||
# Visual Studio 2015 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUNIT
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# DNX
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_i.h
|
||||
*.ilk
|
||||
*.map
|
||||
*.exp
|
||||
|
||||
# Precompiled Headers
|
||||
*.gch
|
||||
*.meta
|
||||
*.obj
|
||||
*.pch
|
||||
|
||||
# Libraries
|
||||
*.lib
|
||||
*.a
|
||||
*.la
|
||||
*.lo
|
||||
|
||||
# Shared objects (inc. Windows DLLs)
|
||||
*.dll
|
||||
*.so
|
||||
*.so.*
|
||||
*.dylib
|
||||
|
||||
# Executables
|
||||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
*.i*86
|
||||
*.x86_64
|
||||
*.hex
|
||||
|
||||
# Debug files
|
||||
*.dSYM/
|
||||
*.su
|
||||
*.idb
|
||||
*.pdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Kernel Module Compile Results
|
||||
*.mod*
|
||||
*.cmd
|
||||
.tmp_versions/
|
||||
modules.order
|
||||
Module.symvers
|
||||
Mkfile.old
|
||||
dkms.conf
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# JustCode is a .NET coding add-in
|
||||
.JustCode
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# TODO: Comment the next line if you want to checkin your web deploy settings
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
#*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/packages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/packages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/packages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignoreable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
node_modules/
|
||||
orleans.codegen.cs
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# JetBrains Rider
|
||||
.idea/
|
||||
*.sln.iml
|
||||
|
||||
# CodeRush
|
||||
.cr/
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# WPP
|
||||
*.mof
|
||||
|
||||
# CppCheck
|
||||
*.cppcheck
|
||||
**/*-cppcheck-build-dir
|
||||
|
||||
# Doxygen
|
||||
Doxygen/
|
||||
|
||||
# .lib files collected by the Pre-Link script
|
||||
Libs/
|
||||
|
||||
51
Builds/MiniVisor.sln
Normal file
@@ -0,0 +1,51 @@
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.28729.10
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{35DE61DD-9D91-4B1A-B082-8C80ED644749}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
..\.editorconfig = ..\.editorconfig
|
||||
..\.gitattributes = ..\.gitattributes
|
||||
..\.gitignore = ..\.gitignore
|
||||
..\LICENSE = ..\LICENSE
|
||||
PreLinkEvent.py = PreLinkEvent.py
|
||||
..\README.md = ..\README.md
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MiniVisor", "..\Sources\MiniVisor.vcxproj", "{B94B175C-8D18-47E2-800C-1AFBAAC7AC73}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Platform", "Platform", "{3C22D872-3490-4292-A646-A6DFDA461CED}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "EFI", "EFI", "{278A0532-B8F9-4F76-AB0A-946AD377B07A}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
Platform\EFI\locate_image_base.py = Platform\EFI\locate_image_base.py
|
||||
Platform\EFI\MiniVisorDxe.inf = Platform\EFI\MiniVisorDxe.inf
|
||||
Platform\EFI\MiniVisorPkg.dec = Platform\EFI\MiniVisorPkg.dec
|
||||
Platform\EFI\MiniVisorPkg.dsc = Platform\EFI\MiniVisorPkg.dsc
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x64 = Debug|x64
|
||||
Release|x64 = Release|x64
|
||||
UEFI|x64 = UEFI|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{B94B175C-8D18-47E2-800C-1AFBAAC7AC73}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{B94B175C-8D18-47E2-800C-1AFBAAC7AC73}.Debug|x64.Build.0 = Debug|x64
|
||||
{B94B175C-8D18-47E2-800C-1AFBAAC7AC73}.Release|x64.ActiveCfg = Release|x64
|
||||
{B94B175C-8D18-47E2-800C-1AFBAAC7AC73}.Release|x64.Build.0 = Release|x64
|
||||
{B94B175C-8D18-47E2-800C-1AFBAAC7AC73}.UEFI|x64.ActiveCfg = UEFI|x64
|
||||
{B94B175C-8D18-47E2-800C-1AFBAAC7AC73}.UEFI|x64.Build.0 = UEFI|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{3C22D872-3490-4292-A646-A6DFDA461CED} = {35DE61DD-9D91-4B1A-B082-8C80ED644749}
|
||||
{278A0532-B8F9-4F76-AB0A-946AD377B07A} = {3C22D872-3490-4292-A646-A6DFDA461CED}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {FF5CF53A-DB6B-415D-9F6C-135AFA73B8FB}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
2572
Builds/Minivisor.doxyfile
Normal file
94
Builds/Platform/EFI/MiniVisorDxe.inf
Normal file
@@ -0,0 +1,94 @@
|
||||
[Defines]
|
||||
INF_VERSION = 1.27
|
||||
BASE_NAME = MiniVisorDxe
|
||||
FILE_GUID = 503682AC-F01E-4D10-AAE3-BE5A90A563E7
|
||||
MODULE_TYPE = DXE_RUNTIME_DRIVER
|
||||
VERSION_STRING = 1.0
|
||||
ENTRY_POINT = DriverEntry
|
||||
UNLOAD_IMAGE = DriverUnload
|
||||
|
||||
[Sources]
|
||||
../../../Sources/Platform/EFI/EfiAsm.asm
|
||||
../../../Sources/Platform/EFI/EfiAsm.h
|
||||
../../../Sources/Platform/EFI/EfiBitmap.c
|
||||
../../../Sources/Platform/EFI/EfiBitmap.h
|
||||
../../../Sources/Platform/EFI/EfiCommon.h
|
||||
../../../Sources/Platform/EFI/EfiHostInitialization.c
|
||||
../../../Sources/Platform/EFI/EfiHostInitialization.h
|
||||
../../../Sources/Platform/EFI/EfiLogger.c
|
||||
../../../Sources/Platform/EFI/EfiLogger.h
|
||||
../../../Sources/Platform/EFI/EfiPlatform.c
|
||||
../../../Sources/Platform/EFI/EfiPlatform.h
|
||||
../../../Sources/ExtendedPageTables.c
|
||||
../../../Sources/ExtendedPageTables.h
|
||||
../../../Sources/HostMain.c
|
||||
../../../Sources/HostMain.h
|
||||
#../../../Sources/HostNesting.c
|
||||
../../../Sources/HostNesting.h
|
||||
../../../Sources/HostUtils.c
|
||||
../../../Sources/HostUtils.h
|
||||
../../../Sources/HostVmcall.c
|
||||
../../../Sources/HostVmcall.h
|
||||
../../../Sources/Logger.h
|
||||
../../../Sources/MemoryAccess.c
|
||||
../../../Sources/MemoryAccess.h
|
||||
../../../Sources/MemoryManager.c
|
||||
../../../Sources/MemoryManager.h
|
||||
../../../Sources/MemoryType.c
|
||||
../../../Sources/MemoryType.h
|
||||
../../../Sources/MiniVisor.c
|
||||
../../../Sources/MiniVisor.h
|
||||
../../../Sources/Platform.h
|
||||
../../../Sources/Utils.c
|
||||
../../../Sources/Utils.h
|
||||
../../../Sources/Ia32.h
|
||||
../../../Sources/Asm.asm
|
||||
../../../Sources/Asm.h
|
||||
../../../Sources/Common.h
|
||||
../../../Sources/ia32-doc/out/ia32.h
|
||||
|
||||
[Packages]
|
||||
MdePkg/MdePkg.dec
|
||||
MiniVisorPkg/Builds/Platform/EFI/MiniVisorPkg.dec
|
||||
|
||||
[LibraryClasses]
|
||||
UefiDriverEntryPoint
|
||||
UefiLib
|
||||
DevicePathLib
|
||||
SynchronizationLib
|
||||
MemoryAllocationLib
|
||||
PrintLib
|
||||
|
||||
[Protocols]
|
||||
gEfiLoadedImageProtocolGuid ## CONSUMES
|
||||
gEfiMpServiceProtocolGuid ## CONSUMES
|
||||
|
||||
[Guids]
|
||||
gEfiEventExitBootServicesGuid ## CONSUMES
|
||||
gEfiEventVirtualAddressChangeGuid ## CONSUMES
|
||||
|
||||
[Depex]
|
||||
TRUE
|
||||
|
||||
[BuildOptions.common.DXE_RUNTIME_DRIVER]
|
||||
# Detect use of deprecated interfaces if any.
|
||||
MSFT:*_*_*_CC_FLAGS = -D DISABLE_NEW_DEPRECATED_INTERFACES
|
||||
|
||||
# Remove DebugLib library instances (ASSERT and such) from the RELEASE binary.
|
||||
# https://github.com/tianocore-docs/edk2-UefiDriverWritersGuide/blob/master/31_testing_and_debugging_uefi_drivers/314_debugging_code_statements/3141_configuring_debuglib_with_edk_ii.md
|
||||
MSFT:RELEASE_*_*_CC_FLAGS = -D MDEPKG_NDEBUG
|
||||
|
||||
# EDK2 default defines /ALIGN:32, which is way too small for and causes link
|
||||
# error. Reset to the default value.
|
||||
MSFT:*_*_*_DLINK_FLAGS = /ALIGN:4096 /DEBUG
|
||||
|
||||
# By default, certain meta-data in the PE header is zeroed out to increase
|
||||
# compression ratio. Some of those information can be helpful for a debugger,
|
||||
# for example, to reconstruct stack trace. Leave it for such cases. See also,
|
||||
# https://edk2-docs.gitbooks.io/edk-ii-basetools-user-guides/content/GenFw.html
|
||||
MSFT:*_*_X64_GENFW_FLAGS = --keepexceptiontable --keepzeropending --keepoptionalheader
|
||||
|
||||
# Finally, note that the RELEASE build will generate excessively large binary
|
||||
# file. To avoid this open the Conf/tools_def.txt, locate the below line,
|
||||
# RELEASE_VS2019_X64_DLINK_FLAGS = /NOLOGO /NODEFAULTLIB /IGNORE:4001 /IGNORE:4281 /IGNORE:4254 /OPT:REF /OPT:ICF=10 /MAP /ALIGN:32 /SECTION:.xdata,D /SECTION:.pdata,D /Machine:X64 /LTCG /DLL /ENTRY:$(IMAGE_ENTRY_POINT) /SUBSYSTEM:EFI_BOOT_SERVICE_DRIVER /SAFESEH:NO /BASE:0 /DRIVER /MERGE:.rdata=.data
|
||||
# then, remove "/MERGE:.rdata=.data".
|
||||
9
Builds/Platform/EFI/MiniVisorPkg.dec
Normal file
@@ -0,0 +1,9 @@
|
||||
[Defines]
|
||||
DEC_SPECIFICATION = 1.27
|
||||
PACKAGE_NAME = MiniVisorPkg
|
||||
PACKAGE_GUID = 6BFA833B-A9DF-490D-AF7E-7F92A80E3F9A
|
||||
PACKAGE_VERSION = 1.00
|
||||
|
||||
[Includes]
|
||||
../../../Sources
|
||||
../../../Sources/Platform/EFI
|
||||
51
Builds/Platform/EFI/MiniVisorPkg.dsc
Normal file
@@ -0,0 +1,51 @@
|
||||
[Defines]
|
||||
DSC_SPECIFICATION = 1.28
|
||||
PLATFORM_NAME = MiniVisor
|
||||
PLATFORM_GUID = C5ACE17D-FD90-44F7-847C-693ED2B8BEF9
|
||||
PLATFORM_VERSION = 1.00
|
||||
OUTPUT_DIRECTORY = Build/MiniVisor
|
||||
SUPPORTED_ARCHITECTURES = X64
|
||||
BUILD_TARGETS = DEBUG|RELEASE|NOOPT
|
||||
SKUID_IDENTIFIER = DEFAULT
|
||||
|
||||
[LibraryClasses]
|
||||
UefiDriverEntryPoint|MdePkg/Library/UefiDriverEntryPoint/UefiDriverEntryPoint.inf
|
||||
PcdLib|MdePkg/Library/BasePcdLibNull/BasePcdLibNull.inf
|
||||
TimerLib|MdePkg/Library/BaseTimerLibNullTemplate/BaseTimerLibNullTemplate.inf
|
||||
PrintLib|MdePkg/Library/BasePrintLib/BasePrintLib.inf
|
||||
BaseMemoryLib|MdePkg/Library/BaseMemoryLib/BaseMemoryLib.inf
|
||||
BaseLib|MdePkg/Library/BaseLib/BaseLib.inf
|
||||
SynchronizationLib|MdePkg/Library/BaseSynchronizationLib/BaseSynchronizationLib.inf
|
||||
CpuLib|MdePkg/Library/BaseCpuLib/BaseCpuLib.inf
|
||||
UefiLib|MdePkg/Library/UefiLib/UefiLib.inf
|
||||
UefiBootServicesTableLib|MdePkg/Library/UefiBootServicesTableLib/UefiBootServicesTableLib.inf
|
||||
UefiRuntimeServicesTableLib|MdePkg/Library/UefiRuntimeServicesTableLib/UefiRuntimeServicesTableLib.inf
|
||||
DevicePathLib|MdePkg/Library/UefiDevicePathLibDevicePathProtocol/UefiDevicePathLibDevicePathProtocol.inf
|
||||
!if $(TARGET) == RELEASE
|
||||
DebugLib|MdePkg/Library/BaseDebugLibNull/BaseDebugLibNull.inf
|
||||
!else
|
||||
!ifdef $(DEBUG_ON_SERIAL_PORT)
|
||||
IoLib|MdePkg/Library/BaseIoLibIntrinsic/BaseIoLibIntrinsicSev.inf
|
||||
SerialPortLib|PcAtChipsetPkg/Library/SerialIoLib/SerialIoLib.inf
|
||||
DebugLib|MdePkg/Library/BaseDebugLibSerialPort/BaseDebugLibSerialPort.inf
|
||||
!else
|
||||
DebugLib|MdePkg/Library/UefiDebugLibConOut/UefiDebugLibConOut.inf
|
||||
!endif
|
||||
!endif
|
||||
DebugPrintErrorLevelLib|MdePkg/Library/BaseDebugPrintErrorLevelLib/BaseDebugPrintErrorLevelLib.inf
|
||||
|
||||
[LibraryClasses.common.DXE_RUNTIME_DRIVER]
|
||||
PcdLib|MdePkg/Library/DxePcdLib/DxePcdLib.inf
|
||||
BaseMemoryLib|MdePkg/Library/BaseMemoryLibOptDxe/BaseMemoryLibOptDxe.inf
|
||||
MemoryAllocationLib|MdePkg/Library/UefiMemoryAllocationLib/UefiMemoryAllocationLib.inf
|
||||
|
||||
[PcdsFixedAtBuild]
|
||||
# Define DEBUG_ERROR | DEBUG_VERBOSE | DEBUG_INFO | DEBUG_WARN to enable
|
||||
# logging at those levels. Also, define DEBUG_PROPERTY_ASSERT_DEADLOOP_ENABLED
|
||||
# and such. Assertion failure will call CpuDeadLoop.
|
||||
# https://github.com/tianocore/tianocore.github.io/wiki/EDK-II-Debugging
|
||||
gEfiMdePkgTokenSpaceGuid.PcdDebugPrintErrorLevel|0x80400042
|
||||
gEfiMdePkgTokenSpaceGuid.PcdDebugPropertyMask|0x2f
|
||||
|
||||
[Components]
|
||||
MiniVisorPkg/Builds/Platform/EFI/MiniVisorDxe.inf
|
||||
13
Builds/Platform/EFI/locate_image_base.py
Normal file
@@ -0,0 +1,13 @@
|
||||
current_page_base = idaapi.get_reg_val('rip') & (~0xfff)
|
||||
offset = 0
|
||||
while idc.read_dbg_word(current_page_base - offset) != 0x5a4d:
|
||||
offset += 0x1000
|
||||
|
||||
image_base = current_page_base - offset
|
||||
print(
|
||||
f'Base found at 0x{image_base:02X}. To load symbols, go [File] menu >'
|
||||
f' Load file > PDB file..., then set,\n'
|
||||
f' Input file: the PDB file, for example, C:\\edk2\\MiniVisorPkg\\Builds\\x64\\UEFI\\MiniVisorDxe.pdb\n'
|
||||
f' Address: 0x{image_base:02X}\n'
|
||||
f'and hit [OK], and then, [Yes].'
|
||||
)
|
||||
66
Builds/Platform/Windows/DumpActiveLogs.js
Normal file
@@ -0,0 +1,66 @@
|
||||
/*!
|
||||
@file DumpActiveLogs.js
|
||||
|
||||
@brief Implements the DumpActiveLogs function which dumps buffered log entries.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2019 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
function initializeScript()
|
||||
{
|
||||
return [new host.apiVersionSupport(1, 3)];
|
||||
}
|
||||
|
||||
function invokeScript()
|
||||
{
|
||||
//
|
||||
// Insert your script content here. This method will be called whenever the script is
|
||||
// invoked from a client.
|
||||
//
|
||||
// See the following for more details:
|
||||
//
|
||||
// https://aka.ms/JsDbgExt
|
||||
//
|
||||
}
|
||||
|
||||
const log = x => host.diagnostics.debugLog(x + "\n");
|
||||
const u64 = x => host.memory.readMemoryValues(x, 1, 8)[0];
|
||||
const sizeof = x => host.evaluateExpression("sizeof(" + x + ")");
|
||||
const str = (x) => host.memory.readString(x);
|
||||
const strn = (x, y) => host.memory.readString(x, y);
|
||||
|
||||
/**
|
||||
* Returns an array of arrays of log entries where 0=ProcessName, 1=FunctionName,
|
||||
* and 2=LogMessage.
|
||||
*
|
||||
* Example:
|
||||
* kd> .scriptload C:\edk2\MiniVisorPkg\Builds\Platform\Windows\DumpActiveLogs.js
|
||||
* kd> dx Debugger.State.Scripts.DumpActiveLogs.Contents.DumpActiveLogs(),0xffff
|
||||
*/
|
||||
function DumpActiveLogs()
|
||||
{
|
||||
let addr = host.getModuleSymbolAddress("MiniVisor", "g_Logger");
|
||||
let context = host.createPointerObject(u64(addr), "MiniVisor", "LOGGER_CONTEXT*");
|
||||
let entriesBase = context.PairedLogBuffer.ActiveLogBuffer.LogEntries;
|
||||
|
||||
let logs = [];
|
||||
host.diagnostics.debugLog("Collecting buffered log entries.");
|
||||
for (let offset = 0; offset < context.PairedLogBuffer.ActiveLogBuffer.NextLogOffset; /**/)
|
||||
{
|
||||
let entry = host.createPointerObject(entriesBase.address.add(offset),
|
||||
"MiniVisor",
|
||||
"_DEBUG_LOG_ENTRY*");
|
||||
logs.push([
|
||||
str(entry.ProcessName),
|
||||
str(entry.FunctionName),
|
||||
strn(entry.LogMessage, entry.LogMessageLength),
|
||||
]);
|
||||
offset += sizeof("_DEBUG_LOG_ENTRY") - 1 + entry.LogMessageLength;
|
||||
host.diagnostics.debugLog(".");
|
||||
}
|
||||
host.diagnostics.debugLog("\n");
|
||||
return logs;
|
||||
}
|
||||
30
Builds/PreLinkEvent.py
Normal file
@@ -0,0 +1,30 @@
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
|
||||
def main():
|
||||
path = sys.argv[1]
|
||||
out_dir = sys.argv[2]
|
||||
|
||||
lib_files = []
|
||||
for root, _, files in os.walk(path):
|
||||
for file in files:
|
||||
if '.lib' in file:
|
||||
lib_files.append(os.path.join(root, file))
|
||||
|
||||
if not os.path.exists(out_dir):
|
||||
os.mkdir(out_dir)
|
||||
for lib_file in lib_files:
|
||||
shutil.copy(lib_file, out_dir)
|
||||
|
||||
print(
|
||||
'If you see link error, rebuild the project with the EDK2 build command'
|
||||
' and try again. If you still see error, try updating dependencies.\n'
|
||||
'To do so, open the project properties, "Linker" > "Input", and update'
|
||||
' "Additional Dependencies" with the following:'
|
||||
)
|
||||
print(' ' + ';'.join([os.path.basename(lib_file) for lib_file in lib_files]))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
433
Docs/Building_and_Debugging.md
Normal file
@@ -0,0 +1,433 @@
|
||||
Building and Debugging
|
||||
=======================
|
||||
|
||||
This document provides step-by-step instructions to build and debug MiniVisor for both UEFI and Windows driver configurations.
|
||||
|
||||
|
||||
Common Build Prerequisites
|
||||
---------------------------
|
||||
|
||||
To build MiniVisor, the followings are required.
|
||||
- [Visual Studio Community 2019](https://www.visualstudio.com/downloads/)
|
||||
- [Windows Software Development Kit (SDK) for Windows 10 ](https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk) 10.0.18362 or later
|
||||
- [Windows Driver Kit (WDK) for Windows 10](https://developer.microsoft.com/en-us/windows/hardware/windows-driver-kit) 10.0.18362 or later
|
||||
|
||||
|
||||
Terms
|
||||
------
|
||||
|
||||
In this document, the "host" system refers to the system that builds MiniVisor and runs debugger. It may also run the target system as a virtual machine.
|
||||
|
||||
The "target" system refers to the system that loads MiniVisor". This can be a separate bare-metal machine or a virtual machine running inside the host system.
|
||||
|
||||
|
||||
UEFI: Building the Driver
|
||||
--------------------------
|
||||
|
||||
The below instructions are all to be done on the host system.
|
||||
|
||||
1. Setup EDK2, by follow the official instructions at [tianocore.github.io](https://github.com/tianocore/tianocore.github.io/wiki/Windows-systems). This is mainly made up of those steps below as demonstrated.
|
||||
|
||||
1. Installing Python, NASM, and the ASL Compiler,
|
||||
```
|
||||
> C:\ASL\iasl.exe -v
|
||||
...
|
||||
ASL+ Optimizing Compiler/Disassembler version 20191018
|
||||
> set NASM_PREFIX=C:\Users\tanda\AppData\Local\bin\NASM\
|
||||
> set PYTHON_HOME=C:\Users\tanda\AppData\Local\Programs\Python\Python38
|
||||
```
|
||||
2. Cloning the EDK2 repository,
|
||||
```
|
||||
> cd C:\
|
||||
> git clone https://github.com/tianocore/edk2.git
|
||||
> cd edk2
|
||||
> git submodule update --init
|
||||
```
|
||||
3. Editing Conf\target.txt, and
|
||||
```
|
||||
> edksetup.bat Rebuild
|
||||
(Warnings here is ok)
|
||||
|
||||
> notepad Conf\target.txt
|
||||
---
|
||||
#ACTIVE_PLATFORM = EmulatorPkg/EmulatorPkg.dsc
|
||||
ACTIVE_PLATFORM = MdeModulePkg/MdeModulePkg.dsc
|
||||
...
|
||||
#TOOL_CHAIN_TAG = VS2015x86
|
||||
TOOL_CHAIN_TAG = VS2019
|
||||
---
|
||||
```
|
||||
4. Running test build
|
||||
```
|
||||
> edksetup.bat
|
||||
(Warnings here is ok)
|
||||
|
||||
> build -a X64 -t VS2019 -b NOOPT
|
||||
...
|
||||
- Done -
|
||||
Build end time: 18:54:14, Feb.04 2020
|
||||
Build total time: 00:01:35
|
||||
|
||||
> dir /s Build\MdeModule\NOOPT_VS2019\X64\HelloWorld.efi
|
||||
(You should see multiple entries under C:\edk2\Build\)
|
||||
```
|
||||
|
||||
2. Checking out MiniVisorPkg inside the `edk2` folder
|
||||
|
||||
```
|
||||
> cd C:\edk2
|
||||
> git clone https://github.com/tandasat/MiniVisorPkg.git
|
||||
```
|
||||
|
||||
3. Building MiniVisorPkg
|
||||
|
||||
```
|
||||
> cd C:\edk2
|
||||
> edksetup.bat
|
||||
> build -w -a X64 -t VS2019 -b NOOPT -p MiniVisorPkg\Builds\Platform\EFI\MiniVisorPkg.dsc
|
||||
> dir /s MiniVisorDxe.efi
|
||||
(You should see multiple entries under C:\edk2\Build\)
|
||||
```
|
||||
|
||||
|
||||
UEFI: Preparing the Target System
|
||||
----------------------------------
|
||||
|
||||
The target system must be a UEFI-based x64 system without Secure Boot and be able to boot one of the supported operating systems.
|
||||
|
||||
In the case of virtual machines, only VMware Workstation is supported. For the other possible configurations, see the below sections. With VMware Workstation, make sure that
|
||||
- `Virtualize Intel VT-x/EPT or AMD-V/RVI` is checked,
|
||||
- in the `Advanced` section, `UEFI` is selected, and
|
||||
- `Enable secure boot` is unchecked.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
In the case of bare-metal, some UEFI-based systems enable the "legacy BIOS" mode to boot the operating system. This mode is essentially equivalent to using old fashioned BIOS and not supported by this project. Changing the mode is possible in the UEFI settings but will render the existing operating system unbootable. Here is the example with Dell UEFI settings configured to boot the system with the UEFI mode.
|
||||

|
||||
|
||||
|
||||
UEFI: Loading the Driver
|
||||
-------------------------
|
||||
|
||||
In this instruction, we will boot the target system into the UEFI shell, load MiniVisorDxe.efi from the USB thumb-drive and boot Windows.
|
||||
|
||||
1. Prepare USB thumb-drive
|
||||
1. Format the USB thumb-drive to FAT32. This page assumes that the USB drive is mounted as `D:\` on the host Windows.
|
||||
|
||||
The Windows default GUI tool does not offer the FAT32 format option for the USB driver bigger than 32GB. Use a smaller device or the `format` command instead, for example,
|
||||
```
|
||||
> format /FS:FAT32 D:
|
||||
```
|
||||
|
||||
2. Download pre-compiled the UEFI shell from the EDK2 repository ([Download](https://github.com/tianocore/edk2/raw/edk2-stable201903/ShellBinPkg/UefiShell/X64/Shell.efi)). This instruction assumes the file is downloaded as `%USERPROFILE%\Downloads\Shell.efi`
|
||||
|
||||
3. Deploy the UEFI shell as `Bootx64.efi`, so it can be started automatically.
|
||||
|
||||
```
|
||||
> cd /d D:\
|
||||
> mkdir EFI\Boot
|
||||
> copy %USERPROFILE%\Downloads\Shell.efi EFI\Boot\Bootx64.efi
|
||||
```
|
||||
|
||||
2. Deploy MiniVisorDxe.efi to the USB drive
|
||||
|
||||
```
|
||||
> copy /y C:\edk2\Build\MiniVisor\NOOPT_VS2019\X64\MiniVisorDxe.efi D:\
|
||||
```
|
||||
|
||||
3. Boot the target system into UEFI settings
|
||||
|
||||
In the case of bare-metal, this requires device specific operations. Most of systems require to press F2, F12, or DEL key during the early boot phase.
|
||||
|
||||
With VMware Workstation, select `Power On to Firmware` from the `Power` menu.
|
||||
|
||||
4. Start the EFI shell
|
||||
|
||||
This requires UEFI specific operations. Typically, you can either find the `EFI Internal Shell` option or select the USB drive as a boot device, as we copied the UEFI shell as `Bootx64.efi` already.
|
||||
|
||||
If neither of them is found, reboot the target system into the boot device selection menu. For example, UEFI settings on Dell XPS devices do not offer those options, and instead, booting the device with the F12 key enters the boot device selection menu.
|
||||
|
||||
VMware Workstation offers `EFI Internal Shell (Unsupported option)`.
|
||||

|
||||
|
||||
5. Select the filesystem
|
||||
|
||||
1. Run `map` command to list the available filesystems. Identify one that has `USB` in the path. We assume that is `fs1:` in this instruction.
|
||||
- If not found, USB might not be recognized. Make sure it is accessible from the target system and run `map -r` to refresh the list.
|
||||
2. Run `fs1:` to move to the filesystem.
|
||||
3. Run `ls` command and make sure `MiniVisorDxe.efi` exists.
|
||||
|
||||

|
||||
|
||||
6. Load the driver and boot Windows
|
||||
|
||||
1. Load the the driver and exit from the shell
|
||||
|
||||
```
|
||||
fs1:\> load MiniVisorDxe.efi
|
||||
...
|
||||
Image 'fs1:\MiniVisorDxe.efi' loaded at 77564000 - Success
|
||||
|
||||
fs1:\> exit
|
||||
```
|
||||
|
||||
2. Select the boot manager for Windows. This is typically `Windows Boot Manager`. Windows should boot as usual other than some logs may appear on display.
|
||||
|
||||
Unloading the driver after operating system boot is unsupported.
|
||||
|
||||
|
||||
UEFI: Building the Project on Visual Studio
|
||||
--------------------------------------------
|
||||
|
||||
__Once the project is built with EDK2__, you may build it on Visual Studio with the following steps:
|
||||
|
||||
1. Open `MiniVisorPkg\Builds\MiniVisor.sln` with Visual Studio
|
||||
2. Select `UEFI` build configuration (Default will be `Debug`, which produces the Windows driver)
|
||||
3. Run `Build Solution`
|
||||
|
||||
Visual Studio will produce output as `C:\edk2\MiniVisorPkg\Builds\x64\UEFI\MiniVisorDxe.efi`. This binary is always compiled for debugging without optimization.
|
||||
|
||||
|
||||
UEFI: Build Customization
|
||||
--------------------------
|
||||
|
||||
* Building optimized build
|
||||
|
||||
For optimized binary file, use EDk2's `build` command with the `-b RELEASE` parameter.
|
||||
```
|
||||
> build -w -a X64 -t VS2019 -b RELEASE -p MiniVisorPkg\Builds\Platform\EFI\MiniVisorPkg.dsc
|
||||
```
|
||||
|
||||
* Enabling Serial Logging
|
||||
|
||||
By default, the UEFI driver uses console output for logging. This can be switched to use serial output by building the project with `-D DEBUG_ON_SERIAL_PORT` parameter.
|
||||
```
|
||||
> build -w -a X64 -t VS2019 -b NOOPT -p MiniVisorPkg\Builds\Platform\EFI\MiniVisorPkg.dsc -D DEBUG_ON_SERIAL_PORT
|
||||
```
|
||||
|
||||
For more details of logging with serial output, see the below section.
|
||||
|
||||
|
||||
UEFI & Windows: Testing Existence of MiniVisor
|
||||
-----------------------------------------------
|
||||
|
||||
One can verify that MiniVisor has virtualized all processors by executing the accompanying test program called `CheckHvVendor`.
|
||||
|
||||
This program can be compiled into both for Windows and Linux.
|
||||
|
||||
- For Windows, open `MiniVisorPkg\Tests\CheckHvVendor\CheckHvVendor.sln` with Visual Studio and compile it with `x64` + `Release` configuration.
|
||||
- For Linux, compile `MiniVisorPkg\Tests\CheckHvVendor\CheckHvVendor\CheckHvVendor.cpp` with `g++`.
|
||||
|
||||
Once compiled, deploy the executable onto the target system and run from the terminal. The output should contain `MiniVisor` if MiniVisor is successfully installed.
|
||||
|
||||
```
|
||||
> CheckHvVendor.exe
|
||||
Executing CPUID(0x40000000) on CPU 0
|
||||
Result: MiniVisor
|
||||
Executing CPUID(0x40000000) on CPU 1
|
||||
Result: MiniVisor
|
||||
Executing CPUID(0x40000000) on CPU 2
|
||||
Result: MiniVisor
|
||||
Executing CPUID(0x40000000) on CPU 3
|
||||
Result: MiniVisor
|
||||
```
|
||||
|
||||
|
||||
UEFI: Configuring VMware Virtual Machine for Debugging
|
||||
-------------------------------------------------------
|
||||
|
||||
It is recommended to configure all of them for debugging the UEFI driver.
|
||||
|
||||
* Enabling the GDB debug stub
|
||||
|
||||
This allows you to connect to the virtual machine using one of the debuggers listed below.
|
||||
|
||||
To enable the debug stub, shut down the virtual machine, open the VMX file associated with the virtual machine and add those lines:
|
||||
```
|
||||
debugStub.listen.guest64 = "TRUE"
|
||||
debugStub.hideBreakpoints = "TRUE"
|
||||
```
|
||||
|
||||
To connect to the virtual machine using IDA Pro, see instructions explained in [this blog post](https://www.triplefault.io/2017/07/setup-vmm-debugging-using-vmwares-gdb_9.html). WIth GDB, use the target command.
|
||||
```
|
||||
$ gdb
|
||||
gdb$ target remote localhost:8864
|
||||
```
|
||||
|
||||
* Adding a serial device for logging
|
||||
|
||||
This allows you to see logs for an extended period of time, compared with console output, as well as register dump by VMware in some case of an unhandled exception.
|
||||
|
||||
Add the serial device using the named pipe. Make sure `This end is the server` and `The other end is an application` are selected.
|
||||

|
||||
|
||||
The below section explains how to see logs sent to the serial device.
|
||||
|
||||
* Enabling Full debug logging
|
||||
|
||||
This allows you to see extended logs in `vmware.log` file in case the virtual machine encounters critical error such as triple fault.
|
||||
|
||||
To enable this, select the `Full` logging option in the `Advanced` section.
|
||||

|
||||
|
||||
Here is an example output, showing that triple fault occurred.
|
||||

|
||||
|
||||
|
||||
UEFI: Viewing Serial Output From the Virtual Machine
|
||||
-----------------------------------------------------
|
||||
|
||||
By default, MiniVisor sends debug logs to the console. This is the easiest way to view logs but is limited in that logging are stopped at the early stage of operating system boot due to UEFI API for console access gets disabled.
|
||||
|
||||
Instead, debug logs can be sent to the serial port if the target system has it. This allows logging to remain enabled for an extended period of time, as well as copying and saving output on the host system.
|
||||
|
||||
To do so,
|
||||
1. Add the serial port to the virtual machine as explained above.
|
||||
2. Build the project with the `-D DEBUG_ON_SERIAL_PORT` parameter as explained above.
|
||||
```
|
||||
> build -w -a X64 -t VS2019 -b NOOPT -p MiniVisorPkg\Builds\Platform\EFI\MiniVisorPkg.dsc -D DEBUG_ON_SERIAL_PORT
|
||||
```
|
||||
3. Start the virtual machine and make it ready for loading the driver.
|
||||
4. Download PuTTY (or any terminal software).
|
||||
5. Configure the session as highlighted.
|
||||

|
||||
6. Open the session.
|
||||
7. Load the driver on the target machine. You should be able to see debug logs on PuTTY.
|
||||

|
||||
|
||||
This also displays unhandled exception information that occurred during UEFI boot-time if possible. This is extremely useful since a debugger (mentioned below) is unable to catch and diagnose them.
|
||||

|
||||
|
||||
|
||||
UEFI: Debugging the Driver with a Debugger
|
||||
-------------------------------------------
|
||||
|
||||
Debugging the UEFI driver is not easy due to the fact that source-level debugging is often not possible. Instead, one may have to choose and learn new debuggers and even pay for the license. Here is the list of configurations I evaluated with VMware Workstation.
|
||||
|
||||
* IDA Pro + Hex-Rays
|
||||
|
||||
The combination of them offers a very similar experience to an ordinary debugger. That is, attaching to the virtual machine, breaking into it, displaying local variables with symbol names, single-stepping with decompiled code and inspect memory and registers. However, licenses would cost more than 4000 USD.
|
||||
|
||||
* IDA Pro
|
||||
|
||||
This is still superior to using GDB directly because it lets you inspect the values of system registers like CR0. However, IDA Pro still costs $$$.
|
||||
|
||||
* GDB with WSL
|
||||
|
||||
This is the free workable option. Similar to IDA Pro, it can attach the target system, single-step instructions and inspect some register values, but not all of them. It can take some time to get used to if you are new to GDB.
|
||||
|
||||
* Windbg
|
||||
|
||||
One can do source-level debugging using [Intel® UEFI Development Kit (Intel® UDK) Debugger Tool](https://software.intel.com/en-us/download/intel-uefi-development-kit-intel-udk-debugger-tool-r150-windows) and Windbg. This is confirmed to work with VirtualBox and Hyper-V but not with VMware Workstation. As those two virtualization solutions are not useful (see below section), this is not an option.
|
||||
|
||||
See "Debugging a standalone module loaded in a UEFI shell" section of the accompanying manual if interested.
|
||||
|
||||
|
||||
|
||||
UEFI: Testing with Other Virtualization Platform
|
||||
-------------------------------------------------
|
||||
|
||||
VMware Workstation is the only supported virtualization platform. However, one can choose to try on the other system with some limitations. This section describes limitations on the other vitalization platforms for the purpose of developing and testing a UEFI driver hypervisor.
|
||||
|
||||
- KVM + QEMU on Linux
|
||||
|
||||
This is the most viable alternative, and even in some cases more useful than VMware Workstation.
|
||||
|
||||
Pros:
|
||||
- No software cost.
|
||||
- Possible to inspect the system even on an unhandled exception. This is not possible with VMware Workstation as it terminates immediately.
|
||||
- Functional with a single processor configuration.
|
||||
|
||||
Cons:
|
||||
- Unable to test multi-processor configurations. Boot hangs once VMXON is executed on an application processor.
|
||||
- A Linux host is required.
|
||||
|
||||
- Hyper-V
|
||||
|
||||
Hardly usable due to lack of multi-processor protocol support and underneath Hyper-V forwarding VM-exit despite those are not enabled.
|
||||
|
||||
- VirtualBox
|
||||
|
||||
Not usable due to lack of nested EPT support and buggy nesting implementation.
|
||||
|
||||
- QEMU on Windows
|
||||
|
||||
Not usable due to lack of nested virtualization support.
|
||||
|
||||
- Boches
|
||||
|
||||
Not usable due to lack of UEFI support.
|
||||
|
||||
|
||||
UEFI: Testing with Single-board Computers
|
||||
------------------------------------------
|
||||
|
||||
Testing against physical devices is MUST. While testing on VMware can uncover many issues, it differs from the physical devices, especially in multi-processor and memory cache handling.
|
||||
|
||||
The authors recommend purchasing an AMD64 single-board computer with a serial port and test against it. One of such devices is [MinnowBoard Turbot](https://store.netgate.com/Turbot4.aspx). We do not go into details of how to set it up for debugging as it is device-specific.
|
||||
|
||||
|
||||
UEFI: Testing with Regular Devices
|
||||
-----------------------------------
|
||||
|
||||
Testing against regular laptops and desktops is another way to find bugs. This does not require purchasing and configuring a single board computer, but diagnosing issues tends to be significantly harder due to not being able to use serial logging.
|
||||
|
||||
|
||||
UEFI: Debugging Tricks
|
||||
-----------------------
|
||||
|
||||
1. No INT 3. Use `CpuDeadLoop()` instead, then, attach a debugger.
|
||||
2. If you use IDA Pro for debugging, run `Builds\Platform\EFI\locate_image_base.py` and follow the instructions displayed after attaching to the target.
|
||||
3. Start with a single processor. With a bare-metal, where its processor count cannot be reduced, just skip entering the non-root mode on application processors.
|
||||
|
||||
|
||||
Windows: Building the Driver
|
||||
-----------------------------
|
||||
|
||||
Building the Windows driver can be fully done on Visual Studio with the following stesp:
|
||||
1. Open `MiniVisorPkg\Builds\MiniVisor.sln` with Visual Studio.
|
||||
2. Select `Debug` or `Release` build configuration.
|
||||
3. Run `Build Solution`.
|
||||
|
||||
|
||||
Windows: Preparing the Target System
|
||||
-------------------------------------
|
||||
|
||||
The target system must be a x64 Windows 10 or 7 system with test signing enabled. Also, Virtualization Based Security (VBS) must be disabled.
|
||||
|
||||
In case of testing on VMware, make sure that `Virtualize Intel VT-x/EPT or AMD-V/RVI` is checked,
|
||||

|
||||
|
||||
To enable test signing, run the following command on the Administrator command prompt and reboot the system.
|
||||
```
|
||||
> bcdedit.exe /set testsigning on
|
||||
```
|
||||
|
||||
To disable VBS, follow the instructions at [Manage Windows Defender Credential Guard](https://docs.microsoft.com/en-us/windows/security/identity-protection/credential-guard/credential-guard-manage).
|
||||
|
||||
Finally, run the following command to create the service to load the driver on the Administrator command prompt and reboot the system.
|
||||
```
|
||||
> sc.exe create MiniVisor type= kernel binPath= C:\Users\user\Desktop\MiniVisor.sys
|
||||
```
|
||||
|
||||
|
||||
Windows: Loading the Driver
|
||||
----------------------------
|
||||
|
||||
Running this command on the the Administrator command prompt will load the driver.
|
||||
```
|
||||
> sc.exe start MiniVisor
|
||||
```
|
||||
|
||||
Similarly, the below command will unload the driver.
|
||||
```
|
||||
> sc.exe stop MiniVisor
|
||||
```
|
||||
|
||||
|
||||
Windows: Debugging the Driver with a Debugger
|
||||
----------------------------------------------
|
||||
|
||||
The Windows driver can be debugged in the same way as other Windows drivers. Configure KDNET for Windows 10 and KDCOM for Windows 7, and attach WinDbg as usual.
|
||||
BIN
Docs/Resources/Building_and_Testing_BootMode.jpg
Normal file
|
After Width: | Height: | Size: 251 KiB |
|
After Width: | Height: | Size: 100 KiB |
|
After Width: | Height: | Size: 581 KiB |
BIN
Docs/Resources/Building_and_Testing_PuTTY_Output_with_VMware.png
Normal file
|
After Width: | Height: | Size: 156 KiB |
BIN
Docs/Resources/Building_and_Testing_VMwareWS_FullLogging.png
Normal file
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 965 KiB |
BIN
Docs/Resources/Building_and_Testing_VMwareWS_SelectFS.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
Docs/Resources/Building_and_Testing_VMwareWS_SerialDevice.png
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
Docs/Resources/Building_and_Testing_VMwareWS_Shell.png
Normal file
|
After Width: | Height: | Size: 9.7 KiB |
BIN
Docs/Resources/Building_and_Testing_VMwareWS_UEFI.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
Docs/Resources/Building_and_Testing_VMwareWS_VTx.png
Normal file
|
After Width: | Height: | Size: 60 KiB |
67
README.md
@@ -1 +1,66 @@
|
||||
# MiniVisorPkg
|
||||
What's This
|
||||
============
|
||||
|
||||
This is a research hypervisor written as a UEFI and Windows driver for the educational purpose for Intel processors.
|
||||
|
||||
This MiniVisor, as a UEFI driver, provides the ability to inspect system activities even before the operating system boots, while as a Windows driver, allows developers to debug it with familiar tools like WinDbg.
|
||||
|
||||
|
||||
Showcase
|
||||
---------
|
||||
|
||||
TBD.
|
||||
|
||||
|
||||
Motivation
|
||||
-----------
|
||||
|
||||
The goal of this project is to share an additional learning resource for writing UEFI hypervisors with the community and researchers.
|
||||
|
||||
There are numerous open source hypervisors that can relatively easily study their implementations, but those that support booting operating systems as UEFI drivers are still not many.
|
||||
|
||||
Given the universality of UEFI systems on the AMD64 ecosystem and the unique ability to monitor, attack and protect the system throughout operating system startup on bare-metal systems, the authors believe that having the understanding and being able to author this type of hypervisors are valuable for research.
|
||||
|
||||
With this goal in mind, the project aims to offer
|
||||
- Straightforward and approachable codebase
|
||||
- over highly-abstracted but intricate structures
|
||||
- Rich comments aimed for new learners
|
||||
- over just-enough for those who already know how hypervisor works
|
||||
- Minimal functionality to highlight core concepts to study
|
||||
- over extensibility and direct usefulness
|
||||
- Extensive documentation on development environment setup
|
||||
|
||||
|
||||
System Requirements
|
||||
--------------------
|
||||
|
||||
Common Requirements:
|
||||
- Intel VT-x and EPT supported processors
|
||||
|
||||
Requirements for the UEFI driver:
|
||||
- UEFI-based system
|
||||
- 64bit Windows 10 and Windows 10 IoT Core to boot
|
||||
|
||||
Requirements for the Windows driver:
|
||||
- 64bit Windows 7 and 10
|
||||
|
||||
See [Building and Debugging](Docs/Building_and_Debugging.md) for testing.
|
||||
|
||||
|
||||
Limitations
|
||||
------------
|
||||
|
||||
- Nested virtualization is not supported. The CPUID instruction reports that the VMX extension is unsupported.
|
||||
- Booting Windows SKUs other than the listed above, as well as Linux are not supported or tested.
|
||||
- Sleep and resume are not supported or tested.
|
||||
|
||||
|
||||
Acknowledgments
|
||||
----------------
|
||||
|
||||
The authors thank for creators and maintainers of the following projects:
|
||||
- [Bareflank](https://github.com/Bareflank/hypervisor) and [STM](https://github.com/jyao1/STM) -- for publishing UEFI-base hypervisors with the relatively small codebase.
|
||||
- [zpp_hypervisor](https://github.com/eyalz800/zpp_hypervisor) -- for making me realize that writing UEFI-based hypervisors is viable.
|
||||
- [EfiGuard](https://github.com/Mattiwatti/EfiGuard) -- for clean codebase and rich documentation for UEFI development newbies.
|
||||
- [hvpp](https://github.com/wbenny/hvpp) -- for few techniques required for the UEFI environment.
|
||||
- [ia32-doc](https://github.com/wbenny/ia32-doc) -- for saving me from defining thousands of constants and structures by hand.
|
||||
|
||||
315
Sources/Asm.asm
Normal file
@@ -0,0 +1,315 @@
|
||||
;
|
||||
; @file Asm.asm
|
||||
;
|
||||
; @brief Cross platform MASM-written functions.
|
||||
;
|
||||
; @author Satoshi Tanda
|
||||
;
|
||||
; @copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
;
|
||||
include AsmCommon.inc
|
||||
|
||||
.const
|
||||
|
||||
VMX_OK equ 0
|
||||
VMX_ERROR_WITH_STATUS equ 1
|
||||
VMX_ERROR_WITHOUT_STATUS equ 2
|
||||
KTRAP_FRAME_SIZE equ 190h
|
||||
MACHINE_FRAME_SIZE equ 28h
|
||||
|
||||
.code
|
||||
|
||||
extern HandleVmExit : proc
|
||||
extern HandleVmExitFailure : proc
|
||||
|
||||
;
|
||||
; @brief An entry point for the hypervisor.
|
||||
;
|
||||
; @details The easiest way to understand this code is to see this as an entry
|
||||
; point of "VM-exit handler".
|
||||
;
|
||||
; Up on VM-exit, the processor starts executing this function as
|
||||
; condifured in the VmcsHostRip field of VMCS. At this time, the processor
|
||||
; is in the vmx-root mode, which allows the processor to execute any
|
||||
; instructions without causing VM-exit, and the processor is not governed
|
||||
; by EPT. The code executed from here emulates the instruction caused
|
||||
; VM-exit by, most typically, executing the same instruction on behalf of
|
||||
; the guest (see HandleCpuid for example), or changing relevant processor
|
||||
; state and letting the guest retry, for example, handling EPT violation.
|
||||
;
|
||||
; What we refer to as hypervisor is basically code executed in this
|
||||
; context. We also refer those code as a VM-exit handler.
|
||||
;
|
||||
AsmHypervisorEntryPoint proc frame
|
||||
;
|
||||
; Three not-well known techniques are used in this function in oder for
|
||||
; Windbg to display the stack trace of the guest while the VM-exit
|
||||
; handlers are being executed. You can skip this comment block and ignore
|
||||
; the first SUB instruction, .PUSHFRAME, .ALLOCSTACK and .ENDPROLOG if
|
||||
; not interested in. This is not essential for the hypervisor.
|
||||
;
|
||||
; 1) The use of the FRAME (above) attribute. This emits a function table
|
||||
; entry for this function in the .pdata section. See also:
|
||||
; https://docs.microsoft.com/en-us/cpp/assembler/masm/proc?view=vs-2017
|
||||
;
|
||||
; 2) The use of the .PUSHFRAME pseudo operation. This emits unwind data
|
||||
; indicating that a machine frame has been pushed on the stack. A machine
|
||||
; frame is usually pushed by the CPU in response to a trap or fault (
|
||||
; see: 6.12.1 Exception- or Interrupt-Handler Procedures), hence this
|
||||
; pseudo operation is often used for their handler code. (In Windows
|
||||
; kernel, the use of this pseudo operation is often wrapped in the
|
||||
; GENERATE_TRAP_FRAME macro.) In our case, since VM-exit does not push
|
||||
; the machine frame, we manually allocate it with the SUB instruction.
|
||||
; See also:
|
||||
; https://docs.microsoft.com/en-us/cpp/assembler/masm/dot-pushframe?view=vs-2017
|
||||
;
|
||||
; 3) The use of the .ALLOCSTACK pseudo operation. This also emits another
|
||||
; unwind data indicating how much the function uses stack. (This pseudo
|
||||
; code is often wrapped by the alloc_stack macro and used within the
|
||||
; GENERATE_TRAP_FRAME macro.) This function consumes 100h of stack on
|
||||
; the top of the KTRAP_FRAME size (minus the machine frame size, which
|
||||
; is already indicated by the .PUSHFRAME). See also:
|
||||
; https://docs.microsoft.com/en-us/cpp/assembler/masm/dot-allocstack?view=vs-2017
|
||||
;
|
||||
.pushframe
|
||||
sub rsp, KTRAP_FRAME_SIZE
|
||||
.allocstack KTRAP_FRAME_SIZE - MACHINE_FRAME_SIZE + 100h
|
||||
|
||||
;
|
||||
; Save the general purpose registers as they are not saved to and loaded
|
||||
; from VMCS. Note that the flag register does not have to be saved as it
|
||||
; is saved to VMCS on VM-exit and loaded from there on VM-entry.
|
||||
;
|
||||
; This operation subtracts RSP 8 * 15.
|
||||
;
|
||||
PUSHAQ
|
||||
|
||||
;
|
||||
; Save volatile XMM registers for the same reason as the general purpose
|
||||
; registers.
|
||||
;
|
||||
; 0x60 for XMM registers and 8 for alignment. Remember that those SSE
|
||||
; SEE instructions has to operate on 0x10 aligned memory.
|
||||
;
|
||||
sub rsp, 68h
|
||||
movaps xmmword ptr [rsp + 0h], xmm0
|
||||
movaps xmmword ptr [rsp + 10h], xmm1
|
||||
movaps xmmword ptr [rsp + 20h], xmm2
|
||||
movaps xmmword ptr [rsp + 30h], xmm3
|
||||
movaps xmmword ptr [rsp + 40h], xmm4
|
||||
movaps xmmword ptr [rsp + 50h], xmm5
|
||||
|
||||
;
|
||||
; Save the current stack pointer as an argument of the HandleVmExit
|
||||
; function.
|
||||
;
|
||||
mov rcx, rsp
|
||||
|
||||
;
|
||||
; All stack allocation is done now. Indicate the end of prologue with the
|
||||
; .ENDPROLOG pseudo operation as required by the FRAME attribute.
|
||||
;
|
||||
sub rsp, 20h
|
||||
.endprolog
|
||||
|
||||
;
|
||||
; BOOLEAN continueVm = HandleVmExit(stack);
|
||||
;
|
||||
call HandleVmExit
|
||||
add rsp, 20h
|
||||
|
||||
;
|
||||
; Restore XMM registers.
|
||||
;
|
||||
movaps xmm0, xmmword ptr [rsp + 0h]
|
||||
movaps xmm1, xmmword ptr [rsp + 10h]
|
||||
movaps xmm2, xmmword ptr [rsp + 20h]
|
||||
movaps xmm3, xmmword ptr [rsp + 30h]
|
||||
movaps xmm4, xmmword ptr [rsp + 40h]
|
||||
movaps xmm5, xmmword ptr [rsp + 50h]
|
||||
add rsp, 68h
|
||||
|
||||
;
|
||||
; if (continueVm == 0) goto ExitVm
|
||||
;
|
||||
test al, al
|
||||
jz ExitVm
|
||||
|
||||
;
|
||||
; Otherwise, restore the general purpose registers and resume execution
|
||||
; of the guest.
|
||||
;
|
||||
POPAQ
|
||||
vmresume
|
||||
jmp VmxError
|
||||
|
||||
ExitVm:
|
||||
;
|
||||
; Termination of the VM is requested. Executes VMXOFF and end
|
||||
; virtualization. At this point, some registers have specific values:
|
||||
; RAX = VpContexts
|
||||
; RCX = Guest RIP for the next instruction
|
||||
; RDX = Guest RSP
|
||||
; R8 = Guest RFLAGS
|
||||
;
|
||||
; Note that unlike VMRESUME, VMXOFF does not update RIP, RSP etc, and
|
||||
; just continues the next instruction (but the processor is no longer in
|
||||
; VMX-root mode). We will check if error occured with VMXOFF subsequently.
|
||||
;
|
||||
POPAQ
|
||||
vmxoff
|
||||
|
||||
;
|
||||
; if (ZF) goto VmxError
|
||||
; if (CF) goto VmxError
|
||||
;
|
||||
jz VmxError
|
||||
jc VmxError
|
||||
|
||||
;
|
||||
; Restore RFLAGS, RSP, and jump to the next instruction.
|
||||
;
|
||||
push r8
|
||||
popf
|
||||
mov rsp, rdx
|
||||
push rcx
|
||||
ret
|
||||
|
||||
VmxError:
|
||||
;
|
||||
; Any of VMX instructions failed. Unrecoverble. The most useful thing
|
||||
; to do here is probably to call a C-function that does diagnostics
|
||||
; like dumping VMCS.
|
||||
;
|
||||
PUSHAQ
|
||||
sub rsp, 68h
|
||||
movaps xmmword ptr [rsp + 0h], xmm0
|
||||
movaps xmmword ptr [rsp + 10h], xmm1
|
||||
movaps xmmword ptr [rsp + 20h], xmm2
|
||||
movaps xmmword ptr [rsp + 30h], xmm3
|
||||
movaps xmmword ptr [rsp + 40h], xmm4
|
||||
movaps xmmword ptr [rsp + 50h], xmm5
|
||||
mov rcx, rsp
|
||||
sub rsp, 20h
|
||||
call HandleVmExitFailure
|
||||
AsmHypervisorEntryPoint endp
|
||||
|
||||
;
|
||||
; @brief Invalidate translations derived from EPT
|
||||
;
|
||||
; @param[in] RCX - A type of invalidation.
|
||||
;
|
||||
; @param[in] RDX - A description of translations to invalidate.
|
||||
;
|
||||
; @return An appropriate VMX_RESULT value.
|
||||
;
|
||||
AsmInvept proc
|
||||
invept rcx, oword ptr [rdx]
|
||||
|
||||
;
|
||||
; if (ZF) goto ErrorWithCode
|
||||
; if (CF) goto ErrorWithoutCode
|
||||
; return VMX_OK
|
||||
;
|
||||
jz ErrorWithCode
|
||||
jc ErrorWithoutCode
|
||||
xor rax, rax
|
||||
ret
|
||||
|
||||
ErrorWithCode:
|
||||
mov rax, VMX_ERROR_WITH_STATUS
|
||||
ret
|
||||
|
||||
ErrorWithoutCode:
|
||||
mov rax, VMX_ERROR_WITHOUT_STATUS
|
||||
ret
|
||||
AsmInvept endp
|
||||
|
||||
;
|
||||
; @brief Invalidate translations based on VPID
|
||||
;
|
||||
; @param[in] RCX - A type of invalidation.
|
||||
;
|
||||
; @param[in] RDX - A description of translations to invalidate.
|
||||
;
|
||||
; @return An appropriate VMX_RESULT value.
|
||||
;
|
||||
AsmInvvpid proc
|
||||
invvpid rcx, oword ptr [rdx]
|
||||
|
||||
;
|
||||
; if (ZF) goto ErrorWithCode
|
||||
; if (CF) goto ErrorWithoutCode
|
||||
; return VMX_OK
|
||||
;
|
||||
jz ErrorWithCode
|
||||
jc errorWithoutCode
|
||||
xor rax, rax
|
||||
ret
|
||||
|
||||
ErrorWithCode:
|
||||
mov rax, VMX_ERROR_WITH_STATUS
|
||||
ret
|
||||
|
||||
errorWithoutCode:
|
||||
mov rax, VMX_ERROR_WITHOUT_STATUS
|
||||
ret
|
||||
AsmInvvpid endp
|
||||
|
||||
;
|
||||
; @brief Reads the access rights byte of the segment.
|
||||
;
|
||||
; @details See: LAR-Load Access Rights Byte
|
||||
;
|
||||
; @param[in] RCX - The selector of the segment to read.
|
||||
;
|
||||
; @return The access rights byte of the segment, or 0 on failure.
|
||||
;
|
||||
AsmLoadAccessRightsByte proc
|
||||
lar rax, rcx
|
||||
jz Success
|
||||
xor rax, rax
|
||||
Success:
|
||||
ret
|
||||
AsmLoadAccessRightsByte endp
|
||||
|
||||
;
|
||||
; @brief Issues hypercall.
|
||||
;
|
||||
; @param[in] RCX - The hypercall number.
|
||||
;
|
||||
; @param[in] RDX - The arbitrary 64bit parameter 1.
|
||||
;
|
||||
; @param[in] R8 - The arbitrary 64bit parameter 2.
|
||||
;
|
||||
; @param[in] R9 - The arbitrary 64bit parameter 3.
|
||||
;
|
||||
; @return The 64bit return value. Meaning is depends on RCX.
|
||||
;
|
||||
AsmVmxCall proc
|
||||
vmcall
|
||||
ret
|
||||
AsmVmxCall endp
|
||||
|
||||
;
|
||||
; @brief Returns the address of the return address from this function.
|
||||
;
|
||||
; @return The address of the return address from this function.
|
||||
;
|
||||
AsmGetCurrentInstructionPointer proc
|
||||
mov rax, [rsp]
|
||||
ret
|
||||
AsmGetCurrentInstructionPointer endp
|
||||
|
||||
;
|
||||
; @brief Returns the current value of RSP.
|
||||
;
|
||||
; @return The current value of RSP.
|
||||
;
|
||||
AsmGetCurrentStackPointer proc
|
||||
mov rax, rsp
|
||||
add rax, 8
|
||||
ret
|
||||
AsmGetCurrentStackPointer endp
|
||||
|
||||
end
|
||||
113
Sources/Asm.h
Normal file
@@ -0,0 +1,113 @@
|
||||
/*!
|
||||
@file Asm.h
|
||||
|
||||
@brief MASM-written functions.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#pragma once
|
||||
#include "Common.h"
|
||||
|
||||
#if defined(NTDDI_VERSION)
|
||||
#include "Platform/Windows/WinAsm.h"
|
||||
#else
|
||||
#include "Platform/EFI/EfiAsm.h"
|
||||
#endif
|
||||
|
||||
/*!
|
||||
@brief An entry point for the hypervisor.
|
||||
|
||||
@details See x64.asm.
|
||||
*/
|
||||
VOID
|
||||
AsmHypervisorEntryPoint (
|
||||
VOID
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Invalidate translations derived from EPT.
|
||||
|
||||
@param[in] InvEptType - A type of invalidation.
|
||||
|
||||
@param[in] InvEptDescriptor - A description of translations to invalidate.
|
||||
|
||||
@return An appropriate VMX_RESULT value.
|
||||
*/
|
||||
VMX_RESULT
|
||||
AsmInvept (
|
||||
_In_ INVEPT_TYPE InvEptType,
|
||||
_In_ CONST INVEPT_DESCRIPTOR* InvEptDescriptor
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Invalidate translations based on VPID.
|
||||
|
||||
@param[in] InvVpidType - A type of invalidation.
|
||||
|
||||
@param[in] InvVpidDescriptor - A description of translations to invalidate.
|
||||
|
||||
@return An appropriate VMX_RESULT value.
|
||||
*/
|
||||
VMX_RESULT
|
||||
AsmInvvpid (
|
||||
_In_ INVVPID_TYPE InvVpidType,
|
||||
_In_ CONST INVVPID_DESCRIPTOR* InvVpidDescriptor
|
||||
);
|
||||
|
||||
|
||||
/*!
|
||||
@brief Reads the access rights byte of the segment.
|
||||
|
||||
@details See: LAR-Load Access Rights Byte
|
||||
|
||||
@param[in] SegmentSelector - The selector of the segment to read.
|
||||
|
||||
@return The access rights byte of the segment, or 0 on failure.
|
||||
*/
|
||||
UINT32
|
||||
AsmLoadAccessRightsByte (
|
||||
_In_ UINT16 SegmentSelector
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Issues hypercall.
|
||||
|
||||
@param[in] HyperCallNumber - The hypercall number.
|
||||
|
||||
@param[in] Parameter1 - The arbitrary 64bit parameter 1.
|
||||
|
||||
@param[in] Parameter2 - The arbitrary 64bit parameter 2.
|
||||
|
||||
@param[in] Parameter3 - The arbitrary 64bit parameter 3.
|
||||
|
||||
@return The 64bit return value. Meaning is depends on HyperCallNumber.
|
||||
*/
|
||||
UINT64
|
||||
AsmVmxCall (
|
||||
_In_ UINT64 HyperCallNumber,
|
||||
_In_ UINT64 Parameter1,
|
||||
_In_ UINT64 Parameter2,
|
||||
_In_ UINT64 Parameter3
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Returns the address of the return address from this function.
|
||||
|
||||
@return The address of the return address from this function.
|
||||
*/
|
||||
UINT64
|
||||
AsmGetCurrentInstructionPointer (
|
||||
VOID
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Returns the current value of RSP.
|
||||
|
||||
@return The current value of RSP.
|
||||
*/
|
||||
UINT64
|
||||
AsmGetCurrentStackPointer (
|
||||
VOID
|
||||
);
|
||||
55
Sources/AsmCommon.inc
Normal file
@@ -0,0 +1,55 @@
|
||||
;
|
||||
; @file AsmCommon.inc
|
||||
;
|
||||
; @brief Cross platform MASM-written marcos.
|
||||
;
|
||||
; @author Satoshi Tanda
|
||||
;
|
||||
; @copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
;
|
||||
|
||||
;
|
||||
; @brief Saves all general purpose registers, except for RSP, to the stack.
|
||||
;
|
||||
; @details This macro does not alter the flag register.
|
||||
;
|
||||
PUSHAQ macro
|
||||
push rax
|
||||
push rcx
|
||||
push rdx
|
||||
push rbx
|
||||
push rbp
|
||||
push rsi
|
||||
push rdi
|
||||
push r8
|
||||
push r9
|
||||
push r10
|
||||
push r11
|
||||
push r12
|
||||
push r13
|
||||
push r14
|
||||
push r15
|
||||
endm
|
||||
|
||||
;
|
||||
; @brief Loads all general purpose registers, except for RSP, from the stack.
|
||||
;
|
||||
; @details This macro does not alter the flag register.
|
||||
;
|
||||
POPAQ macro
|
||||
pop r15
|
||||
pop r14
|
||||
pop r13
|
||||
pop r12
|
||||
pop r11
|
||||
pop r10
|
||||
pop r9
|
||||
pop r8
|
||||
pop rdi
|
||||
pop rsi
|
||||
pop rbp
|
||||
pop rbx
|
||||
pop rdx
|
||||
pop rcx
|
||||
pop rax
|
||||
endm
|
||||
72
Sources/Common.h
Normal file
@@ -0,0 +1,72 @@
|
||||
/*!
|
||||
@file Common.h
|
||||
|
||||
@brief Common things across the project.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#pragma once
|
||||
#if defined(NTDDI_VERSION)
|
||||
#include "Platform/Windows/WinCommon.h"
|
||||
#else
|
||||
#include "Platform/EFI/EfiCommon.h"
|
||||
#endif
|
||||
#include "Ia32.h"
|
||||
|
||||
//
|
||||
// The platform agnostic status type.
|
||||
//
|
||||
typedef _Return_type_success_(return >= 0) long MV_STATUS;
|
||||
|
||||
//
|
||||
// Possible status values.
|
||||
//
|
||||
#define MV_STATUS_SUCCESS ((MV_STATUS)0x00000000L)
|
||||
#define MV_STATUS_UNSUCCESSFUL ((MV_STATUS)0xC0000001L)
|
||||
#define MV_STATUS_ACCESS_DENIED ((MV_STATUS)0xC0000022L)
|
||||
#define MV_STATUS_INSUFFICIENT_RESOURCES ((MV_STATUS)0xC000009AL)
|
||||
#define MV_STATUS_HV_OPERATION_FAILED ((MV_STATUS)0xC0350071L)
|
||||
|
||||
//
|
||||
// The status check macro(s).
|
||||
//
|
||||
#define MV_ERROR(Status) ((UINT32)(Status) >= (UINT32)0xc0000000)
|
||||
|
||||
//
|
||||
// Computes offsets from the given pointer.
|
||||
//
|
||||
#define MV_ADD2PTR(Ptr, Value) ((VOID*)((UINT8*)(Ptr) + (Value)))
|
||||
|
||||
//
|
||||
// Hyper-V Hypervisor Top-Level Functional Specification (TLFS) related.
|
||||
//
|
||||
#define CPUID_HV_VENDOR_AND_MAX_FUNCTIONS ((UINT32)0x40000000)
|
||||
#define CPUID_HV_INTERFACE ((UINT32)0x40000001)
|
||||
#define CPUID_HV_MAX CPUID_HV_INTERFACE
|
||||
|
||||
//
|
||||
// Indicates the invalid physical address.
|
||||
//
|
||||
#define MV_INVALID_PHYSICAL_ADDRESS ((UINT64)-1)
|
||||
|
||||
//
|
||||
// Replacement of BOOLEAN for the flag to indicate whether the operation is read
|
||||
// or write.
|
||||
//
|
||||
typedef enum _OPERATION_TYPE
|
||||
{
|
||||
OperationRead,
|
||||
OperationWrite,
|
||||
} OPERATION_TYPE;
|
||||
|
||||
//
|
||||
// The result type of Microsoft VMX-intrinsic functions.
|
||||
//
|
||||
typedef enum _VMX_RESULT
|
||||
{
|
||||
VmxResultOk = 0, //!< Operation succeeded
|
||||
VmxResultErrorWithStatus = 1, //!< Operation failed with extended status available
|
||||
VmxResultErrorWithoutStatus = 2, //!< Operation failed without status available
|
||||
} VMX_RESULT;
|
||||
480
Sources/ExtendedPageTables.c
Normal file
@@ -0,0 +1,480 @@
|
||||
/*!
|
||||
@file ExtendedPageTables.c
|
||||
|
||||
@brief Functions for EPT handling.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#include "ExtendedPageTables.h"
|
||||
#include "Asm.h"
|
||||
#include "Ia32.h"
|
||||
#include "MemoryManager.h"
|
||||
#include "Platform.h"
|
||||
#include "Logger.h"
|
||||
#include "MemoryType.h"
|
||||
#include "Utils.h"
|
||||
|
||||
//
|
||||
// The set of EPT paging structure entries involved with to translate the GPA.
|
||||
//
|
||||
typedef struct _EPT_ENTRIES
|
||||
{
|
||||
EPT_PML4* Pml4e;
|
||||
union
|
||||
{
|
||||
EPDPTE_1GB* AsLargePage;
|
||||
EPDPTE* AsRegularPage;
|
||||
} Pdpte;
|
||||
union
|
||||
{
|
||||
EPDE_2MB* AsLargePage;
|
||||
EPDE* AsRegularPage;
|
||||
} Pde;
|
||||
EPTE* Pte;
|
||||
} EPT_ENTRIES;
|
||||
C_ASSERT(sizeof(EPT_ENTRIES) == sizeof(VOID*) * 4);
|
||||
|
||||
/*!
|
||||
@brief Cleans up all EPT entries and the table recursively.
|
||||
|
||||
@param[in,out] EptTable - A pointer to the EPT table to clean up.
|
||||
|
||||
@param[in] PageMapLevel - A level of the table.
|
||||
*/
|
||||
static
|
||||
VOID
|
||||
CleanupTables (
|
||||
_Inout_ _Pre_notnull_ EPT_ENTRY* EptTable,
|
||||
_In_ UINT32 PageMapLevel
|
||||
)
|
||||
{
|
||||
//
|
||||
// EPT PT does not have any subtables to delete, and so attempting so is
|
||||
// invalid.
|
||||
//
|
||||
MV_ASSERT(PageMapLevel != EPT_LEVEL_PTE);
|
||||
|
||||
//
|
||||
// Go through all 512 entries in the table.
|
||||
//
|
||||
for (UINT32 i = 0; i < 512; ++i)
|
||||
{
|
||||
EPT_ENTRY eptEntry;
|
||||
VOID* subTable;
|
||||
|
||||
eptEntry = EptTable[i];
|
||||
|
||||
//
|
||||
// Go to the next entry of the table if the entry is not initialized or
|
||||
// is a large page entry, which does not point to the next table.
|
||||
//
|
||||
if ((eptEntry.PageFrameNumber == 0) ||
|
||||
(eptEntry.LargePage != FALSE))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
//
|
||||
// Get the address of the subtable. Free it if it is EPT PTE (ie, the
|
||||
// current table is EPT PD) as EPT PTE does not have any more subtables.
|
||||
// Otherwise, perform the same operations against the subtable.
|
||||
//
|
||||
subTable = GetVirtualAddress(eptEntry.PageFrameNumber << PAGE_SHIFT);
|
||||
if (PageMapLevel == EPT_LEVEL_PDE)
|
||||
{
|
||||
MmFreePages(subTable);
|
||||
}
|
||||
else
|
||||
{
|
||||
CleanupTables(subTable, PageMapLevel - 1);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// All subtables referenced from this table were freed. It is OK to free
|
||||
// this table as well.
|
||||
//
|
||||
MmFreePages(EptTable);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Builds EPT paging structures for the range of 512GB managed by the
|
||||
given EPT PML4E.
|
||||
|
||||
@param[in,out] EptPml4 - The pointer to the EPT PML4.
|
||||
|
||||
@param[in] EptPml4Index - The index within the EPT PML4 to build translation.
|
||||
|
||||
@return MV_STATUS_SUCCESS on success; otherwise, an appropriate error code.
|
||||
*/
|
||||
static
|
||||
_Must_inspect_result_
|
||||
MV_STATUS
|
||||
BuildEptEntriesFor512Gb (
|
||||
_Inout_ EPT_PML4* EptPml4,
|
||||
_In_ UINT32 EptPml4Index
|
||||
)
|
||||
{
|
||||
static CONST UINT64 oneGigaByte = (1 * 1024 * 1024 * 1024);
|
||||
static CONST UINT64 twoMegaByte = (2 * 1024 * 1024);
|
||||
|
||||
MV_STATUS status;
|
||||
EPT_ENTRY defaultPermissions;
|
||||
UINT64 hostPhysicalAddress;
|
||||
IA32_MEMORY_TYPE memoryType;
|
||||
EPT_ENTRIES eptEntries;
|
||||
EPDPTE* eptPdpt;
|
||||
EPDE_2MB* eptPd;
|
||||
EPTE* eptPt;
|
||||
|
||||
defaultPermissions.Flags = 0;
|
||||
defaultPermissions.ReadAccess = TRUE;
|
||||
defaultPermissions.WriteAccess = TRUE;
|
||||
defaultPermissions.ExecuteAccess = TRUE;
|
||||
|
||||
//
|
||||
// Allocate the EPT PDPT and fill with the default all allow permissions, and
|
||||
// initialize the EPT PML4 with it.
|
||||
//
|
||||
eptPdpt = MmAllocatePages(1);
|
||||
if (eptPdpt == NULL)
|
||||
{
|
||||
status = MV_STATUS_INSUFFICIENT_RESOURCES;
|
||||
goto Exit;
|
||||
}
|
||||
__stosq((UINT64*)eptPdpt, defaultPermissions.Flags, EPT_PDPTE_ENTRY_COUNT);
|
||||
|
||||
eptEntries.Pml4e = &EptPml4[EptPml4Index];
|
||||
eptEntries.Pml4e->ReadAccess = TRUE;
|
||||
eptEntries.Pml4e->WriteAccess = TRUE;
|
||||
eptEntries.Pml4e->ExecuteAccess = TRUE;
|
||||
eptEntries.Pml4e->PageFrameNumber = (GetPhysicalAddress(eptPdpt) >> PAGE_SHIFT);
|
||||
|
||||
//
|
||||
// Initialize all 512 entries in the EPT PDPT pointed by the EPT PML4E.
|
||||
//
|
||||
for (UINT32 eptPdptIndex = 0; eptPdptIndex < EPT_PDPTE_ENTRY_COUNT; ++eptPdptIndex)
|
||||
{
|
||||
//
|
||||
// Allocate the EPT PD and fill with the default all allow permissions,
|
||||
// and initialize the EPT PDTE with it.
|
||||
//
|
||||
eptPd = MmAllocatePages(1);
|
||||
if (eptPd == NULL)
|
||||
{
|
||||
status = MV_STATUS_INSUFFICIENT_RESOURCES;
|
||||
goto Exit;
|
||||
}
|
||||
__stosq((UINT64*)eptPd, defaultPermissions.Flags, EPT_PDE_ENTRY_COUNT);
|
||||
|
||||
eptEntries.Pdpte.AsRegularPage = &eptPdpt[eptPdptIndex];
|
||||
eptEntries.Pdpte.AsRegularPage->PageFrameNumber = (GetPhysicalAddress(eptPd) >> PAGE_SHIFT);
|
||||
|
||||
//
|
||||
// Initialize all 512 entries in the EPT PD pointed by the EPT PDPTE.
|
||||
//
|
||||
for (UINT32 eptPdIndex = 0; eptPdIndex < EPT_PDE_ENTRY_COUNT; ++eptPdIndex)
|
||||
{
|
||||
eptEntries.Pde.AsLargePage = &eptPd[eptPdIndex];
|
||||
|
||||
//
|
||||
// Use the 2MB translation if the entire 2MB managed by this PDE has
|
||||
// same memory type. Otherwise, this PDE points to the EPT PT.
|
||||
//
|
||||
hostPhysicalAddress = ComputeAddressFromIndexes(EptPml4Index,
|
||||
eptPdptIndex,
|
||||
eptPdIndex,
|
||||
0);
|
||||
memoryType = GetMemoryTypeForRange(hostPhysicalAddress, twoMegaByte);
|
||||
if (memoryType != MEMORY_TYPE_INVALID)
|
||||
{
|
||||
eptEntries.Pde.AsLargePage->LargePage = TRUE;
|
||||
eptEntries.Pde.AsLargePage->MemoryType = memoryType;
|
||||
eptEntries.Pde.AsLargePage->PageFrameNumber = (hostPhysicalAddress >> PAGE_SHIFT_2BM);
|
||||
continue;
|
||||
}
|
||||
|
||||
//
|
||||
// Cannot be the single 2MB page. Allocate the EPT PT and fill with
|
||||
// the default all allow permissions, and initialize the EPT PDE with it.
|
||||
//
|
||||
eptPt = MmAllocatePages(1);
|
||||
if (eptPt == NULL)
|
||||
{
|
||||
status = MV_STATUS_INSUFFICIENT_RESOURCES;
|
||||
goto Exit;
|
||||
}
|
||||
__stosq((UINT64*)eptPt, defaultPermissions.Flags, EPT_PTE_ENTRY_COUNT);
|
||||
eptEntries.Pde.AsRegularPage->PageFrameNumber = (GetPhysicalAddress(eptPt) >> PAGE_SHIFT);
|
||||
|
||||
//
|
||||
// Initialize all 512 entries in the EPT PT pointed by the EPT PDE.
|
||||
//
|
||||
for (UINT32 eptPteIndex = 0; eptPteIndex < EPT_PTE_ENTRY_COUNT; ++eptPteIndex)
|
||||
{
|
||||
hostPhysicalAddress = ComputeAddressFromIndexes(EptPml4Index,
|
||||
eptPdptIndex,
|
||||
eptPdIndex,
|
||||
eptPteIndex);
|
||||
memoryType = GetMemoryTypeForRange(hostPhysicalAddress, PAGE_SIZE);
|
||||
MV_ASSERT(memoryType != MEMORY_TYPE_INVALID);
|
||||
|
||||
eptEntries.Pte = &eptPt[eptPteIndex];
|
||||
eptEntries.Pte->MemoryType = memoryType;
|
||||
eptEntries.Pte->PageFrameNumber = (hostPhysicalAddress >> PAGE_SHIFT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
status = MV_STATUS_SUCCESS;
|
||||
|
||||
Exit:
|
||||
return status;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Split a 2MB EPT PDE to 512 EPT PTEs.
|
||||
|
||||
@param[in,out] EptPdeLarge - The pointer to the 2MB EPT PDE to split.
|
||||
|
||||
@return MV_STATUS_SUCCESS on success; otherwise, an appropriate error code.
|
||||
*/
|
||||
static
|
||||
_Must_inspect_result_
|
||||
MV_STATUS
|
||||
Split2MbPage (
|
||||
_Inout_ EPDE_2MB* EptPdeLarge
|
||||
)
|
||||
{
|
||||
MV_STATUS status;
|
||||
EPDE* eptPde;
|
||||
EPTE* eptPt;
|
||||
UINT64 hostPaBase;
|
||||
UINT64 hostPaToMap;
|
||||
|
||||
MV_ASSERT(EptPdeLarge->LargePage != FALSE);
|
||||
|
||||
//
|
||||
// Allocate the EPT PT as we are going to split one 2MB page to 512 4KB pages.
|
||||
//
|
||||
eptPt = MmAllocatePages(1);
|
||||
if (eptPt == NULL)
|
||||
{
|
||||
status = MV_STATUS_INSUFFICIENT_RESOURCES;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
//
|
||||
// Clear the large page bit, and propagate the current permissions to the
|
||||
// all entries in the EPT PTE.
|
||||
//
|
||||
EptPdeLarge->LargePage = FALSE;
|
||||
__stosq((UINT64*)eptPt, EptPdeLarge->Flags, EPT_PTE_ENTRY_COUNT);
|
||||
|
||||
//
|
||||
// Update the page frame of each EPT PTE.
|
||||
//
|
||||
hostPaBase = (EptPdeLarge->PageFrameNumber << PAGE_SHIFT_2BM);
|
||||
for (UINT32 eptPtIndex = 0; eptPtIndex < EPT_PTE_ENTRY_COUNT; ++eptPtIndex)
|
||||
{
|
||||
hostPaToMap = hostPaBase + ((UINT64)eptPtIndex * PAGE_SIZE);
|
||||
eptPt[eptPtIndex].PageFrameNumber = (hostPaToMap >> PAGE_SHIFT);
|
||||
}
|
||||
|
||||
//
|
||||
// Finally, update the PDE by pointing to the EPT PT.
|
||||
//
|
||||
eptPde = (EPDE*)EptPdeLarge;
|
||||
eptPde->Reserved1 = eptPde->Reserved2 = eptPde->Reserved3 = eptPde->Reserved4 = 0;
|
||||
eptPde->PageFrameNumber = (GetPhysicalAddress(eptPt) >> PAGE_SHIFT);
|
||||
|
||||
status = MV_STATUS_SUCCESS;
|
||||
|
||||
Exit:
|
||||
return status;
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
MV_STATUS
|
||||
InitializeExtendedPageTables (
|
||||
EPT_CONTEXT* EptContext
|
||||
)
|
||||
{
|
||||
MV_STATUS status;
|
||||
EPT_PML4* eptPml4;
|
||||
|
||||
eptPml4 = MmAllocatePages(1);
|
||||
if (eptPml4 == NULL)
|
||||
{
|
||||
status = MV_STATUS_INSUFFICIENT_RESOURCES;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
status = BuildEptEntriesFor512Gb(eptPml4, 0);
|
||||
if (MV_ERROR(status))
|
||||
{
|
||||
LOG_ERROR("BuildEptEntriesFor512Gb failed : %08x", status);
|
||||
goto Exit;
|
||||
}
|
||||
EptContext->EptPml4 = eptPml4;
|
||||
|
||||
//
|
||||
// All EPT initialization completed successfully. Set up the
|
||||
// extended-page-table pointer (EPTP).
|
||||
//
|
||||
// The EPTP is the top level data structure for EPT that governs the vast
|
||||
// majority of EPT related behavior and is written to VMCS. This structure
|
||||
// can be understood as CR3 equivalent in EPT as it holds a physical address
|
||||
// of the EPT PML4 table.
|
||||
// See: 24.6.11 Extended-Page-Table Pointer (EPTP)
|
||||
//
|
||||
|
||||
//
|
||||
// Specify the memory-type used for accessing to any of EPT paging-structures.
|
||||
// We use the memory-type as those structures are allocated, that is the
|
||||
// write-back memory type. We assume that CR0.CD (cache disabled) is 0, which
|
||||
// should always be the case here.
|
||||
// See: 28.2.6.1 Memory Type Used for Accessing EPT Paging Structures
|
||||
//
|
||||
EptContext->EptPointer.MemoryType = MEMORY_TYPE_WRITE_BACK;
|
||||
|
||||
//
|
||||
// "This value is 1 less than the EPT page-walk length."
|
||||
// "The EPT translation mechanism (...) uses a page-walk length of 4".
|
||||
// See: Table 24-8. Format of Extended-Page-Table Pointer
|
||||
// See: 28.2.2 EPT Translation Mechanism
|
||||
//
|
||||
EptContext->EptPointer.PageWalkLength = EPT_PAGE_WALK_LENGTH_4;
|
||||
|
||||
//
|
||||
// PFN of the EPT PML4 table.
|
||||
//
|
||||
EptContext->EptPointer.PageFrameNumber = GetPhysicalAddress(EptContext->EptPml4) >> PAGE_SHIFT;
|
||||
|
||||
Exit:
|
||||
if (MV_ERROR(status))
|
||||
{
|
||||
if (eptPml4 != NULL)
|
||||
{
|
||||
CleanupTables((EPT_ENTRY*)eptPml4, EPT_LEVEL_PML4E);
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
VOID
|
||||
CleanupExtendedPageTables (
|
||||
EPT_CONTEXT* EptContext
|
||||
)
|
||||
{
|
||||
CleanupTables((EPT_ENTRY*)EptContext->EptPml4, EPT_LEVEL_PML4E);
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
MV_STATUS
|
||||
UpdateExtendPageTables (
|
||||
EPT_PML4* EptPml4,
|
||||
UINT64 GuestPhysicalAddress,
|
||||
CONST UINT64* HostPhysicalAddress,
|
||||
CONST EPT_ENTRY* Permissions
|
||||
)
|
||||
{
|
||||
MV_STATUS status;
|
||||
ADDRESS_TRANSLATION_HELPER helper;
|
||||
EPT_ENTRIES eptEntries;
|
||||
EPDPTE_1GB* eptPdpt;
|
||||
EPDE_2MB* eptPd;
|
||||
EPTE* eptPt;
|
||||
|
||||
MV_ASSERT(ARGUMENT_PRESENT(HostPhysicalAddress) ||
|
||||
ARGUMENT_PRESENT(Permissions));
|
||||
|
||||
//
|
||||
// Locate the EPT PML4E for the GPA.
|
||||
//
|
||||
helper.AsUInt64 = GuestPhysicalAddress;
|
||||
eptEntries.Pml4e = &EptPml4[helper.AsIndex.Pml4];
|
||||
MV_ASSERT(MV_IS_EPT_ENTRY_PRESENT(eptEntries.Pml4e) != FALSE);
|
||||
|
||||
//
|
||||
// Locate the EPT PDPTE for the GPA. The entry must not be large page as we
|
||||
// do not use 1GB page.
|
||||
//
|
||||
eptPdpt = GetVirtualAddress(eptEntries.Pml4e->PageFrameNumber << PAGE_SHIFT);
|
||||
eptEntries.Pdpte.AsLargePage = &eptPdpt[helper.AsIndex.Pdpt];
|
||||
MV_ASSERT(MV_IS_EPT_ENTRY_PRESENT(eptEntries.Pdpte.AsRegularPage) != FALSE);
|
||||
MV_ASSERT(eptEntries.Pdpte.AsLargePage->LargePage == FALSE);
|
||||
|
||||
//
|
||||
// Locate the EPT PDE for the GPA. If the entry is the 2MB page, split it.
|
||||
//
|
||||
eptPd = GetVirtualAddress(eptEntries.Pdpte.AsRegularPage->PageFrameNumber << PAGE_SHIFT);
|
||||
eptEntries.Pde.AsLargePage = &eptPd[helper.AsIndex.Pd];
|
||||
MV_ASSERT(MV_IS_EPT_ENTRY_PRESENT(eptEntries.Pde.AsRegularPage) != FALSE);
|
||||
|
||||
if (eptEntries.Pde.AsLargePage->LargePage != FALSE)
|
||||
{
|
||||
status = Split2MbPage(eptEntries.Pde.AsLargePage);
|
||||
if (MV_ERROR(status))
|
||||
{
|
||||
goto Exit;
|
||||
}
|
||||
}
|
||||
|
||||
MV_ASSERT(eptEntries.Pdpte.AsLargePage->LargePage == FALSE);
|
||||
|
||||
//
|
||||
// Locate the EPT PTE for the GPA and update translation and permissions as
|
||||
// requested.
|
||||
//
|
||||
eptPt = GetVirtualAddress(eptEntries.Pde.AsRegularPage->PageFrameNumber << PAGE_SHIFT);
|
||||
eptEntries.Pte = &eptPt[helper.AsIndex.Pt];
|
||||
if (ARGUMENT_PRESENT(HostPhysicalAddress))
|
||||
{
|
||||
eptEntries.Pte->PageFrameNumber = (*HostPhysicalAddress >> PAGE_SHIFT);
|
||||
}
|
||||
if (ARGUMENT_PRESENT(Permissions))
|
||||
{
|
||||
eptEntries.Pte->ReadAccess = Permissions->ReadAccess;
|
||||
eptEntries.Pte->WriteAccess = Permissions->WriteAccess;
|
||||
eptEntries.Pte->ExecuteAccess = Permissions->ExecuteAccess;
|
||||
}
|
||||
|
||||
status = MV_STATUS_SUCCESS;
|
||||
|
||||
Exit:
|
||||
return status;
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
VOID
|
||||
InvalidateEptDerivedCache (
|
||||
UINT64 EptPointer
|
||||
)
|
||||
{
|
||||
INVEPT_DESCRIPTOR descriptor;
|
||||
INVEPT_TYPE type;
|
||||
|
||||
RtlZeroMemory(&descriptor, sizeof(descriptor));
|
||||
descriptor.EptPointer = EptPointer;
|
||||
type = (EptPointer == 0) ? InveptAllContext : InveptSingleContext;
|
||||
MV_VERIFY(AsmInvept(type, &descriptor) == VmxResultOk);
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
VOID
|
||||
InvalidateVpidDerivedCache (
|
||||
UINT16 VirtualProcessorId
|
||||
)
|
||||
{
|
||||
INVVPID_DESCRIPTOR descriptor;
|
||||
INVVPID_TYPE type;
|
||||
|
||||
RtlZeroMemory(&descriptor, sizeof(descriptor));
|
||||
descriptor.Vpid = VirtualProcessorId;
|
||||
type = (VirtualProcessorId == 0) ? InvvpidAllContext : InvvpidSingleContext;
|
||||
MV_VERIFY(AsmInvvpid(type, &descriptor)== VmxResultOk);
|
||||
}
|
||||
132
Sources/ExtendedPageTables.h
Normal file
@@ -0,0 +1,132 @@
|
||||
/*!
|
||||
@file ExtendedPageTables.h
|
||||
|
||||
@brief Functions for EPT handling.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 -, Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#pragma once
|
||||
#include "Common.h"
|
||||
|
||||
/*!
|
||||
@brief Checks whether the EPT entry is present.
|
||||
|
||||
@param[in] EptEntry - The pointer to the EPT entry to check.
|
||||
|
||||
@return TRUE when the entry is present.
|
||||
*/
|
||||
#define MV_IS_EPT_ENTRY_PRESENT(EptEntry) \
|
||||
(((EptEntry)->ReadAccess != FALSE) || \
|
||||
((EptEntry)->WriteAccess != FALSE) || \
|
||||
((EptEntry)->ExecuteAccess != FALSE))
|
||||
|
||||
/*!
|
||||
@brief Copies the permission of the EPT entry to the other entry.
|
||||
|
||||
@param[out] Destination - The pointer to the EPT entry to updates its permission.
|
||||
|
||||
@param[in] EptEntry - The pointer to the EPT entry to copy its permission from.
|
||||
*/
|
||||
#define MV_COPY_EPT_ENTRY_PERMISSIONS(Destination, EptEntry) \
|
||||
(Destination)->ReadAccess = (EptEntry)->ReadAccess; \
|
||||
(Destination)->WriteAccess = (EptEntry)->WriteAccess; \
|
||||
(Destination)->ExecuteAccess = (EptEntry)->ExecuteAccess
|
||||
|
||||
/*!
|
||||
@brief Aggregates the permission of the EPT entry to the other entry.
|
||||
|
||||
@param[out] Destination - The pointer to the EPT entry to updates its permission.
|
||||
|
||||
@param[in] EptEntry - The pointer to the EPT entry to aggregate its permission from.
|
||||
*/
|
||||
#define MV_AGGREGATE_EPT_ENTRY_PERMISSIONS(Destination, EptEntry) \
|
||||
(Destination)->ReadAccess &= (EptEntry)->ReadAccess; \
|
||||
(Destination)->WriteAccess &= (EptEntry)->WriteAccess; \
|
||||
(Destination)->ExecuteAccess &= (EptEntry)->ExecuteAccess
|
||||
|
||||
//
|
||||
// Holds the context specific to EPT.
|
||||
//
|
||||
typedef struct _EPT_CONTEXT
|
||||
{
|
||||
//
|
||||
// EPTP written to VMCS.
|
||||
//
|
||||
EPT_POINTER EptPointer;
|
||||
|
||||
//
|
||||
// The virtual address of the EPT PML4.
|
||||
//
|
||||
EPT_PML4* EptPml4;
|
||||
} EPT_CONTEXT;
|
||||
|
||||
/*!
|
||||
@brief Initializes EPT with pass-through style configurations.
|
||||
|
||||
@param[in,out] EptContext - A pointer to the EPT context to initialize.
|
||||
|
||||
@return MV_STATUS_SUCCESS on success; otherwise, an appropriate error code.
|
||||
*/
|
||||
_Must_inspect_result_
|
||||
MV_STATUS
|
||||
InitializeExtendedPageTables (
|
||||
_Inout_ EPT_CONTEXT* EptContext
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Cleans up EPT context.
|
||||
|
||||
@param[in,out] EptContext - A pointer to the EPT context to clean up.
|
||||
*/
|
||||
VOID
|
||||
CleanupExtendedPageTables (
|
||||
_Inout_ EPT_CONTEXT* EptContext
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Updates the EPT PTE for the given GPA with new HPA and permissions.
|
||||
|
||||
@param[in] EptPml4 - The pointer to the EPT PML4.
|
||||
|
||||
@param[in] GuestPhysicalAddress - The GPA to update its EPT PTE.
|
||||
|
||||
@param[in] HostPhysicalAddress - The pointer to the HPA to update to. If NULL
|
||||
is specified, the function does not change the translation.
|
||||
|
||||
@param[in] Permissions - The pointer to the new permission to update to. If
|
||||
NULL is specified, the functions does not change the permissions.
|
||||
|
||||
@return MV_STATUS_SUCCESS on success; otherwise, an appropriate error code.
|
||||
*/
|
||||
_Must_inspect_result_
|
||||
MV_STATUS
|
||||
UpdateExtendPageTables (
|
||||
_In_ EPT_PML4* EptPml4,
|
||||
_In_ UINT64 GuestPhysicalAddress,
|
||||
_In_opt_ CONST UINT64* HostPhysicalAddress,
|
||||
_In_opt_ CONST EPT_ENTRY* Permissions
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Invalidate guest-physical and combined caches.
|
||||
|
||||
@param[in] EptPointer - The EPT pointer to invalidate associated caches. If
|
||||
0 is specified, caches associated with any EPT pointers are invalidated.
|
||||
*/
|
||||
VOID
|
||||
InvalidateEptDerivedCache (
|
||||
_In_ UINT64 EptPointer
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Invalidate liner and combined caches.
|
||||
|
||||
@param[in] VirtualProcessorId - The VPID to invalidate associated caches. If
|
||||
0 is specified, caches associated with any VPID are invalidated.
|
||||
*/
|
||||
VOID
|
||||
InvalidateVpidDerivedCache (
|
||||
_In_ UINT16 VirtualProcessorId
|
||||
);
|
||||
87
Sources/HostInitialization.h
Normal file
@@ -0,0 +1,87 @@
|
||||
/*!
|
||||
@file HostInitialization.h
|
||||
|
||||
@brief Functions for host environment initialization.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#pragma once
|
||||
#include "Common.h"
|
||||
|
||||
/*!
|
||||
@brief Initializes the host environment.
|
||||
*/
|
||||
VOID
|
||||
InitializeHostEnvironment (
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Returns the host CR3.
|
||||
|
||||
@return The host CR3.
|
||||
*/
|
||||
CR3
|
||||
GetHostCr3 (
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Returns the pointer to the host IDTR.
|
||||
|
||||
@return The pointer to the host IDTR.
|
||||
*/
|
||||
CONST IDTR*
|
||||
GetHostIdtr (
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Sets up the task state segment (TSS) and the TR register.
|
||||
|
||||
@details On EFI, this functions takes a copy of the existing GDT into NewGdt,
|
||||
adds a new entry into it, which points to NewTss, then, updates the GDTR
|
||||
and TR to point to the NewGdt and the newly added entry. Those updated
|
||||
GDTR and TR may be used as both host and guest GDTR/TR.
|
||||
|
||||
On Windows, this function is no-op.
|
||||
|
||||
@param[in,out] NewTss - The pointer to buffer to be used as the task state
|
||||
segment.
|
||||
|
||||
@param[out] NewGdt - The pointer to the buffet to be used as the new GDT.
|
||||
This will be initialized with the contents of the current GDT with the
|
||||
new entry for TSS.
|
||||
|
||||
@param[in] NewGdtSize - The size of NewGdt in bytes.
|
||||
|
||||
@param[out] OriginalGdtr - The pointer to the GDTR to receive the value before
|
||||
this function updates.
|
||||
*/
|
||||
VOID
|
||||
InitializeGdt (
|
||||
_Inout_ TASK_STATE_SEGMENT_64* NewTss,
|
||||
_Out_writes_bytes_(NewGdtSize) SEGMENT_DESCRIPTOR_64* NewGdt,
|
||||
_In_ UINT64 NewGdtSize,
|
||||
_Out_ GDTR* OriginalGdtr
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Restores the GDTR to the specified value.
|
||||
|
||||
@details On EFI, this function updates the current GDTR, however, does not
|
||||
restore the TR to the original value. This is because the original value
|
||||
is expected to be zero, which cannot write to TR anymore (causes #GP).
|
||||
Because of this, TR will point to an invalid entry in the restored GDT.
|
||||
This is an unsolvable issue unless we reuse the existing GDT instead of
|
||||
creating a copy, which does not work on VMware Workstation due to the
|
||||
physical address hosting the GDT is not modifiable. The only sane
|
||||
workaround would be to disallow unloading of the MiniVisor module.
|
||||
|
||||
On Windows, this function is no-op.
|
||||
|
||||
@param[in] OriginalGdtr - The pointer to the GDTR to restore to.
|
||||
*/
|
||||
VOID
|
||||
CleanupGdt (
|
||||
_In_ CONST GDTR* OriginalGdtr
|
||||
);
|
||||
915
Sources/HostMain.c
Normal file
@@ -0,0 +1,915 @@
|
||||
/*!
|
||||
@file HostMain.c
|
||||
|
||||
@brief Functions for VM-exit handling.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 -, Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#include "HostMain.h"
|
||||
#include "HostUtils.h"
|
||||
#include "Public.h"
|
||||
#include "Logger.h"
|
||||
#include "ExtendedPageTables.h"
|
||||
#include "HostVmcall.h"
|
||||
#include "HostNesting.h"
|
||||
|
||||
//
|
||||
// The trap frame structure for x64 systems. This is structure is used to help
|
||||
// Windbg to construct call stack while VM-exit handlers are being executed.
|
||||
// Since this is for Windbg, this is a Windows specific structure, and its
|
||||
// layout can be found as nt!_KTRAP_FRAME. In our case, only the Rip and Rsp
|
||||
// members are used since those are only fields needed to be set for Windbg to
|
||||
// show proper call stack.
|
||||
//
|
||||
typedef struct _WINDOWS_KTRAP_FRAME
|
||||
{
|
||||
UINT64 Reserved1[45];
|
||||
UINT64 Rip;
|
||||
UINT64 Reserved2[2];
|
||||
UINT64 Rsp;
|
||||
UINT64 Reserved3;
|
||||
} WINDOWS_KTRAP_FRAME;
|
||||
C_ASSERT(sizeof(WINDOWS_KTRAP_FRAME) == 0x190);
|
||||
|
||||
//
|
||||
// The layout of hypervisor stack when the C-handler (HandleVmExit) is executed.
|
||||
// GuestRegisters and TrapFrame are pushed in assembler part.
|
||||
//
|
||||
typedef struct _INITIAL_HYPERVISOR_STACK
|
||||
{
|
||||
GUEST_REGISTERS GuestRegisters;
|
||||
WINDOWS_KTRAP_FRAME TrapFrame;
|
||||
HYPERVISOR_CONTEXT HypervisorContext;
|
||||
} INITIAL_HYPERVISOR_STACK;
|
||||
|
||||
/*!
|
||||
@brief Handles VM-exit due to execution of the RDMSR and WRMSR instruction.
|
||||
|
||||
@param[in,out] GuestContext - A pointer to the guest context.
|
||||
|
||||
@param[in] OperationType - The type of the operation.
|
||||
*/
|
||||
static
|
||||
VOID
|
||||
HandleMsrAccess (
|
||||
_Inout_ GUEST_CONTEXT* GuestContext,
|
||||
_In_ OPERATION_TYPE OperationType
|
||||
)
|
||||
{
|
||||
IA32_MSR_ADDRESS msr;
|
||||
UINT64 value;
|
||||
|
||||
msr = (IA32_MSR_ADDRESS)GuestContext->StackBasedRegisters->Rcx;
|
||||
if (OperationType == OperationRead)
|
||||
{
|
||||
//
|
||||
// Performs the same read on behalf of the guest.
|
||||
//
|
||||
value = __readmsr(msr);
|
||||
GuestContext->StackBasedRegisters->Rax = value & MAXUINT32;
|
||||
GuestContext->StackBasedRegisters->Rdx = (value >> 32) & MAXUINT32;
|
||||
}
|
||||
else
|
||||
{
|
||||
//
|
||||
// Performs the same write on behalf of the guest.
|
||||
//
|
||||
value = (GuestContext->StackBasedRegisters->Rax & MAXUINT32) |
|
||||
((GuestContext->StackBasedRegisters->Rdx & MAXUINT32) << 32);
|
||||
__writemsr(msr, value);
|
||||
}
|
||||
|
||||
AdvanceGuestInstructionPointer(GuestContext);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Handles VM-exit due to execution of the RDMSR instruction.
|
||||
|
||||
@param[in,out] GuestContext - A pointer to the guest context.
|
||||
*/
|
||||
static
|
||||
VOID
|
||||
HandleMsrRead (
|
||||
_Inout_ GUEST_CONTEXT* GuestContext
|
||||
)
|
||||
{
|
||||
HandleMsrAccess(GuestContext, OperationRead);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Handles VM-exit due to execution of the WRMSR instruction.
|
||||
|
||||
@param[in,out] GuestContext - A pointer to the guest context.
|
||||
*/
|
||||
static
|
||||
VOID
|
||||
HandleMsrWrite (
|
||||
_Inout_ GUEST_CONTEXT* GuestContext
|
||||
)
|
||||
{
|
||||
HandleMsrAccess(GuestContext, OperationWrite);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Handles VM-exit due to execution of the CPUID instruction.
|
||||
|
||||
@param[in,out] GuestContext - A pointer to the guest context.
|
||||
*/
|
||||
static
|
||||
VOID
|
||||
HandleCpuid (
|
||||
_Inout_ GUEST_CONTEXT* GuestContext
|
||||
)
|
||||
{
|
||||
int registers[4];
|
||||
int leaf, subLeaf;
|
||||
|
||||
//
|
||||
// Execute the same instruction on behalf of the guest.
|
||||
//
|
||||
leaf = (int)GuestContext->StackBasedRegisters->Rax;
|
||||
subLeaf = (int)GuestContext->StackBasedRegisters->Rcx;
|
||||
__cpuidex(registers, leaf, subLeaf);
|
||||
|
||||
//
|
||||
// Then, modify results when necessary.
|
||||
//
|
||||
switch (leaf)
|
||||
{
|
||||
case CPUID_VERSION_INFORMATION:
|
||||
//
|
||||
// Do not indicate the VMX feature is available on this processor to
|
||||
// prevent other hypervisor tries to use it, as MiniVisor does not
|
||||
// support nesting the hypervisor.
|
||||
//
|
||||
ClearFlag(registers[2], CPUID_FEATURE_INFORMATION_ECX_VIRTUAL_MACHINE_EXTENSIONS_FLAG);
|
||||
break;
|
||||
|
||||
case CPUID_HV_VENDOR_AND_MAX_FUNCTIONS:
|
||||
//
|
||||
// Return a maximum supported hypervisor CPUID leaf range and a vendor
|
||||
// ID signature as required by the spec.
|
||||
//
|
||||
registers[0] = CPUID_HV_MAX;
|
||||
registers[1] = 'iniM'; // "MiniVisor "
|
||||
registers[2] = 'osiV';
|
||||
registers[3] = ' r';
|
||||
break;
|
||||
|
||||
case CPUID_HV_INTERFACE:
|
||||
//
|
||||
// Return non Hv#1 value. This indicate that the MiniVisor does NOT
|
||||
// conform to the Microsoft hypervisor interface.
|
||||
//
|
||||
registers[0] = '0#vH'; // Hv#0
|
||||
registers[1] = registers[2] = registers[3] = 0;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
//
|
||||
// Update guest's GPRs with results.
|
||||
//
|
||||
GuestContext->StackBasedRegisters->Rax = (UINT64)registers[0];
|
||||
GuestContext->StackBasedRegisters->Rbx = (UINT64)registers[1];
|
||||
GuestContext->StackBasedRegisters->Rcx = (UINT64)registers[2];
|
||||
GuestContext->StackBasedRegisters->Rdx = (UINT64)registers[3];
|
||||
|
||||
AdvanceGuestInstructionPointer(GuestContext);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Handles VM-exit due to execution of the VMCALL instruction.
|
||||
|
||||
@param[in,out] GuestContext - A pointer to the guest context.
|
||||
*/
|
||||
static
|
||||
VOID
|
||||
HandleVmCall (
|
||||
_Inout_ GUEST_CONTEXT* GuestContext
|
||||
)
|
||||
{
|
||||
UINT64 hypercallNumber;
|
||||
|
||||
//
|
||||
// Our hypercall takes the hypercall number in RCX.
|
||||
//
|
||||
hypercallNumber = GuestContext->StackBasedRegisters->Rcx;
|
||||
if (hypercallNumber >= VmcallInvalid)
|
||||
{
|
||||
//
|
||||
// Undefined hypercall number. Inject #UD.
|
||||
//
|
||||
InjectInterruption(HardwareException, InvalidOpcode, FALSE, 0);
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
//
|
||||
// Executes the corresponding hypercall handler.
|
||||
//
|
||||
k_VmcallHandlers[hypercallNumber](GuestContext);
|
||||
|
||||
AdvanceGuestInstructionPointer(GuestContext);
|
||||
|
||||
Exit:
|
||||
return;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Handles VM-exit due to execution of the XSETBV instruction.
|
||||
|
||||
@param[in,out] GuestContext - A pointer to the guest context.
|
||||
*/
|
||||
static
|
||||
VOID
|
||||
HandleXsetbv (
|
||||
_Inout_ GUEST_CONTEXT* GuestContext
|
||||
)
|
||||
{
|
||||
UINT64 value;
|
||||
CR4 hostCr4;
|
||||
|
||||
//
|
||||
// Execution of the XSETBV instruction requires the OSXSAVE bit to be set,
|
||||
// and the host CR4 may not have it. Set the bit and execute the instruction.
|
||||
//
|
||||
hostCr4.Flags = __readcr4();
|
||||
hostCr4.OsXsave = TRUE;
|
||||
__writecr4(hostCr4.Flags);
|
||||
|
||||
value = (GuestContext->StackBasedRegisters->Rax & MAXUINT32) |
|
||||
((GuestContext->StackBasedRegisters->Rdx & MAXUINT32) << 32);
|
||||
_xsetbv((UINT32)GuestContext->StackBasedRegisters->Rcx, value);
|
||||
|
||||
AdvanceGuestInstructionPointer(GuestContext);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Returns the address of where the guest general purpose register that
|
||||
corresponds to the given index is stored.
|
||||
|
||||
@param[in,out] GuestContext - A pointer to the guest context.
|
||||
|
||||
@param[in] RegisterIndex - The index provided by VMCS up on VM-exit.
|
||||
|
||||
@return The address of where the guest general purpose register that
|
||||
corresponds to the given index is stored.
|
||||
*/
|
||||
static
|
||||
UINT64*
|
||||
SelectEffectiveRegister (
|
||||
_Inout_ GUEST_CONTEXT* GuestContext,
|
||||
_In_ UINT64 RegisterIndex
|
||||
)
|
||||
{
|
||||
UINT64* effectiveRegister;
|
||||
|
||||
switch (RegisterIndex)
|
||||
{
|
||||
case 0: effectiveRegister = &GuestContext->StackBasedRegisters->Rax; break;
|
||||
case 1: effectiveRegister = &GuestContext->StackBasedRegisters->Rcx; break;
|
||||
case 2: effectiveRegister = &GuestContext->StackBasedRegisters->Rdx; break;
|
||||
case 3: effectiveRegister = &GuestContext->StackBasedRegisters->Rbx; break;
|
||||
case 4: effectiveRegister = &GuestContext->VmcsBasedRegisters.Rsp; break;
|
||||
case 5: effectiveRegister = &GuestContext->StackBasedRegisters->Rbp; break;
|
||||
case 6: effectiveRegister = &GuestContext->StackBasedRegisters->Rsi; break;
|
||||
case 7: effectiveRegister = &GuestContext->StackBasedRegisters->Rdi; break;
|
||||
case 8: effectiveRegister = &GuestContext->StackBasedRegisters->R8; break;
|
||||
case 9: effectiveRegister = &GuestContext->StackBasedRegisters->R9; break;
|
||||
case 10: effectiveRegister = &GuestContext->StackBasedRegisters->R10; break;
|
||||
case 11: effectiveRegister = &GuestContext->StackBasedRegisters->R11; break;
|
||||
case 12: effectiveRegister = &GuestContext->StackBasedRegisters->R12; break;
|
||||
case 13: effectiveRegister = &GuestContext->StackBasedRegisters->R13; break;
|
||||
case 14: effectiveRegister = &GuestContext->StackBasedRegisters->R14; break;
|
||||
case 15: effectiveRegister = &GuestContext->StackBasedRegisters->R15; break;
|
||||
default: MV_PANIC(); break;
|
||||
}
|
||||
|
||||
return effectiveRegister;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Handles VM-exit due to execution of access to the control register.
|
||||
|
||||
@param[in,out] GuestContext - A pointer to the guest context.
|
||||
*/
|
||||
static
|
||||
VOID
|
||||
HandleCrAccess (
|
||||
_Inout_ GUEST_CONTEXT* GuestContext
|
||||
)
|
||||
{
|
||||
VMX_EXIT_QUALIFICATION_MOV_CR qualification;
|
||||
UINT64 newValue;
|
||||
CR0 newCr0, currentCr0;
|
||||
CR4 newCr4;
|
||||
|
||||
qualification.Flags = VmxRead(VMCS_EXIT_QUALIFICATION);
|
||||
newValue = *SelectEffectiveRegister(GuestContext,
|
||||
qualification.GeneralPurposeRegister);
|
||||
|
||||
switch (qualification.AccessType)
|
||||
{
|
||||
case VMX_EXIT_QUALIFICATION_ACCESS_MOV_TO_CR:
|
||||
//
|
||||
// Update what the guest sees (ie, VMCS_CTRL_CRn_READ_SHADOW) exactly
|
||||
// as the guest requested, then update the actual state (VMCS_GUEST_CRn)
|
||||
// after applying the FIXED0 and FIXED1 MSRs. This ensures VMX continues
|
||||
// to function, for example, by keeping the VMXE bit set.
|
||||
//
|
||||
switch (qualification.ControlRegister)
|
||||
{
|
||||
case VMX_EXIT_QUALIFICATION_REGISTER_CR0:
|
||||
newCr0.Flags = newValue;
|
||||
currentCr0.Flags = VmxRead(VMCS_GUEST_CR0);
|
||||
VmxWrite(VMCS_CTRL_CR0_READ_SHADOW, newCr0.Flags);
|
||||
VmxWrite(VMCS_GUEST_CR0, AdjustGuestCr0(newCr0).Flags);
|
||||
if (currentCr0.PagingEnable != newCr0.PagingEnable)
|
||||
{
|
||||
SwitchGuestPagingMode(newCr0);
|
||||
}
|
||||
break;
|
||||
case VMX_EXIT_QUALIFICATION_REGISTER_CR4:
|
||||
newCr4.Flags = newValue;
|
||||
VmxWrite(VMCS_CTRL_CR4_READ_SHADOW, newCr4.Flags);
|
||||
VmxWrite(VMCS_GUEST_CR4, AdjustGuestCr4(newCr4).Flags);
|
||||
break;
|
||||
default:
|
||||
MV_PANIC();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
MV_PANIC();
|
||||
}
|
||||
|
||||
AdvanceGuestInstructionPointer(GuestContext);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Handles VM-exit due to EPT violation.
|
||||
|
||||
@param[in,out] GuestContext - A pointer to the guest context.
|
||||
*/
|
||||
static
|
||||
VOID
|
||||
HandleEptViolation (
|
||||
_Inout_ GUEST_CONTEXT* GuestContext
|
||||
)
|
||||
{
|
||||
UINT64 faultPhysicalAddress;
|
||||
|
||||
//
|
||||
// As of now, this should never happen and can panic here. We inject #GP(0)
|
||||
// instead, because this is what you may want to do once some EPT related logic
|
||||
// such as protecting pages is written.
|
||||
//
|
||||
faultPhysicalAddress = VmxRead(VMCS_GUEST_PHYSICAL_ADDRESS);
|
||||
LOG_WARNING("IP:%016llx PA:%016llx",
|
||||
GuestContext->VmcsBasedRegisters.Rip,
|
||||
faultPhysicalAddress);
|
||||
|
||||
InjectInterruption(HardwareException, GeneralProtection, TRUE, 0);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Handles VM-exit due to EPT misconfiguration.
|
||||
|
||||
@param[in,out] GuestContext - A pointer to the guest context.
|
||||
*/
|
||||
static
|
||||
VOID
|
||||
HandleEptMisconfig (
|
||||
_Inout_ GUEST_CONTEXT* GuestContext
|
||||
)
|
||||
{
|
||||
UINT64 faultPhysicalAddress;
|
||||
ADDRESS_TRANSLATION_HELPER helper;
|
||||
|
||||
//
|
||||
// This is a programming error that should never happen. The most helpful
|
||||
// thing can be done is to dump information for diagnostics.
|
||||
//
|
||||
DumpGuestState();
|
||||
faultPhysicalAddress = VmxRead(VMCS_GUEST_PHYSICAL_ADDRESS);
|
||||
LOG_ERROR("IP:%016llx PA:%016llx",
|
||||
GuestContext->VmcsBasedRegisters.Rip,
|
||||
faultPhysicalAddress);
|
||||
LOG_ERROR("EPT_PML4:%016llx EPTP:%016llx",
|
||||
GuestContext->Contexts->EptContext->EptPml4->Flags,
|
||||
GuestContext->Contexts->EptContext->EptPointer.Flags);
|
||||
helper.AsUInt64 = faultPhysicalAddress;
|
||||
LOG_ERROR("Indexes: %llu %llu %llu %llu",
|
||||
helper.AsIndex.Pml4,
|
||||
helper.AsIndex.Pdpt,
|
||||
helper.AsIndex.Pd,
|
||||
helper.AsIndex.Pt);
|
||||
|
||||
MV_PANIC();
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Handles VM-exit due to interrupt or exception.
|
||||
|
||||
@param[in,out] GuestContext - A pointer to the guest context.
|
||||
*/
|
||||
static
|
||||
VOID
|
||||
HandleExceptionOrNmi (
|
||||
_Inout_ GUEST_CONTEXT* GuestContext
|
||||
)
|
||||
{
|
||||
static BOOLEAN isKeInitAmd64SpecificStateCalled;
|
||||
VMEXIT_INTERRUPT_INFORMATION interruptInfo;
|
||||
|
||||
//
|
||||
// This handler is specialized for skipping main initialization of PatchGuard.
|
||||
// When #DE occurs with the guest state that seems to be during initialization
|
||||
// of PatchGuard, suppress it. Otherwise, just inject the exception (pass-through).
|
||||
//
|
||||
interruptInfo.Flags = (UINT32)VmxRead(VMCS_VMEXIT_INTERRUPTION_INFORMATION);
|
||||
MV_ASSERT(interruptInfo.InterruptionType == HardwareException);
|
||||
MV_ASSERT(interruptInfo.Vector == DivideError);
|
||||
|
||||
//
|
||||
// The below check detects division that will trigger initialization of
|
||||
// PatchGuard. The very instruction is this on all versions of Windows.
|
||||
// idiv r8d
|
||||
// The IDIV instruction in this form performs (int64)edx:eax / (int32)r8d,
|
||||
// and cases #DE, in particular when a positive result is greater than
|
||||
// 0x7fffffff. If the kernel debugger is not attached and disabled, the NT
|
||||
// kernel executes this instruction with the following values, resulting in
|
||||
// the #DE.
|
||||
// ((int64)0xffffffff80000000 / -1) => 0x80000000
|
||||
// When this condition is detected for the first time, we do not inject #DE
|
||||
// to the guest to avoid initialization of main PatchGuard logic.
|
||||
//
|
||||
if ((isKeInitAmd64SpecificStateCalled == FALSE) &&
|
||||
((UINT32)GuestContext->StackBasedRegisters->Rax == (UINT32)0x80000000) &&
|
||||
((UINT32)GuestContext->StackBasedRegisters->Rdx == (UINT32)0xffffffff) &&
|
||||
((UINT32)GuestContext->StackBasedRegisters->R8 == (UINT32)-1))
|
||||
{
|
||||
LOG_DEBUG("KeInitAmd64SpecificState triggered #DE. Ignoring it.");
|
||||
isKeInitAmd64SpecificStateCalled = TRUE;
|
||||
AdvanceGuestInstructionPointer(GuestContext);
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
//
|
||||
// Otherwise, just forward the exception.
|
||||
//
|
||||
InjectInterruption(interruptInfo.InterruptionType, interruptInfo.Vector, FALSE, 0);
|
||||
|
||||
Exit:
|
||||
return;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Handles VM-exit due to the INIT signal.
|
||||
|
||||
@param[in,out] GuestContext - A pointer to the guest context.
|
||||
*/
|
||||
static
|
||||
VOID
|
||||
HandleInitSignal (
|
||||
_Inout_ GUEST_CONTEXT* GuestContext
|
||||
)
|
||||
{
|
||||
UNREFERENCED_PARAMETER(GuestContext);
|
||||
|
||||
//
|
||||
// Simply put the processor into the "wait-for-SIPI" state.
|
||||
//
|
||||
// "All the processors on the system bus (...) execute the multiple processor
|
||||
// (MP) initialization protocol. ... The application (non-BSP) processors
|
||||
// (APs) go into a Wait For Startup IPI (SIPI) state while the BSP is executing
|
||||
// initialization code."
|
||||
//
|
||||
// See: 9.1 INITIALIZATION OVERVIEW
|
||||
//
|
||||
// "Upon receiving an INIT ..., the processor responds by beginning the
|
||||
// initialization process of the processor core and the local APIC. The state
|
||||
// of the local APIC following an INIT reset is the same as it is after a
|
||||
// power-up or hardware reset ... . This state is also referred to at the
|
||||
// "wait-for-SIPI" state."
|
||||
//
|
||||
// See: 10.4.7.3 Local APIC State After an INIT Reset (“Wait-for-SIPI” State)
|
||||
//
|
||||
VmxWrite(VMCS_GUEST_ACTIVITY_STATE, VmxWaitForSipi);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Handles VM-exit due to the Startup-IPI (SIPI) signal.
|
||||
|
||||
@param[in,out] GuestContext - A pointer to the guest context.
|
||||
*/
|
||||
static
|
||||
VOID
|
||||
HandleStartupIpi (
|
||||
_Inout_ GUEST_CONTEXT* GuestContext
|
||||
)
|
||||
{
|
||||
int regs[4];
|
||||
CPUID_EAX_01 cpuVersionInfo;
|
||||
UINT64 extendedModel;
|
||||
UINT64 vector;
|
||||
VMX_SEGMENT_ACCESS_RIGHTS accessRights;
|
||||
IA32_VMX_ENTRY_CTLS_REGISTER vmEntryControls;
|
||||
CR0 newCr0;
|
||||
CR4 newCr4;
|
||||
|
||||
//
|
||||
// Intel SDM suggest to issue SIPI twice. This does not appear to be
|
||||
// implemented by many of kernels such as FreeBSD, Linux and Windows, but
|
||||
// ignore it if this occurs, as we already initialized the processor with
|
||||
// the first SIPI.
|
||||
//
|
||||
if (VmxRead(VMCS_GUEST_ACTIVITY_STATE) == VmxActive)
|
||||
{
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
//
|
||||
// Initializes the processor to the state after INIT as described in the
|
||||
// Intel SDM.
|
||||
//
|
||||
// See: Table 9-1. IA-32 and Intel 64 Processor States Following Power-up,
|
||||
// Reset, or INIT
|
||||
//
|
||||
VmxWrite(VMCS_GUEST_RFLAGS, RFLAGS_READ_AS_1_FLAG);
|
||||
VmxWrite(VMCS_GUEST_RIP, 0xfff0);
|
||||
VmxWrite(VMCS_CTRL_CR0_READ_SHADOW, CR0_EXTENSION_TYPE_FLAG);
|
||||
__writecr2(0);
|
||||
VmxWrite(VMCS_GUEST_CR3, 0);
|
||||
VmxWrite(VMCS_CTRL_CR4_READ_SHADOW, 0);
|
||||
|
||||
//
|
||||
// Actual guest CR0 and CR4 must fulfill requirements for VMX. Apply those.
|
||||
//
|
||||
newCr0.Flags = CR0_EXTENSION_TYPE_FLAG;
|
||||
newCr4.Flags = 0;
|
||||
VmxWrite(VMCS_GUEST_CR0, AdjustGuestCr0(newCr0).Flags);
|
||||
VmxWrite(VMCS_GUEST_CR4, AdjustGuestCr4(newCr4).Flags);
|
||||
|
||||
accessRights.Flags = 0;
|
||||
|
||||
accessRights.Type = SEGMENT_DESCRIPTOR_TYPE_CODE_EXECUTE_READ_ACCESSED;
|
||||
accessRights.DescriptorType = TRUE;
|
||||
accessRights.Present = TRUE;
|
||||
VmxWrite(VMCS_GUEST_CS_SELECTOR, 0xf000);
|
||||
VmxWrite(VMCS_GUEST_CS_BASE, 0xffff0000);
|
||||
VmxWrite(VMCS_GUEST_CS_LIMIT, 0xffff);
|
||||
VmxWrite(VMCS_GUEST_CS_ACCESS_RIGHTS, accessRights.Flags);
|
||||
|
||||
accessRights.Type = SEGMENT_DESCRIPTOR_TYPE_DATA_READ_WRITE_ACCESSED;
|
||||
VmxWrite(VMCS_GUEST_SS_SELECTOR, 0);
|
||||
VmxWrite(VMCS_GUEST_SS_BASE, 0);
|
||||
VmxWrite(VMCS_GUEST_SS_LIMIT, 0xffff);
|
||||
VmxWrite(VMCS_GUEST_SS_ACCESS_RIGHTS, accessRights.Flags);
|
||||
VmxWrite(VMCS_GUEST_DS_SELECTOR, 0);
|
||||
VmxWrite(VMCS_GUEST_DS_BASE, 0);
|
||||
VmxWrite(VMCS_GUEST_DS_LIMIT, 0xffff);
|
||||
VmxWrite(VMCS_GUEST_DS_ACCESS_RIGHTS, accessRights.Flags);
|
||||
VmxWrite(VMCS_GUEST_ES_SELECTOR, 0);
|
||||
VmxWrite(VMCS_GUEST_ES_BASE, 0);
|
||||
VmxWrite(VMCS_GUEST_ES_LIMIT, 0xffff);
|
||||
VmxWrite(VMCS_GUEST_ES_ACCESS_RIGHTS, accessRights.Flags);
|
||||
VmxWrite(VMCS_GUEST_FS_SELECTOR, 0);
|
||||
VmxWrite(VMCS_GUEST_FS_BASE, 0);
|
||||
VmxWrite(VMCS_GUEST_FS_LIMIT, 0xffff);
|
||||
VmxWrite(VMCS_GUEST_FS_ACCESS_RIGHTS, accessRights.Flags);
|
||||
VmxWrite(VMCS_GUEST_GS_SELECTOR, 0);
|
||||
VmxWrite(VMCS_GUEST_GS_BASE, 0);
|
||||
VmxWrite(VMCS_GUEST_GS_LIMIT, 0xffff);
|
||||
VmxWrite(VMCS_GUEST_GS_ACCESS_RIGHTS, accessRights.Flags);
|
||||
|
||||
__cpuid(regs, CPUID_VERSION_INFORMATION);
|
||||
cpuVersionInfo.CpuidVersionInformation.Flags = regs[0];
|
||||
extendedModel = cpuVersionInfo.CpuidVersionInformation.ExtendedModelId;
|
||||
GuestContext->StackBasedRegisters->Rdx = 0x600 | (extendedModel << 16);
|
||||
GuestContext->StackBasedRegisters->Rbx = 0;
|
||||
GuestContext->StackBasedRegisters->Rcx = 0;
|
||||
GuestContext->StackBasedRegisters->Rsi = 0;
|
||||
GuestContext->StackBasedRegisters->Rdi = 0;
|
||||
GuestContext->StackBasedRegisters->Rbp = 0;
|
||||
VmxWrite(VMCS_GUEST_RSP, 0);
|
||||
|
||||
VmxWrite(VMCS_GUEST_GDTR_BASE, 0);
|
||||
VmxWrite(VMCS_GUEST_GDTR_LIMIT, 0xffff);
|
||||
VmxWrite(VMCS_GUEST_IDTR_BASE, 0);
|
||||
VmxWrite(VMCS_GUEST_IDTR_LIMIT, 0xffff);
|
||||
|
||||
accessRights.Type = SEGMENT_DESCRIPTOR_TYPE_LDT;
|
||||
accessRights.DescriptorType = FALSE;
|
||||
VmxWrite(VMCS_GUEST_LDTR_SELECTOR, 0);
|
||||
VmxWrite(VMCS_GUEST_LDTR_BASE, 0);
|
||||
VmxWrite(VMCS_GUEST_LDTR_LIMIT, 0xffff);
|
||||
VmxWrite(VMCS_GUEST_LDTR_ACCESS_RIGHTS, accessRights.Flags);
|
||||
|
||||
accessRights.Type = SEGMENT_DESCRIPTOR_TYPE_TSS_BUSY;
|
||||
VmxWrite(VMCS_GUEST_TR_SELECTOR, 0);
|
||||
VmxWrite(VMCS_GUEST_TR_BASE, 0);
|
||||
VmxWrite(VMCS_GUEST_TR_LIMIT, 0xffff);
|
||||
VmxWrite(VMCS_GUEST_TR_ACCESS_RIGHTS, accessRights.Flags);
|
||||
|
||||
__writedr(0, 0);
|
||||
__writedr(1, 0);
|
||||
__writedr(2, 0);
|
||||
__writedr(3, 0);
|
||||
__writedr(6, 0xffff0ff0);
|
||||
VmxWrite(VMCS_GUEST_DR7, 0x400);
|
||||
|
||||
GuestContext->StackBasedRegisters->R8 = 0;
|
||||
GuestContext->StackBasedRegisters->R9 = 0;
|
||||
GuestContext->StackBasedRegisters->R10 = 0;
|
||||
GuestContext->StackBasedRegisters->R11 = 0;
|
||||
GuestContext->StackBasedRegisters->R12 = 0;
|
||||
GuestContext->StackBasedRegisters->R13 = 0;
|
||||
GuestContext->StackBasedRegisters->R14 = 0;
|
||||
GuestContext->StackBasedRegisters->R15 = 0;
|
||||
|
||||
//
|
||||
// Those registers are supposed to be cleared but that is not implemented here.
|
||||
// - IA32_XSS
|
||||
// - BNDCFGU
|
||||
// - BND0-BND3
|
||||
// - IA32_BNDCFGS
|
||||
//
|
||||
|
||||
VmxWrite(VMCS_GUEST_EFER, 0);
|
||||
VmxWrite(VMCS_GUEST_FS_BASE, 0);
|
||||
VmxWrite(VMCS_GUEST_GS_BASE, 0);
|
||||
|
||||
vmEntryControls.Flags = VmxRead(VMCS_CTRL_VMENTRY_CONTROLS);
|
||||
vmEntryControls.Ia32EModeGuest = FALSE;
|
||||
VmxWrite(VMCS_CTRL_VMENTRY_CONTROLS, vmEntryControls.Flags);
|
||||
|
||||
//
|
||||
// Then, emulate effects of SIPI by making further changes.
|
||||
//
|
||||
// "For a start-up IPI (SIPI), the exit qualification contains the SIPI
|
||||
// vector information in bits 7:0. Bits 63:8 of the exit qualification are
|
||||
// cleared to 0."
|
||||
// See: 27.2.1 Basic VM-Exit Information
|
||||
//
|
||||
vector = VmxRead(VMCS_EXIT_QUALIFICATION);
|
||||
|
||||
//
|
||||
// "At the end of the boot-strap procedure, the BSP sets ... broadcasts a
|
||||
// SIPI message to all the APs in the system. Here, the SIPI message contains
|
||||
// a vector to the BIOS AP initialization code (at 000VV000H, where VV is the
|
||||
// vector contained in the SIPI message)."
|
||||
//
|
||||
// See: 8.4.3 MP Initialization Protocol Algorithm for MP Systems
|
||||
//
|
||||
VmxWrite(VMCS_GUEST_CS_SELECTOR, ((UINT64)vector) << 8);
|
||||
VmxWrite(VMCS_GUEST_CS_BASE, ((UINT64)vector) << 12);
|
||||
VmxWrite(VMCS_GUEST_RIP, 0);
|
||||
|
||||
//
|
||||
// Changing CR0.PG from 1 to 0 *using the MOV instruction* invalidates TLBs.
|
||||
// The case with INIT-SIPI does not seem to be documented but we do so just
|
||||
// in case. Emulate this invalidating combined caches (GVA to HPA translation
|
||||
// caches).
|
||||
//
|
||||
InvalidateVpidDerivedCache((UINT16)VmxRead(VMCS_CTRL_VIRTUAL_PROCESSOR_IDENTIFIER));
|
||||
|
||||
//
|
||||
// Done
|
||||
//
|
||||
VmxWrite(VMCS_GUEST_ACTIVITY_STATE, VmxActive);
|
||||
|
||||
Exit:
|
||||
return;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Handles VM-exit. This is the C-level entry point of the hypervisor.
|
||||
|
||||
@details This function is called the actual entry point of hypervisor, the
|
||||
AsmHypervisorEntryPoint function, after it preserved guest registers to
|
||||
stack as necessary. Such register values can be referenced and updated
|
||||
through the point to the stack location as provided by the Stack
|
||||
parameter. Those values are restored in the AsmHypervisorEntryPoint
|
||||
function after this function is executed and reflected to the guest.
|
||||
|
||||
Any hypervisor code including this and the AsmHypervisorEntryPoint
|
||||
functions are executed while interrupt is disabled via RFLAGS.IF being
|
||||
0 (See: 27.5.3 Loading Host RIP, RSP, and RFLAGS). This means IPI, if
|
||||
requested, is never delivered and causes deadlock. This condition is
|
||||
essentially equal to IRQL being HIGH_LEVEL (i.e., at a higher IRQL than
|
||||
IPI_LEVEL), and so, it is unsafe to call any Windows provided API that
|
||||
is not stated as callable at HIGH_LEVEL.
|
||||
|
||||
@param[in,out] Stack - A pointer to the hypervisor stack containing the
|
||||
guest register values.
|
||||
|
||||
@return TRUE when virtualization should continue and the VMRESUME instruction
|
||||
should be executed. FALSE when it should end and the VMXOFF instruction
|
||||
should be executed.
|
||||
*/
|
||||
_Must_inspect_result_
|
||||
BOOLEAN
|
||||
HandleVmExit (
|
||||
_Inout_ INITIAL_HYPERVISOR_STACK* Stack
|
||||
)
|
||||
{
|
||||
VMX_VMEXIT_REASON vmExitReason;
|
||||
GUEST_CONTEXT guestContext;
|
||||
|
||||
//
|
||||
// "Determine the exit reason through a VMREAD of the exit-reason field in
|
||||
// the working-VMCS."
|
||||
// See: 31.7 HANDLING OF VM EXITS
|
||||
//
|
||||
vmExitReason.Flags = (UINT32)VmxRead(VMCS_EXIT_REASON);
|
||||
|
||||
//
|
||||
// Copy some pointers to a single structure for ease of use.
|
||||
//
|
||||
guestContext.ContinueVm = TRUE;
|
||||
guestContext.Contexts = &Stack->HypervisorContext;
|
||||
guestContext.StackBasedRegisters = &Stack->GuestRegisters;
|
||||
|
||||
//
|
||||
// Read some of commonly used guest registers that are stored in the VMCS
|
||||
// (instead of stack). Reading them are useful for debugging, as we cannot
|
||||
// tell which instruction caused the VM-exit if VMCS_GUEST_RIP is not read,
|
||||
// for example. Note that those values are not automatically written back to
|
||||
// the VMCS. When any of those values should be updated and reflected to the
|
||||
// guest, the VMWRITE instruction (the VmxWrite function) should be used.
|
||||
//
|
||||
guestContext.VmcsBasedRegisters.Rflags.Flags = VmxRead(VMCS_GUEST_RFLAGS);
|
||||
guestContext.VmcsBasedRegisters.Rsp = VmxRead(VMCS_GUEST_RSP);
|
||||
guestContext.VmcsBasedRegisters.Rip = VmxRead(VMCS_GUEST_RIP);
|
||||
|
||||
//
|
||||
// Update the _KTRAP_FRAME structure values in hypervisor stack, so that
|
||||
// Windbg can reconstruct call stack of the guest during debug session.
|
||||
// This is optional but very useful thing to do for debugging.
|
||||
//
|
||||
Stack->TrapFrame.Rsp = guestContext.VmcsBasedRegisters.Rsp;
|
||||
Stack->TrapFrame.Rip = guestContext.VmcsBasedRegisters.Rip +
|
||||
VmxRead(VMCS_VMEXIT_INSTRUCTION_LENGTH);
|
||||
|
||||
//
|
||||
// Comment in this for debugging the handlers below.
|
||||
//
|
||||
//MV_DEBUG_BREAK();
|
||||
|
||||
switch (vmExitReason.BasicExitReason)
|
||||
{
|
||||
case VMX_EXIT_REASON_EXCEPTION_OR_NMI:
|
||||
HandleExceptionOrNmi(&guestContext);
|
||||
break;
|
||||
|
||||
case VMX_EXIT_REASON_INIT_SIGNAL:
|
||||
HandleInitSignal(&guestContext);
|
||||
break;
|
||||
|
||||
case VMX_EXIT_REASON_STARTUP_IPI:
|
||||
HandleStartupIpi(&guestContext);
|
||||
break;
|
||||
|
||||
case VMX_EXIT_REASON_EXECUTE_CPUID:
|
||||
HandleCpuid(&guestContext);
|
||||
break;
|
||||
|
||||
case VMX_EXIT_REASON_EXECUTE_VMCALL:
|
||||
HandleVmCall(&guestContext);
|
||||
break;
|
||||
|
||||
case VMX_EXIT_REASON_MOV_CR:
|
||||
HandleCrAccess(&guestContext);
|
||||
break;
|
||||
|
||||
case VMX_EXIT_REASON_EXECUTE_RDMSR:
|
||||
HandleMsrRead(&guestContext);
|
||||
break;
|
||||
|
||||
case VMX_EXIT_REASON_EXECUTE_WRMSR:
|
||||
HandleMsrWrite(&guestContext);
|
||||
break;
|
||||
|
||||
case VMX_EXIT_REASON_EPT_VIOLATION:
|
||||
HandleEptViolation(&guestContext);
|
||||
break;
|
||||
|
||||
case VMX_EXIT_REASON_EPT_MISCONFIGURATION:
|
||||
HandleEptMisconfig(&guestContext);
|
||||
break;
|
||||
|
||||
case VMX_EXIT_REASON_EXECUTE_XSETBV:
|
||||
HandleXsetbv(&guestContext);
|
||||
break;
|
||||
|
||||
default:
|
||||
DumpGuestState();
|
||||
DumpHostState();
|
||||
DumpControl();
|
||||
LOG_DEBUG("VM-exit reason (Full) = %x", vmExitReason.Flags);
|
||||
MV_PANIC();
|
||||
}
|
||||
|
||||
if (guestContext.ContinueVm == FALSE)
|
||||
{
|
||||
//
|
||||
// End of virtualization is requested. prevent undesired retention of
|
||||
// cache.
|
||||
//
|
||||
// "Software can use the INVVPID instruction with the "all-context" INVVPID
|
||||
// type (...) immediately prior to execution of the VMXOFF instruction."
|
||||
// "Software can use the INVEPT instruction with the "all-context" INVEPT
|
||||
// type (...) immediately prior to execution of the VMXOFF instruction."
|
||||
// See: 28.3.3.3 Guidelines for Use of the INVVPID Instruction
|
||||
// See: 28.3.3.4 Guidelines for Use of the INVEPT Instruction
|
||||
//
|
||||
InvalidateEptDerivedCache(0);
|
||||
InvalidateVpidDerivedCache(0);
|
||||
}
|
||||
|
||||
return guestContext.ContinueVm;
|
||||
}
|
||||
|
||||
typedef struct _EXCEPTION_STACK
|
||||
{
|
||||
UINT64 R15;
|
||||
UINT64 R14;
|
||||
UINT64 R13;
|
||||
UINT64 R12;
|
||||
UINT64 R11;
|
||||
UINT64 R10;
|
||||
UINT64 R9;
|
||||
UINT64 R8;
|
||||
UINT64 Rdi;
|
||||
UINT64 Rsi;
|
||||
UINT64 Rbp;
|
||||
UINT64 Rbx;
|
||||
UINT64 Rdx;
|
||||
UINT64 Rcx;
|
||||
UINT64 Rax;
|
||||
UINT64 InterruptNumber;
|
||||
UINT64 ErrorCode;
|
||||
UINT64 Rip;
|
||||
UINT64 Cs;
|
||||
UINT64 Rflags;
|
||||
} EXCEPTION_STACK;
|
||||
|
||||
/*!
|
||||
@brief Handles the interrupt and exception occurred during execution of the
|
||||
host.
|
||||
|
||||
@details On Windows, this function is unused because the host uses the same
|
||||
IDT as that of the guest. All interrupts and exceptions are handled by
|
||||
the NT kernel.
|
||||
|
||||
@param[in] Stack - The pointer to the hypervisor stack containing the
|
||||
guest register values.
|
||||
*/
|
||||
VOID
|
||||
HandleHostException (
|
||||
_In_ CONST EXCEPTION_STACK* Stack
|
||||
)
|
||||
{
|
||||
DumpGuestState();
|
||||
DumpHostState();
|
||||
DumpControl();
|
||||
LOG_ERROR("Exception or interrupt 0x%llx(0x%llx)", Stack->InterruptNumber, Stack->ErrorCode);
|
||||
LOG_ERROR("RIP - %016llx, CS - %016llx, RFLAGS - %016llx", Stack->Rip, Stack->Cs, Stack->Rflags);
|
||||
LOG_ERROR("RAX - %016llx, RCX - %016llx, RDX - %016llx", Stack->Rax, Stack->Rcx, Stack->Rdx);
|
||||
LOG_ERROR("RBX - %016llx, RSP - %016llx, RBP - %016llx", Stack->Rbx, 0ull, Stack->Rbp);
|
||||
LOG_ERROR("RSI - %016llx, RDI - %016llx", Stack->Rsi, Stack->Rdi);
|
||||
LOG_ERROR("R8 - %016llx, R9 - %016llx, R10 - %016llx", Stack->R8, Stack->R9, Stack->R10);
|
||||
LOG_ERROR("R11 - %016llx, R12 - %016llx, R13 - %016llx", Stack->R11, Stack->R12, Stack->R13);
|
||||
LOG_ERROR("R14 - %016llx, R15 - %016llx", Stack->R14, Stack->R15);
|
||||
MV_PANIC();
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Handles error occurred on attempt to exit to the guest.
|
||||
|
||||
@param[in] Stack - The pointer to the hypervisor stack containing the
|
||||
guest register values.
|
||||
*/
|
||||
VOID
|
||||
HandleVmExitFailure (
|
||||
_In_ CONST INITIAL_HYPERVISOR_STACK* Stack
|
||||
)
|
||||
{
|
||||
VMX_ERROR_NUMBER vmxErrorNumber;
|
||||
VMX_VMEXIT_REASON vmExitReason;
|
||||
|
||||
UNREFERENCED_PARAMETER(Stack);
|
||||
|
||||
vmxErrorNumber = (VMX_ERROR_NUMBER)VmxRead(VMCS_VM_INSTRUCTION_ERROR);
|
||||
vmExitReason.Flags = (UINT32)VmxRead(VMCS_EXIT_REASON);
|
||||
|
||||
DumpGuestState();
|
||||
DumpHostState();
|
||||
DumpControl();
|
||||
LOG_ERROR("VM-exit reason (full) = %x, Error = %ul", vmExitReason.Flags, vmxErrorNumber);
|
||||
MV_PANIC();
|
||||
}
|
||||
11
Sources/HostMain.h
Normal file
@@ -0,0 +1,11 @@
|
||||
/*!
|
||||
@file HostMain.h
|
||||
|
||||
@brief Functions for VM-exit handling.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 -, Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#pragma once
|
||||
#include "Common.h"
|
||||
79
Sources/HostNesting.h
Normal file
@@ -0,0 +1,79 @@
|
||||
/*!
|
||||
@file HostNesting.h
|
||||
|
||||
@brief Incomplete nesting related code. Do not study.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#pragma once
|
||||
#include "Common.h"
|
||||
#include "Ia32.h"
|
||||
#include "HostUtils.h"
|
||||
#include "ExtendedPageTables.h"
|
||||
|
||||
typedef enum _VMX_OPERATION
|
||||
{
|
||||
VmxOperationNotInVmxOperation,
|
||||
VmxOperationRoot,
|
||||
VmxOperationNonRoot,
|
||||
} VMX_OPERATION;
|
||||
|
||||
typedef enum _VMCS_LAUNCH_STATE
|
||||
{
|
||||
LaunchStateUnintialized,
|
||||
LaunchStateClear,
|
||||
LaunchStateLaunched,
|
||||
} VMCS_LAUNCH_STATE;
|
||||
|
||||
typedef struct _NEXTED_VMX_CONTEXT
|
||||
{
|
||||
VMX_OPERATION VmxOperation;
|
||||
VMCS_LAUNCH_STATE VmcsLaunchState;
|
||||
|
||||
//
|
||||
// The physical address of the VMXON region (used by L1 for L2)
|
||||
//
|
||||
UINT64 Vmxon12Pa;
|
||||
|
||||
//
|
||||
// The physical addresses of VMCSs (used by L0 for L1 and L2)
|
||||
//
|
||||
UINT64 Vmcs01Pa;
|
||||
UINT64 Vmcs02Pa;
|
||||
|
||||
//
|
||||
// Current VMCS from the point of view of the nested VMM (used by L1 for L2)
|
||||
//
|
||||
UINT64 Vmcs12Pa;
|
||||
|
||||
//
|
||||
// EPT related data (used by L0 for L2)
|
||||
//
|
||||
EPT_CONTEXT Ept02Context;
|
||||
EPT_PML4* EptPml4_02;
|
||||
|
||||
//
|
||||
// VMCS (used by L0 for L2).
|
||||
//
|
||||
DECLSPEC_ALIGN(PAGE_SIZE) VMCS Vmcs02;
|
||||
} NEXTED_VMX_CONTEXT;
|
||||
|
||||
VOID
|
||||
HandleVmx (
|
||||
_Inout_ GUEST_CONTEXT* GuestContext,
|
||||
_In_ UINT32 ExitReason
|
||||
);
|
||||
|
||||
VOID
|
||||
EmulateVmExitForL1Vmm (
|
||||
_Inout_ GUEST_CONTEXT* GuestContext,
|
||||
_In_ UINT32 ExitReason
|
||||
);
|
||||
|
||||
BOOLEAN
|
||||
IsVmExitForL1 (
|
||||
CONST GUEST_CONTEXT* GuestContext,
|
||||
VMX_VMEXIT_REASON VmExitReason
|
||||
);
|
||||
415
Sources/HostUtils.c
Normal file
@@ -0,0 +1,415 @@
|
||||
/*!
|
||||
@file HostUtils.c
|
||||
|
||||
@brief Utility functions and structures for the host.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#include "HostUtils.h"
|
||||
#include "Logger.h"
|
||||
#include "ExtendedPageTables.h"
|
||||
#include "Utils.h"
|
||||
|
||||
/*!
|
||||
@brief Dumps the segment access rights value.
|
||||
|
||||
@param[in] AccessRights - The segment access rights value to dump.
|
||||
*/
|
||||
static
|
||||
VOID
|
||||
DumpAccessRights (
|
||||
_In_ UINT64 AccessRights
|
||||
)
|
||||
{
|
||||
VMX_SEGMENT_ACCESS_RIGHTS rights;
|
||||
|
||||
rights.Flags = (UINT32)AccessRights;
|
||||
LOG_ERROR(" - Type = %ul", rights.Type);
|
||||
LOG_ERROR(" - S = %ul", rights.DescriptorType);
|
||||
LOG_ERROR(" - DPL = %ul", rights.DescriptorPrivilegeLevel);
|
||||
LOG_ERROR(" - P = %ul", rights.Present);
|
||||
LOG_ERROR(" - Reserved1 = %ul", rights.Reserved1);
|
||||
LOG_ERROR(" - Available = %ul", rights.AvailableBit);
|
||||
LOG_ERROR(" - L = %ul", rights.LongMode);
|
||||
LOG_ERROR(" - D/B = %ul", rights.DefaultBig);
|
||||
LOG_ERROR(" - G = %ul", rights.Granularity);
|
||||
LOG_ERROR(" - Unusable = %ul", rights.Unusable);
|
||||
LOG_ERROR(" - Reserved2 = %ul", rights.Reserved2);
|
||||
}
|
||||
|
||||
VOID
|
||||
DumpHostState (
|
||||
)
|
||||
{
|
||||
//
|
||||
// 16-Bit Host-State Fields
|
||||
//
|
||||
LOG_ERROR("Host ES Selector = %016llx", VmxRead(VMCS_HOST_ES_SELECTOR));
|
||||
LOG_ERROR("Host CS Selector = %016llx", VmxRead(VMCS_HOST_CS_SELECTOR));
|
||||
LOG_ERROR("Host SS Selector = %016llx", VmxRead(VMCS_HOST_SS_SELECTOR));
|
||||
LOG_ERROR("Host DS Selector = %016llx", VmxRead(VMCS_HOST_DS_SELECTOR));
|
||||
LOG_ERROR("Host FS Selector = %016llx", VmxRead(VMCS_HOST_FS_SELECTOR));
|
||||
LOG_ERROR("Host GS Selector = %016llx", VmxRead(VMCS_HOST_GS_SELECTOR));
|
||||
LOG_ERROR("Host TR Selector = %016llx", VmxRead(VMCS_HOST_TR_SELECTOR));
|
||||
|
||||
//
|
||||
// 64-Bit Host-State Fields
|
||||
//
|
||||
LOG_ERROR("Host IA32_PAT = %016llx", VmxRead(VMCS_HOST_PAT));
|
||||
LOG_ERROR("Host IA32_EFER = %016llx", VmxRead(VMCS_HOST_EFER));
|
||||
LOG_ERROR("Host IA32_PERF_GLOBAL_CTRL = %016llx", VmxRead(VMCS_HOST_PERF_GLOBAL_CTRL));
|
||||
|
||||
//
|
||||
// 32-Bit Host-State Fields
|
||||
//
|
||||
LOG_ERROR("Host IA32_SYSENTER_CS = %016llx", VmxRead(VMCS_HOST_SYSENTER_CS));
|
||||
|
||||
//
|
||||
// Natural-Width Host-State Fields
|
||||
//
|
||||
LOG_ERROR("Host CR0 = %016llx", VmxRead(VMCS_HOST_CR0));
|
||||
LOG_ERROR("Host CR3 = %016llx", VmxRead(VMCS_HOST_CR3));
|
||||
LOG_ERROR("Host CR4 = %016llx", VmxRead(VMCS_HOST_CR4));
|
||||
LOG_ERROR("Host FS Base = %016llx", VmxRead(VMCS_HOST_FS_BASE));
|
||||
LOG_ERROR("Host GS Base = %016llx", VmxRead(VMCS_HOST_GS_BASE));
|
||||
LOG_ERROR("Host TR base = %016llx", VmxRead(VMCS_HOST_TR_BASE));
|
||||
LOG_ERROR("Host GDTR base = %016llx", VmxRead(VMCS_HOST_GDTR_BASE));
|
||||
LOG_ERROR("Host IDTR base = %016llx", VmxRead(VMCS_HOST_IDTR_BASE));
|
||||
LOG_ERROR("Host IA32_SYSENTER_ESP = %016llx", VmxRead(VMCS_HOST_SYSENTER_ESP));
|
||||
LOG_ERROR("Host IA32_SYSENTER_EIP = %016llx", VmxRead(VMCS_HOST_SYSENTER_EIP));
|
||||
LOG_ERROR("Host RSP = %016llx", VmxRead(VMCS_HOST_RSP));
|
||||
LOG_ERROR("Host RIP = %016llx", VmxRead(VMCS_HOST_RIP));
|
||||
}
|
||||
|
||||
VOID
|
||||
DumpGuestState (
|
||||
)
|
||||
{
|
||||
//
|
||||
// 16-Bit Guest-State Fields
|
||||
//
|
||||
LOG_ERROR("Guest ES Selector = %016llx", VmxRead(VMCS_GUEST_ES_SELECTOR));
|
||||
LOG_ERROR("Guest CS Selector = %016llx", VmxRead(VMCS_GUEST_CS_SELECTOR));
|
||||
LOG_ERROR("Guest SS Selector = %016llx", VmxRead(VMCS_GUEST_SS_SELECTOR));
|
||||
LOG_ERROR("Guest DS Selector = %016llx", VmxRead(VMCS_GUEST_DS_SELECTOR));
|
||||
LOG_ERROR("Guest FS Selector = %016llx", VmxRead(VMCS_GUEST_FS_SELECTOR));
|
||||
LOG_ERROR("Guest GS Selector = %016llx", VmxRead(VMCS_GUEST_GS_SELECTOR));
|
||||
LOG_ERROR("Guest LDTR Selector = %016llx", VmxRead(VMCS_GUEST_LDTR_SELECTOR));
|
||||
LOG_ERROR("Guest TR Selector = %016llx", VmxRead(VMCS_GUEST_TR_SELECTOR));
|
||||
LOG_ERROR("Guest interrupt status = %016llx", VmxRead(VMCS_GUEST_INTERRUPT_STATUS));
|
||||
LOG_ERROR("PML index = %016llx", VmxRead(VMCS_GUEST_PML_INDEX));
|
||||
|
||||
//
|
||||
// 64-Bit Guest-State Fields
|
||||
//
|
||||
LOG_ERROR("VMCS link pointer = %016llx", VmxRead(VMCS_GUEST_VMCS_LINK_POINTER));
|
||||
LOG_ERROR("Guest IA32_DEBUGCTL = %016llx", VmxRead(VMCS_GUEST_DEBUGCTL));
|
||||
LOG_ERROR("Guest IA32_PAT = %016llx", VmxRead(VMCS_GUEST_PAT));
|
||||
LOG_ERROR("Guest IA32_EFER = %016llx", VmxRead(VMCS_GUEST_EFER));
|
||||
LOG_ERROR("Guest IA32_PERF_GLOBAL_CTRL = %016llx", VmxRead(VMCS_GUEST_PERF_GLOBAL_CTRL));
|
||||
LOG_ERROR("Guest PDPTE0 = %016llx", VmxRead(VMCS_GUEST_PDPTE0));
|
||||
LOG_ERROR("Guest PDPTE1 = %016llx", VmxRead(VMCS_GUEST_PDPTE1));
|
||||
LOG_ERROR("Guest PDPTE2 = %016llx", VmxRead(VMCS_GUEST_PDPTE2));
|
||||
LOG_ERROR("Guest PDPTE3 = %016llx", VmxRead(VMCS_GUEST_PDPTE3));
|
||||
LOG_ERROR("Guest IA32_BNDCFGS = %016llx", VmxRead(VMCS_GUEST_BNDCFGS));
|
||||
LOG_ERROR("Guest IA32_RTIT_CTL = %016llx", VmxRead(VMCS_GUEST_RTIT_CTL));
|
||||
|
||||
//
|
||||
// 32-Bit Guest-State Fields
|
||||
//
|
||||
LOG_ERROR("Guest ES Limit = %016llx", VmxRead(VMCS_GUEST_ES_LIMIT));
|
||||
LOG_ERROR("Guest CS Limit = %016llx", VmxRead(VMCS_GUEST_CS_LIMIT));
|
||||
LOG_ERROR("Guest SS Limit = %016llx", VmxRead(VMCS_GUEST_SS_LIMIT));
|
||||
LOG_ERROR("Guest DS Limit = %016llx", VmxRead(VMCS_GUEST_DS_LIMIT));
|
||||
LOG_ERROR("Guest FS Limit = %016llx", VmxRead(VMCS_GUEST_FS_LIMIT));
|
||||
LOG_ERROR("Guest GS Limit = %016llx", VmxRead(VMCS_GUEST_GS_LIMIT));
|
||||
LOG_ERROR("Guest LDTR Limit = %016llx", VmxRead(VMCS_GUEST_LDTR_LIMIT));
|
||||
LOG_ERROR("Guest TR Limit = %016llx", VmxRead(VMCS_GUEST_TR_LIMIT));
|
||||
LOG_ERROR("Guest GDTR limit = %016llx", VmxRead(VMCS_GUEST_GDTR_LIMIT));
|
||||
LOG_ERROR("Guest IDTR limit = %016llx", VmxRead(VMCS_GUEST_IDTR_LIMIT));
|
||||
LOG_ERROR("Guest ES access rights = %016llx", VmxRead(VMCS_GUEST_ES_ACCESS_RIGHTS));
|
||||
LOG_ERROR("Guest CS access rights = %016llx", VmxRead(VMCS_GUEST_CS_ACCESS_RIGHTS));
|
||||
LOG_ERROR("Guest SS access rights = %016llx", VmxRead(VMCS_GUEST_SS_ACCESS_RIGHTS));
|
||||
LOG_ERROR("Guest DS access rights = %016llx", VmxRead(VMCS_GUEST_DS_ACCESS_RIGHTS));
|
||||
LOG_ERROR("Guest FS access rights = %016llx", VmxRead(VMCS_GUEST_FS_ACCESS_RIGHTS));
|
||||
LOG_ERROR("Guest GS access rights = %016llx", VmxRead(VMCS_GUEST_GS_ACCESS_RIGHTS));
|
||||
LOG_ERROR("Guest LDTR access rights = %016llx", VmxRead(VMCS_GUEST_LDTR_ACCESS_RIGHTS));
|
||||
LOG_ERROR("Guest TR access rights = %016llx", VmxRead(VMCS_GUEST_TR_ACCESS_RIGHTS));
|
||||
LOG_ERROR("Guest interruptibility state = %016llx", VmxRead(VMCS_GUEST_INTERRUPTIBILITY_STATE));
|
||||
LOG_ERROR("Guest activity state = %016llx", VmxRead(VMCS_GUEST_ACTIVITY_STATE));
|
||||
LOG_ERROR("Guest SMBASE = %016llx", VmxRead(VMCS_GUEST_SMBASE));
|
||||
LOG_ERROR("Guest IA32_SYSENTER_CS = %016llx", VmxRead(VMCS_GUEST_SYSENTER_CS));
|
||||
LOG_ERROR("VMX-preemption timer value = %016llx", VmxRead(VMCS_GUEST_VMX_PREEMPTION_TIMER_VALUE));
|
||||
|
||||
//
|
||||
// Natural-Width Guest-State Fields
|
||||
//
|
||||
LOG_ERROR("Guest CR0 = %016llx", VmxRead(VMCS_GUEST_CR0));
|
||||
LOG_ERROR("Guest CR3 = %016llx", VmxRead(VMCS_GUEST_CR3));
|
||||
LOG_ERROR("Guest CR4 = %016llx", VmxRead(VMCS_GUEST_CR4));
|
||||
LOG_ERROR("Guest ES Base = %016llx", VmxRead(VMCS_GUEST_ES_BASE));
|
||||
LOG_ERROR("Guest CS Base = %016llx", VmxRead(VMCS_GUEST_CS_BASE));
|
||||
LOG_ERROR("Guest SS Base = %016llx", VmxRead(VMCS_GUEST_SS_BASE));
|
||||
LOG_ERROR("Guest DS Base = %016llx", VmxRead(VMCS_GUEST_DS_BASE));
|
||||
LOG_ERROR("Guest FS Base = %016llx", VmxRead(VMCS_GUEST_FS_BASE));
|
||||
LOG_ERROR("Guest GS Base = %016llx", VmxRead(VMCS_GUEST_GS_BASE));
|
||||
LOG_ERROR("Guest LDTR base = %016llx", VmxRead(VMCS_GUEST_LDTR_BASE));
|
||||
LOG_ERROR("Guest TR base = %016llx", VmxRead(VMCS_GUEST_TR_BASE));
|
||||
LOG_ERROR("Guest GDTR base = %016llx", VmxRead(VMCS_GUEST_GDTR_BASE));
|
||||
LOG_ERROR("Guest IDTR base = %016llx", VmxRead(VMCS_GUEST_IDTR_BASE));
|
||||
LOG_ERROR("Guest DR7 = %016llx", VmxRead(VMCS_GUEST_DR7));
|
||||
LOG_ERROR("Guest RSP = %016llx", VmxRead(VMCS_GUEST_RSP));
|
||||
LOG_ERROR("Guest RIP = %016llx", VmxRead(VMCS_GUEST_RIP));
|
||||
LOG_ERROR("Guest RFLAGS = %016llx", VmxRead(VMCS_GUEST_RFLAGS));
|
||||
LOG_ERROR("Guest pending debug exceptions = %016llx", VmxRead(VMCS_GUEST_PENDING_DEBUG_EXCEPTIONS));
|
||||
LOG_ERROR("Guest IA32_SYSENTER_ESP = %016llx", VmxRead(VMCS_GUEST_SYSENTER_ESP));
|
||||
LOG_ERROR("Guest IA32_SYSENTER_EIP = %016llx", VmxRead(VMCS_GUEST_SYSENTER_EIP));
|
||||
}
|
||||
|
||||
VOID
|
||||
DumpControl (
|
||||
)
|
||||
{
|
||||
//
|
||||
// 16-Bit Control Fields
|
||||
//
|
||||
LOG_ERROR("Virtual-processor identifier = %016llx", VmxRead(VMCS_CTRL_VIRTUAL_PROCESSOR_IDENTIFIER));
|
||||
LOG_ERROR("Posted-interrupt notification vector = %016llx", VmxRead(VMCS_CTRL_POSTED_INTERRUPT_NOTIFICATION_VECTOR));
|
||||
LOG_ERROR("EPTP index = %016llx", VmxRead(VMCS_CTRL_EPTP_INDEX));
|
||||
|
||||
//
|
||||
// 64-Bit Control Fields
|
||||
//
|
||||
LOG_ERROR("Address of I/O bitmap A = %016llx", VmxRead(VMCS_CTRL_IO_BITMAP_A_ADDRESS));
|
||||
LOG_ERROR("Address of I/O bitmap B = %016llx", VmxRead(VMCS_CTRL_IO_BITMAP_B_ADDRESS));
|
||||
LOG_ERROR("Address of MSR bitmaps = %016llx", VmxRead(VMCS_CTRL_MSR_BITMAP_ADDRESS));
|
||||
LOG_ERROR("VM-exit MSR-store address = %016llx", VmxRead(VMCS_CTRL_VMEXIT_MSR_STORE_ADDRESS));
|
||||
LOG_ERROR("VM-exit MSR-load address = %016llx", VmxRead(VMCS_CTRL_VMEXIT_MSR_LOAD_ADDRESS));
|
||||
LOG_ERROR("VM-entry MSR-load address = %016llx", VmxRead(VMCS_CTRL_VMENTRY_MSR_LOAD_ADDRESS));
|
||||
LOG_ERROR("Executive-VMCS pointer = %016llx", VmxRead(VMCS_CTRL_EXECUTIVE_VMCS_POINTER));
|
||||
LOG_ERROR("PML address = %016llx", VmxRead(VMCS_CTRL_PML_ADDRESS));
|
||||
LOG_ERROR("TSC offset = %016llx", VmxRead(VMCS_CTRL_TSC_OFFSET));
|
||||
LOG_ERROR("Virtual-APIC address = %016llx", VmxRead(VMCS_CTRL_VIRTUAL_APIC_ADDRESS));
|
||||
LOG_ERROR("APIC-access address = %016llx", VmxRead(VMCS_CTRL_APIC_ACCESS_ADDRESS));
|
||||
LOG_ERROR("Posted-interrupt descriptor address = %016llx", VmxRead(VMCS_CTRL_POSTED_INTERRUPT_DESCRIPTOR_ADDRESS));
|
||||
LOG_ERROR("VM-function controls = %016llx", VmxRead(VMCS_CTRL_VMFUNC_CONTROLS));
|
||||
LOG_ERROR("EPT pointer = %016llx", VmxRead(VMCS_CTRL_EPT_POINTER));
|
||||
LOG_ERROR("EOI-exit bitmap 0 = %016llx", VmxRead(VMCS_CTRL_EOI_EXIT_BITMAP_0));
|
||||
LOG_ERROR("EOI-exit bitmap 1 = %016llx", VmxRead(VMCS_CTRL_EOI_EXIT_BITMAP_1));
|
||||
LOG_ERROR("EOI-exit bitmap 2 = %016llx", VmxRead(VMCS_CTRL_EOI_EXIT_BITMAP_2));
|
||||
LOG_ERROR("EOI-exit bitmap 3 = %016llx", VmxRead(VMCS_CTRL_EOI_EXIT_BITMAP_3));
|
||||
LOG_ERROR("EPTP-list address = %016llx", VmxRead(VMCS_CTRL_EPT_POINTER_LIST_ADDRESS));
|
||||
LOG_ERROR("VMREAD-bitmap address = %016llx", VmxRead(VMCS_CTRL_VMREAD_BITMAP_ADDRESS));
|
||||
LOG_ERROR("VMWRITE-bitmap address = %016llx", VmxRead(VMCS_CTRL_VMWRITE_BITMAP_ADDRESS));
|
||||
LOG_ERROR("Virtualization-exception information address = %016llx", VmxRead(VMCS_CTRL_VIRTUALIZATION_EXCEPTION_INFORMATION_ADDRESS));
|
||||
LOG_ERROR("XSS-exiting bitmap = %016llx", VmxRead(VMCS_CTRL_XSS_EXITING_BITMAP));
|
||||
LOG_ERROR("ENCLS-exiting bitmap = %016llx", VmxRead(VMCS_CTRL_ENCLS_EXITING_BITMAP));
|
||||
LOG_ERROR("TSC multiplier = %016llx", VmxRead(VMCS_CTRL_TSC_MULTIPLIER));
|
||||
|
||||
//
|
||||
// 32-Bit Control Fields
|
||||
//
|
||||
LOG_ERROR("Pin-based VM-execution controls = %016llx", VmxRead(VMCS_CTRL_PIN_BASED_VM_EXECUTION_CONTROLS));
|
||||
LOG_ERROR("Primary processor-based VM-execution controls = %016llx", VmxRead(VMCS_CTRL_PROCESSOR_BASED_VM_EXECUTION_CONTROLS));
|
||||
LOG_ERROR("Exception bitmap = %016llx", VmxRead(VMCS_CTRL_EXCEPTION_BITMAP));
|
||||
LOG_ERROR("Page-fault error-code mask = %016llx", VmxRead(VMCS_CTRL_PAGEFAULT_ERROR_CODE_MASK));
|
||||
LOG_ERROR("Page-fault error-code match = %016llx", VmxRead(VMCS_CTRL_PAGEFAULT_ERROR_CODE_MATCH));
|
||||
LOG_ERROR("CR3-target count = %016llx", VmxRead(VMCS_CTRL_CR3_TARGET_COUNT));
|
||||
LOG_ERROR("VM-exit controls = %016llx", VmxRead(VMCS_CTRL_VMEXIT_CONTROLS));
|
||||
LOG_ERROR("VM-exit MSR-store count = %016llx", VmxRead(VMCS_CTRL_VMEXIT_MSR_STORE_COUNT));
|
||||
LOG_ERROR("VM-exit MSR-load count = %016llx", VmxRead(VMCS_CTRL_VMEXIT_MSR_LOAD_COUNT));
|
||||
LOG_ERROR("VM-entry controls = %016llx", VmxRead(VMCS_CTRL_VMENTRY_CONTROLS));
|
||||
LOG_ERROR("VM-entry MSR-load count = %016llx", VmxRead(VMCS_CTRL_VMENTRY_MSR_LOAD_COUNT));
|
||||
LOG_ERROR("VM-entry interruption-information field = %016llx", VmxRead(VMCS_CTRL_VMENTRY_INTERRUPTION_INFORMATION_FIELD));
|
||||
LOG_ERROR("VM-entry exception error code = %016llx", VmxRead(VMCS_CTRL_VMENTRY_EXCEPTION_ERROR_CODE));
|
||||
LOG_ERROR("VM-entry instruction length = %016llx", VmxRead(VMCS_CTRL_VMENTRY_INSTRUCTION_LENGTH));
|
||||
LOG_ERROR("TPR threshold = %016llx", VmxRead(VMCS_CTRL_TPR_THRESHOLD));
|
||||
LOG_ERROR("Secondary processor-based VM-execution controls = %016llx", VmxRead(VMCS_CTRL_SECONDARY_PROCESSOR_BASED_VM_EXECUTION_CONTROLS));
|
||||
LOG_ERROR("PLE_Gap = %016llx", VmxRead(VMCS_CTRL_PLE_GAP));
|
||||
LOG_ERROR("PLE_Window = %016llx", VmxRead(VMCS_CTRL_PLE_WINDOW));
|
||||
|
||||
//
|
||||
// Natural-Width Control Fields
|
||||
//
|
||||
LOG_ERROR("CR0 guest/host mask = %016llx", VmxRead(VMCS_CTRL_CR0_GUEST_HOST_MASK));
|
||||
LOG_ERROR("CR4 guest/host mask = %016llx", VmxRead(VMCS_CTRL_CR4_GUEST_HOST_MASK));
|
||||
LOG_ERROR("CR0 read shadow = %016llx", VmxRead(VMCS_CTRL_CR0_READ_SHADOW));
|
||||
LOG_ERROR("CR4 read shadow = %016llx", VmxRead(VMCS_CTRL_CR4_READ_SHADOW));
|
||||
LOG_ERROR("CR3-target value 0 = %016llx", VmxRead(VMCS_CTRL_CR3_TARGET_VALUE_0));
|
||||
LOG_ERROR("CR3-target value 1 = %016llx", VmxRead(VMCS_CTRL_CR3_TARGET_VALUE_1));
|
||||
LOG_ERROR("CR3-target value 2 = %016llx", VmxRead(VMCS_CTRL_CR3_TARGET_VALUE_2));
|
||||
LOG_ERROR("CR3-target value 3 = %016llx", VmxRead(VMCS_CTRL_CR3_TARGET_VALUE_3));
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
VOID
|
||||
VmxWrite (
|
||||
VMCS_FIELD Field,
|
||||
UINT64 FieldValue
|
||||
)
|
||||
{
|
||||
VMX_RESULT result;
|
||||
|
||||
result = __vmx_vmwrite(Field, FieldValue);
|
||||
if (result != VmxResultOk)
|
||||
{
|
||||
VMX_ERROR_NUMBER vmxErrorStatus;
|
||||
|
||||
vmxErrorStatus = (result == VmxResultErrorWithStatus) ?
|
||||
(VMX_ERROR_NUMBER)VmxRead(VMCS_VM_INSTRUCTION_ERROR) : 0;
|
||||
if (vmxErrorStatus != VMX_ERROR_VMREAD_VMWRITE_INVALID_COMPONENT)
|
||||
{
|
||||
MV_PANIC();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
UINT64
|
||||
VmxRead (
|
||||
VMCS_FIELD Field
|
||||
)
|
||||
{
|
||||
VMX_RESULT result;
|
||||
UINT64 fieldValue;
|
||||
|
||||
result = __vmx_vmread(Field, &fieldValue);
|
||||
if (result != VmxResultOk)
|
||||
{
|
||||
VMX_ERROR_NUMBER vmxErrorStatus;
|
||||
|
||||
vmxErrorStatus = (result == VmxResultErrorWithStatus) ?
|
||||
(VMX_ERROR_NUMBER)VmxRead(VMCS_VM_INSTRUCTION_ERROR) : 0;
|
||||
if (vmxErrorStatus != VMX_ERROR_VMREAD_VMWRITE_INVALID_COMPONENT)
|
||||
{
|
||||
MV_PANIC();
|
||||
}
|
||||
fieldValue = MAXUINT64;
|
||||
}
|
||||
return fieldValue;
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
VOID
|
||||
AdvanceGuestInstructionPointer (
|
||||
GUEST_CONTEXT* GuestContext
|
||||
)
|
||||
{
|
||||
UINT64 exitInstructionLength;
|
||||
|
||||
exitInstructionLength = VmxRead(VMCS_VMEXIT_INSTRUCTION_LENGTH);
|
||||
GuestContext->VmcsBasedRegisters.Rip += exitInstructionLength;
|
||||
VmxWrite(VMCS_GUEST_RIP, GuestContext->VmcsBasedRegisters.Rip);
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
BOOLEAN
|
||||
IsGuestInKernelMode (
|
||||
VOID
|
||||
)
|
||||
{
|
||||
VMX_SEGMENT_ACCESS_RIGHTS accessRight;
|
||||
|
||||
accessRight.Flags = (UINT32)VmxRead(VMCS_GUEST_SS_ACCESS_RIGHTS);
|
||||
return (accessRight.DescriptorPrivilegeLevel == 0);
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
VOID
|
||||
InjectInterruption (
|
||||
INTERRUPTION_TYPE InterruptionType,
|
||||
EXCEPTION_VECTOR Vector,
|
||||
BOOLEAN DeliverErrorCode,
|
||||
UINT32 ErrorCode
|
||||
)
|
||||
{
|
||||
VMENTRY_INTERRUPT_INFORMATION interruptToInject;
|
||||
|
||||
interruptToInject.Flags = 0;
|
||||
interruptToInject.Valid = TRUE;
|
||||
interruptToInject.InterruptionType = (UINT32)InterruptionType;
|
||||
interruptToInject.Vector = (UINT32)Vector;
|
||||
interruptToInject.DeliverErrorCode = DeliverErrorCode;
|
||||
VmxWrite(VMCS_CTRL_VMENTRY_INTERRUPTION_INFORMATION_FIELD, interruptToInject.Flags);
|
||||
|
||||
if (DeliverErrorCode != FALSE)
|
||||
{
|
||||
VmxWrite(VMCS_CTRL_VMENTRY_EXCEPTION_ERROR_CODE, ErrorCode);
|
||||
}
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
VOID
|
||||
SwitchGuestPagingMode (
|
||||
CR0 NewGuestCr0
|
||||
)
|
||||
{
|
||||
IA32_EFER_REGISTER guestEfer;
|
||||
IA32_VMX_ENTRY_CTLS_REGISTER vmEntryControls;
|
||||
|
||||
//
|
||||
// "Enable paging by setting CR0.PG = 1. This causes the processor to set the
|
||||
// IA32_EFER.LMA bit to 1."
|
||||
// See: 9.8.5 Initializing IA-32e Mode
|
||||
//
|
||||
// "The processor always sets IA32_EFER.LMA to CR0.PG & IA32_EFER.LME.
|
||||
// Software cannot directly modify IA32_EFER.LMA; an execution of WRMSR to
|
||||
// the IA32_EFER MSR ignores bit 10 of its source operand."
|
||||
// See: 4.1.1 Three Paging Modes
|
||||
//
|
||||
guestEfer.Flags = VmxRead(VMCS_GUEST_EFER);
|
||||
guestEfer.Ia32EModeActive = (NewGuestCr0.PagingEnable & guestEfer.Ia32EModeEnable);
|
||||
VmxWrite(VMCS_GUEST_EFER, guestEfer.Flags);
|
||||
|
||||
//
|
||||
// Apply the paging mode change in the VM-entry control VMCS field too.
|
||||
//
|
||||
vmEntryControls.Flags = VmxRead(VMCS_CTRL_VMENTRY_CONTROLS);
|
||||
vmEntryControls.Ia32EModeGuest = guestEfer.Ia32EModeActive;
|
||||
VmxWrite(VMCS_CTRL_VMENTRY_CONTROLS, vmEntryControls.Flags);
|
||||
|
||||
//
|
||||
// Changing the paging mode results in invalidating TLB. Emulate this by
|
||||
// invalidating combined caches (GVA to HPA translation caches).
|
||||
//
|
||||
InvalidateVpidDerivedCache((UINT16)VmxRead(VMCS_CTRL_VIRTUAL_PROCESSOR_IDENTIFIER));
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
CR0
|
||||
AdjustGuestCr0 (
|
||||
CR0 Cr0
|
||||
)
|
||||
{
|
||||
CR0 newCr0;
|
||||
IA32_VMX_PROCBASED_CTLS2_REGISTER secondaryProcBasedControls;
|
||||
|
||||
newCr0 = AdjustCr0(Cr0);
|
||||
|
||||
//
|
||||
// When the UnrestrictedGuest bit is set, ProtectionEnable and PagingEnable
|
||||
// bits are allowed to be zero. Make this adjustment, by setting them 1 only
|
||||
// when the guest did indeed requested them to be 1 (ie,
|
||||
// Cr0.ProtectionEnable == 1) and the FIXED0 MSR indicated them to be 1 (ie,
|
||||
// newCr0.ProtectionEnable == 1).
|
||||
//
|
||||
secondaryProcBasedControls.Flags = VmxRead(
|
||||
VMCS_CTRL_SECONDARY_PROCESSOR_BASED_VM_EXECUTION_CONTROLS);
|
||||
if (secondaryProcBasedControls.UnrestrictedGuest != FALSE)
|
||||
{
|
||||
newCr0.ProtectionEnable &= Cr0.ProtectionEnable;
|
||||
newCr0.PagingEnable &= Cr0.PagingEnable;
|
||||
}
|
||||
return newCr0;
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
CR4
|
||||
AdjustGuestCr4 (
|
||||
CR4 Cr4
|
||||
)
|
||||
{
|
||||
return AdjustCr4(Cr4);
|
||||
}
|
||||
217
Sources/HostUtils.h
Normal file
@@ -0,0 +1,217 @@
|
||||
/*!
|
||||
@file HostUtils.h
|
||||
|
||||
@brief Utility functions and structures for the host.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#pragma once
|
||||
#include "Common.h"
|
||||
#include "Public.h"
|
||||
|
||||
//
|
||||
// 128bit XMM register (ie, equivalent to __m128 on MSVC).
|
||||
//
|
||||
typedef struct _XMM
|
||||
{
|
||||
UINT8 Value[16];
|
||||
} XMM;
|
||||
|
||||
//
|
||||
// Guest General Purpose Registers (GPRs) created on VM-exit from the guest
|
||||
// state and write back to the guest on VM-entry.
|
||||
//
|
||||
typedef struct _GUEST_REGISTERS
|
||||
{
|
||||
XMM Xmm[6];
|
||||
VOID* Alignment;
|
||||
UINT64 R15;
|
||||
UINT64 R14;
|
||||
UINT64 R13;
|
||||
UINT64 R12;
|
||||
UINT64 R11;
|
||||
UINT64 R10;
|
||||
UINT64 R9;
|
||||
UINT64 R8;
|
||||
UINT64 Rdi;
|
||||
UINT64 Rsi;
|
||||
UINT64 Rbp;
|
||||
UINT64 Rbx;
|
||||
UINT64 Rdx;
|
||||
UINT64 Rcx;
|
||||
UINT64 Rax;
|
||||
} GUEST_REGISTERS;
|
||||
|
||||
//
|
||||
// The guest registers that are stored in the VMCS as opposed to stack like
|
||||
// ones in the GUEST_REGISTERS structure.
|
||||
//
|
||||
typedef struct _VMCS_BASED_REGISTERS
|
||||
{
|
||||
UINT64 Rip;
|
||||
UINT64 Rsp;
|
||||
RFLAGS Rflags;
|
||||
} VMCS_BASED_REGISTERS;
|
||||
|
||||
//
|
||||
// State of the guest.
|
||||
//
|
||||
typedef struct _GUEST_CONTEXT
|
||||
{
|
||||
//
|
||||
// Indicates that the processor should continue virtualization. FALSE of
|
||||
// results in disablement of hypervisor with the VMXOFF instruction. See
|
||||
// x64.asm. This value is used as a return value of the HandleVmExit function.
|
||||
//
|
||||
BOOLEAN ContinueVm;
|
||||
|
||||
//
|
||||
// Collection of pointers passed from the kernel via the host stack.
|
||||
//
|
||||
HYPERVISOR_CONTEXT* Contexts;
|
||||
|
||||
//
|
||||
// The guest states stored in hypervisor stack.
|
||||
//
|
||||
GUEST_REGISTERS* StackBasedRegisters;
|
||||
|
||||
//
|
||||
// The guest states stored in the VMCS.
|
||||
//
|
||||
VMCS_BASED_REGISTERS VmcsBasedRegisters;
|
||||
} GUEST_CONTEXT;
|
||||
|
||||
/*!
|
||||
@brief Dumps host state VMCS fields.
|
||||
*/
|
||||
VOID
|
||||
DumpHostState (
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Dumps guest state VMCS fields.
|
||||
*/
|
||||
VOID
|
||||
DumpGuestState (
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Dumps control VMCS fields.
|
||||
*/
|
||||
VOID
|
||||
DumpControl (
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Writes the value to the VMCS.
|
||||
|
||||
@param[in] Field - A VMCS field to write the value to.
|
||||
|
||||
@param[in] FieldValue - A value to write.
|
||||
*/
|
||||
VOID
|
||||
VmxWrite (
|
||||
_In_ VMCS_FIELD Field,
|
||||
_In_ UINT64 FieldValue
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Read a value from the VMCS.
|
||||
|
||||
@param[in] Field - A VMCS field to read a value from.
|
||||
|
||||
@return A value read from the VMCS. MAXUINT64 is returned when a non-existent
|
||||
VMCS field is requested for read.
|
||||
*/
|
||||
UINT64
|
||||
VmxRead (
|
||||
_In_ VMCS_FIELD Field
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Advances the guest's RIP to the address of the next instruction. This
|
||||
implies that the hypervisor completed emulation of the instruction.
|
||||
|
||||
@param[in,out] GuestContext - A pointer to the guest context.
|
||||
*/
|
||||
VOID
|
||||
AdvanceGuestInstructionPointer (
|
||||
_Inout_ GUEST_CONTEXT* GuestContext
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Tests whether the guest was at the CPL 0 (kernel-mode) when VM-exit
|
||||
happened.
|
||||
|
||||
@return TRUE when the guest was at the CPL 0, otherwise FALSE.
|
||||
*/
|
||||
_Must_inspect_result_
|
||||
BOOLEAN
|
||||
IsGuestInKernelMode (
|
||||
VOID
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Queues interrupt to occur to the VMCS.
|
||||
|
||||
@details Generally, this interrupt fires on VM-entry and the guests runs a
|
||||
corresponding exception handler before executing the instruction pointed
|
||||
by Rip.
|
||||
|
||||
@param[in] InterruptionType - A type of interrupt to inject.
|
||||
|
||||
@param[in] Vector - A vector number of interrupt to inject.
|
||||
|
||||
@param[in] DeliverErrorCode - TRUE when the interrupt should have an error
|
||||
code. Whether the interrupt should have an error code is defined by the
|
||||
Intel SDM. See comments in the EXCEPTION_VECTOR definitions for a quick
|
||||
reference.
|
||||
|
||||
@param[in] ErrorCode - An error code. Not used when DeliverErrorCode is FALSE.
|
||||
*/
|
||||
VOID
|
||||
InjectInterruption (
|
||||
_In_ INTERRUPTION_TYPE InterruptionType,
|
||||
_In_ EXCEPTION_VECTOR Vector,
|
||||
_In_ BOOLEAN DeliverErrorCode,
|
||||
_In_ UINT32 ErrorCode
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Switches the guest paging mode between 32 and 64bit modes according
|
||||
with CR0 and EFER.
|
||||
|
||||
@param[in] NewGuestCr0 - The guest CR0 value to check the mode to switch to.
|
||||
*/
|
||||
VOID
|
||||
SwitchGuestPagingMode (
|
||||
_In_ CR0 NewGuestCr0
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Returns the CR0 value after the FIXED0 and FIXED1 MSR values are applied
|
||||
for the guest.
|
||||
|
||||
@param[in] Cr0 - The CR0 value to apply the FIXED0 and FIXED1 MSR values.
|
||||
|
||||
@return The CR0 value where the FIXED0 and FIXED1 MSR values are applied.
|
||||
*/
|
||||
CR0
|
||||
AdjustGuestCr0 (
|
||||
_In_ CR0 Cr0
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Returns the CR4 value after the FIXED0 and FIXED1 MSR values are applied
|
||||
for the guest.
|
||||
|
||||
@param[in] Cr4 - The CR4 value to apply the FIXED0 and FIXED1 MSR values.
|
||||
|
||||
@return The CR4 value where the FIXED0 and FIXED1 MSR values are applied.
|
||||
*/
|
||||
CR4
|
||||
AdjustGuestCr4 (
|
||||
_In_ CR4 Cr4
|
||||
);
|
||||
89
Sources/HostVmcall.c
Normal file
@@ -0,0 +1,89 @@
|
||||
/*!
|
||||
@file HostVmcall.c
|
||||
|
||||
@brief Implementation of hypercall functions.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#include "HostVmcall.h"
|
||||
|
||||
_Use_decl_annotations_
|
||||
VOID
|
||||
HandleVmcallUninstall (
|
||||
GUEST_CONTEXT* GuestContext
|
||||
)
|
||||
{
|
||||
GDTR gdtr;
|
||||
IDTR idtr;
|
||||
|
||||
//
|
||||
// This hypercall is not allowed for ring 3.
|
||||
//
|
||||
if (IsGuestInKernelMode() == FALSE)
|
||||
{
|
||||
GuestContext->StackBasedRegisters->Rax = (UINT64)MV_STATUS_ACCESS_DENIED;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
//
|
||||
// On VM-exit, the processor loads registers according with the Host state
|
||||
// fields in the VMCS. Some registers are changed, e.g, GPRs, and some
|
||||
// others are changed with hard-coded values. The limits of GDTR and IDTR
|
||||
// are such example, and updated to 0xFFFF. When the VMRESUME instruction
|
||||
// is executed, this is not an issue as VM-entry reloads the proper values
|
||||
// from the guest state fields of the VMCS. However, it is not the case when
|
||||
// the VMRESUME is not called, like here. In such a case those values must
|
||||
// be restored with normal value manually, or PatchGuard will report
|
||||
// integrity violation.
|
||||
//
|
||||
// "The GDTR and IDTR limits are each set to FFFFH."
|
||||
// See: 27.5.2 Loading Host Segment and Descriptor-Table Registers
|
||||
//
|
||||
gdtr.BaseAddress = VmxRead(VMCS_GUEST_GDTR_BASE);
|
||||
gdtr.Limit = (UINT16)VmxRead(VMCS_GUEST_GDTR_LIMIT);
|
||||
_sgdt(&gdtr);
|
||||
|
||||
idtr.BaseAddress = VmxRead(VMCS_GUEST_IDTR_BASE);
|
||||
idtr.Limit = (UINT16)VmxRead(VMCS_GUEST_IDTR_LIMIT);
|
||||
__lidt(&idtr);
|
||||
|
||||
//
|
||||
// The host may use a different CR3 than that of the guest. This is the case
|
||||
// on EFI. Apply the guest one. This assumes that translation both the host
|
||||
// CR3 and the guest CR3 has the same translation. Otherwise, the system will
|
||||
// crash immediately after updating CR3.
|
||||
//
|
||||
__writecr3(VmxRead(VMCS_GUEST_CR3));
|
||||
|
||||
//
|
||||
// Save some values needed for clean up in the volatile registers.
|
||||
// RAX = The address of the all-processors context. This is used as a
|
||||
// return value of the AsmVmxCall function.
|
||||
// RCX = The address to continue execution after the execution of the VMXOFF
|
||||
// instruction. This value is needed because we have to manually
|
||||
// transfer execution instead of doing so automatically with the
|
||||
// VMRESUME instruction in this pass.
|
||||
// RDX = The RSP value to be restored. Same as the case of RIP, the RSP is
|
||||
// not automatically restored in this pass, and so, has to be updated
|
||||
// by the original value (not host's RSP).
|
||||
// Param2 = The RFLAGS value to be restored. Also same as the case of RIP and
|
||||
// RSP. Recall that RFLAGS is also updated automatically on VM-exit.
|
||||
// "RFLAGS is cleared, except bit 1, which is always set."
|
||||
// See: 27.5.3 Loading Host RIP, RSP, and RFLAGS
|
||||
//
|
||||
GuestContext->StackBasedRegisters->Rax = (UINT64)GuestContext->Contexts->VpContexts;
|
||||
GuestContext->StackBasedRegisters->Rcx = GuestContext->VmcsBasedRegisters.Rip +
|
||||
VmxRead(VMCS_VMEXIT_INSTRUCTION_LENGTH);
|
||||
GuestContext->StackBasedRegisters->Rdx = GuestContext->VmcsBasedRegisters.Rsp;
|
||||
GuestContext->StackBasedRegisters->R8 = GuestContext->VmcsBasedRegisters.Rflags.Flags;
|
||||
|
||||
//
|
||||
// Finally, indicates that virtualization should be terminated.
|
||||
//
|
||||
GuestContext->ContinueVm = FALSE;
|
||||
|
||||
Exit:
|
||||
return;
|
||||
}
|
||||
38
Sources/HostVmcall.h
Normal file
@@ -0,0 +1,38 @@
|
||||
/*!
|
||||
@file HostVmcall.h
|
||||
|
||||
@brief Implementation of hypercall functions.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#pragma once
|
||||
#include "Common.h"
|
||||
#include "HostUtils.h"
|
||||
#include "Public.h"
|
||||
|
||||
//
|
||||
// The VMCALL handler type.
|
||||
//
|
||||
typedef
|
||||
VOID
|
||||
VMCALL_HANDLER (
|
||||
_Inout_ GUEST_CONTEXT* GuestContext
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Handles hypercall for uninstalling the hypervisor.
|
||||
|
||||
@param[in,out] GuestContext - A pointer to the guest context.
|
||||
*/
|
||||
VMCALL_HANDLER HandleVmcallUninstall;
|
||||
|
||||
//
|
||||
// VMCALL handlers and mapping.
|
||||
//
|
||||
static VMCALL_HANDLER* k_VmcallHandlers[] =
|
||||
{
|
||||
HandleVmcallUninstall,
|
||||
};
|
||||
C_ASSERT(RTL_NUMBER_OF(k_VmcallHandlers) == VmcallInvalid);
|
||||
139
Sources/Ia32.h
Normal file
@@ -0,0 +1,139 @@
|
||||
/*!
|
||||
@file Ia32.h
|
||||
|
||||
@brief Intel SDM defined constants and structures.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
//
|
||||
// "nonstandard extension used: bit field types other than int"
|
||||
//
|
||||
#pragma warning(disable: 4214)
|
||||
|
||||
//
|
||||
// "nonstandard extension used: nameless struct/union"
|
||||
//
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4201)
|
||||
#include "ia32-doc/out/ia32.h"
|
||||
#pragma warning(pop)
|
||||
|
||||
//
|
||||
// The entry count within an EPT page table.
|
||||
//
|
||||
#define EPT_PTE_ENTRY_COUNT 512
|
||||
|
||||
//
|
||||
// The entry counts within paging structures.
|
||||
//
|
||||
#define PML4_ENTRY_COUNT 512
|
||||
#define PDPT_ENTRY_COUNT 512
|
||||
#define PDT_ENTRY_COUNT 512
|
||||
#define PT_ENTRY_COUNT 512
|
||||
|
||||
//
|
||||
// The entry count within the IDT.
|
||||
//
|
||||
#define IDT_ENTRY_COUNT 256
|
||||
|
||||
//
|
||||
// The levels of paging structures.
|
||||
//
|
||||
#define PT_LEVEL_PML4E 4
|
||||
#define PT_LEVEL_PDPTE 3
|
||||
#define PT_LEVEL_PDE 2
|
||||
#define PT_LEVEL_PTE 1
|
||||
|
||||
//
|
||||
// Bits useful for working with paging structures and EPTs.
|
||||
//
|
||||
#ifndef PAGE_SHIFT
|
||||
#define PAGE_SHIFT 12
|
||||
#endif
|
||||
#define PAGE_SHIFT_2BM 21
|
||||
#define PAGE_SHIFT_1GB 30
|
||||
#define PAGE_MASK (PAGE_SIZE - 1)
|
||||
|
||||
//
|
||||
// See: 11.11.2.2 Fixed Range MTRRs
|
||||
//
|
||||
typedef union _IA32_MTRR_FIXED_RANGE_MSR
|
||||
{
|
||||
struct
|
||||
{
|
||||
UINT8 Types[8];
|
||||
} u;
|
||||
UINT64 Flags;
|
||||
} IA32_MTRR_FIXED_RANGE_MSR;
|
||||
|
||||
//
|
||||
// See: Table 11-10. Memory Ranges That Can Be Encoded With PAT
|
||||
//
|
||||
typedef UINT32 IA32_MEMORY_TYPE;
|
||||
|
||||
typedef UINT64 VMCS_FIELD;
|
||||
|
||||
typedef SEGMENT_DESCRIPTOR_REGISTER_64 GDTR, IDTR;
|
||||
|
||||
typedef UINT32 IA32_MSR_ADDRESS;
|
||||
|
||||
//
|
||||
// See: Table 30-1. VM-Instruction Error Numbers
|
||||
//
|
||||
typedef UINT32 VMX_ERROR_NUMBER;
|
||||
|
||||
//
|
||||
// The helper structure for translating the guest physical address to the
|
||||
// host physical address.
|
||||
//
|
||||
typedef union _ADDRESS_TRANSLATION_HELPER
|
||||
{
|
||||
//
|
||||
// Indexes to locate paging-structure entries corresponds to this virtual
|
||||
// address.
|
||||
//
|
||||
struct
|
||||
{
|
||||
UINT64 Unused : 12; //< [11:0]
|
||||
UINT64 Pt : 9; //< [20:12]
|
||||
UINT64 Pd : 9; //< [29:21]
|
||||
UINT64 Pdpt : 9; //< [38:30]
|
||||
UINT64 Pml4 : 9; //< [47:39]
|
||||
} AsIndex;
|
||||
|
||||
//
|
||||
// The page offset for each type of pages. For example, for 4KB pages, bits
|
||||
// [11:0] are treated as the page offset and Mapping4Kb can be used for it.
|
||||
//
|
||||
union
|
||||
{
|
||||
UINT64 Mapping4Kb : 12; //< [11:0]
|
||||
UINT64 Mapping2Mb : 21; //< [20:0]
|
||||
UINT64 Mapping1Gb : 30; //< [29:0]
|
||||
} AsPageOffset;
|
||||
|
||||
UINT64 AsUInt64;
|
||||
} ADDRESS_TRANSLATION_HELPER;
|
||||
|
||||
//
|
||||
// See: Figure 7-11. 64-Bit TSS Format
|
||||
//
|
||||
#pragma pack(push, 1)
|
||||
typedef struct _TASK_STATE_SEGMENT_64
|
||||
{
|
||||
UINT32 Reserved0;
|
||||
UINT64 Rsp0;
|
||||
UINT64 Rsp1;
|
||||
UINT64 Rsp2;
|
||||
UINT64 Reserved1;
|
||||
UINT64 Ist[7];
|
||||
UINT64 Reserved3;
|
||||
UINT16 Reserved4;
|
||||
UINT16 IoMapBaseAddress;
|
||||
} TASK_STATE_SEGMENT_64;
|
||||
C_ASSERT(sizeof(TASK_STATE_SEGMENT_64) == 104);
|
||||
#pragma pack(pop)
|
||||
109
Sources/Logger.h
Normal file
@@ -0,0 +1,109 @@
|
||||
/*!
|
||||
@file Logger.h
|
||||
|
||||
@brief Declarations of functions and structures for logging.
|
||||
|
||||
@details Strings provided for the LOG_* macros are NOT removed from the
|
||||
release build. If you wish so, wrap them with preprocessor and make them
|
||||
no-op.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#pragma once
|
||||
#include "Common.h"
|
||||
|
||||
//
|
||||
// Logging chars and wide-chars require different format strings because of the
|
||||
// difference of the formatting functions. Use them like the standard's PRIx macro
|
||||
// family.
|
||||
//
|
||||
#if defined(NTDDI_VERSION)
|
||||
#define LOG_PRIANSI "s"
|
||||
#define LOG_PRIUNICODE "S"
|
||||
#else
|
||||
#define LOG_PRIANSI "a"
|
||||
#define LOG_PRIUNICODE "s"
|
||||
#endif
|
||||
|
||||
//
|
||||
// Log levels.
|
||||
//
|
||||
typedef enum _LOG_LEVEL
|
||||
{
|
||||
LogLevelNone,
|
||||
LogLevelError,
|
||||
LogLevelWarning,
|
||||
LogLevelInfo,
|
||||
LogLevelDebug,
|
||||
LogLevelReserved,
|
||||
} LOG_LEVEL;
|
||||
|
||||
/*!
|
||||
@brief Logs the error message without depending on the logger to be initialized.
|
||||
|
||||
@param[in] Format - The format string.
|
||||
*/
|
||||
#define LOG_EARLY_ERROR(Format, ...) \
|
||||
LogEarlyErrorMessage(Format ## "\n", __VA_ARGS__)
|
||||
|
||||
/*!
|
||||
@brief Logs the error-level message.
|
||||
|
||||
@param[in] Format - The format string.
|
||||
*/
|
||||
#define LOG_ERROR(Format, ...) \
|
||||
LogMessage(LogLevelError, __FUNCTION__, (Format), __VA_ARGS__)
|
||||
|
||||
/*!
|
||||
@brief Logs the warning-level message.
|
||||
|
||||
@param[in] Format - The format string.
|
||||
*/
|
||||
#define LOG_WARNING(Format, ...) \
|
||||
LogMessage(LogLevelWarning, __FUNCTION__, (Format), __VA_ARGS__)
|
||||
|
||||
/*!
|
||||
@brief Logs the information-level message.
|
||||
|
||||
@param[in] Format - The format string.
|
||||
*/
|
||||
#define LOG_INFO(Format, ...) \
|
||||
LogMessage(LogLevelInfo, __FUNCTION__, (Format), __VA_ARGS__)
|
||||
|
||||
/*!
|
||||
@brief Logs the debug-level message.
|
||||
|
||||
@param[in] Format - The format string.
|
||||
*/
|
||||
#define LOG_DEBUG(Format, ...) \
|
||||
LogMessage(LogLevelDebug, __FUNCTION__, (Format), __VA_ARGS__)
|
||||
|
||||
/*!
|
||||
@brief Logs the log message.
|
||||
|
||||
@param[in] Level - The level of the message.
|
||||
|
||||
@param[in] FunctionName - The name of the function initiated this logging.
|
||||
|
||||
@param[in] Format - The format string.
|
||||
*/
|
||||
VOID
|
||||
LogMessage (
|
||||
_In_ LOG_LEVEL Level,
|
||||
_In_ CONST CHAR* FunctionName,
|
||||
_In_ _Printf_format_string_ CONST CHAR* Format,
|
||||
...
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Logs the error log message immediately.
|
||||
|
||||
@param[in] Format - The format string.
|
||||
*/
|
||||
VOID
|
||||
LogEarlyErrorMessage (
|
||||
_In_ _Printf_format_string_ CONST CHAR* Format,
|
||||
...
|
||||
);
|
||||
712
Sources/MemoryAccess.c
Normal file
@@ -0,0 +1,712 @@
|
||||
/*!
|
||||
@file MemoryAccess.c
|
||||
|
||||
@brief Functions for guest virtual memory access from the hypervisor.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#include "MemoryAccess.h"
|
||||
#include "HostUtils.h"
|
||||
#include "Platform.h"
|
||||
#include "MemoryManager.h"
|
||||
|
||||
/*!
|
||||
@brief Split a 2MB EPT PDE to 512 EPT PTEs.
|
||||
|
||||
@param[in,out] PdeLarge - The pointer to the 2MB EPT PDE to split.
|
||||
|
||||
@return MV_STATUS_SUCCESS on success; otherwise, an appropriate error code.
|
||||
*/
|
||||
static
|
||||
_Must_inspect_result_
|
||||
MV_STATUS
|
||||
Split2MbPage (
|
||||
_Inout_ PDE_2MB_64* PdeLarge
|
||||
)
|
||||
{
|
||||
MV_STATUS status;
|
||||
PDE_64* pde;
|
||||
PTE_64* pt;
|
||||
UINT64 paBase;
|
||||
UINT64 paToMap;
|
||||
|
||||
MV_ASSERT(PdeLarge->LargePage != FALSE);
|
||||
|
||||
//
|
||||
// Allocate the PT as we are going to split one 2MB page to 512 4KB pages.
|
||||
//
|
||||
pt = MmAllocatePages(1);
|
||||
if (pt == NULL)
|
||||
{
|
||||
status = MV_STATUS_INSUFFICIENT_RESOURCES;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
//
|
||||
// Clear the large page bit, and propagate the current permissions to the
|
||||
// all entries in the PT.
|
||||
//
|
||||
PdeLarge->LargePage = FALSE;
|
||||
__stosq((UINT64*)pt, PdeLarge->Flags, PT_ENTRY_COUNT);
|
||||
|
||||
//
|
||||
// Update the page frame of each PTE.
|
||||
//
|
||||
paBase = (PdeLarge->PageFrameNumber << PAGE_SHIFT_2BM);
|
||||
for (UINT32 ptIndex = 0; ptIndex < PT_ENTRY_COUNT; ++ptIndex)
|
||||
{
|
||||
paToMap = paBase + ((UINT64)ptIndex * PAGE_SIZE);
|
||||
pt[ptIndex].PageFrameNumber = (paToMap >> PAGE_SHIFT);
|
||||
}
|
||||
|
||||
//
|
||||
// Finally, update the PDE by pointing to the PT.
|
||||
//
|
||||
pde = (PDE_64*)PdeLarge;
|
||||
pde->Reserved1 = pde->Reserved2 = 0;
|
||||
pde->PageFrameNumber = (GetPhysicalAddress(pt) >> PAGE_SHIFT);
|
||||
|
||||
status = MV_STATUS_SUCCESS;
|
||||
|
||||
Exit:
|
||||
return status;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Returns the pointer to the final paging structure entry used to
|
||||
translate the given virtual address in the current CR3.
|
||||
|
||||
@param[in] VirtualAddress - The virtual address to retrieve its PTE.
|
||||
|
||||
@param[in] HostCr3 - The host CR3.
|
||||
|
||||
@param[out] PageMapLevel - The pointer to the integer to receive the level of
|
||||
paging structures of the returned entry.
|
||||
|
||||
@return The pointer to the final paging structure when the virtual address
|
||||
is not mapped in the physical address. If not, returns the pointer to the
|
||||
paging structure entry that indicated that the page is not present (ie,
|
||||
the Present bit is cleared).
|
||||
*/
|
||||
static
|
||||
_Must_inspect_result_
|
||||
PT_ENTRY_64*
|
||||
GetPteForVa (
|
||||
_In_ VOID* VirtualAddress,
|
||||
_In_ CR3 HostCr3,
|
||||
_Out_opt_ UINT32* PageMapLevel
|
||||
)
|
||||
{
|
||||
ADDRESS_TRANSLATION_HELPER helper;
|
||||
UINT32 level;
|
||||
PT_ENTRY_64* finalEntry;
|
||||
PML4E_64* pml4;
|
||||
PML4E_64* pml4e;
|
||||
PDPTE_64* pdpt;
|
||||
PDPTE_64* pdpte;
|
||||
PDE_64* pd;
|
||||
PDE_64* pde;
|
||||
PTE_64* pt;
|
||||
PTE_64* pte;
|
||||
|
||||
helper.AsUInt64 = (UINT64)VirtualAddress;
|
||||
|
||||
//
|
||||
// Locate PML4E from CR3.
|
||||
//
|
||||
pml4 = (PML4E_64*)GetVirtualAddress(HostCr3.AddressOfPageDirectory << PAGE_SHIFT);
|
||||
pml4e = &pml4[helper.AsIndex.Pml4];
|
||||
if (pml4e->Present == FALSE)
|
||||
{
|
||||
finalEntry = (PT_ENTRY_64*)pml4e;
|
||||
level = PT_LEVEL_PML4E;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
//
|
||||
// Locate PDPTE from PML4E. If the located entry indicates this is the 1GB
|
||||
// page, return the entry.
|
||||
//
|
||||
pdpt = (PDPTE_64*)GetVirtualAddress(pml4e->PageFrameNumber << PAGE_SHIFT);
|
||||
pdpte = &pdpt[helper.AsIndex.Pdpt];
|
||||
if ((pdpte->Present == FALSE) || (pdpte->LargePage != FALSE))
|
||||
{
|
||||
finalEntry = (PT_ENTRY_64*)pdpte;
|
||||
level = PT_LEVEL_PDPTE;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
//
|
||||
// Locate PDE from PDPTE. If the located entry indicates this is the 2MB
|
||||
// page, return the entry.
|
||||
//
|
||||
pd = (PDE_64*)GetVirtualAddress(pdpte->PageFrameNumber << PAGE_SHIFT);
|
||||
pde = &pd[helper.AsIndex.Pd];
|
||||
if ((pde->Present == FALSE) || (pde->LargePage != FALSE))
|
||||
{
|
||||
finalEntry = (PT_ENTRY_64*)pde;
|
||||
level = PT_LEVEL_PDE;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
//
|
||||
// Locate PTE from PDE and return it.
|
||||
//
|
||||
pt = (PTE_64*)GetVirtualAddress(pde->PageFrameNumber << PAGE_SHIFT);
|
||||
pte = &pt[helper.AsIndex.Pt];
|
||||
finalEntry = (PT_ENTRY_64*)pte;
|
||||
level = PT_LEVEL_PTE;
|
||||
|
||||
Exit:
|
||||
if (ARGUMENT_PRESENT(PageMapLevel))
|
||||
{
|
||||
*PageMapLevel = level;
|
||||
}
|
||||
return finalEntry;
|
||||
}
|
||||
|
||||
MV_SECTION_PAGED
|
||||
_Use_decl_annotations_
|
||||
MV_STATUS
|
||||
InitializeMemoryAccess (
|
||||
MEMORY_ACCESS_CONTEXT* Context,
|
||||
CR3 HostCr3
|
||||
)
|
||||
{
|
||||
MV_STATUS status;
|
||||
UINT32 level;
|
||||
VOID* reservedPage;
|
||||
PT_ENTRY_64* reservedPagePte;
|
||||
PTE_64* allocatedPageTable;
|
||||
|
||||
PAGED_CODE();
|
||||
|
||||
allocatedPageTable = NULL;
|
||||
|
||||
//
|
||||
// Reserve a single page that will map the guest's memory to access it from
|
||||
// the hypervisor. At this point, this page is not mapped to anywhere and not
|
||||
// accessible. MapPa() will do this job.
|
||||
//
|
||||
reservedPage = ReserveVirtualAddress(1);
|
||||
if (reservedPage == NULL)
|
||||
{
|
||||
status = MV_STATUS_INSUFFICIENT_RESOURCES;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
//
|
||||
// Get the address of the paging structures entry that has the translation
|
||||
// for the virtual address. If the resulted entry is a PDE, it means the
|
||||
// virtual address is within a large (2MB) page and cannot safely modify its
|
||||
// contents. Split the PDE into PTEs in this case. This is the case on EFI
|
||||
// because we built our own identity mapping using large pages.
|
||||
//
|
||||
reservedPagePte = GetPteForVa(reservedPage, HostCr3, &level);
|
||||
if (level == PT_LEVEL_PDE)
|
||||
{
|
||||
MV_ASSERT(reservedPagePte->LargePage != FALSE);
|
||||
status = Split2MbPage((PDE_2MB_64*)reservedPagePte);
|
||||
if (MV_ERROR(status))
|
||||
{
|
||||
goto Exit;
|
||||
}
|
||||
reservedPagePte = GetPteForVa(reservedPage, HostCr3, &level);
|
||||
allocatedPageTable = PAGE_ALIGN(reservedPagePte);
|
||||
MV_ASSERT(level == PT_LEVEL_PTE);
|
||||
MV_ASSERT(reservedPagePte->LargePage == FALSE);
|
||||
}
|
||||
|
||||
//
|
||||
// Drop the translation of the virtual address. This is not required and done
|
||||
// to track map/unmap state of the reserved page. The entry may already not
|
||||
// have translation. This is the case on Windows because of underneath API.
|
||||
//
|
||||
reservedPagePte->Flags = 0;
|
||||
|
||||
//
|
||||
// We are good. Fill out the context structure.
|
||||
//
|
||||
status = MV_STATUS_SUCCESS;
|
||||
Context->ReservedPage = reservedPage;
|
||||
Context->Pte = (PTE_64*)reservedPagePte;
|
||||
Context->AllocatedPageTable = allocatedPageTable;
|
||||
|
||||
Exit:
|
||||
if (MV_ERROR(status))
|
||||
{
|
||||
if (reservedPage != NULL)
|
||||
{
|
||||
FreeReservedVirtualAddress(reservedPage, 1);
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
MV_SECTION_PAGED
|
||||
_Use_decl_annotations_
|
||||
VOID
|
||||
CleanupMemoryAccess (
|
||||
MEMORY_ACCESS_CONTEXT* Context
|
||||
)
|
||||
{
|
||||
PAGED_CODE();
|
||||
|
||||
//
|
||||
// Mapping should not be active; otherwise, FreeReservedVirtualAddress()
|
||||
// will bug checks.
|
||||
//
|
||||
MV_ASSERT(Context->Pte->Present == FALSE);
|
||||
|
||||
if (Context->AllocatedPageTable != NULL)
|
||||
{
|
||||
MmFreePages(Context->AllocatedPageTable);
|
||||
}
|
||||
|
||||
FreeReservedVirtualAddress(Context->ReservedPage, 1);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Maps the given physical address to the reserved page.
|
||||
|
||||
@details This function modifies the PTE of the reserved page to map the given
|
||||
physical address to the virtual address. This function maps the virtual
|
||||
address as writable regardless of the permission of the virtual address
|
||||
used by the guest.
|
||||
|
||||
@param[in,out] Context - The pointer to the memory access context.
|
||||
|
||||
@param[in] PhysicalAddress - The physical address to map to the reserved
|
||||
virtual address.
|
||||
|
||||
@return The virtual address that maps the specified physical address. The
|
||||
caller must unmap this using UnmapPa() when mapping is no longer needed.
|
||||
*/
|
||||
static
|
||||
_Must_inspect_result_
|
||||
VOID*
|
||||
MapPa (
|
||||
_Inout_ MEMORY_ACCESS_CONTEXT* Context,
|
||||
_In_ UINT64 PhysicalAddress
|
||||
)
|
||||
{
|
||||
//
|
||||
// Make sure the caller called UnmapPa(). This is purely for easier state
|
||||
// tracking.
|
||||
//
|
||||
MV_ASSERT(Context->Pte->Flags == 0);
|
||||
|
||||
//
|
||||
// Make the page present and writable, change the page frame, then flush TLB.
|
||||
//
|
||||
Context->Pte->Present = TRUE;
|
||||
Context->Pte->Write = TRUE;
|
||||
Context->Pte->PageFrameNumber = (PhysicalAddress >> PAGE_SHIFT);
|
||||
__invlpg(Context->ReservedPage);
|
||||
|
||||
//
|
||||
// Return the pointer within the reserved page with the page offset.
|
||||
//
|
||||
return MV_ADD2PTR(Context->ReservedPage, (PhysicalAddress & PAGE_MASK));
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Unmaps the physical address that is currently mapped to the reserved
|
||||
page.
|
||||
|
||||
@param[in,out] Context - The pointer to the memory access context.
|
||||
*/
|
||||
static
|
||||
VOID
|
||||
UnmapPa (
|
||||
_Inout_ MEMORY_ACCESS_CONTEXT* Context
|
||||
)
|
||||
{
|
||||
MV_ASSERT(Context->Pte->Flags != 0);
|
||||
|
||||
//
|
||||
// Invalidates the reserved page.
|
||||
//
|
||||
Context->Pte->Flags = 0;
|
||||
__invlpg(Context->ReservedPage);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Reads or writes memory from or to the location specified as the
|
||||
physical address.
|
||||
|
||||
@param[in] Context - The pointer to the memory access context.
|
||||
|
||||
@param[in] OperationType - Indicates whether this is read or write operation.
|
||||
|
||||
@param[in] PhysicalAddress - The physical address to read or write memory.
|
||||
|
||||
@param[in,out] Buffer - The pointer to buffer to store the read memory,
|
||||
or the pointer to buffer containing data to write.
|
||||
|
||||
@param[in] BytesToCopy - The size to read or write in bytes.
|
||||
*/
|
||||
static
|
||||
VOID
|
||||
ReadOrWriteOnPhysicalAddress (
|
||||
_In_ MEMORY_ACCESS_CONTEXT* Context,
|
||||
_In_ OPERATION_TYPE OperationType,
|
||||
_In_ UINT64 PhysicalAddress,
|
||||
_When_(OperationType == OperationRead, _Out_writes_bytes_(BytesToCopy))
|
||||
_When_(OperationType == OperationWrite, _In_reads_bytes_(BytesToCopy)) VOID* Buffer,
|
||||
_In_ UINT64 BytesToCopy
|
||||
)
|
||||
{
|
||||
VOID* mappedVa;
|
||||
|
||||
//
|
||||
// BytesToCopy should be more than one and within the range or the page.
|
||||
//
|
||||
MV_ASSERT(BytesToCopy != 0);
|
||||
MV_ASSERT(BytesToCopy <= (PAGE_SIZE - (PhysicalAddress & PAGE_MASK)));
|
||||
|
||||
//
|
||||
// Map the physical address to this address space and copy to or from it.
|
||||
//
|
||||
mappedVa = MapPa(Context, PhysicalAddress);
|
||||
if (OperationType == OperationRead)
|
||||
{
|
||||
RtlCopyMemory(Buffer, mappedVa, BytesToCopy);
|
||||
}
|
||||
else
|
||||
{
|
||||
RtlCopyMemory(mappedVa, Buffer, BytesToCopy);
|
||||
}
|
||||
UnmapPa(Context);
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
UINT64
|
||||
GetPhysicalAddressForGuest (
|
||||
MEMORY_ACCESS_CONTEXT* Context,
|
||||
UINT64 GuestVirtualAddress,
|
||||
PT_ENTRY_64* AggregatedPagePermissions
|
||||
)
|
||||
{
|
||||
UINT64 pa;
|
||||
ADDRESS_TRANSLATION_HELPER helper;
|
||||
PT_ENTRY_64 permission;
|
||||
CR3 guestCr3;
|
||||
UINT64 tableBasePa;
|
||||
UINT64 tableEntryPa;
|
||||
PML4E_64 pml4e;
|
||||
PDPTE_64 pdpte;
|
||||
PDE_64 pde;
|
||||
PTE_64 pte;
|
||||
|
||||
//
|
||||
// Return MV_INVALID_PHYSICAL_ADDRESS if the virtual address is not mapped into
|
||||
// the guest address space (ie, there is no associated physical memory).
|
||||
//
|
||||
pa = MV_INVALID_PHYSICAL_ADDRESS;
|
||||
helper.AsUInt64 = GuestVirtualAddress;
|
||||
permission.Flags = 0;
|
||||
guestCr3.Flags = VmxRead(VMCS_GUEST_CR3);
|
||||
|
||||
//
|
||||
// Read the guest PML4E from the guest CR3. If the page is present, save the
|
||||
// permission bits.
|
||||
//
|
||||
tableBasePa = (guestCr3.AddressOfPageDirectory << PAGE_SHIFT);
|
||||
tableEntryPa = tableBasePa + (helper.AsIndex.Pml4 * sizeof(PML4E_64));
|
||||
ReadOrWriteOnPhysicalAddress(Context, OperationRead, tableEntryPa, &pml4e, sizeof(pml4e));
|
||||
if (pml4e.Present == FALSE)
|
||||
{
|
||||
goto Exit;
|
||||
}
|
||||
permission.Write = pml4e.Write;
|
||||
permission.Supervisor = pml4e.Supervisor;
|
||||
permission.ExecuteDisable = pml4e.ExecuteDisable;
|
||||
|
||||
//
|
||||
// Read the guest PDPTE from the guest PML4E. If the page is present,
|
||||
// aggregate the permission bits.
|
||||
//
|
||||
tableBasePa = (pml4e.PageFrameNumber << PAGE_SHIFT);
|
||||
tableEntryPa = tableBasePa + (helper.AsIndex.Pdpt * sizeof(PDPTE_64));
|
||||
ReadOrWriteOnPhysicalAddress(Context, OperationRead, tableEntryPa, &pdpte, sizeof(pdpte));
|
||||
if (pdpte.Present == FALSE)
|
||||
{
|
||||
goto Exit;
|
||||
}
|
||||
permission.Write &= pdpte.Write;
|
||||
permission.Supervisor &= pdpte.Supervisor;
|
||||
permission.ExecuteDisable |= pdpte.ExecuteDisable;
|
||||
|
||||
//
|
||||
// In case of the 1GB page, compute the physical address and exit.
|
||||
//
|
||||
if (pdpte.LargePage != FALSE)
|
||||
{
|
||||
PDPTE_1GB_64 pdpte1Gb;
|
||||
|
||||
pdpte1Gb.Flags = pdpte.Flags;
|
||||
pa = (pdpte1Gb.PageFrameNumber << PAGE_SHIFT_1GB) | helper.AsPageOffset.Mapping1Gb;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
//
|
||||
// Same. Read the guest PDE from the guest PDPTE. If the page is present,
|
||||
// aggregate the permission bits.
|
||||
//
|
||||
tableBasePa = (pdpte.PageFrameNumber << PAGE_SHIFT);
|
||||
tableEntryPa = tableBasePa + (helper.AsIndex.Pd * sizeof(PDE_64));
|
||||
ReadOrWriteOnPhysicalAddress(Context, OperationRead, tableEntryPa, &pde, sizeof(pde));
|
||||
if (pde.Present == FALSE)
|
||||
{
|
||||
goto Exit;
|
||||
}
|
||||
permission.Write &= pde.Write;
|
||||
permission.Supervisor &= pde.Supervisor;
|
||||
permission.ExecuteDisable |= pde.ExecuteDisable;
|
||||
|
||||
//
|
||||
// Same. If the page is the 2MB page, exit here.
|
||||
//
|
||||
if (pde.LargePage != FALSE)
|
||||
{
|
||||
PDE_2MB_64 pde2Mb;
|
||||
|
||||
pde2Mb.Flags = pde.Flags;
|
||||
pa = (pde2Mb.PageFrameNumber << PAGE_SHIFT_2BM) | helper.AsPageOffset.Mapping2Mb;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
//
|
||||
// Same. Read the guest PTE from the guest PDE. If the page is present,
|
||||
// aggregate the permission bits. Finally, compute the physical address.
|
||||
//
|
||||
tableBasePa = (pde.PageFrameNumber << PAGE_SHIFT);
|
||||
tableEntryPa = tableBasePa + (helper.AsIndex.Pt * sizeof(PTE_64));
|
||||
ReadOrWriteOnPhysicalAddress(Context, OperationRead, tableEntryPa, &pte, sizeof(pte));
|
||||
if (pte.Present == FALSE)
|
||||
{
|
||||
goto Exit;
|
||||
}
|
||||
permission.Write &= pte.Write;
|
||||
permission.Supervisor &= pte.Supervisor;
|
||||
permission.ExecuteDisable |= pte.ExecuteDisable;
|
||||
|
||||
pa = (pte.PageFrameNumber << PAGE_SHIFT) | helper.AsPageOffset.Mapping4Kb;
|
||||
|
||||
Exit:
|
||||
//
|
||||
// Return the collected permission bits on success.
|
||||
//
|
||||
if ((pa != MV_INVALID_PHYSICAL_ADDRESS) &&
|
||||
ARGUMENT_PRESENT(AggregatedPagePermissions))
|
||||
{
|
||||
*AggregatedPagePermissions = permission;
|
||||
}
|
||||
return pa;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Reads or writes memory from or the location specified as the guest
|
||||
virtual address.
|
||||
|
||||
@param[in] Context - The pointer to the memory access context.
|
||||
|
||||
@param[in] OperationType - Whether this is a read or write operation.
|
||||
|
||||
@param[in] KernelMode - Whether this is kernel-mode access.
|
||||
|
||||
@param[in] GuestVirtualAddress - The guest virtual address to work on.
|
||||
|
||||
@param[in,out] Buffer - The pointer to buffer to store the read memory,
|
||||
or the pointer to buffer containing data to write.
|
||||
|
||||
@param[in] BytesToCopy - The size to read or write in bytes.
|
||||
|
||||
@param[out] ErrorInformation - The pointer to the structure to receive error
|
||||
information on failure. On success, this structure is cleared to zero.
|
||||
|
||||
@return TRUE when the requested operation is completed. Otherwise, for example,
|
||||
when it encountered page permission violation in the middle, FALSE.
|
||||
*/
|
||||
static
|
||||
_Success_(return != FALSE)
|
||||
_Must_inspect_result_
|
||||
BOOLEAN
|
||||
ReadOrWriteGuestVirtualAddress (
|
||||
_In_ MEMORY_ACCESS_CONTEXT* Context,
|
||||
_In_ OPERATION_TYPE OperationType,
|
||||
_In_ BOOLEAN KernelMode,
|
||||
_In_ UINT64 GuestVirtualAddress,
|
||||
_When_(OperationType == OperationRead, _Out_writes_bytes_(BytesToCopy))
|
||||
_When_(OperationType == OperationWrite, _In_reads_bytes_(BytesToCopy)) VOID* Buffer,
|
||||
_In_ UINT64 BytesToCopy,
|
||||
_Out_ MEMORY_ACCESS_ERROR_INFORMATION* ErrorInformation
|
||||
)
|
||||
{
|
||||
BOOLEAN successful;
|
||||
UINT64 physicalAddress;
|
||||
VOID* failedVa;
|
||||
UINT64 guestVaToOperate;
|
||||
UINT8* currentBuffer;
|
||||
UINT64 remainingBytesToCopy;
|
||||
|
||||
//
|
||||
// Likely a programming error. Catch it.
|
||||
//
|
||||
MV_ASSERT(BytesToCopy != 0);
|
||||
|
||||
RtlZeroMemory(ErrorInformation, sizeof(*ErrorInformation));
|
||||
|
||||
successful = FALSE;
|
||||
|
||||
//
|
||||
// Start iterating memory access until all request bytes are processed.
|
||||
// Each iteration is at most 4KB-length.
|
||||
//
|
||||
// Note that this is broken in that it does not guarantee atomicity of memory
|
||||
// access. Consider the case where a single memory access is performed on the
|
||||
// page boundary, and only the 2nd page is paged out. This logic will access
|
||||
// to the first page, then injects #PF to complete access to the 2nd page.
|
||||
// As this lets the guest to execute the #PF handler and there is a relatively
|
||||
// larger window that allows other core to modify the 2nd page meanwhile.
|
||||
//
|
||||
failedVa = NULL;
|
||||
currentBuffer = Buffer;
|
||||
guestVaToOperate = GuestVirtualAddress;
|
||||
remainingBytesToCopy = BytesToCopy;
|
||||
while (remainingBytesToCopy > 0)
|
||||
{
|
||||
UINT64 bytesToOperate;
|
||||
UINT64 accessibleBytes;
|
||||
PT_ENTRY_64 permissions;
|
||||
|
||||
//
|
||||
// Round down the operation length to the page-boundary.
|
||||
//
|
||||
accessibleBytes = PAGE_SIZE - (guestVaToOperate & PAGE_MASK);
|
||||
bytesToOperate = MV_MIN(accessibleBytes, remainingBytesToCopy);
|
||||
|
||||
//
|
||||
// Try to get the physical address.
|
||||
//
|
||||
physicalAddress = GetPhysicalAddressForGuest(Context,
|
||||
guestVaToOperate,
|
||||
&permissions);
|
||||
if ((physicalAddress == MV_INVALID_PHYSICAL_ADDRESS) ||
|
||||
((permissions.Write == FALSE) && (OperationType == OperationWrite)) ||
|
||||
((permissions.Supervisor == FALSE) && (KernelMode == FALSE)))
|
||||
{
|
||||
//
|
||||
// Either the page not present, write access to non-writable page, or
|
||||
// kernel address access from the user-mode. Inject #PF(ErrorCode).
|
||||
// See: Interrupt 14-Page-Fault Exception (#PF)
|
||||
//
|
||||
ErrorInformation->ErrorType = PageFault;
|
||||
ErrorInformation->u.PageFault.FaultAddress = guestVaToOperate;
|
||||
ErrorInformation->u.PageFault.ErrorCode.Present = (physicalAddress != MV_INVALID_PHYSICAL_ADDRESS);
|
||||
ErrorInformation->u.PageFault.ErrorCode.Write = (OperationType == OperationWrite);
|
||||
ErrorInformation->u.PageFault.ErrorCode.UserModeAccess = (KernelMode == FALSE);
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
//
|
||||
// Copy bytes from or to the physical address as requested.
|
||||
//
|
||||
ReadOrWriteOnPhysicalAddress(Context,
|
||||
OperationType,
|
||||
physicalAddress,
|
||||
currentBuffer,
|
||||
bytesToOperate);
|
||||
|
||||
currentBuffer += bytesToOperate;
|
||||
guestVaToOperate += bytesToOperate;
|
||||
remainingBytesToCopy -= bytesToOperate;
|
||||
}
|
||||
|
||||
successful = TRUE;
|
||||
|
||||
Exit:
|
||||
return successful;
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
BOOLEAN
|
||||
ReadGuestVirtualAddress (
|
||||
MEMORY_ACCESS_CONTEXT* Context,
|
||||
BOOLEAN KernelMode,
|
||||
UINT64 GuestVirtualAddress,
|
||||
VOID* Buffer,
|
||||
UINT64 BytesToRead,
|
||||
MEMORY_ACCESS_ERROR_INFORMATION* ErrorInformation
|
||||
)
|
||||
{
|
||||
return ReadOrWriteGuestVirtualAddress(Context,
|
||||
OperationRead,
|
||||
KernelMode,
|
||||
GuestVirtualAddress,
|
||||
Buffer,
|
||||
BytesToRead,
|
||||
ErrorInformation);
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
BOOLEAN
|
||||
WriteGuestVirtualAddress (
|
||||
MEMORY_ACCESS_CONTEXT* Context,
|
||||
BOOLEAN KernelMode,
|
||||
UINT64 GuestVirtualAddress,
|
||||
CONST VOID* Data,
|
||||
UINT64 BytesToWrite,
|
||||
MEMORY_ACCESS_ERROR_INFORMATION* ErrorInformation
|
||||
)
|
||||
{
|
||||
return ReadOrWriteGuestVirtualAddress(Context,
|
||||
OperationWrite,
|
||||
KernelMode,
|
||||
GuestVirtualAddress,
|
||||
(VOID*)Data,
|
||||
BytesToWrite,
|
||||
ErrorInformation);
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
VOID*
|
||||
MapGuestPage (
|
||||
MEMORY_ACCESS_CONTEXT* Context,
|
||||
UINT64 GuestPageNumber
|
||||
)
|
||||
{
|
||||
UINT64 physicalAddress;
|
||||
VOID* mappedVa;
|
||||
|
||||
mappedVa = NULL;
|
||||
|
||||
physicalAddress = GetPhysicalAddressForGuest(Context,
|
||||
(GuestPageNumber << PAGE_SHIFT),
|
||||
NULL);
|
||||
if (physicalAddress == MV_INVALID_PHYSICAL_ADDRESS)
|
||||
{
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
mappedVa = MapPa(Context, physicalAddress);
|
||||
|
||||
Exit:
|
||||
return mappedVa;
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
VOID
|
||||
UnmapGuestPage (
|
||||
MEMORY_ACCESS_CONTEXT* Context,
|
||||
VOID* MappedVa
|
||||
)
|
||||
{
|
||||
MV_ASSERT(MappedVa == Context->ReservedPage);
|
||||
DBG_UNREFERENCED_PARAMETER(MappedVa);
|
||||
|
||||
UnmapPa(Context);
|
||||
}
|
||||
202
Sources/MemoryAccess.h
Normal file
@@ -0,0 +1,202 @@
|
||||
/*!
|
||||
@file MemoryAccess.h
|
||||
|
||||
@brief Functions for guest virtual memory access from the hypervisor.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#pragma once
|
||||
#include "Common.h"
|
||||
#include "Ia32.h"
|
||||
|
||||
typedef struct _MEMORY_ACCESS_CONTEXT
|
||||
{
|
||||
//
|
||||
// Reserved virtual address (page) for access to the guest virtual memory
|
||||
// from the hypervisor.
|
||||
//
|
||||
VOID* ReservedPage;
|
||||
|
||||
//
|
||||
// The pointer to the PTE of the reserved page.
|
||||
//
|
||||
PTE_64* Pte;
|
||||
|
||||
//
|
||||
// The address of the page table that is dynamically allocated to translate
|
||||
// ReservedPage with the 4KB page (and not the large page).
|
||||
//
|
||||
PTE_64* AllocatedPageTable;
|
||||
} MEMORY_ACCESS_CONTEXT;
|
||||
|
||||
//
|
||||
// Error information can be filled by (Read|Write)GuestVirtualAddress().
|
||||
//
|
||||
typedef struct _MEMORY_ACCESS_ERROR_INFORMATION
|
||||
{
|
||||
EXCEPTION_VECTOR ErrorType;
|
||||
union
|
||||
{
|
||||
struct
|
||||
{
|
||||
PAGE_FAULT_EXCEPTION ErrorCode;
|
||||
UINT64 FaultAddress;
|
||||
} PageFault;
|
||||
} u;
|
||||
} MEMORY_ACCESS_ERROR_INFORMATION;
|
||||
|
||||
/*!
|
||||
@brief Initializes the memory access context.
|
||||
|
||||
@param[out] Context - The pointer to the context to initialize. On success,
|
||||
the caller must clean up this context with CleanupMemoryAccess().
|
||||
|
||||
@param[in] HostCr3 - The host CR3.
|
||||
|
||||
@return MV_STATUS_SUCCESS on success; otherwise, an appropriate error code.
|
||||
*/
|
||||
_IRQL_requires_max_(APC_LEVEL)
|
||||
_Must_inspect_result_
|
||||
MV_STATUS
|
||||
InitializeMemoryAccess (
|
||||
_Out_ MEMORY_ACCESS_CONTEXT* Context,
|
||||
_In_ CR3 HostCr3
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Cleans up the memory access context initialized with
|
||||
InitializeMemoryAccess().
|
||||
|
||||
@param[in,out] Context - The pointer to the context to clean up.
|
||||
*/
|
||||
_IRQL_requires_max_(APC_LEVEL)
|
||||
VOID
|
||||
CleanupMemoryAccess (
|
||||
_Inout_ MEMORY_ACCESS_CONTEXT* Context
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Retrieves the physical address associated with the guest virtual
|
||||
address.
|
||||
|
||||
@details This function walks through the guest paging structures and for the
|
||||
given virtual address and retrieves the guest physical address of it.
|
||||
This is equivalent to changing the current CR3 with the guest CR3 and
|
||||
calling GetPhysicalAddress(). This function, however, exists to avoid
|
||||
problems associated with CR3 update, for example, updating the CR3 crashes
|
||||
the system immediately if the KVA Shadow is enabled and the guest CR3
|
||||
contains the USER CR3, as it does not map our driver.
|
||||
|
||||
@param[in] Context - The pointer to the memory access context.
|
||||
|
||||
@param[in] GuestVirtualAddress - The guest virtual address to look for its
|
||||
physical address.
|
||||
|
||||
@param[out] AggregatedPagePermissions - The pointer to the paging-structure
|
||||
entry to receive aggregated copy of the page permissions specified in
|
||||
the guest paging structure entries used to translate the guest virtual
|
||||
address. If the guest physical address is mapped to the physical address,
|
||||
Write, Supervisor, and ExecuteDisable bits are updated accordingly, and
|
||||
the rest of bits are cleared.
|
||||
|
||||
@return The physical address associated with the specified virtual address if
|
||||
exists. Otherwise, MV_INVALID_PHYSICAL_ADDRESS.
|
||||
*/
|
||||
_Must_inspect_result_
|
||||
_Success_(return != MV_INVALID_PHYSICAL_ADDRESS)
|
||||
UINT64
|
||||
GetPhysicalAddressForGuest (
|
||||
_In_ MEMORY_ACCESS_CONTEXT* Context,
|
||||
_In_ UINT64 GuestVirtualAddress,
|
||||
_Out_opt_ PT_ENTRY_64* AggregatedPagePermissions
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Reads memory from the location specified as the guest virtual address.
|
||||
|
||||
@param[in] Context - See ReadOrWriteGuestVirtualAddress().
|
||||
|
||||
@param[in] KernelMode - See ReadOrWriteGuestVirtualAddress().
|
||||
|
||||
@param[in] GuestVirtualAddress - See ReadOrWriteGuestVirtualAddress().
|
||||
|
||||
@param[out] Buffer - See ReadOrWriteGuestVirtualAddress().
|
||||
|
||||
@param[in] BytesToRead - See ReadOrWriteGuestVirtualAddress().
|
||||
|
||||
@param[out] ErrorInformation - See ReadOrWriteGuestVirtualAddress().
|
||||
|
||||
@return See ReadOrWriteGuestVirtualAddress().
|
||||
*/
|
||||
_Must_inspect_result_
|
||||
BOOLEAN
|
||||
ReadGuestVirtualAddress (
|
||||
_In_ MEMORY_ACCESS_CONTEXT* Context,
|
||||
_In_ BOOLEAN KernelMode,
|
||||
_In_ UINT64 GuestVirtualAddress,
|
||||
_Out_writes_bytes_(BytesToRead) VOID* Buffer,
|
||||
_In_ UINT64 BytesToRead,
|
||||
_Out_ MEMORY_ACCESS_ERROR_INFORMATION* ErrorInformation
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Write memory to the location specified as the guest virtual address.
|
||||
|
||||
@param[in] Context - See ReadOrWriteGuestVirtualAddress().
|
||||
|
||||
@param[in] KernelMode - See ReadOrWriteGuestVirtualAddress().
|
||||
|
||||
@param[in] GuestVirtualAddress - See ReadOrWriteGuestVirtualAddress().
|
||||
|
||||
@param[out] Data - See ReadOrWriteGuestVirtualAddress().
|
||||
|
||||
@param[in] BytesToWrite - See ReadOrWriteGuestVirtualAddress().
|
||||
|
||||
@param[out] ErrorInformation - See ReadOrWriteGuestVirtualAddress().
|
||||
|
||||
@return See ReadOrWriteGuestVirtualAddress().
|
||||
*/
|
||||
_Must_inspect_result_
|
||||
BOOLEAN
|
||||
WriteGuestVirtualAddress (
|
||||
_In_ MEMORY_ACCESS_CONTEXT* Context,
|
||||
_In_ BOOLEAN KernelMode,
|
||||
_In_ UINT64 GuestVirtualAddress,
|
||||
_In_reads_bytes_(BytesToWrite) CONST VOID* Data,
|
||||
_In_ UINT64 BytesToWrite,
|
||||
_Out_ MEMORY_ACCESS_ERROR_INFORMATION* ErrorInformation
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Maps the specified guest page number to the current address space.
|
||||
|
||||
@param[in] Context - The pointer to the memory access context.
|
||||
|
||||
@param[in] GuestPageNumber - See ReadOrWriteGuestVirtualAddress().
|
||||
|
||||
@return The virtual address mapping the same physical page as specified as
|
||||
the page number, or NULL if the specified page number does not have
|
||||
a corresponding physical page. The caller must unmap the return value
|
||||
with UnmapGuestPage() when it is no longer needed.
|
||||
*/
|
||||
_Must_inspect_result_
|
||||
VOID*
|
||||
MapGuestPage (
|
||||
_Inout_ MEMORY_ACCESS_CONTEXT* Context,
|
||||
_In_ UINT64 GuestPageNumber
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Unmaps the address mapped with MapGuestPage().
|
||||
|
||||
@param[in] Context - The pointer to the memory access context.
|
||||
|
||||
@param[in] MappedVa - The pointer returned by MapGuestPage().
|
||||
*/
|
||||
VOID
|
||||
UnmapGuestPage (
|
||||
_Inout_ MEMORY_ACCESS_CONTEXT* Context,
|
||||
_In_ VOID* MappedVa
|
||||
);
|
||||
298
Sources/MemoryManager.c
Normal file
@@ -0,0 +1,298 @@
|
||||
/*!
|
||||
@file MemoryManager.c
|
||||
|
||||
@brief Functions for memory management.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#include "MemoryManager.h"
|
||||
#include "Platform.h"
|
||||
#include "Logger.h"
|
||||
#if !defined(NTDDI_VERSION)
|
||||
#include "Platform/EFI/EfiBitmap.h"
|
||||
#endif
|
||||
|
||||
#if !defined(CHAR_BIT)
|
||||
#define CHAR_BIT (8)
|
||||
#endif
|
||||
|
||||
typedef struct _MEMORY_MANAGER_CONTEXT
|
||||
{
|
||||
//
|
||||
// Lock for concurrent access to the this memory manager instance.
|
||||
//
|
||||
SPIN_LOCK SpinLock;
|
||||
|
||||
//
|
||||
// The number of pages reserved for use by the memory manager, and the
|
||||
// pointer to the reserved pages.
|
||||
//
|
||||
UINT32 PageCount;
|
||||
VOID* AllocatedPages;
|
||||
|
||||
//
|
||||
// The bit index pointing to the bit found to be clear and used by the latest
|
||||
// allocation within AllocatedPages. The memory manager will start look for
|
||||
// the next clear bit from this index as optimization.
|
||||
//
|
||||
UINT32 LastUsedBitIndex;
|
||||
|
||||
//
|
||||
// The bitmap header and actual bitmap buffer. The memory manager tracks
|
||||
// which pages within AllocatedPages are allocated for caller by setting a
|
||||
// bit to the corresponding offset in this bitmap. For example, if
|
||||
// AllocatedPages[0] to AllocatedPages[3] are allocated, bit 0-3 of the
|
||||
// bitmap are set.
|
||||
//
|
||||
RTL_BITMAP BitmapHeader;
|
||||
VOID* AllocationBitmap;
|
||||
|
||||
//
|
||||
// The array of the allocated page lengths for callers. The memory manager
|
||||
// tracks how many pages are allocated by the single request by setting the
|
||||
// length in a corresponding entry in this array. For example, if the caller
|
||||
// requests 3 pages, and the memory manager finds 3 contiguous free pages at
|
||||
// AllocatedPages[0], the memory manager sets 3 to AllocationLengthMap[0].
|
||||
// This is used to know the page length from the pointer on FreePages().
|
||||
//
|
||||
UINT8* AllocationLengthMap;
|
||||
} MEMORY_MANAGER_CONTEXT;
|
||||
|
||||
//
|
||||
// The singleton instance of the memory manager.
|
||||
//
|
||||
static MEMORY_MANAGER_CONTEXT g_MemoryManager;
|
||||
|
||||
MV_SECTION_PAGED
|
||||
_Use_decl_annotations_
|
||||
MV_STATUS
|
||||
MmInitializeMemoryManager (
|
||||
UINT32 PageCount
|
||||
)
|
||||
{
|
||||
MV_STATUS status;
|
||||
VOID* pages;
|
||||
UINT32 bitmapBytesCount;
|
||||
UINT32 bitmapPagesCount;
|
||||
VOID* bitmap;
|
||||
UINT8* lengthMap;
|
||||
UINT32 lengthMapBytesCount;
|
||||
UINT32 lengthMapPagesCount;
|
||||
MEMORY_MANAGER_CONTEXT* memoryManager;
|
||||
|
||||
PAGED_CODE()
|
||||
|
||||
memoryManager = &g_MemoryManager;
|
||||
lengthMapPagesCount = 0;
|
||||
lengthMap = NULL;
|
||||
bitmapPagesCount = 0;
|
||||
bitmap = NULL;
|
||||
pages = NULL;
|
||||
|
||||
MV_ASSERT(PageCount > 0);
|
||||
MV_ASSERT(memoryManager->PageCount == 0);
|
||||
|
||||
//
|
||||
// Allocate the memory pool for the memory manager. This can be VERY large
|
||||
// memory allocation request and fail on system with little RAM.
|
||||
//
|
||||
pages = AllocateSystemMemory(PageCount);
|
||||
if (pages == NULL)
|
||||
{
|
||||
status = MV_STATUS_INSUFFICIENT_RESOURCES;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
//
|
||||
// Computes how many bytes are required to cover the PageCount bits,
|
||||
// round it up to the page count (as we do not have API to allocate smaller
|
||||
// granularity), then allocate the bitmap.
|
||||
//
|
||||
bitmapBytesCount = (PageCount / CHAR_BIT) +
|
||||
((PageCount % CHAR_BIT) != 0);
|
||||
bitmapPagesCount = BYTES_TO_PAGES(bitmapBytesCount);
|
||||
bitmap = AllocateSystemMemory(bitmapPagesCount);
|
||||
if (bitmap == NULL)
|
||||
{
|
||||
status = MV_STATUS_INSUFFICIENT_RESOURCES;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
//
|
||||
// Compute the how many bytes are required to make the array of UINT8s
|
||||
// (lengths) for "PageCount" entries. Then, round it up to the page count
|
||||
// and allocate it.
|
||||
//
|
||||
lengthMapBytesCount = (PageCount * sizeof(UINT8));
|
||||
lengthMapPagesCount = BYTES_TO_PAGES(lengthMapBytesCount);
|
||||
lengthMap = AllocateSystemMemory(lengthMapPagesCount);
|
||||
if (lengthMap == NULL)
|
||||
{
|
||||
status = MV_STATUS_INSUFFICIENT_RESOURCES;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
//
|
||||
// All good. Initialize the memory manager instance.
|
||||
//
|
||||
status = MV_STATUS_SUCCESS;
|
||||
|
||||
InitializeSystemSpinLock(&memoryManager->SpinLock);
|
||||
memoryManager->PageCount = PageCount;
|
||||
memoryManager->AllocatedPages = pages;
|
||||
memoryManager->LastUsedBitIndex = 0;
|
||||
RtlInitializeBitMap(&memoryManager->BitmapHeader, bitmap, PageCount);
|
||||
memoryManager->AllocationBitmap = bitmap;
|
||||
memoryManager->AllocationLengthMap = lengthMap;
|
||||
|
||||
Exit:
|
||||
if (MV_ERROR(status))
|
||||
{
|
||||
if (lengthMap != NULL)
|
||||
{
|
||||
FreeSystemMemory(lengthMap, lengthMapPagesCount);
|
||||
}
|
||||
if (bitmap != NULL)
|
||||
{
|
||||
FreeSystemMemory(bitmap, bitmapPagesCount);
|
||||
}
|
||||
if (pages != NULL)
|
||||
{
|
||||
FreeSystemMemory(pages, PageCount);
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
MV_SECTION_PAGED
|
||||
_Use_decl_annotations_
|
||||
VOID
|
||||
MmCleanupMemoryManager (
|
||||
)
|
||||
{
|
||||
UINT32 bitmapBytesCount;
|
||||
UINT32 bitmapPagesCount;
|
||||
UINT32 lengthMapBytesCount;
|
||||
UINT32 lengthMapPagesCount;
|
||||
MEMORY_MANAGER_CONTEXT* memoryManager;
|
||||
|
||||
PAGED_CODE()
|
||||
|
||||
memoryManager = &g_MemoryManager;
|
||||
|
||||
//
|
||||
// The memory manager must be initialized already.
|
||||
//
|
||||
MV_ASSERT(memoryManager->PageCount != 0);
|
||||
MV_ASSERT(memoryManager->AllocatedPages != NULL);
|
||||
MV_ASSERT(memoryManager->AllocationLengthMap != NULL);
|
||||
MV_ASSERT(memoryManager->AllocatedPages != NULL);
|
||||
|
||||
//
|
||||
// All memory allocated for the callers must be freed.
|
||||
//
|
||||
MV_ASSERT(RtlAreBitsClear(&memoryManager->BitmapHeader,
|
||||
0,
|
||||
memoryManager->PageCount) != FALSE);
|
||||
|
||||
bitmapBytesCount = (memoryManager->PageCount / CHAR_BIT) +
|
||||
((memoryManager->PageCount % CHAR_BIT) != 0);
|
||||
bitmapPagesCount = BYTES_TO_PAGES(bitmapBytesCount);
|
||||
|
||||
lengthMapBytesCount = (memoryManager->PageCount * sizeof(UINT8));
|
||||
lengthMapPagesCount = BYTES_TO_PAGES(lengthMapBytesCount);
|
||||
|
||||
FreeSystemMemory(memoryManager->AllocationBitmap,
|
||||
bitmapPagesCount);
|
||||
FreeSystemMemory(memoryManager->AllocationLengthMap,
|
||||
lengthMapPagesCount);
|
||||
FreeSystemMemory(memoryManager->AllocatedPages,
|
||||
memoryManager->PageCount);
|
||||
|
||||
RtlZeroMemory(memoryManager, sizeof(*memoryManager));
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
VOID*
|
||||
MmAllocatePages (
|
||||
UINT8 PageCount
|
||||
)
|
||||
{
|
||||
VOID* pages;
|
||||
UINT32 bitIndex;
|
||||
MEMORY_MANAGER_CONTEXT* memoryManager;
|
||||
UINT8 oldIrql;
|
||||
|
||||
memoryManager = &g_MemoryManager;
|
||||
|
||||
//
|
||||
// Search the contiguous free page(s) that suffices the request.
|
||||
//
|
||||
oldIrql = AcquireSystemSpinLock(&memoryManager->SpinLock);
|
||||
bitIndex = RtlFindClearBitsAndSet(&memoryManager->BitmapHeader,
|
||||
PageCount,
|
||||
memoryManager->LastUsedBitIndex);
|
||||
ReleaseSystemSpinLock(&memoryManager->SpinLock, oldIrql);
|
||||
|
||||
if (bitIndex == MAXUINT32)
|
||||
{
|
||||
MV_DEBUG_BREAK();
|
||||
LOG_ERROR("Memory allocation failed : %lu", (UINT32)PageCount * PAGE_SIZE);
|
||||
pages = NULL;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
//
|
||||
// Return the page(s) from the pool, and update the book keeping fields.
|
||||
//
|
||||
pages = MV_ADD2PTR(memoryManager->AllocatedPages, ((UINT64)bitIndex * PAGE_SIZE));
|
||||
memoryManager->AllocationLengthMap[bitIndex] = PageCount;
|
||||
memoryManager->LastUsedBitIndex = bitIndex;
|
||||
|
||||
Exit:
|
||||
return pages;
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
VOID
|
||||
MmFreePages (
|
||||
VOID* Pages
|
||||
)
|
||||
{
|
||||
UINT64 offsetInBytes;
|
||||
UINT32 bitIndex;
|
||||
MEMORY_MANAGER_CONTEXT* memoryManager;
|
||||
UINT8 oldIrql;
|
||||
UINT8 pageLength;
|
||||
|
||||
memoryManager = &g_MemoryManager;
|
||||
|
||||
//
|
||||
// The pointer must be page aligned, within the range of
|
||||
// [AllocatedPages, AllocatedPages + PageCount).
|
||||
//
|
||||
MV_ASSERT(Pages == PAGE_ALIGN(Pages));
|
||||
MV_ASSERT((UINT64)Pages >= (UINT64)memoryManager->AllocatedPages);
|
||||
MV_ASSERT((UINT64)Pages <
|
||||
(UINT64)memoryManager->AllocatedPages + ((UINT64)memoryManager->PageCount * PAGE_SIZE));
|
||||
|
||||
//
|
||||
// Compute the bit index corresponds to the pointer requested for freeing,
|
||||
// and look up its length with it. The length must be more than zero, meaning
|
||||
// that the page allocated for the caller.
|
||||
//
|
||||
offsetInBytes = ((UINT64)Pages - (UINT64)memoryManager->AllocatedPages);
|
||||
bitIndex = (UINT32)(offsetInBytes / PAGE_SIZE);
|
||||
pageLength = memoryManager->AllocationLengthMap[bitIndex];
|
||||
MV_ASSERT(pageLength > 0);
|
||||
|
||||
//
|
||||
// Clears the bitmap and the length to "free" the page.
|
||||
//
|
||||
oldIrql = AcquireSystemSpinLock(&memoryManager->SpinLock);
|
||||
RtlClearBits(&memoryManager->BitmapHeader, bitIndex, pageLength);
|
||||
memoryManager->AllocationLengthMap[bitIndex] = 0;
|
||||
ReleaseSystemSpinLock(&memoryManager->SpinLock, oldIrql);
|
||||
}
|
||||
61
Sources/MemoryManager.h
Normal file
@@ -0,0 +1,61 @@
|
||||
/*!
|
||||
@file MemoryManager.h
|
||||
|
||||
@brief Functions for memory management.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#pragma once
|
||||
#include "Common.h"
|
||||
|
||||
/*!
|
||||
@brief Allocates the requested size of memory and initialize the singleton
|
||||
memory manager instance with it.
|
||||
|
||||
@param[in] PageCount - The page count to allocate for the use by the
|
||||
memory manager.
|
||||
|
||||
@return MV_STATUS_SUCCESS on success; otherwise, an appropriate error code.
|
||||
*/
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
_Must_inspect_result_
|
||||
MV_STATUS
|
||||
MmInitializeMemoryManager (
|
||||
_In_ UINT32 PageCount
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Cleans up the memory manager.
|
||||
*/
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
VOID
|
||||
MmCleanupMemoryManager (
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Allocates page-aligned, zero-initialized physical page backed pool.
|
||||
|
||||
@param[in] PageCount - The page count to allocate.
|
||||
|
||||
@return The base of allocated pointer, or NULL on failure. The caller must
|
||||
free the return value with FreeSystemMemory().
|
||||
*/
|
||||
_Post_maybenull_
|
||||
_Post_writable_byte_size_(PageCount * PAGE_SIZE)
|
||||
_Must_inspect_result_
|
||||
VOID*
|
||||
MmAllocatePages (
|
||||
_In_ UINT8 PageCount
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Frees the memory allocated by AllocatePages().
|
||||
|
||||
@param[in] Pages - The pointer to free.
|
||||
*/
|
||||
VOID
|
||||
MmFreePages (
|
||||
_Pre_notnull_ VOID* Pages
|
||||
);
|
||||
383
Sources/MemoryType.c
Normal file
@@ -0,0 +1,383 @@
|
||||
/*!
|
||||
@file MemoryType.c
|
||||
|
||||
@brief Functions for MTRR (memory type range registers) handling.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#include "MemoryType.h"
|
||||
#include "Logger.h"
|
||||
|
||||
//
|
||||
// A physical address range and its memory type.
|
||||
//
|
||||
typedef struct _MEMORY_TYPE_RANGE
|
||||
{
|
||||
BOOLEAN FixedMtrr;
|
||||
IA32_MEMORY_TYPE MemoryType;
|
||||
UINT64 RangeBase; // Inclusive
|
||||
UINT64 RangeEnd; // Exclusive
|
||||
} MEMORY_TYPE_RANGE;
|
||||
|
||||
//
|
||||
// Represents MTRR configurations on this system.
|
||||
//
|
||||
typedef struct _MTRR_CONTEXT
|
||||
{
|
||||
//
|
||||
// The memory type should be used for physical addresses whose memory types
|
||||
// are not specified by MTRR.
|
||||
//
|
||||
IA32_MEMORY_TYPE DefaultMemoryType;
|
||||
|
||||
//
|
||||
// The collection of physical memory address ranges and their memory types
|
||||
// collected from MTRR.
|
||||
//
|
||||
MEMORY_TYPE_RANGE MemoryTypeRanges[IA32_MTRR_COUNT];
|
||||
} MTRR_CONTEXT;
|
||||
|
||||
//
|
||||
// Mapping of physical addresses to memory types.
|
||||
//
|
||||
static MTRR_CONTEXT g_MtrrDatabase;
|
||||
|
||||
|
||||
typedef struct _FIXED_MTRR_RANGE_INFORMATION
|
||||
{
|
||||
//
|
||||
// The fixed-range MTRR MSR.
|
||||
//
|
||||
IA32_MSR_ADDRESS Msr;
|
||||
|
||||
//
|
||||
// The start physical address where the fixed-range MTRR MSR manages.
|
||||
//
|
||||
UINT64 BaseAddress;
|
||||
|
||||
//
|
||||
// The size where a single "range" of the fixed-range MTRR MSR manages.
|
||||
// A single fixed-range MTRR contains 8 ranges, and this field represents
|
||||
// the size a single range manages.
|
||||
// See: 11.11.2.2 Fixed Range MTRRs
|
||||
//
|
||||
UINT64 ManagedSize;
|
||||
} FIXED_MTRR_RANGE_INFORMATION;
|
||||
|
||||
MV_SECTION_INIT
|
||||
VOID
|
||||
InitializeMemoryTypeMapping (
|
||||
)
|
||||
{
|
||||
//
|
||||
// This array defines all fixed-range MTRRs.
|
||||
// See: 11.11.2.2 Fixed Range MTRRs
|
||||
//
|
||||
static CONST FIXED_MTRR_RANGE_INFORMATION rangeInformation[] =
|
||||
{
|
||||
{ IA32_MTRR_FIX64K_00000, 0x0, 0x10000, },
|
||||
{ IA32_MTRR_FIX16K_80000, 0x80000, 0x4000, },
|
||||
{ IA32_MTRR_FIX16K_A0000, 0xA0000, 0x4000, },
|
||||
{ IA32_MTRR_FIX4K_C0000, 0xC0000, 0x1000, },
|
||||
{ IA32_MTRR_FIX4K_C8000, 0xC8000, 0x1000, },
|
||||
{ IA32_MTRR_FIX4K_D0000, 0xD0000, 0x1000, },
|
||||
{ IA32_MTRR_FIX4K_D8000, 0xD8000, 0x1000, },
|
||||
{ IA32_MTRR_FIX4K_E0000, 0xE0000, 0x1000, },
|
||||
{ IA32_MTRR_FIX4K_E8000, 0xE8000, 0x1000, },
|
||||
{ IA32_MTRR_FIX4K_F0000, 0xF0000, 0x1000, },
|
||||
{ IA32_MTRR_FIX4K_F8000, 0xF8000, 0x1000, },
|
||||
};
|
||||
|
||||
UINT32 index;
|
||||
IA32_MTRR_DEF_TYPE_REGISTER defaultType;
|
||||
IA32_MTRR_CAPABILITIES_REGISTER capabilities;
|
||||
MEMORY_TYPE_RANGE rangeType;
|
||||
MTRR_CONTEXT* mtrr;
|
||||
IA32_MEMORY_TYPE memoryType;
|
||||
UINT64 baseForRange;
|
||||
|
||||
mtrr = &g_MtrrDatabase;
|
||||
index = 0;
|
||||
defaultType.Flags = __readmsr(IA32_MTRR_DEF_TYPE);
|
||||
capabilities.Flags = __readmsr(IA32_MTRR_CAPABILITIES);
|
||||
|
||||
RtlZeroMemory(&rangeType, sizeof(rangeType));
|
||||
rangeType.MemoryType = MEMORY_TYPE_INVALID;
|
||||
|
||||
//
|
||||
// We assume MTRRs are enabled. This should always be the case.
|
||||
//
|
||||
MV_ASSERT(defaultType.MtrrEnable != FALSE);
|
||||
|
||||
//
|
||||
// Use the fixed-range MTRRs when supported and enabled.
|
||||
//
|
||||
if ((capabilities.FixedRangeSupported != FALSE) &&
|
||||
(defaultType.FixedRangeMtrrEnable != FALSE))
|
||||
{
|
||||
//
|
||||
// Parse all fixed-range MTRRs.
|
||||
//
|
||||
for (UINT32 i = 0; i < RTL_NUMBER_OF(rangeInformation); ++i)
|
||||
{
|
||||
CONST FIXED_MTRR_RANGE_INFORMATION* range;
|
||||
IA32_MTRR_FIXED_RANGE_MSR fixedRange;
|
||||
|
||||
range = &rangeInformation[i];
|
||||
fixedRange.Flags = __readmsr(range->Msr);
|
||||
|
||||
//
|
||||
// The fixed-range MTRR consists of 8 memory type fields. Each of
|
||||
// them specifies the memory type of the range computed as follows:
|
||||
// start address + (managed size * index of the range).
|
||||
// Go through each of them and save pairs of ranges and memory types.
|
||||
// See: 11.11.2.2 Fixed Range MTRRs
|
||||
// See: Table 11-9. Address Mapping for Fixed-Range MTRRs
|
||||
//
|
||||
for (UINT32 j = 0; j < RTL_NUMBER_OF(fixedRange.u.Types); ++j)
|
||||
{
|
||||
memoryType = fixedRange.u.Types[j];
|
||||
baseForRange = range->BaseAddress + (range->ManagedSize * j);
|
||||
|
||||
//
|
||||
// Combine this entry if it is contiguous from the previous entry
|
||||
// with the same memory type.
|
||||
//
|
||||
if ((rangeType.MemoryType == memoryType) &&
|
||||
(rangeType.RangeEnd == baseForRange))
|
||||
{
|
||||
rangeType.RangeEnd += range->ManagedSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
//
|
||||
// Otherwise, save the previous (possibly combined) entry to
|
||||
// the database if exists. Then, keep the current entry for
|
||||
// combining with any following contiguous entries.
|
||||
//
|
||||
if (rangeType.MemoryType != MEMORY_TYPE_INVALID)
|
||||
{
|
||||
mtrr->MemoryTypeRanges[index] = rangeType;
|
||||
index++;
|
||||
}
|
||||
rangeType.FixedMtrr = TRUE;
|
||||
rangeType.MemoryType = memoryType;
|
||||
rangeType.RangeBase = baseForRange;
|
||||
rangeType.RangeEnd = baseForRange + (range->ManagedSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Go through all variable-range MTRRs.
|
||||
// See: 11.11.2.3 Variable Range MTRRs
|
||||
//
|
||||
for (UINT32 i = 0; i < capabilities.VariableRangeCount; ++i)
|
||||
{
|
||||
IA32_MSR_ADDRESS physMaskMsr, physBaseMsr;
|
||||
IA32_MTRR_PHYSMASK_REGISTER physMaskValue;
|
||||
IA32_MTRR_PHYSBASE_REGISTER physBaseValue;
|
||||
UINT32 length;
|
||||
UINT64 sizeInPages;
|
||||
|
||||
//
|
||||
// The variable-range MTRR is described with a pair of MSRs:
|
||||
// IA32_MTRR_PHYSBASEn indicating the memory type and the starting
|
||||
// address and IA32_MTRR_PHYSMASKn indicating the size.
|
||||
//
|
||||
physMaskMsr = IA32_MTRR_PHYSMASK0 + (i * 2);
|
||||
physMaskValue.Flags = __readmsr(physMaskMsr);
|
||||
|
||||
//
|
||||
// Skip if the IA32_MTRR_PHYSMASKn and IA32_MTRR_PHYSBASEn pair is
|
||||
// disabled.
|
||||
//
|
||||
if (physMaskValue.Valid == FALSE)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
//
|
||||
// Compute the size of the range.
|
||||
//
|
||||
MV_VERIFY(_BitScanForward64((unsigned long*)&length, physMaskValue.PageFrameNumber) != 0);
|
||||
sizeInPages = (1ull << length);
|
||||
|
||||
//
|
||||
// Get the starting address (in pages) and the memory type, then save
|
||||
// them.
|
||||
//
|
||||
physBaseMsr = IA32_MTRR_PHYSBASE0 + (i * 2);
|
||||
physBaseValue.Flags = __readmsr(physBaseMsr);
|
||||
|
||||
memoryType = (IA32_MEMORY_TYPE)physBaseValue.Type;
|
||||
baseForRange = (physBaseValue.PageFrameNumber << PAGE_SHIFT);
|
||||
|
||||
//
|
||||
// The same logic as above. Combine if contiguous, else save the entry
|
||||
// to the database and keep the current entry for combining with subsequent
|
||||
// entries.
|
||||
//
|
||||
if ((rangeType.FixedMtrr == FALSE) &&
|
||||
(rangeType.MemoryType == memoryType) &&
|
||||
(rangeType.RangeEnd == baseForRange))
|
||||
{
|
||||
rangeType.RangeEnd += (sizeInPages << PAGE_SHIFT);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (rangeType.MemoryType != MEMORY_TYPE_INVALID)
|
||||
{
|
||||
mtrr->MemoryTypeRanges[index] = rangeType;
|
||||
index++;
|
||||
}
|
||||
rangeType.FixedMtrr = FALSE;
|
||||
rangeType.MemoryType = memoryType;
|
||||
rangeType.RangeBase = baseForRange;
|
||||
rangeType.RangeEnd = baseForRange + (sizeInPages << PAGE_SHIFT);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Add the last entry to the database.
|
||||
//
|
||||
if (rangeType.MemoryType != MEMORY_TYPE_INVALID)
|
||||
{
|
||||
mtrr->MemoryTypeRanges[index] = rangeType;
|
||||
}
|
||||
|
||||
mtrr->DefaultMemoryType = (IA32_MEMORY_TYPE)defaultType.DefaultMemoryType;
|
||||
|
||||
//
|
||||
// Dump configured ranges.
|
||||
//
|
||||
LOG_DEBUG("Type=%u (Default)", mtrr->DefaultMemoryType);
|
||||
for (UINT32 i = 0; i <= index; ++i)
|
||||
{
|
||||
LOG_DEBUG("Type=%u Fixed=%u %016llx - %016llx",
|
||||
mtrr->MemoryTypeRanges[i].MemoryType,
|
||||
mtrr->MemoryTypeRanges[i].FixedMtrr,
|
||||
mtrr->MemoryTypeRanges[i].RangeBase,
|
||||
mtrr->MemoryTypeRanges[i].RangeEnd);
|
||||
}
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
IA32_MEMORY_TYPE
|
||||
GetMemoryTypeForRange (
|
||||
UINT64 PhysicalAddress,
|
||||
UINT64 RangeSize
|
||||
)
|
||||
{
|
||||
IA32_MEMORY_TYPE memoryType;
|
||||
MTRR_CONTEXT* mtrr;
|
||||
|
||||
mtrr = &g_MtrrDatabase;
|
||||
|
||||
memoryType = MEMORY_TYPE_INVALID;
|
||||
|
||||
for (UINT32 i = 0; i < RTL_NUMBER_OF(mtrr->MemoryTypeRanges); ++i)
|
||||
{
|
||||
CONST MEMORY_TYPE_RANGE* range;
|
||||
|
||||
range = &mtrr->MemoryTypeRanges[i];
|
||||
|
||||
if ((range->RangeBase == 0) &&
|
||||
(range->RangeEnd == 0))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
//
|
||||
// Look for the next range information if the base address is outside
|
||||
// the range this entry manages.
|
||||
//
|
||||
if ((PhysicalAddress < range->RangeBase) ||
|
||||
(PhysicalAddress >= range->RangeEnd))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
//
|
||||
// The first page is managed by the current MTRR entry. Then, all other
|
||||
// pages in the given range must fit in the current MTRR entry; otherwise
|
||||
// the given range has more than one MTRRs either explicitly or
|
||||
// implicitly. Bail out and report error in this case.
|
||||
//
|
||||
if ((PhysicalAddress + RangeSize - 1) >= range->RangeEnd)
|
||||
{
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
//
|
||||
// The fixed-range MTRR takes precedence.
|
||||
//
|
||||
// "If the physical address falls within the first 1 MByte of physical
|
||||
// memory and fixed MTRRs are enabled, the processor uses the memory
|
||||
// type stored for the appropriate fixed-range MTRR."
|
||||
// See: 11.11.4.1 MTRR Precedences
|
||||
//
|
||||
if (range->FixedMtrr != FALSE)
|
||||
{
|
||||
memoryType = range->MemoryType;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
//
|
||||
// The UC memory type takes precedence.
|
||||
//
|
||||
// "If two or more variable memory ranges match and one of the memory
|
||||
// types is UC, the UC memory type used."
|
||||
// See: 11.11.4.1 MTRR Precedences
|
||||
//
|
||||
if (range->MemoryType == MEMORY_TYPE_UNCACHEABLE)
|
||||
{
|
||||
memoryType = range->MemoryType;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
//
|
||||
// The WT memory type takes precedence over the WB memory type.
|
||||
//
|
||||
// "If two or more variable memory ranges match and the memory types are
|
||||
// WT and WB, the WT memory type is used."
|
||||
// See: 11.11.4.1 MTRR Precedences
|
||||
//
|
||||
if (((range->MemoryType == MEMORY_TYPE_WRITE_THROUGH) &&
|
||||
(memoryType == MEMORY_TYPE_WRITE_BACK)) ||
|
||||
((range->MemoryType == MEMORY_TYPE_WRITE_BACK) &&
|
||||
(memoryType == MEMORY_TYPE_WRITE_THROUGH)))
|
||||
{
|
||||
memoryType = MEMORY_TYPE_WRITE_THROUGH;
|
||||
}
|
||||
else
|
||||
{
|
||||
//
|
||||
// Use the last matching MTRR (even with multiple entries overlap).
|
||||
//
|
||||
// "For overlaps not defined by the above rules, processor behavior
|
||||
// is undefined."
|
||||
// See: 11.11.4.1 MTRR Precedences
|
||||
//
|
||||
memoryType = range->MemoryType;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Use the default type if none of MTRRs controls any page in this range.
|
||||
//
|
||||
// "If no fixed or variable memory range matches, the processor uses the
|
||||
// default memory type."
|
||||
// See: 11.11.4.1 MTRR Precedences
|
||||
//
|
||||
if (memoryType == MEMORY_TYPE_INVALID)
|
||||
{
|
||||
memoryType = mtrr->DefaultMemoryType;
|
||||
}
|
||||
|
||||
Exit:
|
||||
return memoryType;
|
||||
}
|
||||
35
Sources/MemoryType.h
Normal file
@@ -0,0 +1,35 @@
|
||||
/*!
|
||||
@file MemoryType.h
|
||||
|
||||
@brief Functions for MTRR (memory type range registers) handling.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#pragma once
|
||||
#include "Common.h"
|
||||
#include "Ia32.h"
|
||||
|
||||
/*!
|
||||
@brief Initializes the MTRR context.
|
||||
*/
|
||||
VOID
|
||||
InitializeMemoryTypeMapping (
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Returns a memory type for the given physical address range.
|
||||
|
||||
@param[in] PhysicalAddress - A physical address to retrieve its memory type.
|
||||
|
||||
@param[in] RangeSize - The size of the range to check.
|
||||
|
||||
@return The memory type for the given physical address. If the range contains
|
||||
more than one memory type, return MEMORY_TYPE_INVALID.
|
||||
*/
|
||||
IA32_MEMORY_TYPE
|
||||
GetMemoryTypeForRange (
|
||||
_In_ UINT64 PhysicalAddress,
|
||||
_In_ UINT64 RangeSize
|
||||
);
|
||||
1381
Sources/MiniVisor.c
Normal file
30
Sources/MiniVisor.h
Normal file
@@ -0,0 +1,30 @@
|
||||
/*!
|
||||
@file MiniVisor.h
|
||||
|
||||
@brief MiniVisor initialization.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#pragma once
|
||||
#include "Common.h"
|
||||
|
||||
/*!
|
||||
@brief Cross platform entry point. Initializes MiniVisor.
|
||||
|
||||
@return MV_STATUS_SUCCESS on success; otherwise, an appropriate error code.
|
||||
*/
|
||||
MV_STATUS
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
_Must_inspect_result_
|
||||
InitializeMiniVisor (
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Cross platform clean up entry callback entry point. Cleans up MiniVisor.
|
||||
*/
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
VOID
|
||||
CleanupMiniVisor (
|
||||
);
|
||||
175
Sources/MiniVisor.vcxproj
Normal file
@@ -0,0 +1,175 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="UEFI|x64">
|
||||
<Configuration>UEFI</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{B94B175C-8D18-47E2-800C-1AFBAAC7AC73}</ProjectGuid>
|
||||
<TemplateGuid>{dd38f7fc-d7bd-488b-9242-7d8754cde80d}</TemplateGuid>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<MinimumVisualStudioVersion>12.0</MinimumVisualStudioVersion>
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform Condition="'$(Platform)' == ''">Win32</Platform>
|
||||
<RootNamespace>MiniVisor</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>$(LatestTargetPlatformVersion)</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Label="Configuration">
|
||||
<TargetVersion>Windows7</TargetVersion>
|
||||
<UseDebugLibraries Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</UseDebugLibraries>
|
||||
<UseDebugLibraries Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</UseDebugLibraries>
|
||||
<PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset>
|
||||
<ConfigurationType>Driver</ConfigurationType>
|
||||
<DriverType>WDM</DriverType>
|
||||
<DriverTargetPlatform>Desktop</DriverTargetPlatform>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Label="Configuration" Condition="'$(Configuration)'=='UEFI'">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>NotSet</CharacterSet>
|
||||
<WholeProgramOptimization>false</WholeProgramOptimization>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
<PropertyGroup Label="UserMacros">
|
||||
<Edk2Dir>$(SolutionDir)..\..\</Edk2Dir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)'!='UEFI'">
|
||||
<DebuggerFlavor>DbgengKernelDebugger</DebuggerFlavor>
|
||||
<IncludePath>$(VC_IncludePath);$(IncludePath)</IncludePath>
|
||||
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
|
||||
<PostBuildEventUseInBuild>false</PostBuildEventUseInBuild>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)'=='UEFI'">
|
||||
<IncludePath>$(SolutionDir)Include;$(Edk2Dir)MdePkg\Include;$(Edk2Dir)MdePkg\Include\X64</IncludePath>
|
||||
<LibraryPath>$(SolutionDir)Libs</LibraryPath>
|
||||
<TargetExt>.efi</TargetExt>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)'=='UEFI'">
|
||||
<TargetName>$(ProjectName)Dxe</TargetName>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<PreprocessorDefinitions>POOL_NX_OPTIN=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)'=='UEFI'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<BufferSecurityCheck>false</BufferSecurityCheck>
|
||||
<BasicRuntimeChecks>Default</BasicRuntimeChecks>
|
||||
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
|
||||
<SupportJustMyCode>false</SupportJustMyCode>
|
||||
<Optimization Condition="'$(Configuration)'=='UEFI'">Disabled</Optimization>
|
||||
<OmitFramePointers Condition="'$(Configuration)'=='UEFI'">false</OmitFramePointers>
|
||||
<WholeProgramOptimization Condition="'$(Configuration)'=='UEFI'">false</WholeProgramOptimization>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>EFI Runtime</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<IgnoreAllDefaultLibraries>true</IgnoreAllDefaultLibraries>
|
||||
<AdditionalDependencies>BaseDebugLibSerialPort.lib;BaseDebugPrintErrorLevelLib.lib;BaseIoLibIntrinsicSev.lib;BaseLib.lib;BaseMemoryLibOptDxe.lib;BasePrintLib.lib;BaseSynchronizationLib.lib;BaseTimerLibNullTemplate.lib;DxePcdLib.lib;UefiBootServicesTableLib.lib;UefiDevicePathLibDevicePathProtocol.lib;UefiDriverEntryPoint.lib;UefiLib.lib;UefiMemoryAllocationLib.lib;UefiRuntimeServicesTableLib.lib;MiniVisorDxe.lib;PcAtSerialPortLib.lib</AdditionalDependencies>
|
||||
<EntryPointSymbol>_ModuleEntryPoint</EntryPointSymbol>
|
||||
<EnableUAC>false</EnableUAC>
|
||||
<RandomizedBaseAddress>false</RandomizedBaseAddress>
|
||||
<DataExecutionPrevention>false</DataExecutionPrevention>
|
||||
</Link>
|
||||
<PreLinkEvent>
|
||||
<Command>python $(SolutionDir)PreLinkEvent.py $(Edk2Dir)Build\MiniVisor\NOOPT_VS2019\X64 $(SolutionDir)Libs</Command>
|
||||
</PreLinkEvent>
|
||||
<PostBuildEvent>
|
||||
<Command Condition="'$(Configuration)'=='UEFI'">copy /y $(OutDir)$(TargetName)$(TargetExt) D:\</Command>
|
||||
</PostBuildEvent>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<FilesToPackage Include="$(TargetPath)" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<MASM Include="Asm.asm" />
|
||||
<MASM Include="Platform\EFI\EfiAsm.asm">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)'!='UEFI'">true</ExcludedFromBuild>
|
||||
</MASM>
|
||||
<MASM Include="Platform\Windows\WinAsm.asm">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)'=='UEFI'">true</ExcludedFromBuild>
|
||||
</MASM>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="Asm.h" />
|
||||
<ClInclude Include="Common.h" />
|
||||
<ClInclude Include="ExtendedPageTables.h" />
|
||||
<ClInclude Include="HostNesting.h" />
|
||||
<ClInclude Include="HostUtils.h" />
|
||||
<ClInclude Include="HostInitialization.h" />
|
||||
<ClInclude Include="HostMain.h" />
|
||||
<ClInclude Include="HostVmcall.h" />
|
||||
<ClInclude Include="Ia32.h" />
|
||||
<ClInclude Include="Logger.h" />
|
||||
<ClInclude Include="MemoryAccess.h" />
|
||||
<ClInclude Include="MemoryManager.h" />
|
||||
<ClInclude Include="MemoryType.h" />
|
||||
<ClInclude Include="MiniVisor.h" />
|
||||
<ClInclude Include="Platform.h" />
|
||||
<ClInclude Include="Platform\EFI\EfiAsm.h" />
|
||||
<ClInclude Include="Platform\EFI\EfiBitmap.h" />
|
||||
<ClInclude Include="Platform\EFI\EfiCommon.h" />
|
||||
<ClInclude Include="Platform\EFI\EfiHostInitialization.h" />
|
||||
<ClInclude Include="Platform\EFI\EfiLogger.h" />
|
||||
<ClInclude Include="Platform\EFI\EfiPlatform.h" />
|
||||
<ClInclude Include="Platform\Windows\WinAsm.h" />
|
||||
<ClInclude Include="Platform\Windows\WinCommon.h" />
|
||||
<ClInclude Include="Platform\Windows\WinHostInitialization.h" />
|
||||
<ClInclude Include="Platform\Windows\WinLogger.h" />
|
||||
<ClInclude Include="Platform\Windows\WinPlatform.h" />
|
||||
<ClInclude Include="Public.h" />
|
||||
<ClInclude Include="Utils.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="ExtendedPageTables.c" />
|
||||
<ClCompile Include="HostUtils.c" />
|
||||
<ClCompile Include="HostMain.c" />
|
||||
<ClCompile Include="HostVmcall.c" />
|
||||
<ClCompile Include="MemoryAccess.c" />
|
||||
<ClCompile Include="MemoryManager.c" />
|
||||
<ClCompile Include="MemoryType.c" />
|
||||
<ClCompile Include="MiniVisor.c" />
|
||||
<ClCompile Include="Platform\EFI\EfiBitmap.c">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)'!='UEFI'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Platform\EFI\EfiHostInitialization.c">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)'!='UEFI'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Platform\EFI\EfiLogger.c">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)'!='UEFI'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Platform\EFI\EfiPlatform.c">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)'!='UEFI'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Platform\Windows\WinHostInitialization.c">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)'=='UEFI'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Platform\Windows\WinLogger.c">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)'=='UEFI'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Platform\Windows\WinPlatform.c">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)'=='UEFI'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Utils.c" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="AsmCommon.inc" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets" />
|
||||
</Project>
|
||||
187
Sources/MiniVisor.vcxproj.filters
Normal file
@@ -0,0 +1,187 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Source Files\Platform">
|
||||
<UniqueIdentifier>{e293d76d-1cb2-498e-865d-1408d410a323}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Source Files\Platform\Windows">
|
||||
<UniqueIdentifier>{89b31625-554c-4a64-adb9-53d69633d23e}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Source Files\Platform\EFI">
|
||||
<UniqueIdentifier>{0666652f-cc2f-4a3b-a0c5-0eb8b57f2ef5}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Header Files\Platform">
|
||||
<UniqueIdentifier>{e9cab091-74b4-4aa0-b8ea-0526186a92ba}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Header Files\Platform\Windows">
|
||||
<UniqueIdentifier>{377c4141-5e61-4508-ad18-231451501b85}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Header Files\Platform\EFI">
|
||||
<UniqueIdentifier>{39c270e1-5eba-4499-b137-e359597058c3}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="Common.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ExtendedPageTables.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="HostInitialization.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="HostMain.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Ia32.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Logger.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="MemoryManager.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="MemoryType.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="MiniVisor.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Platform.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Public.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Utils.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="HostVmcall.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="HostUtils.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Platform\Windows\WinCommon.h">
|
||||
<Filter>Header Files\Platform\Windows</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Platform\EFI\EfiCommon.h">
|
||||
<Filter>Header Files\Platform\EFI</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Platform\EFI\EfiAsm.h">
|
||||
<Filter>Header Files\Platform\EFI</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Platform\Windows\WinAsm.h">
|
||||
<Filter>Header Files\Platform\Windows</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Asm.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Platform\EFI\EfiBitmap.h">
|
||||
<Filter>Header Files\Platform\EFI</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Platform\EFI\EfiLogger.h">
|
||||
<Filter>Header Files\Platform\EFI</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Platform\Windows\WinLogger.h">
|
||||
<Filter>Header Files\Platform\Windows</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Platform\Windows\WinPlatform.h">
|
||||
<Filter>Header Files\Platform\Windows</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Platform\EFI\EfiPlatform.h">
|
||||
<Filter>Header Files\Platform\EFI</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Platform\EFI\EfiHostInitialization.h">
|
||||
<Filter>Header Files\Platform\EFI</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Platform\Windows\WinHostInitialization.h">
|
||||
<Filter>Header Files\Platform\Windows</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="MemoryAccess.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="HostNesting.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="ExtendedPageTables.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="HostMain.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MemoryManager.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MemoryType.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MiniVisor.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Utils.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="HostVmcall.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="HostUtils.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Platform\Windows\WinLogger.c">
|
||||
<Filter>Source Files\Platform\Windows</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Platform\Windows\WinPlatform.c">
|
||||
<Filter>Source Files\Platform\Windows</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Platform\EFI\EfiPlatform.c">
|
||||
<Filter>Source Files\Platform\EFI</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Platform\EFI\EfiLogger.c">
|
||||
<Filter>Source Files\Platform\EFI</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Platform\EFI\EfiBitmap.c">
|
||||
<Filter>Source Files\Platform\EFI</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Platform\EFI\EfiHostInitialization.c">
|
||||
<Filter>Source Files\Platform\EFI</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Platform\Windows\WinHostInitialization.c">
|
||||
<Filter>Source Files\Platform\Windows</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MemoryAccess.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<MASM Include="Asm.asm">
|
||||
<Filter>Source Files</Filter>
|
||||
</MASM>
|
||||
<MASM Include="Platform\EFI\EfiAsm.asm">
|
||||
<Filter>Source Files\Platform\EFI</Filter>
|
||||
</MASM>
|
||||
<MASM Include="Platform\Windows\WinAsm.asm">
|
||||
<Filter>Source Files\Platform\Windows</Filter>
|
||||
</MASM>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="AsmCommon.inc">
|
||||
<Filter>Header Files</Filter>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
247
Sources/Platform.h
Normal file
@@ -0,0 +1,247 @@
|
||||
/*!
|
||||
@file Platform.h
|
||||
|
||||
@brief Platform specific API.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#pragma once
|
||||
#include "Common.h"
|
||||
|
||||
//
|
||||
// Spin lock type and state names.
|
||||
//
|
||||
#if defined(NTDDI_VERSION)
|
||||
typedef volatile LONG64 SPIN_LOCK;
|
||||
typedef enum _SPIN_LOCK_STATE
|
||||
{
|
||||
SpinLockReleased,
|
||||
SpinLockAcquired,
|
||||
} SPIN_LOCK_STATE;
|
||||
#else
|
||||
#include <Library/SynchronizationLib.h>
|
||||
#endif
|
||||
|
||||
/*!
|
||||
@brief Initializes platform specific bits.
|
||||
|
||||
@return MV_STATUS_SUCCESS on success; otherwise, an appropriate error code.
|
||||
*/
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
_Must_inspect_result_
|
||||
MV_STATUS
|
||||
InitializePlatform (
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Cleans up platform specific bits.
|
||||
*/
|
||||
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||
VOID
|
||||
CleanupPlatform (
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Stalls execution of the current processor.
|
||||
|
||||
@details Use of this API from the host is not allowed.
|
||||
|
||||
@param[in] Milliseconds - The time to stall in milliseconds.
|
||||
*/
|
||||
_IRQL_requires_max_(APC_LEVEL)
|
||||
VOID
|
||||
Sleep (
|
||||
_In_ UINT64 Milliseconds
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Returns the active logical processor count.
|
||||
|
||||
@details Use of this API from the host is not allowed.
|
||||
|
||||
@return The active logical processor count.
|
||||
*/
|
||||
UINT32
|
||||
GetActiveProcessorCount (
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Returns the current processor number. The BSP will return 0.
|
||||
|
||||
@details Use of this API from the host is not allowed.
|
||||
|
||||
@return The current processor number. 0 for BSP.
|
||||
*/
|
||||
UINT32
|
||||
GetCurrentProcessorNumber (
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Returns the physical address of the given virtual address.
|
||||
|
||||
@param[in] VirualAddress - A virtual address to retrieve its physical
|
||||
address for the current CR3. This must be non paged pool, otherwise the
|
||||
result is undefined.
|
||||
|
||||
@return The physical address of the given virtual address.
|
||||
*/
|
||||
UINT64
|
||||
GetPhysicalAddress (
|
||||
_In_ VOID* VirualAddress
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Returns the virtual address of the given physical address.
|
||||
|
||||
@param[in] PhysicalAddress - The physical address to retrieve its virtual
|
||||
address for the current CR3.
|
||||
|
||||
@return The virtual address of the given physical address.
|
||||
*/
|
||||
VOID*
|
||||
GetVirtualAddress (
|
||||
_In_ UINT64 PhysicalAddress
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Allocates page-aligned, zero-initialized physical memory resident pages.
|
||||
|
||||
@details Use of this API from the host is not allowed.
|
||||
|
||||
@param[in] PageCount - The page count to allocate.
|
||||
|
||||
@return The base of allocated pointer, or NULL on failure. The caller must
|
||||
free the return value with FreeSystemMemory().
|
||||
*/
|
||||
__drv_allocatesMem(Mem)
|
||||
_IRQL_requires_max_(DISPATCH_LEVEL)
|
||||
_Post_maybenull_
|
||||
_Post_writable_byte_size_(PageCount * PAGE_SIZE)
|
||||
_Must_inspect_result_
|
||||
VOID*
|
||||
AllocateSystemMemory (
|
||||
_In_ UINT64 PageCount
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Frees the memory allocated by AllocateSystemMemory().
|
||||
|
||||
@details Use of this API from the host is not allowed.
|
||||
|
||||
@param[in] Pages - The pointer to free.
|
||||
|
||||
@param[in] PageCount - Unused.
|
||||
*/
|
||||
_IRQL_requires_max_(DISPATCH_LEVEL)
|
||||
VOID
|
||||
FreeSystemMemory (
|
||||
_Pre_notnull_ __drv_freesMem(Mem) VOID* Pages,
|
||||
_In_ UINT64 PageCount
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Reserves the virtual address. The returned address is not accessible.
|
||||
|
||||
@details Use of this API from the host is not allowed.
|
||||
|
||||
@param[in] PageCount - The page count to reserve.
|
||||
|
||||
@return The address of reserved region on success or NULL. The caller must
|
||||
free this value with FreeReservedVirtualAddress().
|
||||
*/
|
||||
_IRQL_requires_max_(APC_LEVEL)
|
||||
_Must_inspect_result_
|
||||
VOID*
|
||||
ReserveVirtualAddress (
|
||||
_In_ UINT64 PageCount
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Frees the address reserved with ReserveVirtualAddress().
|
||||
|
||||
@details Use of this API from the host is not allowed.
|
||||
|
||||
@param[in] Pages - The pointer returned from ReserveVirtualAddress().
|
||||
|
||||
@param[in] PageCount - Unused.
|
||||
*/
|
||||
_IRQL_requires_max_(APC_LEVEL)
|
||||
VOID
|
||||
FreeReservedVirtualAddress (
|
||||
_In_ VOID* Pages,
|
||||
_In_ UINT64 PageCount
|
||||
);
|
||||
|
||||
typedef
|
||||
VOID
|
||||
USER_PASSIVE_CALLBACK (
|
||||
_Inout_ VOID* Context
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Executes the callback at the PASSIVE_LEVEL on each processor one by one.
|
||||
|
||||
@details Use of this API from the host is not allowed.
|
||||
|
||||
@param[in] Callback - The pointer to the function to execute.
|
||||
|
||||
@param[in,out] Context - The pointer to arbitrary context data.
|
||||
*/
|
||||
_IRQL_requires_max_(APC_LEVEL)
|
||||
VOID
|
||||
RunOnAllProcessors (
|
||||
_In_ USER_PASSIVE_CALLBACK* Callback,
|
||||
_Inout_ VOID* Context
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Initializes the spin lock.
|
||||
|
||||
@param[out] SpinLock - The pointer to spin lock to initialize.
|
||||
*/
|
||||
VOID
|
||||
InitializeSystemSpinLock (
|
||||
_Out_ SPIN_LOCK* SpinLock
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Acquires the spin lock.
|
||||
|
||||
@details The custom spin lock is used because NT provided spin lock API is
|
||||
not compatible with Driver Verifier when they are used from hypervisor.
|
||||
|
||||
@param[in,out] SpinLock - The pointer to the spin lock to acquire.
|
||||
|
||||
@return The opaque previous context.
|
||||
*/
|
||||
_Requires_lock_not_held_(*SpinLock)
|
||||
_Acquires_lock_(*SpinLock)
|
||||
_IRQL_requires_max_(HIGH_LEVEL)
|
||||
_IRQL_saves_
|
||||
_IRQL_raises_(DISPATCH_LEVEL)
|
||||
UINT8
|
||||
AcquireSystemSpinLock (
|
||||
_Inout_ SPIN_LOCK* SpinLock
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Release the spin lock and lowers IRQL if necessary.
|
||||
|
||||
@details The custom spin lock is used because NT provided spin lock API is
|
||||
not compatible with Driver Verifier when they are used from hypervisor.
|
||||
|
||||
@param[in,out] SpinLock - The spin lock to release.
|
||||
|
||||
@param[in] PreviousContext - The opaque previous context returned by the
|
||||
AcquireSpinLock function.
|
||||
*/
|
||||
_Requires_lock_held_(*SpinLock)
|
||||
_Releases_lock_(*SpinLock)
|
||||
_IRQL_requires_max_(HIGH_LEVEL)
|
||||
VOID
|
||||
ReleaseSystemSpinLock (
|
||||
_Inout_ SPIN_LOCK* SpinLock,
|
||||
_In_ _IRQL_restores_ UINT8 PreviousContext
|
||||
);
|
||||
105
Sources/Platform/EFI/EfiAsm.asm
Normal file
@@ -0,0 +1,105 @@
|
||||
;
|
||||
; @file EfiAsm.asm
|
||||
;
|
||||
; @brief EFI specific MASM-written functions.
|
||||
;
|
||||
; @author Satoshi Tanda
|
||||
;
|
||||
; @copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
;
|
||||
include AsmCommon.inc
|
||||
.code
|
||||
|
||||
extern HandleHostException : proc
|
||||
|
||||
;
|
||||
; The index to track an interrupt number for generating AsmDefaultExceptionHandlers.
|
||||
;
|
||||
Index = 0
|
||||
|
||||
;
|
||||
; Generates the default exception handler code for the given interrupt/exception
|
||||
; number. The generated code assumes that the interrupt/exception does not push
|
||||
; error code.
|
||||
;
|
||||
; Index is incremented whenever this macro is used.
|
||||
;
|
||||
INTERRUPT_HANDLER macro InterruptNumber
|
||||
push 0 ; Push dummy error code for consistent stack layout.
|
||||
push InterruptNumber
|
||||
jmp AsmCommonExceptionHandler
|
||||
Index = Index + 1
|
||||
endm
|
||||
|
||||
;
|
||||
; Generates the default exception handler code for the given interrupt/exception
|
||||
; number. The generated code assumes that the interrupt/exception pushes error code.
|
||||
;
|
||||
; Index is incremented whenever this macro is used.
|
||||
;
|
||||
INTERRUPT_HANDLER_WITH_CODE macro InterruptNumber
|
||||
nop ; Error code is expected to be pushed by the processor.
|
||||
nop
|
||||
push InterruptNumber
|
||||
jmp AsmCommonExceptionHandler
|
||||
Index = Index + 1
|
||||
endm
|
||||
|
||||
;
|
||||
; @brief The default host exception handlers.
|
||||
;
|
||||
; @details This is the function containing actually 256 stub functions generated
|
||||
; with the INTERRUPT_HANDLER and INTERRUPT_HANDLER_WITH_CODE macros. Each function
|
||||
; works as a hendler of the corresponding interrupt/exception in the host.
|
||||
;
|
||||
AsmDefaultExceptionHandlers proc
|
||||
repeat 8
|
||||
INTERRUPT_HANDLER Index ; INT0-7
|
||||
endm
|
||||
|
||||
INTERRUPT_HANDLER_WITH_CODE Index ; INT8
|
||||
INTERRUPT_HANDLER Index ; INT9
|
||||
|
||||
repeat 5
|
||||
INTERRUPT_HANDLER_WITH_CODE Index ; INT10-14
|
||||
endm
|
||||
|
||||
repeat 2
|
||||
INTERRUPT_HANDLER Index ; INT15-16
|
||||
endm
|
||||
|
||||
INTERRUPT_HANDLER_WITH_CODE Index ; INT17
|
||||
|
||||
repeat 238
|
||||
INTERRUPT_HANDLER Index ; INT18-255
|
||||
endm
|
||||
AsmDefaultExceptionHandlers endp
|
||||
|
||||
;
|
||||
; @brief The common logic for the exception handlers.
|
||||
;
|
||||
; @details This function pushes register values into the stack and calls the
|
||||
; high-level handler written in C.
|
||||
;
|
||||
AsmCommonExceptionHandler proc
|
||||
PUSHAQ
|
||||
mov rcx, rsp
|
||||
sub rsp, 20h
|
||||
call HandleHostException
|
||||
add rsp, 20h
|
||||
POPAQ
|
||||
add rsp, 10h ; Remove the error code and interrupt number.
|
||||
iretq
|
||||
AsmCommonExceptionHandler endp
|
||||
|
||||
;
|
||||
; @brief The NMI handler for the host.
|
||||
;
|
||||
; @details This implementation is incomplete. When NMI occurs while the host is
|
||||
; executed, it should be injected to the guest.
|
||||
;
|
||||
AsmNmiExceptionHandler proc
|
||||
iretq
|
||||
AsmNmiExceptionHandler endp
|
||||
|
||||
end
|
||||
27
Sources/Platform/EFI/EfiAsm.h
Normal file
@@ -0,0 +1,27 @@
|
||||
/*!
|
||||
@file EfiAsm.h
|
||||
|
||||
@brief EFI specific MASM-written functions.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#pragma once
|
||||
#include "EfiCommon.h"
|
||||
|
||||
/*!
|
||||
@brief The array of the default host exception handlers.
|
||||
*/
|
||||
VOID
|
||||
AsmDefaultExceptionHandlers (
|
||||
VOID
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief The host NMI handler.
|
||||
*/
|
||||
VOID
|
||||
AsmNmiExceptionHandler (
|
||||
VOID
|
||||
);
|
||||
98
Sources/Platform/EFI/EfiBitmap.c
Normal file
@@ -0,0 +1,98 @@
|
||||
/*!
|
||||
@file EfiBitmap.c
|
||||
|
||||
@brief EFI specific implementation of bitmap algorithm.
|
||||
|
||||
@details Implementation of algorithm is good enough for the current use of
|
||||
those API but is incomplete and broken, for example, bits are NEVER
|
||||
reused once they are set, even after they are "cleared".
|
||||
|
||||
For complete implementation, one can copy ReactOS's implementation if
|
||||
licensing the project under GPL is acceptable. hvpp by wbenny has its own
|
||||
implementation of bitmap but is actually influenced by ReactOS
|
||||
implementation and such should be treated as GPL.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#include "EfiBitmap.h"
|
||||
|
||||
VOID
|
||||
RtlInitializeBitMap (
|
||||
RTL_BITMAP* BitMapHeader,
|
||||
UINT32* BitMapBuffer,
|
||||
UINT32 SizeOfBitMap
|
||||
)
|
||||
{
|
||||
BitMapHeader->SizeOfBitMap = SizeOfBitMap;
|
||||
BitMapHeader->Buffer = BitMapBuffer;
|
||||
BitMapHeader->NextAvailableBitIndex = 0;
|
||||
BitMapHeader->SetBitCount = 0;
|
||||
}
|
||||
|
||||
UINT32
|
||||
RtlFindClearBitsAndSet (
|
||||
RTL_BITMAP* BitMapHeader,
|
||||
UINT32 NumberToFind,
|
||||
UINT32 HintIndex
|
||||
)
|
||||
{
|
||||
UINT32 clearBitIndex;
|
||||
|
||||
//
|
||||
// Return error if the bitmap does not have enough bits after the current
|
||||
// index. In other words, it never search from the index 0 because implementation
|
||||
// never clears bits.
|
||||
//
|
||||
if (BitMapHeader->NextAvailableBitIndex + NumberToFind > BitMapHeader->SizeOfBitMap)
|
||||
{
|
||||
clearBitIndex = MAXUINT32;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
//
|
||||
// "Find" clear bits, which is just using bits from the current position.
|
||||
//
|
||||
clearBitIndex = BitMapHeader->NextAvailableBitIndex;
|
||||
|
||||
//
|
||||
// "Set" requested bits, which is just moving the index further.
|
||||
//
|
||||
BitMapHeader->SetBitCount += NumberToFind;
|
||||
BitMapHeader->NextAvailableBitIndex += NumberToFind;
|
||||
|
||||
Exit:
|
||||
return clearBitIndex;
|
||||
}
|
||||
|
||||
BOOLEAN
|
||||
RtlAreBitsClear (
|
||||
RTL_BITMAP* BitMapHeader,
|
||||
UINT32 StartingIndex,
|
||||
UINT32 Length
|
||||
)
|
||||
{
|
||||
//
|
||||
// This implementation support checking only whether an entire bitmap is
|
||||
// cleared.
|
||||
//
|
||||
ASSERT(StartingIndex == 0);
|
||||
ASSERT(Length == BitMapHeader->SizeOfBitMap);
|
||||
|
||||
return (BitMapHeader->SetBitCount == 0);
|
||||
}
|
||||
|
||||
VOID
|
||||
RtlClearBits (
|
||||
RTL_BITMAP* BitMapHeader,
|
||||
UINT32 StartingIndex,
|
||||
UINT32 NumberToClear
|
||||
)
|
||||
{
|
||||
//
|
||||
// This implementation only change this counter, and never actually clear
|
||||
// bits and let them to be re-set.
|
||||
//
|
||||
BitMapHeader->SetBitCount -= NumberToClear;
|
||||
}
|
||||
96
Sources/Platform/EFI/EfiBitmap.h
Normal file
@@ -0,0 +1,96 @@
|
||||
/*!
|
||||
@file EfiBitmap.h
|
||||
|
||||
@brief EFI specific implementation of bitmap algorithm.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#pragma once
|
||||
#include "EfiCommon.h"
|
||||
|
||||
typedef struct _RTL_BITMAP
|
||||
{
|
||||
UINT32 SizeOfBitMap; // Number of bits in bit map
|
||||
UINT32* Buffer; // Pointer to the bit map itself
|
||||
UINT32 NextAvailableBitIndex; // Index of the next cleared bit
|
||||
UINT32 SetBitCount; // Number of bits currently set
|
||||
} RTL_BITMAP;
|
||||
|
||||
/*!
|
||||
@brief Initializes the header of a bitmap variable.
|
||||
|
||||
@param[out] BitMapHeader - The pointer to the bitmap variable to initialize.
|
||||
|
||||
@param[in] BitMapBuffer - The pointer to caller-allocated memory for the bitmap
|
||||
itself.
|
||||
|
||||
@param[in] SizeOfBitMap - The number of bits in the bitmap.
|
||||
*/
|
||||
VOID
|
||||
RtlInitializeBitMap (
|
||||
RTL_BITMAP* BitMapHeader,
|
||||
UINT32* BitMapBuffer,
|
||||
UINT32 SizeOfBitMap
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Searches for a range of clear bits of a requested size within a bitmap
|
||||
and sets all bits in the range when it has been located.
|
||||
|
||||
@param[out] BitMapHeader - The pointer to the RTL_BITMAP structure that
|
||||
describes the bitmap.
|
||||
|
||||
@param[in] NumberToFind - How many contiguous clear bits will satisfy this
|
||||
request.
|
||||
|
||||
@param[in] HintIndex - Unused.
|
||||
|
||||
@return The zero-based starting bit index for a clear bit range of the
|
||||
requested size that it set, or it returns 0xFFFFFFFF if it cannot find
|
||||
such a range within the given bitmap variable.
|
||||
*/
|
||||
UINT32
|
||||
RtlFindClearBitsAndSet (
|
||||
RTL_BITMAP* BitMapHeader,
|
||||
UINT32 NumberToFind,
|
||||
UINT32 HintIndex
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Determines whether a given range of bits within a bitmap variable is
|
||||
clear.
|
||||
|
||||
@param[in] BitMapHeader - The pointer to the RTL_BITMAP structure that
|
||||
describes the bitmap.
|
||||
|
||||
@param[in] StartingIndex - The start of the bit range to be tested.
|
||||
|
||||
@param[in] Length - How many bits to test.
|
||||
|
||||
@return Whether a given range of bits within a bitmap variable is clear.
|
||||
*/
|
||||
BOOLEAN
|
||||
RtlAreBitsClear (
|
||||
RTL_BITMAP* BitMapHeader,
|
||||
UINT32 StartingIndex,
|
||||
UINT32 Length
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Sets all bits in the specified range of bits in the bitmap to zero.
|
||||
|
||||
@param[out] BitMapHeader - The pointer to the RTL_BITMAP structure that
|
||||
describes the bitmap.
|
||||
|
||||
@param[in] StartingIndex - Unused.
|
||||
|
||||
@param[in] NumberToClear - How many bits to clear.
|
||||
*/
|
||||
VOID
|
||||
RtlClearBits (
|
||||
RTL_BITMAP* BitMapHeader,
|
||||
UINT32 StartingIndex,
|
||||
UINT32 NumberToClear
|
||||
);
|
||||
135
Sources/Platform/EFI/EfiCommon.h
Normal file
@@ -0,0 +1,135 @@
|
||||
/*!
|
||||
@file EfiCommon.h
|
||||
|
||||
@brief EFI specific implementation of common things across the project.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#pragma once
|
||||
#include <Uefi.h>
|
||||
#include <Library/UefiLib.h>
|
||||
#include <Library/DebugLib.h>
|
||||
#include <Library/BaseMemoryLib.h>
|
||||
|
||||
//
|
||||
// "structure was padded due to alignment specifier"
|
||||
//
|
||||
#pragma warning(disable: 4324)
|
||||
|
||||
|
||||
/*!
|
||||
@brief Freezes execution of the processor by entering infinite busy loop.
|
||||
*/
|
||||
#define MV_PANIC() do { CpuDeadLoop(); } while (TRUE)
|
||||
#define MV_DEBUG_BREAK()
|
||||
#define MV_SECTION_INIT
|
||||
#define MV_SECTION_PAGED
|
||||
#define MV_ASSERT(x) ASSERT(x)
|
||||
#if !defined(MDEPKG_NDEBUG)
|
||||
#define MV_VERIFY(x) ASSERT(x)
|
||||
#else
|
||||
#define MV_VERIFY(x) (x)
|
||||
#endif
|
||||
#define MV_MAX(x, y) MAX((x), (y))
|
||||
#define MV_MIN(x, y) MIN((x), (y))
|
||||
|
||||
//
|
||||
// MSVC compatibility type definitions.
|
||||
//
|
||||
typedef CHAR8 CHAR;
|
||||
typedef CHAR16 WCHAR;
|
||||
|
||||
//
|
||||
// MSVC intrinsics.
|
||||
//
|
||||
unsigned __int64 __readcr0(void);
|
||||
unsigned __int64 __readcr2(void);
|
||||
unsigned __int64 __readcr3(void);
|
||||
unsigned __int64 __readcr4(void);
|
||||
unsigned __int64 __readdr(unsigned int);
|
||||
unsigned __int64 __readeflags(void);
|
||||
unsigned __int64 __readmsr(unsigned long);
|
||||
unsigned char __vmx_on(unsigned __int64 *);
|
||||
unsigned char __vmx_vmclear(unsigned __int64 *);
|
||||
unsigned char __vmx_vmlaunch(void);
|
||||
unsigned char __vmx_vmptrld(unsigned __int64 *);
|
||||
unsigned char __vmx_vmread(unsigned __int64, unsigned __int64 *);
|
||||
unsigned char __vmx_vmresume(void);
|
||||
unsigned char __vmx_vmwrite(unsigned __int64, unsigned __int64);
|
||||
unsigned long __segmentlimit(unsigned long);
|
||||
void __cpuid(int[4], int);
|
||||
void __cpuidex(int[4], int, int);
|
||||
void __invlpg(void *);
|
||||
void __lidt(void *);
|
||||
void __sidt(void *);
|
||||
void __stosq(unsigned __int64 *, unsigned __int64, unsigned __int64);
|
||||
void __vmx_off(void);
|
||||
void __vmx_vmptrst(unsigned __int64 *);
|
||||
void __writecr0(unsigned __int64);
|
||||
void __writecr2(unsigned __int64);
|
||||
void __writecr3(unsigned __int64);
|
||||
void __writecr4(unsigned __int64);
|
||||
void __writedr(unsigned int, unsigned __int64);
|
||||
void __writemsr(unsigned long, unsigned __int64);
|
||||
void _lgdt(void *);
|
||||
void _sgdt(void *);
|
||||
|
||||
//
|
||||
// MSVC compatibility macro definitions.
|
||||
//
|
||||
#define __drv_aliasesMem
|
||||
#define __drv_allocatesMem(x)
|
||||
#define __drv_freesMem(x)
|
||||
#define _Acquires_lock_(x)
|
||||
#define _In_
|
||||
#define _In_opt_
|
||||
#define _In_range_(x, y)
|
||||
#define _In_reads_bytes_(x)
|
||||
#define _Inout_
|
||||
#define _IRQL_raises_(x)
|
||||
#define _IRQL_requires_max_(x)
|
||||
#define _IRQL_restores_
|
||||
#define _IRQL_saves_
|
||||
#define _Must_inspect_result_
|
||||
#define _Out_
|
||||
#define _Out_opt_
|
||||
#define _Out_writes_bytes_(x)
|
||||
#define _Post_maybenull_
|
||||
#define _Post_writable_byte_size_(x)
|
||||
#define _Pre_notnull_
|
||||
#define _Printf_format_string_
|
||||
#define _Releases_lock_(x)
|
||||
#define _Requires_lock_held_(x)
|
||||
#define _Requires_lock_not_held_(x)
|
||||
#define _Return_type_success_(x)
|
||||
#define _Success_(x)
|
||||
#define _Use_decl_annotations_
|
||||
#define _When_(x, y)
|
||||
|
||||
#define ANSI_NULL ((CHAR)0)
|
||||
#define ANYSIZE_ARRAY (1)
|
||||
#define ARGUMENT_PRESENT(x) ((x) != NULL)
|
||||
#define BooleanFlagOn(F,SF) ((BOOLEAN)(((F) & (SF)) != 0))
|
||||
#define BYTES_TO_PAGES(x) EFI_SIZE_TO_PAGES(x)
|
||||
#define C_ASSERT(x) STATIC_ASSERT(x, #x)
|
||||
#define ClearFlag(_F,_SF) ((_F) &= ~(_SF))
|
||||
#define DBG_UNREFERENCED_PARAMETER(x)
|
||||
#define DECLSPEC_ALIGN(x) __declspec(align(x))
|
||||
#define FlagOn(_F,_SF) ((_F) & (_SF))
|
||||
#define KERNEL_STACK_SIZE (0x6000)
|
||||
#define MAXUINT16 MAX_UINT16
|
||||
#define MAXUINT32 MAX_UINT32
|
||||
#define MAXUINT64 MAX_UINT64
|
||||
#define MAXUINT8 MAX_UINT8
|
||||
#define NOTHING
|
||||
#define PAGE_ALIGN(Va) ((VOID*)((UINT64)(Va) & ~(PAGE_SIZE - 1)))
|
||||
#define PAGE_SIZE EFI_PAGE_SIZE
|
||||
#define PAGED_CODE()
|
||||
#define RTL_NUMBER_OF(x) ARRAY_SIZE(x)
|
||||
#define RtlCopyMemory CopyMem
|
||||
#define RtlZeroMemory ZeroMem
|
||||
#define SetFlag(_F,_SF) ((_F) |= (_SF))
|
||||
#define strcmp(x, y) AsciiStrCmp((x), (y))
|
||||
#define UNREFERENCED_PARAMETER(x)
|
||||
330
Sources/Platform/EFI/EfiHostInitialization.c
Normal file
@@ -0,0 +1,330 @@
|
||||
/*!
|
||||
@file EfiHostInitialization.c
|
||||
|
||||
@brief EFI specific implementation of host environment initialization.
|
||||
|
||||
@details On EFI, the host uses its own paging structures (CR3) and interrupt
|
||||
descriptor table (IDT).
|
||||
|
||||
Its own paging structures is preferable and the most straightforward
|
||||
approach to void impact from the physical mode to the virtual mode
|
||||
transition happens during OS startup time. After this transition (ie,
|
||||
SetVirtualAddressMap is called from a boot loader), the paging structures
|
||||
that is used at the physical mode and the host would be using it becomes
|
||||
invalid, as nothing runs on the physical mode anymore. This results in
|
||||
crash (triple fault) when VM-exit occurs. One solution could be to
|
||||
subscribe the SetVirtualAddressMap event and notify the host to switch
|
||||
to the new CR3 for the virtual mode, but this has to be done for all
|
||||
logical processors requiring some inter processor calls. The MP protocol
|
||||
could do the job but is no longer available at the moment of the transition
|
||||
notification because the system is already switched from the boot time to
|
||||
the run time.
|
||||
|
||||
Its own interrupt descriptor table is required for the same reason. After
|
||||
transitioning to the virtual mode, the existing IDT becomes invalid. One
|
||||
might think the host IDT is not relevant as interrupts are disabled. The
|
||||
fact is that NMI still occurs while the host is running, and also, having
|
||||
basic diagnose handlers are useful in case of access violation, for example.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#include "EfiHostInitialization.h"
|
||||
#include "EfiAsm.h"
|
||||
#include "EfiPlatform.h"
|
||||
#include "../../Utils.h"
|
||||
|
||||
//
|
||||
// The format of the IDT.
|
||||
//
|
||||
typedef struct _INTERRUPT_GATE_DESCRIPTOR
|
||||
{
|
||||
UINT16 Offset15To0 : 16;
|
||||
UINT16 SegmentSelector : 16;
|
||||
UINT8 Reserved0;
|
||||
UINT8 GateType;
|
||||
UINT16 Offset31To16;
|
||||
UINT32 Offset63To32;
|
||||
UINT32 Reserved1;
|
||||
} INTERRUPT_GATE_DESCRIPTOR;
|
||||
C_ASSERT(sizeof(INTERRUPT_GATE_DESCRIPTOR) == 16);
|
||||
|
||||
//
|
||||
// Collections of paging structures. Only the single PDPT is accommodated to
|
||||
// handle only up to 512GB of physical memory.
|
||||
//
|
||||
typedef struct _PAGING_STRUCTURES
|
||||
{
|
||||
//
|
||||
// There is only one PML4, unless 5-level page mapping is enabled.
|
||||
//
|
||||
DECLSPEC_ALIGN(PAGE_SIZE) PML4E_64 Pml4[PML4_ENTRY_COUNT];
|
||||
|
||||
//
|
||||
// Only one PDPT is used for PML4[0]. This covers 512GB of the physical memory
|
||||
// range and is sufficient for our purpose.
|
||||
//
|
||||
DECLSPEC_ALIGN(PAGE_SIZE) PDPTE_64 Pdpt[1][PDPT_ENTRY_COUNT];
|
||||
|
||||
//
|
||||
// PDs are assigned for each PDPT entry, meaning that 512 (PDPTEs) multiplied
|
||||
// by the PDT entry count.
|
||||
//
|
||||
DECLSPEC_ALIGN(PAGE_SIZE) PDE_2MB_64 Pdt[1][PDPT_ENTRY_COUNT][PDT_ENTRY_COUNT];
|
||||
} PAGING_STRUCTURES;
|
||||
|
||||
//
|
||||
// Paging related.
|
||||
//
|
||||
static PAGING_STRUCTURES g_HostPagingStructures;
|
||||
static CR3 g_HostCr3;
|
||||
|
||||
//
|
||||
// IDT related.
|
||||
//
|
||||
static INTERRUPT_GATE_DESCRIPTOR g_HostIdt[IDT_ENTRY_COUNT];
|
||||
static IDTR g_HostIdtr;
|
||||
|
||||
/*!
|
||||
@brief Initializes the host paging structures.
|
||||
|
||||
@details This function fills out the statically allocated paging structures
|
||||
and builds the identity mapping. All translation is done with 2MB pages
|
||||
since not 4KB granularity configuration is not needed.
|
||||
|
||||
The identity mapping works because when the host is loaded, the EFI system
|
||||
also uses the identity mapping, meaning that it is essentially making a
|
||||
clone of existing paging structures.
|
||||
|
||||
Note that page protections are all writable and executable. One may drop
|
||||
the executable attribute for outside the range of the .text section of
|
||||
this module and drop writable for the same range to be W^X.
|
||||
*/
|
||||
static
|
||||
VOID
|
||||
InitializeHostPagingStructures (
|
||||
)
|
||||
{
|
||||
PML4E_64* pml4;
|
||||
PDPTE_64* pdpt;
|
||||
PDE_2MB_64* pdt;
|
||||
UINT32 pml4Index;
|
||||
|
||||
pml4Index = 0;
|
||||
pml4 = g_HostPagingStructures.Pml4;
|
||||
pdpt = g_HostPagingStructures.Pdpt[pml4Index];
|
||||
|
||||
//
|
||||
// Fill out PML4, PDPT, PDT.
|
||||
//
|
||||
pml4[0].Present = TRUE;
|
||||
pml4[0].Write = TRUE;
|
||||
pml4[0].PageFrameNumber = GetPhysicalAddress(pdpt) >> PAGE_SHIFT;
|
||||
|
||||
for (UINT32 pdptIndex = 0; pdptIndex < PDPT_ENTRY_COUNT; ++pdptIndex)
|
||||
{
|
||||
pdt = g_HostPagingStructures.Pdt[pml4Index][pdptIndex];
|
||||
|
||||
pdpt[pdptIndex].Present = TRUE;
|
||||
pdpt[pdptIndex].Write = TRUE;
|
||||
pdpt[pdptIndex].PageFrameNumber = GetPhysicalAddress(pdt) >> PAGE_SHIFT;
|
||||
|
||||
for (UINT32 pdIndex = 0; pdIndex < PDT_ENTRY_COUNT; ++pdIndex)
|
||||
{
|
||||
UINT64 physicalAddress;
|
||||
|
||||
physicalAddress = ComputeAddressFromIndexes(pml4Index,
|
||||
pdptIndex,
|
||||
pdIndex,
|
||||
0);
|
||||
pdt[pdIndex].Present = TRUE;
|
||||
pdt[pdIndex].Write = TRUE;
|
||||
pdt[pdIndex].LargePage = TRUE;
|
||||
pdt[pdIndex].PageFrameNumber = physicalAddress >> PAGE_SHIFT_2BM;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Then initialize the CR3 to point to the PML4.
|
||||
//
|
||||
g_HostCr3.AddressOfPageDirectory = GetPhysicalAddress(pml4) >> PAGE_SHIFT;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Initializes the host IDT.
|
||||
|
||||
@details This function fills out the IDT with AsmDefaultExceptionHandlers[N]
|
||||
where N is the interrupt number, updates IDT[2] with AsmNmiExceptionHandler,
|
||||
and initializes IDTR to point to the IDT.
|
||||
|
||||
AsmDefaultExceptionHandlers is the array of stub functions to transfer
|
||||
execution to the main common logic in AsmCommonExceptionHandler.
|
||||
*/
|
||||
static
|
||||
VOID
|
||||
InitializeHostIdt (
|
||||
)
|
||||
{
|
||||
UINT64 handlerBase;
|
||||
|
||||
//
|
||||
// Get the beginning of the AsmDefaultExceptionHandlers to index.
|
||||
//
|
||||
handlerBase = (UINT64)&AsmDefaultExceptionHandlers;
|
||||
|
||||
//
|
||||
// Fill out all IDT entries.
|
||||
//
|
||||
for (UINT32 i = 0; i < IDT_ENTRY_COUNT; ++i)
|
||||
{
|
||||
static const UINT64 sizeOfHandlerTill0x7f = 9;
|
||||
static const UINT64 sizeOfHandlerFrom0x80 = 12;
|
||||
UINT64 sizeOfHandler;
|
||||
UINT64 handlerAddress;
|
||||
|
||||
//
|
||||
// Compute the address of AsmDefaultExceptionHandlers[i]. Each stub
|
||||
// function is 9 bytes up to 0x7f, and 12 bytes after that.
|
||||
//
|
||||
if (i < 0x80)
|
||||
{
|
||||
sizeOfHandler = sizeOfHandlerTill0x7f;
|
||||
}
|
||||
else
|
||||
{
|
||||
sizeOfHandler = sizeOfHandlerFrom0x80;
|
||||
}
|
||||
handlerAddress = (handlerBase + i * sizeOfHandler);
|
||||
|
||||
//
|
||||
// Fill out the IDT entry. The type is 32-bit Interrupt gate: 0x8E
|
||||
// P=1, DPL=00b, S=0, type=1110b => type_attr=1000_1110b=0x8E)
|
||||
//
|
||||
g_HostIdt[i].Offset15To0 = (UINT16)handlerAddress;
|
||||
g_HostIdt[i].Offset31To16 = (UINT16)(handlerAddress >> 16);
|
||||
g_HostIdt[i].Offset63To32 = (UINT32)(handlerAddress >> 32);
|
||||
g_HostIdt[i].SegmentSelector = AsmReadCs();
|
||||
g_HostIdt[i].GateType = 0x8E;
|
||||
}
|
||||
|
||||
//
|
||||
// Interrupt 0x2 is NMI. This needs special handling.
|
||||
//
|
||||
g_HostIdt[2].Offset15To0 = (UINT16)((UINT64)&AsmNmiExceptionHandler);
|
||||
g_HostIdt[2].Offset31To16 = (UINT16)((UINT64)&AsmNmiExceptionHandler >> 16);
|
||||
g_HostIdt[2].Offset63To32 = (UINT32)((UINT64)&AsmNmiExceptionHandler >> 32);
|
||||
|
||||
//
|
||||
// Finally initialize the IDTR to point to the IDT.
|
||||
//
|
||||
g_HostIdtr.Limit = sizeof(g_HostIdt) - 1;
|
||||
g_HostIdtr.BaseAddress = (UINT64)(&g_HostIdt[0]);
|
||||
}
|
||||
|
||||
VOID
|
||||
InitializeHostEnvironment (
|
||||
)
|
||||
{
|
||||
InitializeHostPagingStructures();
|
||||
InitializeHostIdt();
|
||||
}
|
||||
|
||||
CR3
|
||||
GetHostCr3 (
|
||||
)
|
||||
{
|
||||
return g_HostCr3;
|
||||
}
|
||||
|
||||
CONST IDTR*
|
||||
GetHostIdtr (
|
||||
)
|
||||
{
|
||||
return &g_HostIdtr;
|
||||
}
|
||||
|
||||
VOID
|
||||
InitializeGdt (
|
||||
TASK_STATE_SEGMENT_64* NewTss,
|
||||
SEGMENT_DESCRIPTOR_64* NewGdt,
|
||||
UINT64 NewGdtSize,
|
||||
GDTR* OriginalGdtr
|
||||
)
|
||||
{
|
||||
GDTR newGdtr;
|
||||
SEGMENT_SELECTOR taskRegister;
|
||||
SEGMENT_DESCRIPTOR_64 tssDescriptor;
|
||||
SEGMENT_DESCRIPTOR_64* tssDescriptorInGdt;
|
||||
UINT64 tssAddress;
|
||||
SEGMENT_DESCRIPTOR_32* newGdt32;
|
||||
|
||||
//
|
||||
// Get the current GDTR.
|
||||
//
|
||||
_sgdt(&newGdtr);
|
||||
*OriginalGdtr = newGdtr;
|
||||
|
||||
//
|
||||
// Copy contents of the existing GDT to the new GDT in the processor context.
|
||||
//
|
||||
RtlCopyMemory(NewGdt, (VOID*)newGdtr.BaseAddress, newGdtr.Limit);
|
||||
|
||||
//
|
||||
// Set up TR pointing to the entry going to be added below in the GDT. Divide
|
||||
// by the size of SEGMENT_DESCRIPTOR_32 because the limit field is in bytes
|
||||
// while the index is index in the entry count.
|
||||
//
|
||||
taskRegister.Flags = 0;
|
||||
taskRegister.Index = (newGdtr.Limit + 1ull) / sizeof(SEGMENT_DESCRIPTOR_32);
|
||||
|
||||
//
|
||||
// Update the GDTR. Change the base to the new location and increase the
|
||||
// limit to add one more entry for TR. Make sure we have enough space
|
||||
// in the processor context to copy the contents of GDT.
|
||||
//
|
||||
newGdtr.BaseAddress = (UINT64)NewGdt;
|
||||
newGdtr.Limit += sizeof(SEGMENT_DESCRIPTOR_64);
|
||||
MV_ASSERT(newGdtr.Limit < NewGdtSize);
|
||||
|
||||
//
|
||||
// At this point, the TR points to uninitialized entry in the GDT. Set up
|
||||
// the Task State Segment Descriptor to be written to GDT.
|
||||
//
|
||||
tssAddress = (UINT64)NewTss;
|
||||
RtlZeroMemory(&tssDescriptor, sizeof(tssDescriptor));
|
||||
tssDescriptor.SegmentLimitLow = sizeof(*NewTss) - 1;
|
||||
tssDescriptor.BaseAddressLow = (tssAddress & MAXUINT16);
|
||||
tssDescriptor.BaseAddressMiddle = ((tssAddress >> 16) & MAXUINT8);
|
||||
tssDescriptor.BaseAddressHigh = ((tssAddress >> 24) & MAXUINT8);
|
||||
tssDescriptor.BaseAddressUpper = ((tssAddress >> 32) & MAXUINT32);
|
||||
tssDescriptor.Type = SEGMENT_DESCRIPTOR_TYPE_TSS_AVAILABLE;
|
||||
tssDescriptor.Present = TRUE;
|
||||
|
||||
//
|
||||
// Update the GDT by writing entry for TSS, which is pointed by the TR.
|
||||
//
|
||||
newGdt32 = (SEGMENT_DESCRIPTOR_32*)NewGdt;
|
||||
tssDescriptorInGdt = (SEGMENT_DESCRIPTOR_64*)(&newGdt32[taskRegister.Index]);
|
||||
*tssDescriptorInGdt = tssDescriptor;
|
||||
|
||||
//
|
||||
// Finally, update the GDTR and TR of the current processor. The VT-x
|
||||
// requires the guest task segment register to be configured correctly
|
||||
// and the UEFI platform typically does not (ie, TR being zero). Update TR
|
||||
// to point to the task segment just set up.
|
||||
//
|
||||
// See: 26.3.1.2 Checks on Guest Segment Registers
|
||||
//
|
||||
_lgdt(&newGdtr);
|
||||
AsmWriteTr(taskRegister.Flags);
|
||||
}
|
||||
|
||||
VOID
|
||||
CleanupGdt (
|
||||
CONST GDTR* OriginalGdtr
|
||||
)
|
||||
{
|
||||
MV_ASSERT(OriginalGdtr->BaseAddress != 0);
|
||||
_lgdt((VOID*)OriginalGdtr);
|
||||
}
|
||||
11
Sources/Platform/EFI/EfiHostInitialization.h
Normal file
@@ -0,0 +1,11 @@
|
||||
/*!
|
||||
@file EfiHostInitialization.h
|
||||
|
||||
@brief EFI specific implementation of host environment initialization.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#pragma once
|
||||
#include "../../HostInitialization.h"
|
||||
151
Sources/Platform/EFI/EfiLogger.c
Normal file
@@ -0,0 +1,151 @@
|
||||
/*!
|
||||
@file EfiLogger.c
|
||||
|
||||
@brief EFI specific implementation of the logger.
|
||||
|
||||
@details Logging becomes no-op at the runtime when UefiDebugLibConOut is used,
|
||||
ie, -D DEBUG_ON_SERIAL_PORT is not set. See use of mPostEBS in
|
||||
edk2/MdePkg/Library/UefiDebugLibConOut/DebugLib.c for this behavior.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#include "EfiLogger.h"
|
||||
#include <Guid/EventGroup.h>
|
||||
#include <Library/UefiBootServicesTableLib.h>
|
||||
#include <Library/PrintLib.h>
|
||||
|
||||
//
|
||||
// The event handle for ExitBootServices event subscription.
|
||||
//
|
||||
static EFI_EVENT g_EfiExitBootServicesEvent;
|
||||
|
||||
//
|
||||
// FALSE during boot time. Once the system is transition to the run time, any
|
||||
// EFI API that depends on boot services directly or indirectly cannot be called.
|
||||
// The most significant implication is the console output cannot be used anymore.
|
||||
//
|
||||
static BOOLEAN g_AtRuntime;
|
||||
|
||||
/*!
|
||||
@brief Handles the ExitBootServices notification.
|
||||
|
||||
@details The solo purpose of this handler is to report the end of console
|
||||
debug output.
|
||||
|
||||
@param[in] Event - Unused.
|
||||
|
||||
@param[in] Context - Unused.
|
||||
*/
|
||||
static
|
||||
VOID
|
||||
EFIAPI
|
||||
ExitBootServicesHandler (
|
||||
EFI_EVENT Event,
|
||||
VOID* Context
|
||||
)
|
||||
{
|
||||
LOG_INFO("ExitBootServices was called. Ending console logging if used.");
|
||||
gBS->CloseEvent(g_EfiExitBootServicesEvent);
|
||||
g_AtRuntime = TRUE;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Registers ExitBootServices notification.
|
||||
|
||||
@return EFI_SUCCESS on success; otherwise, an appropriate error code.
|
||||
*/
|
||||
static
|
||||
EFI_STATUS
|
||||
RegisterNotification (
|
||||
)
|
||||
{
|
||||
EFI_STATUS status;
|
||||
|
||||
status = gBS->CreateEventEx(EVT_NOTIFY_SIGNAL,
|
||||
TPL_NOTIFY,
|
||||
ExitBootServicesHandler,
|
||||
NULL,
|
||||
&gEfiEventExitBootServicesGuid,
|
||||
&g_EfiExitBootServicesEvent);
|
||||
if (EFI_ERROR(status))
|
||||
{
|
||||
LOG_ERROR("CreateEventEx failed : %r", status);
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
Exit:
|
||||
return status;
|
||||
}
|
||||
|
||||
EFI_STATUS
|
||||
InitializeLogger (
|
||||
)
|
||||
{
|
||||
EFI_STATUS status;
|
||||
|
||||
status = RegisterNotification();
|
||||
if (EFI_ERROR(status))
|
||||
{
|
||||
LOG_ERROR("RegisterNotifications failed : %r", status);
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
Exit:
|
||||
return status;
|
||||
}
|
||||
|
||||
VOID
|
||||
CleanupLogger (
|
||||
)
|
||||
{
|
||||
if (g_AtRuntime == FALSE)
|
||||
{
|
||||
gBS->CloseEvent(g_EfiExitBootServicesEvent);
|
||||
}
|
||||
}
|
||||
|
||||
VOID
|
||||
LogMessage (
|
||||
LOG_LEVEL Level,
|
||||
CONST CHAR* FunctionName,
|
||||
CONST CHAR* Format,
|
||||
...
|
||||
)
|
||||
{
|
||||
//
|
||||
// Mapping from LOG_LEVEL to the EFI log level.
|
||||
//
|
||||
static CONST UINT64 debugLevelMapping[] =
|
||||
{
|
||||
0,
|
||||
DEBUG_ERROR,
|
||||
DEBUG_WARN,
|
||||
DEBUG_INFO,
|
||||
DEBUG_VERBOSE,
|
||||
};
|
||||
C_ASSERT(RTL_NUMBER_OF(debugLevelMapping) == LogLevelReserved);
|
||||
|
||||
VA_LIST args;
|
||||
CHAR8 message[400];
|
||||
|
||||
VA_START(args, Format);
|
||||
(VOID)AsciiVSPrint(message, sizeof(message), Format, args);
|
||||
VA_END(args);
|
||||
|
||||
DebugPrint(debugLevelMapping[Level], "%a: %a\n", FunctionName, message);
|
||||
}
|
||||
|
||||
VOID
|
||||
LogEarlyErrorMessage (
|
||||
CONST CHAR* Format,
|
||||
...
|
||||
)
|
||||
{
|
||||
VA_LIST args;
|
||||
|
||||
VA_START(args, Format);
|
||||
(VOID)DebugVPrint(DEBUG_ERROR, Format, args);
|
||||
VA_END(args);
|
||||
}
|
||||
26
Sources/Platform/EFI/EfiLogger.h
Normal file
@@ -0,0 +1,26 @@
|
||||
/*!
|
||||
@file EfiLogger.h
|
||||
|
||||
@brief EFI specific implementation of the logger.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#include "../../Logger.h"
|
||||
|
||||
/*!
|
||||
@brief Initializes the global logger.
|
||||
|
||||
@return EFI_SUCCESS on success; otherwise, an appropriate error code.
|
||||
*/
|
||||
EFI_STATUS
|
||||
InitializeLogger (
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Clean up the logger.
|
||||
*/
|
||||
VOID
|
||||
CleanupLogger (
|
||||
);
|
||||
398
Sources/Platform/EFI/EfiPlatform.c
Normal file
@@ -0,0 +1,398 @@
|
||||
/*!
|
||||
@file EfiPlatform.c
|
||||
|
||||
@details Some of API in this module can be called from the host. See the
|
||||
description of each API.
|
||||
|
||||
@brief EFI specific platform API.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#include "EfiPlatform.h"
|
||||
#include <Guid/EventGroup.h>
|
||||
#include <Library/DevicePathLib.h>
|
||||
#include <Library/MemoryAllocationLib.h>
|
||||
#include <Library/UefiBootServicesTableLib.h>
|
||||
#include <Library/UefiRuntimeServicesTableLib.h>
|
||||
#include <Pi/PiDxeCis.h>
|
||||
#include <Protocol/MpService.h>
|
||||
#include <Protocol/LoadedImage.h>
|
||||
#include "EfiLogger.h"
|
||||
#include "../../MiniVisor.h"
|
||||
|
||||
//
|
||||
// Maps conversion between MV_STATUS and NTSTATUS.
|
||||
//
|
||||
typedef struct _STATUS_MAPPING
|
||||
{
|
||||
MV_STATUS MvStatus;
|
||||
EFI_STATUS EfiStatus;
|
||||
} STATUS_MAPPING;
|
||||
|
||||
static CONST STATUS_MAPPING k_StatusMapping[] =
|
||||
{
|
||||
{ MV_STATUS_SUCCESS, EFI_SUCCESS, },
|
||||
{ MV_STATUS_UNSUCCESSFUL, EFI_ABORTED, },
|
||||
{ MV_STATUS_ACCESS_DENIED, EFI_ACCESS_DENIED, },
|
||||
{ MV_STATUS_INSUFFICIENT_RESOURCES, EFI_OUT_OF_RESOURCES, },
|
||||
{ MV_STATUS_HV_OPERATION_FAILED, EFI_UNSUPPORTED, },
|
||||
};
|
||||
|
||||
//
|
||||
// The multi-processor protocol. Only available during the boot-time.
|
||||
//
|
||||
static EFI_MP_SERVICES_PROTOCOL* g_MpServices;
|
||||
|
||||
/*!
|
||||
@brief Converts MV_STATUS to EFI_STATUS.
|
||||
|
||||
@param[in] Status - The MV_STATUS to convert from.
|
||||
|
||||
@return The converted EFI_STATUS.
|
||||
*/
|
||||
static
|
||||
EFI_STATUS
|
||||
ConvertMvToEfiStatus (
|
||||
MV_STATUS Status
|
||||
)
|
||||
{
|
||||
for (UINT32 i = 0; i < RTL_NUMBER_OF(k_StatusMapping); ++i)
|
||||
{
|
||||
if (Status == k_StatusMapping[i].MvStatus)
|
||||
{
|
||||
return k_StatusMapping[i].EfiStatus;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Update the mapping when this assert hits.
|
||||
//
|
||||
MV_ASSERT(FALSE);
|
||||
return EFI_ABORTED;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Converts EFI_STATUS to MV_STATUS.
|
||||
|
||||
@param[in] Status - The EFI_STATUS to convert from.
|
||||
|
||||
@return The converted MV_STATUS.
|
||||
*/
|
||||
static
|
||||
MV_STATUS
|
||||
ConvertEfiToMvStatus (
|
||||
EFI_STATUS Status
|
||||
)
|
||||
{
|
||||
for (UINT32 i = 0; i < RTL_NUMBER_OF(k_StatusMapping); ++i)
|
||||
{
|
||||
if (Status == k_StatusMapping[i].EfiStatus)
|
||||
{
|
||||
return k_StatusMapping[i].MvStatus;
|
||||
}
|
||||
}
|
||||
return MV_STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Displays information about the current module.
|
||||
|
||||
@details Use of this API at the run-time is not allowed.
|
||||
|
||||
@return EFI_SUCCESS on success; otherwise, an appropriate error code.
|
||||
*/
|
||||
static
|
||||
EFI_STATUS
|
||||
PrintLoadedImageInformation (
|
||||
)
|
||||
{
|
||||
EFI_STATUS status;
|
||||
EFI_LOADED_IMAGE_PROTOCOL* loadedImageInfo;
|
||||
CHAR16* devicePath;
|
||||
|
||||
devicePath = NULL;
|
||||
|
||||
status = gBS->OpenProtocol(gImageHandle,
|
||||
&gEfiLoadedImageProtocolGuid,
|
||||
(VOID**)&loadedImageInfo,
|
||||
gImageHandle,
|
||||
NULL,
|
||||
EFI_OPEN_PROTOCOL_GET_PROTOCOL);
|
||||
if (EFI_ERROR(status))
|
||||
{
|
||||
LOG_ERROR("OpenProtocol failed : %r", status);
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
devicePath = ConvertDevicePathToText(loadedImageInfo->FilePath, TRUE, TRUE);
|
||||
if (devicePath == NULL)
|
||||
{
|
||||
LOG_ERROR("ConvertDevicePathToText failed");
|
||||
status = EFI_OUT_OF_RESOURCES;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
LOG_INFO("%s - %llx:%llx",
|
||||
devicePath,
|
||||
loadedImageInfo->ImageBase,
|
||||
MV_ADD2PTR(loadedImageInfo->ImageBase, loadedImageInfo->ImageSize));
|
||||
|
||||
Exit:
|
||||
if (devicePath != NULL)
|
||||
{
|
||||
FreePool(devicePath);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief The platform specific module entry point.
|
||||
|
||||
@param[in] ImageHandle - The handle of this module.
|
||||
|
||||
@param[in] SystemTable - The boot service table pointer.
|
||||
|
||||
@return EFI_SUCCESS on success; otherwise, an appropriate error code.
|
||||
*/
|
||||
EFI_STATUS
|
||||
EFIAPI
|
||||
DriverEntry (
|
||||
EFI_HANDLE ImageHandle,
|
||||
EFI_SYSTEM_TABLE* SystemTable
|
||||
)
|
||||
{
|
||||
ASSERT(ImageHandle == gImageHandle);
|
||||
ASSERT(SystemTable->BootServices == gBS);
|
||||
|
||||
return ConvertMvToEfiStatus(InitializeMiniVisor());
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief The platform specific module unload callback.
|
||||
|
||||
@param[in] ImageHandle - The handle of this module.
|
||||
|
||||
@return Always EFI_SUCCESS.
|
||||
*/
|
||||
EFI_STATUS
|
||||
EFIAPI
|
||||
DriverUnload (
|
||||
EFI_HANDLE ImageHandle
|
||||
)
|
||||
{
|
||||
CleanupMiniVisor();
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
|
||||
MV_STATUS
|
||||
InitializePlatform (
|
||||
)
|
||||
{
|
||||
EFI_STATUS status;
|
||||
BOOLEAN isLoggerInitialized;
|
||||
|
||||
status = InitializeLogger();
|
||||
if (EFI_ERROR(status))
|
||||
{
|
||||
LOG_EARLY_ERROR("InitializeLogger failed : %r", status);
|
||||
goto Exit;
|
||||
}
|
||||
isLoggerInitialized = TRUE;
|
||||
|
||||
PrintLoadedImageInformation();
|
||||
|
||||
//
|
||||
// Locate the protocol for multi-processor handling. UEFI on a Hyper-V VM
|
||||
// does not implement this and fails.
|
||||
//
|
||||
status = gBS->LocateProtocol(&gEfiMpServiceProtocolGuid,
|
||||
NULL,
|
||||
&g_MpServices);
|
||||
if (EFI_ERROR(status))
|
||||
{
|
||||
LOG_ERROR("LocateProtocol failed : %r", status);
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
Exit:
|
||||
if (EFI_ERROR(status))
|
||||
{
|
||||
if (isLoggerInitialized != FALSE)
|
||||
{
|
||||
CleanupLogger();
|
||||
}
|
||||
}
|
||||
return ConvertEfiToMvStatus(status);
|
||||
}
|
||||
|
||||
VOID
|
||||
CleanupPlatform (
|
||||
)
|
||||
{
|
||||
CleanupLogger();
|
||||
}
|
||||
|
||||
UINT32
|
||||
GetActiveProcessorCount (
|
||||
)
|
||||
{
|
||||
EFI_STATUS status;
|
||||
UINTN numberOfProcessors;
|
||||
UINTN numberOfEnabledProcessors;
|
||||
|
||||
status = g_MpServices->GetNumberOfProcessors(g_MpServices,
|
||||
&numberOfProcessors,
|
||||
&numberOfEnabledProcessors);
|
||||
if (EFI_ERROR(status))
|
||||
{
|
||||
MV_PANIC();
|
||||
}
|
||||
|
||||
return (UINT32)numberOfEnabledProcessors;
|
||||
}
|
||||
|
||||
UINT32
|
||||
GetCurrentProcessorNumber (
|
||||
)
|
||||
{
|
||||
EFI_STATUS status;
|
||||
UINTN processorNumber;
|
||||
|
||||
status = g_MpServices->WhoAmI(g_MpServices, &processorNumber);
|
||||
if (EFI_ERROR(status))
|
||||
{
|
||||
MV_PANIC();
|
||||
}
|
||||
|
||||
return (UINT32)processorNumber;
|
||||
}
|
||||
|
||||
UINT64
|
||||
GetPhysicalAddress (
|
||||
VOID* VirualAddress
|
||||
)
|
||||
{
|
||||
//
|
||||
// Assume the current CR3 uses the identity mapping. This is the case during
|
||||
// the boot time or execution of the host.
|
||||
//
|
||||
return (UINT64)VirualAddress;
|
||||
}
|
||||
|
||||
VOID*
|
||||
GetVirtualAddress (
|
||||
UINT64 PhysicalAddress
|
||||
)
|
||||
{
|
||||
//
|
||||
// This function assume the current CR3 uses the identity mapping. This is
|
||||
// the case during the boot time or execution of the host.
|
||||
//
|
||||
return (VOID*)PhysicalAddress;
|
||||
}
|
||||
|
||||
VOID*
|
||||
AllocateSystemMemory (
|
||||
UINT64 PageCount
|
||||
)
|
||||
{
|
||||
VOID* pages;
|
||||
|
||||
pages = AllocateRuntimePages(PageCount);
|
||||
if (pages == NULL)
|
||||
{
|
||||
goto Exit;
|
||||
}
|
||||
ZeroMem(pages, PageCount * EFI_PAGE_SIZE);
|
||||
|
||||
Exit:
|
||||
return pages;
|
||||
}
|
||||
|
||||
VOID
|
||||
FreeSystemMemory (
|
||||
VOID* Pages,
|
||||
UINT64 PageCount
|
||||
)
|
||||
{
|
||||
FreePages(Pages, PageCount);
|
||||
}
|
||||
|
||||
VOID*
|
||||
ReserveVirtualAddress (
|
||||
UINT64 PageCount
|
||||
)
|
||||
{
|
||||
return AllocateSystemMemory(PageCount);
|
||||
}
|
||||
|
||||
VOID
|
||||
FreeReservedVirtualAddress (
|
||||
VOID* BaseAddress,
|
||||
UINT64 PageCount
|
||||
)
|
||||
{
|
||||
FreeSystemMemory(BaseAddress, PageCount);
|
||||
}
|
||||
|
||||
VOID
|
||||
RunOnAllProcessors (
|
||||
USER_PASSIVE_CALLBACK* Callback,
|
||||
VOID* Context
|
||||
)
|
||||
{
|
||||
EFI_STATUS status;
|
||||
|
||||
Callback(Context);
|
||||
if (GetActiveProcessorCount() == 1)
|
||||
{
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
status = g_MpServices->StartupAllAPs(g_MpServices,
|
||||
Callback,
|
||||
TRUE,
|
||||
NULL,
|
||||
0,
|
||||
Context,
|
||||
NULL);
|
||||
if (EFI_ERROR(status))
|
||||
{
|
||||
MV_PANIC();
|
||||
}
|
||||
|
||||
Exit:
|
||||
return;
|
||||
}
|
||||
|
||||
VOID
|
||||
InitializeSystemSpinLock (
|
||||
SPIN_LOCK* SpinLock
|
||||
)
|
||||
{
|
||||
(VOID)InitializeSpinLock(SpinLock);
|
||||
}
|
||||
|
||||
UINT8
|
||||
AcquireSystemSpinLock (
|
||||
SPIN_LOCK* SpinLock
|
||||
)
|
||||
{
|
||||
//
|
||||
// This function does not raise TPL as it is not available at the run time.
|
||||
//
|
||||
(VOID)AcquireSpinLock(SpinLock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
VOID
|
||||
ReleaseSystemSpinLock (
|
||||
SPIN_LOCK* SpinLock,
|
||||
UINT8 PreviousContext
|
||||
)
|
||||
{
|
||||
ASSERT(PreviousContext == 0);
|
||||
(VOID)ReleaseSpinLock(SpinLock);
|
||||
}
|
||||
11
Sources/Platform/EFI/EfiPlatform.h
Normal file
@@ -0,0 +1,11 @@
|
||||
/*!
|
||||
@file EfiPlatform.h
|
||||
|
||||
@brief EFI specific platform API.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#pragma once
|
||||
#include "../../Platform.h"
|
||||
102
Sources/Platform/Windows/WinAsm.asm
Normal file
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,11 @@
|
||||
/*!
|
||||
@file WinPlatform.h
|
||||
|
||||
@brief Windows specific platform API.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#pragma once
|
||||
#include "../../Platform.h"
|
||||
68
Sources/Public.h
Normal file
@@ -0,0 +1,68 @@
|
||||
/*!
|
||||
@file Public.h
|
||||
|
||||
@brief Interfaces to communicate with our hypervisor.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#pragma once
|
||||
#include "Common.h"
|
||||
|
||||
//
|
||||
// The VMCALL numbers our hypervisor provides.
|
||||
//
|
||||
typedef enum _HYPERVISOR_VMCALL_NUMBER
|
||||
{
|
||||
//
|
||||
// Uninstall the hypervisor.
|
||||
//
|
||||
VmcallUninstall,
|
||||
|
||||
//
|
||||
// The maximum valid VMCALL number (exclusive).
|
||||
//
|
||||
VmcallInvalid,
|
||||
} HYPERVISOR_VMCALL_NUMBER;
|
||||
|
||||
//
|
||||
// The arbitrary collection of data passed to our hypervisor from kernel-mode
|
||||
// code through stack of the hypervisor.
|
||||
//
|
||||
// The structure must be 16-byte aligned so that hypervisor's stack pointer is
|
||||
// always 16-byte aligned and SSE instructions can be used to save XMM registers.
|
||||
// See Asm.asm for relevant code.
|
||||
//
|
||||
typedef struct _HYPERVISOR_CONTEXT
|
||||
{
|
||||
//
|
||||
// The processor number associated with this context. 0 for BSP.
|
||||
//
|
||||
UINT32 ProcessorNumber;
|
||||
UINT32 Padding1;
|
||||
UINT64 Padding2;
|
||||
|
||||
//
|
||||
// A pointer to the all-processors context. This value is not used by the
|
||||
// hypervisor, and the hypervisor doe not know its layout. It is stored here
|
||||
// so that it can be returned and freed when hypervisor is being disabled.
|
||||
//
|
||||
struct _SHARED_PROCESSOR_CONTEXT* VpContexts;
|
||||
|
||||
//
|
||||
// A pointer to the EPT context. Needed to handle EPT violation VM-exit.
|
||||
//
|
||||
struct _EPT_CONTEXT* EptContext;
|
||||
|
||||
//
|
||||
// A pointer to the memory access context. Used to access guest's memory.
|
||||
//
|
||||
struct _MEMORY_ACCESS_CONTEXT* MemoryAccessContext;
|
||||
|
||||
//
|
||||
// The state of the nested hypervisor if any.
|
||||
//
|
||||
struct _NEXTED_VMX_CONTEXT* NestedVmxContext;
|
||||
} HYPERVISOR_CONTEXT;
|
||||
C_ASSERT((sizeof(HYPERVISOR_CONTEXT) % 0x10) == 0);
|
||||
222
Sources/Utils.c
Normal file
@@ -0,0 +1,222 @@
|
||||
/*!
|
||||
@file Utils.c
|
||||
|
||||
@brief Utility functions that could be used by both on root and non-root
|
||||
operations.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#include "Utils.h"
|
||||
#include "Asm.h"
|
||||
|
||||
_Use_decl_annotations_
|
||||
UINT64
|
||||
ComputeAddressFromIndexes (
|
||||
UINT32 Pml4Index,
|
||||
UINT32 PdptIndex,
|
||||
UINT32 PdIndex,
|
||||
UINT32 PtIndex
|
||||
)
|
||||
{
|
||||
ADDRESS_TRANSLATION_HELPER helper;
|
||||
|
||||
helper.AsUInt64 = 0;
|
||||
helper.AsIndex.Pml4 = Pml4Index;
|
||||
helper.AsIndex.Pdpt = PdptIndex;
|
||||
helper.AsIndex.Pd = PdIndex;
|
||||
helper.AsIndex.Pt = PtIndex;
|
||||
return helper.AsUInt64;
|
||||
}
|
||||
|
||||
UINT32
|
||||
GetSegmentAccessRight (
|
||||
_In_ UINT16 SegmentSelector
|
||||
)
|
||||
{
|
||||
SEGMENT_SELECTOR segmentSelector;
|
||||
UINT32 nativeAccessRight;
|
||||
VMX_SEGMENT_ACCESS_RIGHTS accessRight;
|
||||
|
||||
segmentSelector.Flags = SegmentSelector;
|
||||
|
||||
//
|
||||
// "In general, a segment register is unusable if it has been loaded with a
|
||||
// null selector."
|
||||
// See: 24.4.1 Guest Register State
|
||||
//
|
||||
if ((segmentSelector.Table == 0) &&
|
||||
(segmentSelector.Index == 0))
|
||||
{
|
||||
accessRight.Flags = 0;
|
||||
accessRight.Unusable = TRUE;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
//
|
||||
// Convert the native access right to the format for VMX. Those two formats
|
||||
// are almost identical except that first 8 bits of the native format does
|
||||
// not exist in the VMX format, and that few fields are undefined in the
|
||||
// native format but reserved to be zero in the VMX format.
|
||||
//
|
||||
nativeAccessRight = AsmLoadAccessRightsByte(SegmentSelector);
|
||||
MV_ASSERT(nativeAccessRight);
|
||||
accessRight.Flags = (nativeAccessRight >> 8);
|
||||
accessRight.Reserved1 = 0;
|
||||
accessRight.Reserved2 = 0;
|
||||
accessRight.Unusable = FALSE;
|
||||
|
||||
Exit:
|
||||
return accessRight.Flags;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Returns the segment descriptor corresponds to the SegmentSelector.
|
||||
|
||||
@param[in] DescriptorTableBase - An address of the base of the descriptor
|
||||
table.
|
||||
|
||||
@param[in] SegmentSelector - A segment selector value.
|
||||
|
||||
@return The segment descriptor corresponds to the SegmentSelector.
|
||||
*/
|
||||
static
|
||||
SEGMENT_DESCRIPTOR_32*
|
||||
GetSegmentDescriptor (
|
||||
_In_ UINT64 DescriptorTableBase,
|
||||
_In_ UINT16 SegmentSelector
|
||||
)
|
||||
{
|
||||
SEGMENT_SELECTOR segmentSelector;
|
||||
SEGMENT_DESCRIPTOR_32* segmentDescriptors;
|
||||
|
||||
//
|
||||
// "Selects one of 8192 descriptors in the GDT or LDT. The processor multiplies
|
||||
// the index value by 8 (the number of bytes in a segment descriptor) and
|
||||
// adds the result to the base address of the GDT or LDT (from the GDTR or
|
||||
// LDTR register, respectively)."
|
||||
// See: 3.4.2 Segment Selectors
|
||||
//
|
||||
segmentSelector.Flags = SegmentSelector;
|
||||
segmentDescriptors = (SEGMENT_DESCRIPTOR_32*)DescriptorTableBase;
|
||||
return &segmentDescriptors[segmentSelector.Index];
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Returns the base address of SegmentDescriptor.
|
||||
|
||||
@param[in] SegmentDescriptor - The segment descriptor from which retrieve
|
||||
the base address.
|
||||
|
||||
@return The base address of SegmentDescriptor.
|
||||
*/
|
||||
static
|
||||
UINT64
|
||||
GetSegmentBaseByDescriptor (
|
||||
_In_ CONST SEGMENT_DESCRIPTOR_32* SegmentDescriptor
|
||||
)
|
||||
{
|
||||
UINT64 segmentBase;
|
||||
UINT64 baseHigh, baseMiddle, baseLow;
|
||||
|
||||
baseHigh = ((UINT64)SegmentDescriptor->BaseAddressHigh) << (6 * 4);
|
||||
baseMiddle = ((UINT64)SegmentDescriptor->BaseAddressMiddle) << (4 * 4);
|
||||
baseLow = SegmentDescriptor->BaseAddressLow;
|
||||
segmentBase = (baseHigh | baseMiddle | baseLow) & MAXUINT32;
|
||||
|
||||
//
|
||||
// Few system descriptors are expanded to 16 bytes on x64. For practical
|
||||
// reasons, we only detect TSS descriptors (that is the System field is
|
||||
// cleared, and the Type field has either one of specific values).
|
||||
//
|
||||
// See: 3.5.2 Segment Descriptor Tables in IA-32e Mode
|
||||
//
|
||||
if ((SegmentDescriptor->System == 0) &&
|
||||
((SegmentDescriptor->Type == SEGMENT_DESCRIPTOR_TYPE_TSS_AVAILABLE) ||
|
||||
(SegmentDescriptor->Type == SEGMENT_DESCRIPTOR_TYPE_TSS_BUSY)))
|
||||
{
|
||||
CONST SEGMENT_DESCRIPTOR_64* descriptor64;
|
||||
|
||||
descriptor64 = (CONST SEGMENT_DESCRIPTOR_64*)SegmentDescriptor;
|
||||
segmentBase |= ((UINT64)descriptor64->BaseAddressUpper << 32);
|
||||
}
|
||||
return segmentBase;
|
||||
}
|
||||
|
||||
UINT64
|
||||
GetSegmentBase (
|
||||
_In_ UINT64 DescriptorTableBase,
|
||||
_In_ UINT16 SegmentSelector
|
||||
)
|
||||
{
|
||||
UINT64 segmentBase;
|
||||
SEGMENT_SELECTOR segmentSelector;
|
||||
|
||||
segmentSelector.Flags = SegmentSelector;
|
||||
|
||||
if ((segmentSelector.Table == 0) &&
|
||||
(segmentSelector.Index == 0))
|
||||
{
|
||||
//
|
||||
// The null segment selectors technically does not point to a valid
|
||||
// segment descriptor, hence no valid base address either. We return
|
||||
// 0 for convenience, however.
|
||||
//
|
||||
// "The first entry of the GDT is not used by the processor. A segment
|
||||
// selector that points to this entry of the GDT (that is, a segment
|
||||
// selector with an index of 0 and the TI flag set to 0) is used as a
|
||||
// "null segment selector."".
|
||||
// 3.4.2 Segment Selectors
|
||||
//
|
||||
segmentBase = 0;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
//
|
||||
// For practical reasons, we do not support LDT. This will not be an issue
|
||||
// as we are running as a SYSTEM which will not use LDT.
|
||||
//
|
||||
// "Specifies the descriptor table to use: clearing this flag selects the GDT;
|
||||
// setting this flag selects the current LDT."
|
||||
// See: 3.4.2 Segment Selectors
|
||||
//
|
||||
MV_ASSERT(segmentSelector.Table == 0);
|
||||
segmentBase = GetSegmentBaseByDescriptor(GetSegmentDescriptor(DescriptorTableBase,
|
||||
SegmentSelector));
|
||||
|
||||
Exit:
|
||||
return segmentBase;
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
CR0
|
||||
AdjustCr0 (
|
||||
CR0 Cr0
|
||||
)
|
||||
{
|
||||
CR0 newCr0, fixed0Cr0, fixed1Cr0;
|
||||
|
||||
newCr0 = Cr0;
|
||||
fixed0Cr0.Flags = __readmsr(IA32_VMX_CR0_FIXED0);
|
||||
fixed1Cr0.Flags = __readmsr(IA32_VMX_CR0_FIXED1);
|
||||
newCr0.Flags &= fixed1Cr0.Flags;
|
||||
newCr0.Flags |= fixed0Cr0.Flags;
|
||||
return newCr0;
|
||||
}
|
||||
|
||||
_Use_decl_annotations_
|
||||
CR4
|
||||
AdjustCr4 (
|
||||
CR4 Cr4
|
||||
)
|
||||
{
|
||||
CR4 newCr4, fixed0Cr4, fixed1Cr4;
|
||||
|
||||
newCr4 = Cr4;
|
||||
fixed0Cr4.Flags = __readmsr(IA32_VMX_CR4_FIXED0);
|
||||
fixed1Cr4.Flags = __readmsr(IA32_VMX_CR4_FIXED1);
|
||||
newCr4.Flags &= fixed1Cr4.Flags;
|
||||
newCr4.Flags |= fixed0Cr4.Flags;
|
||||
return newCr4;
|
||||
}
|
||||
87
Sources/Utils.h
Normal file
@@ -0,0 +1,87 @@
|
||||
/*!
|
||||
@file Utils.h
|
||||
|
||||
@brief Utility functions that could be used by both on root and non-root
|
||||
operations.
|
||||
|
||||
@author Satoshi Tanda
|
||||
|
||||
@copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved.
|
||||
*/
|
||||
#pragma once
|
||||
#include "Common.h"
|
||||
|
||||
/*!
|
||||
@brief Computes the address from the four page table indexes.
|
||||
|
||||
@param[in] Pml4Index - The index for PML4.
|
||||
|
||||
@param[in] PdptIndex - The index for PDPT.
|
||||
|
||||
@param[in] PdIndex - The index for PE.
|
||||
|
||||
@param[in] PtIndex - The index for PE.
|
||||
|
||||
@return The resulted address.
|
||||
*/
|
||||
UINT64
|
||||
ComputeAddressFromIndexes (
|
||||
_In_ UINT32 Pml4Index,
|
||||
_In_ UINT32 PdptIndex,
|
||||
_In_ UINT32 PdIndex,
|
||||
_In_ UINT32 PtIndex
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Returns the access right of the segment specified by the SegmentSelector
|
||||
for VMX.
|
||||
|
||||
@param[in] SegmentSelector - A segment selector value.
|
||||
|
||||
@return The access right of the segment for VMX.
|
||||
*/
|
||||
UINT32
|
||||
GetSegmentAccessRight (
|
||||
_In_ UINT16 SegmentSelector
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Returns the base address of the segment specified by SegmentSelector.
|
||||
|
||||
@param[in] DescriptorTableBase - An address of the base of the descriptor
|
||||
table.
|
||||
|
||||
@param[in] SegmentSelector - The segment selector which points to the
|
||||
segment descriptor to retrieve the base address from.
|
||||
|
||||
@return The base address of the segment specified by SegmentSelector.
|
||||
*/
|
||||
UINT64
|
||||
GetSegmentBase (
|
||||
_In_ UINT64 DescriptorTableBase,
|
||||
_In_ UINT16 SegmentSelector
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Returns the CR0 value after the FIXED0 and FIXED1 MSR values are applied.
|
||||
|
||||
@param[in] Cr0 - The CR0 value to apply the FIXED0 and FIXED1 MSR values.
|
||||
|
||||
@return The CR0 value where the FIXED0 and FIXED1 MSR values are applied.
|
||||
*/
|
||||
CR0
|
||||
AdjustCr0 (
|
||||
_In_ CR0 Cr0
|
||||
);
|
||||
|
||||
/*!
|
||||
@brief Returns the CR4 value after the FIXED0 and FIXED1 MSR values are applied.
|
||||
|
||||
@param[in] Cr4 - The CR4 value to apply the FIXED0 and FIXED1 MSR values.
|
||||
|
||||
@return The CR4 value where the FIXED0 and FIXED1 MSR values are applied.
|
||||
*/
|
||||
CR4
|
||||
AdjustCr4 (
|
||||
_In_ CR4 Cr4
|
||||
);
|
||||
21433
Sources/ia32-doc/out/ia32.h
Normal file
31
Tests/CheckHvVendor/CheckHvVendor.sln
Normal file
@@ -0,0 +1,31 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.29709.97
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CheckHvVendor", "CheckHvVendor\CheckHvVendor.vcxproj", "{243E8ED0-58CF-4322-BB7D-D52E70352608}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{243E8ED0-58CF-4322-BB7D-D52E70352608}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{243E8ED0-58CF-4322-BB7D-D52E70352608}.Debug|x64.Build.0 = Debug|x64
|
||||
{243E8ED0-58CF-4322-BB7D-D52E70352608}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{243E8ED0-58CF-4322-BB7D-D52E70352608}.Debug|x86.Build.0 = Debug|Win32
|
||||
{243E8ED0-58CF-4322-BB7D-D52E70352608}.Release|x64.ActiveCfg = Release|Win32
|
||||
{243E8ED0-58CF-4322-BB7D-D52E70352608}.Release|x64.Build.0 = Release|Win32
|
||||
{243E8ED0-58CF-4322-BB7D-D52E70352608}.Release|x86.ActiveCfg = Release|Win32
|
||||
{243E8ED0-58CF-4322-BB7D-D52E70352608}.Release|x86.Build.0 = Release|Win32
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {7698274F-4C44-4EDE-8DE0-FC9195A1FB39}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
122
Tests/CheckHvVendor/CheckHvVendor/CheckHvVendor.cpp
Normal file
@@ -0,0 +1,122 @@
|
||||
//
|
||||
// This program executes CPUID(0x40000000) on all logical processors.
|
||||
//
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__)
|
||||
#define WINDOWS
|
||||
|
||||
#include <intrin.h>
|
||||
#include <Windows.h>
|
||||
|
||||
#elif defined(__linux__)
|
||||
#define LINUX
|
||||
|
||||
#include <sys/sysinfo.h>
|
||||
#include <sched.h>
|
||||
#include <cpuid.h>
|
||||
|
||||
#endif
|
||||
|
||||
static
|
||||
void
|
||||
cpuid (
|
||||
int* regs,
|
||||
int leaf
|
||||
)
|
||||
{
|
||||
#if defined(WINDOWS)
|
||||
__cpuid(regs, leaf);
|
||||
#elif defined(LINUX)
|
||||
__cpuid(leaf, regs[0], regs[1], regs[2], regs[3]);
|
||||
#endif
|
||||
}
|
||||
|
||||
static
|
||||
unsigned long
|
||||
get_logical_processor_count (
|
||||
)
|
||||
{
|
||||
#if defined(WINDOWS)
|
||||
SYSTEM_INFO systemInfo;
|
||||
|
||||
GetSystemInfo(&systemInfo);
|
||||
return systemInfo.dwNumberOfProcessors;
|
||||
#elif defined(LINUX)
|
||||
return get_nprocs();
|
||||
#endif
|
||||
}
|
||||
|
||||
static
|
||||
int
|
||||
get_current_processor_number (
|
||||
)
|
||||
{
|
||||
#if defined(WINDOWS)
|
||||
return GetCurrentProcessorNumber();
|
||||
#elif defined(LINUX)
|
||||
return sched_getcpu();
|
||||
#endif
|
||||
}
|
||||
|
||||
static
|
||||
bool
|
||||
set_affinity (
|
||||
int processor_number
|
||||
)
|
||||
{
|
||||
#if defined(WINDOWS)
|
||||
return (SetProcessAffinityMask(GetCurrentProcess(), ((DWORD_PTR)1) << processor_number) != FALSE);
|
||||
#elif defined(LINUX)
|
||||
cpu_set_t mask;
|
||||
|
||||
CPU_ZERO(&mask);
|
||||
CPU_SET(processor_number, &mask);
|
||||
return (sched_setaffinity(0, sizeof(mask), &mask) != -1);
|
||||
#endif
|
||||
}
|
||||
|
||||
static
|
||||
void
|
||||
run_cpuid (
|
||||
)
|
||||
{
|
||||
int registers[4] = {}; // EAX, EBX, ECX, and EDX
|
||||
char vendorId[13];
|
||||
|
||||
printf("Executing CPUID(0x40000000) on CPU %d\n", get_current_processor_number());
|
||||
cpuid(registers, 0x40000000);
|
||||
memcpy(vendorId + 0, ®isters[1], sizeof(registers[1]));
|
||||
memcpy(vendorId + 4, ®isters[2], sizeof(registers[2]));
|
||||
memcpy(vendorId + 8, ®isters[3], sizeof(registers[3]));
|
||||
vendorId[12] = '\0';
|
||||
printf("Result: %s\n", vendorId);
|
||||
}
|
||||
|
||||
|
||||
static
|
||||
void
|
||||
test_cpuid_on_all_processors (
|
||||
)
|
||||
{
|
||||
unsigned long cpuCount = get_logical_processor_count();
|
||||
|
||||
for (unsigned long i = 0; i < cpuCount; ++i)
|
||||
{
|
||||
if (!set_affinity(i))
|
||||
{
|
||||
printf("set_affinity failed\n");
|
||||
return;
|
||||
}
|
||||
|
||||
run_cpuid();
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
main (
|
||||
)
|
||||
{
|
||||
test_cpuid_on_all_processors();
|
||||
}
|
||||
162
Tests/CheckHvVendor/CheckHvVendor/CheckHvVendor.vcxproj
Normal file
@@ -0,0 +1,162 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{243E8ED0-58CF-4322-BB7D-D52E70352608}</ProjectGuid>
|
||||
<SccProjectName>SAK</SccProjectName>
|
||||
<SccAuxPath>SAK</SccAuxPath>
|
||||
<SccLocalPath>SAK</SccLocalPath>
|
||||
<SccProvider>SAK</SccProvider>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>CheckHvVendor</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0.18362.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset Condition="'$(VisualStudioVersion)' == '15.0'">v141</PlatformToolset>
|
||||
<PlatformToolset Condition="'$(VisualStudioVersion)' == '16.0'">v142</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset Condition="'$(VisualStudioVersion)' == '15.0'">v141</PlatformToolset>
|
||||
<PlatformToolset Condition="'$(VisualStudioVersion)' == '16.0'">v142</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset Condition="'$(VisualStudioVersion)' == '15.0'">v141</PlatformToolset>
|
||||
<PlatformToolset Condition="'$(VisualStudioVersion)' == '16.0'">v142</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset Condition="'$(VisualStudioVersion)' == '15.0'">v141</PlatformToolset>
|
||||
<PlatformToolset Condition="'$(VisualStudioVersion)' == '16.0'">v142</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
<LibraryPath>$(VC_ReferencesPath_VC_X86)\..\onecore\x86;$(WindowsSDK_LibraryPath_x86);$(UniversalCRT_LibraryPath_x86)</LibraryPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
<LibraryPath>$(VC_ReferencesPath_VC_X86)\..\onecore\x64;$(WindowsSDK_LibraryPath_x64);$(UniversalCRT_LibraryPath_x64)</LibraryPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<LibraryPath>$(VC_ReferencesPath_VC_X86)\..\onecore\x86;$(WindowsSDK_LibraryPath_x86);$(UniversalCRT_LibraryPath_x86)</LibraryPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<LibraryPath>$(VC_ReferencesPath_VC_X86)\..\onecore\x64;$(WindowsSDK_LibraryPath_x64);$(UniversalCRT_LibraryPath_x64)</LibraryPath>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>onecoreuap.lib</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>onecoreuap.lib</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<AdditionalDependencies>onecoreuap.lib</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<AdditionalDependencies>onecoreuap.lib</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="CheckHvVendor.cpp" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<ClCompile Include="CheckHvVendor.cpp" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||