Add WHP MMIO support and mapping optimizations

Introduces MMIO mapping support in the WHP backend, including trapped-page integration and periodic host-page refresh callbacks. Reworks WHP GPA mapping updates to support incremental dirty-range rebuilds (with full rebuild fallback), and defers CS/SS segment writes into the normal register flush path for cleaner register state updates.

Also optimizes Windows PE relocation patching by processing relocation tables and page patches in pooled buffers, and adds registry hive path/root caching to reduce repeated traversal overhead. Includes minor register-assignment formatting cleanup in BinaryEmulator.
This commit is contained in:
AdvDebug
2026-07-24 15:24:37 +03:00
parent 872a5ecacc
commit bebf7b6d0a
5 changed files with 523 additions and 59 deletions
@@ -30,6 +30,9 @@ namespace Brovan.Core.Emulation
public bool SetMemoryProtection(ulong address, ulong size, MemoryProtection protection)
=> Inner.SetMemoryProtection(address, size, protection);
public bool MapMmio(ulong address, ulong size, MmioReadCallback read, MmioWriteCallback write)
=> Inner.MapMmio(address, size, read, write);
public bool WriteMemory(ulong address, byte[] value, uint length = 0)
=> Inner.WriteMemory(address, value, length);
+8 -8
View File
@@ -1838,14 +1838,14 @@ namespace Brovan.Core.Emulation
ulong[] Vals = _gprBatchScratch ??= new ulong[GprBatchCount];
if (!_emulator.ReadRegisterBatch(Regs, Vals, Regs.Length))
return false;
c.RAX = Vals[0]; c.RBX = Vals[1]; c.RCX = Vals[2]; c.RDX = Vals[3];
c.RSI = Vals[4]; c.RDI = Vals[5]; c.RBP = Vals[6]; c.RSP = Vals[7];
c.RAX = Vals[0]; c.RBX = Vals[1]; c.RCX = Vals[2]; c.RDX = Vals[3];
c.RSI = Vals[4]; c.RDI = Vals[5]; c.RBP = Vals[6]; c.RSP = Vals[7];
if (Regs.Length == GprBatchCount32)
{
c.RIP = Vals[8]; c.RFLAGS = Vals[9];
return true;
}
c.R8 = Vals[8]; c.R9 = Vals[9]; c.R10 = Vals[10]; c.R11 = Vals[11];
c.R8 = Vals[8]; c.R9 = Vals[9]; c.R10 = Vals[10]; c.R11 = Vals[11];
c.R12 = Vals[12]; c.R13 = Vals[13]; c.R14 = Vals[14]; c.R15 = Vals[15];
c.RIP = Vals[16]; c.RFLAGS = Vals[17];
return true;
@@ -1856,17 +1856,17 @@ namespace Brovan.Core.Emulation
if (c == null) return;
int[] Regs = GetGprBatchRegs();
ulong[] Vals = _gprBatchScratch ??= new ulong[GprBatchCount];
Vals[0] = c.RAX; Vals[1] = c.RBX; Vals[2] = c.RCX; Vals[3] = c.RDX;
Vals[4] = c.RSI; Vals[5] = c.RDI; Vals[6] = c.RBP; Vals[7] = c.RSP;
Vals[0] = c.RAX; Vals[1] = c.RBX; Vals[2] = c.RCX; Vals[3] = c.RDX;
Vals[4] = c.RSI; Vals[5] = c.RDI; Vals[6] = c.RBP; Vals[7] = c.RSP;
if (Regs.Length == GprBatchCount32)
{
Vals[8] = c.RIP; Vals[9] = c.RFLAGS;
}
else
{
Vals[8] = c.R8; Vals[9] = c.R9; Vals[10] = c.R10; Vals[11] = c.R11;
Vals[12] = c.R12; Vals[13] = c.R13; Vals[14] = c.R14; Vals[15] = c.R15;
Vals[16] = c.RIP; Vals[17] = c.RFLAGS;
Vals[8] = c.R8; Vals[9] = c.R9; Vals[10] = c.R10; Vals[11] = c.R11;
Vals[12] = c.R12; Vals[13] = c.R13; Vals[14] = c.R14; Vals[15] = c.R15;
Vals[16] = c.RIP; Vals[17] = c.RFLAGS;
}
_emulator.WriteRegisterBatch(Regs, Vals, Regs.Length);
}
@@ -1,4 +1,5 @@
using System.Buffers;
using System.Buffers.Binary;
using System.Runtime.InteropServices;
using Brovan.Core.Emulation.OS.Windows;
using Brovan.Core.Emulation.OS.Windows.Win32k;
@@ -784,48 +785,105 @@ namespace Brovan.Core.Emulation
return;
ulong RelocationTableAddress = MappedBase + RelocationDirectory.VirtualAddress;
ulong RelocationTableEnd = RelocationTableAddress + RelocationDirectory.Size;
int TableSize = (int)RelocationDirectory.Size;
while (RelocationTableAddress < RelocationTableEnd)
byte[] Table = ArrayPool<byte>.Shared.Rent(TableSize);
const int PatchWindow = (int)PageSize + 8;
byte[] PageBuffer = ArrayPool<byte>.Shared.Rent(PatchWindow);
try
{
uint PageRva = ReadMemoryUInt(RelocationTableAddress);
uint BlockSize = ReadMemoryUInt(RelocationTableAddress + 4);
RelocationTableAddress += 8;
if (!ReadMemory(RelocationTableAddress, Table.AsSpan(0, TableSize), (uint)TableSize))
return;
int EntryCount = ((int)BlockSize - 8) / 2;
for (int Index = 0; Index < EntryCount; Index++)
int Cursor = 0;
while (Cursor + 8 <= TableSize)
{
ushort Entry = BitConverter.ToUInt16(ReadMemory(RelocationTableAddress, 2));
RelocationTableAddress += 2;
uint PageRva = BinaryPrimitives.ReadUInt32LittleEndian(Table.AsSpan(Cursor, 4));
uint BlockSize = BinaryPrimitives.ReadUInt32LittleEndian(Table.AsSpan(Cursor + 4, 4));
if (BlockSize < 8)
break;
ushort RelocationType = (ushort)(Entry >> 12);
ushort Offset = (ushort)(Entry & 0x0FFF);
ulong PatchAddress = MappedBase + PageRva + Offset;
int EntriesOffset = Cursor + 8;
int EntryBytes = (int)BlockSize - 8;
if (EntriesOffset + EntryBytes > TableSize)
EntryBytes = TableSize - EntriesOffset;
if (EntryBytes < 0)
break;
switch (RelocationType)
Cursor += (int)BlockSize;
ulong PageBase = MappedBase + PageRva;
int Available = PatchWindow;
if (!ReadMemory(PageBase, PageBuffer.AsSpan(0, Available), (uint)Available))
{
case 0:
break;
Available = (int)PageSize;
if (!ReadMemory(PageBase, PageBuffer.AsSpan(0, Available), (uint)Available))
continue;
}
case 3:
uint Original32 = ReadMemoryUInt(PatchAddress);
uint Patched32 = unchecked((uint)((long)Original32 + RelocationDelta));
_emulator.WriteMemory(PatchAddress, Patched32);
break;
int LowOffset = int.MaxValue;
int HighOffset = -1;
int EntryCount = EntryBytes / 2;
case 10:
ulong Original64 = ReadMemoryULong(PatchAddress);
ulong Patched64 = unchecked((ulong)((long)Original64 + RelocationDelta));
_emulator.WriteMemory(PatchAddress, Patched64);
break;
for (int Index = 0; Index < EntryCount; Index++)
{
ushort Entry = BinaryPrimitives.ReadUInt16LittleEndian(Table.AsSpan(EntriesOffset + Index * 2, 2));
ushort RelocationType = (ushort)(Entry >> 12);
int Offset = Entry & 0x0FFF;
default:
int Width = RelocationType == 3 ? 4 : RelocationType == 10 ? 8 : 0;
if (RelocationType == 0)
continue;
if (Width == 0)
{
if ((Settings.Flags & LogFlags.Issues) != 0)
TriggerEventMessage($"[-] Unsupported relocation type {RelocationType} in {Module.Name}", LogFlags.Issues);
break;
continue;
}
if (Offset + Width > Available)
{
ulong StraddleAddress = PageBase + (ulong)Offset;
if (Width == 4)
{
uint Original32 = ReadMemoryUInt(StraddleAddress);
_emulator.WriteMemory(StraddleAddress, unchecked((uint)((long)Original32 + RelocationDelta)));
}
else
{
ulong Original64 = ReadMemoryULong(StraddleAddress);
_emulator.WriteMemory(StraddleAddress, unchecked((ulong)((long)Original64 + RelocationDelta)));
}
continue;
}
Span<byte> Target = PageBuffer.AsSpan(Offset, Width);
if (Width == 4)
{
uint Original32 = BinaryPrimitives.ReadUInt32LittleEndian(Target);
BinaryPrimitives.WriteUInt32LittleEndian(Target, unchecked((uint)((long)Original32 + RelocationDelta)));
}
else
{
ulong Original64 = BinaryPrimitives.ReadUInt64LittleEndian(Target);
BinaryPrimitives.WriteUInt64LittleEndian(Target, unchecked((ulong)((long)Original64 + RelocationDelta)));
}
if (Offset < LowOffset) LowOffset = Offset;
if (Offset + Width > HighOffset) HighOffset = Offset + Width;
}
if (HighOffset > 0)
_emulator.WriteMemory(PageBase + (ulong)LowOffset, PageBuffer.AsSpan(LowOffset, HighOffset - LowOffset));
}
}
finally
{
ArrayPool<byte>.Shared.Return(Table);
ArrayPool<byte>.Shared.Return(PageBuffer);
}
}
public BinaryFile LoadBinary(string Path)
+380 -21
View File
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
@@ -43,6 +44,11 @@ namespace Brovan.Core.Emulation
private readonly Dictionary<ulong, InstalledMap> _desiredMaps = new();
private readonly List<ulong> _staleMapKeys = new();
private readonly List<ulong> _activeMapStarts = new();
private bool _fullRebuildRequired = true;
private readonly List<DirtyRange> _dirtyRanges = new();
private const int MaxDirtyRanges = 16;
private ulong _pml4Gpa;
private ulong _nextInternalGpa = WhpConstants.InternalPageTableBase;
@@ -63,13 +69,20 @@ namespace Brovan.Core.Emulation
private WhpRegisters _regsCache;
private bool _regsValid;
private bool _regsDirty;
private WhvRegisterValue _pendingCs;
private WhvRegisterValue _pendingSs;
private bool _segmentsDirty;
private readonly List<MemoryHookEntry> _memoryHooks = new();
private readonly List<MmioRegion> _mmioRegions = new();
private InstructionHookEntry _syscallHook;
private readonly List<InstructionHookEntry> _instructionHooks = new();
private readonly List<InterruptHookEntry> _interruptHooks = new();
private readonly List<IntPtr> _liveHookHandles = new();
private readonly object _mmioLock = new();
private Thread _mmioRefreshThread;
private bool _completionActive;
private ulong _completionPageGpa;
private ulong _completionAccessGpa;
@@ -108,6 +121,12 @@ namespace Brovan.Core.Emulation
public WhvMapGpaRangeFlags Flags;
}
private struct DirtyRange
{
public ulong Start;
public ulong End;
}
private sealed class MemoryHookEntry
{
public ulong Begin;
@@ -116,6 +135,15 @@ namespace Brovan.Core.Emulation
public MemoryHookCallback Callback;
}
private sealed class MmioRegion
{
public ulong Address;
public ulong Size;
public MmioReadCallback ReadCallback;
public MmioWriteCallback WriteCallback;
public IntPtr[] HostPages;
}
private sealed class InstructionHookEntry
{
public BackendInstructionHook Type;
@@ -160,6 +188,16 @@ namespace Brovan.Core.Emulation
(uint)WhvRegisterName.Rip, (uint)WhvRegisterName.Rflags,
};
private static readonly uint[] GpRegNamesWithSegments =
{
(uint)WhvRegisterName.Rax, (uint)WhvRegisterName.Rbx, (uint)WhvRegisterName.Rcx, (uint)WhvRegisterName.Rdx,
(uint)WhvRegisterName.Rsi, (uint)WhvRegisterName.Rdi, (uint)WhvRegisterName.Rsp, (uint)WhvRegisterName.Rbp,
(uint)WhvRegisterName.R8, (uint)WhvRegisterName.R9, (uint)WhvRegisterName.R10, (uint)WhvRegisterName.R11,
(uint)WhvRegisterName.R12, (uint)WhvRegisterName.R13, (uint)WhvRegisterName.R14, (uint)WhvRegisterName.R15,
(uint)WhvRegisterName.Rip, (uint)WhvRegisterName.Rflags,
(uint)WhvRegisterName.Cs, (uint)WhvRegisterName.Ss,
};
public Whp(Arch arch, Mode mode)
{
if (arch != Arch.X86 || mode != Mode.MODE_64)
@@ -250,6 +288,7 @@ namespace Brovan.Core.Emulation
}
page.Permissions = perm;
MarkSpanDirty(guest, WhpConstants.PageSize);
EnsureVirtualMapping(guest);
}
@@ -268,6 +307,12 @@ namespace Brovan.Core.Emulation
return false;
}
int removedRegions;
lock (_mmioLock)
{
removedRegions = _mmioRegions.RemoveAll(r => r.Address >= address && r.Address < address + size);
}
for (ulong off = 0; off < size; off += WhpConstants.PageSize)
{
ulong guest = address + off;
@@ -278,7 +323,10 @@ namespace Brovan.Core.Emulation
}
}
RebuildMappings();
if (removedRegions != 0)
RefreshTrappedPages();
else
RebuildMappings();
_error = WhpErrors.Ok;
return true;
}
@@ -291,7 +339,10 @@ namespace Brovan.Core.Emulation
for (ulong off = 0; off < size; off += WhpConstants.PageSize)
{
if (_mappedPages.TryGetValue(address + off, out MappedPage page))
{
page.Permissions = perm;
MarkSpanDirty(address + off, WhpConstants.PageSize);
}
}
RebuildMappings();
@@ -299,6 +350,100 @@ namespace Brovan.Core.Emulation
return true;
}
public bool MapMmio(ulong address, ulong size, MmioReadCallback read, MmioWriteCallback write)
{
if (DisposedCheck()) return false;
if (read == null || write == null ||
(address & WhpConstants.PageMask) != 0 || (size & WhpConstants.PageMask) != 0 || size == 0)
{
_error = WhpErrors.InvalidArgument;
return false;
}
if (!MapMemory(address, size, MemoryProtection.Read))
return false;
MmioRegion region = new MmioRegion
{
Address = address,
Size = size,
ReadCallback = read,
WriteCallback = write,
HostPages = new IntPtr[size / WhpConstants.PageSize],
};
for (ulong offset = 0; offset < size; offset += WhpConstants.PageSize)
{
if (_mappedPages.TryGetValue(address + offset, out MappedPage page) && page != null)
region.HostPages[offset / WhpConstants.PageSize] = page.HostPage;
}
lock (_mmioLock)
{
_mmioRegions.RemoveAll(r => r.Address == address);
_mmioRegions.Add(region);
RefreshMmioRegion(region);
}
RefreshTrappedPages();
EnsureMmioRefreshThread();
_error = WhpErrors.Ok;
return true;
}
private void RefreshMmioBackedRegions()
{
if (_mmioRegions.Count == 0) return;
lock (_mmioLock)
{
for (int i = 0; i < _mmioRegions.Count; i++)
RefreshMmioRegion(_mmioRegions[i]);
}
}
private unsafe void RefreshMmioRegion(MmioRegion region)
{
for (int i = 0; i < region.HostPages.Length; i++)
{
IntPtr hostPage = region.HostPages[i];
if (hostPage == IntPtr.Zero)
continue;
ulong offset = (ulong)i * WhpConstants.PageSize;
int chunk = (int)Math.Min(WhpConstants.PageSize, region.Size - offset);
region.ReadCallback(offset, new Span<byte>((void*)hostPage, chunk));
}
}
private void EnsureMmioRefreshThread()
{
if (_mmioRefreshThread != null)
return;
_mmioRefreshThread = new Thread(MmioRefreshLoop)
{
IsBackground = true,
Name = "WhpMmioRefresh"
};
_mmioRefreshThread.Start();
}
private void MmioRefreshLoop()
{
while (!Disposed && !Disposing)
{
lock (_mmioLock)
{
for (int i = 0; i < _mmioRegions.Count; i++)
RefreshMmioRegion(_mmioRegions[i]);
}
Thread.Sleep(1);
}
}
public bool WriteMemory(ulong address, byte[] value, uint length = 0)
{
if (DisposedCheck()) return false;
@@ -718,6 +863,8 @@ namespace Brovan.Core.Emulation
{
while (!_stopRequested)
{
RefreshMmioBackedRegions();
FlushRegisterCache();
ref WhvRunVpExitContext exit = ref RunVirtualProcessor();
@@ -863,6 +1010,17 @@ namespace Brovan.Core.Emulation
}
}
for (int i = 0; i < _mmioRegions.Count; i++)
{
MmioRegion region = _mmioRegions[i];
for (ulong page = region.Address; page < region.Address + region.Size; page += WhpConstants.PageSize)
{
if (!_trappedPages.ContainsKey(page))
_trappedPages[page] = true;
}
}
RequireFullRebuild();
RebuildMappings();
}
@@ -996,6 +1154,11 @@ namespace Brovan.Core.Emulation
{
if (Interlocked.Exchange(ref _disposing, 1) == 1) return;
lock (_mmioLock)
{
_mmioRegions.Clear();
}
try
{
RemoveHooks();
@@ -1090,20 +1253,11 @@ namespace Brovan.Core.Emulation
private static readonly WhvRegisterValue UserDataSegment =
MakeSegment(WhpConstants.UserDataSelector, false, true);
private unsafe void SetCsSs(WhvRegisterValue cs, WhvRegisterValue ss)
private void QueueCsSs(WhvRegisterValue cs, WhvRegisterValue ss)
{
Span<uint> names = stackalloc uint[2] { (uint)WhvRegisterName.Cs, (uint)WhvRegisterName.Ss };
Span<WhvRegisterValue> values = stackalloc WhvRegisterValue[2] { cs, ss };
lock (_vcpuLock)
{
fixed (uint* n = names)
fixed (WhvRegisterValue* v = values)
{
int hr = WhpNative.WHvSetVirtualProcessorRegisters(_partition, VpIndex, n, 2, v);
if (WhpNative.Failed(hr))
throw new WhpException("WHvSetVirtualProcessorRegisters(CS/SS) failed", hr);
}
}
_pendingCs = cs;
_pendingSs = ss;
_segmentsDirty = true;
}
private void ClearTrapFlag()
@@ -1468,6 +1622,7 @@ namespace Brovan.Core.Emulation
_mappedPages[guestAddress] = page;
_sortedPageKeysDirty = true;
_mappingsDirty = true;
MarkSpanDirty(guestAddress, WhpConstants.PageSize);
_lastLookupPageBase = ulong.MaxValue;
_lastLookupPage = null;
}
@@ -1477,10 +1632,65 @@ namespace Brovan.Core.Emulation
if (!_mappedPages.Remove(guestAddress)) return;
_sortedPageKeysDirty = true;
_mappingsDirty = true;
MarkSpanDirty(guestAddress, WhpConstants.PageSize);
_lastLookupPageBase = ulong.MaxValue;
_lastLookupPage = null;
}
private void MarkSpanDirty(ulong address, ulong size)
{
if (_fullRebuildRequired) return;
ulong start = address & ~WhpConstants.PageMask;
ulong end = GetRangeEndAligned(address, size);
if (end <= start) return;
for (int i = _dirtyRanges.Count - 1; i >= 0; i--)
{
DirtyRange range = _dirtyRanges[i];
if (start > range.End || range.Start > end) continue;
if (range.Start < start) start = range.Start;
if (range.End > end) end = range.End;
_dirtyRanges.RemoveAt(i);
}
if (_dirtyRanges.Count >= MaxDirtyRanges)
{
RequireFullRebuild();
return;
}
_dirtyRanges.Add(new DirtyRange { Start = start, End = end });
}
private static ulong GetRangeEndAligned(ulong address, ulong size)
{
ulong end = address + size;
if (end < address) return ulong.MaxValue & ~WhpConstants.PageMask;
return (end + WhpConstants.PageMask) & ~WhpConstants.PageMask;
}
private void RequireFullRebuild()
{
_fullRebuildRequired = true;
_dirtyRanges.Clear();
}
private void AddActiveMap(ulong start, InstalledMap map)
{
_activeMaps[start] = map;
int index = _activeMapStarts.BinarySearch(start);
if (index < 0) _activeMapStarts.Insert(~index, start);
}
private void RemoveActiveMap(ulong start)
{
if (!_activeMaps.Remove(start)) return;
int index = _activeMapStarts.BinarySearch(start);
if (index >= 0) _activeMapStarts.RemoveAt(index);
}
private ulong[] GetSortedPageKeys()
{
if (!_sortedPageKeysDirty) return _sortedPageKeys;
@@ -1499,6 +1709,22 @@ namespace Brovan.Core.Emulation
{
_mappingsDirty = false;
if (!_fullRebuildRequired)
{
for (int i = 0; i < _dirtyRanges.Count; i++)
RebuildMappingsIncremental(_dirtyRanges[i].Start, _dirtyRanges[i].End);
_dirtyRanges.Clear();
return;
}
RebuildMappingsFull();
_fullRebuildRequired = false;
_dirtyRanges.Clear();
}
private void RebuildMappingsFull()
{
ulong[] sortedKeys = GetSortedPageKeys();
int keyCount = _mappedPages.Count;
bool anyTrapped = _trappedPages.Count != 0;
@@ -1581,6 +1807,127 @@ namespace Brovan.Core.Emulation
MapGpaRange(kv.Key, kv.Value.Size, kv.Value.Host, kv.Value.Flags);
_activeMaps[kv.Key] = kv.Value;
}
_activeMapStarts.Clear();
foreach (KeyValuePair<ulong, InstalledMap> kv in _activeMaps)
_activeMapStarts.Add(kv.Key);
_activeMapStarts.Sort();
}
private int FirstActiveMapIndexAtOrBefore(ulong address)
{
int index = _activeMapStarts.BinarySearch(address);
if (index >= 0) return index;
index = ~index;
return index > 0 ? index - 1 : 0;
}
private void RebuildMappingsIncremental(ulong spanStart, ulong spanEnd)
{
int firstIndex = FirstActiveMapIndexAtOrBefore(spanStart);
for (int i = firstIndex; i < _activeMapStarts.Count; i++)
{
ulong start = _activeMapStarts[i];
if (start >= spanEnd) break;
InstalledMap active = _activeMaps[start];
ulong end = start + active.Size;
if (end <= spanStart) continue;
if (start < spanStart) spanStart = start;
if (end > spanEnd) spanEnd = end;
}
_desiredMaps.Clear();
BuildDesiredMapsForSpan(spanStart, spanEnd);
_staleMapKeys.Clear();
firstIndex = FirstActiveMapIndexAtOrBefore(spanStart);
for (int i = firstIndex; i < _activeMapStarts.Count; i++)
{
ulong start = _activeMapStarts[i];
if (start >= spanEnd) break;
InstalledMap active = _activeMaps[start];
if (start + active.Size <= spanStart) continue;
if (!_desiredMaps.TryGetValue(start, out InstalledMap want)
|| want.Size != active.Size
|| want.Host != active.Host
|| want.Flags != active.Flags)
{
UnmapGpaRange(start, active.Size);
_staleMapKeys.Add(start);
}
else
{
_desiredMaps.Remove(start);
}
}
for (int i = 0; i < _staleMapKeys.Count; i++)
RemoveActiveMap(_staleMapKeys[i]);
foreach (KeyValuePair<ulong, InstalledMap> kv in _desiredMaps)
{
MapGpaRange(kv.Key, kv.Value.Size, kv.Value.Host, kv.Value.Flags);
AddActiveMap(kv.Key, kv.Value);
}
}
private void BuildDesiredMapsForSpan(ulong spanStart, ulong spanEnd)
{
bool anyTrapped = _trappedPages.Count != 0;
for (ulong address = spanStart; address < spanEnd;)
{
if (!_mappedPages.TryGetValue(address, out MappedPage page)
|| page == null
|| page.HostPage == IntPtr.Zero
|| page.Permissions == WhpMemoryPermission.None)
{
address += WhpConstants.PageSize;
continue;
}
if (anyTrapped && _trappedPages.TryGetValue(address, out bool writeOnly))
{
if (writeOnly)
{
_desiredMaps[address] = new InstalledMap
{
Size = WhpConstants.PageSize,
Host = page.HostPage,
Flags = WhvMapGpaRangeFlags.Read | WhvMapGpaRangeFlags.Execute,
};
}
address += WhpConstants.PageSize;
continue;
}
long runHostBaseLong = page.HostPage.ToInt64();
WhvMapGpaRangeFlags runFlags = ToWhpMapFlags(page.Permissions);
ulong runSize = WhpConstants.PageSize;
while (address + runSize < spanEnd)
{
if (!_mappedPages.TryGetValue(address + runSize, out MappedPage next) || next == null) break;
if (next.Permissions != page.Permissions) break;
if (next.HostPage == IntPtr.Zero) break;
if (anyTrapped && _trappedPages.ContainsKey(address + runSize)) break;
if (next.HostPage.ToInt64() != runHostBaseLong + (long)runSize) break;
runSize += WhpConstants.PageSize;
}
_desiredMaps[address] = new InstalledMap
{
Size = runSize,
Host = new IntPtr(runHostBaseLong),
Flags = runFlags,
};
address += runSize;
}
}
private unsafe void MapGpaRange(ulong gpa, ulong size, IntPtr host, WhvMapGpaRangeFlags flags)
@@ -1858,7 +2205,7 @@ namespace Brovan.Core.Emulation
regs.Rflags = frameRflags;
_regsDirty = true;
SetCsSs(MakeSegment((ushort)frameCs, true, (frameCs & 3) == 3),
QueueCsSs(MakeSegment((ushort)frameCs, true, (frameCs & 3) == 3),
MakeSegment((ushort)frameSs, false, (frameSs & 3) == 3));
}
@@ -1888,7 +2235,7 @@ namespace Brovan.Core.Emulation
after.Rip += 2;
_regsDirty = true;
SetCsSs(UserCodeSegment, UserDataSegment);
QueueCsSs(UserCodeSegment, UserDataSegment);
return true;
}
@@ -1984,7 +2331,9 @@ namespace Brovan.Core.Emulation
private unsafe void StoreRegisters()
{
Span<WhvRegisterValue> values = stackalloc WhvRegisterValue[GpRegNames.Length];
bool withSegments = _segmentsDirty;
uint[] names = withSegments ? GpRegNamesWithSegments : GpRegNames;
Span<WhvRegisterValue> values = stackalloc WhvRegisterValue[GpRegNamesWithSegments.Length];
values[0] = WhvRegisterValue.FromReg64(_regsCache.Rax);
values[1] = WhvRegisterValue.FromReg64(_regsCache.Rbx);
values[2] = WhvRegisterValue.FromReg64(_regsCache.Rcx);
@@ -2004,13 +2353,20 @@ namespace Brovan.Core.Emulation
values[16] = WhvRegisterValue.FromReg64(_regsCache.Rip);
values[17] = WhvRegisterValue.FromReg64(_regsCache.Rflags | 0x2UL);
if (withSegments)
{
values[18] = _pendingCs;
values[19] = _pendingSs;
_segmentsDirty = false;
}
lock (_vcpuLock)
{
fixed (uint* names = GpRegNames)
fixed (uint* n = names)
fixed (WhvRegisterValue* vals = values)
{
int hr = WhpNative.WHvSetVirtualProcessorRegisters(_partition, VpIndex, names,
(uint)GpRegNames.Length, vals);
int hr = WhpNative.WHvSetVirtualProcessorRegisters(_partition, VpIndex, n,
(uint)names.Length, vals);
if (WhpNative.Failed(hr))
throw new WhpException("WHvSetVirtualProcessorRegisters(GP) failed", hr);
}
@@ -2019,7 +2375,7 @@ namespace Brovan.Core.Emulation
private void FlushRegisterCache()
{
if (_regsDirty)
if (_regsDirty || _segmentsDirty)
{
_regsDirty = false;
StoreRegisters();
@@ -2041,6 +2397,9 @@ namespace Brovan.Core.Emulation
private unsafe WhvRegisterValue GetSingleRegister(WhvRegisterName name)
{
if (_segmentsDirty && (name == WhvRegisterName.Cs || name == WhvRegisterName.Ss))
FlushRegisterCache();
uint n = (uint)name;
WhvRegisterValue value;
lock (_vcpuLock)
+47 -3
View File
@@ -59,6 +59,10 @@ namespace Brovan.Core.Helpers
private readonly SafeFileHandle Handle;
private readonly long Length;
private readonly Dictionary<string, HiveKey> PathCache = new(StringComparer.OrdinalIgnoreCase);
private const int MaxCachedPaths = 8192;
private HiveKey CachedRootKey;
public RegistryHiveReader(SafeFileHandle Handle, long Length)
{
this.Handle = Handle ?? throw new ArgumentNullException(nameof(Handle));
@@ -68,7 +72,15 @@ namespace Brovan.Core.Helpers
public HiveKey GetRootKey()
{
return ReadKeyAtAbsolute(MainKeyBlockOffset);
return CachedRootKey ??= ReadKeyAtAbsolute(MainKeyBlockOffset);
}
private void StorePathResult(string Path, HiveKey Key)
{
if (PathCache.Count >= MaxCachedPaths)
PathCache.Clear();
PathCache[Path] = Key;
}
public bool TryOpenPath(string RelativePath, out HiveKey Key)
@@ -86,6 +98,22 @@ namespace Brovan.Core.Helpers
return true;
}
if (PathCache.TryGetValue(RelativePath, out HiveKey Cached))
{
Key = Cached;
return Cached != null;
}
bool Resolved = TryOpenPathUncached(RelativePath, out HiveKey Opened);
StorePathResult(RelativePath, Resolved ? Opened : null);
Key = Resolved ? Opened : default;
return Resolved;
}
private bool TryOpenPathUncached(string RelativePath, out HiveKey Key)
{
Key = default;
static bool TryTraverse(RegistryHiveReader Self, string Path, out HiveKey OutKey)
{
OutKey = default;
@@ -96,14 +124,30 @@ namespace Brovan.Core.Helpers
Path = Path.Replace("\\CurrentControlSet\\", "\\ControlSet001\\", StringComparison.OrdinalIgnoreCase);
string[] Parts = Path.Trim('\\').Split('\\', StringSplitOptions.RemoveEmptyEntries);
string Prefix = string.Empty;
for (int i = 0; i < Parts.Length; i++)
{
Prefix = string.Concat(Prefix, "\\", Parts[i]);
if (Self.PathCache.TryGetValue(Prefix, out HiveKey CachedNode))
{
if (!Self.TryGetSubKey(Current, Parts[i], out HiveKey Next))
if (CachedNode == null)
return false;
Current = Next;
Current = CachedNode;
continue;
}
if (!Self.TryGetSubKey(Current, Parts[i], out HiveKey Next))
{
Self.StorePathResult(Prefix, null);
return false;
}
Self.StorePathResult(Prefix, Next);
Current = Next;
}
OutKey = Current;
return true;
}