mirror of
https://github.com/FEX-Emu/FEX.git
synced 2026-10-27 03:00:52 +01:00
Improve VM utility code
This commit is contained in:
1 parent
6a2f79fa2c
commit
f078b299a0
2 files changed
+590
-36
No files matched your search
@@ -1,8 +1,10 @@
|
||||
#include "LogManager.h"
|
||||
#include "VM.h"
|
||||
|
||||
#include <memory>
|
||||
#include <fcntl.h>
|
||||
#include <linux/kvm.h>
|
||||
#include <linux/mman.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
@@ -11,9 +13,77 @@
|
||||
#include <sys/stat.h>
|
||||
#include <vector>
|
||||
|
||||
inline uint64_t AlignUp(uint64_t value, uint64_t size) {
|
||||
return value + (size - value % size) % size;
|
||||
};
|
||||
|
||||
class Memmap {
|
||||
public:
|
||||
bool AllocateSHMRegion(size_t Size);
|
||||
void DeallocateSHMRegion();
|
||||
int GetFD() { return SHMfd; }
|
||||
void *MapRegionFlags(size_t Offset, size_t Size, uint32_t flags, bool Fixed);
|
||||
void *MapRegion(size_t Offset, size_t Size, bool Fixed);
|
||||
|
||||
private:
|
||||
int SHMfd;
|
||||
size_t SHMSize;
|
||||
void *Base;
|
||||
};
|
||||
void *Memmap::MapRegion(size_t Offset, size_t Size, bool Fixed) {
|
||||
return MapRegionFlags(Offset, Size, PROT_READ | PROT_WRITE, Fixed);
|
||||
}
|
||||
|
||||
void *Memmap::MapRegionFlags(size_t Offset, size_t Size, uint32_t flags, bool Fixed) {
|
||||
uintptr_t PtrOffset = reinterpret_cast<uintptr_t>(Base) + Offset;
|
||||
void *Ptr = mmap(reinterpret_cast<void*>(PtrOffset), Size, flags,
|
||||
MAP_SHARED | (Fixed ? MAP_FIXED : 0), SHMfd, Offset);
|
||||
|
||||
if (Ptr == MAP_FAILED) {
|
||||
LogMan::Msg::A("Failed to map memory region [0x%lx, 0x%lx) - %d", Offset, Size, Fixed);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return Ptr;
|
||||
}
|
||||
|
||||
bool Memmap::AllocateSHMRegion(size_t Size) {
|
||||
const std::string SHMName = "VM" + std::to_string(getpid());
|
||||
SHMfd = shm_open(SHMName.c_str(), O_RDWR | O_CREAT | O_EXCL, 0600);
|
||||
if (SHMfd == -1) {
|
||||
LogMan::Msg::E("Couldn't open SHM");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Unlink the shm file immediately to not leave it around
|
||||
shm_unlink(SHMName.c_str());
|
||||
|
||||
// Extend the SHM to the size we requested
|
||||
if (ftruncate(SHMfd, Size) != 0) {
|
||||
LogMan::Msg::E("Couldn't set SHM size");
|
||||
return false;
|
||||
}
|
||||
|
||||
SHMSize = Size;
|
||||
Base = MapRegion(0, SHMSize, false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Memmap::DeallocateSHMRegion() {
|
||||
close(SHMfd);
|
||||
}
|
||||
|
||||
namespace SU::VM {
|
||||
|
||||
namespace KVM {
|
||||
struct MemoryRegion {
|
||||
uint64_t VirtualBase;
|
||||
uint64_t PhysicalBase;
|
||||
uint64_t RegionSize;
|
||||
void *MappedLocation;
|
||||
};
|
||||
|
||||
// This only supports 4k pages
|
||||
class PML4Control final {
|
||||
public:
|
||||
@@ -41,11 +111,6 @@ namespace SU::VM {
|
||||
|
||||
void SetupTables();
|
||||
private:
|
||||
struct MemoryRegion {
|
||||
uint64_t VirtualBase;
|
||||
uint64_t PhysicalBase;
|
||||
uint64_t RegionSize;
|
||||
};
|
||||
std::vector<MemoryRegion> MemoryMappings;
|
||||
uint64_t PML4VirtualAddress{};
|
||||
uint64_t PML4PhysicalAddress{};
|
||||
@@ -85,22 +150,25 @@ namespace SU::VM {
|
||||
|
||||
uint64_t NumPages = Region.RegionSize / PAGE_SIZE;
|
||||
|
||||
uint64_t PML4_Offset = (VirtualAddress >> 39) & 0x1FF;
|
||||
uint64_t PDPE_Offset = (VirtualAddress >> 30) & 0x1FF;
|
||||
uint64_t PDE_Offset = (VirtualAddress >> 21) & 0x1FF;
|
||||
uint64_t PTE_Offset = (VirtualAddress >> 12) & 0x1FF;
|
||||
uint64_t Physical_Offset = (VirtualAddress ) & 0xFFF;
|
||||
|
||||
// Now walk the tables for all the pages and map the table
|
||||
for (uint64_t i = 0; i < NumPages; ++i) {
|
||||
uint64_t OffsetVirtual = VirtualAddress + (i * PAGE_SIZE);
|
||||
uint64_t OffsetPhysical = PhysicalAddress + (i * PAGE_SIZE);
|
||||
|
||||
uint64_t PML4_Offset = (OffsetVirtual >> 39) & 0x1FF;
|
||||
uint64_t PDPE_Offset = (OffsetVirtual >> 30) & 0x1FF;
|
||||
uint64_t PDE_Offset = (OffsetVirtual >> 21) & 0x1FF;
|
||||
uint64_t PTE_Offset = (OffsetVirtual >> 12) & 0x1FF;
|
||||
uint64_t Physical_Offset = (OffsetVirtual ) & 0xFFF;
|
||||
|
||||
// Set the PML4_table to point to Base of the PDPE table offset
|
||||
PML4_Table[PML4_Offset] = (uint64_t)Physical_PDPE_Table | 0b11;
|
||||
PML4_Table[PML4_Offset] = (uint64_t)Physical_PDPE_Table | 0b111;
|
||||
// Continue mapping
|
||||
PDPE_Table[PDPE_Offset] = (uint64_t)Physical_PDE_Table | 0b11;
|
||||
PDPE_Table[PDPE_Offset] = (uint64_t)Physical_PDE_Table | 0b111;
|
||||
// Continue mapping
|
||||
PDE_Table[PDE_Offset] = (uint64_t)Physical_PTE_Table | 0b11;
|
||||
PDE_Table[PDE_Offset] = (uint64_t)Physical_PTE_Table | 0b111;
|
||||
// This now maps directly to the region's physical address
|
||||
PTE_Table[PTE_Offset] = PhysicalAddress | 0b11;
|
||||
PTE_Table[PTE_Offset] = OffsetPhysical | 0b000'100000011;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -120,6 +188,13 @@ namespace SU::VM {
|
||||
bool SetStepping(bool Step) override;
|
||||
bool Run() override;
|
||||
int ExitReason() override { return VCPUMemory->exit_reason; }
|
||||
uint64_t GetFailEntryReason() override { return VCPUMemory->fail_entry.hardware_entry_failure_reason; }
|
||||
VMInstance::RegisterState GetRegisterState() override;
|
||||
VMInstance::SpecialRegisterState GetSpecialRegisterState() override;
|
||||
|
||||
void SetRegisterState(VMInstance::RegisterState &State) override;
|
||||
void SetSpecialRegisterState(VMInstance::SpecialRegisterState &State) override;
|
||||
void Debug() override;
|
||||
|
||||
private:
|
||||
// FDs
|
||||
@@ -140,8 +215,18 @@ namespace SU::VM {
|
||||
// Allocated memory
|
||||
void *PML4_Addr;
|
||||
void *VM_Addr;
|
||||
MemoryRegion IDTRegion {
|
||||
.RegionSize = 4096,
|
||||
};
|
||||
MemoryRegion GDTRegion {
|
||||
.RegionSize = 16 * 4096,
|
||||
};
|
||||
MemoryRegion APICRegion {
|
||||
.RegionSize = 4096,
|
||||
};
|
||||
bool SetupCPUInLongMode();
|
||||
bool SetupMemory();
|
||||
Memmap Mapper;
|
||||
};
|
||||
|
||||
KVMInstance::~KVMInstance() {
|
||||
@@ -153,27 +238,88 @@ namespace SU::VM {
|
||||
// unmap all of our pointers
|
||||
munmap(VM_Addr, PhysicalMemorySize);
|
||||
munmap(PML4_Addr, PML4.GetPML4Size());
|
||||
munmap(IDTRegion.MappedLocation, IDTRegion.RegionSize);
|
||||
}
|
||||
|
||||
bool KVMInstance::SetupMemory() {
|
||||
// ============================
|
||||
// Physical Memory map currently
|
||||
// ============================
|
||||
|
||||
// Everything is mapped linearly from [0, end)
|
||||
// [UserMemory, UserMemory + Size)
|
||||
// [IDTRegion, IDTRegion + Size)
|
||||
// [GDTRegion, GDTRegion + Size)
|
||||
// [APICRegion, APICRegion + Size)
|
||||
// [PML4, PML4 + Size)
|
||||
//
|
||||
// ============================
|
||||
// Virtual Memory map currently
|
||||
// ============================
|
||||
// Virtual memory map directly mirrors the physical mapping
|
||||
|
||||
uint64_t VirtualBase = 0x0000'00F0'0000'0000;
|
||||
uint64_t PhysicalMemoryBase = 0;
|
||||
// Calculate the amount of physical memory backing we need
|
||||
PhysicalMemorySize = PhysicalUserMemorySize;
|
||||
|
||||
IDTRegion.PhysicalBase = PhysicalMemorySize;
|
||||
IDTRegion.VirtualBase = VirtualBase + IDTRegion.PhysicalBase; // Mapped to the same place
|
||||
PhysicalMemorySize += IDTRegion.RegionSize;
|
||||
|
||||
GDTRegion.PhysicalBase = PhysicalMemorySize;
|
||||
GDTRegion.VirtualBase = VirtualBase + GDTRegion.PhysicalBase; // Mapped to the same place
|
||||
PhysicalMemorySize += GDTRegion.RegionSize;
|
||||
|
||||
APICRegion.PhysicalBase = PhysicalMemorySize;
|
||||
APICRegion.VirtualBase = VirtualBase + APICRegion.PhysicalBase; // Mapped to the same place
|
||||
PhysicalMemorySize += APICRegion.RegionSize;
|
||||
|
||||
// Physical and virtual map directly atm
|
||||
PML4.SetPhysicalAddress(PhysicalMemorySize);
|
||||
PML4.SetVirtualAddress(PhysicalMemorySize);
|
||||
PML4.SetVirtualAddress(VirtualBase + PhysicalMemorySize);
|
||||
|
||||
PhysicalMemorySize += PML4.GetPML4Size();
|
||||
|
||||
// Align up just to make sure we are page aligned
|
||||
PhysicalMemorySize = AlignUp(PhysicalMemorySize, 4096);
|
||||
|
||||
Mapper.AllocateSHMRegion(1ULL << 36);
|
||||
|
||||
// Physical memory is just [0, PhysicalMemorySize)
|
||||
// Just map physical zero to virtual zero
|
||||
// This maps both our user set memory region and our PML4 region to be accessable
|
||||
PML4.AddMemoryMapping(0, 0, PhysicalMemorySize);
|
||||
PML4.AddMemoryMapping(VirtualBase, 0, PhysicalMemorySize);
|
||||
|
||||
// Set up user memory
|
||||
{
|
||||
// Allocate the memory for the user physical memory
|
||||
VM_Addr = Mapper.MapRegion(0, PhysicalUserMemorySize, true);
|
||||
LogMan::Throw::A(VM_Addr != (void*)-1ULL, "Couldn't Allocate");
|
||||
|
||||
// Map this memory to the guest VM
|
||||
kvm_userspace_memory_region Region = {
|
||||
.slot = MemorySlot++,
|
||||
.flags = 0,
|
||||
.guest_phys_addr = PhysicalMemoryBase,
|
||||
.memory_size = PhysicalUserMemorySize,
|
||||
.userspace_addr = (uint64_t)VM_Addr,
|
||||
};
|
||||
|
||||
if (ioctl(VM_FD, KVM_SET_USER_MEMORY_REGION, &Region) == -1) {
|
||||
printf("Couldn't set guestPD region\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// The PM4L setup with handle the page mapping
|
||||
}
|
||||
|
||||
// Set up PML4
|
||||
{
|
||||
// Allocate the host facing PML4 memory
|
||||
PML4_Addr = mmap(nullptr, PML4.GetPML4Size(), PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0);
|
||||
PML4_Addr = Mapper.MapRegion(PML4.GetPhysicalAddress(), PML4.GetPML4Size(), true);
|
||||
LogMan::Throw::A(PML4_Addr != (void*)-1ULL, "Couldn't Allocate");
|
||||
|
||||
if (PML4_Addr == reinterpret_cast<void*>(-1ULL)) {
|
||||
fprintf(stderr, "Couldn't allocate PML4 table memory");
|
||||
return false;
|
||||
@@ -183,7 +329,7 @@ namespace SU::VM {
|
||||
// Map this address space in to the VM
|
||||
kvm_userspace_memory_region Region = {
|
||||
.slot = MemorySlot++,
|
||||
.flags = 0, // Read-only?
|
||||
.flags = 0,
|
||||
.guest_phys_addr = PML4.GetPhysicalAddress(),
|
||||
.memory_size = PML4.GetPML4Size(),
|
||||
.userspace_addr = (uint64_t)PML4_Addr,
|
||||
@@ -198,18 +344,19 @@ namespace SU::VM {
|
||||
PML4.SetupTables();
|
||||
}
|
||||
|
||||
// Set up user memory
|
||||
// Set up GDT
|
||||
// GDT contains SEGMENTS
|
||||
{
|
||||
// Allocate the memory for the user physical memory
|
||||
VM_Addr = mmap(nullptr, PhysicalUserMemorySize, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0);
|
||||
GDTRegion.MappedLocation = Mapper.MapRegion(GDTRegion.PhysicalBase, GDTRegion.RegionSize, true);
|
||||
LogMan::Throw::A(GDTRegion.MappedLocation != (void*)-1ULL, "Couldn't Allocate");
|
||||
|
||||
// Map this memory to the guest VM
|
||||
kvm_userspace_memory_region Region = {
|
||||
.slot = MemorySlot++,
|
||||
.flags = 0, // Read-only?
|
||||
.guest_phys_addr = PhysicalMemoryBase,
|
||||
.memory_size = PhysicalUserMemorySize,
|
||||
.userspace_addr = (uint64_t)VM_Addr,
|
||||
.flags = 0,
|
||||
.guest_phys_addr = GDTRegion.PhysicalBase,
|
||||
.memory_size = GDTRegion.RegionSize,
|
||||
.userspace_addr = (uint64_t)GDTRegion.MappedLocation,
|
||||
};
|
||||
|
||||
if (ioctl(VM_FD, KVM_SET_USER_MEMORY_REGION, &Region) == -1) {
|
||||
@@ -217,13 +364,214 @@ namespace SU::VM {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The PM4L setup with handle the page mapping
|
||||
// Set up the table entries
|
||||
union GDTSegment {
|
||||
struct NullSegment {
|
||||
uint32_t Unused_0;
|
||||
uint32_t Unused_1;
|
||||
} Null;
|
||||
struct CodeSegment {
|
||||
uint32_t Unused;
|
||||
unsigned Unused_0 : 8;
|
||||
unsigned Unused_1 : 1;
|
||||
unsigned Unused_2 : 1;
|
||||
unsigned C : 1;
|
||||
unsigned MustBeSet: 2;
|
||||
unsigned DPL : 2;
|
||||
unsigned P : 1;
|
||||
unsigned Unused_3 : 4;
|
||||
unsigned Unused_4 : 1;
|
||||
unsigned L : 1;
|
||||
unsigned D : 1;
|
||||
unsigned Unused_5 : 1;
|
||||
unsigned Unused_6 : 8;
|
||||
} Code;
|
||||
struct DataSegment {
|
||||
uint32_t Unused_0;
|
||||
unsigned Unused_1 : 11;
|
||||
unsigned DataType : 2;
|
||||
unsigned Unused_2 : 2;
|
||||
unsigned P : 1;
|
||||
unsigned Unused_3 : 16;
|
||||
} Data;
|
||||
} __attribute__((packed));
|
||||
|
||||
// Even though this is still a GDT segment
|
||||
// its size has increased to 64 bits which means it takes two slots
|
||||
struct SystemSegment {
|
||||
uint16_t SegmentLimitLow;
|
||||
uint16_t BaseLow;
|
||||
unsigned BaseLowMid : 8;
|
||||
unsigned Type : 4;
|
||||
unsigned MustBeZero_0 : 1;
|
||||
unsigned DPL : 2;
|
||||
unsigned P : 1;
|
||||
unsigned SegmentLimitHigh : 4;
|
||||
unsigned Unused_0 : 2;
|
||||
unsigned G : 1;
|
||||
unsigned BaseLowHigh : 8;
|
||||
uint32_t BaseHigh;
|
||||
unsigned Unused_1 : 8;
|
||||
unsigned MustBeZero_1 : 5;
|
||||
unsigned Unused_2 : 19;
|
||||
} __attribute__((packed));
|
||||
|
||||
GDTSegment DefaultCodeSegment = {
|
||||
.Code = {
|
||||
.MustBeSet = 0b11,
|
||||
},
|
||||
};
|
||||
GDTSegment DefaultDataSegment = {
|
||||
.Data = {
|
||||
.DataType = 0b10,
|
||||
},
|
||||
};
|
||||
SystemSegment DefaultSystemSegment = {};
|
||||
|
||||
unsigned Entries = GDTRegion.RegionSize / sizeof(GDTSegment);
|
||||
|
||||
GDTSegment *GuestEntries = (GDTSegment*)GDTRegion.MappedLocation;
|
||||
|
||||
// Null Segment
|
||||
GDTSegment NullSegment{};
|
||||
|
||||
// Data segment
|
||||
GDTSegment DataSegment = DefaultDataSegment;
|
||||
DataSegment.Data.P = 1;
|
||||
|
||||
// Code Segment
|
||||
GDTSegment CodeSegment = DefaultCodeSegment;
|
||||
CodeSegment.Code.P = 1;
|
||||
CodeSegment.Code.L = 1; // 64bit Code
|
||||
CodeSegment.Code.D = 0; // MUST be 0 in 64bit mode. CS.D = 0 and CS.L = 1
|
||||
|
||||
// 64bit TSS segment
|
||||
SystemSegment Enable64BitTSS = DefaultSystemSegment;
|
||||
Enable64BitTSS.Type = 0b1001;
|
||||
Enable64BitTSS.P = 1;
|
||||
|
||||
unsigned CurrentOffset = 0;
|
||||
|
||||
// Null segment is first
|
||||
GuestEntries[CurrentOffset++] = NullSegment;
|
||||
GuestEntries[CurrentOffset++] = CodeSegment;
|
||||
GuestEntries[CurrentOffset++] = DataSegment;
|
||||
|
||||
void *Ptr = &GuestEntries[CurrentOffset];
|
||||
CurrentOffset += 2;
|
||||
memcpy(Ptr, &Enable64BitTSS, sizeof(SystemSegment));
|
||||
}
|
||||
|
||||
// Set up IDT
|
||||
// When an exception or interrupt occurs, the processor uses the interrupt vector number as an index in to the IDT
|
||||
// IDT is used in all operating modes
|
||||
// IDT contains GATES
|
||||
{
|
||||
IDTRegion.MappedLocation = Mapper.MapRegion(IDTRegion.PhysicalBase, IDTRegion.RegionSize, true);
|
||||
LogMan::Throw::A(IDTRegion.MappedLocation != (void*)-1ULL, "Couldn't Allocate");
|
||||
|
||||
// Map this memory to the guest VM
|
||||
kvm_userspace_memory_region Region = {
|
||||
.slot = MemorySlot++,
|
||||
.flags = 0,
|
||||
.guest_phys_addr = IDTRegion.PhysicalBase,
|
||||
.memory_size = IDTRegion.RegionSize,
|
||||
.userspace_addr = (uint64_t)IDTRegion.MappedLocation,
|
||||
};
|
||||
|
||||
if (ioctl(VM_FD, KVM_SET_USER_MEMORY_REGION, &Region) == -1) {
|
||||
printf("Couldn't set guestPD region\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
union GateDesc {
|
||||
struct CallGateType {
|
||||
uint16_t TargetLow;
|
||||
uint16_t TargetSelector;
|
||||
unsigned Unused_0 : 8;
|
||||
unsigned Type : 4;
|
||||
unsigned MustBeZero : 1;
|
||||
unsigned DPL : 2;
|
||||
unsigned P : 1;
|
||||
uint16_t TargetMid;
|
||||
uint32_t TargetHigh;
|
||||
uint32_t Pad;
|
||||
} Call;
|
||||
|
||||
struct InterruptTrapType {
|
||||
uint16_t TargetLow;
|
||||
uint16_t TargetSelector;
|
||||
unsigned IST : 2;
|
||||
unsigned Unused_0 : 6;
|
||||
unsigned Type : 4;
|
||||
unsigned MustBeZero : 1;
|
||||
unsigned DPL : 2;
|
||||
unsigned P : 1;
|
||||
uint16_t TargetMid;
|
||||
uint32_t TargetHigh;
|
||||
uint32_t Pad;
|
||||
} IntTrap;
|
||||
} __attribute__((packed));
|
||||
|
||||
GateDesc Test;
|
||||
|
||||
// Types 0x04-0x07 are illegal in 64bit mode
|
||||
uint8_t TypeCall = 0x0C;
|
||||
uint8_t TypeIDT = 0x0E;
|
||||
uint8_t TypeTrap = 0x0F;
|
||||
Test.IntTrap.TargetLow = 0;
|
||||
Test.IntTrap.TargetSelector = 0;
|
||||
Test.IntTrap.P = 1; // If P == 0 then `Segment not present` fault occurs
|
||||
Test.IntTrap.IST = 0; // Must be zero otherwise we need a TSS register set up
|
||||
Test.IntTrap.Type = TypeTrap;
|
||||
Test.IntTrap.TargetMid = 0;
|
||||
Test.IntTrap.TargetHigh = 0;
|
||||
|
||||
unsigned Entries = IDTRegion.RegionSize / sizeof(GateDesc);
|
||||
printf("%d IDT entries\n", Entries);
|
||||
GateDesc *GuestEntries = (GateDesc*)IDTRegion.MappedLocation;
|
||||
|
||||
// Section 8.2 Vectors of the AMD64 system programmer's manual
|
||||
// Claims that the first 32 vectors are reserved for predefined exception and interrupt conditions
|
||||
// The IDT can fit 256 interrupts
|
||||
|
||||
//Back to IDT
|
||||
for (unsigned i = 0; i < Entries; ++i) {
|
||||
GuestEntries[i] = Test;
|
||||
}
|
||||
}
|
||||
|
||||
// Set up our initial APIC region
|
||||
{
|
||||
APICRegion.MappedLocation = Mapper.MapRegion(APICRegion.PhysicalBase, APICRegion.RegionSize, true);
|
||||
LogMan::Throw::A(APICRegion.MappedLocation != (void*)-1ULL, "Couldn't Allocate");
|
||||
|
||||
// Map this memory to the guest VM
|
||||
kvm_userspace_memory_region Region = {
|
||||
.slot = MemorySlot++,
|
||||
.flags = 0,
|
||||
.guest_phys_addr = APICRegion.PhysicalBase,
|
||||
.memory_size = APICRegion.RegionSize,
|
||||
.userspace_addr = (uint64_t)APICRegion.MappedLocation,
|
||||
};
|
||||
|
||||
if (ioctl(VM_FD, KVM_SET_USER_MEMORY_REGION, &Region) == -1) {
|
||||
printf("Couldn't set guestPD region\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
uint64_t *APICRegisters = (uint64_t*)APICRegion.MappedLocation;
|
||||
for (unsigned i = 0; i < 0x54; ++i) {
|
||||
APICRegisters[i] = ~0ULL;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool KVMInstance::SetupCPUInLongMode() {
|
||||
|
||||
kvm_sregs SpecialRegisters = {
|
||||
.cs = {
|
||||
.base = 0, // Ignored in 64bit mode
|
||||
@@ -232,31 +580,73 @@ namespace SU::VM {
|
||||
.db = 0, // Ignored in 64bit mode
|
||||
.dpl = 0, // Data-pivilege-level Still used in 64bit mode to allow accesses
|
||||
.l = 1, // 1 for 64bit execution mode. 0 if you want 32bit compatibility
|
||||
.present = 1,
|
||||
},
|
||||
.ds = {
|
||||
.present = 1,
|
||||
},
|
||||
.es = {
|
||||
.present = 1,
|
||||
},
|
||||
.fs = {
|
||||
.present = 1,
|
||||
},
|
||||
.gs = {
|
||||
.present = 1,
|
||||
},
|
||||
.ss = {
|
||||
.present = 1,
|
||||
},
|
||||
|
||||
.cr0 = (1U << 0) | // Protected Mode enable. Required for 64bit mode
|
||||
(0U << 2) | // EM bit. 0 means State x87 FPU is present
|
||||
(0U << 3) | // TS bit. If set causes a device not available exception
|
||||
(1U << 31) | // (PG) Enable paging bit. Required for 64bit mode
|
||||
0,
|
||||
.cr3 = PML4.GetVirtualAddress(), // PDBR in PAE mode. Required for 64bit mode
|
||||
.cr4 = (1 << 5) | // PAE. Required for 64bit mode
|
||||
.cr3 = PML4.GetPhysicalAddress(), // PDBR in PAE mode. Required for 64bit mode
|
||||
.cr4 = (1U << 5) | // PAE. Required for 64bit mode
|
||||
(1U << 9) | // OSFXSR. Required to enable SSE
|
||||
// (1U << 18) | // XXX: OSXSave. Required to enable AVX. Requires setting the guest CPUID to say it supports xsave
|
||||
0,
|
||||
.efer = (1 << 10) | // Long Mode Active
|
||||
(1 << 8) | // Long Mode Enable
|
||||
.efer = (1U << 10) | // (LMA) Long Mode Active.
|
||||
(1U << 8) | // (LME) Long Mode Enable
|
||||
0,
|
||||
.gdt = { // Is GDT actually necessary?
|
||||
.gdt = {
|
||||
.base = GDTRegion.VirtualBase,
|
||||
.limit = (uint16_t)GDTRegion.RegionSize,
|
||||
},
|
||||
.idt = {
|
||||
.base = IDTRegion.VirtualBase,
|
||||
.limit = (uint16_t)IDTRegion.RegionSize,
|
||||
},
|
||||
.apic_base = (1 << 11) | // (AE) APIC Enable
|
||||
// (APICRegion.PhysicalBase >> 12 << 32), // ABA, APIC Base Address. ABA is zext by 12 bits to form a 52bit physical address
|
||||
0,
|
||||
// Do we need the LDT(Local Descriptor Table) and TR (Task Register)?
|
||||
.tr = {
|
||||
.base = 0,
|
||||
.limit = 0,
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
.ldt = {
|
||||
.base = 0,
|
||||
.limit = 0,
|
||||
},
|
||||
};
|
||||
|
||||
kvm_regs Registers = {
|
||||
.rip = ~0ULL, // Make sure the user sets the RIP
|
||||
.rflags = 0x2, // 0x2 is the default state of rflags
|
||||
.rip = ~0ULL,
|
||||
.rsp = 0xFF0,
|
||||
.rflags = 0x2 | // 0x2 is the default state of rflags
|
||||
(1 << 9) | // By default enable hardware interrupts
|
||||
(1 << 19), // By default enable virtual hardware interrupts
|
||||
};
|
||||
|
||||
if (ioctl(VCPU_FD, KVM_SET_SREGS, &SpecialRegisters) == -1) {
|
||||
fprintf(stderr, "Couldn't set kvm_sregs\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ioctl(VCPU_FD, KVM_SET_REGS, &Registers) == -1) {
|
||||
fprintf(stderr, "Couldn't set kvm_regs\n");
|
||||
return false;
|
||||
@@ -311,6 +701,7 @@ namespace SU::VM {
|
||||
|
||||
// Map in the kvm_run structure that is mapped to VCPU_FD at offset 0
|
||||
VCPUMemory = reinterpret_cast<kvm_run*>(mmap(nullptr, VCPUSize, PROT_READ | PROT_WRITE, MAP_SHARED, VCPU_FD, 0));
|
||||
LogMan::Throw::A(VCPUMemory != (void*)-1ULL, "Couldn't Allocate");
|
||||
|
||||
if (!SetupMemory()) {
|
||||
fprintf(stderr, "Couldn't set up the CPU's memory\n");
|
||||
@@ -322,6 +713,15 @@ namespace SU::VM {
|
||||
goto err;
|
||||
}
|
||||
|
||||
// Set a default TSS
|
||||
// AMD SVM literally doesn't care but Intel VMX mandates it
|
||||
// Needs to fit under 4GB and also have a minimum of three pages
|
||||
Result = ioctl(VM_FD, KVM_SET_TSS_ADDR, -1U - (4096 * 3));
|
||||
if (Result == -1) {
|
||||
fprintf(stderr, "Couldn't set TSS ADDR\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
return true;
|
||||
err:
|
||||
if (VCPU_FD != -1) {
|
||||
@@ -378,8 +778,141 @@ err:
|
||||
fprintf(stderr, "KVM_RUN failed\n");
|
||||
return false;
|
||||
}
|
||||
kvm_regs Registers;
|
||||
ioctl(VCPU_FD, KVM_GET_REGS, &Registers);
|
||||
printf("RIP is now: 0x%llx\n", Registers.rip);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
VMInstance::RegisterState KVMInstance::GetRegisterState() {
|
||||
kvm_regs Registers;
|
||||
if (ioctl(VCPU_FD, KVM_GET_REGS, &Registers) == -1) {
|
||||
fprintf(stderr, "Couldn't get KVM register state\n");
|
||||
return {};
|
||||
}
|
||||
VMInstance::RegisterState State;
|
||||
// This map 1:1
|
||||
memcpy(&State, &Registers, sizeof(kvm_regs));
|
||||
return State;
|
||||
}
|
||||
|
||||
VMInstance::SpecialRegisterState KVMInstance::GetSpecialRegisterState() {
|
||||
kvm_sregs Registers;
|
||||
if (ioctl(VCPU_FD, KVM_GET_SREGS, &Registers) == -1) {
|
||||
fprintf(stderr, "Couldn't get KVM special register state\n");
|
||||
return {};
|
||||
}
|
||||
VMInstance::SpecialRegisterState State;
|
||||
State.fs.base = Registers.fs.base;
|
||||
State.gs.base = Registers.gs.base;
|
||||
return State;
|
||||
}
|
||||
|
||||
void KVMInstance::SetRegisterState(VMInstance::RegisterState &State) {
|
||||
kvm_regs Registers;
|
||||
// This map 1:1
|
||||
memcpy(&Registers, &State, sizeof(kvm_regs));
|
||||
|
||||
if (ioctl(VCPU_FD, KVM_SET_REGS, &Registers) == -1) {
|
||||
fprintf(stderr, "Couldn't set kvm_regs\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void KVMInstance::SetSpecialRegisterState(VMInstance::SpecialRegisterState &State) {
|
||||
kvm_sregs Registers;
|
||||
|
||||
// First read the state so we don't destroy a bunch of data
|
||||
if (ioctl(VCPU_FD, KVM_GET_SREGS, &Registers) == -1) {
|
||||
fprintf(stderr, "Couldn't get KVM special register state\n");
|
||||
return;
|
||||
}
|
||||
Registers.fs.base = State.fs.base;
|
||||
Registers.gs.base = State.gs.base;
|
||||
|
||||
if (ioctl(VCPU_FD, KVM_SET_SREGS, &Registers) == -1) {
|
||||
fprintf(stderr, "Couldn't set kvm_sregs\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void KVMInstance::Debug() {
|
||||
kvm_sregs Registers;
|
||||
if (ioctl(VCPU_FD, KVM_GET_SREGS, &Registers) == -1) {
|
||||
fprintf(stderr, "Couldn't get KVM special register state\n");
|
||||
return;
|
||||
}
|
||||
|
||||
auto PrintDTable = [](const char *Name, kvm_dtable &Table) {
|
||||
LogMan::Msg::D("%s 0x%016llx 0x%04x", Name, Table.base, Table.limit);
|
||||
};
|
||||
|
||||
auto PrintSegment = [](const char *Name, kvm_segment &Segment) {
|
||||
LogMan::Msg::D("%s 0x%016llx 0x%08x 0x%04x 0x%02x %d %d %d %d %d %d %d",
|
||||
Name, Segment.base, Segment.limit, Segment.selector, Segment.type,
|
||||
Segment.present, Segment.dpl, Segment.db, Segment.s, Segment.l, Segment.g, Segment.avl);
|
||||
};
|
||||
|
||||
SU::VM::VMInstance::RegisterState State;
|
||||
State = GetRegisterState();
|
||||
LogMan::Msg::D("================");
|
||||
LogMan::Msg::D(" RAX RBX RCX RDX");
|
||||
LogMan::Msg::D("%016lx %016lx %016lx %016lx", State.rax, State.rbx, State.rcx, State.rdx);
|
||||
LogMan::Msg::D(" RSI RDI RSP RBP");
|
||||
LogMan::Msg::D("%016lx %016lx %016lx %016lx", State.rsi, State.rdi, State.rsp, State.rbp);
|
||||
LogMan::Msg::D(" R8 R9 R10 R11");
|
||||
LogMan::Msg::D("%016lx %016lx %016lx %016lx", State.r8, State.r9, State.r10, State.r11);
|
||||
LogMan::Msg::D(" R12 R13 R14 R15");
|
||||
LogMan::Msg::D("%016lx %016lx %016lx %016lx", State.r12, State.r13, State.r14, State.r15);
|
||||
LogMan::Msg::D(" RIP RFLAGS");
|
||||
LogMan::Msg::D("%016lx %016lx", State.rip, State.rflags);
|
||||
|
||||
LogMan::Msg::D("================");
|
||||
LogMan::Msg::D("cr0: cr2: cr3:");
|
||||
LogMan::Msg::D("%016llx %016llx %016llx", Registers.cr0, Registers.cr2, Registers.cr3);
|
||||
LogMan::Msg::D("cr4: cr6:");
|
||||
LogMan::Msg::D("%016llx %016llx", Registers.cr4, Registers.cr8);
|
||||
|
||||
LogMan::Msg::D("================");
|
||||
LogMan::Msg::D("Reg Base Limit Select Type p dpl db s l g avl");
|
||||
|
||||
PrintSegment("CS ", Registers.cs);
|
||||
PrintSegment("DS ", Registers.ds);
|
||||
PrintSegment("ES ", Registers.es);
|
||||
PrintSegment("FS ", Registers.fs);
|
||||
PrintSegment("GS ", Registers.gs);
|
||||
PrintSegment("SS ", Registers.ss);
|
||||
|
||||
PrintSegment("TR ", Registers.tr);
|
||||
PrintSegment("LDT", Registers.ldt);
|
||||
|
||||
LogMan::Msg::D("================");
|
||||
PrintDTable("GDT", Registers.gdt);
|
||||
PrintDTable("IDT", Registers.idt);
|
||||
|
||||
LogMan::Msg::D("================");
|
||||
LogMan::Msg::D("EFER: 0x%016llx APIC: 0x%016llx", Registers.efer, Registers.apic_base);
|
||||
LogMan::Msg::D("================");
|
||||
LogMan::Msg::D("Interrupt Bitmap");
|
||||
for (int i = 0; i < (KVM_NR_INTERRUPTS + 63) / 64; ++i) {
|
||||
LogMan::Msg::D("Interrupt: %d: 0x%016llx", Registers.interrupt_bitmap[i]);
|
||||
}
|
||||
|
||||
void *PhysBase = VM_Addr;
|
||||
uint8_t *GuestData = reinterpret_cast<uint8_t*>(PhysBase);
|
||||
|
||||
uint64_t Offset = State.rip - 0x1000'0000ULL;
|
||||
uint64_t Size = 16;
|
||||
LogMan::Msg::D("Mem: [%016llx, %016llx)", Offset, Offset + Size);
|
||||
for (uint64_t i = 0; i < 16 * 3; i += 16) {
|
||||
LogMan::Msg::D("%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x",
|
||||
GuestData[Offset + i + 0], GuestData[Offset + i + 1], GuestData[Offset + i + 2], GuestData[Offset + i + 3],
|
||||
GuestData[Offset + i + 4], GuestData[Offset + i + 5], GuestData[Offset + i + 6], GuestData[Offset + i + 7],
|
||||
GuestData[Offset + i + 8], GuestData[Offset + i + 9], GuestData[Offset + i + 10], GuestData[Offset + i + 11],
|
||||
GuestData[Offset + i + 12], GuestData[Offset + i + 13], GuestData[Offset + i + 14], GuestData[Offset + i + 15]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VMInstance* VMInstance::Create() {
|
||||
|
||||
Reference in new issue
Block a user