Improve KVM hooks and Windows time emulation

Adds MMIO mapping support to the emulation backend and significantly improves KVM memory handling, including trapped read/write hooks, MMIO access completion, page tracking, safer backing allocation lifetime, and register cache flush/invalidate behavior around vCPU runs. It also fixes KVM constants and tightens hook validation/error paths. On the Windows side, guest time and performance counter behavior are made more consistent by using wall-clock plus skew tick accounting, exposing QPC frequency in KUSER_SHARED_DATA, scaling QueryPerformanceCounter values, and improving loader-tracker synchronization through syscall/image-map driven pumping.
This commit is contained in:
AdvDebug
2026-07-17 08:35:37 +03:00
parent 2753e32be0
commit 5e50feef7e
10 changed files with 555 additions and 127 deletions
@@ -73,6 +73,8 @@ namespace Brovan.Core.Emulation
}
public delegate bool MemoryHookCallback(BackendMemoryAccessType type, ulong address, uint size, ulong value);
public delegate void MmioReadCallback(ulong offset, Span<byte> destination);
public delegate void MmioWriteCallback(ulong offset, ReadOnlySpan<byte> data);
public delegate void CodeHookCallback(ulong address, uint size);
public delegate void InterruptHookCallback(uint interruptNumber);
public delegate void InstructionHookCallback();
@@ -91,6 +93,8 @@ namespace Brovan.Core.Emulation
bool UnmapMemory(ulong address, ulong size);
bool SetMemoryProtection(ulong address, ulong size, MemoryProtection protection);
bool MapMmio(ulong address, ulong size, MmioReadCallback read, MmioWriteCallback write) => false;
bool WriteMemory(ulong address, byte[] value, uint length = 0);
bool WriteMemory(ulong address, byte[] value, int offset, int length);
bool WriteMemory(ulong address, ReadOnlySpan<byte> value, uint length = 0);
@@ -25,21 +25,31 @@ namespace Brovan.Core.Emulation
public bool MapMemory(ulong address, ulong size, MemoryProtection protection)
=> Inner.MapMemory(address, size, protection);
public bool UnmapMemory(ulong address, ulong size)
=> Inner.UnmapMemory(address, size);
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);
public bool WriteMemory(ulong address, byte[] value, int offset, int length)
=> Inner.WriteMemory(address, value, offset, length);
public bool WriteMemory(ulong address, ReadOnlySpan<byte> value, uint length = 0)
=> Inner.WriteMemory(address, value, length);
public bool WriteMemory(ulong address, ulong value, uint length = 0)
=> Inner.WriteMemory(address, value, length);
public bool WriteMemory(ulong address, uint value, uint length = 0)
=> Inner.WriteMemory(address, value, length);
public bool WriteMemory(ulong address, int value, uint length = 0)
=> Inner.WriteMemory(address, value, length);
public bool WriteMemory(ulong address, ushort value, uint length = 0)
+25 -15
View File
@@ -390,10 +390,23 @@ namespace Brovan.Core.Emulation
private const ulong RdtscpReadCycles = 90;
private readonly long EmulatedSystemTimeBaseFileTimeUtc = DateTime.UtcNow.ToFileTimeUtc();
private readonly System.Diagnostics.Stopwatch _wallClock = System.Diagnostics.Stopwatch.StartNew();
private long _emulatedTimeSkewMilliseconds;
/// <summary>
/// Current deterministic guest tick count in milliseconds.
/// Current guest tick count in milliseconds.
/// </summary>
internal long EmulatedTickCount64 { get; private set; }
internal long EmulatedTickCount64
{
get
{
long elapsed = _wallClock.ElapsedMilliseconds;
long skew = Volatile.Read(ref _emulatedTimeSkewMilliseconds);
if (elapsed > long.MaxValue - skew)
return long.MaxValue;
return elapsed + skew;
}
}
/// <summary>
/// Returns the current deterministic guest system time as a Windows file time.
@@ -429,26 +442,21 @@ namespace Brovan.Core.Emulation
}
/// <summary>
/// Advances deterministic guest time without depending on host execution speed.
/// Advances guest time for a wait that was not served in real time.
/// </summary>
internal void AdvanceEmulatedTimeMilliseconds(long Milliseconds, bool AdvanceTimestampCounter = false)
{
if (Milliseconds <= 0)
return;
long AppliedMilliseconds;
if (EmulatedTickCount64 > long.MaxValue - Milliseconds)
{
AppliedMilliseconds = long.MaxValue - EmulatedTickCount64;
EmulatedTickCount64 = long.MaxValue;
}
else
{
AppliedMilliseconds = Milliseconds;
EmulatedTickCount64 += Milliseconds;
}
long Skew = Volatile.Read(ref _emulatedTimeSkewMilliseconds);
long AppliedMilliseconds = Skew > long.MaxValue - Milliseconds ? long.MaxValue - Skew : Milliseconds;
if (AppliedMilliseconds <= 0)
return;
if (AdvanceTimestampCounter && AppliedMilliseconds > 0)
Interlocked.Add(ref _emulatedTimeSkewMilliseconds, AppliedMilliseconds);
if (AdvanceTimestampCounter)
{
ulong Ticks = (ulong)AppliedMilliseconds;
if (Ticks > (ulong.MaxValue - _timestampCounter) / TscCyclesPerMillisecond)
@@ -2472,6 +2480,8 @@ namespace Brovan.Core.Emulation
if (Debug)
TriggerDebugMessage($"scheduler: slice exception tid={ImmaBeEmulatedOOO.ThreadId} {ex.GetType().Name}: {ex.Message}");
Utils.LogError($"[Scheduler] Thread {ImmaBeEmulatedOOO.ThreadId} terminated by an unhandled {ex.GetType().Name}: {ex.Message}");
if (ImmaBeEmulatedOOO.State != EmulatedThreadState.Terminated)
ImmaBeEmulatedOOO.ExitCode = unchecked((int)(uint)ImmaBeEmulatedOOO.Context.RAX);
@@ -519,6 +519,8 @@ namespace Brovan.Core.Emulation.Guests
public bool TryHandleSyscall(BinaryEmulator Instance)
{
WinHelper?.LdrTracker?.SyncFromSyscall();
try
{
uint Syscall = Instance._binary.Architecture == BinaryArchitecture.x64
@@ -59,7 +59,7 @@ namespace Brovan.Core.Emulation
public const uint KvmIoSetMsrs = 0x4008AE89;
public const uint KvmIoGetFpu = 0x81A0AE8C;
public const uint KvmIoSetFpu = 0x41A0AE8D;
public const uint KvmIoGetCpuid2 = 0x90B0AE91;
public const uint KvmIoGetCpuid2 = 0xC008AE91;
public const uint KvmIoSetCpuid2 = 0x4008AE90;
public const uint KvmIoGetXsave = 0x9000AEA4;
public const uint KvmIoSetXsave = 0x5000AEA5;
+386 -58
View File
@@ -40,6 +40,8 @@ namespace Brovan.Core.Emulation
private readonly Dictionary<ulong, InstalledSlot> _activeSlots = new();
private readonly SortedDictionary<ulong, MappedPage> _mappedPages = new();
private readonly Dictionary<IntPtr, BackingAllocation> _backingAllocations = new();
private readonly HashSet<ulong> _trappedPages = new();
private readonly Dictionary<ulong, IntPtr> _pageTableViews = new();
private ulong _pml4Gpa;
private ulong _nextInternalGpa = KvmConstants.InternalPageTableBase;
@@ -59,6 +61,13 @@ namespace Brovan.Core.Emulation
private readonly object _vcpuLock = new();
private LinuxKvmRegisters _regsCache;
private LinuxKvmSpecialRegisters _sregsCache;
private bool _regsValid;
private bool _regsDirty;
private bool _sregsValid;
private bool _sregsDirty;
private readonly List<MemoryHookEntry> _memoryHooks = new();
private readonly List<MmioRegion> _mmioRegions = new();
private InstructionHookEntry _syscallHook;
@@ -84,10 +93,15 @@ namespace Brovan.Core.Emulation
{
public IntPtr HostPage;
public IntPtr OwnedBacking;
public ulong OwnedSize;
public KvmMemoryPermission Permissions;
}
private sealed class BackingAllocation
{
public ulong Size;
public int LivePages;
}
private sealed class InstalledSlot
{
public int Id;
@@ -108,8 +122,8 @@ namespace Brovan.Core.Emulation
{
public ulong Address;
public ulong Size;
public MemoryHookCallback ReadCallback;
public MemoryHookCallback WriteCallback;
public MmioReadCallback ReadCallback;
public MmioWriteCallback WriteCallback;
}
private sealed class InstructionHookEntry
@@ -152,6 +166,7 @@ namespace Brovan.Core.Emulation
InitializeVirtualProcessorState();
InitializeSyscallTrapPage();
InitializeExceptionHandling();
FlushRegisterCache();
}
public KvmErrors GetLastError() => _error;
@@ -182,14 +197,19 @@ namespace Brovan.Core.Emulation
{
IntPtr backing = AllocateBackingMemory(size);
long backingAddr = backing.ToInt64();
_backingAllocations[backing] = new BackingAllocation
{
Size = size,
LivePages = (int)(size / KvmConstants.PageSize),
};
for (ulong off = 0; off < size; off += KvmConstants.PageSize)
{
ulong guest = address + off;
MappedPage page = new MappedPage
{
HostPage = new IntPtr(backingAddr + (long)off),
OwnedBacking = off == 0 ? backing : IntPtr.Zero,
OwnedSize = off == 0 ? size : 0,
OwnedBacking = backing,
Permissions = perm,
};
_mappedPages[guest] = page;
@@ -213,9 +233,13 @@ namespace Brovan.Core.Emulation
if (page.HostPage == IntPtr.Zero)
{
IntPtr backing = AllocateBackingMemory(KvmConstants.PageSize);
_backingAllocations[backing] = new BackingAllocation
{
Size = KvmConstants.PageSize,
LivePages = 1,
};
page.HostPage = backing;
page.OwnedBacking = backing;
page.OwnedSize = KvmConstants.PageSize;
}
page.Permissions = perm;
@@ -242,8 +266,7 @@ namespace Brovan.Core.Emulation
ulong guest = address + off;
if (_mappedPages.TryGetValue(guest, out MappedPage page))
{
if (page.OwnedBacking != IntPtr.Zero && page.OwnedSize > 0)
FreeBackingMemory(page.OwnedBacking, page.OwnedSize);
ReleaseBacking(page);
_mappedPages.Remove(guest);
}
}
@@ -260,6 +283,36 @@ namespace Brovan.Core.Emulation
return true;
}
public bool MapMmio(ulong address, ulong size, MmioReadCallback read, MmioWriteCallback write)
{
if (DisposedCheck()) return false;
if (write == null || write == null ||
(address & KvmConstants.PageMask) != 0 || (size & KvmConstants.PageMask) != 0 || size == 0)
{
_error = KvmErrors.InvalidArgument;
return false;
}
if (!MapMemory(address, size, MemoryProtection.Read))
return false;
MmioRegion region = new MmioRegion
{
Address = address,
Size = size,
ReadCallback = read,
WriteCallback = write,
};
_mmioRegions.RemoveAll(r => r.Address == address);
_mmioRegions.Add(region);
RefreshMmioRegion(region);
_error = KvmErrors.Ok;
return true;
}
public bool SetMemoryProtection(ulong address, ulong size, MemoryProtection protection)
{
if (DisposedCheck()) return false;
@@ -302,6 +355,12 @@ namespace Brovan.Core.Emulation
return true;
}
if (TryWriteMemoryInternal(address, new ReadOnlySpan<byte>(value, offset, length)))
{
_error = KvmErrors.Ok;
return true;
}
_error = KvmErrors.MemoryWriteUnmapped;
return false;
}
@@ -320,6 +379,12 @@ namespace Brovan.Core.Emulation
return true;
}
if (TryWriteMemoryInternal(address, value.Slice(0, (int)writeLen)))
{
_error = KvmErrors.Ok;
return true;
}
_error = KvmErrors.MemoryWriteUnmapped;
return false;
}
@@ -348,7 +413,7 @@ namespace Brovan.Core.Emulation
public bool WriteMemoryByte(ulong address, byte value, uint length = 0)
{
if (DisposedCheck()) return false;
if (length == 0) { _error = KvmErrors.Ok; return true; }
if (length == 0) return false;
if (length <= 16)
{
@@ -400,11 +465,21 @@ namespace Brovan.Core.Emulation
if (DisposedCheck()) return Array.Empty<byte>();
if (length > int.MaxValue) return null;
byte[] value = new byte[length];
if (length > 0 && TryGetHostPointer(address, (int)length, out byte* src, out long offset))
if (length == 0)
{
_error = KvmErrors.Ok;
return value;
}
if (TryGetHostPointer(address, (int)length, out byte* src, out long offset))
{
_error = KvmErrors.Ok;
Unsafe.CopyBlockUnaligned(ref value[0], ref Unsafe.AsRef<byte>(src + offset), length);
}
else if (TryReadMemoryInternal(address, value))
{
_error = KvmErrors.Ok;
}
else
{
_error = KvmErrors.MemoryReadUnmapped;
@@ -426,6 +501,12 @@ namespace Brovan.Core.Emulation
return true;
}
if (TryReadMemoryInternal(address, value.Slice(0, (int)readLen)))
{
_error = KvmErrors.Ok;
return true;
}
_error = KvmErrors.MemoryReadUnmapped;
return false;
}
@@ -445,6 +526,12 @@ namespace Brovan.Core.Emulation
_error = KvmErrors.Ok;
return *(ulong*)(ptr + offset);
}
Span<byte> buffer = stackalloc byte[sizeof(ulong)];
if (TryReadMemoryInternal(address, buffer))
{
_error = KvmErrors.Ok;
return BitConverter.ToUInt64(buffer);
}
_error = KvmErrors.MemoryReadUnmapped;
return 0;
}
@@ -457,6 +544,12 @@ namespace Brovan.Core.Emulation
_error = KvmErrors.Ok;
return *(uint*)(ptr + offset);
}
Span<byte> buffer = stackalloc byte[sizeof(uint)];
if (TryReadMemoryInternal(address, buffer))
{
_error = KvmErrors.Ok;
return BitConverter.ToUInt32(buffer);
}
_error = KvmErrors.MemoryReadUnmapped;
return 0;
}
@@ -469,6 +562,12 @@ namespace Brovan.Core.Emulation
_error = KvmErrors.Ok;
return *(ushort*)(ptr + offset);
}
Span<byte> buffer = stackalloc byte[sizeof(ushort)];
if (TryReadMemoryInternal(address, buffer))
{
_error = KvmErrors.Ok;
return BitConverter.ToUInt16(buffer);
}
_error = KvmErrors.MemoryReadUnmapped;
return 0;
}
@@ -486,6 +585,10 @@ namespace Brovan.Core.Emulation
_error = KvmErrors.Ok;
Unsafe.CopyBlockUnaligned(ref buffer[0], ref Unsafe.AsRef<byte>(src + offset), (uint)length);
}
else if (TryReadMemoryInternal(address, buffer.AsSpan(0, length)))
{
_error = KvmErrors.Ok;
}
else
{
_error = KvmErrors.MemoryReadUnmapped;
@@ -659,12 +762,16 @@ namespace Brovan.Core.Emulation
RefreshMmioBackedRegions();
FlushRegisterCache();
int rc;
lock (_vcpuLock)
{
rc = KvmNative.ioctl(_vcpuFd, KvmConstants.KvmIoRun, IntPtr.Zero);
}
InvalidateRegisterCache();
if (rc < 0)
{
int errno = Marshal.GetLastWin32Error();
@@ -676,6 +783,7 @@ namespace Brovan.Core.Emulation
}
run = ref GetRunRef();
switch (run.ExitReason)
{
case KvmConstants.ExitHlt:
@@ -750,19 +858,39 @@ namespace Brovan.Core.Emulation
return true;
}
private const BackendHookType WhitelistedMemoryHookTypes =
private const BackendHookType FaultMemoryHookTypes =
BackendHookType.MemoryUnmapped | BackendHookType.MemoryProtected;
private const BackendHookType TrappedMemoryHookTypes =
BackendHookType.MemoryRead | BackendHookType.MemoryWrite | BackendHookType.MemoryReadAfter;
private const BackendHookType SupportedMemoryHookTypes =
FaultMemoryHookTypes | TrappedMemoryHookTypes;
private static bool IsUnboundedRange(ulong begin, ulong end) => end == 0 || end < begin;
public IntPtr AddMemoryHook(ulong begin, ulong end, BackendHookType hookType, MemoryHookCallback callback)
{
if (callback == null) return IntPtr.Zero;
if (DisposedCheck()) return IntPtr.Zero;
if (NoHooks && (hookType & WhitelistedMemoryHookTypes) == 0)
if (NoHooks && (hookType & FaultMemoryHookTypes) == 0)
{
_error = KvmErrors.Ok;
return IntPtr.Zero;
}
if ((hookType & ~SupportedMemoryHookTypes) != 0)
{
_error = KvmErrors.HookError;
return IntPtr.Zero;
}
if ((hookType & TrappedMemoryHookTypes) != 0 && IsUnboundedRange(begin, end))
{
_error = KvmErrors.HookError;
return IntPtr.Zero;
}
MemoryHookEntry entry = new MemoryHookEntry
{
Begin = begin,
@@ -771,15 +899,39 @@ namespace Brovan.Core.Emulation
Callback = callback
};
_memoryHooks.Add(entry);
if ((hookType & TrappedMemoryHookTypes) != 0)
RefreshTrappedPages();
return PinHookEntry(entry);
}
private void RefreshTrappedPages()
{
_trappedPages.Clear();
for (int i = 0; i < _memoryHooks.Count; i++)
{
MemoryHookEntry entry = _memoryHooks[i];
if ((entry.Type & TrappedMemoryHookTypes) == 0) continue;
if (IsUnboundedRange(entry.Begin, entry.End)) continue;
ulong first = entry.Begin & ~KvmConstants.PageMask;
ulong last = entry.End & ~KvmConstants.PageMask;
for (ulong page = first; page <= last; page += KvmConstants.PageSize)
_trappedPages.Add(page);
}
RebuildMappings();
}
public IntPtr AddCodeHook(ulong begin, ulong end, CodeHookCallback callback)
{
if (DisposedCheck()) return IntPtr.Zero;
if (NoHooks) { _error = KvmErrors.Ok; return IntPtr.Zero; }
return PinHookEntry(new object());
_error = KvmErrors.HookError;
return IntPtr.Zero;
}
public IntPtr AddInterruptHook(InterruptHookCallback callback)
@@ -845,7 +997,10 @@ namespace Brovan.Core.Emulation
switch (target)
{
case MemoryHookEntry mem: _memoryHooks.Remove(mem); break;
case MemoryHookEntry mem:
_memoryHooks.Remove(mem);
if ((mem.Type & TrappedMemoryHookTypes) != 0) RefreshTrappedPages();
break;
case InterruptHookEntry intr: _interruptHooks.Remove(intr); break;
case InstructionHookEntry ins:
_instructionHooks.Remove(ins);
@@ -870,6 +1025,10 @@ namespace Brovan.Core.Emulation
_instructionHooks.Clear();
_interruptHooks.Clear();
_syscallHook = null;
if (_trappedPages.Count > 0 && !Disposing)
RefreshTrappedPages();
_error = KvmErrors.Ok;
return true;
}
@@ -909,13 +1068,9 @@ namespace Brovan.Core.Emulation
_runMmapPtr = IntPtr.Zero;
}
HashSet<IntPtr> freed = new HashSet<IntPtr>();
foreach (KeyValuePair<ulong, MappedPage> kv in _mappedPages)
{
IntPtr backing = kv.Value.OwnedBacking;
if (backing != IntPtr.Zero && freed.Add(backing))
FreeBackingMemory(backing, kv.Value.OwnedSize);
}
foreach (KeyValuePair<IntPtr, BackingAllocation> kv in _backingAllocations)
FreeBackingMemory(kv.Key, kv.Value.Size);
_backingAllocations.Clear();
_mappedPages.Clear();
if (_internalPoolPtr != IntPtr.Zero)
@@ -1011,13 +1166,23 @@ namespace Brovan.Core.Emulation
KvmNative.MAP_PRIVATE | KvmNative.MAP_ANONYMOUS, -1, 0);
if (ptr == KvmNative.MAP_FAILED)
throw new KvmException("mmap failed", Marshal.GetLastWin32Error());
Unsafe.InitBlockUnaligned((void*)ptr, 0, (uint)size);
return ptr;
}
private static unsafe void FreeBackingMemory(IntPtr ptr, ulong size)
=> KvmNative.munmap(ptr, (UIntPtr)size);
private void ReleaseBacking(MappedPage page)
{
if (page.OwnedBacking == IntPtr.Zero) return;
if (!_backingAllocations.TryGetValue(page.OwnedBacking, out BackingAllocation allocation)) return;
if (--allocation.LivePages > 0) return;
FreeBackingMemory(page.OwnedBacking, allocation.Size);
_backingAllocations.Remove(page.OwnedBacking);
}
private unsafe ref LinuxKvmRun GetRunRef()
=> ref Unsafe.AsRef<LinuxKvmRun>((void*)_runMmapPtr);
@@ -1116,7 +1281,8 @@ namespace Brovan.Core.Emulation
sregs.Cr0 = 0x80000033UL;
sregs.Cr4 = 0x620UL;
sregs.Cr3 = _pml4Gpa;
sregs.Efer = (1UL << 0) | (1UL << 8) | (1UL << 10);
sregs.Efer = (1UL << 0) | (1UL << 8) | (1UL << 10) | (1UL << 11);
SetSpecialRegisters(sregs);
LinuxKvmRegisters regs = GetRegisters();
@@ -1322,7 +1488,6 @@ namespace Brovan.Core.Emulation
{
HostPage = new IntPtr(backingBase + (long)off),
OwnedBacking = IntPtr.Zero,
OwnedSize = 0,
Permissions = permissions,
};
_mappedPages[pageGpa] = page;
@@ -1394,7 +1559,8 @@ namespace Brovan.Core.Emulation
if (!_mappedPages.TryGetValue(runAddress, out MappedPage page)
|| page == null
|| page.HostPage == IntPtr.Zero
|| page.Permissions == KvmMemoryPermission.None)
|| page.Permissions == KvmMemoryPermission.None
|| _trappedPages.Contains(runAddress))
{
i++;
continue;
@@ -1410,6 +1576,7 @@ namespace Brovan.Core.Emulation
if (!_mappedPages.TryGetValue(sortedKeys[j], out MappedPage next) || next == null) break;
if (next.Permissions != page.Permissions) break;
if (sortedKeys[j] != runAddress + runSize) break;
if (_trappedPages.Contains(sortedKeys[j])) break;
long nextHostLong = next.HostPage.ToInt64();
if (nextHostLong != runHostBaseLong + (long)runSize) break;
runSize += KvmConstants.PageSize;
@@ -1497,8 +1664,22 @@ namespace Brovan.Core.Emulation
private void RefreshMmioBackedRegions()
{
if (_mmioRegions.Count == 0) return;
for (int i = 0; i < _mmioRegions.Count; i++)
RefreshMmioRegion(_mmioRegions[i]);
}
private unsafe void RefreshMmioRegion(MmioRegion region)
{
for (ulong offset = 0; offset < region.Size; offset += KvmConstants.PageSize)
{
if (!_mappedPages.TryGetValue(region.Address + offset, out MappedPage page)
|| page == null
|| page.HostPage == IntPtr.Zero)
continue;
int chunk = (int)Math.Min(KvmConstants.PageSize, region.Size - offset);
region.ReadCallback(offset, new Span<byte>((void*)page.HostPage, chunk));
}
}
private bool HandlePreRunInstruction()
@@ -1544,6 +1725,32 @@ namespace Brovan.Core.Emulation
return true;
}
private unsafe bool TryWriteMemoryInternal(ulong address, ReadOnlySpan<byte> buffer)
{
ulong current = address;
int offset = 0;
int remaining = buffer.Length;
while (remaining > 0)
{
ulong pageBase = current & ~KvmConstants.PageMask;
if (!_mappedPages.TryGetValue(pageBase, out MappedPage page) || page == null || page.HostPage == IntPtr.Zero)
return false;
ulong pageOffset = current - pageBase;
int chunk = (int)Math.Min((ulong)remaining, KvmConstants.PageSize - pageOffset);
Unsafe.CopyBlockUnaligned(
ref Unsafe.AsRef<byte>((void*)(page.HostPage + (int)pageOffset)),
ref Unsafe.AsRef<byte>(in buffer[offset]),
(uint)chunk);
current += (ulong)chunk;
offset += chunk;
remaining -= chunk;
}
return true;
}
private bool HandleInstructionHook(BackendInstructionHook type, ulong instructionSize)
{
ulong rip = ReadRegister(Registers.UC_X86_REG_RIP);
@@ -1652,36 +1859,90 @@ namespace Brovan.Core.Emulation
foreach (MmioRegion region in _mmioRegions)
{
if (physAddr >= region.Address && physAddr < region.Address + region.Size)
{
if (isWrite != 0 && region.WriteCallback != null)
{
region.WriteCallback(BackendMemoryAccessType.Write, physAddr, len, mmio.Data);
}
else if (isWrite == 0 && region.ReadCallback != null)
{
ulong data = mmio.Data;
region.ReadCallback(BackendMemoryAccessType.Read, physAddr, len, data);
}
return true;
}
if (physAddr < region.Address || physAddr >= region.Address + region.Size) continue;
if (isWrite == 0) break;
ulong data = mmio.Data;
region.WriteCallback(physAddr - region.Address,
new ReadOnlySpan<byte>(&data, (int)Math.Min(len, sizeof(ulong))));
return true;
}
BackendMemoryAccessType type = isWrite != 0
? BackendMemoryAccessType.WriteUnmapped
: BackendMemoryAccessType.ReadUnmapped;
ulong faultPage = physAddr & ~KvmConstants.PageMask;
bool mapped = _mappedPages.TryGetValue(faultPage, out MappedPage faulted)
&& faulted != null
&& faulted.HostPage != IntPtr.Zero;
if (mapped && _trappedPages.Contains(faultPage))
return HandleTrappedAccess(ref mmio, physAddr, len, isWrite != 0);
BackendHookType required = mapped ? BackendHookType.MemoryProtected : BackendHookType.MemoryUnmapped;
BackendMemoryAccessType type = mapped
? (isWrite != 0 ? BackendMemoryAccessType.WriteProtected : BackendMemoryAccessType.ReadProtected)
: (isWrite != 0 ? BackendMemoryAccessType.WriteUnmapped : BackendMemoryAccessType.ReadUnmapped);
for (int i = 0; i < _memoryHooks.Count; i++)
{
MemoryHookEntry entry = _memoryHooks[i];
if ((entry.Type & BackendHookType.MemoryUnmapped) == 0) continue;
if ((entry.Type & required) == 0) continue;
if (entry.End == 0 || entry.End < entry.Begin || (entry.Begin <= physAddr && entry.End >= physAddr))
{
if (entry.Callback(type, physAddr, len, 0)) return true;
if (entry.Callback(type, physAddr, len, isWrite != 0 ? mmio.Data : 0))
{
CompleteMmioAccess(ref mmio);
return true;
}
}
}
return false;
}
private unsafe bool HandleTrappedAccess(ref LinuxKvmMmioExit mmio, ulong physAddr, uint len, bool isWrite)
{
BackendHookType required = isWrite ? BackendHookType.MemoryWrite : BackendHookType.MemoryRead;
BackendMemoryAccessType type = isWrite ? BackendMemoryAccessType.Write : BackendMemoryAccessType.Read;
for (int i = 0; i < _memoryHooks.Count; i++)
{
MemoryHookEntry entry = _memoryHooks[i];
if ((entry.Type & required) == 0) continue;
if (entry.Begin > physAddr || entry.End < physAddr) continue;
entry.Callback(type, physAddr, len, isWrite ? mmio.Data : 0);
}
CompleteMmioAccess(ref mmio);
if (!isWrite)
{
for (int i = 0; i < _memoryHooks.Count; i++)
{
MemoryHookEntry entry = _memoryHooks[i];
if ((entry.Type & BackendHookType.MemoryReadAfter) == 0) continue;
if (entry.Begin > physAddr || entry.End < physAddr) continue;
entry.Callback(BackendMemoryAccessType.ReadAfter, physAddr, len, mmio.Data);
}
}
return true;
}
private unsafe void CompleteMmioAccess(ref LinuxKvmMmioExit mmio)
{
uint len = mmio.Len;
if (len == 0 || len > sizeof(ulong)) return;
if (mmio.IsWrite != 0)
{
ulong data = mmio.Data;
TryWriteMemoryInternal(mmio.PhysAddr, new ReadOnlySpan<byte>(&data, (int)len));
return;
}
ulong value = 0;
if (TryReadMemoryInternal(mmio.PhysAddr, new Span<byte>(&value, (int)len)))
mmio.Data = value;
}
private bool HandleException(uint exception, uint errorCode)
{
if (exception == 6 && HandleInvalidInstructionHook()) return true;
@@ -1689,13 +1950,18 @@ namespace Brovan.Core.Emulation
if (exception == 14)
{
ulong faultAddress = ReadRegister(Registers.UC_X86_REG_CR2);
BackendMemoryAccessType type = (errorCode & 0x2) != 0
? BackendMemoryAccessType.WriteUnmapped
: BackendMemoryAccessType.ReadUnmapped;
if ((errorCode & 0x1) != 0)
type = (errorCode & 0x2) != 0
? BackendMemoryAccessType.WriteProtected
: BackendMemoryAccessType.ReadProtected;
bool present = (errorCode & 0x1) != 0;
bool write = (errorCode & 0x2) != 0;
bool fetch = (errorCode & 0x10) != 0;
BackendMemoryAccessType type;
if (fetch)
type = present ? BackendMemoryAccessType.FetchProtected : BackendMemoryAccessType.FetchUnmapped;
else if (write)
type = present ? BackendMemoryAccessType.WriteProtected : BackendMemoryAccessType.WriteUnmapped;
else
type = present ? BackendMemoryAccessType.ReadProtected : BackendMemoryAccessType.ReadUnmapped;
FlushRegisterCache();
for (int i = 0; i < _memoryHooks.Count; i++)
@@ -1886,9 +2152,13 @@ namespace Brovan.Core.Emulation
{
lock (_vcpuLock)
{
if (_regsValid) return _regsCache;
LinuxKvmRegisters r = new LinuxKvmRegisters();
if (KvmNative.ioctl(_vcpuFd, KvmConstants.KvmIoGetRegisters, ref r) < 0)
throw new KvmException("KVM_GET_REGS failed", Marshal.GetLastWin32Error());
_regsCache = r;
_regsValid = true;
return r;
}
}
@@ -1897,8 +2167,9 @@ namespace Brovan.Core.Emulation
{
lock (_vcpuLock)
{
if (KvmNative.ioctl(_vcpuFd, KvmConstants.KvmIoSetRegisters, ref regs) < 0)
throw new KvmException("KVM_SET_REGS failed", Marshal.GetLastWin32Error());
_regsCache = regs;
_regsValid = true;
_regsDirty = true;
}
}
@@ -1906,9 +2177,13 @@ namespace Brovan.Core.Emulation
{
lock (_vcpuLock)
{
if (_sregsValid) return _sregsCache;
LinuxKvmSpecialRegisters s = new LinuxKvmSpecialRegisters();
if (KvmNative.ioctl(_vcpuFd, KvmConstants.KvmIoGetSpecialRegisters, ref s) < 0)
throw new KvmException("KVM_GET_SREGS failed", Marshal.GetLastWin32Error());
_sregsCache = s;
_sregsValid = true;
return s;
}
}
@@ -1917,14 +2192,40 @@ namespace Brovan.Core.Emulation
{
lock (_vcpuLock)
{
if (KvmNative.ioctl(_vcpuFd, KvmConstants.KvmIoSetSpecialRegisters, ref sregs) < 0)
throw new KvmException("KVM_SET_SREGS failed", Marshal.GetLastWin32Error());
_sregsCache = sregs;
_sregsValid = true;
_sregsDirty = true;
}
}
private void FlushRegisterCache() { }
private void FlushRegisterCache()
{
lock (_vcpuLock)
{
if (_regsDirty)
{
_regsDirty = false;
if (KvmNative.ioctl(_vcpuFd, KvmConstants.KvmIoSetRegisters, ref _regsCache) < 0)
throw new KvmException("KVM_SET_REGS failed", Marshal.GetLastWin32Error());
}
private void InvalidateRegisterCache() { }
if (_sregsDirty)
{
_sregsDirty = false;
if (KvmNative.ioctl(_vcpuFd, KvmConstants.KvmIoSetSpecialRegisters, ref _sregsCache) < 0)
throw new KvmException("KVM_SET_SREGS failed", Marshal.GetLastWin32Error());
}
}
}
private void InvalidateRegisterCache()
{
lock (_vcpuLock)
{
_regsValid = false;
_sregsValid = false;
}
}
private unsafe void SetFpu(ref LinuxKvmFpu fpu)
{
@@ -2001,6 +2302,8 @@ namespace Brovan.Core.Emulation
return true;
}
if (!IsSregRegister(register)) return false;
LinuxKvmSpecialRegisters s = GetSpecialRegisters();
if (!TryApplySregWrite(ref s, register, value)) return false;
SetSpecialRegisters(s);
@@ -2015,10 +2318,35 @@ namespace Brovan.Core.Emulation
return true;
}
if (!IsSregRegister(register))
{
value = 0;
return false;
}
LinuxKvmSpecialRegisters s = GetSpecialRegisters();
return TryApplySregRead(ref s, register, out value);
}
private static bool IsSregRegister(Registers register) => register switch
{
Registers.UC_X86_REG_FS_BASE => true,
Registers.UC_X86_REG_GS_BASE => true,
Registers.UC_X86_REG_CS => true,
Registers.UC_X86_REG_SS => true,
Registers.UC_X86_REG_DS => true,
Registers.UC_X86_REG_ES => true,
Registers.UC_X86_REG_FS => true,
Registers.UC_X86_REG_GS => true,
Registers.UC_X86_REG_CR0 => true,
Registers.UC_X86_REG_CR2 => true,
Registers.UC_X86_REG_CR3 => true,
Registers.UC_X86_REG_CR4 => true,
Registers.UC_X86_REG_CR8 => true,
Registers.UC_X86_REG_MSR => true,
_ => false,
};
private static bool TryApplySregWrite(ref LinuxKvmSpecialRegisters s, Registers register, ulong value)
{
switch (register)
@@ -35,6 +35,8 @@ namespace Brovan.Core.Emulation.OS.Windows
if (!Instance.WinHelper.UnmapViewOfSection(BaseAddress))
return NTSTATUS.STATUS_INVALID_ADDRESS;
Instance.WinHelper.LdrTracker?.NotifyImageMapped();
if (Flags == 0 && string.Equals(SyscallName, nameof(NtUnmapViewOfSection), StringComparison.Ordinal))
if ((Instance.Settings.Flags & LogFlags.Syscall) != 0)
Instance.TriggerEventMessage($"[+] NtUnmapViewOfSection: Base=0x{BaseAddress:X}", LogFlags.Syscall);
@@ -23,8 +23,7 @@ namespace Brovan.Core.Emulation.OS.Windows
if (PerformanceCounterPtr != 0)
{
ulong CounterValue = (ulong)System.Diagnostics.Stopwatch.GetTimestamp();
Instance._emulator.WriteMemory(PerformanceCounterPtr, CounterValue, 0);
Instance._emulator.WriteMemory(PerformanceCounterPtr, WinSysHelper.QueryPerformanceCounterValue(), 0);
}
if (PerformanceFrequencyPtr != 0 && !Instance.IsRegionMapped(PerformanceFrequencyPtr, 8))
@@ -33,7 +32,7 @@ namespace Brovan.Core.Emulation.OS.Windows
}
else
{
Instance._emulator.WriteMemory(PerformanceFrequencyPtr, 10000000UL);
Instance._emulator.WriteMemory(PerformanceFrequencyPtr, (ulong)KuserSharedDataManager.QpcFrequency);
}
return NTSTATUS.STATUS_SUCCESS;
}
@@ -289,8 +289,11 @@ namespace Brovan.Core.Emulation.OS.Windows
private const int OffsetNtMinorVersion = 0x270;
private const int OffsetProcessorFeatures = 0x274;
private const int OffsetXStateConfiguration = 0x3D8;
private const int OffsetQpcFrequency = 0x300;
private const int OffsetSystemCallX86 = 0x300;
private const int OffsetSystemCallX64 = 0x308;
internal const long QpcFrequency = 10_000_000;
private const int OffsetTickCountQuad = 0x320;
private const int OffsetCookie = 0x330;
@@ -298,7 +301,7 @@ namespace Brovan.Core.Emulation.OS.Windows
private readonly BinaryEmulator Emulator;
private MemoryHookCallback ReadHook;
private bool HookInstalled;
private bool Installed;
private long LastUpdateTimestamp;
@@ -312,34 +315,35 @@ namespace Brovan.Core.Emulation.OS.Windows
public void Initialize()
{
if (HookInstalled)
if (Installed)
return;
if (!Emulator.IsRegionMapped(Emulator.KUSER_SHARED_DATA, PageSize))
byte[] Page = BuildInitialPage();
BaseInterruptTime = ReadKsystemTimeFromBuffer(Page, OffsetInterruptTime);
LastUpdateTimestamp = 0;
if (!Emulator._emulator.MapMmio(Emulator.KUSER_SHARED_DATA, PageSize, FillTimeFields, IgnoreWrite))
{
if (Emulator.MapMemoryRegion(Emulator.KUSER_SHARED_DATA, PageSize, MemoryProtection.Read) == 0)
if (!Emulator.IsRegionMapped(Emulator.KUSER_SHARED_DATA, PageSize) &&
Emulator.MapMemoryRegion(Emulator.KUSER_SHARED_DATA, PageSize, MemoryProtection.Read) == 0)
{
Utils.LogError($"[KUSER_MANAGER] Failed to map KUSER_SHARED_DATA. Last Unicorn Error: {Emulator.GetLastError()}");
Utils.LogError($"[KUSER_MANAGER] Failed to map KUSER_SHARED_DATA: {Emulator.GetLastError()}");
}
ReadHook = OnRead;
if (Emulator._emulator.AddMemoryHook(Emulator.KUSER_SHARED_DATA,
Emulator.KUSER_SHARED_DATA + (PageSize - 1), BackendHookType.MemoryRead, ReadHook) == IntPtr.Zero)
{
Utils.LogError($"[KUSER_MANAGER] No way to keep KUSER_SHARED_DATA current: {Emulator.GetLastError()}");
}
}
byte[] Page = BuildInitialPage();
if (!Emulator._emulator.WriteMemory(Emulator.KUSER_SHARED_DATA, Page))
{
Utils.LogError($"[KUSER_MANAGER] Failed write the initial page data to KUSER_SHARED_DATA. Last Unicorn Error: {Emulator.GetLastError()}");
Utils.LogError($"[KUSER_MANAGER] Failed write the initial page data to KUSER_SHARED_DATA: {Emulator.GetLastError()}");
}
BaseInterruptTime = ReadKsystemTimeFromBuffer(Page, OffsetInterruptTime);
LastUpdateTimestamp = 0;
ReadHook = OnRead;
if (Emulator._emulator.AddMemoryHook(Emulator.KUSER_SHARED_DATA, Emulator.KUSER_SHARED_DATA + (PageSize - 1), BackendHookType.MemoryRead, ReadHook) == IntPtr.Zero)
{
Utils.LogError($"[KUSER_MANAGER] Failed to add a hook for KUSER_SHARED_DATA. Error: {Emulator.GetLastError()}");
}
HookInstalled = true;
Installed = true;
Emulator._emulator.WriteMemory(Emulator.KUSER_SHARED_DATA + (ulong)GetSystemCallOffset(), 0u, 4);
UpdateDynamicFields(true);
@@ -351,6 +355,33 @@ namespace Brovan.Core.Emulation.OS.Windows
return true;
}
private void FillTimeFields(ulong Offset, Span<byte> Destination)
{
if (Offset != 0 || Destination.Length < OffsetTickCountQuad + 12)
return;
ComputeDynamicFields(out ulong SystemTime, out ulong InterruptTime, out ulong TickCountQuad);
WriteKsystemTimeToSpan(Destination, OffsetSystemTime, SystemTime);
WriteKsystemTimeToSpan(Destination, OffsetInterruptTime, InterruptTime);
WriteKsystemTimeToSpan(Destination, OffsetTickCountQuad, TickCountQuad);
BitConverter.TryWriteBytes(Destination.Slice(OffsetTickCountLowDeprecated, 4), (uint)TickCountQuad);
}
private void IgnoreWrite(ulong Offset, ReadOnlySpan<byte> Data)
{
}
private void ComputeDynamicFields(out ulong SystemTime, out ulong InterruptTime, out ulong TickCountQuad)
{
long Now = Emulator.EmulatedTickCount64;
ulong Elapsed100Ns = unchecked((ulong)Math.Max(0, Now)) * 10_000UL;
SystemTime = unchecked((ulong)Emulator.GetEmulatedSystemTimeFileTimeUtc());
InterruptTime = BaseInterruptTime + Elapsed100Ns;
TickCountQuad = InterruptTime / HundredNsPerDefaultTick;
}
private void UpdateDynamicFields(bool Force)
{
long Now = Emulator.EmulatedTickCount64;
@@ -359,23 +390,26 @@ namespace Brovan.Core.Emulation.OS.Windows
LastUpdateTimestamp = Now;
ulong Elapsed100Ns = unchecked((ulong)Math.Max(0, Now)) * 10_000UL;
ulong SystemTime = unchecked((ulong)Emulator.GetEmulatedSystemTimeFileTimeUtc());
ulong InterruptTime = BaseInterruptTime + Elapsed100Ns;
ComputeDynamicFields(out ulong SystemTime, out ulong InterruptTime, out ulong TickCountQuad);
WriteKsystemTimeToMemory(OffsetSystemTime, SystemTime);
WriteKsystemTimeToMemory(OffsetInterruptTime, InterruptTime);
ulong TickCountQuad = InterruptTime / HundredNsPerDefaultTick;
WriteKsystemTimeToMemory(OffsetTickCountQuad, TickCountQuad);
uint TickCountLow = (uint)TickCountQuad;
Emulator._emulator.WriteMemory(Emulator.KUSER_SHARED_DATA + OffsetTickCountLowDeprecated, TickCountLow, 4);
Emulator._emulator.WriteMemory(Emulator.KUSER_SHARED_DATA + OffsetTickCountLowDeprecated, (uint)TickCountQuad, 4);
Emulator._emulator.WriteMemory(Emulator.KUSER_SHARED_DATA + (ulong)GetSystemCallOffset(), 0u, 4);
}
private static void WriteKsystemTimeToSpan(Span<byte> Page, int Offset, ulong Value)
{
uint Low = (uint)(Value & 0xFFFFFFFF);
uint High = (uint)(Value >> 32);
BitConverter.TryWriteBytes(Page.Slice(Offset, 4), Low);
BitConverter.TryWriteBytes(Page.Slice(Offset + 4, 4), High);
BitConverter.TryWriteBytes(Page.Slice(Offset + 8, 4), High);
}
private int GetSystemCallOffset()
{
return Emulator._binary.Architecture == BinaryArchitecture.x86 ? OffsetSystemCallX86 : OffsetSystemCallX64;
@@ -449,6 +483,9 @@ namespace Brovan.Core.Emulation.OS.Windows
// SystemTime
WriteInt64(OffsetSystemTime, Emulator.GetEmulatedSystemTimeFileTimeUtc());
if (Emulator._binary.Architecture != BinaryArchitecture.x86)
WriteInt64(OffsetQpcFrequency, QpcFrequency);
// KdDebuggerEnabled
WriteByte(0x02D4, 0x00);
@@ -567,12 +604,13 @@ namespace Brovan.Core.Emulation.OS.Windows
private bool PebHookInstalled;
private bool BlockHookInstalled;
private bool PollDriven;
private readonly HashSet<ulong> HookedLdrDataBases = new HashSet<ulong>();
private volatile bool PendingRefreshHooks;
private volatile bool PendingSync;
private int DelayBlocks;
private int DelayEdges;
private long LastPumpTicks;
@@ -595,12 +633,42 @@ namespace Brovan.Core.Emulation.OS.Windows
internal void Install()
{
InstallPebLdrPointerHook();
InstallBlockHook();
PollDriven = !BlockHookInstalled;
if (!PollDriven)
InstallPebLdrPointerHook();
NotifyImageMapped();
}
internal void NotifyImageMapped()
{
PendingRefreshHooks = true;
PendingSync = true;
DelayBlocks = 2;
DelayEdges = 2;
}
internal void SyncFromSyscall()
{
if (!PollDriven)
return;
Drain();
}
private void Drain()
{
if (!PendingSync && !PendingRefreshHooks)
return;
if (DelayEdges > 0)
{
DelayEdges--;
return;
}
Pump();
}
private void InstallPebLdrPointerHook()
@@ -641,30 +709,18 @@ namespace Brovan.Core.Emulation.OS.Windows
{
PendingRefreshHooks = true;
PendingSync = true;
DelayBlocks = 2;
DelayEdges = 2;
return true;
}
private bool OnLdrDataWrite(BackendMemoryAccessType type, ulong address, uint size, ulong value)
{
PendingSync = true;
DelayBlocks = 2;
DelayEdges = 2;
return true;
}
private void OnBlock(ulong address, uint size)
{
if (!PendingSync && !PendingRefreshHooks)
return;
if (DelayBlocks > 0)
{
DelayBlocks--;
return;
}
Pump();
}
private void OnBlock(ulong address, uint size) => Drain();
internal void Pump()
{
@@ -687,7 +743,7 @@ namespace Brovan.Core.Emulation.OS.Windows
if (!TrySnapshotAndApply())
{
PendingSync = true;
DelayBlocks = 2;
DelayEdges = 2;
return;
}
@@ -697,15 +753,16 @@ namespace Brovan.Core.Emulation.OS.Windows
private void RefreshLdrHooks()
{
ulong LdrData = SafeReadUlong(Emulator.PEB + (ulong)PebOffsetLdr);
if (LdrData == 0)
if (LdrData == 0 || !Emulator.IsRegionMapped(LdrData, (uint)PebLdrSize))
{
PendingRefreshHooks = true;
return;
}
if (HookedLdrDataBases.Contains(LdrData))
return;
if (!Emulator.IsRegionMapped(LdrData, (uint)PebLdrSize))
return;
ulong Begin = LdrData;
ulong End = LdrData + (ulong)PebLdrSize - 1;
@@ -1005,6 +1005,19 @@ namespace Brovan.Core.Emulation.OS.Windows
public List<WinModule> MappedImageViews = new List<WinModule>();
private readonly Dictionary<string, int> ImageViewCountsByPath = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
internal KuserSharedDataManager KuserSharedData;
/// <summary>
/// Current performance counter, in the units <see cref="KuserSharedDataManager.QpcFrequency"/> claims.
/// </summary>
internal static ulong QueryPerformanceCounterValue()
{
long Ticks = System.Diagnostics.Stopwatch.GetTimestamp();
long HostFrequency = System.Diagnostics.Stopwatch.Frequency;
if (HostFrequency == KuserSharedDataManager.QpcFrequency)
return (ulong)Ticks;
return (ulong)((decimal)Ticks * KuserSharedDataManager.QpcFrequency / HostFrequency);
}
internal HandleManager HandleManager = new HandleManager();
private static string WinRegPath = Path.Combine(AppContext.BaseDirectory, "WinReg");
public RegistryManager RegManager = new RegistryManager(WinRegPath);
@@ -2206,6 +2219,9 @@ namespace Brovan.Core.Emulation.OS.Windows
public void AddModule(WinModule Module, bool TriggerMessage)
{
bool Finished = false;
LdrTracker?.NotifyImageMapped();
try
{
Finished = true;