mirror of
https://github.com/AdvDebug/Brovan
synced 2026-08-09 11:57:17 +00:00
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:
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user