mirror of
https://github.com/wbenny/hvpp
synced 2026-08-17 04:26:04 -04:00
big refactoring (not as bad as it seems)
- remove support for HVPP_SINGLE_VCPU - classes DON'T have initialize()/destroy() methods anymore (briefly tackled by issue #11) - the initialization/destroy of the objects is now handled properly via constructors/destructors - most of the time, the constructors WON'T fail, the only exception is vmexit_stats_handler::vmexit_stats_handler() - device::initialize() method is renamed to device::create() - namespaces still DO have initialize()/destroy() functions - logger, memory manager, ... - hvpp::hypervisor class is transformed into hvpp::hypervisor namespace - hypervisor::is_started() renamed to hypervisor::is_running() - memory_manager namespace is renamed to mm - use new C++ member initialization form wherever possible - vcpu_t::launch()/vcpu_t::terminate() renamed to vcpu_t::start()/vcpu_t::stop - vmexit_handler::invoke_termination() renamed to vmexit_handler::teardown() - predefined interrupts moved to hvpp::interrupt interface
This commit is contained in:
parent
b8aaa631f3
commit
8f9e1ace68
53 changed files with 1224 additions and 1176 deletions
25
README.md
25
README.md
|
|
@ -84,21 +84,20 @@ read and navigate through 5000 pages with browser's built-in PDF reader.
|
|||
- preallocate enough memory and initialize the **hvpp** memory manager
|
||||
- initialize the logger
|
||||
- Bootstrap of the hypervisor (hvppdrv, [main.cpp](src/hvppdrv/main.cpp))
|
||||
- create **hvpp** instance
|
||||
- create **vmexit_handler** instance
|
||||
- Start the hypervisor with provided VM-exit handler (`hypervisor::start(vmexit_handler* handler)`)
|
||||
- initialize each virtual cpu (VCPU) on each logical processor via IPI (inter-processor interrupt) - this also includes
|
||||
initialization of EPT
|
||||
- Start the hypervisor with provided VM-exit handler: `hypervisor::start(vmexit_handler& handler)`
|
||||
- initialize virtual cpu (VCPU) for each logical processor
|
||||
- assign provided `vmexit_handler` instance to each VCPU
|
||||
- launch all VCPUs - for each VCPU `vmexit_handler::setup()` is called within `vcpu_t::launch()` method, which
|
||||
allows anyone to initialize the VM-exit handler and/or modify the VMCS before the launch (see `custom_vmexit_handler::setup()`
|
||||
in hvppdrv, [custom_vmexit.cpp](src/hvppdrv/custom_vmexit.cpp))
|
||||
- launch all VCPUs via IPI (inter-processor interrupt): `vcpu_t::start()`
|
||||
- setup VMXON region and VMCS: `vcpu_t::vmx_enter()`
|
||||
- `vmexit_handler::setup()` is called, which allows anyone to initialize the VM-exit handler and/or modify the VMCS
|
||||
before the launch (see `vmexit_custom_handler::setup()` in hvppdrv, [vmexit_custom.cpp](src/hvppdrv/vmexit_custom.cpp))
|
||||
- Hypervisor is now running and handling VM-exits via provided VM-exit handler
|
||||
- Terminate the hypervisor (`hypervisor::destroy()`)
|
||||
- destroy each VCPU via IPI - for each VCPU `vmexit_handler::invoke_termination()` is called within `vcpu_t::destroy()`
|
||||
method, which should be responsible for switching into VMX mode and then call `vcpu_t::terminate()`
|
||||
- this is by default handled via `VMCALL` instruction
|
||||
- `vcpu_t::terminate()` leaves VMX mode with `VMXOFF` instruction (which is available only in VMX mode),
|
||||
- Stop the hypervisor: `hypervisor::stop()`
|
||||
- destroy each VCPU via IPI: `vcpu_t::stop()`
|
||||
- `vmexit_handler::teardown()` is called and switches into VMX mode (`vmexit_passthrough_handler::teardown()` does
|
||||
it by `VMCALL` instruction)
|
||||
- in VMX mode, `vcpu_t::vmx_leave()` is called - it leaves VMX mode with `VMXOFF` instruction
|
||||
|
||||
### Compilation
|
||||
|
||||
|
|
@ -154,7 +153,7 @@ Run **hvppctrl**:
|
|||
|
||||
|
||||
- **hvppctrl** performs `CPUID` instruction with `EAX = 0x70707668 ('hvpp')` which **hvpp** should intercept and return
|
||||
string `hello from hvpp` in EAX, EBX, ECX and EDX registers (see [custom_vmexit.cpp](src/hvppdrv/custom_vmexit.cpp)).
|
||||
string `hello from hvpp` in EAX, EBX, ECX and EDX registers (see [vmexit_custom.cpp](src/hvppdrv/vmexit_custom.cpp)).
|
||||
**hvppctrl** should print this string.
|
||||
|
||||
- **hvppctrl** tries to "stealthily" hook `ntdll!ZwClose` function using EPT. The exact process is described
|
||||
|
|
|
|||
|
|
@ -132,6 +132,7 @@
|
|||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="hvpp\hvpp.h" />
|
||||
<ClInclude Include="hvpp\interrupt.h" />
|
||||
<ClInclude Include="hvpp\lib\debugger.h" />
|
||||
<ClInclude Include="hvpp\lib\device.h" />
|
||||
<ClInclude Include="hvpp\lib\ioctl.h" />
|
||||
|
|
|
|||
|
|
@ -302,6 +302,9 @@
|
|||
<ClInclude Include="hvpp\vmexit\vmexit_c_wrapper.h">
|
||||
<Filter>Header Files\hvpp\vmexit</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="hvpp\interrupt.h">
|
||||
<Filter>Header Files\hvpp</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<MASM Include="hvpp\ia32\context.asm">
|
||||
|
|
|
|||
|
|
@ -6,12 +6,6 @@
|
|||
|
||||
#define HVPP_MAX_CPU 256
|
||||
|
||||
//
|
||||
// Uncomment this if you want to subvert just one CPU (with ID 0).
|
||||
// This can be helpful for debugging purposes.
|
||||
//
|
||||
// #define HVPP_SINGLE_VCPU
|
||||
|
||||
//
|
||||
// Uncomment this if you plan to intercept I/O ports 0x5658/0x5659
|
||||
// in VMWare and you don't want the VMWare Tools to crash.
|
||||
|
|
|
|||
|
|
@ -7,54 +7,35 @@
|
|||
|
||||
namespace hvpp {
|
||||
|
||||
auto ept_t::initialize() noexcept -> error_code_t
|
||||
ept_t::ept_t() noexcept
|
||||
: epml4_{}
|
||||
, eptptr_{}
|
||||
{
|
||||
//
|
||||
// Initialize EPT's PML4. Each PML4 maps 512GB of memory. We would be fine
|
||||
// Initialize EPT's PML4. Each PML4 maps 512GB of memory. We would be fine
|
||||
// with just one PML4 in most scenarios, but we have to waste single page
|
||||
// on it anyway. Single page can handle 512 PML4s (their size is 8 bytes)
|
||||
// so just fill the whole page with 512 PML4s.
|
||||
//
|
||||
static_assert(sizeof(epte_t) * 512 == page_size);
|
||||
|
||||
epml4_ = new epte_t[512];
|
||||
hvpp_assert(epml4_ != nullptr);
|
||||
|
||||
if (!epml4_)
|
||||
{
|
||||
return make_error_code_t(std::errc::not_enough_memory);
|
||||
}
|
||||
|
||||
memset(epml4_, 0, sizeof(epte_t) * 512);
|
||||
|
||||
//
|
||||
// Get physical address of EPT's PML4.
|
||||
//
|
||||
pa_t empl4_pa = pa_t::from_va(epml4_);
|
||||
const pa_t empl4_pa = pa_t::from_va(epml4_);
|
||||
|
||||
//
|
||||
// Initialize EPT pointer.
|
||||
// It's not really JUST pointer, but Intel Manual calls it this way.
|
||||
//
|
||||
eptptr_.flags = 0;
|
||||
eptptr_.memory_type = static_cast<uint64_t>(memory_manager::mtrr().type(empl4_pa));
|
||||
eptptr_.memory_type = static_cast<uint64_t>(mm::mtrr().type(empl4_pa));
|
||||
eptptr_.page_walk_length = ept_ptr_t::page_walk_length_4;
|
||||
eptptr_.page_frame_number = empl4_pa.pfn();
|
||||
|
||||
return error_code_t{};
|
||||
}
|
||||
|
||||
void ept_t::destroy() noexcept
|
||||
ept_t::~ept_t() noexcept
|
||||
{
|
||||
eptptr_.flags = 0;
|
||||
|
||||
if (epml4_)
|
||||
{
|
||||
unmap_table(epml4_);
|
||||
delete[] epml4_;
|
||||
|
||||
epml4_ = nullptr;
|
||||
}
|
||||
unmap_table(epml4_);
|
||||
}
|
||||
|
||||
void ept_t::map_identity(epte_t::access_type access /* = epte_t::access_type::read_write_execute */) noexcept
|
||||
|
|
@ -185,8 +166,8 @@ epte_t* ept_t::ept_entry(pa_t guest_pa, pml level /* = pml::pt */) noexcept
|
|||
// Start at PML4 and traverse down the paging hierarchy.
|
||||
// Returns nullptr for unmapped (non-present) physical addresses.
|
||||
//
|
||||
auto pml4e = &epml4_[guest_pa.index(pml::pml4)];
|
||||
auto pdpte = pml4e->present()
|
||||
const auto pml4e = &epml4_[guest_pa.index(pml::pml4)];
|
||||
const auto pdpte = pml4e->present()
|
||||
? &pml4e->subtable()[guest_pa.index(pml::pdpt)]
|
||||
: nullptr;
|
||||
|
||||
|
|
@ -195,7 +176,7 @@ epte_t* ept_t::ept_entry(pa_t guest_pa, pml level /* = pml::pt */) noexcept
|
|||
return pdpte;
|
||||
}
|
||||
|
||||
auto pde = pdpte->present()
|
||||
const auto pde = pdpte->present()
|
||||
? &pdpte->subtable()[guest_pa.index(pml::pd)]
|
||||
: nullptr;
|
||||
|
||||
|
|
@ -204,7 +185,7 @@ epte_t* ept_t::ept_entry(pa_t guest_pa, pml level /* = pml::pt */) noexcept
|
|||
return pde;
|
||||
}
|
||||
|
||||
auto pte = pde->present()
|
||||
const auto pte = pde->present()
|
||||
? &pde->subtable()[guest_pa.index(pml::pt)]
|
||||
: nullptr;
|
||||
|
||||
|
|
@ -260,7 +241,7 @@ void ept_t::split(pa_t guest_pa, pa_t host_pa, epte_t::access_type access) noexc
|
|||
// The returned EPT entry is fetched at the "ept_table_from_t::level",
|
||||
// this means that if we're splitting from PD to PTs, we've fetched PD entry.
|
||||
//
|
||||
auto entry = ept_entry(guest_pa, ept_table_from_t::level);
|
||||
const auto entry = ept_entry(guest_pa, ept_table_from_t::level);
|
||||
|
||||
//
|
||||
// Make sure that the fetched entry is indeed large.
|
||||
|
|
@ -388,7 +369,7 @@ epte_t* ept_t::map_subtable(epte_t* table) noexcept
|
|||
return table->subtable();
|
||||
}
|
||||
|
||||
auto subtable = new epte_t[512];
|
||||
const auto subtable = new epte_t[512];
|
||||
hvpp_assert(subtable != nullptr);
|
||||
memset(subtable, 0, sizeof(epte_t) * 512);
|
||||
static_assert(sizeof(epte_t) * 512 == page_size);
|
||||
|
|
@ -400,8 +381,8 @@ epte_t* ept_t::map_subtable(epte_t* table) noexcept
|
|||
epte_t* ept_t::map_pml4(pa_t guest_pa, pa_t host_pa, epte_t* pml4,
|
||||
epte_t::access_type access, pml large) noexcept
|
||||
{
|
||||
auto pml4e = &pml4[guest_pa.index(pml::pml4)];
|
||||
auto pdpt = map_subtable(pml4e);
|
||||
const auto pml4e = &pml4[guest_pa.index(pml::pml4)];
|
||||
const auto pdpt = map_subtable(pml4e);
|
||||
|
||||
return map_pdpt(guest_pa, host_pa, pdpt, access, large);
|
||||
}
|
||||
|
|
@ -409,42 +390,42 @@ epte_t* ept_t::map_pml4(pa_t guest_pa, pa_t host_pa, epte_t* pml4,
|
|||
epte_t* ept_t::map_pdpt(pa_t guest_pa, pa_t host_pa, epte_t* pdpt,
|
||||
epte_t::access_type access, pml large) noexcept
|
||||
{
|
||||
auto pdpte = &pdpt[guest_pa.index(pml::pdpt)];
|
||||
const auto pdpte = &pdpt[guest_pa.index(pml::pdpt)];
|
||||
|
||||
if (large == pml::pdpt)
|
||||
{
|
||||
pdpte->update(host_pa, memory_manager::mtrr().type(guest_pa), true, access);
|
||||
pdpte->update(host_pa, mm::mtrr().type(guest_pa), true, access);
|
||||
return pdpte;
|
||||
}
|
||||
|
||||
auto pd = map_subtable(pdpte);
|
||||
const auto pd = map_subtable(pdpte);
|
||||
return map_pd(guest_pa, host_pa, pd, access, large);
|
||||
}
|
||||
|
||||
epte_t* ept_t::map_pd(pa_t guest_pa, pa_t host_pa, epte_t* pd,
|
||||
epte_t::access_type access, pml large) noexcept
|
||||
{
|
||||
auto pde = &pd[guest_pa.index(pml::pd)];
|
||||
const auto pde = &pd[guest_pa.index(pml::pd)];
|
||||
|
||||
if (large == pml::pd)
|
||||
{
|
||||
pde->update(host_pa, memory_manager::mtrr().type(guest_pa), true, access);
|
||||
pde->update(host_pa, mm::mtrr().type(guest_pa), true, access);
|
||||
return pde;
|
||||
}
|
||||
|
||||
auto pt = map_subtable(pde);
|
||||
const auto pt = map_subtable(pde);
|
||||
return map_pt(guest_pa, host_pa, pt, access, large);
|
||||
}
|
||||
|
||||
epte_t* ept_t::map_pt(pa_t guest_pa, pa_t host_pa, epte_t* pt,
|
||||
epte_t::access_type access, pml large) noexcept
|
||||
{
|
||||
auto pte = &pt[guest_pa.index(pml::pt)];
|
||||
const auto pte = &pt[guest_pa.index(pml::pt)];
|
||||
|
||||
(void)(large);
|
||||
hvpp_assert(large == pml::pt);
|
||||
{
|
||||
pte->update(host_pa, memory_manager::mtrr().type(guest_pa), access);
|
||||
pte->update(host_pa, mm::mtrr().type(guest_pa), access);
|
||||
return pte;
|
||||
}
|
||||
}
|
||||
|
|
@ -461,7 +442,7 @@ void ept_t::unmap_table(epte_t* table, pml level /* = pml::pml4 */) noexcept
|
|||
//
|
||||
for (int i = 0; i < 512; ++i)
|
||||
{
|
||||
auto entry = &table[i];
|
||||
const auto entry = &table[i];
|
||||
unmap_entry(entry, level);
|
||||
}
|
||||
}
|
||||
|
|
@ -492,7 +473,7 @@ void ept_t::unmap_entry(epte_t* entry, pml level) noexcept
|
|||
//
|
||||
// Fetch subtable. Only non-large pages have subtables.
|
||||
//
|
||||
auto subtable = entry->subtable();
|
||||
const auto subtable = entry->subtable();
|
||||
|
||||
//
|
||||
// Unmap and/or deallocate the subtable based on current page map level.
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ using namespace ia32;
|
|||
class ept_t final
|
||||
{
|
||||
public:
|
||||
auto initialize() noexcept -> error_code_t;
|
||||
void destroy() noexcept;
|
||||
ept_t() noexcept;
|
||||
~ept_t() noexcept;
|
||||
|
||||
void map_identity(epte_t::access_type access = epte_t::access_type::read_write_execute) noexcept;
|
||||
|
||||
|
|
@ -35,9 +35,9 @@ class ept_t final
|
|||
epte_t::access_type access = epte_t::access_type::read_write_execute) noexcept;
|
||||
|
||||
void join_2mb_to_1gb(pa_t guest_pa, pa_t host_pa,
|
||||
epte_t::access_type access = epte_t::access_type::read_write_execute) noexcept;
|
||||
epte_t::access_type access = epte_t::access_type::read_write_execute) noexcept;
|
||||
void join_4kb_to_2mb(pa_t guest_pa, pa_t host_pa,
|
||||
epte_t::access_type access = epte_t::access_type::read_write_execute) noexcept;
|
||||
epte_t::access_type access = epte_t::access_type::read_write_execute) noexcept;
|
||||
|
||||
epte_t* ept_entry(pa_t guest_pa, pml level = pml::pt) noexcept;
|
||||
ept_ptr_t ept_pointer() const noexcept;
|
||||
|
|
@ -69,8 +69,9 @@ class ept_t final
|
|||
void unmap_table(epte_t* table, pml level = pml::pml4) noexcept;
|
||||
void unmap_entry(epte_t* entry, pml level) noexcept;
|
||||
|
||||
alignas(page_size)
|
||||
epte_t epml4_[512];
|
||||
ept_ptr_t eptptr_;
|
||||
epte_t* epml4_;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include "hypervisor.h"
|
||||
#include "vcpu.h"
|
||||
#include "lib/assert.h"
|
||||
#include "lib/cr3_guard.h"
|
||||
#include "lib/driver.h"
|
||||
#include "lib/mm.h"
|
||||
|
|
@ -13,7 +14,6 @@
|
|||
using namespace ia32;
|
||||
using namespace hvpp;
|
||||
|
||||
#define hvpp_ ((hypervisor*)Hvpp)
|
||||
#define vcpu_ ((vcpu_t*)Vcpu)
|
||||
#define ept_ ((ept_t*)Ept)
|
||||
|
||||
|
|
@ -200,55 +200,33 @@ HvppEptGetEptPointer(
|
|||
|
||||
#pragma region hypervisor.h
|
||||
|
||||
vmexit_c_wrapper_handler* c_exit_handler = nullptr;
|
||||
|
||||
NTSTATUS
|
||||
NTAPI
|
||||
HvppInitialize(
|
||||
_Out_ PHVPP* Hvpp
|
||||
VOID
|
||||
)
|
||||
{
|
||||
//
|
||||
// Initialize the memory manager and logger.
|
||||
//
|
||||
|
||||
driver::common::initialize();
|
||||
|
||||
//
|
||||
// Allocate memory for the hypervisor instance.
|
||||
//
|
||||
|
||||
*Hvpp = (PHVPP)new hypervisor();
|
||||
|
||||
if (!*Hvpp)
|
||||
if (auto err = driver::common::initialize())
|
||||
{
|
||||
//
|
||||
// Allocation failed - exit.
|
||||
//
|
||||
|
||||
driver::common::destroy();
|
||||
return STATUS_INSUFFICIENT_RESOURCES;
|
||||
}
|
||||
|
||||
//
|
||||
// Initialize the hypervisor.
|
||||
//
|
||||
|
||||
return ErrorCodeToNtStatus(((hypervisor*)(*Hvpp))->initialize());
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
VOID
|
||||
NTAPI
|
||||
HvppDestroy(
|
||||
_In_ PHVPP Hvpp
|
||||
VOID
|
||||
)
|
||||
{
|
||||
//
|
||||
// Destroy the hypervisor.
|
||||
//
|
||||
|
||||
hvpp_->destroy();
|
||||
delete &hvpp_->exit_handler();
|
||||
delete hvpp_;
|
||||
|
||||
//
|
||||
// Destroy the memory manager and logger.
|
||||
//
|
||||
|
|
@ -259,16 +237,9 @@ HvppDestroy(
|
|||
NTSTATUS
|
||||
NTAPI
|
||||
HvppStart(
|
||||
_In_ PHVPP Hvpp,
|
||||
_In_ PVMEXIT_HANDLER VmExitHandler
|
||||
)
|
||||
{
|
||||
//
|
||||
// Create the VM-exit handler instance.
|
||||
//
|
||||
|
||||
auto exit_handler = new vmexit_c_wrapper_handler();
|
||||
|
||||
//
|
||||
// Initialize the C-handlers array.
|
||||
//
|
||||
|
|
@ -277,34 +248,39 @@ HvppStart(
|
|||
memcpy(c_handlers.data(), VmExitHandler->HandlerRoutine, sizeof(VmExitHandler->HandlerRoutine));
|
||||
|
||||
//
|
||||
// Initialize the VM-exit handler.
|
||||
// Create the VM-exit handler instance.
|
||||
//
|
||||
|
||||
exit_handler->initialize(c_handlers);
|
||||
hvpp_assert(c_exit_handler == nullptr);
|
||||
c_exit_handler = new vmexit_c_wrapper_handler(c_handlers);
|
||||
|
||||
//
|
||||
// Start the hypervisor.
|
||||
//
|
||||
|
||||
return ErrorCodeToNtStatus(hvpp_->start(*exit_handler));
|
||||
return ErrorCodeToNtStatus(hypervisor::start(*c_exit_handler));
|
||||
}
|
||||
|
||||
VOID
|
||||
NTAPI
|
||||
HvppStop(
|
||||
_In_ PHVPP Hvpp
|
||||
VOID
|
||||
)
|
||||
{
|
||||
hvpp_->stop();
|
||||
hvpp_assert(c_exit_handler != nullptr);
|
||||
|
||||
hypervisor::stop();
|
||||
|
||||
delete c_exit_handler;
|
||||
}
|
||||
|
||||
BOOLEAN
|
||||
NTAPI
|
||||
HvppIsStarted(
|
||||
_In_ PHVPP Hvpp
|
||||
HvppIsRunning(
|
||||
VOID
|
||||
)
|
||||
{
|
||||
return hvpp_->is_started();
|
||||
return hypervisor::is_running();
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
|
|
|||
|
|
@ -820,7 +820,6 @@ typedef enum _VMCS_FIELD
|
|||
// Opaque type definitions.
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
typedef PVOID PHVPP;
|
||||
typedef PVOID PVCPU;
|
||||
typedef PVOID PEPT;
|
||||
|
||||
|
|
@ -1216,32 +1215,31 @@ HvppEptGetEptPointer(
|
|||
NTSTATUS
|
||||
NTAPI
|
||||
HvppInitialize(
|
||||
_Out_ PHVPP* Hvpp
|
||||
VOID
|
||||
);
|
||||
|
||||
VOID
|
||||
NTAPI
|
||||
HvppDestroy(
|
||||
_In_ PHVPP Hvpp
|
||||
VOID
|
||||
);
|
||||
|
||||
NTSTATUS
|
||||
NTAPI
|
||||
HvppStart(
|
||||
_In_ PHVPP Hvpp,
|
||||
_In_ PVMEXIT_HANDLER VmExitHandler
|
||||
);
|
||||
|
||||
VOID
|
||||
NTAPI
|
||||
HvppStop(
|
||||
_In_ PHVPP Hvpp
|
||||
VOID
|
||||
);
|
||||
|
||||
BOOLEAN
|
||||
NTAPI
|
||||
HvppIsStarted(
|
||||
_In_ PHVPP Hvpp
|
||||
HvppIsRunning(
|
||||
VOID
|
||||
);
|
||||
|
||||
#pragma endregion
|
||||
|
|
|
|||
|
|
@ -7,191 +7,171 @@
|
|||
#include "lib/mm.h"
|
||||
#include "lib/mp.h"
|
||||
|
||||
#ifdef HVPP_SINGLE_VCPU
|
||||
# include <ntddk.h>
|
||||
|
||||
# define single_cpu_call(callback) \
|
||||
do \
|
||||
{ \
|
||||
auto idx = 0; \
|
||||
KeSetSystemAffinityThread((ULONG_PTR)1 << (idx)); \
|
||||
callback(); \
|
||||
KeRevertToUserAffinityThread(); \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
namespace hvpp {
|
||||
|
||||
auto hypervisor::initialize() noexcept -> error_code_t
|
||||
namespace hvpp::hypervisor
|
||||
{
|
||||
vcpu_list_ = new vcpu_t[mp::cpu_count()];
|
||||
exit_handler_ = nullptr;
|
||||
check_passed_ = false;
|
||||
started_ = false;
|
||||
|
||||
if (!vcpu_list_)
|
||||
namespace detail
|
||||
{
|
||||
return make_error_code_t(std::errc::not_enough_memory);
|
||||
static
|
||||
bool
|
||||
check_cpu_features(
|
||||
void
|
||||
) noexcept
|
||||
{
|
||||
cpuid_eax_01 cpuid_info;
|
||||
ia32_asm_cpuid(cpuid_info.cpu_info, 1);
|
||||
if (!cpuid_info.feature_information_ecx.virtual_machine_extensions)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto cr4 = read<cr4_t>();
|
||||
if (cr4.vmx_enable)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto vmx_basic = msr::read<msr::vmx_basic_t>();
|
||||
if (
|
||||
vmx_basic.vmcs_size_in_bytes > page_size ||
|
||||
vmx_basic.memory_type != uint64_t(memory_type::write_back) ||
|
||||
!vmx_basic.true_controls
|
||||
)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto vmx_ept_vpid_cap = msr::read<msr::vmx_ept_vpid_cap_t>();
|
||||
if (
|
||||
!vmx_ept_vpid_cap.page_walk_length_4 ||
|
||||
!vmx_ept_vpid_cap.memory_type_write_back ||
|
||||
!vmx_ept_vpid_cap.invept ||
|
||||
!vmx_ept_vpid_cap.invept_all_contexts ||
|
||||
!vmx_ept_vpid_cap.execute_only_pages ||
|
||||
!vmx_ept_vpid_cap.pde_2mb_pages
|
||||
)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!check_cpu_features())
|
||||
struct global_t
|
||||
{
|
||||
return make_error_code_t(std::errc::not_supported);
|
||||
vcpu_t* vcpu_list;
|
||||
bool running;
|
||||
};
|
||||
|
||||
global_t global;
|
||||
|
||||
auto start(vmexit_handler& handler) noexcept -> error_code_t
|
||||
{
|
||||
//
|
||||
// If hypervisor is already running,
|
||||
// don't do anything.
|
||||
//
|
||||
hvpp_assert(!global.running);
|
||||
if (global.running)
|
||||
{
|
||||
return make_error_code_t(std::errc::operation_not_permitted);
|
||||
}
|
||||
|
||||
//
|
||||
// Create array of VCPUs.
|
||||
// Note that since
|
||||
// - vcpu_t is not default-constructible
|
||||
// - operator new[] doesn't support constructing objects
|
||||
// with parameters
|
||||
// ... we have to construct this array "placement new".
|
||||
//
|
||||
hvpp_assert(global.vcpu_list == nullptr);
|
||||
|
||||
global.vcpu_list = reinterpret_cast<vcpu_t*>(operator new(sizeof(vcpu_t) * mp::cpu_count()));
|
||||
if (!global.vcpu_list)
|
||||
{
|
||||
return make_error_code_t(std::errc::not_enough_memory);
|
||||
}
|
||||
|
||||
//
|
||||
// Construct each vcpu_t object as `vcpu_t(handler)'.
|
||||
//
|
||||
std::for_each_n(global.vcpu_list, mp::cpu_count(),
|
||||
[&](vcpu_t& vp) {
|
||||
::new (static_cast<void*>(std::addressof(vp)))
|
||||
vcpu_t(handler);
|
||||
});
|
||||
|
||||
//
|
||||
// Check that CPU supports all required features to
|
||||
// run this hypervisor.
|
||||
// Note that this check is performed only on current CPU
|
||||
// and assumes all CPUs are symmetrical.
|
||||
//
|
||||
if (!detail::check_cpu_features())
|
||||
{
|
||||
return make_error_code_t(std::errc::not_supported);
|
||||
}
|
||||
|
||||
//
|
||||
// Start virtualization on all CPUs.
|
||||
// TODO:
|
||||
// - error handling
|
||||
// - create new error_category for VMX errors?
|
||||
//
|
||||
mp::ipi_call([]() {
|
||||
mm::allocator_guard _;
|
||||
|
||||
const auto idx = mp::cpu_index();
|
||||
global.vcpu_list[idx].start();
|
||||
});
|
||||
|
||||
//
|
||||
// Signalize that hypervisor has started.
|
||||
//
|
||||
global.running = true;
|
||||
|
||||
return error_code_t{};
|
||||
}
|
||||
|
||||
return error_code_t{};
|
||||
}
|
||||
|
||||
void hypervisor::destroy() noexcept
|
||||
{
|
||||
if (started_)
|
||||
void stop() noexcept
|
||||
{
|
||||
stop();
|
||||
//
|
||||
// If hypervisor is already stopped,
|
||||
// don't do anything.
|
||||
//
|
||||
hvpp_assert(global.running);
|
||||
if (!global.running)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Stop virtualization on all CPUs.
|
||||
//
|
||||
mp::ipi_call([]() {
|
||||
mm::allocator_guard _;
|
||||
|
||||
const auto idx = mp::cpu_index();
|
||||
global.vcpu_list[idx].stop();
|
||||
});
|
||||
|
||||
//
|
||||
// Destroy array of VCPUs.
|
||||
//
|
||||
std::destroy_n(global.vcpu_list, mp::cpu_count());
|
||||
delete global.vcpu_list;
|
||||
|
||||
global.vcpu_list = nullptr;
|
||||
|
||||
//
|
||||
// Signalize that hypervisor has stopped.
|
||||
//
|
||||
global.running = false;
|
||||
}
|
||||
|
||||
if (vcpu_list_)
|
||||
bool is_running() noexcept
|
||||
{
|
||||
delete[] vcpu_list_;
|
||||
vcpu_list_ = nullptr;
|
||||
check_passed_ = false;
|
||||
started_ = false;
|
||||
return global.running;
|
||||
}
|
||||
}
|
||||
|
||||
auto hypervisor::start(vmexit_handler& handler) noexcept -> error_code_t
|
||||
{
|
||||
hvpp_assert(vcpu_list_ && check_passed_);
|
||||
hvpp_assert(!started_);
|
||||
|
||||
if (!vcpu_list_ || !check_passed_)
|
||||
{
|
||||
return make_error_code_t(std::errc::invalid_argument);
|
||||
}
|
||||
|
||||
if (started_)
|
||||
{
|
||||
return make_error_code_t(std::errc::operation_not_permitted);
|
||||
}
|
||||
|
||||
exit_handler_ = &handler;
|
||||
|
||||
#ifdef HVPP_SINGLE_VCPU
|
||||
single_cpu_call(start_ipi_callback);
|
||||
#else
|
||||
mp::ipi_call(this, &hypervisor::start_ipi_callback);
|
||||
#endif
|
||||
|
||||
started_ = true;
|
||||
|
||||
return error_code_t{};
|
||||
}
|
||||
|
||||
void hypervisor::stop() noexcept
|
||||
{
|
||||
hvpp_assert(started_);
|
||||
|
||||
if (!started_)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef HVPP_SINGLE_VCPU
|
||||
single_cpu_call(stop_ipi_callback);
|
||||
#else
|
||||
mp::ipi_call(this, &hypervisor::stop_ipi_callback);
|
||||
#endif
|
||||
|
||||
started_ = false;
|
||||
}
|
||||
|
||||
bool hypervisor::is_started() const noexcept
|
||||
{
|
||||
return started_;
|
||||
}
|
||||
|
||||
auto hypervisor::exit_handler() noexcept -> vmexit_handler&
|
||||
{
|
||||
return *exit_handler_;
|
||||
}
|
||||
|
||||
//
|
||||
// Private
|
||||
//
|
||||
|
||||
bool hypervisor::check_cpu_features() noexcept
|
||||
{
|
||||
hvpp_assert(vcpu_list_);
|
||||
|
||||
#ifdef HVPP_SINGLE_VCPU
|
||||
single_cpu_call(check_ipi_callback);
|
||||
#else
|
||||
mp::ipi_call(this, &hypervisor::check_ipi_callback);
|
||||
#endif
|
||||
|
||||
return check_passed_;
|
||||
}
|
||||
|
||||
void hypervisor::check_ipi_callback() noexcept
|
||||
{
|
||||
cpuid_eax_01 cpuid_info;
|
||||
ia32_asm_cpuid(cpuid_info.cpu_info, 1);
|
||||
if (!cpuid_info.feature_information_ecx.virtual_machine_extensions)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto cr4 = read<cr4_t>();
|
||||
if (cr4.vmx_enable)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto vmx_basic = msr::read<msr::vmx_basic_t>();
|
||||
if (
|
||||
vmx_basic.vmcs_size_in_bytes > page_size ||
|
||||
vmx_basic.memory_type != uint64_t(memory_type::write_back) ||
|
||||
!vmx_basic.true_controls
|
||||
)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto vmx_ept_vpid_cap = msr::read<msr::vmx_ept_vpid_cap_t>();
|
||||
if (
|
||||
!vmx_ept_vpid_cap.page_walk_length_4 ||
|
||||
!vmx_ept_vpid_cap.memory_type_write_back ||
|
||||
!vmx_ept_vpid_cap.invept ||
|
||||
!vmx_ept_vpid_cap.invept_all_contexts ||
|
||||
!vmx_ept_vpid_cap.execute_only_pages ||
|
||||
!vmx_ept_vpid_cap.pde_2mb_pages
|
||||
)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
check_passed_ = true;
|
||||
}
|
||||
|
||||
void hypervisor::start_ipi_callback() noexcept
|
||||
{
|
||||
//
|
||||
// TODO:
|
||||
// - error handling
|
||||
// - create new error_category for VMX errors
|
||||
//
|
||||
memory_manager::allocator_guard _;
|
||||
|
||||
auto idx = mp::cpu_index();
|
||||
vcpu_list_[idx].initialize(*exit_handler_);
|
||||
vcpu_list_[idx].launch();
|
||||
}
|
||||
|
||||
void hypervisor::stop_ipi_callback() noexcept
|
||||
{
|
||||
memory_manager::allocator_guard _;
|
||||
|
||||
auto idx = mp::cpu_index();
|
||||
vcpu_list_[idx].destroy();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,33 +4,10 @@
|
|||
|
||||
#include "lib/error.h"
|
||||
|
||||
namespace hvpp {
|
||||
|
||||
using namespace ia32;
|
||||
|
||||
class hypervisor final
|
||||
namespace hvpp::hypervisor
|
||||
{
|
||||
public:
|
||||
auto initialize() noexcept -> error_code_t;
|
||||
void destroy() noexcept;
|
||||
|
||||
auto start(vmexit_handler& handler) noexcept -> error_code_t;
|
||||
void stop() noexcept;
|
||||
|
||||
bool is_started() const noexcept;
|
||||
auto exit_handler() noexcept -> vmexit_handler&;
|
||||
|
||||
private:
|
||||
bool check_cpu_features() noexcept;
|
||||
|
||||
void start_ipi_callback() noexcept;
|
||||
void stop_ipi_callback() noexcept;
|
||||
void check_ipi_callback() noexcept;
|
||||
|
||||
vcpu_t* vcpu_list_;
|
||||
vmexit_handler* exit_handler_;
|
||||
bool check_passed_;
|
||||
bool started_;
|
||||
};
|
||||
auto start(vmexit_handler& handler) noexcept -> error_code_t;
|
||||
void stop() noexcept;
|
||||
|
||||
bool is_running() noexcept;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -148,7 +148,8 @@ struct segment_access_t
|
|||
// Segment access (as represented in VMX)
|
||||
//
|
||||
|
||||
struct segment_access_vmx_t : segment_access_t
|
||||
struct segment_access_vmx_t
|
||||
: segment_access_t
|
||||
{
|
||||
struct
|
||||
{
|
||||
|
|
@ -209,7 +210,7 @@ struct gdt_entry_t
|
|||
|
||||
if (!access.descriptor_type)
|
||||
{
|
||||
result |= static_cast<uint64_t>(base_address_upper) << 32;
|
||||
result |= uint64_t(base_address_upper) << 32;
|
||||
}
|
||||
|
||||
return reinterpret_cast<void*>(result);
|
||||
|
|
@ -226,6 +227,13 @@ struct gdt_entry_t
|
|||
// Used for LDT.
|
||||
//
|
||||
|
||||
const gdt_entry_t& at(segment_selector_t selector) const noexcept
|
||||
{
|
||||
return *reinterpret_cast<gdt_entry_t*>(
|
||||
uint64_t(base_address()) + selector.index * 8
|
||||
);
|
||||
}
|
||||
|
||||
gdt_entry_t& at(segment_selector_t selector) noexcept
|
||||
{
|
||||
return *reinterpret_cast<gdt_entry_t*>(
|
||||
|
|
@ -233,10 +241,11 @@ struct gdt_entry_t
|
|||
);
|
||||
}
|
||||
|
||||
const gdt_entry_t& operator[](segment_selector_t selector) const noexcept
|
||||
{ return at(selector); }
|
||||
|
||||
gdt_entry_t& operator[](segment_selector_t selector) noexcept
|
||||
{
|
||||
return at(selector);
|
||||
}
|
||||
{ return at(selector); }
|
||||
};
|
||||
|
||||
struct idt_entry_t
|
||||
|
|
@ -270,6 +279,16 @@ struct descriptor_table32_t
|
|||
// GDT entries are accessed by segment selector.
|
||||
//
|
||||
|
||||
const gdt_entry_t& at(segment_selector_t selector) const noexcept
|
||||
{
|
||||
//
|
||||
// See explanation of (selector.index * 8) in vcpu.inl.
|
||||
//
|
||||
return *reinterpret_cast<gdt_entry_t*>(
|
||||
uint64_t(base_address) + selector.index * 8
|
||||
);
|
||||
}
|
||||
|
||||
gdt_entry_t& at(segment_selector_t selector) noexcept
|
||||
{
|
||||
//
|
||||
|
|
@ -284,6 +303,13 @@ struct descriptor_table32_t
|
|||
// IDT entries are accessed by numeric index.
|
||||
//
|
||||
|
||||
const idt_entry_t& at(int index) const noexcept
|
||||
{
|
||||
return reinterpret_cast<idt_entry_t*>(
|
||||
base_address
|
||||
)[index];
|
||||
}
|
||||
|
||||
idt_entry_t& at(int index) noexcept
|
||||
{
|
||||
return reinterpret_cast<idt_entry_t*>(
|
||||
|
|
@ -291,15 +317,17 @@ struct descriptor_table32_t
|
|||
)[index];
|
||||
}
|
||||
|
||||
const gdt_entry_t& operator[](segment_selector_t selector) const noexcept
|
||||
{ return at(selector); }
|
||||
|
||||
gdt_entry_t& operator[](segment_selector_t selector) noexcept
|
||||
{
|
||||
return at(selector);
|
||||
}
|
||||
{ return at(selector); }
|
||||
|
||||
const idt_entry_t& operator[](int index) const noexcept
|
||||
{ return at(index); }
|
||||
|
||||
idt_entry_t& operator[](int index) noexcept
|
||||
{
|
||||
return at(index);
|
||||
}
|
||||
{ return at(index); }
|
||||
};
|
||||
|
||||
struct descriptor_table64_t
|
||||
|
|
@ -307,6 +335,16 @@ struct descriptor_table64_t
|
|||
uint16_t limit;
|
||||
uint64_t base_address;
|
||||
|
||||
const gdt_entry_t& at(segment_selector_t selector) const noexcept
|
||||
{
|
||||
//
|
||||
// See explanation of (selector.index * 8) in vcpu.inl.
|
||||
//
|
||||
return *reinterpret_cast<gdt_entry_t*>(
|
||||
uint64_t(base_address) + selector.index * 8
|
||||
);
|
||||
}
|
||||
|
||||
gdt_entry_t& at(segment_selector_t selector) noexcept
|
||||
{
|
||||
//
|
||||
|
|
@ -317,6 +355,13 @@ struct descriptor_table64_t
|
|||
);
|
||||
}
|
||||
|
||||
const idt_entry_t& at(int index) const noexcept
|
||||
{
|
||||
return reinterpret_cast<idt_entry_t*>(
|
||||
base_address
|
||||
)[index];
|
||||
}
|
||||
|
||||
idt_entry_t& at(int index) noexcept
|
||||
{
|
||||
return reinterpret_cast<idt_entry_t*>(
|
||||
|
|
@ -324,15 +369,17 @@ struct descriptor_table64_t
|
|||
)[index];
|
||||
}
|
||||
|
||||
const gdt_entry_t& operator[](segment_selector_t selector) const noexcept
|
||||
{ return at(selector); }
|
||||
|
||||
gdt_entry_t& operator[](segment_selector_t selector) noexcept
|
||||
{
|
||||
return at(selector);
|
||||
}
|
||||
{ return at(selector); }
|
||||
|
||||
const idt_entry_t& operator[](int index) const noexcept
|
||||
{ return at(index); }
|
||||
|
||||
idt_entry_t& operator[](int index) noexcept
|
||||
{
|
||||
return at(index);
|
||||
}
|
||||
{ return at(index); }
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
|
|
@ -377,42 +424,34 @@ struct segment_t
|
|||
T selector;
|
||||
|
||||
segment_t() noexcept
|
||||
: base_address()
|
||||
, limit()
|
||||
, access()
|
||||
, selector()
|
||||
{
|
||||
|
||||
}
|
||||
: base_address{}
|
||||
, limit{}
|
||||
, access{}
|
||||
, selector{}
|
||||
{ }
|
||||
|
||||
segment_t(T selector) noexcept
|
||||
: base_address()
|
||||
, limit()
|
||||
, access()
|
||||
, selector(selector)
|
||||
{
|
||||
|
||||
}
|
||||
: base_address{}
|
||||
, limit{}
|
||||
, access{}
|
||||
, selector{ selector }
|
||||
{ }
|
||||
|
||||
segment_t(T selector, void* base_address) noexcept
|
||||
: base_address(base_address)
|
||||
, limit()
|
||||
, access()
|
||||
, selector(selector)
|
||||
{
|
||||
|
||||
}
|
||||
: base_address{ base_address }
|
||||
, limit{}
|
||||
, access{}
|
||||
, selector{ selector }
|
||||
{ }
|
||||
|
||||
segment_t(void* base_address, uint32_t limit, segment_access_vmx_t access, T selector) noexcept
|
||||
: base_address(base_address)
|
||||
, limit(limit)
|
||||
, access(access)
|
||||
, selector(selector)
|
||||
{
|
||||
: base_address{ base_address }
|
||||
, limit{ limit }
|
||||
, access{ access }
|
||||
, selector{ selector }
|
||||
{ }
|
||||
|
||||
}
|
||||
|
||||
segment_t(descriptor_table64_t& descriptor_table, const T& segment_selector) noexcept
|
||||
segment_t(descriptor_table64_t descriptor_table, T segment_selector) noexcept
|
||||
{
|
||||
static_assert(sizeof(segment_t) == 24);
|
||||
|
||||
|
|
@ -449,7 +488,7 @@ struct segment_t
|
|||
//
|
||||
if (selector.table == segment_selector_t::table_ldt) ia32_asm_int3();
|
||||
|
||||
auto& table_entry = selector.table
|
||||
const auto& table_entry = selector.table
|
||||
? descriptor_table[read<ldtr_t>()][selector]
|
||||
: descriptor_table[ selector];
|
||||
|
||||
|
|
|
|||
|
|
@ -27,13 +27,13 @@ enum class exception_vector : uint32_t
|
|||
virtualization_exception = 20,
|
||||
|
||||
//
|
||||
// Windows specific.
|
||||
// NT (Windows) specific exception vectors.
|
||||
//
|
||||
|
||||
apc_interrupt = 31,
|
||||
dpc_interrupt = 47,
|
||||
clock_interrupt = 209,
|
||||
pmi_interrupt = 254,
|
||||
nt_apc_interrupt = 31,
|
||||
nt_dpc_interrupt = 47,
|
||||
nt_clock_interrupt = 209,
|
||||
nt_pmi_interrupt = 254,
|
||||
};
|
||||
|
||||
struct pagefault_error_code_t
|
||||
|
|
@ -76,7 +76,7 @@ struct exception_error_code_t
|
|||
};
|
||||
};
|
||||
|
||||
inline constexpr const char* exception_vector_to_string(exception_vector value) noexcept
|
||||
constexpr inline const char* exception_vector_to_string(exception_vector value) noexcept
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
|
|
@ -100,6 +100,10 @@ inline constexpr const char* exception_vector_to_string(exception_vector value)
|
|||
case exception_vector::machine_check: return "machine_check";
|
||||
case exception_vector::simd_floating_point_error: return "simd_floating_point_error";
|
||||
case exception_vector::virtualization_exception: return "virtualization_exception";
|
||||
case exception_vector::nt_apc_interrupt: return "nt_apc_interrupt";
|
||||
case exception_vector::nt_dpc_interrupt: return "nt_dpc_interrupt";
|
||||
case exception_vector::nt_clock_interrupt: return "nt_clock_interrupt";
|
||||
case exception_vector::nt_pmi_interrupt: return "nt_pmi_interrupt";
|
||||
default: return "";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ namespace detail
|
|||
|
||||
pe_t* va_t::pt_entry(cr3_t cr3 /*= read<cr3_t>()*/, pml level /*= pml::pt*/) const noexcept
|
||||
{
|
||||
auto pml4e = &reinterpret_cast<pe_t*>(
|
||||
const auto pml4e = &reinterpret_cast<pe_t*>(
|
||||
pa_t::from_pfn(cr3.page_frame_number).va()
|
||||
)[index(pml::pml4)];
|
||||
|
||||
|
|
@ -31,7 +31,7 @@ pe_t* va_t::pt_entry(cr3_t cr3 /*= read<cr3_t>()*/, pml level /*= pml::pt*/) con
|
|||
return pml4e;
|
||||
}
|
||||
|
||||
auto pdpte = &reinterpret_cast<pe_t*>(
|
||||
const auto pdpte = &reinterpret_cast<pe_t*>(
|
||||
pa_t::from_pfn(pml4e->page_frame_number).va()
|
||||
)[index(pml::pdpt)];
|
||||
|
||||
|
|
@ -40,7 +40,7 @@ pe_t* va_t::pt_entry(cr3_t cr3 /*= read<cr3_t>()*/, pml level /*= pml::pt*/) con
|
|||
return pdpte;
|
||||
}
|
||||
|
||||
auto pde = &reinterpret_cast<pe_t*>(
|
||||
const auto pde = &reinterpret_cast<pe_t*>(
|
||||
pa_t::from_pfn(pdpte->page_frame_number).va()
|
||||
)[index(pml::pd)];
|
||||
|
||||
|
|
@ -49,7 +49,7 @@ pe_t* va_t::pt_entry(cr3_t cr3 /*= read<cr3_t>()*/, pml level /*= pml::pt*/) con
|
|||
return pde;
|
||||
}
|
||||
|
||||
auto pt = &reinterpret_cast<pe_t*>(
|
||||
const auto pt = &reinterpret_cast<pe_t*>(
|
||||
pa_t::from_pfn(pde->page_frame_number).va()
|
||||
)[index(pml::pt)];
|
||||
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ class pa_t
|
|||
pa_t(pa_t&& other) noexcept = default;
|
||||
pa_t& operator=(const pa_t& other) noexcept = default;
|
||||
pa_t& operator=(pa_t&& other) noexcept = default;
|
||||
pa_t(uint64_t pa) noexcept : value_(pa) { }
|
||||
pa_t(uint64_t pa) noexcept : value_{ pa } { }
|
||||
|
||||
pa_t& operator= (uint64_t other) noexcept { value_ = other; return *this; }
|
||||
|
||||
|
|
@ -124,8 +124,8 @@ class va_t
|
|||
va_t(va_t&& other) noexcept = default;
|
||||
va_t& operator=(const va_t& other) noexcept = default;
|
||||
va_t& operator=(va_t&& other) noexcept = default;
|
||||
va_t(const void* va) noexcept : value_(uint64_t(va)) { }
|
||||
va_t(uint64_t va) noexcept : value_(va) { }
|
||||
va_t(const void* va) noexcept : value_{ uint64_t(va) } { }
|
||||
va_t(uint64_t va) noexcept : value_{ va } { }
|
||||
|
||||
va_t& operator= (uint64_t other) noexcept { value_ = other; return *this; }
|
||||
|
||||
|
|
@ -206,13 +206,13 @@ class memory_range
|
|||
memory_range(const memory_range& other) noexcept = default;
|
||||
memory_range(memory_range&& other) noexcept = default;
|
||||
memory_range(const void* begin_va, const void* end_va) noexcept
|
||||
: begin_(reinterpret_cast<const std::byte*>(begin_va))
|
||||
, end_(reinterpret_cast<const std::byte*>(end_va))
|
||||
: begin_{ reinterpret_cast<const std::byte*>(begin_va) }
|
||||
, end_{ reinterpret_cast<const std::byte*>(end_va) }
|
||||
{ }
|
||||
|
||||
memory_range(const void* data, size_t size) noexcept
|
||||
: begin_(reinterpret_cast<const std::byte*>(data))
|
||||
, end_(reinterpret_cast<const std::byte*>(data) + size)
|
||||
: begin_{ reinterpret_cast<const std::byte*>(data) }
|
||||
, end_{ reinterpret_cast<const std::byte*>(data) + size }
|
||||
{ }
|
||||
|
||||
memory_range& operator=(const memory_range& other) noexcept = default;
|
||||
|
|
@ -276,8 +276,8 @@ class physical_memory_range
|
|||
physical_memory_range(const physical_memory_range& other) noexcept = default;
|
||||
physical_memory_range(physical_memory_range&& other) noexcept = default;
|
||||
physical_memory_range(pa_t begin_pa, pa_t end_pa) noexcept
|
||||
: begin_(begin_pa)
|
||||
, end_(end_pa)
|
||||
: begin_{ begin_pa }
|
||||
, end_{ end_pa }
|
||||
{ }
|
||||
|
||||
physical_memory_range& operator=(const physical_memory_range& other) noexcept = default;
|
||||
|
|
@ -352,7 +352,7 @@ class physical_memory_descriptor
|
|||
int count_ = 0;
|
||||
};
|
||||
|
||||
inline constexpr const char* memory_type_to_string(memory_type type) noexcept
|
||||
constexpr inline const char* memory_type_to_string(memory_type type) noexcept
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -30,29 +30,29 @@ enum class pml : uint8_t
|
|||
pml4 = 3,
|
||||
};
|
||||
|
||||
inline constexpr pml& operator++(pml& ptl) noexcept
|
||||
{ reinterpret_cast<uint8_t&>(ptl)++; return ptl; }
|
||||
constexpr inline pml& operator++(pml& ptl) noexcept
|
||||
{ ((uint8_t&)(ptl))++; return ptl; }
|
||||
|
||||
inline constexpr pml& operator--(pml& ptl) noexcept
|
||||
{ reinterpret_cast<uint8_t&>(ptl)--; return ptl; }
|
||||
constexpr inline pml& operator--(pml& ptl) noexcept
|
||||
{ ((uint8_t&)(ptl))--; return ptl; }
|
||||
|
||||
inline constexpr pml operator++(pml& ptl, int) noexcept
|
||||
{ auto result = ptl; reinterpret_cast<uint8_t&>(ptl)++; return result; }
|
||||
constexpr inline pml operator++(pml& ptl, int) noexcept
|
||||
{ auto result = ptl; ((uint8_t&)(ptl))++; return result; }
|
||||
|
||||
inline constexpr pml operator--(pml& ptl, int) noexcept
|
||||
{ auto result = ptl; reinterpret_cast<uint8_t&>(ptl)--; return result; }
|
||||
constexpr inline pml operator--(pml& ptl, int) noexcept
|
||||
{ auto result = ptl; ((uint8_t&)(ptl))--; return result; }
|
||||
|
||||
inline constexpr pml operator+(pml ptl, uint8_t value) noexcept
|
||||
constexpr inline pml operator+(pml ptl, uint8_t value) noexcept
|
||||
{ return static_cast<pml>(static_cast<uint8_t>(ptl) + value); }
|
||||
|
||||
inline constexpr pml operator-(pml ptl, uint8_t value) noexcept
|
||||
constexpr inline pml operator-(pml ptl, uint8_t value) noexcept
|
||||
{ return static_cast<pml>(static_cast<uint8_t>(ptl) - value); }
|
||||
|
||||
inline constexpr pml& operator+=(pml& ptl, uint8_t value) noexcept
|
||||
{ reinterpret_cast<uint8_t&>(ptl) += value; return ptl; }
|
||||
constexpr inline pml& operator+=(pml& ptl, uint8_t value) noexcept
|
||||
{ ((uint8_t&)(ptl)) += value; return ptl; }
|
||||
|
||||
inline constexpr pml& operator-=(pml& ptl, uint8_t value) noexcept
|
||||
{ reinterpret_cast<uint8_t&>(ptl) -= value; return ptl; }
|
||||
constexpr inline pml& operator-=(pml& ptl, uint8_t value) noexcept
|
||||
{ ((uint8_t&)(ptl)) -= value; return ptl; }
|
||||
|
||||
//
|
||||
// Page Table Entry
|
||||
|
|
@ -346,7 +346,7 @@ template <
|
|||
)
|
||||
>
|
||||
>
|
||||
inline constexpr T page_align(T va, PAGE_DESCRIPTOR) noexcept
|
||||
constexpr inline T page_align(T va, PAGE_DESCRIPTOR) noexcept
|
||||
{ return (T)(uintptr_t(va) & PAGE_DESCRIPTOR::mask); }
|
||||
|
||||
//
|
||||
|
|
@ -362,7 +362,7 @@ template <
|
|||
)
|
||||
>
|
||||
>
|
||||
inline constexpr T page_align_up(T va, PAGE_DESCRIPTOR) noexcept
|
||||
constexpr inline T page_align_up(T va, PAGE_DESCRIPTOR) noexcept
|
||||
{ return (T)((uintptr_t(va) + PAGE_DESCRIPTOR::size - 1) & PAGE_DESCRIPTOR::mask); }
|
||||
|
||||
//
|
||||
|
|
@ -378,7 +378,7 @@ template <
|
|||
)
|
||||
>
|
||||
>
|
||||
inline constexpr uint32_t byte_offset(T va, PAGE_DESCRIPTOR) noexcept
|
||||
constexpr inline uint32_t byte_offset(T va, PAGE_DESCRIPTOR) noexcept
|
||||
{ return (uint32_t)(uintptr_t(va) & ~PAGE_DESCRIPTOR::mask); }
|
||||
|
||||
//
|
||||
|
|
@ -392,7 +392,7 @@ template <
|
|||
std::is_integral_v<T>
|
||||
>
|
||||
>
|
||||
inline constexpr uint64_t bytes_to_pages(T size, PAGE_DESCRIPTOR) noexcept
|
||||
constexpr inline uint64_t bytes_to_pages(T size, PAGE_DESCRIPTOR) noexcept
|
||||
{ return (size >> PAGE_DESCRIPTOR::shift) + ((size & ~PAGE_DESCRIPTOR::mask) != 0); }
|
||||
|
||||
//
|
||||
|
|
@ -406,7 +406,7 @@ template <
|
|||
std::is_integral_v<T>
|
||||
>
|
||||
>
|
||||
inline constexpr uint64_t round_to_pages(T size, PAGE_DESCRIPTOR) noexcept
|
||||
constexpr inline uint64_t round_to_pages(T size, PAGE_DESCRIPTOR) noexcept
|
||||
{ return uint64_t(page_align_up(size, PAGE_DESCRIPTOR{})); }
|
||||
|
||||
//
|
||||
|
|
@ -414,23 +414,23 @@ inline constexpr uint64_t round_to_pages(T size, PAGE_DESCRIPTOR) noexcept
|
|||
//
|
||||
|
||||
template <typename T>
|
||||
inline constexpr T page_align(T va) noexcept
|
||||
constexpr inline T page_align(T va) noexcept
|
||||
{ return page_align(va, pt_t{}); }
|
||||
|
||||
template <typename T>
|
||||
inline constexpr T page_align_up(T va) noexcept
|
||||
constexpr inline T page_align_up(T va) noexcept
|
||||
{ return page_align_up(va, pt_t{}); }
|
||||
|
||||
template <typename T>
|
||||
inline constexpr uint32_t byte_offset(T va) noexcept
|
||||
constexpr inline uint32_t byte_offset(T va) noexcept
|
||||
{ return byte_offset(va, pt_t{}); }
|
||||
|
||||
template <typename T>
|
||||
inline constexpr uint64_t bytes_to_pages(T size) noexcept
|
||||
constexpr inline uint64_t bytes_to_pages(T size) noexcept
|
||||
{ return bytes_to_pages(size, pt_t{}); }
|
||||
|
||||
template <typename T>
|
||||
inline constexpr uint64_t round_to_pages(T size) noexcept
|
||||
constexpr inline uint64_t round_to_pages(T size) noexcept
|
||||
{ return round_to_pages(size, pt_t{}); }
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ enum class exit_reason : uint16_t
|
|||
execute_xrstors = 0x00000040,
|
||||
};
|
||||
|
||||
inline constexpr const char* exit_reason_to_string(exit_reason value) noexcept
|
||||
constexpr inline const char* exit_reason_to_string(exit_reason value) noexcept
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ enum instruction_error : uint32_t
|
|||
invept_invvpid_invalid_operand = 28,
|
||||
};
|
||||
|
||||
inline constexpr const char* instruction_error_to_string(instruction_error value) noexcept
|
||||
constexpr inline const char* instruction_error_to_string(instruction_error value) noexcept
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -236,7 +236,7 @@ struct instruction_info_t
|
|||
};
|
||||
};
|
||||
|
||||
inline constexpr const char* instruction_info_gdtr_idtr_to_string(uint64_t value) noexcept
|
||||
constexpr inline const char* instruction_info_gdtr_idtr_to_string(uint64_t value) noexcept
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
|
|
@ -249,7 +249,7 @@ inline constexpr const char* instruction_info_gdtr_idtr_to_string(uint64_t value
|
|||
}
|
||||
|
||||
|
||||
inline constexpr const char* instruction_info_ldtr_tr_to_string(uint64_t value) noexcept
|
||||
constexpr inline const char* instruction_info_ldtr_tr_to_string(uint64_t value) noexcept
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ struct interruptibility_state_t
|
|||
};
|
||||
};
|
||||
|
||||
inline constexpr const char* interrupt_type_to_string(interrupt_type value) noexcept
|
||||
constexpr inline const char* interrupt_type_to_string(interrupt_type value) noexcept
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
|
|
|
|||
133
src/hvpp/hvpp/interrupt.h
Normal file
133
src/hvpp/hvpp/interrupt.h
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
#pragma once
|
||||
#include "ia32/exception.h"
|
||||
#include "ia32/vmx.h"
|
||||
|
||||
namespace hvpp {
|
||||
|
||||
class interrupt_t final
|
||||
{
|
||||
public:
|
||||
//
|
||||
// Constructors.
|
||||
//
|
||||
constexpr
|
||||
interrupt_t(
|
||||
vmx::interrupt_type interrupt_type,
|
||||
exception_vector exception_vector,
|
||||
int rip_adjust = -1
|
||||
) noexcept
|
||||
: interrupt_t{ interrupt_type,
|
||||
exception_vector,
|
||||
exception_error_code_t{},
|
||||
false,
|
||||
rip_adjust }
|
||||
{ }
|
||||
|
||||
constexpr
|
||||
interrupt_t(
|
||||
vmx::interrupt_type interrupt_type,
|
||||
exception_vector exception_vector,
|
||||
exception_error_code_t exception_code,
|
||||
int rip_adjust = -1
|
||||
) noexcept
|
||||
: interrupt_t{ interrupt_type,
|
||||
exception_vector,
|
||||
exception_code,
|
||||
true,
|
||||
rip_adjust }
|
||||
{ }
|
||||
|
||||
//
|
||||
// Default copy/move constructor.
|
||||
// Default copy/move assignment operator.
|
||||
//
|
||||
constexpr interrupt_t(const interrupt_t& other) noexcept = default;
|
||||
constexpr interrupt_t(interrupt_t&& other) noexcept = default;
|
||||
constexpr interrupt_t& operator=(const interrupt_t& other) noexcept = default;
|
||||
constexpr interrupt_t& operator=(interrupt_t&& other) noexcept = default;
|
||||
|
||||
//
|
||||
// Getters.
|
||||
//
|
||||
constexpr auto vector() const noexcept { return static_cast<exception_vector>(info_.vector); }
|
||||
constexpr auto type() const noexcept { return static_cast<vmx::interrupt_type>(info_.type); }
|
||||
constexpr auto error_code_valid() const noexcept { return info_.error_code_valid; }
|
||||
constexpr auto nmi_unblocking() const noexcept { return info_.nmi_unblocking; }
|
||||
constexpr auto valid() const noexcept { return info_.valid; }
|
||||
constexpr auto error_code() const noexcept { return error_code_; }
|
||||
constexpr auto rip_adjust() const noexcept { return rip_adjust_; }
|
||||
|
||||
private:
|
||||
friend class vcpu_t;
|
||||
|
||||
constexpr
|
||||
interrupt_t() noexcept
|
||||
: info_{}
|
||||
, error_code_{}
|
||||
, rip_adjust_{}
|
||||
{ }
|
||||
|
||||
constexpr
|
||||
interrupt_t(
|
||||
vmx::interrupt_type interrupt_type,
|
||||
exception_vector exception_vector,
|
||||
exception_error_code_t exception_code,
|
||||
bool exception_code_valid,
|
||||
int rip_adjust
|
||||
) noexcept
|
||||
: error_code_{ exception_code }
|
||||
, rip_adjust_{ rip_adjust }
|
||||
{
|
||||
info_.flags = 0;
|
||||
|
||||
info_.vector = static_cast<uint32_t>(exception_vector);
|
||||
info_.type = static_cast<uint32_t>(interrupt_type);
|
||||
info_.valid = true;
|
||||
|
||||
//
|
||||
// Final sanitization of the following fields takes place
|
||||
// in vcpu::interrupt_inject_force().
|
||||
//
|
||||
|
||||
info_.error_code_valid = exception_code_valid;
|
||||
}
|
||||
|
||||
vmx::interrupt_info_t info_;
|
||||
exception_error_code_t error_code_;
|
||||
int rip_adjust_;
|
||||
};
|
||||
|
||||
namespace interrupt
|
||||
{
|
||||
//
|
||||
// Predefined interrupt structures.
|
||||
// Helpful when injecting events.
|
||||
//
|
||||
|
||||
static constexpr auto nmi =
|
||||
interrupt_t {
|
||||
vmx::interrupt_type::nmi,
|
||||
exception_vector::nmi_interrupt
|
||||
};
|
||||
|
||||
static constexpr auto debug =
|
||||
interrupt_t {
|
||||
vmx::interrupt_type::hardware_exception,
|
||||
exception_vector::debug
|
||||
};
|
||||
|
||||
static constexpr auto invalid_opcode =
|
||||
interrupt_t {
|
||||
vmx::interrupt_type::hardware_exception,
|
||||
exception_vector::invalid_opcode
|
||||
};
|
||||
|
||||
static constexpr auto general_protection =
|
||||
interrupt_t {
|
||||
vmx::interrupt_type::hardware_exception,
|
||||
exception_vector::general_protection,
|
||||
exception_error_code_t{}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -18,20 +18,20 @@
|
|||
class bitmap
|
||||
{
|
||||
public:
|
||||
bitmap() noexcept : buffer_(nullptr), size_in_bits_(0) { };
|
||||
bitmap() noexcept : buffer_{ nullptr }, size_in_bits_{ 0 } { };
|
||||
bitmap(const bitmap& other) noexcept = delete;
|
||||
bitmap(bitmap&& other) noexcept = default;
|
||||
bitmap& operator=(const bitmap& other) = delete;
|
||||
bitmap& operator=(bitmap&& other) = default;
|
||||
|
||||
bitmap(void* buffer, int size_in_bits) noexcept
|
||||
: buffer_(reinterpret_cast<word_t*>(buffer))
|
||||
, size_in_bits_(size_in_bits) { }
|
||||
: buffer_{ reinterpret_cast<word_t*>(buffer) }
|
||||
, size_in_bits_{ size_in_bits } { }
|
||||
|
||||
template <typename T, int SIZE>
|
||||
bitmap(T(&buffer)[SIZE], int size_in_bits = SIZE * sizeof(T)) noexcept
|
||||
: buffer_(reinterpret_cast<word_t*>(buffer))
|
||||
, size_in_bits_(size_in_bits) { }
|
||||
: buffer_{ reinterpret_cast<word_t*>(buffer) }
|
||||
, size_in_bits_{ size_in_bits } { }
|
||||
|
||||
~bitmap() noexcept = default;
|
||||
|
||||
|
|
@ -90,7 +90,7 @@ class bitmap_local
|
|||
: public bitmap
|
||||
{
|
||||
public:
|
||||
bitmap_local() : bitmap(buffer_, SIZE_IN_BITS) { }
|
||||
bitmap_local() : bitmap{ buffer_, SIZE_IN_BITS } { }
|
||||
bitmap_local(const bitmap_local& other) noexcept = delete;
|
||||
bitmap_local(bitmap_local&& other) noexcept = default;
|
||||
bitmap_local& operator=(const bitmap_local& other) noexcept = delete;
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ class cr3_guard
|
|||
{
|
||||
public:
|
||||
cr3_guard(ia32::cr3_t new_cr3) noexcept
|
||||
: previous_cr3_(ia32::read<ia32::cr3_t>())
|
||||
: previous_cr3_{ ia32::read<ia32::cr3_t>() }
|
||||
{ ia32::write<ia32::cr3_t>(::detail::kernel_cr3(new_cr3)); }
|
||||
|
||||
~cr3_guard() noexcept
|
||||
|
|
|
|||
|
|
@ -12,12 +12,13 @@
|
|||
class device
|
||||
{
|
||||
public:
|
||||
virtual ~device() noexcept {}
|
||||
device() noexcept {}
|
||||
virtual ~device() noexcept { destroy(); }
|
||||
|
||||
virtual auto initialize() noexcept -> error_code_t;
|
||||
virtual void destroy() noexcept;
|
||||
virtual const char* name() const noexcept = 0;
|
||||
|
||||
virtual const char* name() const noexcept = 0;
|
||||
auto create() noexcept -> error_code_t;
|
||||
void destroy() noexcept;
|
||||
|
||||
//
|
||||
// Dispatch methods.
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ namespace driver::common
|
|||
return err;
|
||||
}
|
||||
|
||||
if (auto err = memory_manager::initialize())
|
||||
if (auto err = mm::initialize())
|
||||
{
|
||||
return err;
|
||||
}
|
||||
|
|
@ -49,8 +49,8 @@ namespace driver::common
|
|||
//
|
||||
// Print memory information to the debugger.
|
||||
//
|
||||
memory_manager::mtrr().dump();
|
||||
memory_manager::physical_memory_descriptor().dump();
|
||||
mm::mtrr().dump();
|
||||
mm::physical_memory_descriptor().dump();
|
||||
|
||||
//
|
||||
// Estimate required memory size.
|
||||
|
|
@ -59,7 +59,7 @@ namespace driver::common
|
|||
//
|
||||
// Default required memory size is 34MB per CPU.
|
||||
//
|
||||
auto required_memory_size = (
|
||||
const auto required_memory_size = (
|
||||
//
|
||||
// Estimated EPT size:
|
||||
// Make space for 2MB EPT entries for 512 GB of the physical
|
||||
|
|
@ -88,7 +88,7 @@ namespace driver::common
|
|||
//
|
||||
// Allocate memory.
|
||||
//
|
||||
system_memory_ = memory_manager::system_allocate(required_memory_size);
|
||||
system_memory_ = mm::system_allocate(required_memory_size);
|
||||
|
||||
if (!system_memory_)
|
||||
{
|
||||
|
|
@ -98,7 +98,7 @@ namespace driver::common
|
|||
//
|
||||
// Assign allocated memory to the memory manager.
|
||||
//
|
||||
if (auto err = memory_manager::assign(system_memory_, system_memory_size_))
|
||||
if (auto err = mm::assign(system_memory_, system_memory_size_))
|
||||
{
|
||||
return err;
|
||||
}
|
||||
|
|
@ -122,7 +122,7 @@ namespace driver::common
|
|||
//
|
||||
// Destroy memory manager and logger.
|
||||
//
|
||||
memory_manager::destroy();
|
||||
mm::destroy();
|
||||
logger::destroy();
|
||||
|
||||
//
|
||||
|
|
@ -130,7 +130,7 @@ namespace driver::common
|
|||
//
|
||||
if (system_memory_)
|
||||
{
|
||||
memory_manager::system_free(system_memory_);
|
||||
mm::system_free(system_memory_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,41 +17,44 @@
|
|||
class error_code_t
|
||||
{
|
||||
public:
|
||||
error_code_t() noexcept
|
||||
: value_(0) { }
|
||||
constexpr error_code_t() noexcept
|
||||
: value_{ 0 }
|
||||
{ }
|
||||
|
||||
error_code_t(int value) noexcept
|
||||
: value_(value) { }
|
||||
constexpr error_code_t(int value) noexcept
|
||||
: value_{ value }
|
||||
{ }
|
||||
|
||||
template<
|
||||
class EnumT,
|
||||
std::enable_if_t<std::is_error_code_enum_v<EnumT>, int> = 0
|
||||
>
|
||||
error_code_t(EnumT value) noexcept
|
||||
: value_((int)value) { }
|
||||
constexpr error_code_t(EnumT value) noexcept
|
||||
: value_{ (int)value }
|
||||
{ }
|
||||
|
||||
template<
|
||||
class EnumT,
|
||||
std::enable_if_t<std::is_error_code_enum_v<EnumT>, int> = 0
|
||||
>
|
||||
error_code_t& operator=(EnumT value) noexcept
|
||||
constexpr error_code_t& operator=(EnumT value) noexcept
|
||||
{ value_ = (int)value; return *this; }
|
||||
|
||||
void assign(int value) noexcept
|
||||
constexpr void assign(int value) noexcept
|
||||
{ value_ = value; }
|
||||
|
||||
void clear() noexcept
|
||||
constexpr void clear() noexcept
|
||||
{ value_ = 0; }
|
||||
|
||||
int value() const noexcept
|
||||
constexpr int value() const noexcept
|
||||
{ return value_; }
|
||||
|
||||
explicit operator bool() const noexcept
|
||||
constexpr explicit operator bool() const noexcept
|
||||
{ return value() != 0; }
|
||||
|
||||
private:
|
||||
int value_;
|
||||
};
|
||||
|
||||
inline error_code_t make_error_code_t(std::errc value) noexcept
|
||||
constexpr inline error_code_t make_error_code_t(std::errc value) noexcept
|
||||
{ return error_code_t((int)value); }
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ enum class ioctl_access : uint32_t
|
|||
read_write = read | write
|
||||
};
|
||||
|
||||
inline constexpr auto
|
||||
constexpr inline auto
|
||||
make_ioctl_code_windows(
|
||||
uint32_t id,
|
||||
ioctl_access access,
|
||||
|
|
@ -61,7 +61,7 @@ make_ioctl_code_windows(
|
|||
uint32_t(access));
|
||||
}
|
||||
|
||||
inline constexpr auto
|
||||
constexpr inline auto
|
||||
make_ioctl_code_linux(
|
||||
uint32_t id,
|
||||
ioctl_access access,
|
||||
|
|
@ -86,7 +86,7 @@ make_ioctl_code_linux(
|
|||
return ctl_code_impl(uint32_t(access), 'H', id, size);
|
||||
}
|
||||
|
||||
inline constexpr auto
|
||||
constexpr inline auto
|
||||
make_ioctl_code(
|
||||
uint32_t id,
|
||||
ioctl_access access,
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@
|
|||
// 4096 bytes.
|
||||
//
|
||||
|
||||
namespace memory_manager
|
||||
namespace mm
|
||||
{
|
||||
using pgbmp_t = object_t<bitmap>;
|
||||
using pgmap_t = uint16_t;
|
||||
|
|
@ -196,7 +196,7 @@ namespace memory_manager
|
|||
//
|
||||
if (ia32::byte_offset(address) != 0)
|
||||
{
|
||||
uint32_t lost_bytes = ia32::byte_offset(address);
|
||||
const auto lost_bytes = ia32::byte_offset(address);
|
||||
|
||||
address = reinterpret_cast<uint8_t*>(ia32::page_align(address)) + ia32::page_size;
|
||||
|
||||
|
|
@ -285,7 +285,7 @@ namespace memory_manager
|
|||
// This should help with debugging uninitialized variables
|
||||
// and class members.
|
||||
//
|
||||
int reserved_bytes = static_cast<int>(global.page_bitmap_buffer_size + global.page_allocation_map_size);
|
||||
const auto reserved_bytes = static_cast<int>(global.page_bitmap_buffer_size + global.page_allocation_map_size);
|
||||
memset(global.base_address + reserved_bytes, 0xcc, size - reserved_bytes);
|
||||
|
||||
//
|
||||
|
|
@ -370,7 +370,7 @@ namespace memory_manager
|
|||
//
|
||||
hvpp_assert(ia32::byte_offset(address) == 0);
|
||||
|
||||
int offset = static_cast<int>(ia32::bytes_to_pages(reinterpret_cast<uint8_t*>(address) - global.base_address));
|
||||
const auto offset = static_cast<int>(ia32::bytes_to_pages(reinterpret_cast<uint8_t*>(address) - global.base_address));
|
||||
|
||||
if (address == nullptr)
|
||||
{
|
||||
|
|
@ -404,7 +404,7 @@ namespace memory_manager
|
|||
//
|
||||
// Clear number of allocated pages.
|
||||
//
|
||||
int page_count = global.page_allocation_map[offset];
|
||||
const auto page_count = static_cast<int>(global.page_allocation_map[offset]);
|
||||
global.page_allocation_map[offset] = 0;
|
||||
|
||||
//
|
||||
|
|
@ -461,17 +461,17 @@ namespace detail
|
|||
{
|
||||
void generic_free(void* address) noexcept
|
||||
{
|
||||
reinterpret_cast<uint8_t*>(address) >= memory_manager::global.base_address &&
|
||||
reinterpret_cast<uint8_t*>(address) < memory_manager::global.base_address + memory_manager::global.available_size
|
||||
? memory_manager::free (address)
|
||||
: memory_manager::system_free(address);
|
||||
reinterpret_cast<uint8_t*>(address) >= mm::global.base_address &&
|
||||
reinterpret_cast<uint8_t*>(address) < mm::global.base_address + mm::global.available_size
|
||||
? mm::free (address)
|
||||
: mm::system_free(address);
|
||||
}
|
||||
}
|
||||
|
||||
void* operator new (size_t size) { return memory_manager::global.allocator[mp::cpu_index()].allocate(size); }
|
||||
void* operator new[](size_t size) { return memory_manager::global.allocator[mp::cpu_index()].allocate(size); }
|
||||
void* operator new (size_t size, std::align_val_t) { return memory_manager::global.allocator[mp::cpu_index()].allocate(size); }
|
||||
void* operator new[](size_t size, std::align_val_t) { return memory_manager::global.allocator[mp::cpu_index()].allocate(size); }
|
||||
void* operator new (size_t size) { return mm::global.allocator[mp::cpu_index()].allocate(size); }
|
||||
void* operator new[](size_t size) { return mm::global.allocator[mp::cpu_index()].allocate(size); }
|
||||
void* operator new (size_t size, std::align_val_t) { return mm::global.allocator[mp::cpu_index()].allocate(size); }
|
||||
void* operator new[](size_t size, std::align_val_t) { return mm::global.allocator[mp::cpu_index()].allocate(size); }
|
||||
|
||||
void operator delete (void* address) { detail::generic_free(address); }
|
||||
void operator delete[](void* address) { detail::generic_free(address); }
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
#include <cstdint>
|
||||
|
||||
namespace memory_manager
|
||||
namespace mm
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
|
|
|
|||
|
|
@ -37,8 +37,4 @@ namespace mp
|
|||
template <typename T>
|
||||
inline void ipi_call(T function) noexcept
|
||||
{ ipi_call([](void* context) noexcept { ((T*)context)->operator()(); }, &function); }
|
||||
|
||||
template <typename T>
|
||||
inline void ipi_call(T* instance, void (T::*member_function)()) noexcept
|
||||
{ ipi_call([=]() { (instance->*member_function)(); }); }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ template <
|
|||
>
|
||||
void for_each_element(std::tuple<ARGS...>& t, F&& f, std::index_sequence<INDEX...>)
|
||||
{
|
||||
int unused[] = { 0, (f(std::get<INDEX>(t), INDEX), void(), 0)... };
|
||||
const int unused[] = { 0, (f(std::get<INDEX>(t), INDEX), void(), 0)... };
|
||||
(void)(unused);
|
||||
}
|
||||
|
||||
|
|
@ -73,7 +73,7 @@ template <
|
|||
>
|
||||
void for_each_element(const std::tuple<ARGS...>& t, F&& f, std::index_sequence<INDEX...>)
|
||||
{
|
||||
int unused[] = { 0, (f(std::get<INDEX>(t), INDEX), void(), 0)... };
|
||||
const int unused[] = { 0, (f(std::get<INDEX>(t), INDEX), void(), 0)... };
|
||||
(void)(unused);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ try_decode_io_instruction(
|
|||
int size_of_access;
|
||||
bool rep_prefixed;
|
||||
|
||||
const uint8_t* rip = reinterpret_cast<const uint8_t*>(ctx.rip);
|
||||
const auto rip = reinterpret_cast<const uint8_t*>(ctx.rip);
|
||||
if (!try_decode_io_instruction(rip, access_type, size_of_access, rep_prefixed))
|
||||
{
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ ia32::cr3_t kernel_cr3(ia32::cr3_t cr3) noexcept
|
|||
hvpp_assert(cr3.pcid == PCID_USER);
|
||||
#endif
|
||||
|
||||
auto kprocess = reinterpret_cast<NT_KPROCESS*>(PsGetCurrentProcess());
|
||||
const auto kprocess = reinterpret_cast<NT_KPROCESS*>(PsGetCurrentProcess());
|
||||
return ia32::cr3_t{ kprocess->DirectoryTableBase };
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
//
|
||||
// Definition is located in win32/driver.cpp.
|
||||
//
|
||||
extern PDRIVER_OBJECT GlobalDriverObject;
|
||||
EXTERN_C PDRIVER_OBJECT GlobalDriverObject;
|
||||
|
||||
#define HVPP_DEVICE_TAG 'vdvh'
|
||||
#define MAX_BUFFER_SIZE 64
|
||||
|
|
@ -21,7 +21,7 @@ typedef struct _DEVICE_IMPL
|
|||
WCHAR DeviceLinkBuffer[MAX_BUFFER_SIZE + sizeof(L"\\DosDevices\\") - 1];
|
||||
} DEVICE_IMPL, *PDEVICE_IMPL;
|
||||
|
||||
auto device::initialize() noexcept -> error_code_t
|
||||
auto device::create() noexcept -> error_code_t
|
||||
{
|
||||
error_code_t err;
|
||||
|
||||
|
|
@ -153,9 +153,11 @@ void device::destroy() noexcept
|
|||
{
|
||||
IoDeleteSymbolicLink(&DeviceImpl->DeviceLink);
|
||||
IoDeleteDevice(DeviceImpl->DeviceObject);
|
||||
}
|
||||
|
||||
ExFreePoolWithTag(DeviceImpl, HVPP_DEVICE_TAG);
|
||||
ExFreePoolWithTag(DeviceImpl, HVPP_DEVICE_TAG);
|
||||
|
||||
impl_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
error_code_t device::copy_from_user(void* buffer_to, const void* buffer_from, size_t length) noexcept
|
||||
|
|
|
|||
|
|
@ -20,9 +20,11 @@
|
|||
//
|
||||
#define ACCESS_FROM_CTL_CODE(ctrlCode) (((ULONG)(ctrlCode & 0x0000c000)) >> 14)
|
||||
|
||||
EXTERN_C DRIVER_INITIALIZE DriverEntry;
|
||||
|
||||
PDRIVER_OBJECT GlobalDriverObject = nullptr;
|
||||
extern "C"
|
||||
{
|
||||
DRIVER_INITIALIZE DriverEntry;
|
||||
PDRIVER_OBJECT GlobalDriverObject = nullptr;
|
||||
}
|
||||
|
||||
namespace driver
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4,8 +4,6 @@
|
|||
|
||||
#include "../mp.h"
|
||||
|
||||
#include <algorithm> // std::size
|
||||
|
||||
#include <ntddk.h>
|
||||
|
||||
EXTERN_C
|
||||
|
|
@ -28,7 +26,7 @@ namespace logger::detail
|
|||
level == level_t::error ? "ERR\t" :
|
||||
"###\t";
|
||||
|
||||
strcpy_s(buffer, SIZE, level_string);
|
||||
strcpy_s(buffer, level_string);
|
||||
}
|
||||
|
||||
template <size_t SIZE>
|
||||
|
|
@ -49,7 +47,7 @@ namespace logger::detail
|
|||
TIME_FIELDS time_fields;
|
||||
RtlTimeToTimeFields(&local_time, &time_fields);
|
||||
|
||||
sprintf_s(buffer, SIZE, "%02hd:%02hd:%02hd.%03hd\t",
|
||||
sprintf_s(buffer, "%02hd:%02hd:%02hd.%03hd\t",
|
||||
time_fields.Hour, time_fields.Minute,
|
||||
time_fields.Second, time_fields.Milliseconds);
|
||||
}
|
||||
|
|
@ -63,7 +61,7 @@ namespace logger::detail
|
|||
return;
|
||||
}
|
||||
|
||||
sprintf_s(buffer, SIZE, "#%u\t", mp::cpu_index());
|
||||
sprintf_s(buffer, "#%u\t", mp::cpu_index());
|
||||
}
|
||||
|
||||
template <size_t SIZE>
|
||||
|
|
@ -75,13 +73,13 @@ namespace logger::detail
|
|||
return;
|
||||
}
|
||||
|
||||
sprintf_s(buffer, SIZE, "%-40s\t", function);
|
||||
sprintf_s(buffer, "%-40s\t", function);
|
||||
}
|
||||
|
||||
template <size_t SIZE>
|
||||
void make_log_message(char(&buffer)[SIZE], const char* format, va_list args) noexcept
|
||||
{
|
||||
vsprintf_s(buffer, SIZE, format, args);
|
||||
vsprintf_s(buffer, format, args);
|
||||
}
|
||||
|
||||
void do_print(const char* message) noexcept
|
||||
|
|
@ -114,7 +112,7 @@ namespace logger::detail
|
|||
auto thread_id = static_cast<uint32_t>(reinterpret_cast<uintptr_t>(PsGetCurrentThreadId()));
|
||||
auto process_name = PsGetProcessImageFileName(PsGetCurrentProcess());
|
||||
|
||||
sprintf_s(buffer, std::size(buffer), "%s%s%s%5u\t%5u\t%-15s\t%s%s\r\n",
|
||||
sprintf_s(buffer, "%s%s%s%5u\t%5u\t%-15s\t%s%s\r\n",
|
||||
time, level_string, processor_number,
|
||||
process_id, thread_id, process_name,
|
||||
function_name, log_message);
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
#define HVPP_MEMORY_TAG 'ppvh'
|
||||
|
||||
namespace memory_manager::detail
|
||||
namespace mm::detail
|
||||
{
|
||||
auto system_allocate(size_t size) noexcept -> void*
|
||||
{
|
||||
|
|
|
|||
|
|
@ -15,7 +15,48 @@ namespace hvpp {
|
|||
// Public
|
||||
//
|
||||
|
||||
auto vcpu_t::initialize(vmexit_handler& handler) noexcept -> error_code_t
|
||||
vcpu_t::vcpu_t(vmexit_handler& handler) noexcept
|
||||
//
|
||||
// Initialize VMXON region and VMCS.
|
||||
//
|
||||
: vmxon_{}
|
||||
, vmcs_{}
|
||||
|
||||
//
|
||||
// This is not really needed.
|
||||
// MSR bitmaps and I/O bitmaps are actually copied here from
|
||||
// user-provided buffers (via msr_bitmap() and io_bitmap() methods)
|
||||
// before they are enabled.
|
||||
//
|
||||
// , msr_bitmap_{}
|
||||
// , io_bitmap_{}
|
||||
|
||||
, handler_ { handler }
|
||||
|
||||
//
|
||||
// Signalize that this VCPU is turned off.
|
||||
//
|
||||
, state_{ state::off }
|
||||
|
||||
//
|
||||
// Let EPT be uninitialized.
|
||||
// VM-exit handler is responsible for EPT setup.
|
||||
//
|
||||
, ept_{ nullptr }
|
||||
, ept_count_{ 0 }
|
||||
, ept_index_{ 0 }
|
||||
|
||||
//
|
||||
// Initialize pending-interrupt FIFO queue.
|
||||
//
|
||||
, pending_interrupt_first_{ 0 }
|
||||
, pending_interrupt_count_{ 0 }
|
||||
|
||||
//
|
||||
// Well, this is also not necessary.
|
||||
// This member is reset to "false" on each VM-exit in entry_host() method.
|
||||
//
|
||||
, suppress_rip_adjust_{ false }
|
||||
{
|
||||
//
|
||||
// Fill out initial stack with garbage.
|
||||
|
|
@ -31,52 +72,6 @@ auto vcpu_t::initialize(vmexit_handler& handler) noexcept -> error_code_t
|
|||
guest_context_.clear();
|
||||
exit_context_.clear();
|
||||
|
||||
//
|
||||
// Signalize that this VCPU is turned off.
|
||||
//
|
||||
state_ = vcpu_state::off;
|
||||
|
||||
//
|
||||
// Initialize VM-exit handler.
|
||||
//
|
||||
handler_ = &handler;
|
||||
|
||||
//
|
||||
// Initialize VMXON region and VMCS.
|
||||
//
|
||||
memset(&vmxon_, 0, sizeof(vmxon_));
|
||||
memset(&vmcs_, 0, sizeof(vmcs_));
|
||||
|
||||
//
|
||||
// Let EPT be uninitialized.
|
||||
// VM-exit handler is responsible for EPT setup.
|
||||
//
|
||||
ept_ = nullptr;
|
||||
ept_count_ = 0;
|
||||
ept_index_ = 0;
|
||||
|
||||
//
|
||||
// This is not really needed.
|
||||
// MSR bitmaps and I/O bitmaps are actually copied here from
|
||||
// user-provided buffers (via msr_bitmap() and io_bitmap() methods)
|
||||
// before they are enabled.
|
||||
//
|
||||
// memset(&msr_bitmap_, 0, sizeof(msr_bitmap_));
|
||||
// memset(&io_bitmap_, 0, sizeof(io_bitmap_));
|
||||
//
|
||||
|
||||
//
|
||||
// Initialize pending-interrupt FIFO queue.
|
||||
//
|
||||
pending_interrupt_first_ = 0;
|
||||
pending_interrupt_count_ = 0;
|
||||
|
||||
//
|
||||
// Well, this is also not necessary.
|
||||
// This member is reset to "false" on each VM-exit in entry_host() method.
|
||||
//
|
||||
suppress_rip_adjust_ = false;
|
||||
|
||||
//
|
||||
// Assertions.
|
||||
//
|
||||
|
|
@ -97,24 +92,108 @@ auto vcpu_t::initialize(vmexit_handler& handler) noexcept -> error_code_t
|
|||
static_assert(VCPU_RSP + VCPU_LAUNCH_CONTEXT_OFFSET == offsetof(vcpu_t, guest_context_));
|
||||
static_assert(VCPU_RSP + VCPU_EXIT_CONTEXT_OFFSET == offsetof(vcpu_t, exit_context_));
|
||||
};
|
||||
|
||||
return error_code_t{};
|
||||
}
|
||||
|
||||
void vcpu_t::destroy() noexcept
|
||||
vcpu_t::~vcpu_t() noexcept
|
||||
{
|
||||
//
|
||||
// When destructor is called, we should be only in one of the states
|
||||
// metioned in the "assert".
|
||||
//
|
||||
// We can't be in:
|
||||
// - "initializing", because "initializing" goes directly
|
||||
// to "running" (on success) or "terminated" (on error)
|
||||
// - "launching", because (as said above), "launching" goes
|
||||
// directly to "running"
|
||||
// - "terminating", because "terminating" goes directly to "terminated"
|
||||
//
|
||||
hvpp_assert(state_ == state::off ||
|
||||
state_ == state::running ||
|
||||
state_ == state::terminated);
|
||||
|
||||
if (state_ == state::running)
|
||||
{
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
||||
auto vcpu_t::start() noexcept -> error_code_t
|
||||
{
|
||||
//
|
||||
// Launch of the VCPU is performed via similar principle as setjmp/longjmp:
|
||||
// - Save current state here (guest_context_.capture() returns 0 if it's
|
||||
// been called by original code - which is the same as state::off).
|
||||
// - Call vcpu_t::vmx_enter(), which will enter VMX operation and set up VCMS.
|
||||
// - Launch the VM.
|
||||
// Note that vmlaunch() function should NOT return - the next instruction
|
||||
// after vmlaunch should be at vcpu_t::entry_guest_() (vcpu.asm).
|
||||
// - The guest will set guest_context_.rax = state::launching (see entry_guest())
|
||||
// and perform guest_context_.restore() (see vcpu.asm).
|
||||
// That will catapult us back here.
|
||||
// - We'll set state to state::running and exit this function.
|
||||
//
|
||||
|
||||
switch (static_cast<state>(guest_context_.capture()))
|
||||
{
|
||||
case state::off:
|
||||
if (auto err = vmx_enter())
|
||||
{
|
||||
//
|
||||
// There was either error with enabling VMX, setting up VMCS,
|
||||
// or calling vmexit_handler::setup().
|
||||
//
|
||||
return handle_vmx_enter_error(err);
|
||||
}
|
||||
|
||||
//
|
||||
// Launch the VM (i.e.: execute "vmlaunch" instruction).
|
||||
// If succeeded, this function does NOT return.
|
||||
//
|
||||
vmx::vmlaunch();
|
||||
|
||||
//
|
||||
// If we got here, it means the "vmlaunch" failed.
|
||||
//
|
||||
return handle_vmx_launch_error();
|
||||
|
||||
case state::launching:
|
||||
//
|
||||
// The vcpu_t::entry_guest() successfully put this VCPU into
|
||||
// "launching" state and vcpu.asm called guest_context_.restore().
|
||||
// This means that guest is running properly.
|
||||
//
|
||||
state_ = state::running;
|
||||
return error_code_t{};
|
||||
|
||||
default:
|
||||
//
|
||||
// We shouldn't get here.
|
||||
//
|
||||
hvpp_assert(0);
|
||||
return make_error_code_t(std::errc::permission_denied);
|
||||
}
|
||||
}
|
||||
|
||||
void vcpu_t::stop() noexcept
|
||||
{
|
||||
//
|
||||
// Calling this method on any other state than "running" is considered
|
||||
// error.
|
||||
//
|
||||
hvpp_assert(state_ == state::running);
|
||||
|
||||
//
|
||||
// Signalize that this VCPU is terminating.
|
||||
//
|
||||
state_ = vcpu_state::terminating;
|
||||
state_ = state::terminating;
|
||||
|
||||
//
|
||||
// Notify the exit handler that we're about to terminate.
|
||||
// Exit handler should invoke VMEXIT in such way, that causes
|
||||
// handler to call vcpu_t::terminate(); e.g. VMCALL with specific
|
||||
// handler to call vcpu_t::vmx_leave(); e.g. VMCALL with specific
|
||||
// index.
|
||||
//
|
||||
handler_->invoke_termination(*this);
|
||||
handler_.teardown(*this);
|
||||
|
||||
//
|
||||
// Destroy EPT.
|
||||
|
|
@ -122,69 +201,82 @@ void vcpu_t::destroy() noexcept
|
|||
ept_disable();
|
||||
}
|
||||
|
||||
void vcpu_t::launch() noexcept
|
||||
auto vcpu_t::vmx_enter() noexcept -> error_code_t
|
||||
{
|
||||
hvpp_assert(handler_ != nullptr);
|
||||
|
||||
//
|
||||
// Launch of the VCPU is performed via similar principle as setjmp/longjmp:
|
||||
// - Save current state here (guest_context_.capture() returns 0 if it's
|
||||
// been called by original code - which is the same as vcpu_state::off).
|
||||
// - Call setup(), which enters VMX operation, sets up VCMS and launches
|
||||
// the VM.
|
||||
// - The guest sets guest_context_.rax = vcpu_state::launching (see entry_guest())
|
||||
// and perform guest_context_.restore() (see vcpu.asm).
|
||||
// That will catapult us back here.
|
||||
// - We'll set state to vcpu_state::running and exit this function.
|
||||
// Enter VMX operation, invalidate EPT and VPID, load VMCS,
|
||||
// set VMCS fields and call handler's setup() method.
|
||||
//
|
||||
|
||||
switch (static_cast<vcpu_state>(guest_context_.capture()))
|
||||
{
|
||||
case vcpu_state::off:
|
||||
setup();
|
||||
break;
|
||||
if (auto err = load_vmxon())
|
||||
{ return err; }
|
||||
|
||||
case vcpu_state::launching:
|
||||
state_ = vcpu_state::running;
|
||||
break;
|
||||
if (auto err = load_vmcs())
|
||||
{ return err; }
|
||||
|
||||
default:
|
||||
hvpp_assert(0);
|
||||
break;
|
||||
}
|
||||
if (auto err = setup_host())
|
||||
{ return err; }
|
||||
|
||||
if (auto err = setup_guest())
|
||||
{ return err; }
|
||||
|
||||
//
|
||||
// #TODO: This function can fail, make it
|
||||
// return appropriate error_code_t.
|
||||
//
|
||||
|
||||
handler_.setup(*this);
|
||||
|
||||
return error_code_t{};
|
||||
}
|
||||
|
||||
void vcpu_t::terminate() noexcept
|
||||
void vcpu_t::vmx_leave() noexcept
|
||||
{
|
||||
hvpp_assert(state_ != vcpu_state::off && state_ != vcpu_state::terminated);
|
||||
//
|
||||
// This method must be called either:
|
||||
// - when initialization fails
|
||||
// - when VCPU is terminating
|
||||
//
|
||||
hvpp_assert(state_ == state::initializing ||
|
||||
state_ == state::terminating);
|
||||
|
||||
//
|
||||
// Advance RIP before we exit VMX-root mode. This skips the "vmcall"
|
||||
// instruction.
|
||||
// If vmx_leave() is called in the initialization phase,
|
||||
// we don't have to fix-up GDTR/IDTR/CR3, because:
|
||||
// - no VM-exit occured yet
|
||||
// - guest_gdtr/guest_idtr/guest_cr3 may still be uninitialized
|
||||
//
|
||||
exit_context_.rip += exit_instruction_length();
|
||||
|
||||
//
|
||||
// When running in VMX-root mode, the processor will set limits of the
|
||||
// GDT and IDT to 0xffff (notice that there are no Host VMCS fields to
|
||||
// set these values). This causes problems with PatchGuard, which will
|
||||
// believe that the GDTR and IDTR have been modified by malware, and
|
||||
// eventually crash the system. Since we know what the original state
|
||||
// of the GDTR and IDTR was, simply restore it now.
|
||||
//
|
||||
write<gdtr_t>(guest_gdtr());
|
||||
write<idtr_t>(guest_idtr());
|
||||
if (state_ != state::initializing)
|
||||
{
|
||||
//
|
||||
// Advance RIP before we exit VMX-root mode. This skips the "vmcall"
|
||||
// instruction.
|
||||
//
|
||||
exit_context_.rip += exit_instruction_length();
|
||||
|
||||
//
|
||||
// Our callback routine may have interrupted an arbitrary user process,
|
||||
// and therefore not a thread running with a systemwide page directory.
|
||||
// Therefore if we return back to the original caller after turning off
|
||||
// VMX, it will keep our current "host" CR3 value which we set on entry
|
||||
// to the PML4 of the SYSTEM process. We want to return back with the
|
||||
// correct value of the "guest" CR3, so that the currently executing
|
||||
// process continues to run with its expected address space mappings.
|
||||
//
|
||||
write<cr3_t>(guest_cr3());
|
||||
//
|
||||
// When running in VMX-root mode, the processor will set limits of the
|
||||
// GDT and IDT to 0xffff (notice that there are no Host VMCS fields to
|
||||
// set these values). This causes problems with PatchGuard, which will
|
||||
// believe that the GDTR and IDTR have been modified by malware, and
|
||||
// eventually crash the system. Since we know what the original state
|
||||
// of the GDTR and IDTR was, simply restore it now.
|
||||
//
|
||||
write<gdtr_t>(guest_gdtr());
|
||||
write<idtr_t>(guest_idtr());
|
||||
|
||||
//
|
||||
// Our callback routine may have interrupted an arbitrary user process,
|
||||
// and therefore not a thread running with a systemwide page directory.
|
||||
// Therefore if we return back to the original caller after turning off
|
||||
// VMX, it will keep our current "host" CR3 value which we set on entry
|
||||
// to the PML4 of the SYSTEM process. We want to return back with the
|
||||
// correct value of the "guest" CR3, so that the currently executing
|
||||
// process continues to run with its expected address space mappings.
|
||||
//
|
||||
write<cr3_t>(guest_cr3());
|
||||
}
|
||||
|
||||
//
|
||||
// Software can use the INVVPID instruction with the "all-context"
|
||||
|
|
@ -226,7 +318,7 @@ void vcpu_t::terminate() noexcept
|
|||
//
|
||||
// Signalize that this VCPU has terminated.
|
||||
//
|
||||
state_ = vcpu_state::terminated;
|
||||
state_ = state::terminated;
|
||||
}
|
||||
|
||||
void vcpu_t::ept_enable(uint16_t count /* = 1 */) noexcept
|
||||
|
|
@ -238,11 +330,7 @@ void vcpu_t::ept_enable(uint16_t count /* = 1 */) noexcept
|
|||
//
|
||||
ept_ = new ept_t[count];
|
||||
ept_count_ = count;
|
||||
|
||||
for (uint16_t i = 0; i < count; i += 1)
|
||||
{
|
||||
ept_[i].initialize();
|
||||
}
|
||||
hvpp_assert(ept_ != nullptr);
|
||||
|
||||
//
|
||||
// Enable EPT.
|
||||
|
|
@ -264,26 +352,21 @@ void vcpu_t::ept_disable() noexcept
|
|||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Destroy EPT.
|
||||
//
|
||||
for (uint16_t i = 0; i < ept_count_; i++)
|
||||
{
|
||||
ept_[i].destroy();
|
||||
}
|
||||
|
||||
delete[] ept_;
|
||||
ept_ = nullptr;
|
||||
|
||||
//
|
||||
// Disable EPT functionality.
|
||||
//
|
||||
if (state_ != state::terminated)
|
||||
{
|
||||
auto procbased_ctls2 = processor_based_controls2();
|
||||
procbased_ctls2.enable_ept = false;
|
||||
processor_based_controls2(procbased_ctls2);
|
||||
}
|
||||
|
||||
//
|
||||
// #TODO: VMX is already disabled when we're here.
|
||||
// Destroy EPT.
|
||||
//
|
||||
// auto procbased_ctls2 = processor_based_controls2();
|
||||
// procbased_ctls2.enable_ept = false;
|
||||
// processor_based_controls2(procbased_ctls2);
|
||||
delete[] ept_;
|
||||
ept_ = nullptr;
|
||||
}
|
||||
|
||||
auto vcpu_t::ept_index() noexcept -> uint16_t
|
||||
|
|
@ -320,40 +403,47 @@ void vcpu_t::suppress_rip_adjust() noexcept
|
|||
// Private
|
||||
//
|
||||
|
||||
void vcpu_t::error() noexcept
|
||||
auto vcpu_t::handle_common_error(error_code_t err) noexcept -> error_code_t
|
||||
{
|
||||
vmx::instruction_error instruction_error = exit_instruction_error();
|
||||
hvpp_error("error: %p (%s)\n", instruction_error, vmx::instruction_error_to_string(instruction_error));
|
||||
ia32_asm_int3();
|
||||
terminate();
|
||||
//
|
||||
// Signalize that this VCPU is terminated and leave the VMX operation.
|
||||
//
|
||||
state_ = state::terminated;
|
||||
vmx_leave();
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
void vcpu_t::setup() noexcept
|
||||
auto vcpu_t::handle_vmx_enter_error(error_code_t err) noexcept -> error_code_t
|
||||
{
|
||||
//
|
||||
// Enter VMX operation, invalidate EPT and VPID, load VMCS,
|
||||
// set VMCS fields, call handler's setup() method, and launch
|
||||
// the VM. This function should NOT return - the next instruction
|
||||
// after vmlaunch should be at vcpu_t::entry_guest_ (vcpu.asm).
|
||||
//
|
||||
load_vmxon();
|
||||
load_vmcs();
|
||||
|
||||
setup_host();
|
||||
setup_guest();
|
||||
|
||||
handler_->setup(*this);
|
||||
|
||||
vmx::vmlaunch();
|
||||
|
||||
//
|
||||
// If we got here, something wrong has happened.
|
||||
//
|
||||
error();
|
||||
return handle_common_error(err);
|
||||
}
|
||||
|
||||
void vcpu_t::load_vmxon() noexcept
|
||||
auto vcpu_t::handle_vmx_launch_error() noexcept -> error_code_t
|
||||
{
|
||||
//
|
||||
// Fetch VMX error from the VMCS and print it to the debugger.
|
||||
//
|
||||
const auto instruction_error = exit_instruction_error();
|
||||
hvpp_error("error: %u (%s)\n",
|
||||
static_cast<uint32_t>(instruction_error),
|
||||
vmx::instruction_error_to_string(instruction_error));
|
||||
|
||||
//
|
||||
// If debugger is attached, break into it.
|
||||
//
|
||||
if (debugger::is_enabled())
|
||||
{
|
||||
debugger::breakpoint();
|
||||
}
|
||||
|
||||
return handle_common_error(make_error_code_t(std::errc::permission_denied));
|
||||
}
|
||||
|
||||
auto vcpu_t::load_vmxon() noexcept -> error_code_t
|
||||
{
|
||||
hvpp_assert(state_ == state::off);
|
||||
|
||||
//
|
||||
// In VMX operation, processors may fix certain bits in CR0 and CR4
|
||||
// to specific values and not support other values. VMXON fails if
|
||||
|
|
@ -371,71 +461,68 @@ void vcpu_t::load_vmxon() noexcept
|
|||
// write the VMCS revision identifier to the VMXON region.
|
||||
// (ref: Vol3C[24.11.5(VMXON Region)])
|
||||
//
|
||||
auto vmx_basic = msr::read<msr::vmx_basic_t>();
|
||||
const auto vmx_basic = msr::read<msr::vmx_basic_t>();
|
||||
vmxon_.revision_id = vmx_basic.vmcs_revision_id;
|
||||
|
||||
//
|
||||
// Enter VMX operation.
|
||||
//
|
||||
|
||||
if (vmx::on(pa_t::from_va(&vmxon_)) == vmx::error_code::success)
|
||||
if (vmx::on(pa_t::from_va(&vmxon_)) != vmx::error_code::success)
|
||||
{
|
||||
state_ = vcpu_state::initializing;
|
||||
|
||||
//
|
||||
// Software can use the INVVPID instruction with the "all-context"
|
||||
// INVVPID type immediately after execution of the VMXON instruction
|
||||
// or immediately prior to execution of the VMXOFF instruction.
|
||||
// Either prevents potentially undesired retention of information
|
||||
// cached from paging structures between separate uses of VMX operation.
|
||||
// (ref: Vol3C[28.3.3.3(Guidelines for Use of the INVVPID Instruction)])
|
||||
//
|
||||
vmx::invvpid_all_contexts();
|
||||
|
||||
//
|
||||
// Software can use the INVEPT instruction with the "all-context"
|
||||
// INVEPT type immediately after execution of the VMXON instruction
|
||||
// or immediately prior to execution of the VMXOFF instruction.
|
||||
// Either prevents potentially undesired retention of information
|
||||
// cached from EPT paging structures between separate uses of VMX operation.
|
||||
// (ref: Vol3C[28.3.3.4(Guidelines for Use of the INVEPT Instruction)])
|
||||
//
|
||||
vmx::invept_all_contexts();
|
||||
}
|
||||
else
|
||||
{
|
||||
state_ = vcpu_state::terminated;
|
||||
error();
|
||||
return make_error_code_t(std::errc::permission_denied);
|
||||
}
|
||||
|
||||
state_ = state::initializing;
|
||||
|
||||
//
|
||||
// Software can use the INVVPID instruction with the "all-context"
|
||||
// INVVPID type immediately after execution of the VMXON instruction
|
||||
// or immediately prior to execution of the VMXOFF instruction.
|
||||
// Either prevents potentially undesired retention of information
|
||||
// cached from paging structures between separate uses of VMX operation.
|
||||
// (ref: Vol3C[28.3.3.3(Guidelines for Use of the INVVPID Instruction)])
|
||||
//
|
||||
vmx::invvpid_all_contexts();
|
||||
|
||||
//
|
||||
// Software can use the INVEPT instruction with the "all-context"
|
||||
// INVEPT type immediately after execution of the VMXON instruction
|
||||
// or immediately prior to execution of the VMXOFF instruction.
|
||||
// Either prevents potentially undesired retention of information
|
||||
// cached from EPT paging structures between separate uses of VMX operation.
|
||||
// (ref: Vol3C[28.3.3.4(Guidelines for Use of the INVEPT Instruction)])
|
||||
//
|
||||
vmx::invept_all_contexts();
|
||||
|
||||
return error_code_t{};
|
||||
}
|
||||
|
||||
void vcpu_t::load_vmcs() noexcept
|
||||
auto vcpu_t::load_vmcs() noexcept -> error_code_t
|
||||
{
|
||||
hvpp_assert(state_ == vcpu_state::initializing);
|
||||
hvpp_assert(state_ == state::initializing);
|
||||
|
||||
auto vmx_basic = msr::read<msr::vmx_basic_t>();
|
||||
const auto vmx_basic = msr::read<msr::vmx_basic_t>();
|
||||
vmcs_.revision_id = vmx_basic.vmcs_revision_id;
|
||||
|
||||
//
|
||||
// Set VMCS to "clear" state and make the VMCS active.
|
||||
// See Vol3C[24(Virtual Machine Control Structures)] for more information.
|
||||
//
|
||||
if (vmx::vmclear(pa_t::from_va(&vmcs_)) != vmx::error_code::success ||
|
||||
vmx::vmptrld(pa_t::from_va(&vmcs_)) != vmx::error_code::success)
|
||||
{
|
||||
return make_error_code_t(std::errc::permission_denied);
|
||||
}
|
||||
|
||||
if (vmx::vmclear(pa_t::from_va(&vmcs_)) == vmx::error_code::success &&
|
||||
vmx::vmptrld(pa_t::from_va(&vmcs_)) == vmx::error_code::success)
|
||||
{
|
||||
/* NOTHING */;
|
||||
}
|
||||
else
|
||||
{
|
||||
error();
|
||||
}
|
||||
return error_code_t{};
|
||||
}
|
||||
|
||||
void vcpu_t::setup_host() noexcept
|
||||
auto vcpu_t::setup_host() noexcept -> error_code_t
|
||||
{
|
||||
hvpp_assert(state_ == state::initializing);
|
||||
|
||||
//
|
||||
// Sets up state of the CPU each time when VM-exit is triggered.
|
||||
// Sets up what state will the CPU have when VM-exit is triggered.
|
||||
// Notice how these fields mainly consist of descriptor registers,
|
||||
// control registers, and segment registers. This effectively allows us
|
||||
// to run hypervisor in completely separate address space from the OS.
|
||||
|
|
@ -449,14 +536,15 @@ void vcpu_t::setup_host() noexcept
|
|||
// (RAX, RBX, ...) or SSE registers - these registers are preserved from the
|
||||
// guest.
|
||||
//
|
||||
auto gdtr = read<gdtr_t>();
|
||||
const auto gdtr = read<gdtr_t>();
|
||||
const auto idtr = read<idtr_t>();
|
||||
|
||||
//
|
||||
// Note that we're setting just base address of GDTR and IDTR.
|
||||
// The limit of these descriptors is fixed at 0xffff for VMX operations.
|
||||
//
|
||||
host_gdtr(gdtr);
|
||||
host_idtr(read<idtr_t>());
|
||||
host_idtr(idtr);
|
||||
|
||||
//
|
||||
// Note that we're setting just selectors (base address - except for FS and
|
||||
|
|
@ -483,10 +571,14 @@ void vcpu_t::setup_host() noexcept
|
|||
//
|
||||
host_rsp(reinterpret_cast<uint64_t>(std::end(stack_.data)));
|
||||
host_rip(reinterpret_cast<uint64_t>(&vcpu_t::entry_host_));
|
||||
|
||||
return error_code_t{};
|
||||
}
|
||||
|
||||
void vcpu_t::setup_guest() noexcept
|
||||
auto vcpu_t::setup_guest() noexcept -> error_code_t
|
||||
{
|
||||
hvpp_assert(state_ == state::initializing);
|
||||
|
||||
//
|
||||
// VPIDs provide a way for software to identify to the processor the
|
||||
// address spaces for different "virtual processors." The processor
|
||||
|
|
@ -589,10 +681,14 @@ void vcpu_t::setup_guest() noexcept
|
|||
//
|
||||
guest_rsp(reinterpret_cast<uint64_t>(std::end(stack_.data)));
|
||||
guest_rip(reinterpret_cast<uint64_t>(&vcpu_t::entry_guest_));
|
||||
|
||||
return error_code_t{};
|
||||
}
|
||||
|
||||
void vcpu_t::entry_host() noexcept
|
||||
{
|
||||
hvpp_assert(state_ == state::running);
|
||||
|
||||
//
|
||||
// Reset RIP-adjust flag.
|
||||
//
|
||||
|
|
@ -623,13 +719,16 @@ void vcpu_t::entry_host() noexcept
|
|||
|
||||
{
|
||||
//
|
||||
// Because we're in VMX-root mode, the system memory allocator
|
||||
// has to be disabled.
|
||||
// Because we're in VMX-root mode, we can't use the system allocator
|
||||
// (ExAllocatePoolWithTag/ExFreePoolWithTag).
|
||||
// This line will enable "custom allocator" that will be used whenever
|
||||
// "new"/"delete" operator is executed.
|
||||
// See lib/mm.cpp for more details.
|
||||
//
|
||||
memory_manager::allocator_guard _;
|
||||
mm::allocator_guard _;
|
||||
|
||||
auto captured_rsp = exit_context_.rsp;
|
||||
auto captured_rflags = exit_context_.rflags;
|
||||
const auto captured_rsp = exit_context_.rsp;
|
||||
const auto captured_rflags = exit_context_.rflags;
|
||||
|
||||
{
|
||||
exit_context_.rsp = guest_rsp();
|
||||
|
|
@ -649,9 +748,9 @@ void vcpu_t::entry_host() noexcept
|
|||
stack_.machine_frame.rsp = exit_context_.rsp;
|
||||
|
||||
{
|
||||
handler_->handle(*this);
|
||||
handler_.handle(*this);
|
||||
|
||||
if (state_ == vcpu_state::terminated)
|
||||
if (state_ == state::terminated)
|
||||
{
|
||||
//
|
||||
// At this point we're not in the VMX-root mode (vmxoff has been
|
||||
|
|
@ -686,7 +785,9 @@ exit:
|
|||
|
||||
void vcpu_t::entry_guest() noexcept
|
||||
{
|
||||
guest_context_.rax = static_cast<uint64_t>(vcpu_state::launching);
|
||||
// hvpp_assert(state_ == state::initializing);
|
||||
|
||||
guest_context_.rax = static_cast<uint64_t>(state::launching);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
#pragma once
|
||||
#include "ept.h"
|
||||
#include "interrupt.h"
|
||||
|
||||
#include "ia32/arch.h"
|
||||
#include "ia32/exception.h"
|
||||
#include "ia32/vmx.h"
|
||||
|
||||
#include "lib/error.h"
|
||||
|
||||
|
|
@ -15,138 +14,17 @@ using namespace ia32;
|
|||
|
||||
class vmexit_handler;
|
||||
|
||||
class interrupt_t final
|
||||
{
|
||||
public:
|
||||
constexpr interrupt_t(vmx::interrupt_type interrupt_type, exception_vector exception_vector, int rip_adjust = -1) noexcept
|
||||
: interrupt_t(interrupt_type, exception_vector, exception_error_code_t{}, false, rip_adjust) { }
|
||||
|
||||
constexpr interrupt_t(vmx::interrupt_type interrupt_type, exception_vector exception_vector, exception_error_code_t exception_code, int rip_adjust = -1) noexcept
|
||||
: interrupt_t(interrupt_type, exception_vector, exception_code, true, rip_adjust) { }
|
||||
|
||||
constexpr interrupt_t(const interrupt_t& other) noexcept = default;
|
||||
constexpr interrupt_t(interrupt_t&& other) noexcept = default;
|
||||
constexpr interrupt_t& operator=(const interrupt_t& other) noexcept = default;
|
||||
constexpr interrupt_t& operator=(interrupt_t&& other) noexcept = default;
|
||||
|
||||
constexpr auto vector() const noexcept { return static_cast<exception_vector>(info_.vector); }
|
||||
constexpr auto type() const noexcept { return static_cast<vmx::interrupt_type>(info_.type); }
|
||||
constexpr bool error_code_valid() const noexcept { return info_.error_code_valid; }
|
||||
constexpr bool nmi_unblocking() const noexcept { return info_.nmi_unblocking; }
|
||||
constexpr bool valid() const noexcept { return info_.valid; }
|
||||
constexpr auto error_code() const noexcept { return error_code_; }
|
||||
constexpr int rip_adjust() const noexcept { return rip_adjust_; }
|
||||
|
||||
private:
|
||||
friend class vcpu_t;
|
||||
|
||||
constexpr interrupt_t() noexcept
|
||||
: info_(), error_code_(), rip_adjust_() { }
|
||||
|
||||
constexpr interrupt_t(vmx::interrupt_type interrupt_type, exception_vector exception_vector, exception_error_code_t exception_code, bool exception_code_valid, int rip_adjust) noexcept
|
||||
: error_code_(exception_code), rip_adjust_(rip_adjust)
|
||||
{
|
||||
info_.flags = 0;
|
||||
|
||||
info_.vector = static_cast<uint32_t>(exception_vector);
|
||||
info_.type = static_cast<uint32_t>(interrupt_type);
|
||||
info_.valid = true;
|
||||
|
||||
//
|
||||
// Final sanitization of the following fields takes place
|
||||
// in vcpu::interrupt_inject_force().
|
||||
//
|
||||
|
||||
info_.error_code_valid = exception_code_valid;
|
||||
}
|
||||
|
||||
vmx::interrupt_info_t info_;
|
||||
exception_error_code_t error_code_;
|
||||
int rip_adjust_;
|
||||
};
|
||||
|
||||
enum class vcpu_state
|
||||
{
|
||||
//
|
||||
// VCPU is unitialized.
|
||||
//
|
||||
off,
|
||||
|
||||
//
|
||||
// VCPU is in VMX-root mode; host & guest VMCS is being initialized.
|
||||
//
|
||||
initializing,
|
||||
|
||||
//
|
||||
// VCPU successfully performed its initial VMENTRY.
|
||||
//
|
||||
launching,
|
||||
|
||||
//
|
||||
// VCPU is running.
|
||||
//
|
||||
running,
|
||||
|
||||
//
|
||||
// VCPU is terminating; vcpu::destroy has been called.
|
||||
//
|
||||
terminating,
|
||||
|
||||
//
|
||||
// VCPU is terminated, VMX-root mode has been left.
|
||||
//
|
||||
terminated,
|
||||
};
|
||||
|
||||
//
|
||||
// Definition of the stack structure.
|
||||
// See vcpu.asm for more details.
|
||||
//
|
||||
|
||||
static constexpr int vcpu_stack_size = 0x8000;
|
||||
|
||||
struct vcpu_stack_t
|
||||
{
|
||||
struct machine_frame_t
|
||||
{
|
||||
uint64_t rip;
|
||||
uint64_t cs;
|
||||
uint64_t eflags;
|
||||
uint64_t rsp;
|
||||
uint64_t ss;
|
||||
};
|
||||
|
||||
struct shadow_space_t
|
||||
{
|
||||
uint64_t dummy[4];
|
||||
};
|
||||
|
||||
union
|
||||
{
|
||||
uint8_t data[vcpu_stack_size];
|
||||
|
||||
struct
|
||||
{
|
||||
uint8_t dummy[vcpu_stack_size
|
||||
- sizeof(shadow_space_t)
|
||||
- sizeof(machine_frame_t)];
|
||||
shadow_space_t shadow_space;
|
||||
machine_frame_t machine_frame;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
static_assert(sizeof(vcpu_stack_t) == vcpu_stack_size);
|
||||
static_assert(sizeof(vcpu_stack_t::shadow_space_t) == 32);
|
||||
|
||||
class vcpu_t final
|
||||
{
|
||||
public:
|
||||
auto initialize(vmexit_handler& handler) noexcept -> error_code_t;
|
||||
void destroy() noexcept;
|
||||
vcpu_t(vmexit_handler& handler) noexcept;
|
||||
~vcpu_t() noexcept;
|
||||
|
||||
void launch() noexcept;
|
||||
void terminate() noexcept;
|
||||
auto start() noexcept -> error_code_t;
|
||||
void stop() noexcept;
|
||||
|
||||
auto vmx_enter() noexcept -> error_code_t;
|
||||
void vmx_leave() noexcept;
|
||||
|
||||
void ept_enable(uint16_t count = 1) noexcept;
|
||||
void ept_disable() noexcept;
|
||||
|
|
@ -168,7 +46,7 @@ class vcpu_t final
|
|||
// Make storage for up-to 16 pending interrupts.
|
||||
// In practice I haven't seen more than 2 pending interrupts.
|
||||
//
|
||||
static constexpr int pending_interrupt_queue_size = 16;
|
||||
static constexpr auto pending_interrupt_queue_size = 16;
|
||||
|
||||
auto interrupt_info() const noexcept -> interrupt_t;
|
||||
auto idt_vectoring_info() const noexcept -> interrupt_t;
|
||||
|
|
@ -372,14 +250,15 @@ class vcpu_t final
|
|||
//
|
||||
|
||||
private:
|
||||
void error() noexcept;
|
||||
void setup() noexcept;
|
||||
auto handle_common_error(error_code_t err) noexcept -> error_code_t;
|
||||
auto handle_vmx_enter_error(error_code_t err) noexcept -> error_code_t;
|
||||
auto handle_vmx_launch_error() noexcept -> error_code_t;
|
||||
|
||||
void load_vmxon() noexcept;
|
||||
void load_vmcs() noexcept;
|
||||
auto load_vmxon() noexcept -> error_code_t;
|
||||
auto load_vmcs() noexcept -> error_code_t;
|
||||
|
||||
void setup_host() noexcept;
|
||||
void setup_guest() noexcept;
|
||||
auto setup_host() noexcept -> error_code_t;
|
||||
auto setup_guest() noexcept -> error_code_t;
|
||||
|
||||
void entry_host() noexcept;
|
||||
void entry_guest() noexcept;
|
||||
|
|
@ -387,11 +266,85 @@ class vcpu_t final
|
|||
static void entry_host_() noexcept;
|
||||
static void entry_guest_() noexcept;
|
||||
|
||||
enum class state
|
||||
{
|
||||
//
|
||||
// VCPU is unitialized.
|
||||
//
|
||||
off,
|
||||
|
||||
//
|
||||
// VCPU is in VMX-root mode; host & guest VMCS is being initialized.
|
||||
//
|
||||
initializing,
|
||||
|
||||
//
|
||||
// VCPU successfully performed its initial VMENTRY.
|
||||
//
|
||||
launching,
|
||||
|
||||
//
|
||||
// VCPU is running.
|
||||
//
|
||||
running,
|
||||
|
||||
//
|
||||
// VCPU is terminating; vcpu::destroy has been called.
|
||||
//
|
||||
terminating,
|
||||
|
||||
//
|
||||
// VCPU is terminated, VMX-root mode has been left.
|
||||
//
|
||||
terminated,
|
||||
};
|
||||
|
||||
//
|
||||
// Definition of the stack structure.
|
||||
// See vcpu.asm for more details.
|
||||
//
|
||||
|
||||
struct stack_t
|
||||
{
|
||||
static constexpr auto size = 0x8000;
|
||||
|
||||
struct machine_frame_t
|
||||
{
|
||||
uint64_t rip;
|
||||
uint64_t cs;
|
||||
uint64_t eflags;
|
||||
uint64_t rsp;
|
||||
uint64_t ss;
|
||||
};
|
||||
|
||||
struct shadow_space_t
|
||||
{
|
||||
uint64_t dummy[4];
|
||||
};
|
||||
|
||||
union
|
||||
{
|
||||
uint8_t data[size];
|
||||
|
||||
struct
|
||||
{
|
||||
uint8_t dummy[size
|
||||
- sizeof(shadow_space_t)
|
||||
- sizeof(machine_frame_t)];
|
||||
shadow_space_t shadow_space;
|
||||
machine_frame_t machine_frame;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
static_assert(sizeof(stack_t) == stack_t::size);
|
||||
static_assert(sizeof(stack_t::shadow_space_t) == 32);
|
||||
|
||||
//
|
||||
// If you reorder following three members (stack, guest context and exit
|
||||
// context), you have to edit offsets in vcpu.asm.
|
||||
//
|
||||
vcpu_stack_t stack_;
|
||||
stack_t stack_;
|
||||
context_t guest_context_;
|
||||
context_t exit_context_;
|
||||
|
||||
|
|
@ -409,8 +362,8 @@ class vcpu_t final
|
|||
//
|
||||
fxsave_area_t fxsave_area_;
|
||||
|
||||
vmexit_handler* handler_;
|
||||
vcpu_state state_;
|
||||
vmexit_handler& handler_;
|
||||
state state_;
|
||||
|
||||
ept_t* ept_;
|
||||
uint16_t ept_count_;
|
||||
|
|
|
|||
|
|
@ -82,32 +82,22 @@ vmexit_handler::~vmexit_handler() noexcept
|
|||
|
||||
}
|
||||
|
||||
auto vmexit_handler::initialize() noexcept -> error_code_t
|
||||
{
|
||||
return error_code_t{};
|
||||
}
|
||||
|
||||
void vmexit_handler::destroy() noexcept
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void vmexit_handler::setup(vcpu_t& vp) noexcept
|
||||
{
|
||||
(void)(vp);
|
||||
}
|
||||
|
||||
void vmexit_handler::teardown(vcpu_t& vp) noexcept
|
||||
{
|
||||
(void)(vp);
|
||||
}
|
||||
|
||||
void vmexit_handler::handle(vcpu_t& vp) noexcept
|
||||
{
|
||||
auto handler_index = static_cast<int>(vp.exit_reason());
|
||||
const auto handler_index = static_cast<int>(vp.exit_reason());
|
||||
(this->*handlers_[handler_index])(vp);
|
||||
}
|
||||
|
||||
void vmexit_handler::invoke_termination(vcpu_t& vp) noexcept
|
||||
{
|
||||
(void)(vp);
|
||||
}
|
||||
|
||||
//
|
||||
// "Do-nothing" handlers for all VM-exits.
|
||||
// VMX-instruction related VM-exits (VMREAD, VMWRITE, INVEPT, ...)
|
||||
|
|
|
|||
|
|
@ -116,78 +116,53 @@ struct vmexit_storage_t
|
|||
class vmexit_handler
|
||||
{
|
||||
public:
|
||||
|
||||
//
|
||||
// Predefined interrupt structures.
|
||||
// Helpful when injecting events.
|
||||
// Constructor & destructor.
|
||||
//
|
||||
|
||||
static constexpr auto interrupt_nmi = interrupt_t {
|
||||
vmx::interrupt_type::nmi,
|
||||
exception_vector::nmi_interrupt
|
||||
};
|
||||
|
||||
static constexpr auto interrupt_debug = interrupt_t {
|
||||
vmx::interrupt_type::hardware_exception,
|
||||
exception_vector::debug
|
||||
};
|
||||
|
||||
static constexpr auto interrupt_invalid_opcode = interrupt_t {
|
||||
vmx::interrupt_type::hardware_exception,
|
||||
exception_vector::invalid_opcode
|
||||
};
|
||||
|
||||
static constexpr auto interrupt_general_protection = interrupt_t {
|
||||
vmx::interrupt_type::hardware_exception,
|
||||
exception_vector::general_protection,
|
||||
exception_error_code_t{}
|
||||
};
|
||||
|
||||
public:
|
||||
vmexit_handler() noexcept;
|
||||
~vmexit_handler() noexcept;
|
||||
|
||||
// Note:
|
||||
// Constructor & destructor is guaranteed to NOT be called
|
||||
// in VMX-root mode.
|
||||
// Therefore, avoid execution of any VMX instructions there.
|
||||
//
|
||||
// Avoid execution of any VMX instructions here, because
|
||||
// this method is not guaranteed to be called in the VMX-root
|
||||
// mode.
|
||||
//
|
||||
virtual auto initialize() noexcept -> error_code_t;
|
||||
|
||||
//
|
||||
// Avoid execution of any VMX instructions here, because
|
||||
// this method is not guaranteed to be called in the VMX-root
|
||||
// mode.
|
||||
//
|
||||
virtual void destroy() noexcept;
|
||||
vmexit_handler() noexcept;
|
||||
virtual ~vmexit_handler() noexcept;
|
||||
|
||||
//
|
||||
// This method allows you to set up VCPU state before VMLAUNCH.
|
||||
// Use this method for setting up VMCS.
|
||||
//
|
||||
// Note:
|
||||
// This method is guaranteed to be called in VMX-root mode.
|
||||
//
|
||||
virtual void setup(vcpu_t& vp) noexcept;
|
||||
|
||||
//
|
||||
// This method is called from vcpu_t::stop() method.
|
||||
// It should be responsible for initiating VM tear-down
|
||||
// and disabling the VMX mode.
|
||||
//
|
||||
// Note:
|
||||
// This method is guaranteed to NOT be called in VMX-root mode.
|
||||
// Therefore, avoid execution of any VMX instructions there
|
||||
// (including VMXOFF).
|
||||
//
|
||||
// If you wish to execute code in VMX-root mode when this method
|
||||
// is called, use "vmcall".
|
||||
//
|
||||
virtual void teardown(vcpu_t& vp) noexcept;
|
||||
|
||||
//
|
||||
// This method is called on every VM-exit.
|
||||
// By default this method delegates the execution control
|
||||
// to related VM-exit method (i.e.: for "execute CPUID VM-exit"
|
||||
// it calls handle_execute_cpuid() method).
|
||||
//
|
||||
// Keep in mind that this method is not called for VM-exits
|
||||
// that are not enabled in the VMCS.
|
||||
// Note:
|
||||
// Keep in mind that this method is not called for VM-exits
|
||||
// that are not enabled in the VMCS.
|
||||
//
|
||||
virtual void handle(vcpu_t& vp) noexcept;
|
||||
|
||||
//
|
||||
// This method is called from vcpu_t::destroy() method.
|
||||
// It should be responsible for initiating VM tear-down
|
||||
// and disabling the VMX mode.
|
||||
//
|
||||
// Note that this method is not called in VMX-root mode,
|
||||
// therefore you should avoid usage of VMXOFF instruction.
|
||||
//
|
||||
virtual void invoke_termination(vcpu_t& vp) noexcept;
|
||||
|
||||
protected:
|
||||
//
|
||||
// Separate handlers for each VM-exit reason.
|
||||
|
|
@ -271,7 +246,7 @@ class vmexit_handler
|
|||
|
||||
protected:
|
||||
using handler_fn_t = void (vmexit_handler::*)(vcpu_t&);
|
||||
std::array<handler_fn_t, 65> handlers_;
|
||||
const std::array<handler_fn_t, 65> handlers_;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
namespace hvpp {
|
||||
|
||||
auto vmexit_c_wrapper_handler::initialize(const c_handler_array_t& c_handlers, void* context) noexcept -> error_code_t
|
||||
vmexit_c_wrapper_handler::vmexit_c_wrapper_handler(const c_handler_array_t& c_handlers, void* context) noexcept
|
||||
{
|
||||
//
|
||||
// Make local copy of the C-handlers.
|
||||
|
|
@ -12,11 +12,9 @@ auto vmexit_c_wrapper_handler::initialize(const c_handler_array_t& c_handlers, v
|
|||
|
||||
c_handlers_ = c_handlers;
|
||||
context_ = context;
|
||||
|
||||
return error_code_t{};
|
||||
}
|
||||
|
||||
void vmexit_c_wrapper_handler::destroy() noexcept
|
||||
vmexit_c_wrapper_handler::~vmexit_c_wrapper_handler() noexcept
|
||||
{
|
||||
|
||||
}
|
||||
|
|
@ -36,11 +34,11 @@ void vmexit_c_wrapper_handler::setup(vcpu_t& vp) noexcept
|
|||
|
||||
void vmexit_c_wrapper_handler::handle(vcpu_t& vp) noexcept
|
||||
{
|
||||
auto exit_reason = vp.exit_reason();
|
||||
auto exit_reason_index = static_cast<int>(exit_reason);
|
||||
const auto exit_reason = vp.exit_reason();
|
||||
const auto exit_reason_index = static_cast<int>(exit_reason);
|
||||
|
||||
auto cpp_handler = handlers_[exit_reason_index];
|
||||
auto c_handler = c_handlers_[exit_reason_index];
|
||||
const auto cpp_handler = handlers_[exit_reason_index];
|
||||
const auto c_handler = c_handlers_[exit_reason_index];
|
||||
|
||||
if (c_handler)
|
||||
{
|
||||
|
|
@ -74,9 +72,9 @@ void vmexit_c_wrapper_handler::handle_passthrough(passthrough_context* context)
|
|||
// from the pass-trough context and call that method.
|
||||
//
|
||||
|
||||
auto handler_instance = context->handler_instance;
|
||||
auto handler_method = context->handler_method;
|
||||
auto& vp = *context->vcpu;
|
||||
const auto handler_instance = context->handler_instance;
|
||||
const auto handler_method = context->handler_method;
|
||||
auto& vp = *context->vcpu;
|
||||
|
||||
(handler_instance->*handler_method)(vp);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,11 +18,10 @@ class vmexit_c_wrapper_handler
|
|||
|
||||
using c_handler_array_t = std::array<c_handler_fn_t, 65>;
|
||||
|
||||
auto initialize(const c_handler_array_t& c_handlers, void* context = nullptr) noexcept -> error_code_t;
|
||||
void destroy() noexcept;
|
||||
vmexit_c_wrapper_handler(const c_handler_array_t& c_handlers, void* context = nullptr) noexcept;
|
||||
~vmexit_c_wrapper_handler() noexcept override;
|
||||
|
||||
void setup(vcpu_t& vp) noexcept override;
|
||||
|
||||
void handle(vcpu_t& vp) noexcept override;
|
||||
|
||||
private:
|
||||
|
|
|
|||
|
|
@ -19,10 +19,9 @@
|
|||
|
||||
namespace hvpp {
|
||||
|
||||
auto vmexit_dbgbreak_handler::initialize() noexcept -> error_code_t
|
||||
vmexit_dbgbreak_handler::vmexit_dbgbreak_handler() noexcept
|
||||
: storage_{}
|
||||
{
|
||||
memset(&storage_, 0, sizeof(storage_));
|
||||
|
||||
//
|
||||
// Uncomment this to break on IN 0x64 instruction.
|
||||
// Breakpoints on specific VM-exit reasons can be enabled/disabled
|
||||
|
|
@ -30,18 +29,16 @@ auto vmexit_dbgbreak_handler::initialize() noexcept -> error_code_t
|
|||
//
|
||||
// storage_.io_in[0x64] = true;
|
||||
//
|
||||
|
||||
return error_code_t{};
|
||||
}
|
||||
|
||||
void vmexit_dbgbreak_handler::destroy() noexcept
|
||||
vmexit_dbgbreak_handler::~vmexit_dbgbreak_handler() noexcept
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void vmexit_dbgbreak_handler::handle(vcpu_t& vp) noexcept
|
||||
{
|
||||
auto exit_reason = vp.exit_reason();
|
||||
const auto exit_reason = vp.exit_reason();
|
||||
|
||||
hvpp_break_if(storage_.vmexit[static_cast<int>(exit_reason)]);
|
||||
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ class vmexit_dbgbreak_handler
|
|||
: public vmexit_handler
|
||||
{
|
||||
public:
|
||||
auto initialize() noexcept -> error_code_t;
|
||||
void destroy() noexcept;
|
||||
vmexit_dbgbreak_handler() noexcept;
|
||||
~vmexit_dbgbreak_handler() noexcept override;
|
||||
|
||||
void handle(vcpu_t& vp) noexcept override;
|
||||
|
||||
|
|
|
|||
|
|
@ -48,9 +48,11 @@ void vmexit_passthrough_handler::setup(vcpu_t& vp) noexcept
|
|||
vp.guest_dr7(read<dr7_t>());
|
||||
vp.guest_rflags(read<rflags_t>());
|
||||
|
||||
auto gdtr = read<gdtr_t>();
|
||||
const auto gdtr = read<gdtr_t>();
|
||||
const auto idtr = read<idtr_t>();
|
||||
|
||||
vp.guest_gdtr(gdtr);
|
||||
vp.guest_idtr(read<idtr_t>());
|
||||
vp.guest_idtr(idtr);
|
||||
vp.guest_cs(segment_t{ gdtr, read<cs_t>() });
|
||||
vp.guest_ds(segment_t{ gdtr, read<ds_t>() });
|
||||
vp.guest_es(segment_t{ gdtr, read<es_t>() });
|
||||
|
|
@ -61,7 +63,7 @@ void vmexit_passthrough_handler::setup(vcpu_t& vp) noexcept
|
|||
vp.guest_ldtr(segment_t{ gdtr, read<ldtr_t>() });
|
||||
}
|
||||
|
||||
void vmexit_passthrough_handler::invoke_termination(vcpu_t& vp) noexcept
|
||||
void vmexit_passthrough_handler::teardown(vcpu_t& vp) noexcept
|
||||
{
|
||||
(void)(vp);
|
||||
|
||||
|
|
@ -207,7 +209,7 @@ void vmexit_passthrough_handler::handle_execute_vmcall(vcpu_t& vp) noexcept
|
|||
if (vp.exit_context().rcx == vmcall_terminate_id &&
|
||||
vp.guest_cs().selector.request_privilege_level == 0)
|
||||
{
|
||||
vp.terminate();
|
||||
vp.vmx_leave();
|
||||
}
|
||||
else if (vp.exit_context().rcx == vmcall_breakpoint_id)
|
||||
{
|
||||
|
|
@ -227,136 +229,151 @@ void vmexit_passthrough_handler::handle_mov_cr(vcpu_t& vp) noexcept
|
|||
switch (exit_qualification.access_type)
|
||||
{
|
||||
case vmx::exit_qualification_mov_cr_t::access_to_cr:
|
||||
{
|
||||
switch (exit_qualification.cr_number)
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
vp.guest_cr0(cr0_t{ gp_register });
|
||||
vp.cr0_shadow(cr0_t{ gp_register });
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 3:
|
||||
{
|
||||
//
|
||||
// If CR4.PCIDE = 1, bit 63 of the source operand to MOV
|
||||
// to CR3 determines whether the instruction invalidates
|
||||
// entries in the TLBs and the paging-structure caches.
|
||||
// The instruction does not modify bit 63 of CR3, which
|
||||
// is reserved and always 0.
|
||||
// (ref: Vol2B(MOV-Move to/from Control Registers)
|
||||
// (see: Vol3A[4.10.4.1(Operations that Invalidate TLBs and Paging-Structure Caches)]
|
||||
//
|
||||
auto cr3 = cr3_t{ gp_register };
|
||||
if (vp.guest_cr4().pcid_enable)
|
||||
{
|
||||
//
|
||||
// If CR4.PCIDE = 1, bit 63 of the source operand to MOV
|
||||
// to CR3 determines whether the instruction invalidates
|
||||
// entries in the TLBs and the paging-structure caches.
|
||||
// The instruction does not modify bit 63 of CR3, which
|
||||
// is reserved and always 0.
|
||||
// (ref: Vol2B(MOV-Move to/from Control Registers)
|
||||
// (see: Vol3A[4.10.4.1(Operations that Invalidate TLBs and Paging-Structure Caches)]
|
||||
// Equivalent to:
|
||||
// gp_register &= ~(1ull << 63);
|
||||
//
|
||||
auto cr3 = cr3_t{ gp_register };
|
||||
if (vp.guest_cr4().pcid_enable)
|
||||
{
|
||||
//
|
||||
// Equivalent to:
|
||||
// gp_register &= ~(1ull << 63);
|
||||
//
|
||||
cr3.pcid_invalidate = false;
|
||||
}
|
||||
vp.guest_cr3(cr3);
|
||||
|
||||
//
|
||||
// Some instructions invalidate all entries in the TLBs
|
||||
// and paging-structure caches-except for global translations.
|
||||
// An example is the MOV to CR3 instruction.
|
||||
// Emulation of such an instruction may require execution of
|
||||
// the INVVPID instruction as follows:
|
||||
// - The INVVPID type is single-context-retaining-globals (3).
|
||||
// - The VPID in the INVVPID descriptor is the one assigned to
|
||||
// the virtual processor whose execution is being emulated.
|
||||
// (ref: Vol3C[28.3.3.3(Guidelines for Use of the INVVPID Instruction)])
|
||||
//
|
||||
vmx::invvpid_single_context_retaining_globals(vp.vcpu_id());
|
||||
cr3.pcid_invalidate = false;
|
||||
}
|
||||
vp.guest_cr3(cr3);
|
||||
|
||||
//
|
||||
// Some instructions invalidate all entries in the TLBs
|
||||
// and paging-structure caches-except for global translations.
|
||||
// An example is the MOV to CR3 instruction.
|
||||
// Emulation of such an instruction may require execution of
|
||||
// the INVVPID instruction as follows:
|
||||
// - The INVVPID type is single-context-retaining-globals (3).
|
||||
// - The VPID in the INVVPID descriptor is the one assigned to
|
||||
// the virtual processor whose execution is being emulated.
|
||||
// (ref: Vol3C[28.3.3.3(Guidelines for Use of the INVVPID Instruction)])
|
||||
//
|
||||
vmx::invvpid_single_context_retaining_globals(vp.vcpu_id());
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 4:
|
||||
{
|
||||
//
|
||||
// Some instructions invalidate all entries in the TLBs and
|
||||
// paging-structure caches-including for global translations.
|
||||
// An example is the MOV to CR4 instruction if the value of
|
||||
// value of bit 4 (page global enable-PGE) is changing.
|
||||
// Emulation of such an instruction may require execution of
|
||||
// the INVVPID instruction as follows:
|
||||
// - The INVVPID type is single-context (1).
|
||||
// - The VPID in the INVVPID descriptor is the one assigned to
|
||||
// the virtual processor whose execution is being emulated.
|
||||
// (ref: Vol3C[28.3.3.3(Guidelines for Use of the INVVPID Instruction)])
|
||||
//
|
||||
cr4_t new_cr4 = cr4_t{ gp_register };
|
||||
bool pge_changed = new_cr4.page_global_enable != vp.guest_cr4().page_global_enable;
|
||||
|
||||
if (pge_changed)
|
||||
{
|
||||
//
|
||||
// Some instructions invalidate all entries in the TLBs and
|
||||
// paging-structure caches-including for global translations.
|
||||
// An example is the MOV to CR4 instruction if the value of
|
||||
// value of bit 4 (page global enable-PGE) is changing.
|
||||
// Emulation of such an instruction may require execution of
|
||||
// the INVVPID instruction as follows:
|
||||
// - The INVVPID type is single-context (1).
|
||||
// - The VPID in the INVVPID descriptor is the one assigned to
|
||||
// the virtual processor whose execution is being emulated.
|
||||
// (ref: Vol3C[28.3.3.3(Guidelines for Use of the INVVPID Instruction)])
|
||||
//
|
||||
cr4_t new_cr4 = cr4_t{ gp_register };
|
||||
bool pge_changed = new_cr4.page_global_enable != vp.guest_cr4().page_global_enable;
|
||||
|
||||
if (pge_changed)
|
||||
{
|
||||
vmx::invvpid_single_context(vp.vcpu_id());
|
||||
}
|
||||
|
||||
vp.guest_cr4(new_cr4);
|
||||
vp.cr4_shadow(new_cr4);
|
||||
vmx::invvpid_single_context(vp.vcpu_id());
|
||||
}
|
||||
|
||||
vp.guest_cr4(new_cr4);
|
||||
vp.cr4_shadow(new_cr4);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 8:
|
||||
{
|
||||
/* unimplemented */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case vmx::exit_qualification_mov_cr_t::access_from_cr:
|
||||
{
|
||||
switch (exit_qualification.cr_number)
|
||||
{
|
||||
case 3: gp_register = vp.guest_cr3().flags; break;
|
||||
case 8: /* unimplemented */ break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case vmx::exit_qualification_mov_cr_t::access_clts:
|
||||
{
|
||||
auto cr0 = vp.guest_cr0();
|
||||
cr0.task_switched = false;
|
||||
vp.guest_cr0(cr0);
|
||||
vp.cr0_shadow(cr0);
|
||||
}
|
||||
{
|
||||
auto cr0 = vp.guest_cr0();
|
||||
cr0.task_switched = false;
|
||||
vp.guest_cr0(cr0);
|
||||
vp.cr0_shadow(cr0);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case vmx::exit_qualification_mov_cr_t::access_lmsw:
|
||||
{
|
||||
auto msw = static_cast<uint16_t>(exit_qualification.lmsw_source_data);
|
||||
auto cr0 = vp.guest_cr0();
|
||||
{
|
||||
auto msw = static_cast<uint16_t>(exit_qualification.lmsw_source_data);
|
||||
auto cr0 = vp.guest_cr0();
|
||||
|
||||
//
|
||||
// Loads the source operand into the machine status word,
|
||||
// bits 0 through 15 of register CR0. The source operand
|
||||
// can be a 16-bit general-purpose register or a memory
|
||||
// location. Only the low-order 4 bits of the source
|
||||
// operand (which contains the PE, MP, EM, and TS flags)
|
||||
// are loaded into CR0. The PG, CD, NW, AM, WP, NE, and
|
||||
// ET flags of CR0 are not affected. The operand-size
|
||||
// attribute has no effect on this instruction. If the
|
||||
// PE flag of the source operand (bit 0) is set to 1, the
|
||||
// instruction causes the processor to switch to protected
|
||||
// mode. While in protected mode, the LMSW instruction
|
||||
// cannot be used to clear the PE flag and force a switch
|
||||
// back to real-address mode.
|
||||
// (ref: Vol2A[(LMSW-Load Machine Status Word)])
|
||||
//
|
||||
// TL;DR:
|
||||
// CR0[0:3] <- SRC[0:3];
|
||||
//
|
||||
// ...except if CR0.PE (bit 0) is already 1 - then do not
|
||||
// change that bit (lmsw can't be used to switch back to
|
||||
// real mode from the protected mode.
|
||||
//
|
||||
//
|
||||
// Loads the source operand into the machine status word,
|
||||
// bits 0 through 15 of register CR0. The source operand
|
||||
// can be a 16-bit general-purpose register or a memory
|
||||
// location. Only the low-order 4 bits of the source
|
||||
// operand (which contains the PE, MP, EM, and TS flags)
|
||||
// are loaded into CR0. The PG, CD, NW, AM, WP, NE, and
|
||||
// ET flags of CR0 are not affected. The operand-size
|
||||
// attribute has no effect on this instruction. If the
|
||||
// PE flag of the source operand (bit 0) is set to 1, the
|
||||
// instruction causes the processor to switch to protected
|
||||
// mode. While in protected mode, the LMSW instruction
|
||||
// cannot be used to clear the PE flag and force a switch
|
||||
// back to real-address mode.
|
||||
// (ref: Vol2A[(LMSW-Load Machine Status Word)])
|
||||
//
|
||||
// TL;DR:
|
||||
// CR0[0:3] <- SRC[0:3];
|
||||
//
|
||||
// ...except if CR0.PE (bit 0) is already 1 - then do not
|
||||
// change that bit (lmsw can't be used to switch back to
|
||||
// real mode from the protected mode.
|
||||
//
|
||||
|
||||
cr0.flags &= ~0b1110;
|
||||
cr0.flags |= msw & 0b1111;
|
||||
cr0.flags &= ~0b1110;
|
||||
cr0.flags |= msw & 0b1111;
|
||||
|
||||
vp.guest_cr0(cr0);
|
||||
vp.cr0_shadow(cr0);
|
||||
|
||||
vp.guest_cr0(cr0);
|
||||
vp.cr0_shadow(cr0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -387,7 +404,7 @@ void vmexit_passthrough_handler::handle_mov_dr(vcpu_t& vp) noexcept
|
|||
|
||||
if (vp.guest_cs().access.descriptor_privilege_level != 0)
|
||||
{
|
||||
vp.interrupt_inject(interrupt_general_protection);
|
||||
vp.interrupt_inject(interrupt::general_protection);
|
||||
vp.suppress_rip_adjust();
|
||||
return;
|
||||
}
|
||||
|
|
@ -407,7 +424,7 @@ void vmexit_passthrough_handler::handle_mov_dr(vcpu_t& vp) noexcept
|
|||
{
|
||||
if (vp.guest_cr4().debugging_extensions)
|
||||
{
|
||||
vp.interrupt_inject(interrupt_invalid_opcode);
|
||||
vp.interrupt_inject(interrupt::invalid_opcode);
|
||||
vp.suppress_rip_adjust();
|
||||
return;
|
||||
}
|
||||
|
|
@ -444,7 +461,7 @@ void vmexit_passthrough_handler::handle_mov_dr(vcpu_t& vp) noexcept
|
|||
dr7.general_detect = false;
|
||||
vp.guest_dr7(dr7);
|
||||
|
||||
vp.interrupt_inject(interrupt_debug);
|
||||
vp.interrupt_inject(interrupt::debug);
|
||||
vp.suppress_rip_adjust();
|
||||
return;
|
||||
}
|
||||
|
|
@ -460,7 +477,7 @@ void vmexit_passthrough_handler::handle_mov_dr(vcpu_t& vp) noexcept
|
|||
exit_qualification.dr_number == 7) &&
|
||||
(gp_register >> 32) != 0)
|
||||
{
|
||||
vp.interrupt_inject(interrupt_general_protection);
|
||||
vp.interrupt_inject(interrupt::general_protection);
|
||||
vp.suppress_rip_adjust();
|
||||
return;
|
||||
}
|
||||
|
|
@ -752,9 +769,9 @@ void vmexit_passthrough_handler::handle_gdtr_idtr_access(vcpu_t& vp) noexcept
|
|||
// (6 bytes) than on x64 (10 bytes).
|
||||
// The size of written bytes must be correctly emulated.
|
||||
//
|
||||
auto guest_in_long_mode = [&vp]() noexcept -> bool {
|
||||
auto selector = vp.guest_segment_selector(context_t::seg_cs);
|
||||
auto& descriptor_entry = vp.guest_gdtr()[selector];
|
||||
const auto guest_in_long_mode = [&vp]() noexcept -> bool {
|
||||
const auto selector = vp.guest_segment_selector(context_t::seg_cs);
|
||||
const auto& descriptor_entry = vp.guest_gdtr()[selector];
|
||||
|
||||
return descriptor_entry.access.long_mode;
|
||||
};
|
||||
|
|
@ -964,7 +981,7 @@ void vmexit_passthrough_handler::handle_execute_invpcid(vcpu_t& vp) noexcept
|
|||
return;
|
||||
|
||||
inject_general_protection:
|
||||
vp.interrupt_inject(interrupt_general_protection);
|
||||
vp.interrupt_inject(interrupt::general_protection);
|
||||
vp.suppress_rip_adjust();
|
||||
}
|
||||
|
||||
|
|
@ -1006,7 +1023,7 @@ void vmexit_passthrough_handler::handle_execute_vmfunc(vcpu_t& vp) noexcept
|
|||
|
||||
void vmexit_passthrough_handler::handle_vm_fallback(vcpu_t& vp) noexcept
|
||||
{
|
||||
vp.interrupt_inject(interrupt_invalid_opcode);
|
||||
vp.interrupt_inject(interrupt::invalid_opcode);
|
||||
vp.suppress_rip_adjust();
|
||||
}
|
||||
|
||||
|
|
@ -1016,7 +1033,7 @@ void vmexit_passthrough_handler::handle_interrupt(vcpu_t& vp) noexcept
|
|||
// Common code for handling all exceptions and interrupts.
|
||||
//
|
||||
|
||||
auto interrupt = vp.interrupt_info();
|
||||
const auto interrupt = vp.interrupt_info();
|
||||
|
||||
switch (interrupt.type())
|
||||
{
|
||||
|
|
@ -1024,49 +1041,52 @@ void vmexit_passthrough_handler::handle_interrupt(vcpu_t& vp) noexcept
|
|||
switch (interrupt.vector())
|
||||
{
|
||||
case exception_vector::invalid_opcode:
|
||||
{
|
||||
cr3_guard _(vp.guest_cr3());
|
||||
{
|
||||
cr3_guard _(vp.guest_cr3());
|
||||
|
||||
if (detail::is_syscall_instruction(vp.exit_context().rip_as_pointer))
|
||||
{
|
||||
handle_emulate_syscall(vp);
|
||||
vp.suppress_rip_adjust();
|
||||
return;
|
||||
}
|
||||
else if (detail::is_sysret_instruction(vp.exit_context().rip_as_pointer))
|
||||
{
|
||||
handle_emulate_sysret(vp);
|
||||
vp.suppress_rip_adjust();
|
||||
return;
|
||||
}
|
||||
if (detail::is_syscall_instruction(vp.exit_context().rip_as_pointer))
|
||||
{
|
||||
handle_emulate_syscall(vp);
|
||||
vp.suppress_rip_adjust();
|
||||
return;
|
||||
}
|
||||
else if (detail::is_sysret_instruction(vp.exit_context().rip_as_pointer))
|
||||
{
|
||||
handle_emulate_sysret(vp);
|
||||
vp.suppress_rip_adjust();
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case exception_vector::general_protection:
|
||||
{
|
||||
|
||||
#ifdef HVPP_ENABLE_VMWARE_WORKAROUND
|
||||
|
||||
{
|
||||
//
|
||||
// VMWare I/O backdoor (port 0x5658/0x5659) workaround.
|
||||
//
|
||||
cr3_guard _(vp.guest_cr3());
|
||||
//
|
||||
// VMWare I/O backdoor (port 0x5658/0x5659) workaround.
|
||||
//
|
||||
cr3_guard _(vp.guest_cr3());
|
||||
|
||||
vmx::exit_qualification_io_instruction_t exit_qualification;
|
||||
if (try_decode_io_instruction(vp.exit_context(), exit_qualification))
|
||||
{
|
||||
ia32_asm_io_with_context(exit_qualification, vp.exit_context());
|
||||
return;
|
||||
}
|
||||
vmx::exit_qualification_io_instruction_t exit_qualification;
|
||||
if (try_decode_io_instruction(vp.exit_context(), exit_qualification))
|
||||
{
|
||||
ia32_asm_io_with_context(exit_qualification, vp.exit_context());
|
||||
return;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case exception_vector::page_fault:
|
||||
{
|
||||
write<cr2_t>(cr2_t{ vp.exit_qualification().linear_address });
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
|
|
@ -1114,14 +1134,14 @@ void vmexit_passthrough_handler::handle_emulate_syscall(vcpu_t& vp) noexcept
|
|||
// Save the address of the instruction following SYSCALL
|
||||
// into RCX and then load RIP from MSR_LSTAR.
|
||||
//
|
||||
auto lstar = msr::read<msr::lstar_t>();
|
||||
const auto lstar = msr::read<msr::lstar_t>();
|
||||
vp.exit_context().rcx = vp.exit_context().rip + vp.exit_instruction_length();
|
||||
vp.exit_context().rip = lstar;
|
||||
|
||||
//
|
||||
// Save RFLAGS into R11 and then mask RFLAGS using MSR_FMASK.
|
||||
//
|
||||
auto fmask = msr::read<msr::fmask_t>();
|
||||
const auto fmask = msr::read<msr::fmask_t>();
|
||||
vp.exit_context().r11 = vp.exit_context().rflags.flags;
|
||||
vp.exit_context().rflags.flags &= ~fmask.flags;
|
||||
|
||||
|
|
@ -1129,7 +1149,7 @@ void vmexit_passthrough_handler::handle_emulate_syscall(vcpu_t& vp) noexcept
|
|||
// Load the CS and SS selectors with values derived from
|
||||
// bits 47:32 of MSR_STAR.
|
||||
//
|
||||
auto star = msr::read<msr::star_t>();
|
||||
const auto star = msr::read<msr::star_t>();
|
||||
|
||||
//
|
||||
// Verbose version of:
|
||||
|
|
@ -1194,7 +1214,7 @@ void vmexit_passthrough_handler::handle_emulate_sysret(vcpu_t& vp) noexcept
|
|||
// SYSRET loads the CS and SS selectors with values
|
||||
// derived from bits 63:48 of MSR_STAR.
|
||||
//
|
||||
auto star = msr::read<msr::star_t>();
|
||||
const auto star = msr::read<msr::star_t>();
|
||||
|
||||
//
|
||||
// Verbose version of:
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ class vmexit_passthrough_handler
|
|||
{
|
||||
public:
|
||||
void setup(vcpu_t& vp) noexcept override;
|
||||
void invoke_termination(vcpu_t& vp) noexcept override;
|
||||
void teardown(vcpu_t& vp) noexcept override;
|
||||
|
||||
protected:
|
||||
void handle_exception_or_nmi(vcpu_t& vp) noexcept override;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "hvpp/vcpu.h"
|
||||
|
||||
#include "hvpp/lib/assert.h"
|
||||
#include "hvpp/lib/log.h"
|
||||
#include "hvpp/lib/mp.h" // mp::cpu_index()
|
||||
|
||||
|
|
@ -18,7 +19,9 @@
|
|||
|
||||
namespace hvpp {
|
||||
|
||||
auto vmexit_stats_handler::initialize() noexcept -> error_code_t
|
||||
vmexit_stats_handler::vmexit_stats_handler() noexcept
|
||||
: storage_merged_{}
|
||||
, vmexit_trace_bitmap_{}
|
||||
{
|
||||
terminated_vcpu_count_ = 0;
|
||||
|
||||
|
|
@ -26,11 +29,7 @@ auto vmexit_stats_handler::initialize() noexcept -> error_code_t
|
|||
// Allocate memory for statistics (per VCPU).
|
||||
//
|
||||
storage_ = new vmexit_stats_storage_t[mp::cpu_count()];
|
||||
|
||||
if (!storage_)
|
||||
{
|
||||
return make_error_code_t(std::errc::not_enough_memory);
|
||||
}
|
||||
hvpp_assert(storage_ != nullptr);
|
||||
|
||||
memset(storage_, 0, sizeof(*storage_) * mp::cpu_count());
|
||||
|
||||
|
|
@ -46,25 +45,20 @@ auto vmexit_stats_handler::initialize() noexcept -> error_code_t
|
|||
//
|
||||
// vmexit_trace_bitmap_.clear(int(vmx::exit_reason::exception_or_nmi));
|
||||
//
|
||||
|
||||
return error_code_t{};
|
||||
}
|
||||
|
||||
void vmexit_stats_handler::destroy() noexcept
|
||||
vmexit_stats_handler::~vmexit_stats_handler() noexcept
|
||||
{
|
||||
if (storage_)
|
||||
{
|
||||
//
|
||||
// Free the memory.
|
||||
//
|
||||
delete[] storage_;
|
||||
}
|
||||
//
|
||||
// Free the memory.
|
||||
//
|
||||
delete[] storage_;
|
||||
}
|
||||
|
||||
void vmexit_stats_handler::handle(vcpu_t& vp) noexcept
|
||||
{
|
||||
auto exit_reason = vp.exit_reason();
|
||||
auto& stats = storage_[mp::cpu_index()];
|
||||
const auto exit_reason = vp.exit_reason();
|
||||
auto& stats = storage_[mp::cpu_index()];
|
||||
|
||||
stats.vmexit[static_cast<int>(exit_reason)] += 1;
|
||||
|
||||
|
|
@ -327,7 +321,7 @@ void vmexit_stats_handler::storage_merge(vmexit_stats_storage_t& lhs, const vmex
|
|||
|
||||
void vmexit_stats_handler::storage_dump(const vmexit_stats_storage_t& storage_to_dump) const noexcept
|
||||
{
|
||||
auto& stats = storage_to_dump;
|
||||
const auto& stats = storage_to_dump;
|
||||
|
||||
hvpp_info("VMEXIT statistics");
|
||||
for (uint32_t exit_reason_index = 0; exit_reason_index < std::size(stats.vmexit); ++exit_reason_index)
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@ class vmexit_stats_handler
|
|||
: public vmexit_handler
|
||||
{
|
||||
public:
|
||||
auto initialize() noexcept -> error_code_t;
|
||||
void destroy() noexcept;
|
||||
vmexit_stats_handler() noexcept;
|
||||
~vmexit_stats_handler() noexcept override;
|
||||
|
||||
void handle(vcpu_t& vp) noexcept override;
|
||||
|
||||
|
|
|
|||
|
|
@ -17,35 +17,11 @@ namespace hvpp
|
|||
using vmexit_handler_tuple_t = std::tuple<ARGS...>;
|
||||
vmexit_handler_tuple_t handlers;
|
||||
|
||||
auto initialize() noexcept -> error_code_t override
|
||||
{
|
||||
error_code_t err;
|
||||
vmexit_compositor_handler() noexcept
|
||||
{ }
|
||||
|
||||
//
|
||||
// Initialize all handlers.
|
||||
// If initialization of one or more handlers fail, error
|
||||
// code of only the first failed initialization is saved
|
||||
// and returned. Initialization of other handlers doesn't
|
||||
// stop on the first error.
|
||||
//
|
||||
for_each_element(handlers, [&](auto&& handler, int) {
|
||||
auto local_err = handler.initialize();
|
||||
|
||||
if (!err)
|
||||
{
|
||||
err = local_err;
|
||||
}
|
||||
});
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
void destroy() noexcept override
|
||||
{
|
||||
for_each_element(handlers, [&](auto&& handler, int) {
|
||||
handler.destroy();
|
||||
});
|
||||
}
|
||||
~vmexit_compositor_handler() noexcept override
|
||||
{ }
|
||||
|
||||
void setup(vcpu_t& vp) noexcept override
|
||||
{
|
||||
|
|
@ -54,19 +30,19 @@ namespace hvpp
|
|||
});
|
||||
}
|
||||
|
||||
void teardown(vcpu_t& vp) noexcept override
|
||||
{
|
||||
for_each_element(handlers, [&](auto&& handler, int) {
|
||||
handler.teardown(vp);
|
||||
});
|
||||
}
|
||||
|
||||
void handle(vcpu_t& vp) noexcept override
|
||||
{
|
||||
for_each_element(handlers, [&](auto&& handler, int) {
|
||||
handler.handle(vp);
|
||||
});
|
||||
}
|
||||
|
||||
void invoke_termination(vcpu_t& vp) noexcept override
|
||||
{
|
||||
for_each_element(handlers, [&](auto&& handler, int) {
|
||||
handler.invoke_termination(vp);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,19 +15,19 @@
|
|||
<ProjectGuid>{A72DAEF5-C739-4E70-B57E-4310ABA03749}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>hvppctrl</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0.17134.0</WindowsTargetPlatformVersion>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
|
|
|
|||
|
|
@ -246,7 +246,7 @@ void TestIoControl()
|
|||
UINT16 IoPort = 0x64;
|
||||
DWORD BytesReturned;
|
||||
DeviceIoControl(DeviceHandle,
|
||||
ioctl_enable_io_debugbreak_t::code(),
|
||||
ioctl_enable_io_debugbreak_t::code,
|
||||
&IoPort,
|
||||
sizeof(IoPort),
|
||||
&IoPort,
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ namespace driver
|
|||
|
||||
static_assert(std::is_base_of_v<vmexit_handler, vmexit_handler_t>);
|
||||
|
||||
hypervisor* hypervisor_ = nullptr;
|
||||
vmexit_handler_t* vmexit_handler_ = nullptr;
|
||||
device_custom* device_ = nullptr;
|
||||
|
||||
|
|
@ -48,27 +47,7 @@ namespace driver
|
|||
//
|
||||
// Initialize device instance.
|
||||
//
|
||||
if (auto err = device_->initialize())
|
||||
{
|
||||
destroy();
|
||||
return err;
|
||||
}
|
||||
|
||||
//
|
||||
// Create hypervisor instance.
|
||||
//
|
||||
hypervisor_ = new hypervisor();
|
||||
|
||||
if (!hypervisor_)
|
||||
{
|
||||
destroy();
|
||||
return make_error_code_t(std::errc::not_enough_memory);
|
||||
}
|
||||
|
||||
//
|
||||
// Initialize hypervisor.
|
||||
//
|
||||
if (auto err = hypervisor_->initialize())
|
||||
if (auto err = device_->create())
|
||||
{
|
||||
destroy();
|
||||
return err;
|
||||
|
|
@ -85,15 +64,6 @@ namespace driver
|
|||
return make_error_code_t(std::errc::not_enough_memory);
|
||||
}
|
||||
|
||||
//
|
||||
// Initialize VM-exit handler.
|
||||
//
|
||||
if (auto err = vmexit_handler_->initialize())
|
||||
{
|
||||
destroy();
|
||||
return err;
|
||||
}
|
||||
|
||||
//
|
||||
// Assign the vmexit_dbgbreak_handler instance to the device.
|
||||
//
|
||||
|
|
@ -108,13 +78,17 @@ namespace driver
|
|||
//
|
||||
// Start the hypervisor.
|
||||
//
|
||||
hypervisor_->start(*vmexit_handler_);
|
||||
if (auto err = hvpp::hypervisor::start(*vmexit_handler_))
|
||||
{
|
||||
destroy();
|
||||
return err;
|
||||
}
|
||||
|
||||
//
|
||||
// Tell debugger we're started.
|
||||
//
|
||||
hvpp_info("Hypervisor started, current free memory: %" PRIu64 " MB",
|
||||
memory_manager::free_bytes() / 1024 / 1024);
|
||||
mm::free_bytes() / 1024 / 1024);
|
||||
|
||||
return error_code_t{};
|
||||
}
|
||||
|
|
@ -122,22 +96,9 @@ namespace driver
|
|||
void destroy() noexcept
|
||||
{
|
||||
//
|
||||
// Stop and destroy hypervisor.
|
||||
// Stop the hypervisor.
|
||||
//
|
||||
if (hypervisor_)
|
||||
{
|
||||
//
|
||||
// Stopping the hypervisor is not strictly needed here -
|
||||
// the destroy() method stops the hypervisor if necessary.
|
||||
//
|
||||
if (hypervisor_->is_started())
|
||||
{
|
||||
hypervisor_->stop();
|
||||
}
|
||||
|
||||
hypervisor_->destroy();
|
||||
delete hypervisor_;
|
||||
}
|
||||
hvpp::hypervisor::stop();
|
||||
|
||||
//
|
||||
// Destroy VM-exit handler.
|
||||
|
|
@ -149,7 +110,6 @@ namespace driver
|
|||
//
|
||||
std::get<vmexit_stats_handler>(vmexit_handler_->handlers).dump();
|
||||
|
||||
vmexit_handler_->destroy();
|
||||
delete vmexit_handler_;
|
||||
}
|
||||
|
||||
|
|
@ -158,7 +118,6 @@ namespace driver
|
|||
//
|
||||
if (device_)
|
||||
{
|
||||
device_->destroy();
|
||||
delete device_;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,8 +10,6 @@
|
|||
//
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
PHVPP Hypervisor;
|
||||
|
||||
VOID
|
||||
NTAPI
|
||||
DriverUnload(
|
||||
|
|
@ -20,7 +18,7 @@ DriverUnload(
|
|||
{
|
||||
UNREFERENCED_PARAMETER(DriverObject);
|
||||
|
||||
HvppDestroy(Hypervisor);
|
||||
HvppDestroy();
|
||||
}
|
||||
|
||||
NTSTATUS
|
||||
|
|
@ -36,7 +34,7 @@ DriverEntry(
|
|||
|
||||
DriverObject->DriverUnload = &DriverUnload;
|
||||
|
||||
Status = HvppInitialize(&Hypervisor);
|
||||
Status = HvppInitialize();
|
||||
|
||||
if (!NT_SUCCESS(Status))
|
||||
{
|
||||
|
|
@ -49,11 +47,11 @@ DriverEntry(
|
|||
[VMEXIT_REASON_EPT_VIOLATION] = &HvppHandleEptViolation,
|
||||
} };
|
||||
|
||||
Status = HvppStart(Hypervisor, &VmExitHandler);
|
||||
Status = HvppStart(&VmExitHandler);
|
||||
|
||||
if (!NT_SUCCESS(Status))
|
||||
{
|
||||
HvppDestroy(Hypervisor);
|
||||
HvppDestroy();
|
||||
return Status;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue