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:
Petr Benes 2019-03-29 17:24:06 +01:00
parent b8aaa631f3
commit 8f9e1ace68
53 changed files with 1224 additions and 1176 deletions

View file

@ -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

View file

@ -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" />

View file

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

View file

@ -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.

View file

@ -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.

View file

@ -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_;
};
}

View file

@ -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

View file

@ -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

View file

@ -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();
}
}

View file

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

View file

@ -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];

View file

@ -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 "";
}
}

View file

@ -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)];

View file

@ -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)
{

View file

@ -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{}); }
}

View file

@ -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)
{

View file

@ -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)
{

View file

@ -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)
{

View file

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

View file

@ -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;

View file

@ -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

View file

@ -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.

View file

@ -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_);
}
}
}

View file

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

View file

@ -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,

View file

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

View file

@ -6,7 +6,7 @@
#include <cstdint>
namespace memory_manager
namespace mm
{
namespace detail
{

View file

@ -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)(); }); }
}

View file

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

View file

@ -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;

View file

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

View file

@ -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

View file

@ -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
{

View file

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

View file

@ -4,7 +4,7 @@
#define HVPP_MEMORY_TAG 'ppvh'
namespace memory_manager::detail
namespace mm::detail
{
auto system_allocate(size_t size) noexcept -> void*
{

View file

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

View file

@ -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_;

View file

@ -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, ...)

View file

@ -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_;
};
}

View file

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

View file

@ -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:

View file

@ -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)]);

View file

@ -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;

View file

@ -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:

View file

@ -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;

View file

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

View file

@ -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;

View file

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

View file

@ -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>

View file

@ -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,

View file

@ -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_;
}

View file

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