mirror of
https://github.com/wbenny/hvpp
synced 2026-08-17 04:26:04 -04:00
introduce new flexible memory allocator
Previous "custom" allocator was non-replaceable and hard-coded. The "custom allocator" term has been replaced with "hypervisor allocator", as it more closely reflects when is this allocator used. Most importantly, this change allows anyone to write their own allocator/heap manager (or use existing one, such as dlmalloc) and bind it as the hypervisor allocator.
This commit is contained in:
parent
82b7070c8b
commit
d8eb913052
16 changed files with 938 additions and 522 deletions
|
|
@ -104,6 +104,9 @@
|
|||
<ClCompile Include="hvpp\hvpp.cpp" />
|
||||
<ClCompile Include="hvpp\hypervisor.cpp" />
|
||||
<ClCompile Include="hvpp\ia32\memory.cpp" />
|
||||
<ClCompile Include="hvpp\lib\mm\memory_allocator\hypervisor_memory_allocator.cpp" />
|
||||
<ClCompile Include="hvpp\lib\mm\memory_allocator\system_memory_allocator.cpp" />
|
||||
<ClCompile Include="hvpp\lib\mm\memory_allocator\win32\system_memory_allocator.cpp" />
|
||||
<ClCompile Include="hvpp\lib\mm\memory_mapper.cpp" />
|
||||
<ClCompile Include="hvpp\lib\mm\memory_translator.cpp" />
|
||||
<ClCompile Include="hvpp\lib\mm\win32\memory_mapper.cpp" />
|
||||
|
|
@ -128,7 +131,6 @@
|
|||
<ClCompile Include="hvpp\lib\win32\debugger.cpp" />
|
||||
<ClCompile Include="hvpp\lib\win32\device.cpp" />
|
||||
<ClCompile Include="hvpp\lib\win32\log.cpp" />
|
||||
<ClCompile Include="hvpp\lib\win32\mm.cpp" />
|
||||
<ClCompile Include="hvpp\lib\win32\mp.cpp" />
|
||||
<ClCompile Include="hvpp\lib\win32\tracelog.cpp">
|
||||
<ConformanceMode Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">false</ConformanceMode>
|
||||
|
|
@ -146,6 +148,9 @@
|
|||
<ClInclude Include="hvpp\config.h" />
|
||||
<ClInclude Include="hvpp\ept.h" />
|
||||
<ClInclude Include="hvpp\hypervisor.h" />
|
||||
<ClInclude Include="hvpp\lib\mm\memory_allocator.h" />
|
||||
<ClInclude Include="hvpp\lib\mm\memory_allocator\hypervisor_memory_allocator.h" />
|
||||
<ClInclude Include="hvpp\lib\mm\memory_allocator\system_memory_allocator.h" />
|
||||
<ClInclude Include="hvpp\lib\mm\memory_mapper.h" />
|
||||
<ClInclude Include="hvpp\lib\mm\memory_translator.h" />
|
||||
<ClInclude Include="hvpp\lib\mm\mtrr_descriptor.h" />
|
||||
|
|
|
|||
|
|
@ -76,6 +76,15 @@
|
|||
<Filter Include="Source Files\hvpp\lib\mm\win32">
|
||||
<UniqueIdentifier>{c53a048f-d7da-48c6-88a5-fa1ed9bfc1ed}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Header Files\hvpp\lib\mm\memory_allocator">
|
||||
<UniqueIdentifier>{feefc873-6b26-444a-a474-21d23ab84328}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Source Files\hvpp\lib\mm\memory_allocator">
|
||||
<UniqueIdentifier>{63686d7b-f1d9-4d9b-85af-efede73a8e9f}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Source Files\hvpp\lib\mm\memory_allocator\win32">
|
||||
<UniqueIdentifier>{095769d8-145a-47c6-abfa-d8a363b91f9b}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="hvpp\lib\mm.cpp">
|
||||
|
|
@ -114,9 +123,6 @@
|
|||
<ClCompile Include="hvpp\lib\log.cpp">
|
||||
<Filter>Source Files\hvpp\lib</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="hvpp\lib\win32\mm.cpp">
|
||||
<Filter>Source Files\hvpp\lib\win32</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="hvpp\lib\driver.cpp">
|
||||
<Filter>Source Files\hvpp\lib</Filter>
|
||||
</ClCompile>
|
||||
|
|
@ -162,6 +168,15 @@
|
|||
<ClCompile Include="hvpp\lib\mm\win32\physical_memory_descriptor.cpp">
|
||||
<Filter>Source Files\hvpp\lib\mm\win32</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="hvpp\lib\mm\memory_allocator\hypervisor_memory_allocator.cpp">
|
||||
<Filter>Source Files\hvpp\lib\mm\memory_allocator</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="hvpp\lib\mm\memory_allocator\system_memory_allocator.cpp">
|
||||
<Filter>Source Files\hvpp\lib\mm\memory_allocator</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="hvpp\lib\mm\memory_allocator\win32\system_memory_allocator.cpp">
|
||||
<Filter>Source Files\hvpp\lib\mm\memory_allocator\win32</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="hvpp\lib\bitmap.h">
|
||||
|
|
@ -344,6 +359,15 @@
|
|||
<ClInclude Include="hvpp\lib\deque.h">
|
||||
<Filter>Header Files\hvpp\lib</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="hvpp\lib\mm\memory_allocator\hypervisor_memory_allocator.h">
|
||||
<Filter>Header Files\hvpp\lib\mm\memory_allocator</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="hvpp\lib\mm\memory_allocator\system_memory_allocator.h">
|
||||
<Filter>Header Files\hvpp\lib\mm\memory_allocator</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="hvpp\lib\mm\memory_allocator.h">
|
||||
<Filter>Header Files\hvpp\lib\mm</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<MASM Include="hvpp\ia32\context.asm">
|
||||
|
|
|
|||
|
|
@ -218,6 +218,19 @@ HvppInitialize(
|
|||
return STATUS_INSUFFICIENT_RESOURCES;
|
||||
}
|
||||
|
||||
if (auto err = driver::common::system_allocator_default_initialize())
|
||||
{
|
||||
driver::common::destroy();
|
||||
return STATUS_INSUFFICIENT_RESOURCES;
|
||||
}
|
||||
|
||||
if (auto err = driver::common::hypervisor_allocator_default_initialize())
|
||||
{
|
||||
driver::common::system_allocator_default_destroy();
|
||||
driver::common::destroy();
|
||||
return STATUS_INSUFFICIENT_RESOURCES;
|
||||
}
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
|
|
@ -232,6 +245,8 @@ HvppDestroy(
|
|||
//
|
||||
|
||||
driver::common::destroy();
|
||||
driver::common::hypervisor_allocator_default_destroy();
|
||||
driver::common::system_allocator_default_destroy();
|
||||
}
|
||||
|
||||
NTSTATUS
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include "ia32/cpuid/cpuid_eax_01.h"
|
||||
#include "lib/assert.h"
|
||||
#include "lib/driver.h"
|
||||
#include "lib/log.h"
|
||||
#include "lib/mm.h"
|
||||
#include "lib/mp.h"
|
||||
|
|
@ -102,6 +103,28 @@ namespace hvpp::hypervisor
|
|||
vcpu_t(handler);
|
||||
});
|
||||
|
||||
//
|
||||
// Check if hypervisor-allocator has been set.
|
||||
//
|
||||
// If the driver is calling `hypervisor::start()' from
|
||||
// the `driver::initialize()' method without manually
|
||||
// setting the allocator, then the allocator will be empty.
|
||||
//
|
||||
if (!mm::hypervisor_allocator())
|
||||
{
|
||||
//
|
||||
// Hypervisor-allocator has not been set - create default one.
|
||||
//
|
||||
// Note:
|
||||
// Default hypervisor allocator is automatically destroyed
|
||||
// in the `driver::common::destroy()' function.
|
||||
//
|
||||
if (auto err = driver::common::hypervisor_allocator_default_initialize())
|
||||
{
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Check that CPU supports all required features to
|
||||
// run this hypervisor.
|
||||
|
|
@ -160,7 +183,7 @@ namespace hvpp::hypervisor
|
|||
// Destroy array of VCPUs.
|
||||
//
|
||||
std::destroy_n(global.vcpu_list, mp::cpu_count());
|
||||
delete global.vcpu_list;
|
||||
delete static_cast<void*>(global.vcpu_list);
|
||||
|
||||
global.vcpu_list = nullptr;
|
||||
|
||||
|
|
|
|||
|
|
@ -3,17 +3,22 @@
|
|||
#include "assert.h"
|
||||
#include "mm.h"
|
||||
#include "mp.h"
|
||||
#include "object.h"
|
||||
#include "log.h"
|
||||
|
||||
#include <cinttypes>
|
||||
|
||||
namespace driver::common
|
||||
{
|
||||
void* system_memory_ = nullptr;
|
||||
size_t system_memory_size_ = 0;
|
||||
static driver_initialize_fn driver_initialize_;
|
||||
static driver_destroy_fn driver_destroy_;
|
||||
|
||||
driver_initialize_fn driver_initialize_;
|
||||
driver_destroy_fn driver_destroy_;
|
||||
static object_t<mm::system_memory_allocator> system_memory_allocator_object_;
|
||||
static object_t<mm::hypervisor_memory_allocator> hypervisor_memory_allocator_object_;
|
||||
|
||||
static bool has_default_hypervisor_allocator_ = false;
|
||||
static void* hypervisor_allocator_base_address_ = nullptr;
|
||||
static size_t hypervisor_allocator_capacity_ = 0;
|
||||
|
||||
auto
|
||||
initialize(
|
||||
|
|
@ -21,9 +26,6 @@ namespace driver::common
|
|||
driver_destroy_fn driver_destroy
|
||||
) noexcept -> error_code_t
|
||||
{
|
||||
hvpp_assert(system_memory_ == nullptr);
|
||||
hvpp_assert(system_memory_size_ == 0);
|
||||
|
||||
//
|
||||
// Either both must be set or both must be nullptr,
|
||||
// nothing else.
|
||||
|
|
@ -54,59 +56,46 @@ namespace driver::common
|
|||
mm::paging_descriptor().dump();
|
||||
|
||||
//
|
||||
// Estimate required memory size.
|
||||
// If hypervisor begins to run out of memory, required_memory_size
|
||||
// is the right variable to adjust.
|
||||
// Initialize default system allocator.
|
||||
//
|
||||
// Default required memory size is 34MB per CPU.
|
||||
//
|
||||
const auto required_memory_size = (
|
||||
//
|
||||
// Estimated EPT size:
|
||||
// Make space for 2MB EPT entries for 512 GB of the physical
|
||||
// memory. Each EPT entry has 8 bytes.
|
||||
// 512GB / 2MB * 8 = 256kb * 8 = 2MB per CPU.
|
||||
//
|
||||
((512ull * 1024 * 1024 * 1024) / (2ull * 1024 * 1024) * 8)
|
||||
|
||||
+
|
||||
|
||||
//
|
||||
// Additional 32MB per CPU.
|
||||
//
|
||||
(32ull * 1024 * 1024)
|
||||
) * mp::cpu_count();
|
||||
|
||||
//
|
||||
// Round up to page boundary.
|
||||
//
|
||||
system_memory_size_ = ia32::round_to_pages(required_memory_size);
|
||||
|
||||
hvpp_info("Number of processors: %u", mp::cpu_count());
|
||||
hvpp_info("Reserved memory: %" PRIu64 " MB",
|
||||
system_memory_size_ / 1024 / 1024);
|
||||
|
||||
//
|
||||
// Allocate memory.
|
||||
//
|
||||
system_memory_ = mm::system_allocate(required_memory_size);
|
||||
|
||||
if (!system_memory_)
|
||||
{
|
||||
return make_error_code_t(std::errc::not_enough_memory);
|
||||
}
|
||||
|
||||
//
|
||||
// Assign allocated memory to the memory manager.
|
||||
//
|
||||
if (auto err = mm::assign(system_memory_, system_memory_size_))
|
||||
if (auto err = system_allocator_default_initialize())
|
||||
{
|
||||
return err;
|
||||
}
|
||||
|
||||
return driver_initialize_
|
||||
? driver_initialize_()
|
||||
: error_code_t{};
|
||||
//
|
||||
// If driver doesn't have initialize() function, we're finished.
|
||||
//
|
||||
if (!driver_initialize_)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
//
|
||||
// ...otherwise, call the provided initialize() function.
|
||||
//
|
||||
if (auto err = driver_initialize_())
|
||||
{
|
||||
return err;
|
||||
}
|
||||
|
||||
//
|
||||
// Check if hypervisor allocator has been set.
|
||||
//
|
||||
if (mm::hypervisor_allocator())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
//
|
||||
// ...if not, create default hypervisor allocator.
|
||||
//
|
||||
if (auto err = hypervisor_allocator_default_initialize())
|
||||
{
|
||||
return err;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void
|
||||
|
|
@ -126,12 +115,146 @@ namespace driver::common
|
|||
mm::destroy();
|
||||
logger::destroy();
|
||||
|
||||
//
|
||||
// Destroy default hypervisor allocator (if constructed).
|
||||
//
|
||||
if (has_default_hypervisor_allocator_)
|
||||
{
|
||||
hypervisor_allocator_default_destroy();
|
||||
}
|
||||
|
||||
//
|
||||
// At last, destroy default system allocator.
|
||||
//
|
||||
system_allocator_default_destroy();
|
||||
}
|
||||
|
||||
auto system_allocator_default_initialize() noexcept -> error_code_t
|
||||
{
|
||||
//
|
||||
// Construct and assign system allocator object.
|
||||
//
|
||||
system_memory_allocator_object_.initialize();
|
||||
mm::system_allocator(&*system_memory_allocator_object_);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void system_allocator_default_destroy() noexcept
|
||||
{
|
||||
//
|
||||
// Unassign system allocator and destroy the object.
|
||||
//
|
||||
mm::system_allocator(nullptr);
|
||||
system_memory_allocator_object_.destroy();
|
||||
}
|
||||
|
||||
auto hypervisor_allocator_default_initialize() noexcept -> error_code_t
|
||||
{
|
||||
hvpp_assert(hypervisor_allocator_base_address_ == nullptr);
|
||||
hvpp_assert(hypervisor_allocator_capacity_ == 0);
|
||||
|
||||
//
|
||||
// Construct hypervisor allocator object.
|
||||
//
|
||||
hypervisor_memory_allocator_object_.initialize();
|
||||
|
||||
hypervisor_allocator_capacity_ = hypervisor_allocator_recommended_capacity();
|
||||
|
||||
hvpp_info("Number of processors: %u", mp::cpu_count());
|
||||
hvpp_info("Reserved memory: %" PRIu64 " MB",
|
||||
hypervisor_allocator_capacity_ / 1024 / 1024);
|
||||
|
||||
//
|
||||
// Allocate memory.
|
||||
//
|
||||
hypervisor_allocator_base_address_ = mm::system_allocator()->allocate(hypervisor_allocator_capacity_);
|
||||
|
||||
if (!hypervisor_allocator_base_address_)
|
||||
{
|
||||
return make_error_code_t(std::errc::not_enough_memory);
|
||||
}
|
||||
|
||||
//
|
||||
// Attach allocated memory.
|
||||
//
|
||||
if (auto err = hypervisor_memory_allocator_object_->attach(hypervisor_allocator_base_address_, hypervisor_allocator_capacity_))
|
||||
{
|
||||
return err;
|
||||
}
|
||||
|
||||
//
|
||||
// Assign allocator.
|
||||
//
|
||||
mm::hypervisor_allocator(&*hypervisor_memory_allocator_object_);
|
||||
|
||||
has_default_hypervisor_allocator_ = true;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void hypervisor_allocator_default_destroy() noexcept
|
||||
{
|
||||
if (!hypervisor_allocator_base_address_)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mm::hypervisor_allocator())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Unassign allocator.
|
||||
//
|
||||
mm::hypervisor_allocator(nullptr);
|
||||
|
||||
//
|
||||
// Detach allocated memory.
|
||||
//
|
||||
hypervisor_memory_allocator_object_->detach();
|
||||
|
||||
//
|
||||
// Destroy object.
|
||||
//
|
||||
hypervisor_memory_allocator_object_.destroy();
|
||||
|
||||
//
|
||||
// Return allocated memory back to the system.
|
||||
//
|
||||
if (system_memory_)
|
||||
{
|
||||
mm::system_free(system_memory_);
|
||||
}
|
||||
mm::system_allocator()->free(hypervisor_allocator_base_address_);
|
||||
}
|
||||
|
||||
auto hypervisor_allocator_recommended_capacity() noexcept -> size_t
|
||||
{
|
||||
//
|
||||
// Estimate required memory size.
|
||||
// If hypervisor begins to run out of memory, required_memory_size
|
||||
// is the right variable to adjust.
|
||||
//
|
||||
// Default required memory size is 34MB per CPU.
|
||||
//
|
||||
const auto recommended_memory_size = (
|
||||
//
|
||||
// Estimated EPT size:
|
||||
// Make space for 2MB EPT entries for 512 GB of the physical
|
||||
// memory. Each EPT entry has 8 bytes.
|
||||
// 512GB / 2MB * 8 = 256kb * 8 = 2MB per CPU.
|
||||
//
|
||||
((512ull * 1024 * 1024 * 1024) / (2ull * 1024 * 1024) * 8)
|
||||
|
||||
+
|
||||
|
||||
//
|
||||
// Additional 32MB per CPU.
|
||||
//
|
||||
(32ull * 1024 * 1024)
|
||||
) * mp::cpu_count();
|
||||
|
||||
//
|
||||
// Round up to page boundary.
|
||||
//
|
||||
return ia32::round_to_pages(recommended_memory_size);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,14 @@ namespace driver
|
|||
) noexcept -> error_code_t;
|
||||
|
||||
void destroy() noexcept;
|
||||
|
||||
auto hypervisor_allocator_recommended_capacity() noexcept -> size_t;
|
||||
|
||||
auto system_allocator_default_initialize() noexcept -> error_code_t;
|
||||
void system_allocator_default_destroy() noexcept;
|
||||
|
||||
auto hypervisor_allocator_default_initialize() noexcept -> error_code_t;
|
||||
void hypervisor_allocator_default_destroy() noexcept;
|
||||
}
|
||||
|
||||
auto initialize() noexcept -> error_code_t;
|
||||
|
|
|
|||
|
|
@ -1,90 +1,32 @@
|
|||
#include "mm.h"
|
||||
|
||||
#include "hvpp/ia32/memory.h"
|
||||
#include "hvpp/config.h"
|
||||
|
||||
#include "assert.h"
|
||||
#include "bitmap.h"
|
||||
#include "object.h"
|
||||
#include "spinlock.h"
|
||||
#include "mp.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <mutex>
|
||||
|
||||
//
|
||||
// Simple memory manager implementation.
|
||||
//
|
||||
// Because in VM-exits it is very dangerous to call OS
|
||||
// functions for memory (de)allocation (they can cause
|
||||
// IPIs and/or TLB flush), the hypervisor has its own
|
||||
// simple memory manager. The memory manager should be
|
||||
// the very first thing to initialize.
|
||||
//
|
||||
// Memory manager is provided memory space on which it
|
||||
// can operate. Small part from this space is reserved
|
||||
// for the page bitmap and page allocation map.
|
||||
//
|
||||
// Page bitmap sets bit 1 at page offset, if the page is
|
||||
// allocated (e.g.: if 4th page (at base_address + 4*PAGE_SIZE)
|
||||
// is allocated, 4th bit in this bitmap is set).
|
||||
// On deallocation, corresponding bit is reset to 0.
|
||||
//
|
||||
// Page allocation map stores number of pages allocated
|
||||
// for the particular address (e.g.: allocate(8192) returned
|
||||
// (base_address + 4*PAGE_SIZE), which is 2 pages, therefore
|
||||
// page_allocation_map[4] == 2.
|
||||
// On deallocation, corresponding number in the map is reset
|
||||
// to 0.
|
||||
//
|
||||
// Note: allocations are always page-aligned - therefore
|
||||
// allocation for even 1 byte results in waste of
|
||||
// 4096 bytes.
|
||||
//
|
||||
#include "../config.h"
|
||||
|
||||
namespace mm
|
||||
{
|
||||
using pgbmp_t = object_t<bitmap<>>;
|
||||
using pgmap_t = uint16_t;
|
||||
|
||||
struct global_t
|
||||
{
|
||||
uint8_t* base_address; // Pool base address
|
||||
size_t available_size; // Available memory in the pool
|
||||
memory_allocator* allocator[HVPP_MAX_CPU];
|
||||
|
||||
pgbmp_t page_bitmap; // Bitmap holding used pages
|
||||
int page_bitmap_buffer_size; //
|
||||
|
||||
pgmap_t* page_allocation_map; // Map holding number of allocated pages
|
||||
int page_allocation_map_size; //
|
||||
|
||||
int last_page_offset; // Last returned page offset - used as hint
|
||||
|
||||
size_t allocated_bytes;
|
||||
size_t free_bytes;
|
||||
|
||||
allocator_t allocator[HVPP_MAX_CPU];
|
||||
memory_allocator* system_allocator;
|
||||
memory_allocator* custom_allocator;
|
||||
|
||||
object_t<paging_descriptor_t> paging_descriptor;
|
||||
object_t<physical_memory_descriptor_t> physical_memory_descriptor;
|
||||
object_t<mtrr_descriptor_t> mtrr_descriptor;
|
||||
|
||||
object_t<spinlock> lock;
|
||||
};
|
||||
|
||||
global_t global;
|
||||
|
||||
const allocator_t system_allocator = { &system_allocate, &system_free };
|
||||
const allocator_t custom_allocator = { &allocate, &free };
|
||||
|
||||
allocator_guard::allocator_guard() noexcept
|
||||
: allocator_guard(custom_allocator)
|
||||
: allocator_guard(global.custom_allocator)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
allocator_guard::allocator_guard(const allocator_t& new_allocator) noexcept
|
||||
allocator_guard::allocator_guard(memory_allocator* new_allocator) noexcept
|
||||
: previous_allocator_(allocator())
|
||||
{
|
||||
allocator(new_allocator);
|
||||
|
|
@ -98,25 +40,13 @@ namespace mm
|
|||
auto initialize() noexcept -> error_code_t
|
||||
{
|
||||
//
|
||||
// Initialize physical memory descriptor and MTRRs.
|
||||
// Initialize paging descriptor, physical memory descriptor
|
||||
// and MTRR descriptor.
|
||||
//
|
||||
global.paging_descriptor.initialize();
|
||||
global.physical_memory_descriptor.initialize();
|
||||
global.mtrr_descriptor.initialize();
|
||||
|
||||
//
|
||||
// Initialize lock.
|
||||
//
|
||||
global.lock.initialize();
|
||||
|
||||
//
|
||||
// Set system allocator by default.
|
||||
//
|
||||
for (auto& allocator_item : global.allocator)
|
||||
{
|
||||
allocator_item = system_allocator;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
|
|
@ -124,327 +54,45 @@ namespace mm
|
|||
{
|
||||
//
|
||||
// Destroy all objects.
|
||||
// Note that this method doesn't acquire the lock and
|
||||
// assumes all allocations has been already freed.
|
||||
//
|
||||
global.mtrr_descriptor.destroy();
|
||||
global.physical_memory_descriptor.destroy();
|
||||
global.paging_descriptor.destroy();
|
||||
global.lock.destroy();
|
||||
|
||||
//
|
||||
// If no memory has been assigned - leave.
|
||||
//
|
||||
if (!global.base_address)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Mark memory of page_bitmap and page_allocation_map
|
||||
// as freed.
|
||||
//
|
||||
// Note that everything "free" does is clear bits in
|
||||
// page_bitmap and sets 0 to particular page_allocation_map
|
||||
// items.
|
||||
//
|
||||
// These two calls are needed to assure that the next two
|
||||
// asserts below will pass.
|
||||
//
|
||||
free(global.page_bitmap->buffer());
|
||||
free(global.page_allocation_map);
|
||||
|
||||
//
|
||||
// Checks for memory leaks.
|
||||
//
|
||||
hvpp_assert(global.page_bitmap->all_clear());
|
||||
|
||||
//
|
||||
// Checks for allocator corruption.
|
||||
//
|
||||
hvpp_assert(std::all_of(
|
||||
global.page_allocation_map,
|
||||
global.page_allocation_map + global.page_allocation_map_size / sizeof(pgmap_t),
|
||||
[](auto page_count) { return page_count == 0; }));
|
||||
|
||||
global.base_address = nullptr;
|
||||
global.available_size = 0;
|
||||
|
||||
global.page_bitmap.destroy();
|
||||
global.page_bitmap_buffer_size = 0;
|
||||
|
||||
global.page_allocation_map = nullptr;
|
||||
global.page_allocation_map_size = 0;
|
||||
|
||||
global.last_page_offset = 0;
|
||||
global.allocated_bytes = 0;
|
||||
global.free_bytes = 0;
|
||||
}
|
||||
|
||||
auto assign(void* address, size_t size) noexcept -> error_code_t
|
||||
auto system_allocator() noexcept -> memory_allocator*
|
||||
{
|
||||
if (size < page_size * 3)
|
||||
{
|
||||
//
|
||||
// We need at least 3 pages (see explanation below).
|
||||
//
|
||||
hvpp_assert(0);
|
||||
return make_error_code_t(std::errc::invalid_argument);
|
||||
}
|
||||
|
||||
//
|
||||
// If the provided address is not page aligned, align it
|
||||
// to the next page.
|
||||
//
|
||||
if (byte_offset(address) != 0)
|
||||
{
|
||||
const auto lost_bytes = byte_offset(address);
|
||||
|
||||
address = reinterpret_cast<uint8_t*>(page_align(address)) + page_size;
|
||||
|
||||
//
|
||||
// Subtract amount of "lost" bytes due to alignment.
|
||||
//
|
||||
size -= lost_bytes;
|
||||
}
|
||||
|
||||
//
|
||||
// Align size to the page boundary.
|
||||
//
|
||||
size = page_align(size);
|
||||
|
||||
//
|
||||
// Check again.
|
||||
//
|
||||
if (size < page_size * 3)
|
||||
{
|
||||
hvpp_assert(0);
|
||||
return make_error_code_t(std::errc::invalid_argument);
|
||||
}
|
||||
|
||||
//
|
||||
// Address is page-aligned, size is page-aligned, and all
|
||||
// requirements are met. Proceed with initialization.
|
||||
//
|
||||
|
||||
//
|
||||
// The provided memory is split up to 3 parts:
|
||||
// 1. page bitmap - stores information if page is allocated
|
||||
// or not
|
||||
// 2. page count - stores information how many consecutive
|
||||
// pages has been allocated
|
||||
// 3. memory pool - this is the memory which will be provided
|
||||
//
|
||||
// For (1), there is taken (size / PAGE_SIZE / 8) bytes from the
|
||||
// provided memory space.
|
||||
// For (2), there is taken (size / PAGE_SIZE * sizeof(pgmap_t))
|
||||
// bytes from the provided memory space.
|
||||
// The rest memory is used for (3).
|
||||
//
|
||||
// This should account for ~93% of the provided memory space (if
|
||||
// it is big enough, e.g.: 32MB).
|
||||
//
|
||||
|
||||
//
|
||||
// Construct the page bitmap.
|
||||
//
|
||||
uint8_t* page_bitmap_buffer = reinterpret_cast<uint8_t*>(address);
|
||||
global.page_bitmap_buffer_size = static_cast<int>(round_to_pages(size / page_size / 8));
|
||||
memset(page_bitmap_buffer, 0, global.page_bitmap_buffer_size);
|
||||
|
||||
int page_bitmap_size_in_bits = static_cast<int>(size / page_size);
|
||||
global.page_bitmap.initialize(page_bitmap_buffer, page_bitmap_size_in_bits);
|
||||
|
||||
//
|
||||
// Construct the page allocation map.
|
||||
//
|
||||
global.page_allocation_map = reinterpret_cast<pgmap_t*>(page_bitmap_buffer + global.page_bitmap_buffer_size);
|
||||
global.page_allocation_map_size = static_cast<int>(round_to_pages(size / page_size) * sizeof(pgmap_t));
|
||||
memset(global.page_allocation_map, 0, global.page_allocation_map_size);
|
||||
|
||||
//
|
||||
// Compute available memory.
|
||||
//
|
||||
global.base_address = reinterpret_cast<uint8_t*>(address);
|
||||
global.available_size = size;
|
||||
|
||||
//
|
||||
// Mark memory of page_bitmap and page_allocation_map as allocated.
|
||||
// The return value of these allocations should return the exact
|
||||
// address of page_bitmap_buffer and page_allocation_map.
|
||||
//
|
||||
void* page_bitmap_buffer_tmp = allocate(global.page_bitmap_buffer_size);
|
||||
void* page_allocation_map_tmp = allocate(global.page_allocation_map_size);
|
||||
|
||||
hvpp_assert(reinterpret_cast<uintptr_t>( page_bitmap_buffer) == reinterpret_cast<uintptr_t>(page_bitmap_buffer_tmp));
|
||||
hvpp_assert(reinterpret_cast<uintptr_t>(global.page_allocation_map) == reinterpret_cast<uintptr_t>(page_allocation_map_tmp));
|
||||
|
||||
(void)(page_bitmap_buffer_tmp);
|
||||
(void)(page_allocation_map_tmp);
|
||||
|
||||
//
|
||||
// Initialize memory pool with garbage.
|
||||
// This should help with debugging uninitialized variables
|
||||
// and class members.
|
||||
//
|
||||
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);
|
||||
|
||||
//
|
||||
// Set initial values of allocated/free bytes.
|
||||
//
|
||||
global.allocated_bytes = 0;
|
||||
global.free_bytes = size;
|
||||
|
||||
return {};
|
||||
return global.system_allocator;
|
||||
}
|
||||
|
||||
auto allocate(size_t size) noexcept -> void*
|
||||
void system_allocator(memory_allocator* new_allocator) noexcept
|
||||
{
|
||||
hvpp_assert(global.base_address != nullptr && global.available_size > 0);
|
||||
global.system_allocator = new_allocator;
|
||||
|
||||
//
|
||||
// Return at least 1 page, even if someone required 0.
|
||||
//
|
||||
if (size == 0)
|
||||
for (auto& allocator_item : global.allocator)
|
||||
{
|
||||
hvpp_assert(0);
|
||||
size = 1;
|
||||
allocator_item = global.system_allocator;
|
||||
}
|
||||
|
||||
int page_count = static_cast<int>(bytes_to_pages(size));
|
||||
|
||||
//
|
||||
// Check if the desired number of pages can fit into the
|
||||
// allocation map.
|
||||
//
|
||||
if (page_count > std::numeric_limits<pgmap_t>::max() - 1)
|
||||
{
|
||||
hvpp_assert(0);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int previous_page_offset;
|
||||
|
||||
{
|
||||
std::lock_guard _{ *global.lock };
|
||||
|
||||
global.last_page_offset = global.page_bitmap->find_first_clear(global.last_page_offset, page_count);
|
||||
|
||||
if (global.last_page_offset == -1)
|
||||
{
|
||||
global.last_page_offset = 0;
|
||||
global.last_page_offset = global.page_bitmap->find_first_clear(global.last_page_offset, page_count);
|
||||
|
||||
if (global.last_page_offset == -1)
|
||||
{
|
||||
//
|
||||
// Not enough memory...
|
||||
//
|
||||
hvpp_assert(0);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
global.page_bitmap->set(global.last_page_offset, page_count);
|
||||
global.page_allocation_map[global.last_page_offset] = static_cast<pgmap_t>(page_count);
|
||||
|
||||
previous_page_offset = global.last_page_offset;
|
||||
global.last_page_offset += page_count;
|
||||
|
||||
global.allocated_bytes += page_count * page_size;
|
||||
global.free_bytes -= page_count * page_size;
|
||||
}
|
||||
|
||||
//
|
||||
// Return the final address.
|
||||
// Note that we're not under lock here - we don't need it, because
|
||||
// everything neccessary has been done (bitmap + page allocation map
|
||||
// manipulation).
|
||||
//
|
||||
return global.base_address + previous_page_offset * page_size;
|
||||
}
|
||||
|
||||
void free(void* address) noexcept
|
||||
auto hypervisor_allocator() noexcept -> memory_allocator*
|
||||
{
|
||||
//
|
||||
// Our allocator always provides page-aligned memory.
|
||||
//
|
||||
hvpp_assert(byte_offset(address) == 0);
|
||||
|
||||
const auto offset = static_cast<int>(bytes_to_pages(reinterpret_cast<uint8_t*>(address) - global.base_address));
|
||||
|
||||
if (address == nullptr)
|
||||
{
|
||||
//
|
||||
// Return immediatelly if we're trying to free NULL.
|
||||
//
|
||||
// hvpp_assert(0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (size_t(offset) * page_size > global.available_size)
|
||||
{
|
||||
//
|
||||
// We don't own this memory.
|
||||
//
|
||||
hvpp_assert(0);
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard _{ *global.lock };
|
||||
|
||||
if (global.page_allocation_map[offset] == 0)
|
||||
{
|
||||
//
|
||||
// This memory wasn't allocated.
|
||||
//
|
||||
hvpp_assert(0);
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Clear number of allocated pages.
|
||||
//
|
||||
const auto page_count = static_cast<int>(global.page_allocation_map[offset]);
|
||||
global.page_allocation_map[offset] = 0;
|
||||
|
||||
//
|
||||
// Clear pages in the bitmap.
|
||||
//
|
||||
global.page_bitmap->clear(offset, page_count);
|
||||
|
||||
global.allocated_bytes -= page_count * page_size;
|
||||
global.free_bytes += page_count * page_size;
|
||||
return global.custom_allocator;
|
||||
}
|
||||
|
||||
auto system_allocate(size_t size) noexcept -> void*
|
||||
void hypervisor_allocator(memory_allocator* new_allocator) noexcept
|
||||
{
|
||||
return detail::system_allocate(size);
|
||||
global.custom_allocator = new_allocator;
|
||||
}
|
||||
|
||||
void system_free(void* address) noexcept
|
||||
{
|
||||
detail::system_free(address);
|
||||
}
|
||||
|
||||
auto allocated_bytes() noexcept -> size_t
|
||||
{
|
||||
return global.allocated_bytes;
|
||||
}
|
||||
|
||||
auto free_bytes() noexcept -> size_t
|
||||
{
|
||||
return global.free_bytes;
|
||||
}
|
||||
|
||||
auto allocator() noexcept -> const allocator_t&
|
||||
auto allocator() noexcept -> memory_allocator*
|
||||
{
|
||||
return global.allocator[mp::cpu_index()];
|
||||
}
|
||||
|
||||
void allocator(const allocator_t& new_allocator) noexcept
|
||||
void allocator(memory_allocator* new_allocator) noexcept
|
||||
{
|
||||
hvpp_assert(new_allocator);
|
||||
global.allocator[mp::cpu_index()] = new_allocator;
|
||||
}
|
||||
|
||||
|
|
@ -468,28 +116,32 @@ namespace detail
|
|||
{
|
||||
void* generic_allocate(size_t size) noexcept
|
||||
{
|
||||
return mm::global.allocator[mp::cpu_index()].allocate(size);
|
||||
return mm::global.allocator[mp::cpu_index()]->allocate(size);
|
||||
}
|
||||
|
||||
void* generic_allocate_aligned(size_t size, std::align_val_t alignment) noexcept
|
||||
{
|
||||
return mm::global.allocator[mp::cpu_index()]->allocate_aligned(size, static_cast<size_t>(alignment));
|
||||
}
|
||||
|
||||
void generic_free(void* address) noexcept
|
||||
{
|
||||
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);
|
||||
return mm::hypervisor_allocator()->contains(address)
|
||||
? mm::hypervisor_allocator()->free(address)
|
||||
: mm::system_allocator()->free(address);
|
||||
}
|
||||
}
|
||||
|
||||
void* operator new (size_t size) { return detail::generic_allocate(size); }
|
||||
void* operator new[](size_t size) { return detail::generic_allocate(size); }
|
||||
void* operator new (size_t size, std::align_val_t) { return detail::generic_allocate(size); }
|
||||
void* operator new[](size_t size, std::align_val_t) { return detail::generic_allocate(size); }
|
||||
void* operator new (size_t size) { return detail::generic_allocate (size); }
|
||||
void* operator new[](size_t size) { return detail::generic_allocate (size); }
|
||||
void* operator new (size_t size, std::align_val_t alignment) { return detail::generic_allocate_aligned(size, alignment); }
|
||||
void* operator new[](size_t size, std::align_val_t alignment) { return detail::generic_allocate_aligned(size, alignment); }
|
||||
|
||||
void operator delete (void* address) { detail::generic_free(address); }
|
||||
void operator delete[](void* address) { detail::generic_free(address); }
|
||||
void operator delete[](void* address, std::size_t) { detail::generic_free(address); }
|
||||
void operator delete (void* address, std::size_t) { detail::generic_free(address); }
|
||||
void operator delete (void* address, std::align_val_t) { detail::generic_free(address); }
|
||||
void operator delete[](void* address, std::align_val_t) { detail::generic_free(address); }
|
||||
void operator delete[](void* address, std::size_t, std::align_val_t) { detail::generic_free(address); }
|
||||
void operator delete (void* address, std::size_t, std::align_val_t) { detail::generic_free(address); }
|
||||
void operator delete (void* address) { detail::generic_free(address); }
|
||||
void operator delete[](void* address) { detail::generic_free(address); }
|
||||
void operator delete[](void* address, std::size_t) { detail::generic_free(address); }
|
||||
void operator delete (void* address, std::size_t) { detail::generic_free(address); }
|
||||
void operator delete (void* address, std::align_val_t) { detail::generic_free(address); }
|
||||
void operator delete[](void* address, std::align_val_t) { detail::generic_free(address); }
|
||||
void operator delete[](void* address, std::size_t, std::align_val_t) { detail::generic_free(address); }
|
||||
void operator delete (void* address, std::size_t, std::align_val_t) { detail::generic_free(address); }
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@
|
|||
|
||||
#include "error.h"
|
||||
|
||||
#include "mm/memory_allocator.h"
|
||||
#include "mm/memory_allocator/system_memory_allocator.h"
|
||||
#include "mm/memory_allocator/hypervisor_memory_allocator.h"
|
||||
#include "mm/paging_descriptor.h"
|
||||
#include "mm/physical_memory_descriptor.h"
|
||||
#include "mm/mtrr_descriptor.h"
|
||||
|
|
@ -11,58 +14,53 @@
|
|||
|
||||
namespace mm
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
auto system_allocate(size_t size) noexcept -> void*;
|
||||
void system_free(void* address) noexcept;
|
||||
}
|
||||
//
|
||||
// Initialize & destroy.
|
||||
//
|
||||
auto initialize() noexcept -> error_code_t;
|
||||
void destroy() noexcept;
|
||||
|
||||
using allocate_fn_t = void*(*)(size_t);
|
||||
using free_fn_t = void(*)(void*);
|
||||
//
|
||||
// System allocator.
|
||||
//
|
||||
auto system_allocator() noexcept -> memory_allocator*;
|
||||
void system_allocator(memory_allocator* new_allocator) noexcept;
|
||||
|
||||
struct allocator_t
|
||||
{
|
||||
allocate_fn_t allocate;
|
||||
free_fn_t free;
|
||||
};
|
||||
//
|
||||
// Hypervisor allocator.
|
||||
//
|
||||
auto hypervisor_allocator() noexcept -> memory_allocator*;
|
||||
void hypervisor_allocator(memory_allocator* new_allocator) noexcept;
|
||||
|
||||
//
|
||||
// Current allocator.
|
||||
//
|
||||
auto allocator() noexcept -> memory_allocator*;
|
||||
void allocator(memory_allocator* new_allocator) noexcept;
|
||||
|
||||
//
|
||||
// Descriptor getters.
|
||||
//
|
||||
auto paging_descriptor() noexcept -> const paging_descriptor_t&;
|
||||
auto physical_memory_descriptor() noexcept -> const physical_memory_descriptor_t&;
|
||||
auto mtrr_descriptor() noexcept -> const mtrr_descriptor_t&;
|
||||
|
||||
//
|
||||
// Allocator guard.
|
||||
//
|
||||
class allocator_guard
|
||||
{
|
||||
public:
|
||||
allocator_guard() noexcept;
|
||||
allocator_guard(const allocator_guard& other) noexcept = delete;
|
||||
allocator_guard(allocator_guard&& other) noexcept = delete;
|
||||
allocator_guard(const allocator_t& new_allocator) noexcept;
|
||||
allocator_guard(memory_allocator* new_allocator) noexcept;
|
||||
~allocator_guard() noexcept;
|
||||
|
||||
allocator_guard& operator=(const allocator_guard& other) noexcept = delete;
|
||||
allocator_guard& operator=(allocator_guard& other) noexcept = delete;
|
||||
allocator_guard& operator=(allocator_guard&& other) noexcept = delete;
|
||||
|
||||
private:
|
||||
allocator_t previous_allocator_;
|
||||
memory_allocator* previous_allocator_;
|
||||
};
|
||||
|
||||
extern const allocator_t system_allocator;
|
||||
extern const allocator_t custom_allocator;
|
||||
|
||||
auto initialize() noexcept -> error_code_t;
|
||||
void destroy() noexcept;
|
||||
|
||||
auto assign(void* address, size_t size) noexcept -> error_code_t;
|
||||
|
||||
auto allocate(size_t size) noexcept -> void*;
|
||||
void free(void* address) noexcept;
|
||||
|
||||
auto system_allocate(size_t size) noexcept -> void*;
|
||||
void system_free(void* address) noexcept;
|
||||
|
||||
auto allocated_bytes() noexcept -> size_t;
|
||||
auto free_bytes() noexcept -> size_t;
|
||||
|
||||
auto allocator() noexcept -> const allocator_t&;
|
||||
void allocator(const allocator_t& new_allocator) noexcept;
|
||||
|
||||
auto paging_descriptor() noexcept -> const paging_descriptor_t&;
|
||||
auto physical_memory_descriptor() noexcept -> const physical_memory_descriptor_t&;
|
||||
auto mtrr_descriptor() noexcept -> const mtrr_descriptor_t&;
|
||||
}
|
||||
|
|
|
|||
30
src/hvpp/hvpp/lib/mm/memory_allocator.h
Normal file
30
src/hvpp/hvpp/lib/mm/memory_allocator.h
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
#pragma once
|
||||
#include <cstddef> // size_t
|
||||
|
||||
#include "../error.h"
|
||||
|
||||
namespace mm
|
||||
{
|
||||
class memory_allocator
|
||||
{
|
||||
public:
|
||||
memory_allocator() noexcept = default;
|
||||
memory_allocator(const memory_allocator& other) noexcept = delete;
|
||||
memory_allocator(memory_allocator&& other) noexcept = delete;
|
||||
memory_allocator& operator=(const memory_allocator& other) noexcept = delete;
|
||||
memory_allocator& operator=(memory_allocator&& other) noexcept = delete;
|
||||
virtual ~memory_allocator() noexcept = default;
|
||||
|
||||
virtual auto attach(void* address, size_t size) noexcept -> error_code_t = 0;
|
||||
virtual void detach() noexcept = 0;
|
||||
|
||||
virtual auto allocate(size_t size) noexcept -> void* = 0;
|
||||
virtual auto allocate_aligned(size_t size, size_t alignment) noexcept -> void* = 0;
|
||||
virtual void free(void* address) noexcept = 0;
|
||||
|
||||
virtual bool contains(void* address) noexcept = 0;
|
||||
|
||||
virtual auto allocated_bytes() noexcept -> size_t = 0;
|
||||
virtual auto free_bytes() noexcept -> size_t = 0;
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,389 @@
|
|||
#include "hypervisor_memory_allocator.h"
|
||||
|
||||
#include "../../assert.h"
|
||||
#include "../../../config.h"
|
||||
#include "../../../ia32/memory.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <mutex>
|
||||
|
||||
//
|
||||
// Simple memory manager implementation.
|
||||
//
|
||||
// Because in VM-exits it is very dangerous to call OS
|
||||
// functions for memory (de)allocation (they can cause
|
||||
// IPIs and/or TLB flush), the hypervisor has its own
|
||||
// simple memory manager. The memory manager should be
|
||||
// the very first thing to initialize.
|
||||
//
|
||||
// Memory manager is provided memory space on which it
|
||||
// can operate. Small part from this space is reserved
|
||||
// for the page bitmap and page allocation map.
|
||||
//
|
||||
// Page bitmap sets bit 1 at page offset, if the page is
|
||||
// allocated (e.g.: if 4th page (at base_address + 4*PAGE_SIZE)
|
||||
// is allocated, 4th bit in this bitmap is set).
|
||||
// On deallocation, corresponding bit is reset to 0.
|
||||
//
|
||||
// Page allocation map stores number of pages allocated
|
||||
// for the particular address (e.g.: allocate(8192) returned
|
||||
// (base_address + 4*PAGE_SIZE), which is 2 pages, therefore
|
||||
// page_allocation_map[4] == 2.
|
||||
// On deallocation, corresponding number in the map is reset
|
||||
// to 0.
|
||||
//
|
||||
// Note: allocations are always page-aligned - therefore
|
||||
// allocation for even 1 byte results in waste of
|
||||
// 4096 bytes.
|
||||
//
|
||||
|
||||
namespace mm
|
||||
{
|
||||
using namespace ia32;
|
||||
|
||||
hypervisor_memory_allocator::hypervisor_memory_allocator() noexcept
|
||||
: base_address_{}
|
||||
, capacity_{}
|
||||
, page_bitmap_{}
|
||||
, page_bitmap_buffer_size_{}
|
||||
, page_allocation_map_{}
|
||||
, page_allocation_map_size_{}
|
||||
, last_page_offset_{}
|
||||
, allocated_bytes_{}
|
||||
, free_bytes_{}
|
||||
, lock_{}
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
hypervisor_memory_allocator::~hypervisor_memory_allocator() noexcept
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
auto hypervisor_memory_allocator::attach(void* address, size_t size) noexcept -> error_code_t
|
||||
{
|
||||
if (size < page_size * 3)
|
||||
{
|
||||
//
|
||||
// We need at least 3 pages (see explanation below).
|
||||
//
|
||||
hvpp_assert(0);
|
||||
return make_error_code_t(std::errc::invalid_argument);
|
||||
}
|
||||
|
||||
//
|
||||
// If the provided address is not page aligned, align it
|
||||
// to the next page.
|
||||
//
|
||||
if (byte_offset(address) != 0)
|
||||
{
|
||||
const auto lost_bytes = byte_offset(address);
|
||||
|
||||
address = reinterpret_cast<uint8_t*>(page_align(address)) + page_size;
|
||||
|
||||
//
|
||||
// Subtract amount of "lost" bytes due to alignment.
|
||||
//
|
||||
size -= lost_bytes;
|
||||
}
|
||||
|
||||
//
|
||||
// Align size to the page boundary.
|
||||
//
|
||||
size = page_align(size);
|
||||
|
||||
//
|
||||
// Check again.
|
||||
//
|
||||
if (size < page_size * 3)
|
||||
{
|
||||
hvpp_assert(0);
|
||||
return make_error_code_t(std::errc::invalid_argument);
|
||||
}
|
||||
|
||||
//
|
||||
// Address is page-aligned, size is page-aligned, and all
|
||||
// requirements are met. Proceed with initialization.
|
||||
//
|
||||
|
||||
//
|
||||
// The provided memory is split up to 3 parts:
|
||||
// 1. page bitmap - stores information if page is allocated
|
||||
// or not
|
||||
// 2. page count - stores information how many consecutive
|
||||
// pages has been allocated
|
||||
// 3. memory pool - this is the memory which will be provided
|
||||
//
|
||||
// For (1), there is taken (size / PAGE_SIZE / 8) bytes from the
|
||||
// provided memory space.
|
||||
// For (2), there is taken (size / PAGE_SIZE * sizeof(pgmap_t))
|
||||
// bytes from the provided memory space.
|
||||
// The rest memory is used for (3).
|
||||
//
|
||||
// This should account for ~93% of the provided memory space (if
|
||||
// it is big enough, e.g.: 32MB).
|
||||
//
|
||||
|
||||
//
|
||||
// Construct the page bitmap.
|
||||
//
|
||||
auto page_bitmap_buffer = reinterpret_cast<uint8_t*>(address);
|
||||
page_bitmap_buffer_size_ = static_cast<int>(round_to_pages(size / page_size / 8));
|
||||
memset(page_bitmap_buffer, 0, page_bitmap_buffer_size_);
|
||||
|
||||
auto page_bitmap_size_in_bits = static_cast<int>(size / page_size);
|
||||
page_bitmap_ = pgbmp_t(page_bitmap_buffer, page_bitmap_size_in_bits);
|
||||
|
||||
//
|
||||
// Construct the page allocation map.
|
||||
//
|
||||
page_allocation_map_ = reinterpret_cast<pgmap_t*>(page_bitmap_buffer + page_bitmap_buffer_size_);
|
||||
page_allocation_map_size_ = static_cast<int>(round_to_pages(size / page_size) * sizeof(pgmap_t));
|
||||
memset(page_allocation_map_, 0, page_allocation_map_size_);
|
||||
|
||||
//
|
||||
// Compute available memory.
|
||||
//
|
||||
base_address_ = reinterpret_cast<uint8_t*>(address);
|
||||
capacity_ = size;
|
||||
|
||||
//
|
||||
// Mark memory of page_bitmap and page_allocation_map as allocated.
|
||||
// The return value of these allocations should return the exact
|
||||
// address of page_bitmap_buffer and page_allocation_map.
|
||||
//
|
||||
auto page_bitmap_buffer_tmp = allocate(page_bitmap_buffer_size_);
|
||||
auto page_allocation_map_tmp = allocate(page_allocation_map_size_);
|
||||
|
||||
hvpp_assert(reinterpret_cast<uintptr_t>(page_bitmap_buffer) == reinterpret_cast<uintptr_t>(page_bitmap_buffer_tmp));
|
||||
hvpp_assert(reinterpret_cast<uintptr_t>(page_allocation_map_) == reinterpret_cast<uintptr_t>(page_allocation_map_tmp));
|
||||
|
||||
(void)(page_bitmap_buffer_tmp);
|
||||
(void)(page_allocation_map_tmp);
|
||||
|
||||
//
|
||||
// Initialize memory pool with garbage.
|
||||
// This should help with debugging uninitialized variables
|
||||
// and class members.
|
||||
//
|
||||
const auto reserved_bytes = static_cast<int>(page_bitmap_buffer_size_ + page_allocation_map_size_);
|
||||
memset(base_address_ + reserved_bytes, 0xcc, size - reserved_bytes);
|
||||
|
||||
//
|
||||
// Set initial values of allocated/free bytes.
|
||||
//
|
||||
allocated_bytes_ = 0;
|
||||
free_bytes_ = size;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void hypervisor_memory_allocator::detach() noexcept
|
||||
{
|
||||
//
|
||||
// If no memory has been assigned - leave.
|
||||
//
|
||||
if (!base_address_)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Note that this method doesn't acquire the lock and
|
||||
// assumes all allocations has been already freed.
|
||||
//
|
||||
|
||||
//
|
||||
// Mark memory of page_bitmap and page_allocation_map
|
||||
// as freed.
|
||||
//
|
||||
// Note that everything "free" does is clear bits in
|
||||
// page_bitmap and sets 0 to particular page_allocation_map
|
||||
// items.
|
||||
//
|
||||
// These two calls are needed to assure that the next two
|
||||
// asserts below will pass.
|
||||
//
|
||||
free(page_bitmap_.buffer());
|
||||
free(page_allocation_map_);
|
||||
|
||||
//
|
||||
// Checks for memory leaks.
|
||||
//
|
||||
hvpp_assert(page_bitmap_.all_clear());
|
||||
|
||||
//
|
||||
// Checks for allocator corruption.
|
||||
//
|
||||
hvpp_assert(std::all_of(
|
||||
page_allocation_map_,
|
||||
page_allocation_map_ + page_allocation_map_size_ / sizeof(pgmap_t),
|
||||
[](auto page_count) { return page_count == 0; }));
|
||||
|
||||
base_address_ = nullptr;
|
||||
capacity_ = 0;
|
||||
|
||||
page_bitmap_buffer_size_ = 0;
|
||||
|
||||
page_allocation_map_ = nullptr;
|
||||
page_allocation_map_size_ = 0;
|
||||
|
||||
last_page_offset_ = 0;
|
||||
allocated_bytes_ = 0;
|
||||
free_bytes_ = 0;
|
||||
}
|
||||
|
||||
auto hypervisor_memory_allocator::allocate(size_t size) noexcept -> void*
|
||||
{
|
||||
hvpp_assert(base_address_ != nullptr && capacity_ > 0);
|
||||
|
||||
//
|
||||
// Return at least 1 page, even if someone required 0.
|
||||
//
|
||||
if (size == 0)
|
||||
{
|
||||
hvpp_assert(0);
|
||||
size = 1;
|
||||
}
|
||||
|
||||
auto page_count = static_cast<int>(bytes_to_pages(size));
|
||||
|
||||
//
|
||||
// Check if the desired number of pages can fit into the
|
||||
// allocation map.
|
||||
//
|
||||
if (page_count > std::numeric_limits<pgmap_t>::max() - 1)
|
||||
{
|
||||
hvpp_assert(0);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int previous_page_offset;
|
||||
|
||||
{
|
||||
std::lock_guard _{ lock_ };
|
||||
|
||||
last_page_offset_ = page_bitmap_.find_first_clear(last_page_offset_, page_count);
|
||||
|
||||
if (last_page_offset_ == -1)
|
||||
{
|
||||
last_page_offset_ = 0;
|
||||
last_page_offset_ = page_bitmap_.find_first_clear(last_page_offset_, page_count);
|
||||
|
||||
if (last_page_offset_ == -1)
|
||||
{
|
||||
//
|
||||
// Not enough memory...
|
||||
//
|
||||
hvpp_assert(0);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
page_bitmap_.set(last_page_offset_, page_count);
|
||||
page_allocation_map_[last_page_offset_] = static_cast<pgmap_t>(page_count);
|
||||
|
||||
previous_page_offset = last_page_offset_;
|
||||
last_page_offset_ += page_count;
|
||||
|
||||
allocated_bytes_ += page_count * page_size;
|
||||
free_bytes_ -= page_count * page_size;
|
||||
}
|
||||
|
||||
//
|
||||
// Return the final address.
|
||||
// Note that we're not under lock here - we don't need it, because
|
||||
// everything neccessary has been done (bitmap + page allocation map
|
||||
// manipulation).
|
||||
//
|
||||
return base_address_ + previous_page_offset * page_size;
|
||||
}
|
||||
|
||||
auto hypervisor_memory_allocator::allocate_aligned(size_t size, size_t alignment) noexcept -> void*
|
||||
{
|
||||
//
|
||||
// Our allocator always returns page-aligned memory.
|
||||
// Therefore all alignments that are in interval (0, 4096]
|
||||
// AND are power of 2 are valid.
|
||||
//
|
||||
hvpp_assert(
|
||||
alignment > 0 &&
|
||||
alignment <= 4096 &&
|
||||
!(alignment & (alignment - 1)) // is power of 2
|
||||
);
|
||||
|
||||
return allocate(size);
|
||||
}
|
||||
|
||||
void hypervisor_memory_allocator::free(void* address) noexcept
|
||||
{
|
||||
//
|
||||
// Our allocator always provides page-aligned memory.
|
||||
//
|
||||
hvpp_assert(byte_offset(address) == 0);
|
||||
|
||||
if (address == nullptr)
|
||||
{
|
||||
//
|
||||
// Return immediatelly if we're trying to free NULL.
|
||||
//
|
||||
// hvpp_assert(0);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto offset = static_cast<int>(bytes_to_pages(reinterpret_cast<uint8_t*>(address) - base_address_));
|
||||
|
||||
if (size_t(offset) * page_size > capacity_)
|
||||
{
|
||||
//
|
||||
// We don't own this memory.
|
||||
//
|
||||
hvpp_assert(0);
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard _{ lock_ };
|
||||
|
||||
if (page_allocation_map_[offset] == 0)
|
||||
{
|
||||
//
|
||||
// This memory wasn't allocated.
|
||||
//
|
||||
hvpp_assert(0);
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Clear number of allocated pages.
|
||||
//
|
||||
const auto page_count = static_cast<int>(page_allocation_map_[offset]);
|
||||
page_allocation_map_[offset] = 0;
|
||||
|
||||
//
|
||||
// Clear pages in the bitmap.
|
||||
//
|
||||
page_bitmap_.clear(offset, page_count);
|
||||
|
||||
allocated_bytes_ -= page_count * page_size;
|
||||
free_bytes_ += page_count * page_size;
|
||||
}
|
||||
|
||||
bool hypervisor_memory_allocator::contains(void* address) noexcept
|
||||
{
|
||||
return
|
||||
reinterpret_cast<uint8_t*>(address) >= base_address_ &&
|
||||
reinterpret_cast<uint8_t*>(address) < base_address_ + capacity_;
|
||||
}
|
||||
|
||||
auto hypervisor_memory_allocator::allocated_bytes() noexcept -> size_t
|
||||
{
|
||||
return allocated_bytes_;
|
||||
}
|
||||
|
||||
auto hypervisor_memory_allocator::free_bytes() noexcept -> size_t
|
||||
{
|
||||
return free_bytes_;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
#pragma once
|
||||
#include "../memory_allocator.h"
|
||||
|
||||
#include "../../bitmap.h"
|
||||
#include "../../object.h"
|
||||
#include "../../spinlock.h"
|
||||
|
||||
namespace mm
|
||||
{
|
||||
class hypervisor_memory_allocator
|
||||
: public memory_allocator
|
||||
{
|
||||
public:
|
||||
hypervisor_memory_allocator() noexcept;
|
||||
~hypervisor_memory_allocator() noexcept override;
|
||||
|
||||
auto attach(void* address, size_t size) noexcept -> error_code_t override;
|
||||
void detach() noexcept override;
|
||||
|
||||
auto allocate(size_t size) noexcept -> void* override;
|
||||
auto allocate_aligned(size_t size, size_t alignment) noexcept -> void* override;
|
||||
void free(void* address) noexcept override;
|
||||
|
||||
bool contains(void* address) noexcept override;
|
||||
|
||||
auto allocated_bytes() noexcept -> size_t override;
|
||||
auto free_bytes() noexcept -> size_t override;
|
||||
|
||||
private:
|
||||
using pgbmp_t = bitmap<>;
|
||||
using pgmap_t = uint16_t;
|
||||
|
||||
uint8_t* base_address_; // Pool base address
|
||||
size_t capacity_; // Capacity of the pool
|
||||
|
||||
pgbmp_t page_bitmap_; // Bitmap holding used pages
|
||||
int page_bitmap_buffer_size_; //
|
||||
|
||||
pgmap_t* page_allocation_map_; // Map holding number of allocated pages
|
||||
int page_allocation_map_size_; //
|
||||
|
||||
int last_page_offset_; // Last returned page offset - used as hint
|
||||
|
||||
size_t allocated_bytes_;
|
||||
size_t free_bytes_;
|
||||
|
||||
spinlock lock_;
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
#include "system_memory_allocator.h"
|
||||
|
||||
namespace mm
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
auto system_allocate(size_t size) noexcept -> void*;
|
||||
auto system_allocate_aligned(size_t size, size_t alignment) noexcept -> void*;
|
||||
void system_free(void* address) noexcept;
|
||||
}
|
||||
|
||||
auto system_memory_allocator::attach(void* address, size_t size) noexcept -> error_code_t
|
||||
{
|
||||
(void)(address);
|
||||
(void)(size);
|
||||
return make_error_code_t(std::errc::not_supported);
|
||||
}
|
||||
|
||||
void system_memory_allocator::detach() noexcept
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto system_memory_allocator::allocate(size_t size) noexcept -> void*
|
||||
{
|
||||
return detail::system_allocate(size);
|
||||
}
|
||||
|
||||
auto system_memory_allocator::allocate_aligned(size_t size, size_t alignment) noexcept -> void*
|
||||
{
|
||||
return detail::system_allocate_aligned(size, alignment);
|
||||
}
|
||||
|
||||
void system_memory_allocator::free(void* address) noexcept
|
||||
{
|
||||
detail::system_free(address);
|
||||
}
|
||||
|
||||
bool system_memory_allocator::contains(void* address) noexcept
|
||||
{
|
||||
(void)(address);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto system_memory_allocator::allocated_bytes() noexcept -> size_t
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto system_memory_allocator::free_bytes() noexcept -> size_t
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
#pragma once
|
||||
#include "../memory_allocator.h"
|
||||
|
||||
namespace mm
|
||||
{
|
||||
class system_memory_allocator
|
||||
: public memory_allocator
|
||||
{
|
||||
public:
|
||||
auto attach(void* address, size_t size) noexcept -> error_code_t override;
|
||||
void detach() noexcept override;
|
||||
|
||||
auto allocate(size_t size) noexcept -> void* override;
|
||||
auto allocate_aligned(size_t size, size_t alignment) noexcept -> void* override;
|
||||
void free(void* address) noexcept override;
|
||||
|
||||
bool contains(void* address) noexcept override;
|
||||
|
||||
auto allocated_bytes() noexcept -> size_t override;
|
||||
auto free_bytes() noexcept -> size_t override;
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
#include <ntddk.h>
|
||||
|
||||
#define HVPP_MEMORY_TAG 'ppvh'
|
||||
|
||||
namespace mm::detail
|
||||
{
|
||||
auto system_allocate(size_t size) noexcept -> void*
|
||||
{
|
||||
return ExAllocatePoolWithTag(NonPagedPool, size, HVPP_MEMORY_TAG);
|
||||
}
|
||||
|
||||
auto system_allocate_aligned(size_t size, size_t alignment) noexcept -> void*
|
||||
{
|
||||
//
|
||||
// If NumberOfBytes is PAGE_SIZE or greater, a page-aligned buffer is allocated.
|
||||
// Memory allocations of PAGE_SIZE or less are allocated within a page and do not
|
||||
// cross page boundaries. Memory allocations of less than PAGE_SIZE are not
|
||||
// necessarily page-aligned but are aligned to 8-byte boundaries in 32-bit systems
|
||||
// and to 16-byte boundaries in 64-bit systems.
|
||||
//
|
||||
// (ref: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/wdm/nf-wdm-exallocatepoolwithtag)
|
||||
|
||||
NT_ASSERT(
|
||||
alignment > 0 &&
|
||||
alignment <= 4096 &&
|
||||
!(alignment & (alignment - 1)) // is power of 2
|
||||
);
|
||||
|
||||
if (size >= PAGE_SIZE || alignment <= sizeof(void*))
|
||||
{
|
||||
return system_allocate(size);
|
||||
}
|
||||
|
||||
//
|
||||
// Force alignment to PAGE_SIZE.
|
||||
//
|
||||
return system_allocate(PAGE_SIZE);
|
||||
}
|
||||
|
||||
void system_free(void* address) noexcept
|
||||
{
|
||||
//
|
||||
// ExFreePoolWithTag doesn't support freeing NULL.
|
||||
//
|
||||
if (address == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ExFreePoolWithTag(address, HVPP_MEMORY_TAG);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
#include "../mm.h"
|
||||
|
||||
#include <ntddk.h>
|
||||
|
||||
#define HVPP_MEMORY_TAG 'ppvh'
|
||||
|
||||
namespace mm::detail
|
||||
{
|
||||
auto system_allocate(size_t size) noexcept -> void*
|
||||
{
|
||||
return ExAllocatePoolWithTag(NonPagedPool,
|
||||
size,
|
||||
HVPP_MEMORY_TAG);
|
||||
}
|
||||
|
||||
void system_free(void* address) noexcept
|
||||
{
|
||||
//
|
||||
// ExFreePoolWithTag doesn't support freeing NULL.
|
||||
//
|
||||
if (address == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ExFreePoolWithTag(address, HVPP_MEMORY_TAG);
|
||||
}
|
||||
}
|
||||
|
|
@ -87,7 +87,7 @@ namespace driver
|
|||
// Tell debugger we're started.
|
||||
//
|
||||
hvpp_info("Hypervisor started, current free memory: %" PRIu64 " MB",
|
||||
mm::free_bytes() / 1024 / 1024);
|
||||
mm::hypervisor_allocator()->free_bytes() / 1024 / 1024);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue