mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-24 11:48:39 +08:00
initial commit
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
namespace SharpEmu.Core.Cpu;
|
||||
|
||||
public readonly struct CpuControlTransferInfo
|
||||
{
|
||||
public CpuControlTransferInfo(
|
||||
ulong sourceInstructionPointer,
|
||||
byte opcode,
|
||||
ulong targetInstructionPointer,
|
||||
CpuControlTransferKind kind,
|
||||
bool isIndirect,
|
||||
ulong rax,
|
||||
ulong rbx,
|
||||
ulong rcx,
|
||||
ulong rdx,
|
||||
ulong rsi,
|
||||
ulong rdi,
|
||||
ulong rsp,
|
||||
ulong rbp)
|
||||
{
|
||||
SourceInstructionPointer = sourceInstructionPointer;
|
||||
Opcode = opcode;
|
||||
TargetInstructionPointer = targetInstructionPointer;
|
||||
Kind = kind;
|
||||
IsIndirect = isIndirect;
|
||||
Rax = rax;
|
||||
Rbx = rbx;
|
||||
Rcx = rcx;
|
||||
Rdx = rdx;
|
||||
Rsi = rsi;
|
||||
Rdi = rdi;
|
||||
Rsp = rsp;
|
||||
Rbp = rbp;
|
||||
}
|
||||
|
||||
public ulong SourceInstructionPointer { get; }
|
||||
|
||||
public byte Opcode { get; }
|
||||
|
||||
public ulong TargetInstructionPointer { get; }
|
||||
|
||||
public CpuControlTransferKind Kind { get; }
|
||||
|
||||
public bool IsIndirect { get; }
|
||||
|
||||
public ulong Rax { get; }
|
||||
|
||||
public ulong Rbx { get; }
|
||||
|
||||
public ulong Rcx { get; }
|
||||
|
||||
public ulong Rdx { get; }
|
||||
|
||||
public ulong Rsi { get; }
|
||||
|
||||
public ulong Rdi { get; }
|
||||
|
||||
public ulong Rsp { get; }
|
||||
|
||||
public ulong Rbp { get; }
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
namespace SharpEmu.Core.Cpu;
|
||||
|
||||
public enum CpuControlTransferKind
|
||||
{
|
||||
Call = 0,
|
||||
|
||||
Jump = 1,
|
||||
|
||||
Return = 2,
|
||||
}
|
||||
@@ -0,0 +1,580 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using System.Buffers.Binary;
|
||||
using System.Text;
|
||||
using SharpEmu.Core.Cpu.Native;
|
||||
using SharpEmu.Core.Loader;
|
||||
using SharpEmu.Core.Memory;
|
||||
using SharpEmu.HLE;
|
||||
|
||||
namespace SharpEmu.Core.Cpu;
|
||||
|
||||
public sealed class CpuDispatcher : ICpuDispatcher, IDisposable
|
||||
{
|
||||
private const ulong StackBaseAddress = 0x7FFF_F000_0000UL;
|
||||
private const ulong StackSize = 0x0020_0000UL;
|
||||
private const ulong TlsBaseAddress = 0x7FFE_0000_0000UL;
|
||||
private const ulong TlsSize = 0x0001_0000UL;
|
||||
private const ulong BootstrapStubBaseAddress = 0x7FFD_F000_0000UL;
|
||||
private const ulong BootstrapPayloadBaseAddress = 0x7FFD_E000_0000UL;
|
||||
private const ulong DynlibFallbackStubBaseAddress = 0x7FFD_D000_0000UL;
|
||||
private const ulong ReturnToHostStubBaseAddress = 0x7FFD_C000_0000UL;
|
||||
private const ulong BootstrapRegionSize = 0x0000_1000UL;
|
||||
private const ulong ReturnToHostStubStride = 0x0100_0000UL;
|
||||
private const ulong BootstrapPayloadResultOffset = 0x28UL;
|
||||
private const ulong BootstrapStatusOffset = 0x100UL;
|
||||
private static readonly byte[] BootstrapStartSignature =
|
||||
[
|
||||
0x55, 0x48, 0x89, 0xE5, 0x41, 0x57, 0x41, 0x56,
|
||||
0x41, 0x55, 0x41, 0x54, 0x53, 0x50, 0x48, 0x89,
|
||||
];
|
||||
private readonly IVirtualMemory _virtualMemory;
|
||||
private readonly IModuleManager _moduleManager;
|
||||
private INativeCpuBackend? _nativeCpuBackend;
|
||||
|
||||
public CpuDispatcher(
|
||||
IVirtualMemory virtualMemory,
|
||||
IModuleManager moduleManager,
|
||||
INativeCpuBackend? nativeCpuBackend = null)
|
||||
{
|
||||
_virtualMemory = virtualMemory ?? throw new ArgumentNullException(nameof(virtualMemory));
|
||||
_moduleManager = moduleManager ?? throw new ArgumentNullException(nameof(moduleManager));
|
||||
_nativeCpuBackend = nativeCpuBackend;
|
||||
}
|
||||
|
||||
public ulong? LastEntryPoint { get; private set; }
|
||||
|
||||
public CpuTrapInfo? LastTrapInfo { get; private set; }
|
||||
|
||||
public CpuMemoryFaultInfo? LastMemoryFaultInfo { get; private set; }
|
||||
|
||||
public CpuControlTransferInfo? LastControlTransferInfo { get; private set; }
|
||||
|
||||
public CpuNotImplementedInfo? LastNotImplementedInfo { get; private set; }
|
||||
|
||||
public string? LastImportResolutionTrace { get; private set; }
|
||||
|
||||
public string? LastBasicBlockTrace { get; private set; }
|
||||
|
||||
public string? LastMilestoneLog { get; private set; }
|
||||
|
||||
public string? LastRecentInstructionWindow { get; private set; }
|
||||
|
||||
public string? LastRecentControlTransferTrace { get; private set; }
|
||||
|
||||
public CpuSessionSummary LastSessionSummary { get; private set; }
|
||||
|
||||
public OrbisGen2Result DispatchEntry(
|
||||
ulong entryPoint,
|
||||
Generation generation,
|
||||
IReadOnlyDictionary<ulong, string>? importStubs = null,
|
||||
IReadOnlyDictionary<string, ulong>? runtimeSymbols = null,
|
||||
string processImageName = "eboot.bin",
|
||||
CpuExecutionOptions executionOptions = default)
|
||||
{
|
||||
Console.Error.WriteLine("[DISPATCHER] === DispatchEntry START ===");
|
||||
Console.Error.WriteLine($"[DISPATCHER] entryPoint=0x{entryPoint:X16}, generation={generation}");
|
||||
|
||||
try
|
||||
{
|
||||
return DispatchEntryCore(entryPoint, generation, importStubs, runtimeSymbols, processImageName, executionOptions);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine($"[DISPATCHER] FATAL EXCEPTION in DispatchEntry: {ex.GetType().Name}: {ex.Message}");
|
||||
Console.Error.WriteLine($"[DISPATCHER] Stack trace: {ex.StackTrace}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private OrbisGen2Result DispatchEntryCore(
|
||||
ulong entryPoint,
|
||||
Generation generation,
|
||||
IReadOnlyDictionary<ulong, string>? importStubs = null,
|
||||
IReadOnlyDictionary<string, ulong>? runtimeSymbols = null,
|
||||
string processImageName = "eboot.bin",
|
||||
CpuExecutionOptions executionOptions = default)
|
||||
{
|
||||
Console.Error.WriteLine("[DISPATCHER] DispatchEntryCore STARTING...");
|
||||
|
||||
LastEntryPoint = entryPoint;
|
||||
LastTrapInfo = null;
|
||||
LastMemoryFaultInfo = null;
|
||||
LastControlTransferInfo = null;
|
||||
LastNotImplementedInfo = null;
|
||||
LastImportResolutionTrace = null;
|
||||
LastBasicBlockTrace = null;
|
||||
LastMilestoneLog = null;
|
||||
LastRecentInstructionWindow = null;
|
||||
LastRecentControlTransferTrace = null;
|
||||
LastSessionSummary = default;
|
||||
OrbisGen2Result FailEarly(OrbisGen2Result result, CpuExitReason reason = CpuExitReason.UnhandledException)
|
||||
{
|
||||
LastSessionSummary = new CpuSessionSummary(
|
||||
result,
|
||||
reason,
|
||||
exitCode: null,
|
||||
lastGuestRip: entryPoint,
|
||||
lastStubRip: 0,
|
||||
totalInstructions: 0,
|
||||
importsHit: 0,
|
||||
uniqueNidsHit: 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
var stackBase = TryMapStackRegion();
|
||||
if (stackBase == 0)
|
||||
{
|
||||
return FailEarly(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
var tlsBase = TryMapTlsRegion();
|
||||
if (tlsBase == 0)
|
||||
{
|
||||
return FailEarly(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
var trackedMemory = new TrackedCpuMemory(_virtualMemory);
|
||||
var context = new CpuContext(trackedMemory, generation)
|
||||
{
|
||||
Rip = entryPoint,
|
||||
Rflags = 0x202,
|
||||
FsBase = tlsBase,
|
||||
GsBase = tlsBase,
|
||||
};
|
||||
|
||||
var returnToHostStubAddress = TryMapReturnToHostStubRegion();
|
||||
if (returnToHostStubAddress == 0)
|
||||
{
|
||||
return FailEarly(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
context[CpuRegister.Rsp] = stackBase + StackSize - sizeof(ulong);
|
||||
if (!context.TryWriteUInt64(context[CpuRegister.Rsp], returnToHostStubAddress))
|
||||
{
|
||||
return FailEarly(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
if (!InitializeTls(context, tlsBase))
|
||||
{
|
||||
return FailEarly(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
var effectiveImportStubs = importStubs is null
|
||||
? new Dictionary<ulong, string>()
|
||||
: new Dictionary<ulong, string>(importStubs);
|
||||
var programExitHandlerStubAddress = TryMapDynlibFallbackStubRegion();
|
||||
if (programExitHandlerStubAddress == 0)
|
||||
{
|
||||
return FailEarly(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
if (!InitializeProcessEntryFrame(context, processImageName, programExitHandlerStubAddress))
|
||||
{
|
||||
return FailEarly(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
if (ShouldInjectBootstrapPayload(entryPoint))
|
||||
{
|
||||
if (!TryInstallBootstrapPayload(context, effectiveImportStubs))
|
||||
{
|
||||
return FailEarly(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
}
|
||||
|
||||
var entryFrameDiagnostic = BuildEntryFrameDiagnostic(
|
||||
entryPoint,
|
||||
context,
|
||||
sentinelEnabled: true,
|
||||
sentinelValue: returnToHostStubAddress,
|
||||
entryParamsConfigured: true);
|
||||
|
||||
if (executionOptions.CpuEngine != CpuExecutionEngine.NativeOnly)
|
||||
{
|
||||
LastMilestoneLog = string.Concat(
|
||||
entryFrameDiagnostic,
|
||||
Environment.NewLine,
|
||||
$"CpuEngine: {executionOptions.CpuEngine} (unsupported)");
|
||||
LastNotImplementedInfo = new CpuNotImplementedInfo(
|
||||
CpuNotImplementedSource.NativeBackend,
|
||||
entryPoint,
|
||||
nid: null,
|
||||
exportName: "cpu_engine_unsupported",
|
||||
libraryName: executionOptions.CpuEngine.ToString(),
|
||||
detail: "Unsupported CPU engine mode.");
|
||||
return FailEarly(
|
||||
OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_IMPLEMENTED,
|
||||
CpuExitReason.NativeBackendUnavailable);
|
||||
}
|
||||
|
||||
LastMilestoneLog = string.Concat(
|
||||
entryFrameDiagnostic,
|
||||
Environment.NewLine,
|
||||
"CpuEngine: native-only");
|
||||
_nativeCpuBackend ??= new DirectExecutionBackend(_moduleManager);
|
||||
if (_nativeCpuBackend.TryExecute(
|
||||
context,
|
||||
entryPoint,
|
||||
generation,
|
||||
effectiveImportStubs,
|
||||
runtimeSymbols ?? new Dictionary<string, ulong>(StringComparer.Ordinal),
|
||||
executionOptions,
|
||||
out var nativeResult))
|
||||
{
|
||||
LastSessionSummary = new CpuSessionSummary(
|
||||
nativeResult,
|
||||
nativeResult == OrbisGen2Result.ORBIS_GEN2_OK
|
||||
? CpuExitReason.ReturnedToHost
|
||||
: CpuExitReason.UnhandledException,
|
||||
exitCode: null,
|
||||
lastGuestRip: context.Rip,
|
||||
lastStubRip: 0,
|
||||
totalInstructions: 0,
|
||||
importsHit: 0,
|
||||
uniqueNidsHit: 0);
|
||||
return nativeResult;
|
||||
}
|
||||
|
||||
var backendName = string.IsNullOrWhiteSpace(_nativeCpuBackend.BackendName)
|
||||
? "native-backend"
|
||||
: _nativeCpuBackend.BackendName;
|
||||
var backendError = string.IsNullOrWhiteSpace(_nativeCpuBackend.LastError)
|
||||
? "unknown backend error"
|
||||
: _nativeCpuBackend.LastError;
|
||||
LastNotImplementedInfo = new CpuNotImplementedInfo(
|
||||
CpuNotImplementedSource.NativeBackend,
|
||||
entryPoint,
|
||||
nid: null,
|
||||
exportName: "cpu_engine_native_only",
|
||||
libraryName: backendName,
|
||||
detail: backendError);
|
||||
LastMilestoneLog = string.Concat(
|
||||
LastMilestoneLog,
|
||||
Environment.NewLine,
|
||||
$"CpuEngine native-only failed: {backendError}");
|
||||
Console.Error.WriteLine($"[DISPATCHER] Native backend FAILED: {backendError}");
|
||||
return FailEarly(
|
||||
OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_IMPLEMENTED,
|
||||
CpuExitReason.NativeBackendUnavailable);
|
||||
}
|
||||
|
||||
private ulong TryMapStackRegion()
|
||||
{
|
||||
const ulong stackStride = 0x0100_0000UL;
|
||||
for (var i = 0; i < 32; i++)
|
||||
{
|
||||
var candidateBase = StackBaseAddress - ((ulong)i * stackStride);
|
||||
try
|
||||
{
|
||||
_virtualMemory.Map(
|
||||
candidateBase,
|
||||
StackSize,
|
||||
fileOffset: 0,
|
||||
fileData: ReadOnlySpan<byte>.Empty,
|
||||
ProgramHeaderFlags.Read | ProgramHeaderFlags.Write);
|
||||
return candidateBase;
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private ulong TryMapTlsRegion()
|
||||
{
|
||||
const ulong tlsStride = 0x0100_0000UL;
|
||||
for (var i = 0; i < 32; i++)
|
||||
{
|
||||
var candidateBase = TlsBaseAddress - ((ulong)i * tlsStride);
|
||||
try
|
||||
{
|
||||
_virtualMemory.Map(
|
||||
candidateBase,
|
||||
TlsSize,
|
||||
fileOffset: 0,
|
||||
fileData: ReadOnlySpan<byte>.Empty,
|
||||
ProgramHeaderFlags.Read | ProgramHeaderFlags.Write);
|
||||
return candidateBase;
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static bool InitializeTls(CpuContext context, ulong tlsBase)
|
||||
{
|
||||
return context.TryWriteUInt64(tlsBase + 0x00, tlsBase) &&
|
||||
context.TryWriteUInt64(tlsBase + 0x10, tlsBase) &&
|
||||
context.TryWriteUInt64(tlsBase + 0x28, 0xC0DEC0DECAFEBABEUL);
|
||||
}
|
||||
|
||||
private static bool InitializeProcessEntryFrame(
|
||||
CpuContext context,
|
||||
string processImageName,
|
||||
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)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
argv0Buffer[encodedNameLength] = 0;
|
||||
var cursor = context[CpuRegister.Rsp];
|
||||
|
||||
var argv0Address = AlignDown(cursor - (ulong)argv0Buffer.Length, 16);
|
||||
if (!context.Memory.TryWrite(argv0Address, argv0Buffer))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const ulong entryParamsSize = 0x20;
|
||||
var entryParamsAddress = AlignDown(argv0Address - entryParamsSize, 16);
|
||||
if (!TryWriteUInt32(context, entryParamsAddress + 0x00, 1) ||
|
||||
!TryWriteUInt32(context, entryParamsAddress + 0x04, 0) ||
|
||||
!context.TryWriteUInt64(entryParamsAddress + 0x08, argv0Address) ||
|
||||
!context.TryWriteUInt64(entryParamsAddress + 0x10, 0) ||
|
||||
!context.TryWriteUInt64(entryParamsAddress + 0x18, 0))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
context[CpuRegister.Rdi] = entryParamsAddress;
|
||||
context[CpuRegister.Rsi] = programExitHandlerAddress;
|
||||
context[CpuRegister.Rdx] = 0;
|
||||
context[CpuRegister.Rcx] = 0;
|
||||
context[CpuRegister.R8] = 0;
|
||||
context[CpuRegister.R9] = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static ulong AlignDown(ulong value, ulong alignment)
|
||||
{
|
||||
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,
|
||||
bool sentinelEnabled,
|
||||
ulong sentinelValue,
|
||||
bool entryParamsConfigured)
|
||||
{
|
||||
var initialRsp = context[CpuRegister.Rsp];
|
||||
var stackTopText = context.TryReadUInt64(initialRsp, out var stackTop)
|
||||
? $"0x{stackTop:X16}"
|
||||
: "??";
|
||||
var sentinelText = sentinelEnabled ? "yes" : "no";
|
||||
var sentinelValueText = sentinelEnabled ? $"0x{sentinelValue:X16}" : "??";
|
||||
var entryParamsText = entryParamsConfigured ? "yes" : "no";
|
||||
return
|
||||
$"EntryFrame: entry_rip=0x{entryPoint:X16} initial_rsp=0x{initialRsp:X16} [rsp]={stackTopText} sentinel_enabled={sentinelText} sentinel_value={sentinelValueText} entry_params_configured={entryParamsText}";
|
||||
}
|
||||
|
||||
private bool ShouldInjectBootstrapPayload(ulong entryPoint)
|
||||
{
|
||||
Span<byte> probe = stackalloc byte[16];
|
||||
if (!_virtualMemory.TryRead(entryPoint, probe))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var i = 0; i < BootstrapStartSignature.Length; i++)
|
||||
{
|
||||
if (probe[i] != BootstrapStartSignature[i])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryInstallBootstrapPayload(CpuContext context, IDictionary<ulong, string> importStubs)
|
||||
{
|
||||
var stubAddress = TryMapBootstrapStubRegion();
|
||||
if (stubAddress == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var payloadAddress = TryMapBootstrapPayloadRegion();
|
||||
if (payloadAddress == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var statusAddress = payloadAddress + BootstrapStatusOffset;
|
||||
if (!TryWriteUInt64(payloadAddress, stubAddress) ||
|
||||
!TryWriteUInt64(payloadAddress + 0x08, statusAddress) ||
|
||||
!TryWriteUInt64(payloadAddress + 0x10, statusAddress) ||
|
||||
!TryWriteUInt64(payloadAddress + 0x18, statusAddress) ||
|
||||
!TryWriteUInt64(payloadAddress + 0x20, statusAddress) ||
|
||||
!TryWriteUInt64(payloadAddress + BootstrapPayloadResultOffset, statusAddress) ||
|
||||
!TryWriteUInt32(statusAddress, 0))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
importStubs[stubAddress] = RuntimeStubNids.BootstrapBridge;
|
||||
importStubs[stubAddress + 0x0A] = RuntimeStubNids.BootstrapBridge;
|
||||
|
||||
context[CpuRegister.Rdi] = payloadAddress;
|
||||
return true;
|
||||
}
|
||||
|
||||
private ulong TryMapBootstrapStubRegion()
|
||||
{
|
||||
var stubData = new byte[(int)BootstrapRegionSize];
|
||||
stubData[0] = 0xCC;
|
||||
stubData[1] = 0xC3;
|
||||
|
||||
const ulong stride = 0x0100_0000UL;
|
||||
for (var i = 0; i < 16; i++)
|
||||
{
|
||||
var candidateBase = BootstrapStubBaseAddress - ((ulong)i * stride);
|
||||
try
|
||||
{
|
||||
_virtualMemory.Map(
|
||||
candidateBase,
|
||||
BootstrapRegionSize,
|
||||
fileOffset: 0,
|
||||
stubData,
|
||||
ProgramHeaderFlags.Read | ProgramHeaderFlags.Execute);
|
||||
return candidateBase;
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private ulong TryMapBootstrapPayloadRegion()
|
||||
{
|
||||
const ulong stride = 0x0100_0000UL;
|
||||
for (var i = 0; i < 16; i++)
|
||||
{
|
||||
var candidateBase = BootstrapPayloadBaseAddress - ((ulong)i * stride);
|
||||
try
|
||||
{
|
||||
_virtualMemory.Map(
|
||||
candidateBase,
|
||||
BootstrapRegionSize,
|
||||
fileOffset: 0,
|
||||
ReadOnlySpan<byte>.Empty,
|
||||
ProgramHeaderFlags.Read | ProgramHeaderFlags.Write);
|
||||
return candidateBase;
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private ulong TryMapDynlibFallbackStubRegion()
|
||||
{
|
||||
var stubData = new byte[(int)BootstrapRegionSize];
|
||||
stubData[0] = 0x31;
|
||||
stubData[1] = 0xC0;
|
||||
stubData[2] = 0xC3;
|
||||
|
||||
const ulong stride = 0x0100_0000UL;
|
||||
for (var i = 0; i < 16; i++)
|
||||
{
|
||||
var candidateBase = DynlibFallbackStubBaseAddress - ((ulong)i * stride);
|
||||
try
|
||||
{
|
||||
_virtualMemory.Map(
|
||||
candidateBase,
|
||||
BootstrapRegionSize,
|
||||
fileOffset: 0,
|
||||
stubData,
|
||||
ProgramHeaderFlags.Read | ProgramHeaderFlags.Execute);
|
||||
return candidateBase;
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private ulong TryMapReturnToHostStubRegion()
|
||||
{
|
||||
var stubData = new byte[(int)BootstrapRegionSize];
|
||||
stubData[0] = 0xF4;
|
||||
stubData[1] = 0xCC;
|
||||
|
||||
for (var i = 0; i < 16; i++)
|
||||
{
|
||||
var candidateBase = ReturnToHostStubBaseAddress - ((ulong)i * ReturnToHostStubStride);
|
||||
try
|
||||
{
|
||||
_virtualMemory.Map(
|
||||
candidateBase,
|
||||
BootstrapRegionSize,
|
||||
fileOffset: 0,
|
||||
stubData,
|
||||
ProgramHeaderFlags.Read | ProgramHeaderFlags.Execute);
|
||||
return candidateBase;
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private bool TryWriteUInt64(ulong address, ulong value)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[sizeof(ulong)];
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(buffer, value);
|
||||
return _virtualMemory.TryWrite(address, buffer);
|
||||
}
|
||||
|
||||
private bool TryWriteUInt32(ulong address, uint value)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[sizeof(uint)];
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(buffer, value);
|
||||
return _virtualMemory.TryWrite(address, buffer);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_nativeCpuBackend is IDisposable disposableBackend)
|
||||
{
|
||||
disposableBackend.Dispose();
|
||||
}
|
||||
|
||||
_nativeCpuBackend = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
namespace SharpEmu.Core.Cpu;
|
||||
|
||||
public enum CpuExecutionEngine
|
||||
{
|
||||
NativeOnly = 0,
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
namespace SharpEmu.Core.Cpu;
|
||||
|
||||
public readonly struct CpuExecutionOptions
|
||||
{
|
||||
public bool EnableDisasmDiagnostics { get; init; }
|
||||
|
||||
public CpuExecutionEngine CpuEngine { get; init; }
|
||||
|
||||
public bool StrictDynlibResolution { get; init; }
|
||||
|
||||
public int ImportTraceLimit { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
namespace SharpEmu.Core.Cpu;
|
||||
|
||||
public enum CpuExitReason
|
||||
{
|
||||
Exited = 0,
|
||||
|
||||
SentinelReturn = 1,
|
||||
|
||||
ReturnedToHost = 2,
|
||||
|
||||
Halted = 3,
|
||||
|
||||
BudgetExceeded = 4,
|
||||
|
||||
CpuTrap = 5,
|
||||
|
||||
UnhandledException = 6,
|
||||
|
||||
UnhandledSyscall = 7,
|
||||
|
||||
NativeBackendUnavailable = 8,
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
namespace SharpEmu.Core.Cpu;
|
||||
|
||||
public readonly struct CpuMemoryAccessFailure
|
||||
{
|
||||
public CpuMemoryAccessFailure(ulong address, int size, bool isWrite)
|
||||
{
|
||||
Address = address;
|
||||
Size = size;
|
||||
IsWrite = isWrite;
|
||||
}
|
||||
|
||||
public ulong Address { get; }
|
||||
|
||||
public int Size { get; }
|
||||
|
||||
public bool IsWrite { get; }
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
namespace SharpEmu.Core.Cpu;
|
||||
|
||||
public readonly struct CpuMemoryFaultInfo
|
||||
{
|
||||
public CpuMemoryFaultInfo(ulong instructionPointer, byte? opcode, CpuMemoryAccessFailure access)
|
||||
{
|
||||
InstructionPointer = instructionPointer;
|
||||
Opcode = opcode;
|
||||
Access = access;
|
||||
}
|
||||
|
||||
public ulong InstructionPointer { get; }
|
||||
|
||||
public byte? Opcode { get; }
|
||||
|
||||
public CpuMemoryAccessFailure Access { get; }
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
namespace SharpEmu.Core.Cpu;
|
||||
|
||||
public readonly struct CpuNotImplementedInfo
|
||||
{
|
||||
public CpuNotImplementedInfo(
|
||||
CpuNotImplementedSource source,
|
||||
ulong instructionPointer,
|
||||
string? nid,
|
||||
string? exportName,
|
||||
string? libraryName,
|
||||
string? detail)
|
||||
{
|
||||
Source = source;
|
||||
InstructionPointer = instructionPointer;
|
||||
Nid = nid;
|
||||
ExportName = exportName;
|
||||
LibraryName = libraryName;
|
||||
Detail = detail;
|
||||
}
|
||||
|
||||
public CpuNotImplementedSource Source { get; }
|
||||
|
||||
public ulong InstructionPointer { get; }
|
||||
|
||||
public string? Nid { get; }
|
||||
|
||||
public string? ExportName { get; }
|
||||
|
||||
public string? LibraryName { get; }
|
||||
|
||||
public string? Detail { get; }
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
namespace SharpEmu.Core.Cpu;
|
||||
|
||||
public enum CpuNotImplementedSource
|
||||
{
|
||||
Unknown = 0,
|
||||
|
||||
InstructionBudget = 1,
|
||||
|
||||
KernelDynlibDlsym = 2,
|
||||
|
||||
HleExport = 3,
|
||||
|
||||
NativeBackend = 4,
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using SharpEmu.HLE;
|
||||
|
||||
namespace SharpEmu.Core.Cpu;
|
||||
|
||||
public readonly struct CpuSessionSummary
|
||||
{
|
||||
public CpuSessionSummary(
|
||||
OrbisGen2Result result,
|
||||
CpuExitReason reason,
|
||||
int? exitCode,
|
||||
ulong lastGuestRip,
|
||||
ulong lastStubRip,
|
||||
int totalInstructions,
|
||||
int importsHit,
|
||||
int uniqueNidsHit)
|
||||
{
|
||||
Result = result;
|
||||
Reason = reason;
|
||||
ExitCode = exitCode;
|
||||
LastGuestRip = lastGuestRip;
|
||||
LastStubRip = lastStubRip;
|
||||
TotalInstructions = totalInstructions;
|
||||
ImportsHit = importsHit;
|
||||
UniqueNidsHit = uniqueNidsHit;
|
||||
}
|
||||
|
||||
public OrbisGen2Result Result { get; }
|
||||
|
||||
public CpuExitReason Reason { get; }
|
||||
|
||||
public int? ExitCode { get; }
|
||||
|
||||
public ulong LastGuestRip { get; }
|
||||
|
||||
public ulong LastStubRip { get; }
|
||||
|
||||
public int TotalInstructions { get; }
|
||||
|
||||
public int ImportsHit { get; }
|
||||
|
||||
public int UniqueNidsHit { get; }
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
namespace SharpEmu.Core.Cpu;
|
||||
|
||||
public readonly struct CpuTrapInfo
|
||||
{
|
||||
public CpuTrapInfo(ulong instructionPointer, byte opcode)
|
||||
{
|
||||
InstructionPointer = instructionPointer;
|
||||
Opcode = opcode;
|
||||
}
|
||||
|
||||
public ulong InstructionPointer { get; }
|
||||
|
||||
public byte Opcode { get; }
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using Iced.Intel;
|
||||
|
||||
namespace SharpEmu.Core.Cpu.Disasm;
|
||||
|
||||
public readonly struct DecodedInst
|
||||
{
|
||||
public DecodedInst(
|
||||
ulong rip,
|
||||
int length,
|
||||
string text,
|
||||
string mnemonic,
|
||||
FlowControl flowControl,
|
||||
ulong? nearBranchTarget,
|
||||
ulong? memoryAddress,
|
||||
byte[] bytes)
|
||||
{
|
||||
Rip = rip;
|
||||
Length = length;
|
||||
Text = text;
|
||||
Mnemonic = mnemonic;
|
||||
FlowControl = flowControl;
|
||||
NearBranchTarget = nearBranchTarget;
|
||||
MemoryAddress = memoryAddress;
|
||||
Bytes = bytes;
|
||||
}
|
||||
|
||||
public ulong Rip { get; }
|
||||
|
||||
public int Length { get; }
|
||||
|
||||
public string Text { get; }
|
||||
|
||||
public string Mnemonic { get; }
|
||||
|
||||
public FlowControl FlowControl { get; }
|
||||
|
||||
public ulong? NearBranchTarget { get; }
|
||||
|
||||
public ulong? MemoryAddress { get; }
|
||||
|
||||
public byte[] Bytes { get; }
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using Iced.Intel;
|
||||
using SharpEmu.Core.Memory;
|
||||
using SharpEmu.HLE;
|
||||
|
||||
namespace SharpEmu.Core.Cpu.Disasm;
|
||||
|
||||
public static class IcedDecoder
|
||||
{
|
||||
private const int MaxInstructionBytes = 15;
|
||||
|
||||
public static bool TryDecode(ulong rip, ReadOnlySpan<byte> codeBytes, out DecodedInst inst)
|
||||
{
|
||||
if (codeBytes.IsEmpty)
|
||||
{
|
||||
inst = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
var decodeLength = Math.Min(MaxInstructionBytes, codeBytes.Length);
|
||||
var decodeBytes = GC.AllocateUninitializedArray<byte>(decodeLength);
|
||||
codeBytes[..decodeLength].CopyTo(decodeBytes);
|
||||
|
||||
try
|
||||
{
|
||||
var decoder = Decoder.Create(64, new ByteArrayCodeReader(decodeBytes));
|
||||
decoder.IP = rip;
|
||||
decoder.Decode(out var instruction);
|
||||
if (instruction.Code == Code.INVALID || instruction.Length <= 0)
|
||||
{
|
||||
inst = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
var formatter = new IntelFormatter();
|
||||
var output = new StringOutput();
|
||||
formatter.Format(instruction, output);
|
||||
|
||||
var effectiveLength = Math.Min(instruction.Length, decodeBytes.Length);
|
||||
var effectiveBytes = new byte[effectiveLength];
|
||||
Array.Copy(decodeBytes, 0, effectiveBytes, 0, effectiveLength);
|
||||
|
||||
inst = new DecodedInst(
|
||||
rip,
|
||||
instruction.Length,
|
||||
output.ToString(),
|
||||
instruction.Mnemonic.ToString(),
|
||||
instruction.FlowControl,
|
||||
GetNearBranchTarget(in instruction),
|
||||
GetMemoryAddress(in instruction),
|
||||
effectiveBytes);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
inst = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool TryReadGuestBytes(ICpuMemory memory, ulong rip, int maxLen, out byte[] bytes)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(memory);
|
||||
var clampedLength = Math.Clamp(maxLen, 1, MaxInstructionBytes);
|
||||
var buffer = new byte[clampedLength];
|
||||
Span<byte> oneByte = stackalloc byte[1];
|
||||
var readCount = 0;
|
||||
for (var i = 0; i < clampedLength; i++)
|
||||
{
|
||||
if (!memory.TryRead(rip + (ulong)i, oneByte))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
buffer[readCount] = oneByte[0];
|
||||
readCount++;
|
||||
}
|
||||
|
||||
if (readCount == 0)
|
||||
{
|
||||
bytes = Array.Empty<byte>();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (readCount == clampedLength)
|
||||
{
|
||||
bytes = buffer;
|
||||
return true;
|
||||
}
|
||||
|
||||
bytes = new byte[readCount];
|
||||
Array.Copy(buffer, bytes, readCount);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool TryReadGuestBytes(IVirtualMemory memory, ulong rip, int maxLen, out byte[] bytes)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(memory);
|
||||
var clampedLength = Math.Clamp(maxLen, 1, MaxInstructionBytes);
|
||||
var buffer = new byte[clampedLength];
|
||||
Span<byte> oneByte = stackalloc byte[1];
|
||||
var readCount = 0;
|
||||
for (var i = 0; i < clampedLength; i++)
|
||||
{
|
||||
if (!memory.TryRead(rip + (ulong)i, oneByte))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
buffer[readCount] = oneByte[0];
|
||||
readCount++;
|
||||
}
|
||||
|
||||
if (readCount == 0)
|
||||
{
|
||||
bytes = Array.Empty<byte>();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (readCount == clampedLength)
|
||||
{
|
||||
bytes = buffer;
|
||||
return true;
|
||||
}
|
||||
|
||||
bytes = new byte[readCount];
|
||||
Array.Copy(buffer, bytes, readCount);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static string FormatBytes(ReadOnlySpan<byte> bytes)
|
||||
{
|
||||
if (bytes.IsEmpty)
|
||||
{
|
||||
return "??";
|
||||
}
|
||||
|
||||
var parts = new string[bytes.Length];
|
||||
for (var i = 0; i < bytes.Length; i++)
|
||||
{
|
||||
parts[i] = bytes[i].ToString("X2");
|
||||
}
|
||||
|
||||
return string.Join(' ', parts);
|
||||
}
|
||||
|
||||
private static ulong? GetNearBranchTarget(in Instruction instruction)
|
||||
{
|
||||
for (var opIndex = 0; opIndex < instruction.OpCount; opIndex++)
|
||||
{
|
||||
switch (instruction.GetOpKind(opIndex))
|
||||
{
|
||||
case OpKind.NearBranch16:
|
||||
return instruction.NearBranch16;
|
||||
case OpKind.NearBranch32:
|
||||
return instruction.NearBranch32;
|
||||
case OpKind.NearBranch64:
|
||||
return instruction.NearBranch64;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static ulong? GetMemoryAddress(in Instruction instruction)
|
||||
{
|
||||
var hasMemoryOperand = false;
|
||||
for (var opIndex = 0; opIndex < instruction.OpCount; opIndex++)
|
||||
{
|
||||
if (IsMemoryOpKind(instruction.GetOpKind(opIndex)))
|
||||
{
|
||||
hasMemoryOperand = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasMemoryOperand)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (instruction.IsIPRelativeMemoryOperand)
|
||||
{
|
||||
return instruction.IPRelativeMemoryAddress;
|
||||
}
|
||||
|
||||
if (instruction.MemoryBase == Register.None &&
|
||||
instruction.MemoryIndex == Register.None &&
|
||||
instruction.MemoryDisplacement64 != 0)
|
||||
{
|
||||
return instruction.MemoryDisplacement64;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static bool IsMemoryOpKind(OpKind opKind)
|
||||
{
|
||||
return opKind is
|
||||
OpKind.Memory or
|
||||
OpKind.MemorySegSI or
|
||||
OpKind.MemorySegESI or
|
||||
OpKind.MemorySegRSI or
|
||||
OpKind.MemorySegDI or
|
||||
OpKind.MemorySegEDI or
|
||||
OpKind.MemorySegRDI or
|
||||
OpKind.MemoryESDI or
|
||||
OpKind.MemoryESEDI or
|
||||
OpKind.MemoryESRDI;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using SharpEmu.HLE;
|
||||
|
||||
namespace SharpEmu.Core.Cpu;
|
||||
|
||||
public interface ICpuDispatcher
|
||||
{
|
||||
CpuTrapInfo? LastTrapInfo { get; }
|
||||
|
||||
CpuMemoryFaultInfo? LastMemoryFaultInfo { get; }
|
||||
|
||||
CpuControlTransferInfo? LastControlTransferInfo { get; }
|
||||
|
||||
CpuNotImplementedInfo? LastNotImplementedInfo { get; }
|
||||
|
||||
string? LastImportResolutionTrace { get; }
|
||||
|
||||
string? LastBasicBlockTrace { get; }
|
||||
|
||||
string? LastMilestoneLog { get; }
|
||||
|
||||
string? LastRecentInstructionWindow { get; }
|
||||
|
||||
string? LastRecentControlTransferTrace { get; }
|
||||
|
||||
CpuSessionSummary LastSessionSummary { get; }
|
||||
|
||||
OrbisGen2Result DispatchEntry(
|
||||
ulong entryPoint,
|
||||
Generation generation,
|
||||
IReadOnlyDictionary<ulong, string>? importStubs = null,
|
||||
IReadOnlyDictionary<string, ulong>? runtimeSymbols = null,
|
||||
string processImageName = "eboot.bin",
|
||||
CpuExecutionOptions executionOptions = default);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
namespace SharpEmu.Core.Cpu;
|
||||
|
||||
public interface ITrackedCpuMemory
|
||||
{
|
||||
CpuMemoryAccessFailure? LastFailure { get; }
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace SharpEmu.Core.Cpu.Native;
|
||||
|
||||
public sealed unsafe class CpuPatcher : IDisposable
|
||||
{
|
||||
private readonly nint _tlsBaseAddress;
|
||||
|
||||
public CpuPatcher(nint tlsBaseAddress)
|
||||
{
|
||||
_tlsBaseAddress = tlsBaseAddress;
|
||||
}
|
||||
|
||||
public bool TryPatchInstruction(nint address)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
internal unsafe class UnsafeCodeReader : Iced.Intel.CodeReader
|
||||
{
|
||||
private byte* _start;
|
||||
private byte* _current;
|
||||
private int _length;
|
||||
|
||||
public UnsafeCodeReader(byte* start, int length)
|
||||
{
|
||||
_start = start;
|
||||
_current = start;
|
||||
_length = length;
|
||||
}
|
||||
|
||||
public override int ReadByte()
|
||||
{
|
||||
if (_current >= _start + _length)
|
||||
return -1;
|
||||
return *_current++;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using SharpEmu.HLE;
|
||||
|
||||
namespace SharpEmu.Core.Cpu.Native;
|
||||
|
||||
public sealed partial class DirectExecutionBackend
|
||||
{
|
||||
|
||||
private void RecordRecentImportTrace(string traceLine)
|
||||
{
|
||||
_recentImportTrace[_recentImportTraceWriteIndex] = traceLine;
|
||||
_recentImportTraceWriteIndex = (_recentImportTraceWriteIndex + 1) % _recentImportTrace.Length;
|
||||
if (_recentImportTraceCount < _recentImportTrace.Length)
|
||||
{
|
||||
_recentImportTraceCount++;
|
||||
}
|
||||
}
|
||||
|
||||
private void DumpRecentImportTrace()
|
||||
{
|
||||
if (_recentImportTraceCount == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Console.Error.WriteLine($"[LOADER][INFO] Recent import calls ({_recentImportTraceCount}):");
|
||||
int num = (_recentImportTraceWriteIndex - _recentImportTraceCount + _recentImportTrace.Length) % _recentImportTrace.Length;
|
||||
for (int i = 0; i < _recentImportTraceCount; i++)
|
||||
{
|
||||
int num2 = (num + i) % _recentImportTrace.Length;
|
||||
string text = _recentImportTrace[num2];
|
||||
if (!string.IsNullOrEmpty(text))
|
||||
{
|
||||
Console.Error.WriteLine("[LOADER][INFO] " + text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe static List<ulong> ScanSuspiciousResolverPointers(ulong scanStart, ulong scanEnd)
|
||||
{
|
||||
if (scanEnd <= scanStart)
|
||||
{
|
||||
return new List<ulong>(0);
|
||||
}
|
||||
int num = 0;
|
||||
int num2 = 0;
|
||||
List<ulong> list = new List<ulong>(16);
|
||||
ulong num3 = scanStart;
|
||||
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;
|
||||
if (num4 <= num3)
|
||||
{
|
||||
break;
|
||||
}
|
||||
ulong value = Math.Max(num3, baseAddress);
|
||||
ulong num5 = Math.Min(num4, scanEnd);
|
||||
if (lpBuffer.State == 4096 && IsReadableProtection(lpBuffer.Protect) && !IsExecutableProtection(lpBuffer.Protect))
|
||||
{
|
||||
ulong num6 = AlignUp(value, 8uL);
|
||||
for (ulong num7 = num6; num7 + 8 <= num5; num7 += 8)
|
||||
{
|
||||
ulong value2 = *(ulong*)num7;
|
||||
if (IsUnresolvedSentinel(value2))
|
||||
{
|
||||
num++;
|
||||
list.Add(num7);
|
||||
if (num2 < 32)
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][INFO] Suspicious unresolved pointer: slot=0x{num7:X16} value=0x{value2:X16}");
|
||||
num2++;
|
||||
}
|
||||
if (num >= 16384)
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][WARNING] Suspicious unresolved pointer scan reached cap ({16384}); truncating.");
|
||||
return list;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
num3 = num5;
|
||||
}
|
||||
if (num != 0)
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][WARNING] Suspicious unresolved pointer hits: {num}");
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private void ProbeReturnRip(ulong returnRip, long dispatchIndex)
|
||||
{
|
||||
if (_cpuContext == null || returnRip == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Span<byte> destination = stackalloc byte[128];
|
||||
if (!_cpuContext.Memory.TryRead(returnRip, destination))
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] Import#{dispatchIndex} return-rip probe: unreadable @0x{returnRip:X16}");
|
||||
return;
|
||||
}
|
||||
string value = BitConverter.ToString(destination.ToArray()).Replace("-", " ");
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] Import#{dispatchIndex} return-rip bytes @0x{returnRip:X16}: {value}");
|
||||
if (destination[0] == byte.MaxValue && (destination[1] == 21 || destination[1] == 37))
|
||||
{
|
||||
int num = BitConverter.ToInt32(destination.Slice(2, 4));
|
||||
ulong num2 = returnRip + 6 + (ulong)num;
|
||||
if (_cpuContext.TryReadUInt64(num2, out var value2))
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] Import#{dispatchIndex} return-rip slot: [0x{num2:X16}] = 0x{value2:X16}");
|
||||
}
|
||||
}
|
||||
if (destination[0] == 72 && destination[1] == 139 && destination[2] == 5)
|
||||
{
|
||||
int num3 = BitConverter.ToInt32(destination.Slice(3, 4));
|
||||
ulong num4 = returnRip + 7 + (ulong)num3;
|
||||
if (_cpuContext.TryReadUInt64(num4, out var value3))
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] Import#{dispatchIndex} return-rip mov-slot: [0x{num4:X16}] = 0x{value3:X16}");
|
||||
}
|
||||
}
|
||||
for (int i = 0; i + 6 <= destination.Length; i++)
|
||||
{
|
||||
if (destination[i] == byte.MaxValue && (destination[i + 1] == 21 || destination[i + 1] == 37))
|
||||
{
|
||||
int num5 = BitConverter.ToInt32(destination.Slice(i + 2, 4));
|
||||
ulong num6 = returnRip + (ulong)i;
|
||||
ulong num7 = num6 + 6 + (ulong)num5;
|
||||
if (_cpuContext.TryReadUInt64(num7, out var value4))
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] Import#{dispatchIndex} near-indirect @{num6:X16}: slot=0x{num7:X16} val=0x{value4:X16}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsUnresolvedSentinel(ulong value)
|
||||
{
|
||||
return value == 65534 || value == 4294967294u || value == 18446744073709551614uL;
|
||||
}
|
||||
|
||||
private static bool IsPlausibleReturnAddress(ulong address)
|
||||
{
|
||||
return address >= 12884901888L && address < 17592186044416L && !IsUnresolvedSentinel(address);
|
||||
}
|
||||
|
||||
private static bool TryGetPlausibleReturnFromStack(ulong rsp, out ulong returnRip, out ulong nextRsp)
|
||||
{
|
||||
returnRip = 0uL;
|
||||
nextRsp = rsp;
|
||||
if (rsp <= 65536 || rsp >= 140737488355328L)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
ulong num = rsp & 0xFFFFFFFFFFFFFFF8uL;
|
||||
ulong num2 = ((num >= 8) ? (num - 8) : num);
|
||||
for (int i = 0; i < 24; i++)
|
||||
{
|
||||
ulong num3 = num2 + (ulong)((long)i * 8L);
|
||||
if (TryReadStackU64(num3, out var value) && IsLikelyReturnAddress(value))
|
||||
{
|
||||
returnRip = value;
|
||||
nextRsp = num3 + 8;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (ulong num4 = 1uL; num4 < 8; num4++)
|
||||
{
|
||||
for (int j = 0; j < 24; j++)
|
||||
{
|
||||
ulong num5 = rsp + num4 + (ulong)((long)j * 8L);
|
||||
if (TryReadStackU64(num5, out var value2) && IsLikelyReturnAddress(value2))
|
||||
{
|
||||
returnRip = value2;
|
||||
ulong num6 = num5 + 8;
|
||||
nextRsp = (num6 + 7) & 0xFFFFFFFFFFFFFFF8uL;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private unsafe static bool TryReadStackU64(ulong address, out ulong value)
|
||||
{
|
||||
value = 0uL;
|
||||
if (address <= 65536 || address >= 140737488355328L)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (VirtualQuery((void*)address, out var lpBuffer, (nuint)sizeof(MEMORY_BASIC_INFORMATION64)) == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
ulong num = lpBuffer.BaseAddress + lpBuffer.RegionSize;
|
||||
if (num < lpBuffer.BaseAddress || address > num - 8)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (lpBuffer.State != 4096 || !IsReadableProtection(lpBuffer.Protect))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
try
|
||||
{
|
||||
value = *(ulong*)address;
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsLikelyReturnAddress(ulong address)
|
||||
{
|
||||
if (!IsPlausibleReturnAddress(address))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return IsExecutableAddress(address);
|
||||
}
|
||||
|
||||
private unsafe static bool IsExecutableAddress(ulong address)
|
||||
{
|
||||
if (VirtualQuery((void*)address, out var lpBuffer, (nuint)sizeof(MEMORY_BASIC_INFORMATION64)) == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return lpBuffer.State == 4096 && IsExecutableProtection(lpBuffer.Protect);
|
||||
}
|
||||
|
||||
private static ulong AlignUp(ulong value, ulong alignment)
|
||||
{
|
||||
if (alignment == 0)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
ulong num = alignment - 1;
|
||||
return (value + num) & ~num;
|
||||
}
|
||||
|
||||
private static bool IsReadableProtection(uint protect)
|
||||
{
|
||||
if ((protect & 0x100) != 0 || (protect & 1) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return (protect & 0xEE) != 0;
|
||||
}
|
||||
|
||||
private static bool IsExecutableProtection(uint protect)
|
||||
{
|
||||
return (protect & 0xF0) != 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,740 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using SharpEmu.Core.Cpu.Disasm;
|
||||
using SharpEmu.HLE;
|
||||
|
||||
namespace SharpEmu.Core.Cpu.Native;
|
||||
|
||||
public sealed partial class DirectExecutionBackend
|
||||
{
|
||||
private unsafe void SetupExceptionHandler()
|
||||
{
|
||||
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_DISABLE_RAW_HANDLER"), "1", StringComparison.Ordinal))
|
||||
{
|
||||
_rawExceptionHandler = (nint)AddVectoredExceptionHandler(1u, RawVectoredHandlerPtrManaged);
|
||||
Console.Error.WriteLine($"[LOADER][INFO] Raw exception handler installed: 0x{_rawExceptionHandler:X16}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.Error.WriteLine("[LOADER][INFO] Raw exception handler disabled by SHARPEMU_DISABLE_RAW_HANDLER=1");
|
||||
}
|
||||
|
||||
_handlerDelegate = VectoredHandler;
|
||||
_handlerHandle = GCHandle.Alloc(_handlerDelegate);
|
||||
_exceptionHandler = (nint)AddVectoredExceptionHandler(1u, Marshal.GetFunctionPointerForDelegate(_handlerDelegate));
|
||||
Console.Error.WriteLine($"[LOADER][INFO] Exception handler installed: 0x{_exceptionHandler:X16}");
|
||||
|
||||
_unhandledFilterDelegate = UnhandledExceptionFilter;
|
||||
_unhandledFilterHandle = GCHandle.Alloc(_unhandledFilterDelegate);
|
||||
SetUnhandledExceptionFilter(Marshal.GetFunctionPointerForDelegate(_unhandledFilterDelegate));
|
||||
}
|
||||
|
||||
private unsafe int UnhandledExceptionFilter(void* exceptionInfo)
|
||||
{
|
||||
try
|
||||
{
|
||||
EXCEPTION_RECORD* exceptionRecord = ((EXCEPTION_POINTERS*)exceptionInfo)->ExceptionRecord;
|
||||
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}");
|
||||
Console.Error.WriteLine($"[LOADER][FATAL] RIP: 0x{rip:X16}");
|
||||
Console.Error.WriteLine($"[LOADER][FATAL] RSP: 0x{rsp:X16}");
|
||||
Console.Error.Flush();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private unsafe int VectoredHandler(void* exceptionInfo)
|
||||
{
|
||||
if (_vectoredHandlerDepth > 0)
|
||||
{
|
||||
Console.Error.WriteLine("[LOADER][TRACE] Nested VEH exception; passing through.");
|
||||
Console.Error.Flush();
|
||||
return 0;
|
||||
}
|
||||
|
||||
_vectoredHandlerDepth++;
|
||||
try
|
||||
{
|
||||
EXCEPTION_RECORD* exceptionRecord = ((EXCEPTION_POINTERS*)exceptionInfo)->ExceptionRecord;
|
||||
uint exceptionCode = exceptionRecord->ExceptionCode;
|
||||
uint exceptionFlags = exceptionRecord->ExceptionFlags;
|
||||
ulong exceptionAddress = (ulong)exceptionRecord->ExceptionAddress;
|
||||
void* contextRecord = ((EXCEPTION_POINTERS*)exceptionInfo)->ContextRecord;
|
||||
if (contextRecord == null)
|
||||
{
|
||||
Console.Error.WriteLine("[LOADER][FATAL] ContextRecord is null!");
|
||||
Console.Error.Flush();
|
||||
return 0;
|
||||
}
|
||||
|
||||
ulong rip = ReadCtxU64(contextRecord, 248);
|
||||
ulong rsp = ReadCtxU64(contextRecord, 152);
|
||||
|
||||
if (exceptionCode == 3221225477u && TryHandleLazyCommittedPage(exceptionRecord))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
switch (exceptionCode)
|
||||
{
|
||||
case 3221225477u:
|
||||
LogAccessViolationTrace(exceptionAddress, exceptionRecord);
|
||||
break;
|
||||
case 3221226505u:
|
||||
{
|
||||
ulong p0 = exceptionRecord->NumberParameters >= 1 ? (*exceptionRecord->ExceptionInformation) : 0;
|
||||
ulong p1 = exceptionRecord->NumberParameters >= 2 ? exceptionRecord->ExceptionInformation[1] : 0;
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] VEH_FASTFAIL code=0x{exceptionCode:X8} ex=0x{exceptionAddress:X16} rip=0x{rip:X16} rsp=0x{rsp:X16} p0=0x{p0:X16} p1=0x{p1:X16}");
|
||||
Console.Error.Flush();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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}");
|
||||
if (TryFormatNearestRuntimeSymbol(rip, out string symbol))
|
||||
{
|
||||
Console.Error.WriteLine("[LOADER][INFO] RIP symbol: " + symbol);
|
||||
}
|
||||
Console.Error.WriteLine($"[LOADER][INFO] RAX: 0x{rax:X16}");
|
||||
Console.Error.WriteLine($"[LOADER][INFO] RBX: 0x{rbx:X16}");
|
||||
Console.Error.WriteLine($"[LOADER][INFO] RCX: 0x{rcx:X16}");
|
||||
Console.Error.WriteLine($"[LOADER][INFO] RDX: 0x{rdx:X16}");
|
||||
Console.Error.WriteLine($"[LOADER][INFO] RSI: 0x{rsi:X16}");
|
||||
Console.Error.WriteLine($"[LOADER][INFO] RDI: 0x{rdi:X16}");
|
||||
Console.Error.WriteLine($"[LOADER][INFO] RBP: 0x{rbp:X16}");
|
||||
Console.Error.WriteLine($"[LOADER][INFO] RSP: 0x{rsp:X16}");
|
||||
Console.Error.WriteLine($"[LOADER][INFO] R8 : 0x{r8:X16}");
|
||||
Console.Error.WriteLine($"[LOADER][INFO] R9 : 0x{r9:X16}");
|
||||
Console.Error.WriteLine($"[LOADER][INFO] R10: 0x{r10:X16}");
|
||||
Console.Error.WriteLine($"[LOADER][INFO] R11: 0x{r11:X16}");
|
||||
Console.Error.WriteLine($"[LOADER][INFO] R12: 0x{r12:X16}");
|
||||
Console.Error.WriteLine($"[LOADER][INFO] R13: 0x{r13:X16}");
|
||||
Console.Error.WriteLine($"[LOADER][INFO] R14: 0x{r14:X16}");
|
||||
Console.Error.WriteLine($"[LOADER][INFO] R15: 0x{r15:X16}");
|
||||
Console.Error.WriteLine($"[LOADER][INFO] Flags: 0x{exceptionFlags:X8}");
|
||||
|
||||
ulong accessType = 0;
|
||||
ulong target = 0;
|
||||
if (exceptionCode == 3221225477u && exceptionRecord->NumberParameters >= 2)
|
||||
{
|
||||
accessType = *exceptionRecord->ExceptionInformation;
|
||||
target = exceptionRecord->ExceptionInformation[1];
|
||||
string accessText = accessType switch
|
||||
{
|
||||
0uL => "read",
|
||||
1uL => "write",
|
||||
8uL => "execute",
|
||||
_ => $"unknown({accessType})"
|
||||
};
|
||||
Console.Error.WriteLine("[LOADER][INFO] AV access: " + accessText);
|
||||
Console.Error.WriteLine($"[LOADER][INFO] AV target: 0x{target:X16}");
|
||||
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.State:X08} protect=0x{mbi.Protect:X08}");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Console.Error.WriteLine("[LOADER][INFO] Stack qwords (RSP..):");
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
ulong stackAddr = rsp + (ulong)(i * 8);
|
||||
ulong value = (ulong)Marshal.ReadInt64((nint)stackAddr);
|
||||
Console.Error.WriteLine($"[LOADER][INFO] [rsp+0x{i * 8:X2}] @0x{stackAddr:X16} = 0x{value:X16}");
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
Console.Error.WriteLine("[LOADER][WARNING] Could not read stack qwords.");
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
break;
|
||||
}
|
||||
ulong next = (ulong)Marshal.ReadInt64((nint)frame);
|
||||
ulong ret = (ulong)Marshal.ReadInt64((nint)(frame + 8));
|
||||
string extra = TryFormatNearestRuntimeSymbol(ret, out string retSym) ? $" [{retSym}]" : string.Empty;
|
||||
Console.Error.WriteLine($"[LOADER][INFO] frame#{i}: rbp=0x{frame:X16} ret=0x{ret:X16}{extra} next=0x{next:X16}");
|
||||
if (next <= frame)
|
||||
{
|
||||
break;
|
||||
}
|
||||
frame = next;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
Console.Error.WriteLine("[LOADER][WARNING] Could not walk RBP frame chain.");
|
||||
}
|
||||
|
||||
switch (exceptionCode)
|
||||
{
|
||||
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");
|
||||
Console.Error.WriteLine("[LOADER][ERROR] - Guest code accessed unmapped memory");
|
||||
Console.Error.WriteLine("[LOADER][ERROR] - Need to implement HLE for this NID");
|
||||
try
|
||||
{
|
||||
byte[] code = new byte[16];
|
||||
Marshal.Copy((nint)rip, code, 0, code.Length);
|
||||
Console.Error.WriteLine("[LOADER][INFO] Code at RIP: " + BitConverter.ToString(code).Replace("-", " "));
|
||||
if (code[0] == 100)
|
||||
{
|
||||
Console.Error.WriteLine("[LOADER][ERROR] Detected FS segment prefix - TLS access not patched!");
|
||||
}
|
||||
else if (code[0] == 101)
|
||||
{
|
||||
Console.Error.WriteLine("[LOADER][ERROR] Detected GS segment prefix - TLS access not patched!");
|
||||
}
|
||||
else if (code[0] == 197 || code[0] == 196)
|
||||
{
|
||||
Console.Error.WriteLine("[LOADER][INFO] Detected AVX instruction - check CPU support!");
|
||||
Console.Error.WriteLine($"[LOADER][INFO] RBP: 0x{rbp:X16} (mod 16 = {rbp % 16})");
|
||||
Console.Error.WriteLine($"[LOADER][INFO] RSP: 0x{rsp:X16} (mod 16 = {rsp % 16})");
|
||||
}
|
||||
if (rip > 16)
|
||||
{
|
||||
byte[] before = new byte[16];
|
||||
Marshal.Copy((nint)(rip - 16), before, 0, before.Length);
|
||||
Console.Error.WriteLine("[LOADER][INFO] Code before RIP: " + BitConverter.ToString(before).Replace("-", " "));
|
||||
}
|
||||
if (rip > 32)
|
||||
{
|
||||
byte[] window = new byte[64];
|
||||
Marshal.Copy((nint)(rip - 32), window, 0, window.Length);
|
||||
Console.Error.WriteLine("[LOADER][INFO] Code window [RIP-0x20..]: " + BitConverter.ToString(window).Replace("-", " "));
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
Console.Error.WriteLine("[LOADER][ERROR] Could not read code at RIP");
|
||||
}
|
||||
DumpRecentImportTrace();
|
||||
break;
|
||||
case 2147483651u:
|
||||
Console.Error.WriteLine("[LOADER][WARNING] Type: Breakpoint (int3)");
|
||||
Console.Error.WriteLine("[LOADER][WARNING] Unexpected breakpoint in direct-bridge mode");
|
||||
break;
|
||||
case 3221225501u:
|
||||
Console.Error.WriteLine("[LOADER][INFO] Type: Illegal Instruction");
|
||||
break;
|
||||
}
|
||||
|
||||
Console.Error.WriteLine("[LOADER][INFO] =========================================");
|
||||
Console.Error.Flush();
|
||||
return 0;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_vectoredHandlerDepth--;
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe void LogAccessViolationTrace(ulong exceptionAddress, EXCEPTION_RECORD* exceptionRecord)
|
||||
{
|
||||
ulong accessType = exceptionRecord->NumberParameters >= 1 ? (*exceptionRecord->ExceptionInformation) : 0;
|
||||
ulong target = exceptionRecord->NumberParameters >= 2 ? exceptionRecord->ExceptionInformation[1] : 0;
|
||||
if (_lastAvTraceRip == exceptionAddress && _lastAvTraceType == accessType && _lastAvTraceTarget == target)
|
||||
{
|
||||
_lastAvTraceRepeatCount++;
|
||||
if (_lastAvTraceRepeatCount > 4 && _lastAvTraceRepeatCount % 128 != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] VEH_AV repeat#{_lastAvTraceRepeatCount} at 0x{exceptionAddress:X16} type={accessType} target=0x{target:X16}");
|
||||
Console.Error.Flush();
|
||||
return;
|
||||
}
|
||||
|
||||
_lastAvTraceRip = exceptionAddress;
|
||||
_lastAvTraceType = accessType;
|
||||
_lastAvTraceTarget = target;
|
||||
_lastAvTraceRepeatCount = 1;
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] VEH_AV first-chance at 0x{exceptionAddress:X16} type={accessType} target=0x{target:X16}");
|
||||
Console.Error.Flush();
|
||||
}
|
||||
|
||||
private void DumpGuestInstructionStream(string name, ulong startRip, int maxInstructions)
|
||||
{
|
||||
if (_cpuContext == null || startRip < 0x10000 || maxInstructions <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Console.Error.WriteLine($"[LOADER][INFO] {name} disasm @0x{startRip:X16}:");
|
||||
ulong rip = startRip;
|
||||
for (int i = 0; i < maxInstructions; i++)
|
||||
{
|
||||
if (!IcedDecoder.TryReadGuestBytes(_cpuContext.Memory, rip, maxLen: 15, out var bytes) ||
|
||||
!IcedDecoder.TryDecode(rip, bytes, out var instruction))
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][INFO] 0x{rip:X16}: <decode-failed>");
|
||||
break;
|
||||
}
|
||||
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][INFO] 0x{instruction.Rip:X16}: {instruction.Text} bytes={IcedDecoder.FormatBytes(instruction.Bytes)}");
|
||||
rip += (ulong)instruction.Length;
|
||||
}
|
||||
}
|
||||
|
||||
private void DumpUnresolvedSentinelWindow(string name, ulong baseAddress, int size)
|
||||
{
|
||||
if (baseAddress < 0x10000 || size <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ulong scanStart = baseAddress;
|
||||
ulong scanEnd = baseAddress + (ulong)size;
|
||||
List<ulong> hits = ScanSuspiciousResolverPointers(scanStart, scanEnd);
|
||||
if (hits.Count == 0)
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][INFO] {name} unresolved scan: none");
|
||||
return;
|
||||
}
|
||||
|
||||
Console.Error.WriteLine($"[LOADER][INFO] {name} unresolved scan hits: {hits.Count}");
|
||||
for (int i = 0; i < hits.Count && i < 8; i++)
|
||||
{
|
||||
ulong slotAddress = hits[i];
|
||||
if (TryReadQword(slotAddress, out var value))
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][INFO] hit#{i}: slot=0x{slotAddress:X16} value=0x{value:X16}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DumpSentinelPatternWindow(string name, ulong baseAddress, int size)
|
||||
{
|
||||
if (_cpuContext == null || baseAddress < 0x10000 || size <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] buffer = new byte[size];
|
||||
if (!_cpuContext.Memory.TryRead(baseAddress, buffer))
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][INFO] {name} sentinel-pattern scan: unreadable");
|
||||
return;
|
||||
}
|
||||
|
||||
List<string> hits = new();
|
||||
for (int offset = 0; offset + 2 <= buffer.Length; offset++)
|
||||
{
|
||||
if (BinaryPrimitives.ReadUInt16LittleEndian(buffer.AsSpan(offset, 2)) == 0xFFFE)
|
||||
{
|
||||
hits.Add($"+0x{offset:X}:u16");
|
||||
}
|
||||
|
||||
if (offset + 4 <= buffer.Length &&
|
||||
BinaryPrimitives.ReadUInt32LittleEndian(buffer.AsSpan(offset, 4)) == 0xFFFFFFFEu)
|
||||
{
|
||||
hits.Add($"+0x{offset:X}:u32");
|
||||
}
|
||||
|
||||
if (offset + 8 <= buffer.Length &&
|
||||
BinaryPrimitives.ReadUInt64LittleEndian(buffer.AsSpan(offset, 8)) == 0xFFFFFFFFFFFFFFFEuL)
|
||||
{
|
||||
hits.Add($"+0x{offset:X}:u64");
|
||||
}
|
||||
}
|
||||
|
||||
if (hits.Count == 0)
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][INFO] {name} sentinel-pattern scan: none");
|
||||
return;
|
||||
}
|
||||
|
||||
Console.Error.WriteLine($"[LOADER][INFO] {name} sentinel-pattern hits: {string.Join(", ", hits.GetRange(0, Math.Min(hits.Count, 12)))}");
|
||||
}
|
||||
|
||||
private void DumpReturnTargetCandidates(ulong rsp)
|
||||
{
|
||||
if (rsp < 0x10000)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ulong start = rsp >= 0x10 ? rsp - 0x10 : rsp;
|
||||
Console.Error.WriteLine($"[LOADER][INFO] Return-target candidates near RSP=0x{rsp:X16}:");
|
||||
for (int offset = 0; offset <= 0x20; offset++)
|
||||
{
|
||||
ulong address = start + (ulong)offset;
|
||||
try
|
||||
{
|
||||
ulong value = (ulong)Marshal.ReadInt64((nint)address);
|
||||
Console.Error.WriteLine($"[LOADER][INFO] [0x{address:X16}] -> 0x{value:X16}");
|
||||
}
|
||||
catch
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][INFO] [0x{address:X16}] -> <unreadable>");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DumpObjectFieldTargets(string name, ulong objectAddress, int[] offsets, int windowSize)
|
||||
{
|
||||
if (objectAddress < 0x10000 || offsets.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (int offset in offsets)
|
||||
{
|
||||
ulong slotAddress = objectAddress + (ulong)offset;
|
||||
if (!TryReadQword(slotAddress, out var target) || target < 0x10000)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Console.Error.WriteLine($"[LOADER][INFO] {name}+0x{offset:X2} target = {FormatPointerWithNearestSymbol(target)}");
|
||||
DumpPointerWindow($"{name}+0x{offset:X2}", target, windowSize);
|
||||
DumpUnresolvedSentinelWindow($"{name}+0x{offset:X2}", target, 0x80);
|
||||
}
|
||||
}
|
||||
|
||||
private void DumpSuspiciousGlobalSlots()
|
||||
{
|
||||
DumpAbsoluteSlot("callback_slot[0x80293BD08]", 0x000000080293BD08uL);
|
||||
DumpAbsoluteSlot("callback_arg[0x8030FBBE8]", 0x00000008030FBBE8uL);
|
||||
DumpAbsoluteSlot("tsc_freq_global[0x8030FD590]", 0x00000008030FD590uL);
|
||||
DumpAbsoluteSlot("tsc_base_global[0x8030FD598]", 0x00000008030FD598uL);
|
||||
DumpAbsoluteSlot("plt_got[0x8028F6100]", 0x00000008028F6100uL);
|
||||
DumpAbsoluteSlot("plt_got[0x8028F6158]", 0x00000008028F6158uL);
|
||||
DumpAbsoluteSlot("plt_got[0x8028F6160]", 0x00000008028F6160uL);
|
||||
DumpAbsoluteSlot("plt_got[0x8028F64C0]", 0x00000008028F64C0uL);
|
||||
DumpAbsoluteSlot("plt_got[0x8028F64C8]", 0x00000008028F64C8uL);
|
||||
DumpAbsoluteSlot("plt_got[0x8028F6590]", 0x00000008028F6590uL);
|
||||
DumpAbsoluteSlot("plt_got[0x8028F6708]", 0x00000008028F6708uL);
|
||||
DumpUnresolvedSentinelWindow("PLT-GOT", 0x00000008028F6100uL, 0x700);
|
||||
}
|
||||
|
||||
private void DumpAbsoluteSlot(string name, ulong slotAddress)
|
||||
{
|
||||
if (!TryReadQword(slotAddress, out var value))
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][INFO] {name} @0x{slotAddress:X16} = <unreadable>");
|
||||
return;
|
||||
}
|
||||
|
||||
Console.Error.WriteLine($"[LOADER][INFO] {name} @0x{slotAddress:X16} = {FormatPointerWithNearestSymbol(value)}");
|
||||
}
|
||||
private void DumpPointerWindow(string name, ulong baseAddress, int size)
|
||||
{
|
||||
if (baseAddress < 0x10000 || size <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Console.Error.WriteLine($"[LOADER][INFO] {name} window @0x{baseAddress:X16}:");
|
||||
for (int offset = 0; offset < size; offset += 8)
|
||||
{
|
||||
ulong slotAddress = baseAddress + (ulong)offset;
|
||||
if (!TryReadQword(slotAddress, out var value))
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][INFO] +0x{offset:X2}: <unreadable>");
|
||||
break;
|
||||
}
|
||||
|
||||
Console.Error.WriteLine($"[LOADER][INFO] +0x{offset:X2}: {FormatPointerWithNearestSymbol(value)}");
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe bool TryReadQword(ulong address, out ulong value)
|
||||
{
|
||||
value = 0;
|
||||
if (address < 0x10000)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (VirtualQuery((void*)address, out var mbi, (nuint)sizeof(MEMORY_BASIC_INFORMATION64)) == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ulong regionEnd = mbi.BaseAddress + mbi.RegionSize;
|
||||
if (mbi.State != MEM_COMMIT || !IsReadableProtection(mbi.Protect) || regionEnd <= address || address > regionEnd - 8)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
value = (ulong)Marshal.ReadInt64((nint)address);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
value = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private string FormatPointerWithNearestSymbol(ulong value)
|
||||
{
|
||||
string text = $"0x{value:X16}";
|
||||
if (TryFormatNearestRuntimeSymbol(value, out string symbol))
|
||||
{
|
||||
text += $" [{symbol}]";
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
private void InitializeRuntimeSymbolIndex(IReadOnlyDictionary<string, ulong> runtimeSymbols)
|
||||
{
|
||||
_runtimeSymbolsByName.Clear();
|
||||
if (runtimeSymbols.Count == 0)
|
||||
{
|
||||
_runtimeSymbolsByAddress = Array.Empty<KeyValuePair<string, ulong>>();
|
||||
return;
|
||||
}
|
||||
|
||||
List<KeyValuePair<string, ulong>> list = new(runtimeSymbols.Count);
|
||||
foreach (KeyValuePair<string, ulong> runtimeSymbol in runtimeSymbols)
|
||||
{
|
||||
if (runtimeSymbol.Value != 0L && !string.IsNullOrWhiteSpace(runtimeSymbol.Key))
|
||||
{
|
||||
list.Add(runtimeSymbol);
|
||||
_runtimeSymbolsByName[runtimeSymbol.Key] = runtimeSymbol.Value;
|
||||
}
|
||||
}
|
||||
|
||||
list.Sort((a, b) => a.Value.CompareTo(b.Value));
|
||||
_runtimeSymbolsByAddress = list.ToArray();
|
||||
}
|
||||
|
||||
private bool TryFormatNearestRuntimeSymbol(ulong address, out string text)
|
||||
{
|
||||
text = string.Empty;
|
||||
KeyValuePair<string, ulong>[] runtimeSymbolsByAddress = _runtimeSymbolsByAddress;
|
||||
if (runtimeSymbolsByAddress.Length == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int low = 0;
|
||||
int high = runtimeSymbolsByAddress.Length - 1;
|
||||
int best = -1;
|
||||
while (low <= high)
|
||||
{
|
||||
int mid = low + ((high - low) >> 1);
|
||||
ulong value = runtimeSymbolsByAddress[mid].Value;
|
||||
if (value <= address)
|
||||
{
|
||||
best = mid;
|
||||
low = mid + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
high = mid - 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (best < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
KeyValuePair<string, ulong> symbol = runtimeSymbolsByAddress[best];
|
||||
ulong delta = address - symbol.Value;
|
||||
text = delta == 0
|
||||
? $"{symbol.Key} (0x{symbol.Value:X16})"
|
||||
: $"{symbol.Key}+0x{delta:X} (0x{symbol.Value:X16})";
|
||||
return true;
|
||||
}
|
||||
|
||||
private unsafe static bool TryHandleLazyCommittedPage(EXCEPTION_RECORD* exceptionRecord)
|
||||
{
|
||||
if (exceptionRecord->NumberParameters < 2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ulong accessType = *exceptionRecord->ExceptionInformation;
|
||||
ulong faultAddress = exceptionRecord->ExceptionInformation[1];
|
||||
if (accessType == 8 && faultAddress < 4294967296L)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (faultAddress < 65536 || faultAddress >= 140737488355328L)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (VirtualQuery((void*)faultAddress, out var mbi, (nuint)sizeof(MEMORY_BASIC_INFORMATION64)) == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ulong pageBase = faultAddress & 0xFFFFFFFFFFFFF000uL;
|
||||
uint commitProtect = ResolveLazyCommitProtection(accessType, mbi.AllocationProtect);
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] lazy-query: fault=0x{faultAddress: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}");
|
||||
|
||||
bool committed = false;
|
||||
ulong committedBase = 0;
|
||||
ulong committedSize = 0;
|
||||
|
||||
if (mbi.State == 65536)
|
||||
{
|
||||
ulong largeBase = faultAddress & 0xFFFFFFFFFFE00000uL;
|
||||
if (TryReserveThenCommit(largeBase, 2097152uL, largeBase, 2097152uL, commitProtect))
|
||||
{
|
||||
committed = true;
|
||||
committedBase = largeBase;
|
||||
committedSize = 2097152uL;
|
||||
}
|
||||
else
|
||||
{
|
||||
ulong region64kBase = faultAddress & 0xFFFFFFFFFFFF0000uL;
|
||||
if (TryReserveThenCommit(region64kBase, 65536uL, region64kBase, 65536uL, commitProtect))
|
||||
{
|
||||
committed = true;
|
||||
committedBase = region64kBase;
|
||||
committedSize = 65536uL;
|
||||
}
|
||||
else if (TryReserveThenCommit(pageBase, 4096uL, pageBase, 4096uL, commitProtect))
|
||||
{
|
||||
committed = true;
|
||||
committedBase = pageBase;
|
||||
committedSize = 4096uL;
|
||||
}
|
||||
}
|
||||
|
||||
if (!committed)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
TryCommitRange(pageBase + 4096, 4096uL, commitProtect);
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] lazy-reserve-commit: addr=0x{committedBase:X16} size=0x{committedSize:X16} access={accessType} protect=0x{commitProtect:X8}");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (mbi.State != 8192)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ulong largeCommitBase = faultAddress & 0xFFFFFFFFFFE00000uL;
|
||||
if (TryCommitRange(largeCommitBase, 2097152uL, commitProtect))
|
||||
{
|
||||
committed = true;
|
||||
committedBase = largeCommitBase;
|
||||
committedSize = 2097152uL;
|
||||
}
|
||||
else
|
||||
{
|
||||
ulong region64kBase = faultAddress & 0xFFFFFFFFFFFF0000uL;
|
||||
if (TryCommitRange(region64kBase, 65536uL, commitProtect))
|
||||
{
|
||||
committed = true;
|
||||
committedBase = region64kBase;
|
||||
committedSize = 65536uL;
|
||||
}
|
||||
else if (TryCommitRange(pageBase, 8192uL, commitProtect))
|
||||
{
|
||||
committed = true;
|
||||
committedBase = pageBase;
|
||||
committedSize = 8192uL;
|
||||
}
|
||||
else if (TryCommitRange(pageBase, 4096uL, commitProtect))
|
||||
{
|
||||
committed = true;
|
||||
committedBase = pageBase;
|
||||
committedSize = 4096uL;
|
||||
}
|
||||
}
|
||||
|
||||
if (!committed)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
TryCommitRange(pageBase + 4096, 4096uL, commitProtect);
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] lazy-commit: addr=0x{committedBase:X16} size=0x{committedSize:X16} access={accessType} protect=0x{commitProtect:X8}");
|
||||
return true;
|
||||
|
||||
static unsafe bool TryCommitRange(ulong baseAddress, ulong length, uint protection)
|
||||
{
|
||||
if (length == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return VirtualAlloc((void*)baseAddress, (nuint)length, 4096u, protection) != null;
|
||||
}
|
||||
|
||||
static unsafe bool TryReserveRange(ulong baseAddress, ulong length)
|
||||
{
|
||||
if (length == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return VirtualAlloc((void*)baseAddress, (nuint)length, 8192u, 4u) != null;
|
||||
}
|
||||
|
||||
static bool TryReserveThenCommit(ulong reserveAddress, ulong reserveSize, ulong commitAddress, ulong commitSize, uint protection)
|
||||
{
|
||||
if (!TryReserveRange(reserveAddress, reserveSize))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return TryCommitRange(commitAddress, commitSize, protection);
|
||||
}
|
||||
}
|
||||
|
||||
private static uint ResolveLazyCommitProtection(ulong accessType, uint allocationProtect)
|
||||
{
|
||||
if (accessType == 8 || (allocationProtect & 0xF0) != 0)
|
||||
{
|
||||
return 64u;
|
||||
}
|
||||
return 4u;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,760 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using SharpEmu.Core.Cpu;
|
||||
using SharpEmu.HLE;
|
||||
|
||||
namespace SharpEmu.Core.Cpu.Native;
|
||||
|
||||
public sealed partial class DirectExecutionBackend
|
||||
{
|
||||
private static ulong ImportDispatchGatewayManaged(nint backendHandle, int importIndex, nint argPackPtr)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!(GCHandle.FromIntPtr(backendHandle).Target is DirectExecutionBackend directExecutionBackend))
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][ERROR] ImportDispatchGatewayManaged: invalid backend handle 0x{backendHandle:X16}");
|
||||
return 18446744071562199042uL;
|
||||
}
|
||||
|
||||
return directExecutionBackend.DispatchImport(importIndex, argPackPtr);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][ERROR] ImportDispatchGatewayManaged exception: {ex.GetType().Name}: {ex.Message}");
|
||||
return 18446744071562199298uL;
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe static int RawVectoredHandlerManaged(void* exceptionInfo)
|
||||
{
|
||||
return TryRecoverUnresolvedSentinel(exceptionInfo);
|
||||
}
|
||||
|
||||
private unsafe static int RawUnhandledFilterManaged(void* exceptionInfo)
|
||||
{
|
||||
return TryRecoverUnresolvedSentinel(exceptionInfo);
|
||||
}
|
||||
|
||||
private unsafe static int TryRecoverUnresolvedSentinel(void* exceptionInfo)
|
||||
{
|
||||
EXCEPTION_RECORD* exceptionRecord = ((EXCEPTION_POINTERS*)exceptionInfo)->ExceptionRecord;
|
||||
if (exceptionRecord->ExceptionCode != 3221225477u)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
void* contextRecord = ((EXCEPTION_POINTERS*)exceptionInfo)->ContextRecord;
|
||||
ulong value = ReadCtxU64(contextRecord, 248);
|
||||
ulong value2 = (ulong)exceptionRecord->ExceptionAddress;
|
||||
if (!IsUnresolvedSentinel(value) && !IsUnresolvedSentinel(value2))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
ulong rsp = ReadCtxU64(contextRecord, 152);
|
||||
WriteCtxU64(contextRecord, 120, 0uL);
|
||||
if (TryGetPlausibleReturnFromStack(rsp, out var returnRip, out var nextRsp))
|
||||
{
|
||||
WriteCtxU64(contextRecord, 152, nextRsp);
|
||||
WriteCtxU64(contextRecord, 248, returnRip);
|
||||
Interlocked.Increment(ref _rawSentinelRecoveries);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private unsafe ulong DispatchImport(int importIndex, nint argPackPtr)
|
||||
{
|
||||
long num = Interlocked.Increment(ref _importDispatchCount);
|
||||
MarkExecutionProgress();
|
||||
if (_cpuContext == null)
|
||||
{
|
||||
LastError = "Import dispatch called without active CPU context";
|
||||
return 18446744071562199298uL;
|
||||
}
|
||||
if ((uint)importIndex >= (uint)_importEntries.Length)
|
||||
{
|
||||
LastError = $"Import dispatch index out of range: {importIndex}";
|
||||
return 18446744071562199042uL;
|
||||
}
|
||||
ImportStubEntry importStubEntry = _importEntries[importIndex];
|
||||
int num2 = Volatile.Read(in _rawSentinelRecoveries);
|
||||
if (num2 != _lastReportedRawSentinelRecoveries)
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] Raw sentinel recoveries: {num2} (last import index={importIndex})");
|
||||
_lastReportedRawSentinelRecoveries = num2;
|
||||
}
|
||||
_cpuContext.Rip = importStubEntry.Address;
|
||||
_cpuContext[CpuRegister.Rdi] = *(ulong*)argPackPtr;
|
||||
_cpuContext[CpuRegister.Rsi] = *(ulong*)(argPackPtr + 8);
|
||||
_cpuContext[CpuRegister.Rdx] = *(ulong*)(argPackPtr + 16);
|
||||
_cpuContext[CpuRegister.Rcx] = *(ulong*)(argPackPtr + 24);
|
||||
_cpuContext[CpuRegister.R8] = *(ulong*)(argPackPtr + 32);
|
||||
_cpuContext[CpuRegister.R9] = *(ulong*)(argPackPtr + 40);
|
||||
_cpuContext[CpuRegister.Rbx] = *(ulong*)(argPackPtr + 48);
|
||||
_cpuContext[CpuRegister.Rbp] = *(ulong*)(argPackPtr + 56);
|
||||
_cpuContext[CpuRegister.R12] = *(ulong*)(argPackPtr + 64);
|
||||
_cpuContext[CpuRegister.R13] = *(ulong*)(argPackPtr + 72);
|
||||
_cpuContext[CpuRegister.R14] = *(ulong*)(argPackPtr + 80);
|
||||
_cpuContext[CpuRegister.R15] = *(ulong*)(argPackPtr + 88);
|
||||
_cpuContext[CpuRegister.Rsp] = (ulong)argPackPtr + 96uL;
|
||||
ulong value = _cpuContext[CpuRegister.Rdi];
|
||||
ulong value2 = _cpuContext[CpuRegister.Rsi];
|
||||
ulong num3 = _cpuContext[CpuRegister.Rdx];
|
||||
ulong num4 = _cpuContext[CpuRegister.Rcx];
|
||||
ulong num5 = _cpuContext[CpuRegister.R8];
|
||||
ulong num6 = _cpuContext[CpuRegister.R9];
|
||||
ulong value3 = _cpuContext[CpuRegister.Rbx];
|
||||
ulong value4 = _cpuContext[CpuRegister.Rbp];
|
||||
ulong value5 = _cpuContext[CpuRegister.R12];
|
||||
ulong value6 = _cpuContext[CpuRegister.R13];
|
||||
ulong value7 = _cpuContext[CpuRegister.R14];
|
||||
ulong value8 = _cpuContext[CpuRegister.R15];
|
||||
ulong num7 = *(ulong*)(argPackPtr + 96);
|
||||
if (!IsLikelyReturnAddress(num7))
|
||||
{
|
||||
for (int i = 1; i <= 4; i++)
|
||||
{
|
||||
ulong num8 = *(ulong*)(argPackPtr + 96 + i * 8);
|
||||
if (IsLikelyReturnAddress(num8))
|
||||
{
|
||||
*(ulong*)(argPackPtr + 96) = num8;
|
||||
num7 = num8;
|
||||
Console.Error.WriteLine($"[LOADER][WARNING] Import#{num}: corrected suspicious return RIP using stack slot +0x{i * 8:X} -> 0x{num7:X16}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
TrackDistinctImportNid(importStubEntry.Nid);
|
||||
TrackStrlenPrelude(importStubEntry.Nid, num, num7);
|
||||
bool logBootstrap = string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_BOOTSTRAP"), "1", StringComparison.Ordinal);
|
||||
if (logBootstrap && string.Equals(importStubEntry.Nid, RuntimeStubNids.BootstrapBridge, StringComparison.Ordinal))
|
||||
{
|
||||
string symbolText = "<unreadable>";
|
||||
if (TryReadAsciiZ(value2, 256, out var sym))
|
||||
{
|
||||
symbolText = sym;
|
||||
}
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][TRACE] bootstrap_call#{num}: op=0x{value:X16} sym_ptr=0x{value2:X16} sym='{symbolText}' out_ptr=0x{num3:X16} ret=0x{num7:X16}");
|
||||
}
|
||||
if (!_forcedGuestExit && ShouldForceGuestExitOnImportLoop(importStubEntry.Nid, num7, num, value, value2) && TryForceGuestExitToHostStub(argPackPtr, num, num7, importStubEntry.Nid))
|
||||
{
|
||||
_cpuContext[CpuRegister.Rax] = 1uL;
|
||||
return 1uL;
|
||||
}
|
||||
bool flag0 = ShouldSuppressStrlenTrace(importStubEntry.Nid);
|
||||
bool flag = num7 >= 2156221920u && num7 <= 2156225024u;
|
||||
bool flag2 = num7 >= 2156351360u && num7 <= 2156352080u;
|
||||
bool flag3 = num >= 1020 && num <= 1040;
|
||||
if (!flag0 && (num <= 128 || (num >= 240 && num <= 400) || (num >= 900 && num <= 1300) || num % 100000 == 0L || (importStubEntry.Nid == "tsvEmnenz48" && (num <= 256 || num % 1000 == 0L)) || (importStubEntry.Nid == "rTXw65xmLIA" && (num <= 256 || num % 128 == 0)) || flag || flag2 || flag3))
|
||||
{
|
||||
if (_moduleManager.TryGetExport(importStubEntry.Nid, out ExportedFunction export))
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] Import#{num}: {export.LibraryName}:{export.Name} ({importStubEntry.Nid})");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] Import#{num}: {importStubEntry.Nid}");
|
||||
}
|
||||
}
|
||||
if (!flag0)
|
||||
{
|
||||
RecordRecentImportTrace($"#{num} nid={importStubEntry.Nid} ret=0x{num7:X16} rdi=0x{_cpuContext[CpuRegister.Rdi]:X16} rsi=0x{_cpuContext[CpuRegister.Rsi]:X16} rdx=0x{_cpuContext[CpuRegister.Rdx]:X16}");
|
||||
}
|
||||
if (importStubEntry.Nid == "8zTFvBIAIN8" && num <= 256)
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] memset#{num}: dst=0x{_cpuContext[CpuRegister.Rdi]:X16} val=0x{_cpuContext[CpuRegister.Rsi] & 0xFF:X2} len=0x{_cpuContext[CpuRegister.Rdx]:X16} ret=0x{num7:X16}");
|
||||
}
|
||||
if (importStubEntry.Nid == "tsvEmnenz48" && num <= 64)
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] __cxa_atexit#{num}: func=0x{_cpuContext[CpuRegister.Rdi]:X16} arg=0x{_cpuContext[CpuRegister.Rsi]:X16} dso=0x{_cpuContext[CpuRegister.Rdx]:X16} ret=0x{num7:X16}");
|
||||
}
|
||||
if (importStubEntry.Nid == "bzQExy189ZI" || importStubEntry.Nid == "8G2LB+A3rzg")
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] {importStubEntry.Nid}#{num}: rdi=0x{_cpuContext[CpuRegister.Rdi]:X16} rsi=0x{_cpuContext[CpuRegister.Rsi]:X16} rdx=0x{_cpuContext[CpuRegister.Rdx]:X16} ret=0x{num7:X16}");
|
||||
}
|
||||
if (flag || flag2 || flag3)
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] ImportCtx#{num}: nid={importStubEntry.Nid} ret=0x{num7:X16} rdi=0x{_cpuContext[CpuRegister.Rdi]:X16} rsi=0x{_cpuContext[CpuRegister.Rsi]:X16} rdx=0x{_cpuContext[CpuRegister.Rdx]:X16} rcx=0x{_cpuContext[CpuRegister.Rcx]:X16}");
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] ImportNV#{num}: rbx=0x{value3:X16} rbp=0x{value4:X16} r12=0x{value5:X16} r13=0x{value6:X16} r14=0x{value7:X16} r15=0x{value8:X16}");
|
||||
if (flag3)
|
||||
{
|
||||
ulong num9 = _cpuContext[CpuRegister.Rsp];
|
||||
if (_cpuContext.TryReadUInt64(num9, out var value9) && _cpuContext.TryReadUInt64(num9 + 8, out var value10) && _cpuContext.TryReadUInt64(num9 + 16, out var value11) && _cpuContext.TryReadUInt64(num9 + 24, out var value12) && _cpuContext.TryReadUInt64(num9 + 32, out var value13) && _cpuContext.TryReadUInt64(num9 + 40, out var value14) && _cpuContext.TryReadUInt64(num9 + 48, out var value15) && _cpuContext.TryReadUInt64(num9 + 56, out var value16) && _cpuContext.TryReadUInt64(num9 + 64, out var value17))
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] ImportStackHead#{num}: rsp=0x{num9:X16} [0]=0x{value9:X16} [20]=0x{value13:X16} [40]=0x{value17:X16}");
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] ImportStack#{num}: rsp=0x{num9:X16} [0]=0x{value9:X16} [8]=0x{value10:X16} [10]=0x{value11:X16} [18]=0x{value12:X16} [20]=0x{value13:X16} [28]=0x{value14:X16} [30]=0x{value15:X16} [38]=0x{value16:X16} [40]=0x{value17:X16}");
|
||||
}
|
||||
}
|
||||
if (flag3)
|
||||
{
|
||||
Console.Error.Flush();
|
||||
}
|
||||
}
|
||||
if (importStubEntry.Nid == "Ou3iL1abvng")
|
||||
{
|
||||
try
|
||||
{
|
||||
byte[] array = new byte[64];
|
||||
Marshal.Copy((nint)(num7 - 32), array, 0, array.Length);
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] __stack_chk_fail return-site @0x{num7:X16}: {BitConverter.ToString(array).Replace("-", " ")}");
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
TryBypassStackChkFailTrap(num, num7);
|
||||
}
|
||||
if (importStubEntry.Nid == "9rAeANT2tyE" || importStubEntry.Nid == "1j3S3n-tTW4")
|
||||
{
|
||||
ulong rspDbg = _cpuContext[CpuRegister.Rsp];
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][TRACE] ImportDbg#{num}: nid={importStubEntry.Nid} " +
|
||||
$"ret=0x{num7:X16} rsp=0x{rspDbg:X16} rsp&7=0x{(rspDbg & 7):X} rsp&F=0x{(rspDbg & 0xF):X}");
|
||||
|
||||
if (_cpuContext.TryReadUInt64(rspDbg, out var s0))
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] [rsp+0x00] = 0x{s0:X16}");
|
||||
if (_cpuContext.TryReadUInt64(rspDbg + 8, out var s8))
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] [rsp+0x08] = 0x{s8:X16}");
|
||||
if (_cpuContext.TryReadUInt64(rspDbg + 16, out var s10))
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] [rsp+0x10] = 0x{s10:X16}");
|
||||
if (_cpuContext.TryReadUInt64(rspDbg + 24, out var s18))
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] [rsp+0x18] = 0x{s18:X16}");
|
||||
|
||||
ProbeReturnRip(num7, num);
|
||||
Console.Error.Flush();
|
||||
}
|
||||
ProbeReturnRip(0x0000000800EA020Eul, 999001);
|
||||
ProbeReturnRip(0x0000000800EA0213ul, 999002);
|
||||
ProbeReturnRip(0x0000000800EA040Aul, 999003);
|
||||
try
|
||||
{
|
||||
OrbisGen2Result orbisGen2Result;
|
||||
if (string.Equals(importStubEntry.Nid, RuntimeStubNids.BootstrapBridge, StringComparison.Ordinal))
|
||||
{
|
||||
orbisGen2Result = DispatchBootstrapBridge();
|
||||
}
|
||||
else if (string.Equals(importStubEntry.Nid, RuntimeStubNids.KernelDynlibDlsym, StringComparison.Ordinal))
|
||||
{
|
||||
orbisGen2Result = DispatchKernelDynlibDlsym();
|
||||
}
|
||||
else
|
||||
{
|
||||
orbisGen2Result = _moduleManager.Dispatch(importStubEntry.Nid, _cpuContext);
|
||||
}
|
||||
switch (orbisGen2Result)
|
||||
{
|
||||
case OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND:
|
||||
LastError = "Missing HLE export for NID: " + importStubEntry.Nid;
|
||||
Console.Error.WriteLine($"[LOADER][WARN] Import#{num} unresolved: nid={importStubEntry.Nid} ret=0x{num7:X16}");
|
||||
if (importStubEntry.Nid == "L-Q3LEjIbgA")
|
||||
{
|
||||
string value18 = string.Join(" ", importStubEntry.Nid.Select(delegate (char c)
|
||||
{
|
||||
int num10 = c;
|
||||
return num10.ToString("X2");
|
||||
}));
|
||||
Console.Error.WriteLine($"[LOADER][WARN] map_direct nid raw len={importStubEntry.Nid.Length} chars=[{value18}]");
|
||||
Delegate function;
|
||||
bool value19 = _moduleManager.TryGetFunction(importStubEntry.Nid, out function);
|
||||
ExportedFunction export2;
|
||||
bool value20 = _moduleManager.TryGetExport(importStubEntry.Nid, out export2);
|
||||
Console.Error.WriteLine($"[LOADER][WARN] map_direct lookup with import nid: function={value19}, export={value20}");
|
||||
Console.Error.WriteLine(_moduleManager.TryGetExport("L-Q3LEjIbgA", out ExportedFunction export3) ? $"[LOADER][WARN] Canonical map_direct exists as {export3.LibraryName}:{export3.Name}, target={export3.Target}, ctx_target={_cpuContext.TargetGeneration}" : "[LOADER][WARN] Canonical map_direct export lookup also missing");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Console.Error.WriteLine($"[LOADER][WARN] Import#{num} result: {orbisGen2Result} ({importStubEntry.Nid})");
|
||||
break;
|
||||
case OrbisGen2Result.ORBIS_GEN2_OK:
|
||||
break;
|
||||
}
|
||||
_cpuContext[CpuRegister.Rbx] = value3;
|
||||
_cpuContext[CpuRegister.Rbp] = value4;
|
||||
_cpuContext[CpuRegister.R12] = value5;
|
||||
_cpuContext[CpuRegister.R13] = value6;
|
||||
_cpuContext[CpuRegister.R14] = value7;
|
||||
_cpuContext[CpuRegister.R15] = value8;
|
||||
_cpuContext[CpuRegister.Rdi] = value;
|
||||
_cpuContext[CpuRegister.Rsi] = value2;
|
||||
if (flag || flag2 || flag3)
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] ImportRet#{num}: nid={importStubEntry.Nid} result={orbisGen2Result} rax=0x{_cpuContext[CpuRegister.Rax]:X16}");
|
||||
if (flag3)
|
||||
{
|
||||
Console.Error.Flush();
|
||||
}
|
||||
}
|
||||
return _cpuContext[CpuRegister.Rax];
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LastError = $"HLE dispatch error for {importStubEntry.Nid}: {ex.GetType().Name}: {ex.Message}";
|
||||
_cpuContext[CpuRegister.Rax] = 18446744071562199298uL;
|
||||
return 18446744071562199298uL;
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe bool TryForceGuestExitToHostStub(nint argPackPtr, long dispatchIndex, ulong returnRip, string nid)
|
||||
{
|
||||
ulong num = _entryReturnSentinelRip;
|
||||
if (num < 65536)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
try
|
||||
{
|
||||
*(ulong*)(argPackPtr + 96) = num;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
_forcedGuestExit = true;
|
||||
LastError = $"Detected repeating import loop at import#{dispatchIndex} ({nid}) and forced guest exit.";
|
||||
Console.Error.WriteLine($"[LOADER][ERROR] Import-loop guard fired at import#{dispatchIndex}: nid={nid} ret=0x{returnRip:X16} -> host_exit=0x{num:X16}");
|
||||
DumpRecentImportTrace();
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool ShouldForceGuestExitOnImportLoop(string nid, ulong returnRip, long dispatchIndex, ulong arg0, ulong arg1)
|
||||
{
|
||||
if (dispatchIndex < 1200)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_DISABLE_IMPORT_LOOP_GUARD"), "1", StringComparison.Ordinal))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
RecordImportLoopSignature(BuildImportLoopSignature(nid, returnRip, arg0, arg1));
|
||||
if (!HasRepeatingImportLoopPattern())
|
||||
{
|
||||
if (_importLoopPatternHits > 0)
|
||||
{
|
||||
_importLoopPatternHits--;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
_importLoopPatternHits++;
|
||||
return _importLoopPatternHits >= 6;
|
||||
}
|
||||
|
||||
private ulong BuildImportLoopSignature(string nid, ulong returnRip, ulong arg0, ulong arg1)
|
||||
{
|
||||
if (!_importNidHashCache.TryGetValue(nid, out var value))
|
||||
{
|
||||
value = StableHash64(nid);
|
||||
_importNidHashCache[nid] = value;
|
||||
}
|
||||
ulong num = returnRip >> 2;
|
||||
ulong num2 = ((arg0 >> 4) * 11400714819323198485uL) ^ ((arg1 >> 4) * 14029467366897019727uL);
|
||||
return num ^ value * 11400714819323198485uL ^ num2;
|
||||
}
|
||||
|
||||
private void RecordImportLoopSignature(ulong signature)
|
||||
{
|
||||
_importLoopSignatures[_importLoopSignatureWriteIndex] = signature;
|
||||
_importLoopSignatureWriteIndex = (_importLoopSignatureWriteIndex + 1) % _importLoopSignatures.Length;
|
||||
if (_importLoopSignatureCount < _importLoopSignatures.Length)
|
||||
{
|
||||
_importLoopSignatureCount++;
|
||||
}
|
||||
}
|
||||
|
||||
private bool HasRepeatingImportLoopPattern()
|
||||
{
|
||||
int num = _importLoopSignatureCount;
|
||||
if (num < 96)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
int num2 = Math.Min(48, num / 4);
|
||||
for (int i = 6; i <= num2; i++)
|
||||
{
|
||||
if (HasRepeatingImportLoopPattern(i, 4))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool HasRepeatingImportLoopPattern(int period, int repeats)
|
||||
{
|
||||
int num = period * repeats;
|
||||
if (period <= 0 || repeats < 2 || _importLoopSignatureCount < num)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < period; i++)
|
||||
{
|
||||
ulong importLoopSignatureFromTail = GetImportLoopSignatureFromTail(i);
|
||||
for (int j = 1; j < repeats; j++)
|
||||
{
|
||||
if (GetImportLoopSignatureFromTail(i + j * period) != importLoopSignatureFromTail)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private ulong GetImportLoopSignatureFromTail(int offset)
|
||||
{
|
||||
int num = _importLoopSignatureWriteIndex - 1 - offset;
|
||||
while (num < 0)
|
||||
{
|
||||
num += _importLoopSignatures.Length;
|
||||
}
|
||||
return _importLoopSignatures[num % _importLoopSignatures.Length];
|
||||
}
|
||||
|
||||
private bool ShouldSuppressStrlenTrace(string nid)
|
||||
{
|
||||
return string.Equals(nid, "j4ViWNHEgww", StringComparison.Ordinal) && !_logStrlenImports;
|
||||
}
|
||||
|
||||
private void TrackDistinctImportNid(string nid)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(nid) || string.Equals(_lastDistinctImportNid, nid, StringComparison.Ordinal))
|
||||
{
|
||||
return;
|
||||
}
|
||||
_lastDistinctImportNid = nid;
|
||||
_distinctImportNidHistory[_distinctImportNidHistoryWriteIndex] = nid;
|
||||
_distinctImportNidHistoryWriteIndex = (_distinctImportNidHistoryWriteIndex + 1) % _distinctImportNidHistory.Length;
|
||||
if (_distinctImportNidHistoryCount < _distinctImportNidHistory.Length)
|
||||
{
|
||||
_distinctImportNidHistoryCount++;
|
||||
}
|
||||
}
|
||||
|
||||
private void TrackStrlenPrelude(string nid, long dispatchIndex, ulong returnRip)
|
||||
{
|
||||
if (!string.Equals(nid, "j4ViWNHEgww", StringComparison.Ordinal))
|
||||
{
|
||||
_consecutiveStrlenImports = 0;
|
||||
_strlenPreludeLogged = false;
|
||||
return;
|
||||
}
|
||||
_consecutiveStrlenImports++;
|
||||
if (_strlenPreludeLogged || _consecutiveStrlenImports < 24)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_strlenPreludeLogged = true;
|
||||
List<string> list = GetRecentDistinctImportPrelude(maxCount: 5, skipNid: "j4ViWNHEgww");
|
||||
if (list.Count == 0)
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][WARNING] Import#{dispatchIndex}: detected strlen burst (count={_consecutiveStrlenImports}) ret=0x{returnRip:X16}; no prelude NIDs recorded.");
|
||||
return;
|
||||
}
|
||||
Console.Error.WriteLine($"[LOADER][WARNING] Import#{dispatchIndex}: detected strlen burst (count={_consecutiveStrlenImports}) ret=0x{returnRip:X16}; last5_nids={string.Join(" -> ", list)}");
|
||||
}
|
||||
|
||||
private List<string> GetRecentDistinctImportPrelude(int maxCount, string skipNid)
|
||||
{
|
||||
List<string> list = new List<string>(maxCount);
|
||||
if (maxCount <= 0 || _distinctImportNidHistoryCount == 0)
|
||||
{
|
||||
return list;
|
||||
}
|
||||
HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
|
||||
for (int i = 0; i < _distinctImportNidHistoryCount && list.Count < maxCount; i++)
|
||||
{
|
||||
int num = _distinctImportNidHistoryWriteIndex - 1 - i;
|
||||
while (num < 0)
|
||||
{
|
||||
num += _distinctImportNidHistory.Length;
|
||||
}
|
||||
string text = _distinctImportNidHistory[num % _distinctImportNidHistory.Length];
|
||||
if (string.IsNullOrWhiteSpace(text) || string.Equals(text, skipNid, StringComparison.Ordinal) || !hashSet.Add(text))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (_moduleManager.TryGetExport(text, out ExportedFunction export))
|
||||
{
|
||||
list.Add($"{export.LibraryName}:{export.Name}({text})");
|
||||
}
|
||||
else
|
||||
{
|
||||
list.Add(text);
|
||||
}
|
||||
}
|
||||
list.Reverse();
|
||||
return list;
|
||||
}
|
||||
|
||||
private static ulong StableHash64(string text)
|
||||
{
|
||||
ulong num = 14695981039346656037uL;
|
||||
for (int i = 0; i < text.Length; i++)
|
||||
{
|
||||
num ^= text[i];
|
||||
num *= 1099511628211uL;
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
private OrbisGen2Result DispatchKernelDynlibDlsym()
|
||||
{
|
||||
if (_cpuContext == null)
|
||||
{
|
||||
return OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
ulong symbolNameAddress = _cpuContext[CpuRegister.Rsi];
|
||||
ulong outputAddress = _cpuContext[CpuRegister.Rdx];
|
||||
if (!TryReadAsciiZ(symbolNameAddress, 512, out var symbolName))
|
||||
{
|
||||
_cpuContext[CpuRegister.Rax] = 18446744073709551615uL;
|
||||
return OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
if (!TryResolveRuntimeSymbolAddress(symbolName, out var resolvedAddress))
|
||||
{
|
||||
_cpuContext[CpuRegister.Rax] = 18446744073709551615uL;
|
||||
return OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
if (outputAddress == 0L || !TryWriteUInt64Compat(outputAddress, resolvedAddress))
|
||||
{
|
||||
_cpuContext[CpuRegister.Rax] = 18446744073709551615uL;
|
||||
return OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
_cpuContext[CpuRegister.Rax] = 0uL;
|
||||
return OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
private OrbisGen2Result DispatchBootstrapBridge()
|
||||
{
|
||||
if (_cpuContext == null)
|
||||
{
|
||||
return OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
ulong bridgeHandle = _cpuContext[CpuRegister.Rdi];
|
||||
ulong symbolNameAddress = _cpuContext[CpuRegister.Rsi];
|
||||
ulong outputAddress = _cpuContext[CpuRegister.Rdx];
|
||||
_ = TryReadAsciiZ(symbolNameAddress, 512, out var symbolName);
|
||||
|
||||
OrbisGen2Result result = DispatchKernelDynlibDlsym();
|
||||
if (result != OrbisGen2Result.ORBIS_GEN2_OK)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
bool logBootstrap = string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_BOOTSTRAP"), "1", StringComparison.Ordinal);
|
||||
if (logBootstrap)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][TRACE] bootstrap_dispatch: handle=0x{bridgeHandle:X16} symbol='{symbolName}' out=0x{outputAddress:X16} rax=0x{_cpuContext[CpuRegister.Rax]:X16}");
|
||||
}
|
||||
|
||||
if (_cpuContext[CpuRegister.Rax] == 0uL)
|
||||
{
|
||||
return OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][WARN] bootstrap_bridge unresolved: handle=0x{bridgeHandle:X} symbol='{symbolName}' out=0x{outputAddress:X16}");
|
||||
return OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
private bool TryResolveRuntimeSymbolAddress(string symbolName, out ulong address)
|
||||
{
|
||||
address = 0uL;
|
||||
if (string.IsNullOrWhiteSpace(symbolName))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (_runtimeSymbolsByName.TryGetValue(symbolName, out var value) && IsRuntimeSymbolAddressUsable(value))
|
||||
{
|
||||
address = value;
|
||||
return true;
|
||||
}
|
||||
if (symbolName.StartsWith("_", StringComparison.Ordinal) && _runtimeSymbolsByName.TryGetValue(symbolName[1..], out value) && IsRuntimeSymbolAddressUsable(value))
|
||||
{
|
||||
address = value;
|
||||
return true;
|
||||
}
|
||||
if (_runtimeSymbolsByName.TryGetValue("_" + symbolName, out value) && IsRuntimeSymbolAddressUsable(value))
|
||||
{
|
||||
address = value;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsRuntimeSymbolAddressUsable(ulong value)
|
||||
{
|
||||
return value != 0 && !IsUnresolvedSentinel(value);
|
||||
}
|
||||
|
||||
private bool TryReadAsciiZ(ulong address, int maxLength, out string value)
|
||||
{
|
||||
value = string.Empty;
|
||||
if (_cpuContext == null || address == 0L || maxLength <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
List<byte> list = new List<byte>(Math.Min(maxLength, 256));
|
||||
Span<byte> destination = stackalloc byte[1];
|
||||
for (int i = 0; i < maxLength; i++)
|
||||
{
|
||||
if (!TryReadByteCompat(address + (ulong)i, destination))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (destination[0] == 0)
|
||||
{
|
||||
value = System.Text.Encoding.ASCII.GetString(list.ToArray());
|
||||
return true;
|
||||
}
|
||||
list.Add(destination[0]);
|
||||
}
|
||||
value = System.Text.Encoding.ASCII.GetString(list.ToArray());
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryReadByteCompat(ulong address, Span<byte> destination)
|
||||
{
|
||||
if (_cpuContext == null || destination.Length == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (_cpuContext.Memory.TryRead(address, destination))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
try
|
||||
{
|
||||
destination[0] = Marshal.ReadByte((nint)address);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryWriteUInt64Compat(ulong address, ulong value)
|
||||
{
|
||||
if (_cpuContext == null || address == 0L)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (_cpuContext.TryWriteUInt64(address, value))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
try
|
||||
{
|
||||
Marshal.WriteInt64((nint)address, unchecked((long)value));
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void TryBypassStackChkFailTrap(long dispatchIndex, ulong returnRip)
|
||||
{
|
||||
if (_cpuContext == null || returnRip < 32)
|
||||
{
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
byte[] array = new byte[19];
|
||||
ulong num = returnRip - 23;
|
||||
Marshal.Copy((nint)num, array, 0, array.Length);
|
||||
if (array[0] != 117 || array[1] != 16 || array[2] != 72 || array[3] != 137 || array[4] != 216 || array[5] != 72 || array[6] != 131 || array[7] != 196 || array[9] != 91 || array[10] != 65 || array[11] != 92 || array[12] != 65 || array[13] != 94 || array[14] != 65 || array[15] != 95 || array[16] != 93 || array[17] != 195 || array[18] != 232)
|
||||
{
|
||||
return;
|
||||
}
|
||||
ulong value = returnRip - 21;
|
||||
ulong address = _cpuContext[CpuRegister.Rsp];
|
||||
if (_cpuContext.TryWriteUInt64(address, value))
|
||||
{
|
||||
if (_stackChkBypassSites.Add(num) && TryPatchStackChkFailBranch(num))
|
||||
{
|
||||
Console.Error.WriteLine($"[LOADER][WARNING] Import#{dispatchIndex}: patched stack_chk_fail tail branch at 0x{num:X16} -> NOP NOP");
|
||||
}
|
||||
Console.Error.WriteLine($"[LOADER][WARNING] Import#{dispatchIndex}: redirected __stack_chk_fail return to epilogue 0x{value:X16}");
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe static bool TryPatchStackChkFailBranch(ulong branchAddress)
|
||||
{
|
||||
uint flNewProtect = default(uint);
|
||||
if (!VirtualProtect((void*)branchAddress, 2u, 64u, &flNewProtect))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
try
|
||||
{
|
||||
if (Marshal.ReadByte((nint)branchAddress) != 117)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Marshal.WriteByte((nint)branchAddress, 144);
|
||||
Marshal.WriteByte((nint)(branchAddress + 1), 144);
|
||||
FlushInstructionCache(GetCurrentProcess(), (void*)branchAddress, 2u);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
VirtualProtect((void*)branchAddress, 2u, flNewProtect, &flNewProtect);
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe void TryPatchEa020eLookupCall(long dispatchIndex, ulong returnRip)
|
||||
{
|
||||
if (_patchedEa020eLookupCall || returnRip != 0x0000000800EA01A6uL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const ulong num = 0x0000000800EA020EuL;
|
||||
nint num2 = unchecked((nint)num);
|
||||
uint flNewProtect = default(uint);
|
||||
try
|
||||
{
|
||||
if (Marshal.ReadByte(num2) != 232 || !VirtualProtect((void*)num, 5u, 64u, &flNewProtect))
|
||||
{
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
Marshal.WriteByte(num2 + i, 144);
|
||||
}
|
||||
FlushInstructionCache(GetCurrentProcess(), (void*)num, 5u);
|
||||
_patchedEa020eLookupCall = true;
|
||||
Console.Error.WriteLine($"[LOADER][WARNING] Import#{dispatchIndex}: patched hash-lookup call at 0x{num:X16} -> NOP*5");
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (flNewProtect != 0)
|
||||
{
|
||||
VirtualProtect((void*)num, 5u, flNewProtect, &flNewProtect);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,22 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using SharpEmu.HLE;
|
||||
|
||||
namespace SharpEmu.Core.Cpu.Native;
|
||||
|
||||
public interface INativeCpuBackend
|
||||
{
|
||||
string BackendName { get; }
|
||||
|
||||
string? LastError { get; }
|
||||
|
||||
bool TryExecute(
|
||||
CpuContext context,
|
||||
ulong entryPoint,
|
||||
Generation generation,
|
||||
IReadOnlyDictionary<ulong, string> importStubs,
|
||||
IReadOnlyDictionary<string, ulong> runtimeSymbols,
|
||||
CpuExecutionOptions executionOptions,
|
||||
out OrbisGen2Result result);
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace SharpEmu.Core.Cpu.Native;
|
||||
|
||||
public static unsafe class JitStubs
|
||||
{
|
||||
public const int XsaveBufferSize = 2688;
|
||||
|
||||
public const ulong XsaveChkGuard = 0xDeadBeef5533CCAAu;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct JmpWithIndex
|
||||
{
|
||||
private fixed byte _code[16];
|
||||
|
||||
public static ReadOnlySpan<byte> Template => new byte[]
|
||||
{
|
||||
0x68, 0x00, 0x00, 0x00, 0x00, // push <index>
|
||||
0xE9, 0x00, 0x00, 0x00, 0x00, // jmp <handler>
|
||||
0x90, 0x90, 0x90, 0x90, 0x90, 0x90 // nop padding
|
||||
};
|
||||
|
||||
public static int Size => 16;
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
fixed (byte* code = _code)
|
||||
{
|
||||
Template.CopyTo(new Span<byte>(code, 16));
|
||||
}
|
||||
}
|
||||
|
||||
public void SetIndex(uint index)
|
||||
{
|
||||
fixed (byte* code = _code)
|
||||
{
|
||||
*(uint*)(code + 1) = index;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetHandler(void* handler)
|
||||
{
|
||||
fixed (byte* code = _code)
|
||||
{
|
||||
var funcAddr = (long)handler;
|
||||
var ripAddr = (long)(code + 10); // After push + jmp
|
||||
var offset64 = funcAddr - ripAddr;
|
||||
var offset32 = (uint)(ulong)offset64;
|
||||
|
||||
*(uint*)(code + 6) = offset32;
|
||||
}
|
||||
}
|
||||
|
||||
public byte* GetCodePointer()
|
||||
{
|
||||
fixed (byte* code = _code)
|
||||
{
|
||||
return code;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct Call9
|
||||
{
|
||||
private fixed byte _code[9];
|
||||
|
||||
public static ReadOnlySpan<byte> Template => new byte[]
|
||||
{
|
||||
0xE8, 0x00, 0x00, 0x00, 0x00, // call func
|
||||
0x48, 0x89, 0xC0, // mov rax,rax
|
||||
0x90 // nop
|
||||
};
|
||||
|
||||
public static int Size => 9;
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
fixed (byte* code = _code)
|
||||
{
|
||||
Template.CopyTo(new Span<byte>(code, 9));
|
||||
}
|
||||
}
|
||||
|
||||
public void SetFunc(void* func)
|
||||
{
|
||||
fixed (byte* code = _code)
|
||||
{
|
||||
var funcAddr = (long)func;
|
||||
var ripAddr = (long)(code + 5); // After call instruction
|
||||
var offset64 = funcAddr - ripAddr;
|
||||
var offset32 = (uint)(ulong)offset64;
|
||||
|
||||
*(uint*)(code + 1) = offset32;
|
||||
}
|
||||
}
|
||||
|
||||
public byte* GetCodePointer()
|
||||
{
|
||||
fixed (byte* code = _code)
|
||||
{
|
||||
return code;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct JmpRax
|
||||
{
|
||||
private fixed byte _code[12];
|
||||
|
||||
public static ReadOnlySpan<byte> Template => new byte[]
|
||||
{
|
||||
0x48, 0xB8, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, // movabs rax,<addr>
|
||||
0xFF, 0xE0 // jmp rax
|
||||
};
|
||||
|
||||
public static int Size => 12;
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
fixed (byte* code = _code)
|
||||
{
|
||||
Template.CopyTo(new Span<byte>(code, 12));
|
||||
}
|
||||
}
|
||||
|
||||
public void SetTarget(void* target)
|
||||
{
|
||||
fixed (byte* code = _code)
|
||||
{
|
||||
*(ulong*)(code + 2) = (ulong)target;
|
||||
}
|
||||
}
|
||||
|
||||
public byte* GetCodePointer()
|
||||
{
|
||||
fixed (byte* code = _code)
|
||||
{
|
||||
return code;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class SafeCall
|
||||
{
|
||||
public static ReadOnlySpan<byte> Template => new byte[]
|
||||
{
|
||||
0x51,
|
||||
0x52,
|
||||
0x41, 0x50,
|
||||
0x41, 0x51,
|
||||
0x41, 0x52,
|
||||
0x41, 0x53,
|
||||
0x57,
|
||||
0x56,
|
||||
0x48, 0xBF, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11,
|
||||
0x48, 0xBE, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11,
|
||||
0x48, 0xB9, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11,
|
||||
0xB0, 0x01,
|
||||
0x86, 0x07,
|
||||
0x84, 0xC0,
|
||||
0x75, 0xF8,
|
||||
0xB8, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xBA, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0x0F, 0xAE, 0x26,
|
||||
0x48, 0x83, 0xEC, 0x08,
|
||||
0xFF, 0xD1,
|
||||
0x48, 0x83, 0xC4, 0x08,
|
||||
0x48, 0x89, 0xC1,
|
||||
0xB8, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xBA, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0x0F, 0xAE, 0x2E,
|
||||
0x48, 0x89, 0xC8,
|
||||
0xC6, 0x07, 0x00,
|
||||
0x5E,
|
||||
0x5F,
|
||||
0x41, 0x5B,
|
||||
0x41, 0x5A,
|
||||
0x41, 0x59,
|
||||
0x41, 0x58,
|
||||
0x5A,
|
||||
0x59,
|
||||
0xC3
|
||||
};
|
||||
|
||||
public static int Size => Template.Length; // 0x6c = 108 bytes
|
||||
}
|
||||
|
||||
public static ReadOnlySpan<byte> TlsAccessPattern => new byte[]
|
||||
{
|
||||
0x64, 0x48, 0x8B, 0x04, 0x25, 0x00, 0x00, 0x00, 0x00
|
||||
};
|
||||
|
||||
public static void CreateJmpWithIndex(byte* location, uint index, void* handler)
|
||||
{
|
||||
|
||||
var code = location;
|
||||
|
||||
code[0] = 0x68;
|
||||
*(uint*)(code + 1) = index;
|
||||
|
||||
code[5] = 0xE9;
|
||||
var handlerAddr = (long)handler;
|
||||
var ripAddr = (long)(code + 10); // After push + jmp
|
||||
var offset64 = handlerAddr - ripAddr;
|
||||
*(uint*)(code + 6) = (uint)(ulong)offset64;
|
||||
|
||||
for (int i = 10; i < 16; i++)
|
||||
{
|
||||
code[i] = 0x90;
|
||||
}
|
||||
}
|
||||
|
||||
public static void CreateCall9(byte* location, void* func)
|
||||
{
|
||||
|
||||
var code = location;
|
||||
|
||||
code[0] = 0xE8;
|
||||
var funcAddr = (long)func;
|
||||
var ripAddr = (long)(code + 5);
|
||||
var offset64 = funcAddr - ripAddr;
|
||||
*(uint*)(code + 1) = (uint)(ulong)offset64;
|
||||
|
||||
code[5] = 0x48;
|
||||
code[6] = 0x89;
|
||||
code[7] = 0xC0;
|
||||
|
||||
code[8] = 0x90;
|
||||
}
|
||||
|
||||
public static void CreateJmpRax(byte* location, void* target)
|
||||
{
|
||||
|
||||
var code = location;
|
||||
|
||||
code[0] = 0x48;
|
||||
code[1] = 0xB8;
|
||||
*(ulong*)(code + 2) = (ulong)target;
|
||||
|
||||
code[10] = 0xFF;
|
||||
code[11] = 0xE0;
|
||||
}
|
||||
|
||||
public static List<nint> FindTlsAccessPatterns(byte* start, int length)
|
||||
{
|
||||
var results = new List<nint>();
|
||||
var pattern = TlsAccessPattern;
|
||||
var end = start + length - pattern.Length;
|
||||
|
||||
for (var ptr = start; ptr < end; ptr++)
|
||||
{
|
||||
if (MatchesPattern(ptr, pattern))
|
||||
{
|
||||
results.Add((nint)ptr);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private static bool MatchesPattern(byte* ptr, ReadOnlySpan<byte> pattern)
|
||||
{
|
||||
for (var i = 0; i < pattern.Length; i++)
|
||||
{
|
||||
if (ptr[i] != pattern[i])
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
// 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;
|
||||
|
||||
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 byte* _pltMemory;
|
||||
private int _pltOffset;
|
||||
private const int PltMemorySize = 1024 * 1024; // 1MB for stubs
|
||||
|
||||
public StubManager()
|
||||
{
|
||||
_pltMemory = (byte*)VirtualAlloc(
|
||||
null,
|
||||
(nuint)PltMemorySize,
|
||||
AllocationType.Reserve | AllocationType.Commit,
|
||||
MemoryProtection.ExecuteReadWrite);
|
||||
|
||||
if (_pltMemory == null)
|
||||
{
|
||||
throw new OutOfMemoryException("Failed to allocate executable memory for stubs");
|
||||
}
|
||||
|
||||
_pltOffset = 0;
|
||||
}
|
||||
|
||||
public nint CreateImportStub(ulong guestAddress, string nid, ImportHandler handler)
|
||||
{
|
||||
if (!_importHandlers.TryGetValue(nid, out var handlerPtr))
|
||||
{
|
||||
handlerPtr = CreateHandlerTrampoline(nid, handler);
|
||||
_importHandlers[nid] = handlerPtr;
|
||||
}
|
||||
|
||||
var stubPtr = _pltMemory + _pltOffset;
|
||||
var stubIndex = (uint)(_stubAddresses.Count);
|
||||
|
||||
JitStubs.CreateJmpWithIndex(stubPtr, stubIndex, (void*)handlerPtr);
|
||||
|
||||
_pltOffset += JitStubs.JmpWithIndex.Size;
|
||||
_stubAddresses[guestAddress] = (nint)stubPtr;
|
||||
|
||||
return (nint)stubPtr;
|
||||
}
|
||||
|
||||
public nint CreatePltEntry(uint index, nint gotAddress)
|
||||
{
|
||||
var pltPtr = _pltMemory + _pltOffset;
|
||||
|
||||
var pltCode = new byte[]
|
||||
{
|
||||
0x49, 0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // movabs r11,<addr>
|
||||
0x41, 0xFF, 0x73, 0x08, // push QWORD PTR [r11+8]
|
||||
0x41, 0xFF, 0x63, 0x10, // jmp QWORD PTR [r11+16]
|
||||
0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 // nop padding
|
||||
};
|
||||
|
||||
*(ulong*)(pltCode.AsSpan().Slice(2).GetPinnableReference()) = (ulong)gotAddress;
|
||||
|
||||
fixed (byte* src = pltCode)
|
||||
{
|
||||
Buffer.MemoryCopy(src, pltPtr, pltCode.Length, pltCode.Length);
|
||||
}
|
||||
|
||||
_pltOffset += pltCode.Length;
|
||||
|
||||
return (nint)pltPtr;
|
||||
}
|
||||
|
||||
public nint CreateTlsHandler(delegate*<void> tlsGetAddrFunc)
|
||||
{
|
||||
var handlerPtr = _pltMemory + _pltOffset;
|
||||
|
||||
JitStubs.CreateCall9(handlerPtr, (void*)tlsGetAddrFunc);
|
||||
|
||||
_pltOffset += JitStubs.Call9.Size;
|
||||
|
||||
return (nint)handlerPtr;
|
||||
}
|
||||
|
||||
public nint CreateSafeCallTrampoline(delegate*<void> func, byte* regSaveArea, byte* lockVar)
|
||||
{
|
||||
var trampolinePtr = _pltMemory + _pltOffset;
|
||||
|
||||
var template = JitStubs.SafeCall.Template;
|
||||
fixed (byte* src = template)
|
||||
{
|
||||
Buffer.MemoryCopy(src, trampolinePtr, template.Length, template.Length);
|
||||
}
|
||||
|
||||
*(ulong*)(trampolinePtr + 0x0c + 2) = (ulong)lockVar;
|
||||
|
||||
*(ulong*)(trampolinePtr + 0x16 + 2) = (ulong)regSaveArea;
|
||||
|
||||
*(ulong*)(trampolinePtr + 0x20 + 2) = (ulong)func;
|
||||
|
||||
_pltOffset += template.Length;
|
||||
|
||||
return (nint)trampolinePtr;
|
||||
}
|
||||
|
||||
public bool TryGetStubAddress(ulong guestAddress, out nint hostAddress)
|
||||
{
|
||||
return _stubAddresses.TryGetValue(guestAddress, out hostAddress);
|
||||
}
|
||||
|
||||
public bool PatchTlsAccess(byte* guestCode, int codeSize, nint tlsHandler)
|
||||
{
|
||||
var locations = JitStubs.FindTlsAccessPatterns(guestCode, codeSize);
|
||||
|
||||
foreach (var location in locations)
|
||||
{
|
||||
JitStubs.CreateCall9((byte*)location, (void*)tlsHandler);
|
||||
}
|
||||
|
||||
return locations.Count > 0;
|
||||
}
|
||||
|
||||
private nint CreateHandlerTrampoline(string nid, ImportHandler handler)
|
||||
{
|
||||
var trampolinePtr = _pltMemory + _pltOffset;
|
||||
|
||||
|
||||
|
||||
var code = new List<byte>();
|
||||
|
||||
code.Add(0x57);
|
||||
code.Add(0x56);
|
||||
code.Add(0x55);
|
||||
code.Add(0x53);
|
||||
code.AddRange(new byte[] { 0x41, 0x54 });
|
||||
code.AddRange(new byte[] { 0x41, 0x55 });
|
||||
code.AddRange(new byte[] { 0x41, 0x56 });
|
||||
code.AddRange(new byte[] { 0x41, 0x57 });
|
||||
|
||||
code.AddRange(new byte[] { 0x48, 0x83, 0xEC, 0x28 });
|
||||
|
||||
int contextOffset = code.Count;
|
||||
code.AddRange(new byte[] { 0x48, 0xB9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 });
|
||||
|
||||
int handlerOffset = code.Count;
|
||||
code.AddRange(new byte[] { 0x48, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 });
|
||||
|
||||
code.AddRange(new byte[] { 0xFF, 0xD0 });
|
||||
|
||||
code.AddRange(new byte[] { 0x48, 0x83, 0xC4, 0x28 });
|
||||
|
||||
code.AddRange(new byte[] { 0x41, 0x5F });
|
||||
code.AddRange(new byte[] { 0x41, 0x5E });
|
||||
code.AddRange(new byte[] { 0x41, 0x5D });
|
||||
code.AddRange(new byte[] { 0x41, 0x5C });
|
||||
code.Add(0x5B);
|
||||
code.Add(0x5D);
|
||||
code.Add(0x5E);
|
||||
code.Add(0x5F);
|
||||
|
||||
code.Add(0xC3);
|
||||
|
||||
var codeArray = code.ToArray();
|
||||
fixed (byte* src = codeArray)
|
||||
{
|
||||
Buffer.MemoryCopy(src, trampolinePtr, codeArray.Length, codeArray.Length);
|
||||
}
|
||||
|
||||
*(ulong*)(trampolinePtr + contextOffset + 2) = 0;
|
||||
|
||||
var handlerPtr = Marshal.GetFunctionPointerForDelegate(handler);
|
||||
*(ulong*)(trampolinePtr + handlerOffset + 2) = (ulong)handlerPtr;
|
||||
|
||||
_pltOffset += codeArray.Length;
|
||||
|
||||
_allocatedStubs.Add((nint)trampolinePtr);
|
||||
|
||||
return (nint)trampolinePtr;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_pltMemory != null)
|
||||
{
|
||||
VirtualFree(_pltMemory, 0, FreeType.Release);
|
||||
_pltMemory = null;
|
||||
}
|
||||
|
||||
_allocatedStubs.Clear();
|
||||
_importHandlers.Clear();
|
||||
_stubAddresses.Clear();
|
||||
}
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern void* VirtualAlloc(void* lpAddress, nuint dwSize, AllocationType flAllocationType, MemoryProtection flProtect);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern bool VirtualFree(void* lpAddress, nuint dwSize, FreeType 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);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
namespace SharpEmu.Core.Cpu;
|
||||
|
||||
internal static class RuntimeStubNids
|
||||
{
|
||||
public const string BootstrapBridge = "__internal_bootstrap_bridge";
|
||||
|
||||
public const string KernelDynlibDlsym = "__internal_kernel_dynlib_dlsym";
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using SharpEmu.HLE;
|
||||
|
||||
namespace SharpEmu.Core.Cpu;
|
||||
|
||||
public sealed class TrackedCpuMemory : ICpuMemory, ITrackedCpuMemory
|
||||
{
|
||||
private readonly ICpuMemory _inner;
|
||||
|
||||
public TrackedCpuMemory(ICpuMemory inner)
|
||||
{
|
||||
_inner = inner ?? throw new ArgumentNullException(nameof(inner));
|
||||
}
|
||||
|
||||
public CpuMemoryAccessFailure? LastFailure { get; private set; }
|
||||
|
||||
public ICpuMemory Inner => _inner;
|
||||
|
||||
public bool TryRead(ulong virtualAddress, Span<byte> destination)
|
||||
{
|
||||
var result = _inner.TryRead(virtualAddress, destination);
|
||||
if (!result)
|
||||
{
|
||||
LastFailure = new CpuMemoryAccessFailure(virtualAddress, destination.Length, isWrite: false);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public bool TryWrite(ulong virtualAddress, ReadOnlySpan<byte> source)
|
||||
{
|
||||
var result = _inner.TryWrite(virtualAddress, source);
|
||||
if (!result)
|
||||
{
|
||||
LastFailure = new CpuMemoryAccessFailure(virtualAddress, source.Length, isWrite: true);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user