mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-24 11:48:39 +08:00
[AGC/Vulkan] Extend PS5 runtime and rendering compatibility (#216)
* [Core] Add POSIX native execution and PS5 SELF support Extend the native backend, guest TLS, fixed-address memory, and loader paths needed by PS5 titles on Windows, Linux, and macOS. Keep workstation GC so high-core-count hosts do not reserve over fixed guest image bases. * [HLE] Expand PS5 service and media compatibility Add the kernel, threading, save-data, networking, audio, video-codec, font, dialog, and service exports required by newer PS5 software. Preserve every SysAbi NID currently registered by main while adding the compatibility surface used by ASTRO BOT. * [AGC/Vulkan] Extend Gen5 shader and presentation support Expand PM4 handling, Gen5 shader translation, MRT and packed export support, guest image tracking, depth initialization, texture aliasing, and Vulkan presentation. Add the performance overlay and address-filtered diagnostics used to validate ASTRO BOT with original shaders. * [Core] Align static TLS reservation across hosts * [Pad] Align primary user ID with UserService * [Gpu] Preserve runtime scalar buffers across renderer seam * [AGC] Restore omitted command helper exports * [Vulkan] Reuse primary views for promoted MRT targets * [Vulkan] Preserve scratch storage bindings in compute dispatches
This commit is contained in:
@@ -7,15 +7,11 @@ using SharpEmu.Core.Cpu.Native;
|
||||
using SharpEmu.Core.Loader;
|
||||
using SharpEmu.Core.Memory;
|
||||
using SharpEmu.HLE;
|
||||
using SharpEmu.HLE.Host;
|
||||
using SharpEmu.Logging;
|
||||
|
||||
namespace SharpEmu.Core.Cpu;
|
||||
|
||||
public sealed class CpuDispatcher : ICpuDispatcher, IDisposable
|
||||
{
|
||||
private static readonly SharpEmuLogger Log = SharpEmuLog.For("Dispatcher");
|
||||
|
||||
private enum EntryFrameKind
|
||||
{
|
||||
ProcessEntry,
|
||||
@@ -31,9 +27,9 @@ public sealed class CpuDispatcher : ICpuDispatcher, IDisposable
|
||||
private static readonly ulong TlsBaseAddress = OperatingSystem.IsWindows() ? 0x7FFE_0000_0000UL : 0x6FFE_0000_0000UL;
|
||||
private const ulong TlsSize = 0x0001_0000UL;
|
||||
// The static TLS blocks live at negative offsets from the TCB (FreeBSD
|
||||
// amd64 variant II); libc.prx alone reaches beyond -0x1700, so give the
|
||||
// prefix a full 64KB on POSIX. Windows keeps its historical 4KB prefix.
|
||||
private static readonly ulong TlsPrefixSize = OperatingSystem.IsWindows() ? 0x0000_1000UL : 0x0001_0000UL;
|
||||
// amd64 variant II). Keep every host in sync with GuestTlsTemplate's
|
||||
// startup reservation; PS5 modules routinely reach beyond one host page.
|
||||
private const ulong TlsPrefixSize = GuestTlsTemplate.StartupStaticTlsReservation;
|
||||
private static readonly ulong BootstrapStubBaseAddress = OperatingSystem.IsWindows() ? 0x7FFD_F000_0000UL : 0x6FFD_F000_0000UL;
|
||||
private static readonly ulong BootstrapPayloadBaseAddress = OperatingSystem.IsWindows() ? 0x7FFD_E000_0000UL : 0x6FFD_E000_0000UL;
|
||||
private static readonly ulong DynlibFallbackStubBaseAddress = OperatingSystem.IsWindows() ? 0x7FFD_D000_0000UL : 0x6FFD_D000_0000UL;
|
||||
@@ -49,19 +45,16 @@ public sealed class CpuDispatcher : ICpuDispatcher, IDisposable
|
||||
];
|
||||
private readonly IVirtualMemory _virtualMemory;
|
||||
private readonly IModuleManager _moduleManager;
|
||||
private readonly IHostPlatform? _hostPlatform;
|
||||
private INativeCpuBackend? _nativeCpuBackend;
|
||||
|
||||
public CpuDispatcher(
|
||||
IVirtualMemory virtualMemory,
|
||||
IModuleManager moduleManager,
|
||||
INativeCpuBackend? nativeCpuBackend = null,
|
||||
IHostPlatform? hostPlatform = null)
|
||||
INativeCpuBackend? nativeCpuBackend = null)
|
||||
{
|
||||
_virtualMemory = virtualMemory ?? throw new ArgumentNullException(nameof(virtualMemory));
|
||||
_moduleManager = moduleManager ?? throw new ArgumentNullException(nameof(moduleManager));
|
||||
_nativeCpuBackend = nativeCpuBackend;
|
||||
_hostPlatform = hostPlatform;
|
||||
}
|
||||
|
||||
public ulong? LastEntryPoint { get; private set; }
|
||||
@@ -94,8 +87,8 @@ public sealed class CpuDispatcher : ICpuDispatcher, IDisposable
|
||||
string processImageName = "eboot.bin",
|
||||
CpuExecutionOptions executionOptions = default)
|
||||
{
|
||||
Log.Debug("=== DispatchEntry START ===");
|
||||
Log.Debug($"entryPoint=0x{entryPoint:X16}, generation={generation}");
|
||||
Console.Error.WriteLine("[DISPATCHER] === DispatchEntry START ===");
|
||||
Console.Error.WriteLine($"[DISPATCHER] entryPoint=0x{entryPoint:X16}, generation={generation}");
|
||||
|
||||
try
|
||||
{
|
||||
@@ -103,7 +96,8 @@ public sealed class CpuDispatcher : ICpuDispatcher, IDisposable
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Critical($"FATAL EXCEPTION in DispatchEntry: {ex.GetType().Name}: {ex.Message}", ex);
|
||||
Console.Error.WriteLine($"[DISPATCHER] FATAL EXCEPTION in DispatchEntry: {ex.GetType().Name}: {ex.Message}");
|
||||
Console.Error.WriteLine($"[DISPATCHER] Stack trace: {ex.StackTrace}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
@@ -116,8 +110,8 @@ public sealed class CpuDispatcher : ICpuDispatcher, IDisposable
|
||||
string moduleName = "module",
|
||||
CpuExecutionOptions executionOptions = default)
|
||||
{
|
||||
Log.Debug("=== DispatchModuleInitializer START ===");
|
||||
Log.Debug($"moduleInit=0x{entryPoint:X16}, generation={generation}, module={moduleName}");
|
||||
Console.Error.WriteLine("[DISPATCHER] === DispatchModuleInitializer START ===");
|
||||
Console.Error.WriteLine($"[DISPATCHER] moduleInit=0x{entryPoint:X16}, generation={generation}, module={moduleName}");
|
||||
|
||||
try
|
||||
{
|
||||
@@ -132,7 +126,8 @@ public sealed class CpuDispatcher : ICpuDispatcher, IDisposable
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Critical($"FATAL EXCEPTION in DispatchModuleInitializer: {ex.GetType().Name}: {ex.Message}", ex);
|
||||
Console.Error.WriteLine($"[DISPATCHER] FATAL EXCEPTION in DispatchModuleInitializer: {ex.GetType().Name}: {ex.Message}");
|
||||
Console.Error.WriteLine($"[DISPATCHER] Stack trace: {ex.StackTrace}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
@@ -146,7 +141,7 @@ public sealed class CpuDispatcher : ICpuDispatcher, IDisposable
|
||||
CpuExecutionOptions executionOptions = default,
|
||||
EntryFrameKind frameKind = EntryFrameKind.ProcessEntry)
|
||||
{
|
||||
Log.Debug("DispatchEntryCore STARTING...");
|
||||
Console.Error.WriteLine("[DISPATCHER] DispatchEntryCore STARTING...");
|
||||
|
||||
LastEntryPoint = entryPoint;
|
||||
LastTrapInfo = null;
|
||||
@@ -277,7 +272,7 @@ public sealed class CpuDispatcher : ICpuDispatcher, IDisposable
|
||||
entryFrameDiagnostic,
|
||||
Environment.NewLine,
|
||||
"CpuEngine: native-only");
|
||||
_nativeCpuBackend ??= new DirectExecutionBackend(_moduleManager, _hostPlatform);
|
||||
_nativeCpuBackend ??= new DirectExecutionBackend(_moduleManager);
|
||||
if (_nativeCpuBackend.TryExecute(
|
||||
context,
|
||||
entryPoint,
|
||||
@@ -318,7 +313,7 @@ public sealed class CpuDispatcher : ICpuDispatcher, IDisposable
|
||||
LastMilestoneLog,
|
||||
Environment.NewLine,
|
||||
$"CpuEngine native-only failed: {backendError}");
|
||||
Log.Error($"Native backend FAILED: {backendError}");
|
||||
Console.Error.WriteLine($"[DISPATCHER] Native backend FAILED: {backendError}");
|
||||
return FailEarly(
|
||||
OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_IMPLEMENTED,
|
||||
CpuExitReason.NativeBackendUnavailable);
|
||||
@@ -377,11 +372,19 @@ public sealed class CpuDispatcher : ICpuDispatcher, IDisposable
|
||||
|
||||
private static bool InitializeTls(CpuContext context, ulong tlsBase)
|
||||
{
|
||||
return context.TryWriteUInt64(tlsBase - 0xF0, 0) &&
|
||||
context.TryWriteUInt64(tlsBase + 0x00, tlsBase) &&
|
||||
context.TryWriteUInt64(tlsBase + 0x10, tlsBase) &&
|
||||
context.TryWriteUInt64(tlsBase + 0x28, 0xC0DEC0DECAFEBABEUL) &&
|
||||
context.TryWriteUInt64(tlsBase + 0x60, tlsBase);
|
||||
if (!context.TryWriteUInt64(tlsBase - 0xF0, 0) ||
|
||||
!context.TryWriteUInt64(tlsBase + 0x00, tlsBase) ||
|
||||
!context.TryWriteUInt64(tlsBase + 0x10, tlsBase) ||
|
||||
!context.TryWriteUInt64(tlsBase + 0x28, 0xC0DEC0DECAFEBA00UL) ||
|
||||
!context.TryWriteUInt64(tlsBase + 0x60, tlsBase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Seed the static TLS block below the thread pointer with the main
|
||||
// module's initialized thread-locals (variant II layout).
|
||||
SharpEmu.HLE.GuestTlsTemplate.SeedThreadBlock(context, tlsBase);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool InitializeGuestFrameChainSentinel(CpuContext context)
|
||||
@@ -407,35 +410,54 @@ public sealed class CpuDispatcher : ICpuDispatcher, IDisposable
|
||||
ulong programExitHandlerAddress)
|
||||
{
|
||||
var imageName = string.IsNullOrWhiteSpace(processImageName) ? "eboot.bin" : processImageName;
|
||||
var encodedNameLength = Encoding.UTF8.GetByteCount(imageName);
|
||||
Span<byte> argv0Buffer = encodedNameLength + 1 <= 512
|
||||
? stackalloc byte[encodedNameLength + 1]
|
||||
: new byte[encodedNameLength + 1];
|
||||
if (Encoding.UTF8.GetBytes(imageName.AsSpan(), argv0Buffer) != encodedNameLength)
|
||||
var arguments = new List<string>(3) { imageName };
|
||||
var configuredArguments = Environment.GetEnvironmentVariable("SHARPEMU_GUEST_ARGS");
|
||||
if (!string.IsNullOrWhiteSpace(configuredArguments))
|
||||
{
|
||||
return false;
|
||||
// The PS5 entry-parameter ABI exposes three inline argv pointers.
|
||||
// Two compatibility arguments are therefore safe without changing
|
||||
// the fixed 0x20-byte structure expected by existing titles.
|
||||
var compatibilityArguments = configuredArguments.Split(
|
||||
(char[]?)null,
|
||||
StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
arguments.AddRange(compatibilityArguments.Take(2));
|
||||
}
|
||||
|
||||
argv0Buffer[encodedNameLength] = 0;
|
||||
var cursor = context[CpuRegister.Rsp];
|
||||
|
||||
var argv0Address = AlignDown(cursor - (ulong)argv0Buffer.Length, 16);
|
||||
if (!context.Memory.TryWrite(argv0Address, argv0Buffer))
|
||||
var argumentAddresses = new ulong[arguments.Count];
|
||||
for (var index = arguments.Count - 1; index >= 0; index--)
|
||||
{
|
||||
return false;
|
||||
var encoded = Encoding.UTF8.GetBytes(arguments[index] + '\0');
|
||||
cursor = AlignDown(cursor - (ulong)encoded.Length, 16);
|
||||
if (!context.Memory.TryWrite(cursor, encoded))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
argumentAddresses[index] = cursor;
|
||||
}
|
||||
|
||||
const ulong entryParamsSize = 0x20;
|
||||
var entryParamsAddress = AlignDown(argv0Address - entryParamsSize, 16);
|
||||
if (!context.TryWriteUInt32(entryParamsAddress + 0x00, 1) ||
|
||||
!context.TryWriteUInt32(entryParamsAddress + 0x04, 0) ||
|
||||
!context.TryWriteUInt64(entryParamsAddress + 0x08, argv0Address) ||
|
||||
!context.TryWriteUInt64(entryParamsAddress + 0x10, 0) ||
|
||||
!context.TryWriteUInt64(entryParamsAddress + 0x18, 0))
|
||||
var entryParamsAddress = AlignDown(cursor - entryParamsSize, 16);
|
||||
if (!TryWriteUInt32(context, entryParamsAddress + 0x00, (uint)arguments.Count) ||
|
||||
!TryWriteUInt32(context, entryParamsAddress + 0x04, 0) ||
|
||||
!context.TryWriteUInt64(entryParamsAddress + 0x08, argumentAddresses[0]) ||
|
||||
!context.TryWriteUInt64(
|
||||
entryParamsAddress + 0x10,
|
||||
argumentAddresses.Length > 1 ? argumentAddresses[1] : 0) ||
|
||||
!context.TryWriteUInt64(
|
||||
entryParamsAddress + 0x18,
|
||||
argumentAddresses.Length > 2 ? argumentAddresses[2] : 0))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (arguments.Count > 1)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[DISPATCHER] Guest arguments: {string.Join(' ', arguments.Skip(1))}");
|
||||
}
|
||||
|
||||
var entryStackPointer = entryParamsAddress - sizeof(ulong);
|
||||
if (!context.TryWriteUInt64(entryStackPointer, 0))
|
||||
{
|
||||
@@ -468,6 +490,13 @@ public sealed class CpuDispatcher : ICpuDispatcher, IDisposable
|
||||
return value & ~(alignment - 1);
|
||||
}
|
||||
|
||||
private static bool TryWriteUInt32(CpuContext context, ulong address, uint value)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[sizeof(uint)];
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(buffer, value);
|
||||
return context.Memory.TryWrite(address, buffer);
|
||||
}
|
||||
|
||||
private static string BuildEntryFrameDiagnostic(
|
||||
ulong entryPoint,
|
||||
CpuContext context,
|
||||
|
||||
@@ -7,8 +7,8 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using SharpEmu.Core.Cpu.Disasm;
|
||||
using SharpEmu.HLE;
|
||||
using SharpEmu.HLE.Host;
|
||||
using SharpEmu.Logging;
|
||||
|
||||
namespace SharpEmu.Core.Cpu.Native;
|
||||
@@ -17,6 +17,55 @@ public sealed partial class DirectExecutionBackend
|
||||
{
|
||||
private static readonly ConcurrentDictionary<ulong, byte> _knownExecutablePages = new();
|
||||
|
||||
private static readonly bool _perfHleHistogram =
|
||||
string.Equals(System.Environment.GetEnvironmentVariable("SHARPEMU_PERF_HLE"), "1", System.StringComparison.Ordinal);
|
||||
private static readonly System.Collections.Concurrent.ConcurrentDictionary<string, long> _perfHleCounts = new();
|
||||
private static long _perfHleTotal;
|
||||
private static long _perfHleDispatchTicks;
|
||||
|
||||
private static void RecordPerfHleDispatchTime(long ticks)
|
||||
{
|
||||
var total = System.Threading.Interlocked.Add(ref _perfHleDispatchTicks, ticks);
|
||||
var calls = System.Threading.Interlocked.Read(ref _perfHleTotal);
|
||||
if (calls > 0 && calls % 500000 == 0)
|
||||
{
|
||||
var avgUs = (double)total / System.Diagnostics.Stopwatch.Frequency * 1_000_000.0 / calls;
|
||||
System.Console.Error.WriteLine($"[PERF][HLE] managed_dispatch_avg={avgUs:F3}us total_managed_s={(double)total / System.Diagnostics.Stopwatch.Frequency:F2}");
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly bool _perfHleNoDict =
|
||||
string.Equals(System.Environment.GetEnvironmentVariable("SHARPEMU_PERF_HLE_NODICT"), "1", System.StringComparison.Ordinal);
|
||||
|
||||
private static void RecordPerfHleCall(string name)
|
||||
{
|
||||
var total = System.Threading.Interlocked.Increment(ref _perfHleTotal);
|
||||
if (!_perfHleNoDict)
|
||||
{
|
||||
_perfHleCounts.AddOrUpdate(name, 1, static (_, v) => v + 1);
|
||||
}
|
||||
|
||||
if (total % 500000 == 0 && !_perfHleNoDict)
|
||||
{
|
||||
// Snapshot via foreach (a safe moving enumerator) before sorting.
|
||||
// LINQ over a ConcurrentDictionary uses ICollection.CopyTo, which
|
||||
// throws ArgumentException if another thread adds a key between the
|
||||
// Count read and the copy — that exception was being swallowed into
|
||||
// a CPU_TRAP return and crashing the guest.
|
||||
var snapshot = new System.Collections.Generic.List<System.Collections.Generic.KeyValuePair<string, long>>(_perfHleCounts.Count + 16);
|
||||
foreach (var kvp in _perfHleCounts)
|
||||
{
|
||||
snapshot.Add(kvp);
|
||||
}
|
||||
|
||||
var top = snapshot
|
||||
.OrderByDescending(kvp => kvp.Value)
|
||||
.Take(20)
|
||||
.Select(kvp => $"{kvp.Key}={kvp.Value}");
|
||||
System.Console.Error.WriteLine($"[PERF][HLE] total={total} top: {string.Join(", ", top)}");
|
||||
}
|
||||
}
|
||||
|
||||
private void RecordRecentImportTrace(
|
||||
long dispatchIndex,
|
||||
string nid,
|
||||
@@ -25,101 +74,40 @@ public sealed partial class DirectExecutionBackend
|
||||
ulong arg1,
|
||||
ulong arg2)
|
||||
{
|
||||
_recentImportTrace[_recentImportTraceWriteIndex] = new RecentImportTraceEntry(
|
||||
var trace = _recentImportTrace;
|
||||
trace[_recentImportTraceWriteIndex] = new RecentImportTraceEntry(
|
||||
dispatchIndex,
|
||||
nid,
|
||||
returnRip,
|
||||
arg0,
|
||||
arg1,
|
||||
arg2);
|
||||
_recentImportTraceWriteIndex = (_recentImportTraceWriteIndex + 1) % _recentImportTrace.Length;
|
||||
if (_recentImportTraceCount < _recentImportTrace.Length)
|
||||
arg2,
|
||||
GuestThreadExecution.CurrentGuestThreadHandle,
|
||||
Environment.CurrentManagedThreadId);
|
||||
_recentImportTraceWriteIndex = (_recentImportTraceWriteIndex + 1) % trace.Length;
|
||||
if (_recentImportTraceCount < trace.Length)
|
||||
{
|
||||
_recentImportTraceCount++;
|
||||
}
|
||||
}
|
||||
|
||||
private void RecordDeferredBootstrapTrace(
|
||||
long dispatchIndex,
|
||||
ulong op,
|
||||
ulong symbolPointer,
|
||||
ulong outputPointer,
|
||||
ulong returnRip)
|
||||
{
|
||||
lock (_deferredBootstrapTraceGate)
|
||||
{
|
||||
_deferredBootstrapTrace[_deferredBootstrapTraceWriteIndex] = new DeferredBootstrapTraceEntry(
|
||||
dispatchIndex,
|
||||
op,
|
||||
symbolPointer,
|
||||
outputPointer,
|
||||
returnRip);
|
||||
_deferredBootstrapTraceWriteIndex =
|
||||
(_deferredBootstrapTraceWriteIndex + 1) % _deferredBootstrapTrace.Length;
|
||||
if (_deferredBootstrapTraceCount < _deferredBootstrapTrace.Length)
|
||||
{
|
||||
_deferredBootstrapTraceCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrainDeferredBootstrapTraces()
|
||||
{
|
||||
if (!_logBootstrap)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DeferredBootstrapTraceEntry[] pending;
|
||||
lock (_deferredBootstrapTraceGate)
|
||||
{
|
||||
if (_deferredBootstrapTraceCount == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
pending = new DeferredBootstrapTraceEntry[_deferredBootstrapTraceCount];
|
||||
var readIndex = (_deferredBootstrapTraceWriteIndex - _deferredBootstrapTraceCount +
|
||||
_deferredBootstrapTrace.Length) % _deferredBootstrapTrace.Length;
|
||||
for (var i = 0; i < _deferredBootstrapTraceCount; i++)
|
||||
{
|
||||
pending[i] = _deferredBootstrapTrace[(readIndex + i) % _deferredBootstrapTrace.Length];
|
||||
}
|
||||
|
||||
_deferredBootstrapTraceCount = 0;
|
||||
}
|
||||
|
||||
foreach (var entry in pending)
|
||||
{
|
||||
var symbolText = "<unreadable>";
|
||||
if (TryReadAsciiZ(entry.SymbolPointer, 256, out var sym))
|
||||
{
|
||||
symbolText = sym;
|
||||
}
|
||||
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][TRACE] bootstrap_call#{entry.DispatchIndex}: op=0x{entry.Op:X16} " +
|
||||
$"sym_ptr=0x{entry.SymbolPointer:X16} sym='{symbolText}' " +
|
||||
$"out_ptr=0x{entry.OutputPointer:X16} ret=0x{entry.ReturnRip:X16}");
|
||||
}
|
||||
}
|
||||
|
||||
private void DumpRecentImportTrace()
|
||||
{
|
||||
if (_recentImportTraceCount == 0)
|
||||
var trace = _recentImportTrace;
|
||||
if (trace is null || _recentImportTraceCount == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Log.Info($" Recent import calls ({_recentImportTraceCount}):");
|
||||
int num = (_recentImportTraceWriteIndex - _recentImportTraceCount + _recentImportTrace.Length) % _recentImportTrace.Length;
|
||||
Log.Info($" Recent import calls for managed={Environment.CurrentManagedThreadId} guest=0x{GuestThreadExecution.CurrentGuestThreadHandle:X16} ({_recentImportTraceCount}):");
|
||||
int num = (_recentImportTraceWriteIndex - _recentImportTraceCount + trace.Length) % trace.Length;
|
||||
for (int i = 0; i < _recentImportTraceCount; i++)
|
||||
{
|
||||
int num2 = (num + i) % _recentImportTrace.Length;
|
||||
var entry = _recentImportTrace[num2];
|
||||
int num2 = (num + i) % trace.Length;
|
||||
var entry = trace[num2];
|
||||
if (!string.IsNullOrEmpty(entry.Nid))
|
||||
{
|
||||
Log.Info(
|
||||
$" #{entry.DispatchIndex} nid={entry.Nid} ret=0x{entry.ReturnRip:X16} " +
|
||||
$" #{entry.DispatchIndex} managed={entry.ManagedThreadId} guest=0x{entry.GuestThreadHandle:X16} nid={entry.Nid} ret=0x{entry.ReturnRip:X16} " +
|
||||
$"rdi=0x{entry.Arg0:X16} rsi=0x{entry.Arg1:X16} rdx=0x{entry.Arg2:X16}");
|
||||
}
|
||||
}
|
||||
@@ -135,9 +123,8 @@ public sealed partial class DirectExecutionBackend
|
||||
int num2 = 0;
|
||||
List<ulong> list = new List<ulong>(16);
|
||||
ulong num3 = scanStart;
|
||||
var hostMemory = ResolveDiagnosticsHostMemory();
|
||||
HostRegionInfo lpBuffer;
|
||||
while (num3 < scanEnd && hostMemory.Query(num3, out lpBuffer))
|
||||
MEMORY_BASIC_INFORMATION64 lpBuffer;
|
||||
while (num3 < scanEnd && VirtualQuery((void*)num3, out lpBuffer, (nuint)sizeof(MEMORY_BASIC_INFORMATION64)) != 0)
|
||||
{
|
||||
ulong baseAddress = lpBuffer.BaseAddress;
|
||||
ulong num4 = baseAddress + lpBuffer.RegionSize;
|
||||
@@ -147,7 +134,7 @@ public sealed partial class DirectExecutionBackend
|
||||
}
|
||||
ulong value = Math.Max(num3, baseAddress);
|
||||
ulong num5 = Math.Min(num4, scanEnd);
|
||||
if (lpBuffer.State == HostRegionState.Committed && IsReadableProtection(lpBuffer.RawProtection) && !IsExecutableProtection(lpBuffer.RawProtection))
|
||||
if (lpBuffer.State == 4096 && IsReadableProtection(lpBuffer.Protect) && !IsExecutableProtection(lpBuffer.Protect))
|
||||
{
|
||||
ulong num6 = AlignUp(value, 8uL);
|
||||
for (ulong num7 = num6; num7 + 8 <= num5; num7 += 8)
|
||||
@@ -186,6 +173,50 @@ public sealed partial class DirectExecutionBackend
|
||||
{
|
||||
return;
|
||||
}
|
||||
const int preludeSize = 192;
|
||||
Span<byte> prelude = stackalloc byte[preludeSize];
|
||||
if (returnRip >= preludeSize && cpuContext.Memory.TryRead(returnRip - preludeSize, prelude))
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][TRACE] Import#{dispatchIndex} pre-return bytes @0x{returnRip - preludeSize:X16}: " +
|
||||
BitConverter.ToString(prelude.ToArray()).Replace("-", " "));
|
||||
|
||||
List<DecodedInst>? bestCallChain = null;
|
||||
var preludeAddress = returnRip - preludeSize;
|
||||
for (var startOffset = 0; startOffset < preludeSize; startOffset++)
|
||||
{
|
||||
var cursor = preludeAddress + (ulong)startOffset;
|
||||
var candidate = new List<DecodedInst>();
|
||||
while (cursor < returnRip && candidate.Count < 96 &&
|
||||
IcedDecoder.TryReadGuestBytes(cpuContext.Memory, cursor, 15, out var instructionBytes) &&
|
||||
IcedDecoder.TryDecode(cursor, instructionBytes, out var instruction) &&
|
||||
instruction.Length > 0 &&
|
||||
cursor + (ulong)instruction.Length <= returnRip)
|
||||
{
|
||||
candidate.Add(instruction);
|
||||
cursor += (ulong)instruction.Length;
|
||||
}
|
||||
|
||||
if (cursor == returnRip &&
|
||||
candidate.Count > 0 &&
|
||||
string.Equals(candidate[^1].Mnemonic, "Call", StringComparison.OrdinalIgnoreCase) &&
|
||||
(bestCallChain is null || candidate.Count > bestCallChain.Count))
|
||||
{
|
||||
bestCallChain = candidate;
|
||||
}
|
||||
}
|
||||
|
||||
if (bestCallChain is not null)
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] Import#{dispatchIndex} pre-return disassembly:");
|
||||
foreach (var instruction in bestCallChain.TakeLast(32))
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][TRACE] 0x{instruction.Rip:X16}: {instruction.Text} " +
|
||||
$"bytes={IcedDecoder.FormatBytes(instruction.Bytes)}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Span<byte> destination = stackalloc byte[128];
|
||||
if (!cpuContext.Memory.TryRead(returnRip, destination))
|
||||
{
|
||||
@@ -237,15 +268,14 @@ public sealed partial class DirectExecutionBackend
|
||||
ulong callRip = returnRip + (ulong)i;
|
||||
ulong target = unchecked((ulong)((long)(callRip + 5) + rel32));
|
||||
Log.Debug($"Import#{dispatchIndex} near-call @{callRip:X16}: target=0x{target:X16}");
|
||||
var importEntries = _importEntries;
|
||||
for (int importIndex = 0; importIndex < importEntries.Length; importIndex++)
|
||||
for (int importIndex = 0; importIndex < _importEntries.Length; importIndex++)
|
||||
{
|
||||
if (importEntries[importIndex].Address != target)
|
||||
if (_importEntries[importIndex].Address != target)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string nid = importEntries[importIndex].Nid;
|
||||
string nid = _importEntries[importIndex].Nid;
|
||||
if (_moduleManager.TryGetExport(nid, out var export))
|
||||
{
|
||||
Log.Debug(
|
||||
@@ -272,14 +302,14 @@ public sealed partial class DirectExecutionBackend
|
||||
{
|
||||
Log.Debug(
|
||||
$"Import#{dispatchIndex} near-call PLT slot: [0x{slot:X16}] = 0x{slotTarget:X16}");
|
||||
for (int importIndex = 0; importIndex < importEntries.Length; importIndex++)
|
||||
for (int importIndex = 0; importIndex < _importEntries.Length; importIndex++)
|
||||
{
|
||||
if (importEntries[importIndex].Address != slotTarget)
|
||||
if (_importEntries[importIndex].Address != slotTarget)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string nid = importEntries[importIndex].Nid;
|
||||
string nid = _importEntries[importIndex].Nid;
|
||||
if (_moduleManager.TryGetExport(nid, out var export))
|
||||
{
|
||||
Log.Debug(
|
||||
@@ -303,6 +333,28 @@ public sealed partial class DirectExecutionBackend
|
||||
return value == 65534 || value == 4294967294u || value == 18446744073709551614uL;
|
||||
}
|
||||
|
||||
private static ulong ParseOptionalHexAddress(string? value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var text = value.Trim();
|
||||
if (text.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
text = text[2..];
|
||||
}
|
||||
|
||||
return ulong.TryParse(
|
||||
text,
|
||||
System.Globalization.NumberStyles.HexNumber,
|
||||
System.Globalization.CultureInfo.InvariantCulture,
|
||||
out var address)
|
||||
? address
|
||||
: 0;
|
||||
}
|
||||
|
||||
private static bool IsPlausibleReturnAddress(ulong address)
|
||||
{
|
||||
return address >= 12884901888L && address < 17592186044416L && !IsUnresolvedSentinel(address);
|
||||
@@ -352,7 +404,7 @@ public sealed partial class DirectExecutionBackend
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!ResolveDiagnosticsHostMemory().Query(address, out var lpBuffer))
|
||||
if (VirtualQuery((void*)address, out var lpBuffer, (nuint)sizeof(MEMORY_BASIC_INFORMATION64)) == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -361,7 +413,7 @@ public sealed partial class DirectExecutionBackend
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (lpBuffer.State != HostRegionState.Committed || !IsReadableProtection(lpBuffer.RawProtection))
|
||||
if (lpBuffer.State != 4096 || !IsReadableProtection(lpBuffer.Protect))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -393,12 +445,12 @@ public sealed partial class DirectExecutionBackend
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!ResolveDiagnosticsHostMemory().Query(address, out var lpBuffer))
|
||||
if (VirtualQuery((void*)address, out var lpBuffer, (nuint)sizeof(MEMORY_BASIC_INFORMATION64)) == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var executable = lpBuffer.State == HostRegionState.Committed && IsExecutableProtection(lpBuffer.RawProtection);
|
||||
var executable = lpBuffer.State == 4096 && IsExecutableProtection(lpBuffer.Protect);
|
||||
if (executable)
|
||||
{
|
||||
_knownExecutablePages.TryAdd(pageAddress, 0);
|
||||
@@ -417,14 +469,6 @@ public sealed partial class DirectExecutionBackend
|
||||
return (value + num) & ~num;
|
||||
}
|
||||
|
||||
// Diagnostics helpers are static (reachable from static handler paths), so
|
||||
// they use the platform injected into the backend active on this thread and
|
||||
// fall back to the process-wide singleton only when no run is bound.
|
||||
private static IHostMemory ResolveDiagnosticsHostMemory()
|
||||
{
|
||||
return _activeExecutionBackend?._hostMemory ?? HostPlatform.Current.Memory;
|
||||
}
|
||||
|
||||
private static bool IsReadableProtection(uint protect)
|
||||
{
|
||||
if ((protect & 0x100) != 0 || (protect & 1) != 0)
|
||||
|
||||
@@ -9,9 +9,7 @@ using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using SharpEmu.Core.Cpu.Disasm;
|
||||
using SharpEmu.Core.Cpu.Native.Windows;
|
||||
using SharpEmu.HLE;
|
||||
using SharpEmu.HLE.Host;
|
||||
|
||||
namespace SharpEmu.Core.Cpu.Native;
|
||||
|
||||
@@ -19,6 +17,8 @@ public sealed partial class DirectExecutionBackend
|
||||
{
|
||||
private const ulong LazyCommitWindowBytes = 0x0200_0000UL;
|
||||
private static int _lazyCommitTraceCount;
|
||||
private static int _guestAllocatorHoleRecoveries;
|
||||
private static int _auxiliaryThreadExecuteFaultRecoveries;
|
||||
|
||||
private unsafe void SetupExceptionHandler()
|
||||
{
|
||||
@@ -30,12 +30,12 @@ public sealed partial class DirectExecutionBackend
|
||||
|
||||
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_DISABLE_RAW_HANDLER"), "1", StringComparison.Ordinal))
|
||||
{
|
||||
_rawExceptionHandlerStub = _faultHandling.CreateHandlerThunk(RawVectoredHandlerPtrManaged, _hostRspSlotTlsIndex, _tlsGetValueAddress);
|
||||
_rawExceptionHandlerStub = CreateExceptionHandlerTrampoline(RawVectoredHandlerPtrManaged);
|
||||
if (_rawExceptionHandlerStub == 0)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to create raw exception handler trampoline");
|
||||
}
|
||||
_rawExceptionHandler = _faultHandling.AddFirstChanceHandler(_rawExceptionHandlerStub);
|
||||
_rawExceptionHandler = (nint)AddVectoredExceptionHandler(1u, _rawExceptionHandlerStub);
|
||||
Console.Error.WriteLine($"[LOADER][INFO] Raw exception handler installed: 0x{_rawExceptionHandler:X16}");
|
||||
}
|
||||
else
|
||||
@@ -45,22 +45,22 @@ public sealed partial class DirectExecutionBackend
|
||||
|
||||
_handlerDelegate = VectoredHandler;
|
||||
_handlerHandle = GCHandle.Alloc(_handlerDelegate);
|
||||
_exceptionHandlerStub = _faultHandling.CreateHandlerThunk(Marshal.GetFunctionPointerForDelegate(_handlerDelegate), _hostRspSlotTlsIndex, _tlsGetValueAddress);
|
||||
_exceptionHandlerStub = CreateExceptionHandlerTrampoline(Marshal.GetFunctionPointerForDelegate(_handlerDelegate));
|
||||
if (_exceptionHandlerStub == 0)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to create exception handler trampoline");
|
||||
}
|
||||
_exceptionHandler = _faultHandling.AddFirstChanceHandler(_exceptionHandlerStub);
|
||||
_exceptionHandler = (nint)AddVectoredExceptionHandler(1u, _exceptionHandlerStub);
|
||||
Console.Error.WriteLine($"[LOADER][INFO] Exception handler installed: 0x{_exceptionHandler:X16}");
|
||||
|
||||
_unhandledFilterDelegate = UnhandledExceptionFilter;
|
||||
_unhandledFilterHandle = GCHandle.Alloc(_unhandledFilterDelegate);
|
||||
_unhandledFilterStub = _faultHandling.CreateHandlerThunk(Marshal.GetFunctionPointerForDelegate(_unhandledFilterDelegate), _hostRspSlotTlsIndex, _tlsGetValueAddress);
|
||||
_unhandledFilterStub = CreateExceptionHandlerTrampoline(Marshal.GetFunctionPointerForDelegate(_unhandledFilterDelegate));
|
||||
if (_unhandledFilterStub == 0)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to create unhandled exception filter trampoline");
|
||||
}
|
||||
_faultHandling.SetUnhandledFilter(_unhandledFilterStub);
|
||||
SetUnhandledExceptionFilter(_unhandledFilterStub);
|
||||
}
|
||||
|
||||
private unsafe int UnhandledExceptionFilter(void* exceptionInfo)
|
||||
@@ -68,8 +68,8 @@ public sealed partial class DirectExecutionBackend
|
||||
try
|
||||
{
|
||||
EXCEPTION_RECORD* exceptionRecord = ((EXCEPTION_POINTERS*)exceptionInfo)->ExceptionRecord;
|
||||
ulong rip = ReadCtxU64(((EXCEPTION_POINTERS*)exceptionInfo)->ContextRecord, CTX_RIP);
|
||||
ulong rsp = ReadCtxU64(((EXCEPTION_POINTERS*)exceptionInfo)->ContextRecord, CTX_RSP);
|
||||
ulong rip = ReadCtxU64(((EXCEPTION_POINTERS*)exceptionInfo)->ContextRecord, 248);
|
||||
ulong rsp = ReadCtxU64(((EXCEPTION_POINTERS*)exceptionInfo)->ContextRecord, 152);
|
||||
Console.Error.WriteLine("[LOADER][FATAL] Unhandled exception filter fired.");
|
||||
Console.Error.WriteLine($"[LOADER][FATAL] Code: 0x{exceptionRecord->ExceptionCode:X8}");
|
||||
Console.Error.WriteLine($"[LOADER][FATAL] Exception Address: 0x{(ulong)(nint)exceptionRecord->ExceptionAddress:X16}");
|
||||
@@ -108,19 +108,23 @@ public sealed partial class DirectExecutionBackend
|
||||
return 0;
|
||||
}
|
||||
|
||||
ulong rip = ReadCtxU64(contextRecord, CTX_RIP);
|
||||
ulong rsp = ReadCtxU64(contextRecord, CTX_RSP);
|
||||
|
||||
// Thread-mode probe: a hardware exception raised while this thread is inside
|
||||
// the managed import gateway means the VEH->managed reentry happened from
|
||||
// cooperative GC mode — a ReversePInvokeBadTransition candidate.
|
||||
if (LogThreadMode && _threadModeGatewayDepth > 0)
|
||||
ulong rip = ReadCtxU64(contextRecord, 248);
|
||||
ulong rsp = ReadCtxU64(contextRecord, 152);
|
||||
if (TryRecoverGuestInt41(exceptionCode, contextRecord, rip))
|
||||
{
|
||||
TraceThreadMode(
|
||||
$"veh_in_gateway code=0x{exceptionCode:X8} rip=0x{rip:X16} gateway_depth={_threadModeGatewayDepth}");
|
||||
return -1;
|
||||
}
|
||||
if (TryRecoverAuxiliaryThreadExecuteFault(exceptionRecord, contextRecord, rip))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (exceptionCode == WindowsFaultCodes.AccessViolation && TryHandleLazyCommittedPage(exceptionRecord, rip, rsp))
|
||||
if (exceptionCode == 3221225477u && TryHandleLazyCommittedPage(exceptionRecord, rip, rsp))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
if (exceptionCode == 3221225477u &&
|
||||
TryRecoverGuestAllocatorHole(exceptionRecord, contextRecord, rip))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
@@ -135,10 +139,10 @@ public sealed partial class DirectExecutionBackend
|
||||
|
||||
switch (exceptionCode)
|
||||
{
|
||||
case WindowsFaultCodes.AccessViolation:
|
||||
case 3221225477u:
|
||||
LogAccessViolationTrace(exceptionAddress, exceptionRecord);
|
||||
break;
|
||||
case WindowsFaultCodes.FastFail:
|
||||
case 3221226505u:
|
||||
{
|
||||
ulong p0 = exceptionRecord->NumberParameters >= 1 ? (*exceptionRecord->ExceptionInformation) : 0;
|
||||
ulong p1 = exceptionRecord->NumberParameters >= 2 ? exceptionRecord->ExceptionInformation[1] : 0;
|
||||
@@ -148,27 +152,56 @@ public sealed partial class DirectExecutionBackend
|
||||
}
|
||||
}
|
||||
|
||||
ulong rax = ReadCtxU64(contextRecord, CTX_RAX);
|
||||
ulong rbx = ReadCtxU64(contextRecord, CTX_RBX);
|
||||
ulong rcx = ReadCtxU64(contextRecord, CTX_RCX);
|
||||
ulong rdx = ReadCtxU64(contextRecord, CTX_RDX);
|
||||
ulong rsi = ReadCtxU64(contextRecord, CTX_RSI);
|
||||
ulong rdi = ReadCtxU64(contextRecord, CTX_RDI);
|
||||
ulong rbp = ReadCtxU64(contextRecord, CTX_RBP);
|
||||
ulong r8 = ReadCtxU64(contextRecord, CTX_R8);
|
||||
ulong r9 = ReadCtxU64(contextRecord, CTX_R9);
|
||||
ulong r10 = ReadCtxU64(contextRecord, CTX_R10);
|
||||
ulong r11 = ReadCtxU64(contextRecord, CTX_R11);
|
||||
ulong r12 = ReadCtxU64(contextRecord, CTX_R12);
|
||||
ulong r13 = ReadCtxU64(contextRecord, CTX_R13);
|
||||
ulong r14 = ReadCtxU64(contextRecord, CTX_R14);
|
||||
ulong r15 = ReadCtxU64(contextRecord, CTX_R15);
|
||||
ulong rax = ReadCtxU64(contextRecord, 120);
|
||||
ulong rbx = ReadCtxU64(contextRecord, 144);
|
||||
ulong rcx = ReadCtxU64(contextRecord, 128);
|
||||
ulong rdx = ReadCtxU64(contextRecord, 136);
|
||||
ulong rsi = ReadCtxU64(contextRecord, 168);
|
||||
ulong rdi = ReadCtxU64(contextRecord, 176);
|
||||
ulong rbp = ReadCtxU64(contextRecord, 160);
|
||||
ulong r8 = ReadCtxU64(contextRecord, 184);
|
||||
ulong r9 = ReadCtxU64(contextRecord, 192);
|
||||
ulong r10 = ReadCtxU64(contextRecord, 200);
|
||||
ulong r11 = ReadCtxU64(contextRecord, 208);
|
||||
ulong r12 = ReadCtxU64(contextRecord, 216);
|
||||
ulong r13 = ReadCtxU64(contextRecord, 224);
|
||||
ulong r14 = ReadCtxU64(contextRecord, 232);
|
||||
ulong r15 = ReadCtxU64(contextRecord, 240);
|
||||
|
||||
Console.Error.WriteLine("[LOADER][INFO] =========================================");
|
||||
Console.Error.WriteLine("[LOADER][INFO] NATIVE EXCEPTION CAUGHT!");
|
||||
Console.Error.WriteLine($"[LOADER][INFO] Code: 0x{exceptionCode:X8}");
|
||||
Console.Error.WriteLine($"[LOADER][INFO] Exception Address: 0x{exceptionAddress:X16}");
|
||||
Console.Error.WriteLine($"[LOADER][INFO] RIP: 0x{rip:X16}");
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][INFO] Host thread: managed={Environment.CurrentManagedThreadId} " +
|
||||
$"name='{Thread.CurrentThread.Name ?? "<unnamed>"}'");
|
||||
if (_activeGuestThreadState is { } activeGuestThread)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][INFO] Guest thread: handle=0x{activeGuestThread.ThreadHandle:X16} " +
|
||||
$"name='{activeGuestThread.Name}' state={activeGuestThread.State} " +
|
||||
$"last_import={activeGuestThread.LastImportNid ?? "<none>"} " +
|
||||
$"last_ret=0x{activeGuestThread.LastReturnRip:X16}");
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][INFO] Last import registers: " +
|
||||
$"rax=0x{Volatile.Read(ref activeGuestThread.LastImportRax):X16} " +
|
||||
$"result_valid={Volatile.Read(ref activeGuestThread.LastImportResultValid) != 0} " +
|
||||
$"rdi=0x{activeGuestThread.LastImportRdi:X16} " +
|
||||
$"rsi=0x{activeGuestThread.LastImportRsi:X16} " +
|
||||
$"rdx=0x{activeGuestThread.LastImportRdx:X16} " +
|
||||
$"rcx=0x{activeGuestThread.LastImportRcx:X16} " +
|
||||
$"r8=0x{activeGuestThread.LastImportR8:X16} " +
|
||||
$"r9=0x{activeGuestThread.LastImportR9:X16}");
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][INFO] Last import stack args: " +
|
||||
$"0=0x{activeGuestThread.LastImportStack0:X16} " +
|
||||
$"1=0x{activeGuestThread.LastImportStack1:X16} " +
|
||||
$"2=0x{activeGuestThread.LastImportStack2:X16} " +
|
||||
$"3=0x{activeGuestThread.LastImportStack3:X16} " +
|
||||
$"4=0x{activeGuestThread.LastImportStack4:X16} " +
|
||||
$"5=0x{activeGuestThread.LastImportStack5:X16}");
|
||||
}
|
||||
if (TryFormatNearestRuntimeSymbol(rip, out string symbol))
|
||||
{
|
||||
Console.Error.WriteLine("[LOADER][INFO] RIP symbol: " + symbol);
|
||||
@@ -193,7 +226,7 @@ public sealed partial class DirectExecutionBackend
|
||||
|
||||
ulong accessType = 0;
|
||||
ulong target = 0;
|
||||
if (exceptionCode == WindowsFaultCodes.AccessViolation && exceptionRecord->NumberParameters >= 2)
|
||||
if (exceptionCode == 3221225477u && exceptionRecord->NumberParameters >= 2)
|
||||
{
|
||||
accessType = *exceptionRecord->ExceptionInformation;
|
||||
target = exceptionRecord->ExceptionInformation[1];
|
||||
@@ -206,9 +239,9 @@ public sealed partial class DirectExecutionBackend
|
||||
};
|
||||
Console.Error.WriteLine("[LOADER][INFO] AV access: " + accessText);
|
||||
Console.Error.WriteLine($"[LOADER][INFO] AV target: 0x{target:X16}");
|
||||
if (_hostMemory.Query(target, out var mbi))
|
||||
if (VirtualQuery((void*)target, out var mbi, (nuint)sizeof(MEMORY_BASIC_INFORMATION64)) != 0)
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][INFO] AV target region: base=0x{mbi.BaseAddress:X16} size=0x{mbi.RegionSize:X16} state=0x{mbi.RawState:X08} protect=0x{mbi.RawProtection:X08}");
|
||||
Console.Error.WriteLine($"[LOADER][INFO] AV target region: base=0x{mbi.BaseAddress:X16} size=0x{mbi.RegionSize:X16} state=0x{mbi.State:X08} protect=0x{mbi.Protect:X08}");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -225,13 +258,46 @@ public sealed partial class DirectExecutionBackend
|
||||
Console.Error.WriteLine($"[LOADER][INFO] [rsp+0x{i * 8:X2}] @0x{stackAddr:X16} = 0x{value:X16}");
|
||||
}
|
||||
|
||||
if (string.Equals(
|
||||
Environment.GetEnvironmentVariable("SHARPEMU_DUMP_FAULT_STACK_WINDOW"),
|
||||
"1",
|
||||
StringComparison.Ordinal))
|
||||
{
|
||||
Console.Error.WriteLine("[LOADER][INFO] Full fault stack window (RSP-0x300..RSP+0x100):");
|
||||
var windowStart = rsp >= 0x300 ? rsp - 0x300 : 0;
|
||||
for (var stackAddr = windowStart; stackAddr < rsp + 0x100; stackAddr += 8)
|
||||
{
|
||||
if (!TryReadHostQword(stackAddr, out var value))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var relative = unchecked((long)(stackAddr - rsp));
|
||||
var relativeText = relative >= 0
|
||||
? $"+0x{relative:X}"
|
||||
: $"-0x{-relative:X}";
|
||||
var symbolText = TryFormatNearestRuntimeSymbol(value, out var stackSymbol)
|
||||
? $" [{stackSymbol}]"
|
||||
: string.Empty;
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][INFO] [rsp{relativeText}] " +
|
||||
$"@0x{stackAddr:X16} = 0x{value:X16}{symbolText}");
|
||||
}
|
||||
}
|
||||
|
||||
DumpPointerWindow("fault-register-rbx", rbx, 0x60);
|
||||
DumpPointerWindow("fault-register-rsi", rsi, 0x60);
|
||||
DumpPointerWindow("fault-register-rdi", rdi, 0x60);
|
||||
DumpPointerWindow("fault-register-r13", r13, 0x60);
|
||||
DumpPointerWindow("fault-register-r14", r14, 0x60);
|
||||
|
||||
try
|
||||
{
|
||||
Console.Error.WriteLine("[LOADER][INFO] Frame chain (RBP walk):");
|
||||
ulong frame = rbp;
|
||||
for (int i = 0; i < 12; i++)
|
||||
{
|
||||
if (frame < 140733193388032L || frame > 140737488355327L)
|
||||
if (frame < 0x10000)
|
||||
{
|
||||
break;
|
||||
}
|
||||
@@ -256,7 +322,7 @@ public sealed partial class DirectExecutionBackend
|
||||
|
||||
switch (exceptionCode)
|
||||
{
|
||||
case WindowsFaultCodes.AccessViolation:
|
||||
case 3221225477u:
|
||||
Console.Error.WriteLine("[LOADER][ERROR] Type: Access Violation");
|
||||
Console.Error.WriteLine("[LOADER][ERROR] This usually means:");
|
||||
Console.Error.WriteLine("[LOADER][ERROR] - Guest code called an unmapped import");
|
||||
@@ -290,22 +356,57 @@ public sealed partial class DirectExecutionBackend
|
||||
{
|
||||
Console.Error.WriteLine("[LOADER][INFO] Code window [RIP-0x20..]: " + BitConverter.ToString(window).Replace("-", " "));
|
||||
}
|
||||
for (var stackIndex = 0; stackIndex < 16; stackIndex++)
|
||||
{
|
||||
byte[] stackSlot = new byte[8];
|
||||
if (!TryReadHostBytes(rsp + (ulong)(stackIndex * 8), stackSlot))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var candidate = BitConverter.ToUInt64(stackSlot);
|
||||
if (candidate < _entryPoint || candidate >= _entryPoint + 0x10000000 || candidate < 24)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
byte[] callSiteWindow = new byte[48];
|
||||
if (TryReadHostBytes(candidate - 24, callSiteWindow))
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][INFO] Stack guest-code candidate [rsp+0x{stackIndex * 8:X2}]=0x{candidate:X16}, bytes [-0x18..]: " +
|
||||
BitConverter.ToString(callSiteWindow).Replace("-", " "));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.Error.WriteLine("[LOADER][ERROR] Could not read code at RIP");
|
||||
}
|
||||
DumpRecentImportTrace();
|
||||
DumpGuestDisasmDiagnostics(rip, rbp);
|
||||
DumpGuestDisasmDiagnostics(rip, rbp, rsp);
|
||||
DumpGuestRegisterWindowDiagnostics(
|
||||
rax, rbx, rcx, rdx, rsi, rdi, rbp, rsp,
|
||||
r8, r9, r10, r11, r12, r13, r14, r15);
|
||||
DumpGuestReferenceDiagnostics();
|
||||
DumpGuestPointerWindowDiagnostics();
|
||||
break;
|
||||
case WindowsFaultCodes.Breakpoint:
|
||||
case 2147483651u:
|
||||
Console.Error.WriteLine("[LOADER][WARNING] Type: Breakpoint (int3)");
|
||||
Console.Error.WriteLine("[LOADER][WARNING] Unexpected breakpoint in direct-bridge mode");
|
||||
break;
|
||||
case WindowsFaultCodes.IllegalInstruction:
|
||||
case 1073741845u:
|
||||
Console.Error.WriteLine("[LOADER][ERROR] Type: Abort (SIGABRT)");
|
||||
DumpRecentImportTrace();
|
||||
DumpGuestDisasmDiagnostics(rip, rbp, rsp);
|
||||
break;
|
||||
case 3221225501u:
|
||||
Console.Error.WriteLine("[LOADER][INFO] Type: Illegal Instruction");
|
||||
byte[] illegalCode = new byte[16];
|
||||
if (TryReadHostBytes(rip, illegalCode))
|
||||
{
|
||||
Console.Error.WriteLine("[LOADER][INFO] Code at RIP: " + BitConverter.ToString(illegalCode).Replace("-", " "));
|
||||
}
|
||||
DumpRecentImportTrace();
|
||||
DumpGuestDisasmDiagnostics(rip, rbp, rsp);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -319,6 +420,108 @@ public sealed partial class DirectExecutionBackend
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe bool TryRecoverAuxiliaryThreadExecuteFault(
|
||||
EXCEPTION_RECORD* exceptionRecord,
|
||||
void* contextRecord,
|
||||
ulong rip)
|
||||
{
|
||||
if (exceptionRecord->ExceptionCode != 3221225477u ||
|
||||
rip >= 0x0000000800000000UL ||
|
||||
_activeGuestThreadState is not { Name: "tbb_thead" } activeThread)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var hostExit = ActiveEntryReturnSentinelRip;
|
||||
if (hostExit < 0x10000)
|
||||
{
|
||||
hostExit = unchecked((ulong)_guestReturnStub);
|
||||
}
|
||||
if (hostExit < 0x10000)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][WARN] Could not recover auxiliary TBB execute fault: target=0x{rip:X16} " +
|
||||
$"active_exit=0x{ActiveEntryReturnSentinelRip:X16} guest_return_stub=0x{unchecked((ulong)_guestReturnStub):X16}");
|
||||
return false;
|
||||
}
|
||||
|
||||
_ = TryPatchActiveGuestReturnSlot(hostExit);
|
||||
WriteCtxU64(contextRecord, 120, 0);
|
||||
WriteCtxU64(contextRecord, 248, hostExit);
|
||||
var recovery = Interlocked.Increment(ref _auxiliaryThreadExecuteFaultRecoveries);
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][WARN] Recovered auxiliary TBB execute fault #{recovery}: " +
|
||||
$"thread=0x{activeThread.ThreadHandle:X16} target=0x{rip:X16} -> host_exit=0x{hostExit:X16}");
|
||||
return true;
|
||||
}
|
||||
|
||||
private unsafe bool TryRecoverGuestInt41(uint exceptionCode, void* contextRecord, ulong rip)
|
||||
{
|
||||
if (!_ignoreGuestInt41 || exceptionCode != 3221225477u || rip < 0x10000)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
byte[] opcode = new byte[2];
|
||||
if (!TryReadHostBytes(rip, opcode) || opcode[0] != 0xCD || opcode[1] != 0x41)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var count = Interlocked.Increment(ref _ignoredGuestInt41Count);
|
||||
WriteCtxU64(contextRecord, 248, rip + 2);
|
||||
if (count <= 16 || count % 65536 == 0)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][WARN] Ignored guest int 0x41 trap #{count} at 0x{rip:X16} (SHARPEMU_IGNORE_INT41=1)");
|
||||
Console.Error.Flush();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private unsafe static bool TryRecoverGuestAllocatorHole(
|
||||
EXCEPTION_RECORD* exceptionRecord,
|
||||
void* contextRecord,
|
||||
ulong rip)
|
||||
{
|
||||
if (string.Equals(
|
||||
Environment.GetEnvironmentVariable("SHARPEMU_DISABLE_GUEST_ALLOCATOR_HOLE_RECOVERY"),
|
||||
"1",
|
||||
StringComparison.Ordinal) ||
|
||||
exceptionRecord->NumberParameters < 2 ||
|
||||
exceptionRecord->ExceptionInformation[0] != 0 ||
|
||||
exceptionRecord->ExceptionInformation[1] != 8 ||
|
||||
ReadCtxU64(contextRecord, CTX_RDI) != 0 ||
|
||||
rip < 0x10000)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Demon's Souls occasionally leaves an empty payload in a locked pool
|
||||
// tree node. The allocator dereferences payload+8 before reaching its
|
||||
// existing empty-pool fallback. Match the instruction stream instead of
|
||||
// a title-specific absolute address, then resume at that fallback so the
|
||||
// lock is released and the allocator can try its next backing pool.
|
||||
const ulong allocatorHoleSignature = 0x634CFF568D08778BUL;
|
||||
if (*(ulong*)rip != allocatorHoleSignature || *((byte*)rip + 8) != 0xF2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const ulong emptyPoolFallbackDelta = 0x8E;
|
||||
WriteCtxU64(contextRecord, CTX_RIP, rip + emptyPoolFallbackDelta);
|
||||
var recovery = Interlocked.Increment(ref _guestAllocatorHoleRecoveries);
|
||||
if (recovery <= 16 || (recovery & (recovery - 1)) == 0)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][WARN] Guest allocator empty-node adapter recovery #{recovery}: " +
|
||||
$"rip=0x{rip:X16} -> 0x{rip + emptyPoolFallbackDelta:X16}");
|
||||
Console.Error.Flush();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool IsBenignHostDebugException(uint exceptionCode)
|
||||
{
|
||||
return exceptionCode is DBG_PRINTEXCEPTION_C or DBG_PRINTEXCEPTION_WIDE_C or MS_VC_THREADNAME_EXCEPTION;
|
||||
@@ -337,8 +540,8 @@ public sealed partial class DirectExecutionBackend
|
||||
EXCEPTION_POINTERS* pointers = (EXCEPTION_POINTERS*)exceptionInfo;
|
||||
EXCEPTION_RECORD* record = pointers->ExceptionRecord;
|
||||
void* contextRecord = pointers->ContextRecord;
|
||||
ulong rip = contextRecord != null ? ReadCtxU64(contextRecord, CTX_RIP) : 0;
|
||||
ulong rsp = contextRecord != null ? ReadCtxU64(contextRecord, CTX_RSP) : 0;
|
||||
ulong rip = contextRecord != null ? ReadCtxU64(contextRecord, 248) : 0;
|
||||
ulong rsp = contextRecord != null ? ReadCtxU64(contextRecord, 152) : 0;
|
||||
ulong accessType = record->NumberParameters >= 1 ? *record->ExceptionInformation : 0;
|
||||
ulong target = record->NumberParameters >= 2 ? record->ExceptionInformation[1] : 0;
|
||||
Console.Error.WriteLine(
|
||||
@@ -398,7 +601,7 @@ public sealed partial class DirectExecutionBackend
|
||||
}
|
||||
}
|
||||
|
||||
private void DumpGuestDisasmDiagnostics(ulong rip, ulong rbp)
|
||||
private void DumpGuestDisasmDiagnostics(ulong rip, ulong rbp, ulong rsp)
|
||||
{
|
||||
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_DISASM"), "1", StringComparison.Ordinal))
|
||||
{
|
||||
@@ -410,12 +613,20 @@ public sealed partial class DirectExecutionBackend
|
||||
DumpGuestInstructionStream("fault-prelude", rip - 0x20, 24);
|
||||
}
|
||||
|
||||
// Optimized guest code frequently omits frame pointers. The return
|
||||
// address at RSP is then more useful than an RBP walk and identifies the
|
||||
// exact call site that supplied the faulting arguments.
|
||||
if (TryReadHostQword(rsp, out var stackReturn) && stackReturn >= 0x60)
|
||||
{
|
||||
DumpGuestInstructionStream("stack-return-prelude", stackReturn - 0x60, 40);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ulong frame = rbp;
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
if (frame < 140733193388032L || frame > 140737488355327L)
|
||||
if (frame < 0x10000)
|
||||
{
|
||||
break;
|
||||
}
|
||||
@@ -484,7 +695,7 @@ public sealed partial class DirectExecutionBackend
|
||||
ulong address = scanBase;
|
||||
while (address < scanEnd)
|
||||
{
|
||||
if (!_hostMemory.Query(address, out var mbi))
|
||||
if (VirtualQuery((void*)address, out var mbi, (nuint)sizeof(MEMORY_BASIC_INFORMATION64)) == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
@@ -496,9 +707,9 @@ public sealed partial class DirectExecutionBackend
|
||||
break;
|
||||
}
|
||||
|
||||
if (mbi.State == HostRegionState.Committed &&
|
||||
IsReadableProtection(mbi.RawProtection) &&
|
||||
IsExecutableProtection(mbi.RawProtection))
|
||||
if (mbi.State == MEM_COMMIT &&
|
||||
IsReadableProtection(mbi.Protect) &&
|
||||
IsExecutableProtection(mbi.Protect))
|
||||
{
|
||||
ScanExecutableRegionForTargetReferences(regionBase, regionEnd, targetList, hitCounts, maxHitsPerTarget);
|
||||
}
|
||||
@@ -559,6 +770,55 @@ public sealed partial class DirectExecutionBackend
|
||||
}
|
||||
}
|
||||
|
||||
private void DumpGuestRegisterWindowDiagnostics(
|
||||
ulong rax,
|
||||
ulong rbx,
|
||||
ulong rcx,
|
||||
ulong rdx,
|
||||
ulong rsi,
|
||||
ulong rdi,
|
||||
ulong rbp,
|
||||
ulong rsp,
|
||||
ulong r8,
|
||||
ulong r9,
|
||||
ulong r10,
|
||||
ulong r11,
|
||||
ulong r12,
|
||||
ulong r13,
|
||||
ulong r14,
|
||||
ulong r15)
|
||||
{
|
||||
if (!string.Equals(
|
||||
Environment.GetEnvironmentVariable("SHARPEMU_LOG_REGISTER_WINDOWS"),
|
||||
"1",
|
||||
StringComparison.Ordinal))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// A register can be the only surviving reference to the object or
|
||||
// argument array that caused a native guest fault. Capture a compact
|
||||
// window while the process is alive so the post-mortem log can
|
||||
// distinguish an absent object from a partially initialized one.
|
||||
var registers = new (string Name, ulong Value)[]
|
||||
{
|
||||
("rax", rax), ("rbx", rbx), ("rcx", rcx), ("rdx", rdx),
|
||||
("rsi", rsi), ("rdi", rdi), ("rbp", rbp), ("rsp", rsp),
|
||||
("r8", r8), ("r9", r9), ("r10", r10), ("r11", r11),
|
||||
("r12", r12), ("r13", r13), ("r14", r14), ("r15", r15),
|
||||
};
|
||||
var seen = new HashSet<ulong>();
|
||||
foreach (var (name, value) in registers)
|
||||
{
|
||||
if (value < 0x10000 || !seen.Add(value))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
DumpPointerWindow($"register-{name}", value, 0x80);
|
||||
}
|
||||
}
|
||||
|
||||
private void ScanExecutableRegionForTargetReferences(
|
||||
ulong regionBase,
|
||||
ulong regionEnd,
|
||||
@@ -803,13 +1063,13 @@ public sealed partial class DirectExecutionBackend
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_hostMemory.Query(address, out var mbi))
|
||||
if (VirtualQuery((void*)address, out var mbi, (nuint)sizeof(MEMORY_BASIC_INFORMATION64)) == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ulong regionEnd = mbi.BaseAddress + mbi.RegionSize;
|
||||
if (mbi.State != HostRegionState.Committed || !IsReadableProtection(mbi.RawProtection) || regionEnd <= address || address > regionEnd - 8)
|
||||
if (mbi.State != MEM_COMMIT || !IsReadableProtection(mbi.Protect) || regionEnd <= address || address > regionEnd - 8)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -848,7 +1108,7 @@ public sealed partial class DirectExecutionBackend
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe bool TryReadHostBytes(ulong address, byte[] buffer)
|
||||
private unsafe static bool TryReadHostBytes(ulong address, byte[] buffer)
|
||||
{
|
||||
if (address < 65536)
|
||||
{
|
||||
@@ -861,9 +1121,9 @@ public sealed partial class DirectExecutionBackend
|
||||
ulong end = address + (ulong)buffer.Length;
|
||||
for (ulong page = address & 0xFFFFFFFFFFFFF000uL; page < end; page += 4096)
|
||||
{
|
||||
if (!_hostMemory.Query(page, out var mbi) ||
|
||||
mbi.State != HostRegionState.Committed ||
|
||||
!IsReadableProtection(mbi.RawProtection))
|
||||
if (VirtualQuery((void*)page, out var mbi, (nuint)sizeof(MEMORY_BASIC_INFORMATION64)) == 0 ||
|
||||
mbi.State != MEM_COMMIT ||
|
||||
!IsReadableProtection(mbi.Protect))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -976,25 +1236,25 @@ public sealed partial class DirectExecutionBackend
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!_hostMemory.Query(faultAddress, out var mbi))
|
||||
if (VirtualQuery((void*)faultAddress, out var mbi, (nuint)sizeof(MEMORY_BASIC_INFORMATION64)) == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ulong pageBase = faultAddress & 0xFFFFFFFFFFFFF000uL;
|
||||
uint commitProtect = ResolveLazyCommitProtection(accessType, mbi.RawAllocationProtection);
|
||||
uint commitProtect = ResolveLazyCommitProtection(accessType, mbi.AllocationProtect);
|
||||
int traceIndex = Interlocked.Increment(ref _lazyCommitTraceCount);
|
||||
bool traceLazyCommit = ShouldTraceLazyCommit(traceIndex);
|
||||
if (traceLazyCommit)
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] lazy-query#{traceIndex}: fault=0x{faultAddress:X16} owner={owner} rip=0x{rip:X16} rsp=0x{rsp:X16} state=0x{mbi.RawState:X08} base=0x{mbi.BaseAddress:X16} size=0x{mbi.RegionSize:X16} alloc=0x{mbi.RawAllocationProtection:X08} prot=0x{mbi.RawProtection:X08}");
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] lazy-query#{traceIndex}: fault=0x{faultAddress:X16} owner={owner} rip=0x{rip:X16} rsp=0x{rsp:X16} state=0x{mbi.State:X08} base=0x{mbi.BaseAddress:X16} size=0x{mbi.RegionSize:X16} alloc=0x{mbi.AllocationProtect:X08} prot=0x{mbi.Protect:X08}");
|
||||
}
|
||||
|
||||
if (mbi.State == HostRegionState.Committed && IsAccessCompatible(accessType, mbi.RawProtection))
|
||||
if (mbi.State == 4096 && IsAccessCompatible(accessType, mbi.Protect))
|
||||
{
|
||||
if (traceLazyCommit)
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] lazy-commit-race#{traceIndex}: fault=0x{faultAddress:X16} protect=0x{mbi.RawProtection:X08}");
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] lazy-commit-race#{traceIndex}: fault=0x{faultAddress:X16} protect=0x{mbi.Protect:X08}");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -1003,10 +1263,10 @@ public sealed partial class DirectExecutionBackend
|
||||
ulong committedBase = 0;
|
||||
ulong committedSize = 0;
|
||||
|
||||
if (mbi.State == HostRegionState.Free)
|
||||
if (mbi.State == 65536)
|
||||
{
|
||||
if (TryGetLazyCommitWindow(faultAddress, mbi.BaseAddress, mbi.RegionSize, out var windowBase, out var windowSize) &&
|
||||
TryReserveThenCommit(_hostMemory, windowBase, windowSize, windowBase, windowSize, commitProtect))
|
||||
TryReserveThenCommit(windowBase, windowSize, windowBase, windowSize, commitProtect))
|
||||
{
|
||||
committed = true;
|
||||
committedBase = windowBase;
|
||||
@@ -1015,7 +1275,7 @@ public sealed partial class DirectExecutionBackend
|
||||
else
|
||||
{
|
||||
ulong largeBase = faultAddress & 0xFFFFFFFFFFE00000uL;
|
||||
if (TryReserveThenCommit(_hostMemory, largeBase, 2097152uL, largeBase, 2097152uL, commitProtect))
|
||||
if (TryReserveThenCommit(largeBase, 2097152uL, largeBase, 2097152uL, commitProtect))
|
||||
{
|
||||
committed = true;
|
||||
committedBase = largeBase;
|
||||
@@ -1026,13 +1286,13 @@ public sealed partial class DirectExecutionBackend
|
||||
if (!committed)
|
||||
{
|
||||
ulong region64kBase = faultAddress & 0xFFFFFFFFFFFF0000uL;
|
||||
if (TryReserveThenCommit(_hostMemory, region64kBase, 65536uL, region64kBase, 65536uL, commitProtect))
|
||||
if (TryReserveThenCommit(region64kBase, 65536uL, region64kBase, 65536uL, commitProtect))
|
||||
{
|
||||
committed = true;
|
||||
committedBase = region64kBase;
|
||||
committedSize = 65536uL;
|
||||
}
|
||||
else if (TryReserveThenCommit(_hostMemory, pageBase, 4096uL, pageBase, 4096uL, commitProtect))
|
||||
else if (TryReserveThenCommit(pageBase, 4096uL, pageBase, 4096uL, commitProtect))
|
||||
{
|
||||
committed = true;
|
||||
committedBase = pageBase;
|
||||
@@ -1045,7 +1305,7 @@ public sealed partial class DirectExecutionBackend
|
||||
return false;
|
||||
}
|
||||
|
||||
TryCommitRange(_hostMemory, pageBase + 4096, 4096uL, commitProtect);
|
||||
TryCommitRange(pageBase + 4096, 4096uL, commitProtect);
|
||||
if (traceLazyCommit)
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] lazy-reserve-commit#{traceIndex}: addr=0x{committedBase:X16} size=0x{committedSize:X16} access={accessType} protect=0x{commitProtect:X8}");
|
||||
@@ -1053,13 +1313,13 @@ public sealed partial class DirectExecutionBackend
|
||||
return true;
|
||||
}
|
||||
|
||||
if (mbi.State != HostRegionState.Reserved)
|
||||
if (mbi.State != 8192)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (TryGetLazyCommitWindow(faultAddress, mbi.BaseAddress, mbi.RegionSize, out var commitWindowBase, out var commitWindowSize) &&
|
||||
TryCommitRange(_hostMemory, commitWindowBase, commitWindowSize, commitProtect))
|
||||
TryCommitRange(commitWindowBase, commitWindowSize, commitProtect))
|
||||
{
|
||||
committed = true;
|
||||
committedBase = commitWindowBase;
|
||||
@@ -1068,7 +1328,7 @@ public sealed partial class DirectExecutionBackend
|
||||
else
|
||||
{
|
||||
ulong largeCommitBase = faultAddress & 0xFFFFFFFFFFE00000uL;
|
||||
if (TryCommitRange(_hostMemory, largeCommitBase, 2097152uL, commitProtect))
|
||||
if (TryCommitRange(largeCommitBase, 2097152uL, commitProtect))
|
||||
{
|
||||
committed = true;
|
||||
committedBase = largeCommitBase;
|
||||
@@ -1079,19 +1339,19 @@ public sealed partial class DirectExecutionBackend
|
||||
if (!committed)
|
||||
{
|
||||
ulong region64kBase = faultAddress & 0xFFFFFFFFFFFF0000uL;
|
||||
if (TryCommitRange(_hostMemory, region64kBase, 65536uL, commitProtect))
|
||||
if (TryCommitRange(region64kBase, 65536uL, commitProtect))
|
||||
{
|
||||
committed = true;
|
||||
committedBase = region64kBase;
|
||||
committedSize = 65536uL;
|
||||
}
|
||||
else if (TryCommitRange(_hostMemory, pageBase, 8192uL, commitProtect))
|
||||
else if (TryCommitRange(pageBase, 8192uL, commitProtect))
|
||||
{
|
||||
committed = true;
|
||||
committedBase = pageBase;
|
||||
committedSize = 8192uL;
|
||||
}
|
||||
else if (TryCommitRange(_hostMemory, pageBase, 4096uL, commitProtect))
|
||||
else if (TryCommitRange(pageBase, 4096uL, commitProtect))
|
||||
{
|
||||
committed = true;
|
||||
committedBase = pageBase;
|
||||
@@ -1104,7 +1364,7 @@ public sealed partial class DirectExecutionBackend
|
||||
return false;
|
||||
}
|
||||
|
||||
TryCommitRange(_hostMemory, pageBase + 4096, 4096uL, commitProtect);
|
||||
TryCommitRange(pageBase + 4096, 4096uL, commitProtect);
|
||||
if (traceLazyCommit)
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] lazy-commit#{traceIndex}: addr=0x{committedBase:X16} size=0x{committedSize:X16} access={accessType} protect=0x{commitProtect:X8}");
|
||||
@@ -1145,33 +1405,31 @@ public sealed partial class DirectExecutionBackend
|
||||
return true;
|
||||
}
|
||||
|
||||
// The commit protection is one of the two raw values ResolveLazyCommitProtection
|
||||
// produces (0x40 RWX / 0x04 RW); the enum mapping reproduces those exactly.
|
||||
static bool TryCommitRange(IHostMemory hostMemory, ulong baseAddress, ulong length, uint protection)
|
||||
static unsafe bool TryCommitRange(ulong baseAddress, ulong length, uint protection)
|
||||
{
|
||||
if (length == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return hostMemory.Commit(baseAddress, length, protection == 64u ? HostPageProtection.ReadWriteExecute : HostPageProtection.ReadWrite);
|
||||
return VirtualAlloc((void*)baseAddress, (nuint)length, 4096u, protection) != null;
|
||||
}
|
||||
|
||||
static bool TryReserveRange(IHostMemory hostMemory, ulong baseAddress, ulong length)
|
||||
static unsafe bool TryReserveRange(ulong baseAddress, ulong length)
|
||||
{
|
||||
if (length == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return hostMemory.Reserve(baseAddress, length, HostPageProtection.ReadWrite) != 0;
|
||||
return VirtualAlloc((void*)baseAddress, (nuint)length, 8192u, 4u) != null;
|
||||
}
|
||||
|
||||
static bool TryReserveThenCommit(IHostMemory hostMemory, ulong reserveAddress, ulong reserveSize, ulong commitAddress, ulong commitSize, uint protection)
|
||||
static bool TryReserveThenCommit(ulong reserveAddress, ulong reserveSize, ulong commitAddress, ulong commitSize, uint protection)
|
||||
{
|
||||
if (!TryReserveRange(hostMemory, reserveAddress, reserveSize))
|
||||
if (!TryReserveRange(reserveAddress, reserveSize))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return TryCommitRange(hostMemory, commitAddress, commitSize, protection);
|
||||
return TryCommitRange(commitAddress, commitSize, protection);
|
||||
}
|
||||
|
||||
static bool IsAccessCompatible(ulong accessType, uint protection)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,8 +6,6 @@ using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using SharpEmu.HLE;
|
||||
using SharpEmu.HLE.Host;
|
||||
using SharpEmu.HLE.Host.Posix;
|
||||
|
||||
namespace SharpEmu.Core.Cpu.Native;
|
||||
|
||||
@@ -37,6 +35,20 @@ public sealed partial class DirectExecutionBackend
|
||||
private bool _nativeWorkersDisposed;
|
||||
private int _nativeWorkerCreationFailedLogged;
|
||||
|
||||
private const uint StackSizeParamIsAReservation = 0x00010000u;
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern nint CreateThread(
|
||||
nint lpThreadAttributes,
|
||||
nuint dwStackSize,
|
||||
nint lpStartAddress,
|
||||
nint lpParameter,
|
||||
uint dwCreationFlags,
|
||||
out uint lpThreadId);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern uint WaitForSingleObject(nint hHandle, uint dwMilliseconds);
|
||||
|
||||
// Runs an emitted guest entry stub. Preferred path is a pooled native worker
|
||||
// thread; falls back to the historical inline calli (guest frames above this
|
||||
// thread's managed frames) when workers are disabled or unavailable.
|
||||
@@ -49,7 +61,7 @@ public sealed partial class DirectExecutionBackend
|
||||
var worker = RentNativeGuestExecutor();
|
||||
if (worker is null)
|
||||
{
|
||||
_hostThreading.SetTlsValue(_hostRspSlotTlsIndex, (nint)hostRspSlot);
|
||||
TlsSetValue(_hostRspSlotTlsIndex, (nint)hostRspSlot);
|
||||
return CallNativeEntry(entryStub);
|
||||
}
|
||||
try
|
||||
@@ -80,7 +92,10 @@ public sealed partial class DirectExecutionBackend
|
||||
|
||||
private NativeGuestExecutor? RentNativeGuestExecutor()
|
||||
{
|
||||
if (NativeGuestWorkersDisabled)
|
||||
// NativeGuestExecutor emits a Win32 wait loop and creates it with
|
||||
// kernel32!CreateThread. POSIX hosts use the established inline entry
|
||||
// path until the worker loop has a pthread/eventfd implementation.
|
||||
if (!OperatingSystem.IsWindows() || NativeGuestWorkersDisabled)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@@ -234,7 +249,7 @@ public sealed partial class DirectExecutionBackend
|
||||
|
||||
public static NativeGuestExecutor? TryCreate(DirectExecutionBackend backend)
|
||||
{
|
||||
if (!EnsureHostRuntimeExports(backend._hostSymbols))
|
||||
if (!EnsureKernel32Exports())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@@ -247,27 +262,32 @@ public sealed partial class DirectExecutionBackend
|
||||
return executor;
|
||||
}
|
||||
|
||||
private static bool EnsureHostRuntimeExports(IHostSymbolResolver symbols)
|
||||
private static bool EnsureKernel32Exports()
|
||||
{
|
||||
if (_exitThreadAddress != 0)
|
||||
{
|
||||
return _waitForSingleObjectAddress != 0 && _setEventAddress != 0;
|
||||
}
|
||||
_waitForSingleObjectAddress = symbols.GetAddress(HostRuntimeFunction.WaitForSingleObject);
|
||||
_setEventAddress = symbols.GetAddress(HostRuntimeFunction.SetEvent);
|
||||
_exitThreadAddress = symbols.GetAddress(HostRuntimeFunction.ExitThread);
|
||||
nint kernel32 = GetModuleHandle("kernel32.dll");
|
||||
if (kernel32 == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
_waitForSingleObjectAddress = GetProcAddress(kernel32, "WaitForSingleObject");
|
||||
_setEventAddress = GetProcAddress(kernel32, "SetEvent");
|
||||
_exitThreadAddress = GetProcAddress(kernel32, "ExitThread");
|
||||
return _waitForSingleObjectAddress != 0 && _setEventAddress != 0 && _exitThreadAddress != 0;
|
||||
}
|
||||
|
||||
private bool Initialize()
|
||||
{
|
||||
_selfHandle = GCHandle.Alloc(this);
|
||||
_controlBlock = (void*)_backend._hostMemory.Allocate(0, 4096u, HostPageProtection.ReadWrite);
|
||||
_controlBlock = VirtualAlloc(null, 4096u, 12288u, 4u);
|
||||
if (_controlBlock == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
_loopStub = (void*)_backend._hostMemory.Allocate(0, LoopStubSize, HostPageProtection.ReadWriteExecute);
|
||||
_loopStub = VirtualAlloc(null, LoopStubSize, 12288u, 64u);
|
||||
if (_loopStub == null)
|
||||
{
|
||||
return false;
|
||||
@@ -375,15 +395,17 @@ public sealed partial class DirectExecutionBackend
|
||||
*(int*)(code + skipJump) = skipEntryOffset - (skipJump + sizeof(int));
|
||||
|
||||
uint oldProtect = 0;
|
||||
if (!_backend._hostMemory.Protect((ulong)_loopStub, LoopStubSize, HostPageProtection.ReadExecute, out oldProtect))
|
||||
if (!VirtualProtect(_loopStub, LoopStubSize, 32u, &oldProtect))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
_backend._hostMemory.FlushInstructionCache((ulong)_loopStub, LoopStubSize);
|
||||
_threadHandle = _backend._hostThreading.CreateNativeThread(
|
||||
(nint)_loopStub,
|
||||
FlushInstructionCache(GetCurrentProcess(), _loopStub, LoopStubSize);
|
||||
_threadHandle = CreateThread(
|
||||
0,
|
||||
WorkerStackReservation,
|
||||
(nint)_loopStub,
|
||||
0,
|
||||
StackSizeParamIsAReservation,
|
||||
out _nativeThreadId);
|
||||
if (_threadHandle == 0)
|
||||
{
|
||||
@@ -511,7 +533,7 @@ public sealed partial class DirectExecutionBackend
|
||||
_prevYieldRequested = _activeGuestThreadYieldRequested;
|
||||
_prevYieldReason = _activeGuestThreadYieldReason;
|
||||
_prevState = _activeGuestThreadState;
|
||||
_prevHostRspSlot = backend._hostThreading.GetTlsValue(backend._hostRspSlotTlsIndex);
|
||||
_prevHostRspSlot = TlsGetValue(backend._hostRspSlotTlsIndex);
|
||||
_prevGuestThreadHandle = GuestThreadExecution.EnterGuestThread(_runGuestThreadHandle);
|
||||
_entered = true;
|
||||
_activeExecutionBackend = backend;
|
||||
@@ -523,11 +545,11 @@ public sealed partial class DirectExecutionBackend
|
||||
_activeGuestThreadYieldReason = null;
|
||||
_activeGuestThreadState = _runState;
|
||||
backend.BindTlsBase(_runContext!);
|
||||
backend._hostThreading.SetTlsValue(backend._hostRspSlotTlsIndex, _runHostRspSlot);
|
||||
TlsSetValue(backend._hostRspSlotTlsIndex, _runHostRspSlot);
|
||||
if (_runState is { } state)
|
||||
{
|
||||
_prevHostThreadId = Volatile.Read(ref state.HostThreadId);
|
||||
Volatile.Write(ref state.HostThreadId, unchecked((int)backend._hostThreading.CurrentThreadId));
|
||||
Volatile.Write(ref state.HostThreadId, unchecked((int)GetCurrentThreadId()));
|
||||
}
|
||||
if (_runAffinityMask != 0)
|
||||
{
|
||||
@@ -557,7 +579,7 @@ public sealed partial class DirectExecutionBackend
|
||||
{
|
||||
Volatile.Write(ref state.HostThreadId, _prevHostThreadId);
|
||||
}
|
||||
_backend._hostThreading.SetTlsValue(_backend._hostRspSlotTlsIndex, _prevHostRspSlot);
|
||||
TlsSetValue(_backend._hostRspSlotTlsIndex, _prevHostRspSlot);
|
||||
GuestThreadExecution.RestoreGuestThread(_prevGuestThreadHandle);
|
||||
_activeExecutionBackend = _prevBackend;
|
||||
_activeCpuContext = _prevContext;
|
||||
@@ -594,8 +616,8 @@ public sealed partial class DirectExecutionBackend
|
||||
var exited = _threadHandle == 0;
|
||||
if (_threadHandle != 0)
|
||||
{
|
||||
exited = _backend._hostThreading.WaitForThreadExit(_threadHandle, 1000u);
|
||||
_backend._hostThreading.CloseThreadHandle(_threadHandle);
|
||||
exited = WaitForSingleObject(_threadHandle, 1000u) == 0u;
|
||||
CloseHandle(_threadHandle);
|
||||
_threadHandle = 0;
|
||||
}
|
||||
if (!exited)
|
||||
@@ -609,12 +631,12 @@ public sealed partial class DirectExecutionBackend
|
||||
}
|
||||
if (_loopStub != null)
|
||||
{
|
||||
_backend._hostMemory.Free((ulong)_loopStub);
|
||||
VirtualFree(_loopStub, 0u, 32768u);
|
||||
_loopStub = null;
|
||||
}
|
||||
if (_controlBlock != null)
|
||||
{
|
||||
_backend._hostMemory.Free((ulong)_controlBlock);
|
||||
VirtualFree(_controlBlock, 0u, 32768u);
|
||||
_controlBlock = null;
|
||||
}
|
||||
if (_selfHandle.IsAllocated)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using SharpEmu.Core.Cpu.Native.Windows;
|
||||
using SharpEmu.HLE;
|
||||
|
||||
namespace SharpEmu.Core.Cpu.Native;
|
||||
|
||||
@@ -21,6 +21,8 @@ public sealed unsafe partial class DirectExecutionBackend
|
||||
// runtime keeps turning its own faults into managed exceptions.
|
||||
|
||||
private const int PosixSigIll = 4;
|
||||
private const int PosixSigTrap = 5;
|
||||
private const int PosixSigAbort = 6;
|
||||
private const int PosixSigSegv = 11;
|
||||
private static readonly int PosixSigBus = OperatingSystem.IsMacOS() ? 10 : 7;
|
||||
|
||||
@@ -62,6 +64,9 @@ public sealed unsafe partial class DirectExecutionBackend
|
||||
private static bool _posixSignalWarmup;
|
||||
private static readonly nint[] _posixPreviousActions = new nint[32];
|
||||
private static int _posixSignalTraceCount;
|
||||
private static long _perfSignalCount;
|
||||
private static readonly bool _perfSignalCounter =
|
||||
string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_PERF_MEM"), "1", StringComparison.Ordinal);
|
||||
|
||||
[ThreadStatic]
|
||||
private static int _posixSignalHandlerDepth;
|
||||
@@ -88,10 +93,13 @@ public sealed unsafe partial class DirectExecutionBackend
|
||||
}
|
||||
|
||||
WarmUpPosixSignalPath();
|
||||
SharpEmu.HLE.GuestImageWriteTracker.WarmUp();
|
||||
|
||||
if (!InstallPosixSignalHandler(PosixSigSegv) ||
|
||||
!InstallPosixSignalHandler(PosixSigBus) ||
|
||||
!InstallPosixSignalHandler(PosixSigIll))
|
||||
!InstallPosixSignalHandler(PosixSigIll) ||
|
||||
!InstallPosixSignalHandler(PosixSigTrap) ||
|
||||
!InstallPosixSignalHandler(PosixSigAbort))
|
||||
{
|
||||
throw new InvalidOperationException("Failed to install POSIX fault signal handlers");
|
||||
}
|
||||
@@ -132,8 +140,8 @@ public sealed unsafe partial class DirectExecutionBackend
|
||||
// action and sigaction(0, ...) fails with EINVAL).
|
||||
EXCEPTION_RECORD record = default;
|
||||
record.ExceptionCode = DBG_PRINTEXCEPTION_C;
|
||||
byte* contextRecord = stackalloc byte[Win64ContextOffsets.Size];
|
||||
new Span<byte>(contextRecord, Win64ContextOffsets.Size).Clear();
|
||||
byte* contextRecord = stackalloc byte[Win64ContextSize];
|
||||
new Span<byte>(contextRecord, Win64ContextSize).Clear();
|
||||
EXCEPTION_POINTERS pointers;
|
||||
pointers.ExceptionRecord = &record;
|
||||
pointers.ContextRecord = contextRecord;
|
||||
@@ -190,8 +198,27 @@ public sealed unsafe partial class DirectExecutionBackend
|
||||
}
|
||||
|
||||
_posixSignalHandlerDepth++;
|
||||
if (_perfSignalCounter)
|
||||
{
|
||||
var n = Interlocked.Increment(ref _perfSignalCount);
|
||||
if (n % 100000 == 0)
|
||||
{
|
||||
Console.Error.WriteLine($"[PERF][MEM] posix_faults={n}");
|
||||
}
|
||||
}
|
||||
try
|
||||
{
|
||||
// Guest-image write tracking runs first: it only needs the fault
|
||||
// address (safe for host and guest threads alike) and must resume
|
||||
// the faulting write immediately after restoring write access.
|
||||
if (signal != PosixSigIll &&
|
||||
siginfo != 0 &&
|
||||
SharpEmu.HLE.GuestImageWriteTracker.TryHandleWriteFault(
|
||||
*(ulong*)((byte*)siginfo + PosixSigInfoAddressOffset)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (TryHandlePosixFault(signal, siginfo, ucontext))
|
||||
{
|
||||
return;
|
||||
@@ -217,8 +244,8 @@ public sealed unsafe partial class DirectExecutionBackend
|
||||
return false;
|
||||
}
|
||||
|
||||
byte* contextRecord = stackalloc byte[Win64ContextOffsets.Size];
|
||||
new Span<byte>(contextRecord, Win64ContextOffsets.Size).Clear();
|
||||
byte* contextRecord = stackalloc byte[Win64ContextSize];
|
||||
new Span<byte>(contextRecord, Win64ContextSize).Clear();
|
||||
int[] offsets = PosixRegisterOffsets;
|
||||
for (int i = 0; i < offsets.Length; i++)
|
||||
{
|
||||
@@ -231,6 +258,14 @@ public sealed unsafe partial class DirectExecutionBackend
|
||||
{
|
||||
record.ExceptionCode = 3221225501u;
|
||||
}
|
||||
else if (signal == PosixSigTrap)
|
||||
{
|
||||
record.ExceptionCode = 2147483651u;
|
||||
}
|
||||
else if (signal == PosixSigAbort)
|
||||
{
|
||||
record.ExceptionCode = 1073741845u;
|
||||
}
|
||||
else
|
||||
{
|
||||
ulong faultAddress = GetPosixFaultAddress(siginfo, registers);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,425 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using SharpEmu.HLE;
|
||||
|
||||
namespace SharpEmu.Core.Cpu.Native;
|
||||
|
||||
/// <summary>
|
||||
/// POSIX replacements for the kernel32 helpers the native backend embeds in
|
||||
/// emitted x86-64 code. Every stub exposed here follows the Win64 calling
|
||||
/// convention the emitted call sites were written for (first argument in
|
||||
/// ECX, result in RAX, Win64 non-volatile registers preserved), so the
|
||||
/// emission code stays identical across platforms.
|
||||
/// </summary>
|
||||
internal static unsafe class PosixHostStubs
|
||||
{
|
||||
private static readonly object Gate = new();
|
||||
private static bool _initialized;
|
||||
private static nint _tlsGetValueStub;
|
||||
private static nint _queryPerformanceCounterStub;
|
||||
private static nint _switchToThreadStub;
|
||||
private static nint _sleepStub;
|
||||
|
||||
public static nint TlsGetValueStubAddress
|
||||
{
|
||||
get { EnsureInitialized(); return _tlsGetValueStub; }
|
||||
}
|
||||
|
||||
public static nint QueryPerformanceCounterStubAddress
|
||||
{
|
||||
get { EnsureInitialized(); return _queryPerformanceCounterStub; }
|
||||
}
|
||||
|
||||
public static nint SwitchToThreadStubAddress
|
||||
{
|
||||
get { EnsureInitialized(); return _switchToThreadStub; }
|
||||
}
|
||||
|
||||
public static nint SleepStubAddress
|
||||
{
|
||||
get { EnsureInitialized(); return _sleepStub; }
|
||||
}
|
||||
|
||||
public static nint CreateWorkerEvent()
|
||||
{
|
||||
if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
return dispatch_semaphore_create(0);
|
||||
}
|
||||
|
||||
var semaphore = Marshal.AllocHGlobal(64);
|
||||
if (sem_init(semaphore, 0, 0) != 0)
|
||||
{
|
||||
Marshal.FreeHGlobal(semaphore);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return semaphore;
|
||||
}
|
||||
|
||||
public static bool SignalWorkerEvent(nint handle)
|
||||
{
|
||||
if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
_ = dispatch_semaphore_signal(handle);
|
||||
return true;
|
||||
}
|
||||
|
||||
return sem_post(handle) == 0;
|
||||
}
|
||||
|
||||
public static bool WaitWorkerEvent(nint handle, int timeoutMilliseconds)
|
||||
{
|
||||
if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
if (timeoutMilliseconds < 0)
|
||||
{
|
||||
return dispatch_semaphore_wait(handle, ulong.MaxValue) == 0;
|
||||
}
|
||||
|
||||
var deadline = dispatch_time(0, timeoutMilliseconds * 1_000_000L);
|
||||
return dispatch_semaphore_wait(handle, deadline) == 0;
|
||||
}
|
||||
|
||||
if (timeoutMilliseconds < 0)
|
||||
{
|
||||
while (sem_wait(handle) != 0)
|
||||
{
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
var deadlineTicks = Environment.TickCount64 + timeoutMilliseconds;
|
||||
while (sem_trywait(handle) != 0)
|
||||
{
|
||||
if (Environment.TickCount64 >= deadlineTicks)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Thread.Sleep(1);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void DestroyWorkerEvent(nint handle)
|
||||
{
|
||||
if (handle == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
dispatch_release(handle);
|
||||
return;
|
||||
}
|
||||
|
||||
_ = sem_destroy(handle);
|
||||
Marshal.FreeHGlobal(handle);
|
||||
}
|
||||
|
||||
/// <summary>Allocates a pthread TLS key, mirroring kernel32!TlsAlloc.</summary>
|
||||
public static uint TlsAlloc()
|
||||
{
|
||||
if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
nuint key;
|
||||
return pthread_key_create_mac(&key, 0) == 0 ? (uint)key : uint.MaxValue;
|
||||
}
|
||||
|
||||
uint key32;
|
||||
return pthread_key_create_linux(&key32, 0) == 0 ? key32 : uint.MaxValue;
|
||||
}
|
||||
|
||||
public static bool TlsFree(uint key)
|
||||
{
|
||||
return OperatingSystem.IsMacOS()
|
||||
? pthread_key_delete_mac((nuint)key) == 0
|
||||
: pthread_key_delete_linux(key) == 0;
|
||||
}
|
||||
|
||||
public static bool TlsSetValue(uint key, nint value)
|
||||
{
|
||||
return OperatingSystem.IsMacOS()
|
||||
? pthread_setspecific_mac((nuint)key, value) == 0
|
||||
: pthread_setspecific_linux(key, value) == 0;
|
||||
}
|
||||
|
||||
public static nint TlsGetValue(uint key)
|
||||
{
|
||||
return OperatingSystem.IsMacOS()
|
||||
? pthread_getspecific_mac((nuint)key)
|
||||
: pthread_getspecific_linux(key);
|
||||
}
|
||||
|
||||
/// <summary>Stable numeric id of the calling thread (kernel32!GetCurrentThreadId).</summary>
|
||||
public static uint GetCurrentThreadId()
|
||||
{
|
||||
if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
ulong tid;
|
||||
return pthread_threadid_np(0, &tid) == 0 ? unchecked((uint)tid) : 0u;
|
||||
}
|
||||
|
||||
return unchecked((uint)gettid());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wraps a managed callback (compiled for the SysV ABI on POSIX .NET) in a
|
||||
/// thunk that accepts up to four integer arguments in the Win64 ABI the
|
||||
/// emitted x86-64 call sites use. Win64 passes args in rcx/rdx/r8/r9 and
|
||||
/// treats rdi/rsi as non-volatile; SysV expects rdi/rsi/rdx/rcx and
|
||||
/// clobbers them, so the thunk saves rdi/rsi, shuffles the registers, keeps
|
||||
/// the stack 16-byte aligned for the call, and forwards the rax result.
|
||||
/// </summary>
|
||||
public static nint CreateWin64ToSysVThunk(nint sysvTarget)
|
||||
{
|
||||
var page = (byte*)HostMemory.Alloc(
|
||||
null,
|
||||
4096,
|
||||
HostMemory.MEM_COMMIT | HostMemory.MEM_RESERVE,
|
||||
HostMemory.PAGE_EXECUTE_READWRITE);
|
||||
if (page == null)
|
||||
{
|
||||
throw new OutOfMemoryException("Failed to allocate Win64->SysV thunk page");
|
||||
}
|
||||
|
||||
var offset = 0;
|
||||
Emit(page, ref offset, 0x57); // push rdi
|
||||
Emit(page, ref offset, 0x56); // push rsi
|
||||
Emit(page, ref offset, 0x48, 0x89, 0xCF); // mov rdi, rcx
|
||||
Emit(page, ref offset, 0x48, 0x89, 0xD6); // mov rsi, rdx
|
||||
Emit(page, ref offset, 0x4C, 0x89, 0xC2); // mov rdx, r8
|
||||
Emit(page, ref offset, 0x4C, 0x89, 0xC9); // mov rcx, r9
|
||||
Emit(page, ref offset, 0x48, 0x83, 0xEC, 0x08); // sub rsp, 8 (realign to 16)
|
||||
EmitMovRaxImm64(page, ref offset, sysvTarget); // mov rax, target
|
||||
Emit(page, ref offset, 0xFF, 0xD0); // call rax
|
||||
Emit(page, ref offset, 0x48, 0x83, 0xC4, 0x08); // add rsp, 8
|
||||
Emit(page, ref offset, 0x5E); // pop rsi
|
||||
Emit(page, ref offset, 0x5F); // pop rdi
|
||||
Emit(page, ref offset, 0xC3); // ret
|
||||
|
||||
if (!HostMemory.Protect(page, 4096, HostMemory.PAGE_EXECUTE_READ, out _))
|
||||
{
|
||||
throw new InvalidOperationException("Failed to protect Win64->SysV thunk page");
|
||||
}
|
||||
|
||||
HostMemory.FlushInstructionCache(page, (nuint)offset);
|
||||
return (nint)page;
|
||||
}
|
||||
|
||||
private static void EnsureInitialized()
|
||||
{
|
||||
if (_initialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (Gate)
|
||||
{
|
||||
if (_initialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
BuildStubs();
|
||||
_initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
private static void BuildStubs()
|
||||
{
|
||||
var page = (byte*)HostMemory.Alloc(
|
||||
null,
|
||||
4096,
|
||||
HostMemory.MEM_COMMIT | HostMemory.MEM_RESERVE,
|
||||
HostMemory.PAGE_EXECUTE_READWRITE);
|
||||
if (page == null)
|
||||
{
|
||||
throw new OutOfMemoryException("Failed to allocate POSIX host helper stub page");
|
||||
}
|
||||
|
||||
var offset = 0;
|
||||
_tlsGetValueStub = EmitTlsGetValue(page, ref offset);
|
||||
_queryPerformanceCounterStub = EmitQueryPerformanceCounter(page, ref offset);
|
||||
_switchToThreadStub = EmitSwitchToThread(page, ref offset);
|
||||
_sleepStub = EmitSleep(page, ref offset);
|
||||
|
||||
if (!HostMemory.Protect(page, 4096, HostMemory.PAGE_EXECUTE_READ, out _))
|
||||
{
|
||||
throw new InvalidOperationException("Failed to protect POSIX host helper stub page");
|
||||
}
|
||||
|
||||
HostMemory.FlushInstructionCache(page, (nuint)offset);
|
||||
}
|
||||
|
||||
private static nint EmitTlsGetValue(byte* page, ref int offset)
|
||||
{
|
||||
var start = (nint)(page + offset);
|
||||
if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
// On macOS x86-64 pthread keys index the gs-based thread specific
|
||||
// data array directly, so TlsGetValue(index in ecx) collapses to a
|
||||
// single load that clobbers nothing but RAX.
|
||||
Emit(page, ref offset, 0x89, 0xC8); // mov eax, ecx
|
||||
Emit(page, ref offset, 0x65, 0x48, 0x8B, 0x04, 0xC5, 0, 0, 0, 0); // mov rax, gs:[rax*8]
|
||||
Emit(page, ref offset, 0xC3); // ret
|
||||
return start;
|
||||
}
|
||||
|
||||
// Linux: call pthread_getspecific, preserving the registers that are
|
||||
// volatile in SysV but non-volatile in Win64 (rsi, rdi).
|
||||
var pthreadGetSpecific = ResolveLibcExport("pthread_getspecific");
|
||||
Emit(page, ref offset, 0x56); // push rsi
|
||||
Emit(page, ref offset, 0x57); // push rdi
|
||||
Emit(page, ref offset, 0x48, 0x83, 0xEC, 0x08); // sub rsp, 8
|
||||
Emit(page, ref offset, 0x89, 0xCF); // mov edi, ecx
|
||||
EmitMovRaxImm64(page, ref offset, pthreadGetSpecific); // mov rax, imm64
|
||||
Emit(page, ref offset, 0xFF, 0xD0); // call rax
|
||||
Emit(page, ref offset, 0x48, 0x83, 0xC4, 0x08); // add rsp, 8
|
||||
Emit(page, ref offset, 0x5F); // pop rdi
|
||||
Emit(page, ref offset, 0x5E); // pop rsi
|
||||
Emit(page, ref offset, 0xC3); // ret
|
||||
return start;
|
||||
}
|
||||
|
||||
private static nint EmitQueryPerformanceCounter(byte* page, ref int offset)
|
||||
{
|
||||
// BOOL QueryPerformanceCounter(LARGE_INTEGER* out in rcx): the emitted
|
||||
// consumers only need a monotonically increasing counter, which rdtsc
|
||||
// provides without leaving Win64-safe registers.
|
||||
var start = (nint)(page + offset);
|
||||
Emit(page, ref offset, 0x0F, 0x31); // rdtsc
|
||||
Emit(page, ref offset, 0x48, 0xC1, 0xE2, 0x20); // shl rdx, 32
|
||||
Emit(page, ref offset, 0x48, 0x09, 0xD0); // or rax, rdx
|
||||
Emit(page, ref offset, 0x48, 0x89, 0x01); // mov [rcx], rax
|
||||
Emit(page, ref offset, 0xB8, 0x01, 0x00, 0x00, 0x00); // mov eax, 1
|
||||
Emit(page, ref offset, 0xC3); // ret
|
||||
return start;
|
||||
}
|
||||
|
||||
private static nint EmitSwitchToThread(byte* page, ref int offset)
|
||||
{
|
||||
var schedYield = ResolveLibcExport("sched_yield");
|
||||
var start = (nint)(page + offset);
|
||||
Emit(page, ref offset, 0x56); // push rsi
|
||||
Emit(page, ref offset, 0x57); // push rdi
|
||||
Emit(page, ref offset, 0x48, 0x83, 0xEC, 0x08); // sub rsp, 8
|
||||
EmitMovRaxImm64(page, ref offset, schedYield); // mov rax, imm64
|
||||
Emit(page, ref offset, 0xFF, 0xD0); // call rax
|
||||
Emit(page, ref offset, 0x48, 0x83, 0xC4, 0x08); // add rsp, 8
|
||||
Emit(page, ref offset, 0x5F); // pop rdi
|
||||
Emit(page, ref offset, 0x5E); // pop rsi
|
||||
Emit(page, ref offset, 0xB8, 0x01, 0x00, 0x00, 0x00); // mov eax, 1
|
||||
Emit(page, ref offset, 0xC3); // ret
|
||||
return start;
|
||||
}
|
||||
|
||||
private static nint EmitSleep(byte* page, ref int offset)
|
||||
{
|
||||
// void Sleep(DWORD milliseconds in ecx) -> usleep(microseconds in edi).
|
||||
var usleep = ResolveLibcExport("usleep");
|
||||
var start = (nint)(page + offset);
|
||||
Emit(page, ref offset, 0x56); // push rsi
|
||||
Emit(page, ref offset, 0x57); // push rdi
|
||||
Emit(page, ref offset, 0x48, 0x83, 0xEC, 0x08); // sub rsp, 8
|
||||
Emit(page, ref offset, 0x89, 0xCF); // mov edi, ecx
|
||||
Emit(page, ref offset, 0x81, 0xFF, 0xFF, 0x0F, 0x00, 0x00); // cmp edi, 0xFFF
|
||||
Emit(page, ref offset, 0x76, 0x05); // jbe +5
|
||||
Emit(page, ref offset, 0xBF, 0xFF, 0x0F, 0x00, 0x00); // mov edi, 0xFFF (cap at ~4s)
|
||||
Emit(page, ref offset, 0x69, 0xFF, 0xE8, 0x03, 0x00, 0x00); // imul edi, edi, 1000
|
||||
EmitMovRaxImm64(page, ref offset, usleep); // mov rax, imm64
|
||||
Emit(page, ref offset, 0xFF, 0xD0); // call rax
|
||||
Emit(page, ref offset, 0x48, 0x83, 0xC4, 0x08); // add rsp, 8
|
||||
Emit(page, ref offset, 0x5F); // pop rdi
|
||||
Emit(page, ref offset, 0x5E); // pop rsi
|
||||
Emit(page, ref offset, 0xC3); // ret
|
||||
return start;
|
||||
}
|
||||
|
||||
private static nint ResolveLibcExport(string name)
|
||||
{
|
||||
var libc = NativeLibrary.Load(OperatingSystem.IsMacOS() ? "libSystem.dylib" : "libc.so.6");
|
||||
return NativeLibrary.GetExport(libc, name);
|
||||
}
|
||||
|
||||
private static void Emit(byte* page, ref int offset, params byte[] bytes)
|
||||
{
|
||||
foreach (var value in bytes)
|
||||
{
|
||||
page[offset++] = value;
|
||||
}
|
||||
}
|
||||
|
||||
private static void EmitMovRaxImm64(byte* page, ref int offset, nint value)
|
||||
{
|
||||
Emit(page, ref offset, 0x48, 0xB8);
|
||||
*(long*)(page + offset) = value;
|
||||
offset += sizeof(long);
|
||||
}
|
||||
|
||||
[DllImport("libc", EntryPoint = "pthread_key_create", SetLastError = true)]
|
||||
private static extern int pthread_key_create_mac(nuint* key, nint destructor);
|
||||
|
||||
[DllImport("libc", EntryPoint = "pthread_key_create", SetLastError = true)]
|
||||
private static extern int pthread_key_create_linux(uint* key, nint destructor);
|
||||
|
||||
[DllImport("libc", EntryPoint = "pthread_key_delete")]
|
||||
private static extern int pthread_key_delete_mac(nuint key);
|
||||
|
||||
[DllImport("libc", EntryPoint = "pthread_key_delete")]
|
||||
private static extern int pthread_key_delete_linux(uint key);
|
||||
|
||||
[DllImport("libc", EntryPoint = "pthread_setspecific")]
|
||||
private static extern int pthread_setspecific_mac(nuint key, nint value);
|
||||
|
||||
[DllImport("libc", EntryPoint = "pthread_setspecific")]
|
||||
private static extern int pthread_setspecific_linux(uint key, nint value);
|
||||
|
||||
[DllImport("libc", EntryPoint = "pthread_getspecific")]
|
||||
private static extern nint pthread_getspecific_mac(nuint key);
|
||||
|
||||
[DllImport("libc", EntryPoint = "pthread_getspecific")]
|
||||
private static extern nint pthread_getspecific_linux(uint key);
|
||||
|
||||
[DllImport("libc")]
|
||||
private static extern int pthread_threadid_np(nint thread, ulong* threadId);
|
||||
|
||||
[DllImport("libc")]
|
||||
private static extern int gettid();
|
||||
|
||||
[DllImport("libc")]
|
||||
private static extern nint dispatch_semaphore_create(long value);
|
||||
|
||||
[DllImport("libc")]
|
||||
private static extern nint dispatch_semaphore_signal(nint semaphore);
|
||||
|
||||
[DllImport("libc")]
|
||||
private static extern nint dispatch_semaphore_wait(nint semaphore, ulong timeout);
|
||||
|
||||
[DllImport("libc")]
|
||||
private static extern ulong dispatch_time(ulong when, long deltaNanoseconds);
|
||||
|
||||
[DllImport("libc")]
|
||||
private static extern void dispatch_release(nint handle);
|
||||
|
||||
[DllImport("libc")]
|
||||
private static extern int sem_init(nint semaphore, int shared, uint value);
|
||||
|
||||
[DllImport("libc")]
|
||||
private static extern int sem_post(nint semaphore);
|
||||
|
||||
[DllImport("libc")]
|
||||
private static extern int sem_wait(nint semaphore);
|
||||
|
||||
[DllImport("libc")]
|
||||
private static extern int sem_trywait(nint semaphore);
|
||||
|
||||
[DllImport("libc")]
|
||||
private static extern int sem_destroy(nint semaphore);
|
||||
}
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using SharpEmu.HLE;
|
||||
using SharpEmu.HLE.Host;
|
||||
|
||||
namespace SharpEmu.Core.Cpu.Native;
|
||||
|
||||
@@ -12,15 +11,17 @@ public sealed unsafe class StubManager : IDisposable
|
||||
private readonly List<nint> _allocatedStubs = new();
|
||||
private readonly Dictionary<string, nint> _importHandlers = new();
|
||||
private readonly Dictionary<ulong, nint> _stubAddresses = new();
|
||||
private readonly IHostMemory _hostMemory;
|
||||
private byte* _pltMemory;
|
||||
private int _pltOffset;
|
||||
private const int PltMemorySize = 1024 * 1024; // 1MB for stubs
|
||||
|
||||
public StubManager(IHostMemory? hostMemory = null)
|
||||
public StubManager()
|
||||
{
|
||||
_hostMemory = hostMemory ?? HostPlatform.Current.Memory;
|
||||
_pltMemory = (byte*)_hostMemory.Allocate(0, PltMemorySize, HostPageProtection.ReadWriteExecute);
|
||||
_pltMemory = (byte*)VirtualAlloc(
|
||||
null,
|
||||
(nuint)PltMemorySize,
|
||||
AllocationType.Reserve | AllocationType.Commit,
|
||||
MemoryProtection.ExecuteReadWrite);
|
||||
|
||||
if (_pltMemory == null)
|
||||
{
|
||||
@@ -184,7 +185,7 @@ public sealed unsafe class StubManager : IDisposable
|
||||
{
|
||||
if (_pltMemory != null)
|
||||
{
|
||||
_hostMemory.Free((ulong)_pltMemory);
|
||||
VirtualFree(_pltMemory, 0, FreeType.Release);
|
||||
_pltMemory = null;
|
||||
}
|
||||
|
||||
@@ -193,5 +194,29 @@ public sealed unsafe class StubManager : IDisposable
|
||||
_stubAddresses.Clear();
|
||||
}
|
||||
|
||||
private static void* VirtualAlloc(void* lpAddress, nuint dwSize, AllocationType flAllocationType, MemoryProtection flProtect) =>
|
||||
HostMemory.Alloc(lpAddress, dwSize, (uint)flAllocationType, (uint)flProtect);
|
||||
|
||||
private static bool VirtualFree(void* lpAddress, nuint dwSize, FreeType dwFreeType) =>
|
||||
HostMemory.Free(lpAddress, dwSize, (uint)dwFreeType);
|
||||
|
||||
[Flags]
|
||||
private enum AllocationType : uint
|
||||
{
|
||||
Commit = 0x1000,
|
||||
Reserve = 0x2000,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
private enum MemoryProtection : uint
|
||||
{
|
||||
ExecuteReadWrite = 0x40,
|
||||
}
|
||||
|
||||
private enum FreeType : uint
|
||||
{
|
||||
Release = 0x8000,
|
||||
}
|
||||
|
||||
public delegate void ImportHandler(CpuContext context);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user