mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-15 23:46:15 +08:00
more HLE's and fix cpu execution some titles
This commit is contained in:
@@ -193,6 +193,11 @@ public sealed class CpuDispatcher : ICpuDispatcher, IDisposable
|
|||||||
return FailEarly(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
return FailEarly(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!InitializeGuestFrameChainSentinel(context))
|
||||||
|
{
|
||||||
|
return FailEarly(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||||
|
}
|
||||||
|
|
||||||
if (!InitializeTls(context, tlsBase))
|
if (!InitializeTls(context, tlsBase))
|
||||||
{
|
{
|
||||||
return FailEarly(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
return FailEarly(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||||
@@ -363,6 +368,23 @@ public sealed class CpuDispatcher : ICpuDispatcher, IDisposable
|
|||||||
context.TryWriteUInt64(tlsBase + 0x28, 0xC0DEC0DECAFEBABEUL);
|
context.TryWriteUInt64(tlsBase + 0x28, 0xC0DEC0DECAFEBABEUL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool InitializeGuestFrameChainSentinel(CpuContext context)
|
||||||
|
{
|
||||||
|
var stackTop = context[CpuRegister.Rsp] + sizeof(ulong);
|
||||||
|
var sentinelFrame = AlignDown(stackTop - 0x20, 16);
|
||||||
|
var seedRsp = sentinelFrame - sizeof(ulong);
|
||||||
|
if (!context.TryWriteUInt64(sentinelFrame, 0) ||
|
||||||
|
!context.TryWriteUInt64(sentinelFrame + sizeof(ulong), 0) ||
|
||||||
|
!context.TryWriteUInt64(seedRsp, 0))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
context[CpuRegister.Rbp] = sentinelFrame;
|
||||||
|
context[CpuRegister.Rsp] = seedRsp;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private static bool InitializeProcessEntryFrame(
|
private static bool InitializeProcessEntryFrame(
|
||||||
CpuContext context,
|
CpuContext context,
|
||||||
string processImageName,
|
string processImageName,
|
||||||
|
|||||||
@@ -302,6 +302,23 @@ public sealed partial class DirectExecutionBackend
|
|||||||
_cpuContext[CpuRegister.R15] = value8;
|
_cpuContext[CpuRegister.R15] = value8;
|
||||||
_cpuContext[CpuRegister.Rdi] = value;
|
_cpuContext[CpuRegister.Rdi] = value;
|
||||||
_cpuContext[CpuRegister.Rsi] = value2;
|
_cpuContext[CpuRegister.Rsi] = value2;
|
||||||
|
if (GuestThreadExecution.TryConsumeCurrentEntryExit(out var exitStatus, out var exitReason))
|
||||||
|
{
|
||||||
|
if (TryCompleteGuestEntryToHostStub(argPackPtr, num, num7, importStubEntry.Nid, exitReason, exitStatus))
|
||||||
|
{
|
||||||
|
_cpuContext[CpuRegister.Rax] = unchecked((ulong)exitStatus);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LastError = $"Failed to complete guest entry after {importStubEntry.Nid}: missing host return sentinel";
|
||||||
|
_cpuContext[CpuRegister.Rax] = 18446744071562199298uL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (GuestThreadExecution.TryConsumeCurrentThreadBlock(out var blockReason) &&
|
||||||
|
TryYieldGuestThreadToHostStub(argPackPtr, num, num7, importStubEntry.Nid, blockReason))
|
||||||
|
{
|
||||||
|
_cpuContext[CpuRegister.Rax] = 0uL;
|
||||||
|
}
|
||||||
if (flag || flag2 || flag3)
|
if (flag || flag2 || flag3)
|
||||||
{
|
{
|
||||||
Console.Error.WriteLine($"[LOADER][TRACE] ImportRet#{num}: nid={importStubEntry.Nid} result={orbisGen2Result} rax=0x{_cpuContext[CpuRegister.Rax]:X16}");
|
Console.Error.WriteLine($"[LOADER][TRACE] ImportRet#{num}: nid={importStubEntry.Nid} result={orbisGen2Result} rax=0x{_cpuContext[CpuRegister.Rax]:X16}");
|
||||||
@@ -342,6 +359,49 @@ public sealed partial class DirectExecutionBackend
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private unsafe bool TryCompleteGuestEntryToHostStub(nint argPackPtr, long dispatchIndex, ulong returnRip, string nid, string reason, int status)
|
||||||
|
{
|
||||||
|
ulong hostExit = _entryReturnSentinelRip;
|
||||||
|
if (hostExit < 65536)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
*(ulong*)(argPackPtr + 96) = hostExit;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Console.Error.WriteLine(
|
||||||
|
$"[LOADER][INFO] Guest entry exit at import#{dispatchIndex}: nid={nid} ret=0x{returnRip:X16} reason={reason} status={status}");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe bool TryYieldGuestThreadToHostStub(nint argPackPtr, long dispatchIndex, ulong returnRip, string nid, string reason)
|
||||||
|
{
|
||||||
|
ulong hostExit = _entryReturnSentinelRip;
|
||||||
|
if (hostExit < 65536)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
*(ulong*)(argPackPtr + 96) = hostExit;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_guestThreadYieldRequested = true;
|
||||||
|
_guestThreadYieldReason = string.IsNullOrWhiteSpace(reason) ? nid : reason;
|
||||||
|
Console.Error.WriteLine(
|
||||||
|
$"[LOADER][INFO] Guest thread yield at import#{dispatchIndex}: nid={nid} ret=0x{returnRip:X16} reason={_guestThreadYieldReason}");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private bool ShouldForceGuestExitOnImportLoop(string nid, ulong returnRip, long dispatchIndex, ulong arg0, ulong arg1)
|
private bool ShouldForceGuestExitOnImportLoop(string nid, ulong returnRip, long dispatchIndex, ulong arg0, ulong arg1)
|
||||||
{
|
{
|
||||||
if (dispatchIndex < 1200)
|
if (dispatchIndex < 1200)
|
||||||
|
|||||||
@@ -7,11 +7,14 @@ using System.Diagnostics;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using SharpEmu.Core.Cpu;
|
||||||
|
using SharpEmu.Core.Loader;
|
||||||
|
using SharpEmu.Core.Memory;
|
||||||
using SharpEmu.HLE;
|
using SharpEmu.HLE;
|
||||||
|
|
||||||
namespace SharpEmu.Core.Cpu.Native;
|
namespace SharpEmu.Core.Cpu.Native;
|
||||||
|
|
||||||
public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, IDisposable
|
public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, IGuestThreadScheduler, IDisposable
|
||||||
{
|
{
|
||||||
private const int ImportLoopHistoryLength = 2048;
|
private const int ImportLoopHistoryLength = 2048;
|
||||||
|
|
||||||
@@ -87,6 +90,16 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
|
|
||||||
private const ulong GuestImageScanEnd = 36507222016uL;
|
private const ulong GuestImageScanEnd = 36507222016uL;
|
||||||
|
|
||||||
|
private const ulong GuestThreadStackBaseAddress = 0x7FFF_E000_0000UL;
|
||||||
|
|
||||||
|
private const ulong GuestThreadTlsBaseAddress = 0x7FFE_0000_0000UL;
|
||||||
|
|
||||||
|
private const ulong GuestThreadStackSize = 0x0020_0000UL;
|
||||||
|
|
||||||
|
private const ulong GuestThreadTlsSize = 0x0001_0000UL;
|
||||||
|
|
||||||
|
private const ulong GuestThreadRegionStride = 0x0100_0000UL;
|
||||||
|
|
||||||
private const uint PAGE_EXECUTE_READWRITE = 64u;
|
private const uint PAGE_EXECUTE_READWRITE = 64u;
|
||||||
|
|
||||||
private const uint PAGE_READWRITE = 4u;
|
private const uint PAGE_READWRITE = 4u;
|
||||||
@@ -105,6 +118,8 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
|
|
||||||
private nint _tlsBaseAddress;
|
private nint _tlsBaseAddress;
|
||||||
|
|
||||||
|
private nint _ownedTlsBaseAddress;
|
||||||
|
|
||||||
private bool _ownsTlsBaseAddress;
|
private bool _ownsTlsBaseAddress;
|
||||||
|
|
||||||
private int _tlsPatchStubOffset;
|
private int _tlsPatchStubOffset;
|
||||||
@@ -197,6 +212,52 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
|
|
||||||
private readonly Dictionary<string, ulong> _importNidHashCache = new Dictionary<string, ulong>(StringComparer.Ordinal);
|
private readonly Dictionary<string, ulong> _importNidHashCache = new Dictionary<string, ulong>(StringComparer.Ordinal);
|
||||||
|
|
||||||
|
private enum GuestThreadRunState
|
||||||
|
{
|
||||||
|
Ready,
|
||||||
|
Running,
|
||||||
|
Blocked,
|
||||||
|
Exited,
|
||||||
|
Faulted,
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum GuestNativeCallExitReason
|
||||||
|
{
|
||||||
|
Returned,
|
||||||
|
Blocked,
|
||||||
|
ForcedExit,
|
||||||
|
Exception,
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class GuestThreadState
|
||||||
|
{
|
||||||
|
public ulong ThreadHandle { get; init; }
|
||||||
|
|
||||||
|
public ulong EntryPoint { get; init; }
|
||||||
|
|
||||||
|
public ulong Argument { get; init; }
|
||||||
|
|
||||||
|
public string Name { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
public CpuContext Context { get; init; } = null!;
|
||||||
|
|
||||||
|
public GuestThreadRunState State { get; set; }
|
||||||
|
|
||||||
|
public string? BlockReason { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly object _guestThreadGate = new object();
|
||||||
|
|
||||||
|
private readonly Queue<GuestThreadState> _readyGuestThreads = new Queue<GuestThreadState>();
|
||||||
|
|
||||||
|
private readonly Dictionary<ulong, GuestThreadState> _guestThreads = new Dictionary<ulong, GuestThreadState>();
|
||||||
|
|
||||||
|
private int _guestThreadPumpDepth;
|
||||||
|
|
||||||
|
private bool _guestThreadYieldRequested;
|
||||||
|
|
||||||
|
private string? _guestThreadYieldReason;
|
||||||
|
|
||||||
private bool _forcedGuestExit;
|
private bool _forcedGuestExit;
|
||||||
|
|
||||||
private ulong _lastAvTraceRip;
|
private ulong _lastAvTraceRip;
|
||||||
@@ -219,7 +280,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
|
|
||||||
private nint _selfHandlePtr;
|
private nint _selfHandlePtr;
|
||||||
|
|
||||||
private static readonly byte[] TlsPattern = new byte[9] { 100, 72, 139, 4, 37, 0, 0, 0, 0 };
|
private const int MinTlsPatchInstructionBytes = 9;
|
||||||
|
|
||||||
private delegate ulong ImportGatewayDelegate(nint backendHandle, int importIndex, nint argPackPtr);
|
private delegate ulong ImportGatewayDelegate(nint backendHandle, int importIndex, nint argPackPtr);
|
||||||
private delegate int RawExceptionHandlerDelegate(void* exceptionInfo);
|
private delegate int RawExceptionHandlerDelegate(void* exceptionInfo);
|
||||||
@@ -336,6 +397,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
{
|
{
|
||||||
throw new OutOfMemoryException("Failed to allocate TLS base");
|
throw new OutOfMemoryException("Failed to allocate TLS base");
|
||||||
}
|
}
|
||||||
|
_ownedTlsBaseAddress = _tlsBaseAddress;
|
||||||
_ownsTlsBaseAddress = true;
|
_ownsTlsBaseAddress = true;
|
||||||
SeedTlsLayout(_tlsBaseAddress);
|
SeedTlsLayout(_tlsBaseAddress);
|
||||||
_hostRspSlotStorage = (nint)VirtualAlloc(null, 4096u, 12288u, 4u);
|
_hostRspSlotStorage = (nint)VirtualAlloc(null, 4096u, 12288u, 4u);
|
||||||
@@ -376,12 +438,15 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
_importLoopSignatureWriteIndex = 0;
|
_importLoopSignatureWriteIndex = 0;
|
||||||
_importLoopPatternHits = 0;
|
_importLoopPatternHits = 0;
|
||||||
_importNidHashCache.Clear();
|
_importNidHashCache.Clear();
|
||||||
|
ClearGuestThreads();
|
||||||
_contextualUnresolvedReturnSites.Clear();
|
_contextualUnresolvedReturnSites.Clear();
|
||||||
_stallWatchdogTriggered = 0;
|
_stallWatchdogTriggered = 0;
|
||||||
_stallWatchdogStop = false;
|
_stallWatchdogStop = false;
|
||||||
_patchedEa020eLookupCall = false;
|
_patchedEa020eLookupCall = false;
|
||||||
MarkExecutionProgress();
|
MarkExecutionProgress();
|
||||||
BindTlsBase(context);
|
BindTlsBase(context);
|
||||||
|
var previousGuestThreadScheduler = GuestThreadExecution.Scheduler;
|
||||||
|
GuestThreadExecution.Scheduler = this;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!SetupImportStubs(importStubs))
|
if (!SetupImportStubs(importStubs))
|
||||||
@@ -407,6 +472,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
GuestThreadExecution.Scheduler = previousGuestThreadScheduler;
|
||||||
Console.Error.WriteLine("[LOADER][INFO] === Execute END (LastError: " + (LastError ?? "null") + ") ===");
|
Console.Error.WriteLine("[LOADER][INFO] === Execute END (LastError: " + (LastError ?? "null") + ") ===");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -645,18 +711,15 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
}
|
}
|
||||||
if (num != _tlsBaseAddress)
|
if (num != _tlsBaseAddress)
|
||||||
{
|
{
|
||||||
if (_ownsTlsBaseAddress && _tlsBaseAddress != 0)
|
|
||||||
{
|
|
||||||
VirtualFree((void*)_tlsBaseAddress, 0u, 32768u);
|
|
||||||
_ownsTlsBaseAddress = false;
|
|
||||||
}
|
|
||||||
_tlsBaseAddress = num;
|
_tlsBaseAddress = num;
|
||||||
|
_ownsTlsBaseAddress = _tlsBaseAddress == _ownedTlsBaseAddress;
|
||||||
}
|
}
|
||||||
if (_tlsBaseAddress != 0)
|
if (_tlsBaseAddress != 0)
|
||||||
{
|
{
|
||||||
context.FsBase = (ulong)_tlsBaseAddress;
|
context.FsBase = (ulong)_tlsBaseAddress;
|
||||||
context.GsBase = (ulong)_tlsBaseAddress;
|
context.GsBase = (ulong)_tlsBaseAddress;
|
||||||
SeedTlsLayout(_tlsBaseAddress);
|
SeedTlsLayout(_tlsBaseAddress);
|
||||||
|
UpdateTlsHandlerBase(_tlsBaseAddress);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -669,6 +732,30 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
*(ulong*)(tlsBase + 96) = num;
|
*(ulong*)(tlsBase + 96) = num;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private unsafe void UpdateTlsHandlerBase(nint tlsBase)
|
||||||
|
{
|
||||||
|
if (_tlsHandlerAddress == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint oldProtect = default;
|
||||||
|
if (!VirtualProtect((void*)_tlsHandlerAddress, 16u, 64u, &oldProtect))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
*(long*)((byte*)_tlsHandlerAddress + 2) = tlsBase;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
VirtualProtect((void*)_tlsHandlerAddress, 16u, oldProtect, &oldProtect);
|
||||||
|
FlushInstructionCache(GetCurrentProcess(), (void*)_tlsHandlerAddress, 16u);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private unsafe nint CreateImportHandlerTrampoline(int importIndex)
|
private unsafe nint CreateImportHandlerTrampoline(int importIndex)
|
||||||
{
|
{
|
||||||
void* ptr = VirtualAlloc(null, 192u, 12288u, 64u);
|
void* ptr = VirtualAlloc(null, 192u, 12288u, 64u);
|
||||||
@@ -966,19 +1053,19 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
uint num7 = lpBuffer.Protect & 0xFF;
|
uint num7 = lpBuffer.Protect & 0xFF;
|
||||||
bool flag = lpBuffer.State == 4096 && (lpBuffer.Protect & PAGE_GUARD) == 0 && num7 != PAGE_NOACCESS;
|
bool flag = lpBuffer.State == 4096 && (lpBuffer.Protect & PAGE_GUARD) == 0 && num7 != PAGE_NOACCESS;
|
||||||
bool flag2 = num7 == PAGE_EXECUTE || num7 == 32 || num7 == 64 || num7 == PAGE_EXECUTE_WRITECOPY;
|
bool flag2 = num7 == PAGE_EXECUTE || num7 == 32 || num7 == 64 || num7 == PAGE_EXECUTE_WRITECOPY;
|
||||||
if (flag && flag2 && num6 > num5 + (ulong)TlsPattern.Length)
|
if (flag && flag2 && num6 > num5 + MinTlsPatchInstructionBytes)
|
||||||
{
|
{
|
||||||
byte* ptr = (byte*)num5;
|
byte* ptr = (byte*)num5;
|
||||||
int num8 = (int)(num6 - num5) - TlsPattern.Length;
|
int scanBytes = (int)(num6 - num5);
|
||||||
for (int i = 0; i <= num8; i++)
|
for (int i = 0; i <= scanBytes - MinTlsPatchInstructionBytes; i++)
|
||||||
{
|
{
|
||||||
nint address = (nint)(ptr + i);
|
nint address = (nint)(ptr + i);
|
||||||
if (IsPatternMatch(ptr + i, TlsPattern))
|
int remainingBytes = scanBytes - i;
|
||||||
|
if (TryPatchTlsLoadInstruction(address, ptr + i, remainingBytes))
|
||||||
{
|
{
|
||||||
num3++;
|
num3++;
|
||||||
PatchTlsInstruction(address);
|
|
||||||
}
|
}
|
||||||
else if (TryPatchTlsImmediateStoreInstruction(address, ptr + i))
|
else if (remainingBytes >= 12 && TryPatchTlsImmediateStoreInstruction(address, ptr + i))
|
||||||
{
|
{
|
||||||
num9++;
|
num9++;
|
||||||
}
|
}
|
||||||
@@ -1071,12 +1158,71 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe void PatchTlsInstruction(nint address)
|
private unsafe bool TryPatchTlsLoadInstruction(nint address, byte* source, int availableLength)
|
||||||
|
{
|
||||||
|
if (availableLength < MinTlsPatchInstructionBytes)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var offset = 0;
|
||||||
|
while (offset < availableLength && source[offset] == 0x66)
|
||||||
|
{
|
||||||
|
offset++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset >= availableLength || source[offset] != 0x64)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
offset++;
|
||||||
|
if (offset >= availableLength)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var rex = (byte)0;
|
||||||
|
if (source[offset] >= 0x40 && source[offset] <= 0x4F)
|
||||||
|
{
|
||||||
|
rex = source[offset];
|
||||||
|
offset++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset + 7 > availableLength || source[offset] != 0x8B)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var modRm = source[offset + 1];
|
||||||
|
var sib = source[offset + 2];
|
||||||
|
if ((modRm >> 6) != 0 || (modRm & 7) != 4 || sib != 0x25)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var displacement = *(int*)(source + offset + 3);
|
||||||
|
if (displacement != 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var destinationRegister = ((modRm >> 3) & 7) | (((rex & 4) != 0) ? 8 : 0);
|
||||||
|
var instructionLength = offset + 7;
|
||||||
|
if (instructionLength < MinTlsPatchInstructionBytes)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return PatchTlsLoadInstruction(address, instructionLength, destinationRegister);
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe bool PatchTlsLoadInstruction(nint address, int instructionLength, int destinationRegister)
|
||||||
{
|
{
|
||||||
uint flNewProtect = default(uint);
|
uint flNewProtect = default(uint);
|
||||||
if (!VirtualProtect((void*)address, 9u, 64u, &flNewProtect))
|
if (!VirtualProtect((void*)address, (nuint)instructionLength, 64u, &flNewProtect))
|
||||||
{
|
{
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -1087,20 +1233,29 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
if (num3 < int.MinValue || num3 > int.MaxValue)
|
if (num3 < int.MinValue || num3 > int.MaxValue)
|
||||||
{
|
{
|
||||||
Console.Error.WriteLine($"[LOADER][WARNING] TLS patch out of rel32 range at 0x{address:X16}");
|
Console.Error.WriteLine($"[LOADER][WARNING] TLS patch out of rel32 range at 0x{address:X16}");
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
*(int*)(address + 1) = (int)num3;
|
*(int*)(address + 1) = (int)num3;
|
||||||
*(sbyte*)(address + 5) = 72;
|
var offset = 5;
|
||||||
*(sbyte*)(address + 6) = -119;
|
if (destinationRegister != 0)
|
||||||
*(sbyte*)(address + 7) = -64;
|
{
|
||||||
*(sbyte*)(address + 8) = -112;
|
*(byte*)(address + offset++) = (byte)(0x48 | (destinationRegister >= 8 ? 1 : 0));
|
||||||
|
*(byte*)(address + offset++) = 0x89;
|
||||||
|
*(byte*)(address + offset++) = (byte)(0xC0 | (destinationRegister & 7));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
while (offset < instructionLength)
|
||||||
|
{
|
||||||
|
*(byte*)(address + offset++) = 0x90;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
VirtualProtect((void*)address, 9u, flNewProtect, &flNewProtect);
|
VirtualProtect((void*)address, (nuint)instructionLength, flNewProtect, &flNewProtect);
|
||||||
FlushInstructionCache(GetCurrentProcess(), (void*)address, 9u);
|
FlushInstructionCache(GetCurrentProcess(), (void*)address, (nuint)instructionLength);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1268,6 +1423,439 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool TryStartThread(CpuContext creatorContext, GuestThreadStartRequest request, out string? error)
|
||||||
|
{
|
||||||
|
error = null;
|
||||||
|
if (request.ThreadHandle == 0 || request.EntryPoint < 65536)
|
||||||
|
{
|
||||||
|
error = $"invalid thread start request: handle=0x{request.ThreadHandle:X16} entry=0x{request.EntryPoint:X16}";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!TryCreateGuestThreadState(creatorContext, request, out var thread, out error))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
lock (_guestThreadGate)
|
||||||
|
{
|
||||||
|
_guestThreads[request.ThreadHandle] = thread;
|
||||||
|
_readyGuestThreads.Enqueue(thread);
|
||||||
|
}
|
||||||
|
Console.Error.WriteLine(
|
||||||
|
$"[LOADER][INFO] Scheduled guest thread '{thread.Name}' handle=0x{thread.ThreadHandle:X16} entry=0x{thread.EntryPoint:X16} arg=0x{thread.Argument:X16}");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Pump(CpuContext callerContext, string reason)
|
||||||
|
{
|
||||||
|
_ = callerContext;
|
||||||
|
if (_guestThreadPumpDepth != 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_guestThreadPumpDepth++;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 8; i++)
|
||||||
|
{
|
||||||
|
GuestThreadState? thread = null;
|
||||||
|
lock (_guestThreadGate)
|
||||||
|
{
|
||||||
|
while (_readyGuestThreads.Count > 0)
|
||||||
|
{
|
||||||
|
var candidate = _readyGuestThreads.Dequeue();
|
||||||
|
if (candidate.State == GuestThreadRunState.Ready)
|
||||||
|
{
|
||||||
|
thread = candidate;
|
||||||
|
thread.State = GuestThreadRunState.Running;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (thread == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
RunGuestThread(thread, reason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_guestThreadPumpDepth--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ClearGuestThreads()
|
||||||
|
{
|
||||||
|
lock (_guestThreadGate)
|
||||||
|
{
|
||||||
|
_readyGuestThreads.Clear();
|
||||||
|
_guestThreads.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryCreateGuestThreadState(CpuContext creatorContext, GuestThreadStartRequest request, out GuestThreadState thread, out string? error)
|
||||||
|
{
|
||||||
|
thread = null!;
|
||||||
|
if (!TryGetVirtualMemory(creatorContext, out var virtualMemory))
|
||||||
|
{
|
||||||
|
error = "creator context memory is not backed by IVirtualMemory";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!TryMapGuestThreadRegion(virtualMemory, GuestThreadStackBaseAddress, GuestThreadStackSize, ProgramHeaderFlags.Read | ProgramHeaderFlags.Write, out var stackBase, out error))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!TryMapGuestThreadRegion(virtualMemory, GuestThreadTlsBaseAddress, GuestThreadTlsSize, ProgramHeaderFlags.Read | ProgramHeaderFlags.Write, out var tlsBase, out error))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var trackedMemory = new TrackedCpuMemory(virtualMemory);
|
||||||
|
var context = new CpuContext(trackedMemory, creatorContext.TargetGeneration)
|
||||||
|
{
|
||||||
|
Rip = request.EntryPoint,
|
||||||
|
Rflags = 0x202,
|
||||||
|
FsBase = tlsBase,
|
||||||
|
GsBase = tlsBase,
|
||||||
|
};
|
||||||
|
context[CpuRegister.Rsp] = stackBase + GuestThreadStackSize - sizeof(ulong);
|
||||||
|
context[CpuRegister.Rdi] = request.Argument;
|
||||||
|
context[CpuRegister.Rsi] = 0;
|
||||||
|
context[CpuRegister.Rdx] = 0;
|
||||||
|
context[CpuRegister.Rcx] = 0;
|
||||||
|
context[CpuRegister.R8] = 0;
|
||||||
|
context[CpuRegister.R9] = 0;
|
||||||
|
if (!InitializeGuestThreadFrame(context) || !InitializeGuestThreadTls(context, tlsBase))
|
||||||
|
{
|
||||||
|
error = "failed to initialize guest thread stack/TLS";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
thread = new GuestThreadState
|
||||||
|
{
|
||||||
|
ThreadHandle = request.ThreadHandle,
|
||||||
|
EntryPoint = request.EntryPoint,
|
||||||
|
Argument = request.Argument,
|
||||||
|
Name = string.IsNullOrWhiteSpace(request.Name) ? $"Thread-{request.ThreadHandle:X}" : request.Name,
|
||||||
|
Context = context,
|
||||||
|
State = GuestThreadRunState.Ready,
|
||||||
|
};
|
||||||
|
error = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryGetVirtualMemory(CpuContext context, out IVirtualMemory virtualMemory)
|
||||||
|
{
|
||||||
|
if (context.Memory is IVirtualMemory directMemory)
|
||||||
|
{
|
||||||
|
virtualMemory = directMemory;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (context.Memory is TrackedCpuMemory trackedMemory && trackedMemory.Inner is IVirtualMemory trackedInner)
|
||||||
|
{
|
||||||
|
virtualMemory = trackedInner;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtualMemory = null!;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryMapGuestThreadRegion(
|
||||||
|
IVirtualMemory virtualMemory,
|
||||||
|
ulong baseAddress,
|
||||||
|
ulong size,
|
||||||
|
ProgramHeaderFlags protection,
|
||||||
|
out ulong mappedBase,
|
||||||
|
out string? error)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 64; i++)
|
||||||
|
{
|
||||||
|
var candidateBase = baseAddress - ((ulong)i * GuestThreadRegionStride);
|
||||||
|
if (!IsGuestThreadRegionFree(virtualMemory, candidateBase, size))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
virtualMemory.Map(
|
||||||
|
candidateBase,
|
||||||
|
size,
|
||||||
|
fileOffset: 0,
|
||||||
|
fileData: ReadOnlySpan<byte>.Empty,
|
||||||
|
protection: protection);
|
||||||
|
mappedBase = candidateBase;
|
||||||
|
error = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mappedBase = 0;
|
||||||
|
error = $"failed to map guest thread region near 0x{baseAddress:X16}";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsGuestThreadRegionFree(IVirtualMemory virtualMemory, ulong candidateBase, ulong size)
|
||||||
|
{
|
||||||
|
var candidateEnd = candidateBase + size;
|
||||||
|
foreach (var region in virtualMemory.SnapshotRegions())
|
||||||
|
{
|
||||||
|
var regionStart = region.VirtualAddress;
|
||||||
|
var regionEnd = regionStart + region.MemorySize;
|
||||||
|
if (candidateBase < regionEnd && regionStart < candidateEnd)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool InitializeGuestThreadFrame(CpuContext context)
|
||||||
|
{
|
||||||
|
var stackTop = context[CpuRegister.Rsp] + sizeof(ulong);
|
||||||
|
var sentinelFrame = AlignDown(stackTop - 0x20, 16);
|
||||||
|
var seedRsp = sentinelFrame - sizeof(ulong);
|
||||||
|
if (!context.TryWriteUInt64(sentinelFrame, 0) ||
|
||||||
|
!context.TryWriteUInt64(sentinelFrame + sizeof(ulong), 0) ||
|
||||||
|
!context.TryWriteUInt64(seedRsp, 0))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
context[CpuRegister.Rbp] = sentinelFrame;
|
||||||
|
context[CpuRegister.Rsp] = seedRsp;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool InitializeGuestThreadTls(CpuContext context, ulong tlsBase)
|
||||||
|
{
|
||||||
|
return context.TryWriteUInt64(tlsBase + 0x00, tlsBase) &&
|
||||||
|
context.TryWriteUInt64(tlsBase + 0x10, tlsBase) &&
|
||||||
|
context.TryWriteUInt64(tlsBase + 0x28, 0xC0DEC0DECAFEBABEUL);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RunGuestThread(GuestThreadState thread, string reason)
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine(
|
||||||
|
$"[LOADER][INFO] Pumping guest thread '{thread.Name}' reason={reason} entry=0x{thread.EntryPoint:X16}");
|
||||||
|
var previousContext = _cpuContext;
|
||||||
|
var previousTlsBase = _tlsBaseAddress;
|
||||||
|
var previousOwnsTlsBase = _ownsTlsBaseAddress;
|
||||||
|
var previousForcedGuestExit = _forcedGuestExit;
|
||||||
|
var previousLastError = LastError;
|
||||||
|
var previousGuestThreadHandle = GuestThreadExecution.EnterGuestThread(thread.ThreadHandle);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_cpuContext = thread.Context;
|
||||||
|
_forcedGuestExit = false;
|
||||||
|
LastError = null;
|
||||||
|
BindTlsBase(thread.Context);
|
||||||
|
var exitReason = ExecuteGuestThreadEntry(thread.Context, thread.EntryPoint, thread.Name, out var blockReason);
|
||||||
|
lock (_guestThreadGate)
|
||||||
|
{
|
||||||
|
switch (exitReason)
|
||||||
|
{
|
||||||
|
case GuestNativeCallExitReason.Returned:
|
||||||
|
thread.State = GuestThreadRunState.Exited;
|
||||||
|
break;
|
||||||
|
case GuestNativeCallExitReason.Blocked:
|
||||||
|
thread.State = GuestThreadRunState.Blocked;
|
||||||
|
thread.BlockReason = blockReason;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
thread.State = GuestThreadRunState.Faulted;
|
||||||
|
thread.BlockReason = blockReason;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Console.Error.WriteLine(
|
||||||
|
$"[LOADER][INFO] Guest thread '{thread.Name}' state={thread.State} reason={blockReason ?? "none"}");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
GuestThreadExecution.RestoreGuestThread(previousGuestThreadHandle);
|
||||||
|
_cpuContext = previousContext;
|
||||||
|
_tlsBaseAddress = previousTlsBase;
|
||||||
|
_ownsTlsBaseAddress = previousOwnsTlsBase;
|
||||||
|
if (_tlsBaseAddress != 0)
|
||||||
|
{
|
||||||
|
SeedTlsLayout(_tlsBaseAddress);
|
||||||
|
UpdateTlsHandlerBase(_tlsBaseAddress);
|
||||||
|
}
|
||||||
|
_forcedGuestExit = previousForcedGuestExit;
|
||||||
|
LastError = previousLastError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe GuestNativeCallExitReason ExecuteGuestThreadEntry(CpuContext context, ulong entryPoint, string name, out string? reason)
|
||||||
|
{
|
||||||
|
reason = null;
|
||||||
|
if (context[CpuRegister.Rsp] == 0)
|
||||||
|
{
|
||||||
|
reason = "guest thread stack pointer is zero";
|
||||||
|
return GuestNativeCallExitReason.Exception;
|
||||||
|
}
|
||||||
|
void* ptr = VirtualAlloc(null, 256u, 12288u, 64u);
|
||||||
|
if (ptr == null)
|
||||||
|
{
|
||||||
|
reason = "failed to allocate executable memory for guest thread stub";
|
||||||
|
return GuestNativeCallExitReason.Exception;
|
||||||
|
}
|
||||||
|
var previousSentinel = _entryReturnSentinelRip;
|
||||||
|
var previousHostRspSlotValue = _hostRspSlotStorage != 0 ? *(ulong*)_hostRspSlotStorage : 0;
|
||||||
|
var previousYieldRequested = _guestThreadYieldRequested;
|
||||||
|
var previousYieldReason = _guestThreadYieldReason;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
byte* ptr2 = (byte*)ptr;
|
||||||
|
ulong hostRspSlot = (ulong)ptr + 224uL;
|
||||||
|
int offset = 0;
|
||||||
|
ptr2[offset++] = 83;
|
||||||
|
ptr2[offset++] = 85;
|
||||||
|
ptr2[offset++] = 87;
|
||||||
|
ptr2[offset++] = 86;
|
||||||
|
ptr2[offset++] = 73;
|
||||||
|
ptr2[offset++] = 186;
|
||||||
|
*(ulong*)(ptr2 + offset) = hostRspSlot;
|
||||||
|
offset += 8;
|
||||||
|
ptr2[offset++] = 73;
|
||||||
|
ptr2[offset++] = 137;
|
||||||
|
ptr2[offset++] = 34;
|
||||||
|
ptr2[offset++] = 72;
|
||||||
|
ptr2[offset++] = 184;
|
||||||
|
*(ulong*)(ptr2 + offset) = context[CpuRegister.Rsp];
|
||||||
|
offset += 8;
|
||||||
|
ptr2[offset++] = 72;
|
||||||
|
ptr2[offset++] = 137;
|
||||||
|
ptr2[offset++] = 196;
|
||||||
|
ptr2[offset++] = 72;
|
||||||
|
ptr2[offset++] = 131;
|
||||||
|
ptr2[offset++] = 236;
|
||||||
|
ptr2[offset++] = 8;
|
||||||
|
ptr2[offset++] = 72;
|
||||||
|
ptr2[offset++] = 189;
|
||||||
|
*(ulong*)(ptr2 + offset) = context[CpuRegister.Rbp];
|
||||||
|
offset += 8;
|
||||||
|
ptr2[offset++] = 72;
|
||||||
|
ptr2[offset++] = 184;
|
||||||
|
*(ulong*)(ptr2 + offset) = context[CpuRegister.Rdi];
|
||||||
|
offset += 8;
|
||||||
|
ptr2[offset++] = 72;
|
||||||
|
ptr2[offset++] = 137;
|
||||||
|
ptr2[offset++] = 199;
|
||||||
|
ptr2[offset++] = 72;
|
||||||
|
ptr2[offset++] = 184;
|
||||||
|
*(ulong*)(ptr2 + offset) = context[CpuRegister.Rsi];
|
||||||
|
offset += 8;
|
||||||
|
ptr2[offset++] = 72;
|
||||||
|
ptr2[offset++] = 137;
|
||||||
|
ptr2[offset++] = 198;
|
||||||
|
ptr2[offset++] = 72;
|
||||||
|
ptr2[offset++] = 184;
|
||||||
|
*(ulong*)(ptr2 + offset) = context[CpuRegister.Rdx];
|
||||||
|
offset += 8;
|
||||||
|
ptr2[offset++] = 72;
|
||||||
|
ptr2[offset++] = 137;
|
||||||
|
ptr2[offset++] = 194;
|
||||||
|
ptr2[offset++] = 72;
|
||||||
|
ptr2[offset++] = 184;
|
||||||
|
*(ulong*)(ptr2 + offset) = context[CpuRegister.Rcx];
|
||||||
|
offset += 8;
|
||||||
|
ptr2[offset++] = 72;
|
||||||
|
ptr2[offset++] = 137;
|
||||||
|
ptr2[offset++] = 193;
|
||||||
|
ptr2[offset++] = 72;
|
||||||
|
ptr2[offset++] = 184;
|
||||||
|
*(ulong*)(ptr2 + offset) = entryPoint;
|
||||||
|
offset += 8;
|
||||||
|
ptr2[offset++] = byte.MaxValue;
|
||||||
|
ptr2[offset++] = 208;
|
||||||
|
int sentinelOffset = offset + 4;
|
||||||
|
ptr2[offset++] = 72;
|
||||||
|
ptr2[offset++] = 131;
|
||||||
|
ptr2[offset++] = 196;
|
||||||
|
ptr2[offset++] = 8;
|
||||||
|
ptr2[offset++] = 73;
|
||||||
|
ptr2[offset++] = 186;
|
||||||
|
*(ulong*)(ptr2 + offset) = hostRspSlot;
|
||||||
|
offset += 8;
|
||||||
|
ptr2[offset++] = 73;
|
||||||
|
ptr2[offset++] = 139;
|
||||||
|
ptr2[offset++] = 34;
|
||||||
|
ptr2[offset++] = 94;
|
||||||
|
ptr2[offset++] = 95;
|
||||||
|
ptr2[offset++] = 93;
|
||||||
|
ptr2[offset++] = 91;
|
||||||
|
ptr2[offset++] = 195;
|
||||||
|
ulong sentinel = (ulong)ptr + (ulong)sentinelOffset;
|
||||||
|
_entryReturnSentinelRip = sentinel;
|
||||||
|
if (!context.TryWriteUInt64(context[CpuRegister.Rsp], sentinel))
|
||||||
|
{
|
||||||
|
reason = $"failed to patch guest thread return sentinel at 0x{context[CpuRegister.Rsp]:X16}";
|
||||||
|
return GuestNativeCallExitReason.Exception;
|
||||||
|
}
|
||||||
|
uint oldProtect = default(uint);
|
||||||
|
VirtualProtect(ptr, 256u, 64u, &oldProtect);
|
||||||
|
FlushInstructionCache(GetCurrentProcess(), ptr, 256u);
|
||||||
|
if (_hostRspSlotStorage != 0)
|
||||||
|
{
|
||||||
|
*(ulong*)_hostRspSlotStorage = hostRspSlot;
|
||||||
|
}
|
||||||
|
_guestThreadYieldRequested = false;
|
||||||
|
_guestThreadYieldReason = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var nativeReturn = Marshal.GetDelegateForFunctionPointer<NativeEntryDelegate>((nint)ptr)();
|
||||||
|
if (_guestThreadYieldRequested)
|
||||||
|
{
|
||||||
|
reason = _guestThreadYieldReason ?? "guest thread blocked";
|
||||||
|
return GuestNativeCallExitReason.Blocked;
|
||||||
|
}
|
||||||
|
if (_forcedGuestExit)
|
||||||
|
{
|
||||||
|
reason = LastError ?? "guest thread forced exit";
|
||||||
|
return GuestNativeCallExitReason.ForcedExit;
|
||||||
|
}
|
||||||
|
reason = $"returned 0x{nativeReturn:X8}";
|
||||||
|
return GuestNativeCallExitReason.Returned;
|
||||||
|
}
|
||||||
|
catch (AccessViolationException ex)
|
||||||
|
{
|
||||||
|
reason = "access violation: " + ex.Message;
|
||||||
|
return GuestNativeCallExitReason.Exception;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
reason = ex.GetType().Name + ": " + ex.Message;
|
||||||
|
return GuestNativeCallExitReason.Exception;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_entryReturnSentinelRip = previousSentinel;
|
||||||
|
if (_hostRspSlotStorage != 0)
|
||||||
|
{
|
||||||
|
*(ulong*)_hostRspSlotStorage = previousHostRspSlotValue;
|
||||||
|
}
|
||||||
|
_guestThreadYieldRequested = previousYieldRequested;
|
||||||
|
_guestThreadYieldReason = previousYieldReason;
|
||||||
|
VirtualFree(ptr, 0u, 32768u);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ulong AlignDown(ulong value, ulong alignment)
|
||||||
|
{
|
||||||
|
if (alignment == 0)
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return value & ~(alignment - 1);
|
||||||
|
}
|
||||||
|
|
||||||
private unsafe bool ExecuteEntry(CpuContext context, ulong entryPoint, out OrbisGen2Result result)
|
private unsafe bool ExecuteEntry(CpuContext context, ulong entryPoint, out OrbisGen2Result result)
|
||||||
{
|
{
|
||||||
Console.Error.WriteLine($"[LOADER][INFO] ExecuteEntry starting at 0x{entryPoint:X16}");
|
Console.Error.WriteLine($"[LOADER][INFO] ExecuteEntry starting at 0x{entryPoint:X16}");
|
||||||
@@ -1315,6 +1903,10 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
ptr2[num3++] = 236;
|
ptr2[num3++] = 236;
|
||||||
ptr2[num3++] = 8;
|
ptr2[num3++] = 8;
|
||||||
ptr2[num3++] = 72;
|
ptr2[num3++] = 72;
|
||||||
|
ptr2[num3++] = 189;
|
||||||
|
*(ulong*)(ptr2 + num3) = context[CpuRegister.Rbp];
|
||||||
|
num3 += 8;
|
||||||
|
ptr2[num3++] = 72;
|
||||||
ptr2[num3++] = 184;
|
ptr2[num3++] = 184;
|
||||||
*(ulong*)(ptr2 + num3) = context[CpuRegister.Rdi];
|
*(ulong*)(ptr2 + num3) = context[CpuRegister.Rdi];
|
||||||
num3 += 8;
|
num3 += 8;
|
||||||
@@ -1393,6 +1985,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
{
|
{
|
||||||
num6 = Marshal.GetDelegateForFunctionPointer<NativeEntryDelegate>((nint)ptr)();
|
num6 = Marshal.GetDelegateForFunctionPointer<NativeEntryDelegate>((nint)ptr)();
|
||||||
Console.Error.WriteLine($"[LOADER][INFO] Guest returned: {num6}");
|
Console.Error.WriteLine($"[LOADER][INFO] Guest returned: {num6}");
|
||||||
|
Pump(context, "entry_return");
|
||||||
}
|
}
|
||||||
catch (AccessViolationException ex)
|
catch (AccessViolationException ex)
|
||||||
{
|
{
|
||||||
@@ -1610,11 +2203,13 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
_selfHandle.Free();
|
_selfHandle.Free();
|
||||||
_selfHandlePtr = 0;
|
_selfHandlePtr = 0;
|
||||||
}
|
}
|
||||||
if (_ownsTlsBaseAddress && _tlsBaseAddress != 0)
|
if (_ownedTlsBaseAddress != 0)
|
||||||
{
|
{
|
||||||
VirtualFree((void*)_tlsBaseAddress, 0u, 32768u);
|
VirtualFree((void*)_ownedTlsBaseAddress, 0u, 32768u);
|
||||||
|
_ownedTlsBaseAddress = 0;
|
||||||
}
|
}
|
||||||
_tlsBaseAddress = 0;
|
_tlsBaseAddress = 0;
|
||||||
|
_ownsTlsBaseAddress = false;
|
||||||
if (_tlsModuleBases.Count > 0)
|
if (_tlsModuleBases.Count > 0)
|
||||||
{
|
{
|
||||||
foreach (var (_, num3) in _tlsModuleBases)
|
foreach (var (_, num3) in _tlsModuleBases)
|
||||||
|
|||||||
@@ -0,0 +1,107 @@
|
|||||||
|
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
namespace SharpEmu.HLE;
|
||||||
|
|
||||||
|
public readonly record struct GuestThreadStartRequest(
|
||||||
|
ulong ThreadHandle,
|
||||||
|
ulong EntryPoint,
|
||||||
|
ulong Argument,
|
||||||
|
ulong AttributeAddress,
|
||||||
|
string Name);
|
||||||
|
|
||||||
|
public interface IGuestThreadScheduler
|
||||||
|
{
|
||||||
|
bool TryStartThread(CpuContext creatorContext, GuestThreadStartRequest request, out string? error);
|
||||||
|
|
||||||
|
void Pump(CpuContext callerContext, string reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class GuestThreadExecution
|
||||||
|
{
|
||||||
|
[ThreadStatic]
|
||||||
|
private static ulong _currentGuestThreadHandle;
|
||||||
|
|
||||||
|
[ThreadStatic]
|
||||||
|
private static string? _pendingBlockReason;
|
||||||
|
|
||||||
|
[ThreadStatic]
|
||||||
|
private static bool _pendingEntryExit;
|
||||||
|
|
||||||
|
[ThreadStatic]
|
||||||
|
private static int _pendingEntryExitStatus;
|
||||||
|
|
||||||
|
[ThreadStatic]
|
||||||
|
private static string? _pendingEntryExitReason;
|
||||||
|
|
||||||
|
public static IGuestThreadScheduler? Scheduler { get; set; }
|
||||||
|
|
||||||
|
public static bool IsGuestThread => _currentGuestThreadHandle != 0;
|
||||||
|
|
||||||
|
public static ulong CurrentGuestThreadHandle => _currentGuestThreadHandle;
|
||||||
|
|
||||||
|
public static ulong EnterGuestThread(ulong threadHandle)
|
||||||
|
{
|
||||||
|
var previous = _currentGuestThreadHandle;
|
||||||
|
_currentGuestThreadHandle = threadHandle;
|
||||||
|
_pendingBlockReason = null;
|
||||||
|
_pendingEntryExit = false;
|
||||||
|
_pendingEntryExitStatus = 0;
|
||||||
|
_pendingEntryExitReason = null;
|
||||||
|
return previous;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RestoreGuestThread(ulong previousThreadHandle)
|
||||||
|
{
|
||||||
|
_currentGuestThreadHandle = previousThreadHandle;
|
||||||
|
_pendingBlockReason = null;
|
||||||
|
_pendingEntryExit = false;
|
||||||
|
_pendingEntryExitStatus = 0;
|
||||||
|
_pendingEntryExitReason = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool RequestCurrentThreadBlock(string reason)
|
||||||
|
{
|
||||||
|
if (!IsGuestThread)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_pendingBlockReason = string.IsNullOrWhiteSpace(reason) ? "guest_thread_blocked" : reason;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryConsumeCurrentThreadBlock(out string reason)
|
||||||
|
{
|
||||||
|
reason = _pendingBlockReason ?? string.Empty;
|
||||||
|
if (string.IsNullOrEmpty(reason))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_pendingBlockReason = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RequestCurrentEntryExit(string reason, int status)
|
||||||
|
{
|
||||||
|
_pendingEntryExit = true;
|
||||||
|
_pendingEntryExitStatus = status;
|
||||||
|
_pendingEntryExitReason = string.IsNullOrWhiteSpace(reason) ? "guest_entry_exit" : reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryConsumeCurrentEntryExit(out int status, out string reason)
|
||||||
|
{
|
||||||
|
status = _pendingEntryExitStatus;
|
||||||
|
reason = _pendingEntryExitReason ?? string.Empty;
|
||||||
|
if (!_pendingEntryExit)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_pendingEntryExit = false;
|
||||||
|
_pendingEntryExitStatus = 0;
|
||||||
|
_pendingEntryExitReason = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,6 +16,9 @@ public static class AmprExports
|
|||||||
private const ulong CommandBufferAux0Offset = 0x18;
|
private const ulong CommandBufferAux0Offset = 0x18;
|
||||||
private const ulong CommandBufferAux1Offset = 0x20;
|
private const ulong CommandBufferAux1Offset = 0x20;
|
||||||
private const ulong ReadFileRecordSize = 0x30;
|
private const ulong ReadFileRecordSize = 0x30;
|
||||||
|
private const ulong KernelEventQueueRecordSize = 0x30;
|
||||||
|
private const uint ReadFileRecordType = 1;
|
||||||
|
private const uint KernelEventQueueRecordType = 2;
|
||||||
private static readonly ConcurrentDictionary<ulong, CommandBufferState> _commandBuffers = new();
|
private static readonly ConcurrentDictionary<ulong, CommandBufferState> _commandBuffers = new();
|
||||||
|
|
||||||
private sealed class CommandBufferState
|
private sealed class CommandBufferState
|
||||||
@@ -257,6 +260,108 @@ public static class AmprExports
|
|||||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SysAbiExport(
|
||||||
|
Nid = "vWU-odnS+fU",
|
||||||
|
ExportName = "sceAmprMeasureCommandSizeReadFile",
|
||||||
|
Target = Generation.Gen5,
|
||||||
|
LibraryName = "libSceAmpr")]
|
||||||
|
public static int MeasureCommandSizeReadFile(CpuContext ctx)
|
||||||
|
{
|
||||||
|
TraceAmpr(ctx, "measure_read_file", 0, ReadFileRecordSize, 0);
|
||||||
|
ctx[CpuRegister.Rax] = ReadFileRecordSize;
|
||||||
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
[SysAbiExport(
|
||||||
|
Nid = "sSAUCCU1dv4",
|
||||||
|
ExportName = "sceAmprMeasureCommandSizeWriteKernelEventQueue_04_00",
|
||||||
|
Target = Generation.Gen5,
|
||||||
|
LibraryName = "libSceAmpr")]
|
||||||
|
public static int MeasureCommandSizeWriteKernelEventQueue0400(CpuContext ctx)
|
||||||
|
{
|
||||||
|
TraceAmpr(ctx, "measure_write_equeue", 0, KernelEventQueueRecordSize, 0);
|
||||||
|
ctx[CpuRegister.Rax] = KernelEventQueueRecordSize;
|
||||||
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
[SysAbiExport(
|
||||||
|
Nid = "tZDDEo2tE5k",
|
||||||
|
ExportName = "sceAmprCommandBufferGetSize",
|
||||||
|
Target = Generation.Gen5,
|
||||||
|
LibraryName = "libSceAmpr")]
|
||||||
|
public static int CommandBufferGetSize(CpuContext ctx)
|
||||||
|
{
|
||||||
|
var commandBuffer = ctx[CpuRegister.Rdi];
|
||||||
|
if (commandBuffer == 0)
|
||||||
|
{
|
||||||
|
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TryGetCommandBufferState(ctx, commandBuffer, out _, out var size, out _))
|
||||||
|
{
|
||||||
|
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
TraceAmpr(ctx, "get_size", commandBuffer, size, 0);
|
||||||
|
ctx[CpuRegister.Rax] = size;
|
||||||
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
[SysAbiExport(
|
||||||
|
Nid = "GnxKOHEawhk",
|
||||||
|
ExportName = "sceAmprCommandBufferGetCurrentOffset",
|
||||||
|
Target = Generation.Gen5,
|
||||||
|
LibraryName = "libSceAmpr")]
|
||||||
|
public static int CommandBufferGetCurrentOffset(CpuContext ctx)
|
||||||
|
{
|
||||||
|
var commandBuffer = ctx[CpuRegister.Rdi];
|
||||||
|
if (commandBuffer == 0)
|
||||||
|
{
|
||||||
|
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TryGetCommandBufferOffset(ctx, commandBuffer, out var offset))
|
||||||
|
{
|
||||||
|
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
TraceAmpr(ctx, "get_offset", commandBuffer, offset, 0);
|
||||||
|
ctx[CpuRegister.Rax] = offset;
|
||||||
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
[SysAbiExport(
|
||||||
|
Nid = "H896Pt-yB4I",
|
||||||
|
ExportName = "sceAmprCommandBufferWriteKernelEventQueue_04_00",
|
||||||
|
Target = Generation.Gen5,
|
||||||
|
LibraryName = "libSceAmpr")]
|
||||||
|
public static int CommandBufferWriteKernelEventQueue0400(CpuContext ctx)
|
||||||
|
{
|
||||||
|
var commandBuffer = ctx[CpuRegister.Rdi];
|
||||||
|
var equeue = ctx[CpuRegister.Rsi];
|
||||||
|
var ident = ctx[CpuRegister.Rdx];
|
||||||
|
var filter = ctx[CpuRegister.Rcx];
|
||||||
|
var userData = ctx[CpuRegister.R8];
|
||||||
|
var data = ctx[CpuRegister.R9];
|
||||||
|
|
||||||
|
if (commandBuffer == 0)
|
||||||
|
{
|
||||||
|
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
var extra = 0UL;
|
||||||
|
_ = ctx.TryReadUInt64(ctx[CpuRegister.Rsp] + sizeof(ulong), out extra);
|
||||||
|
|
||||||
|
if (!AppendKernelEventQueueRecord(ctx, commandBuffer, equeue, ident, filter, userData, data, extra))
|
||||||
|
{
|
||||||
|
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
TraceAmpr(ctx, "write_equeue", commandBuffer, equeue, ident);
|
||||||
|
ctx[CpuRegister.Rax] = 0;
|
||||||
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||||
|
}
|
||||||
|
|
||||||
private static bool InitializeCommandBuffer(
|
private static bool InitializeCommandBuffer(
|
||||||
CpuContext ctx,
|
CpuContext ctx,
|
||||||
ulong commandBuffer,
|
ulong commandBuffer,
|
||||||
@@ -350,6 +455,22 @@ public static class AmprExports
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool TryGetCommandBufferOffset(CpuContext ctx, ulong commandBuffer, out ulong offset)
|
||||||
|
{
|
||||||
|
if (!TryGetCommandBufferState(ctx, commandBuffer, out _, out _, out var state) || state is null)
|
||||||
|
{
|
||||||
|
offset = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (state)
|
||||||
|
{
|
||||||
|
offset = state.WriteOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private static int TryReadFileToGuestMemory(
|
private static int TryReadFileToGuestMemory(
|
||||||
CpuContext ctx,
|
CpuContext ctx,
|
||||||
string hostPath,
|
string hostPath,
|
||||||
@@ -420,23 +541,54 @@ public static class AmprExports
|
|||||||
ulong fileOffset,
|
ulong fileOffset,
|
||||||
ulong bytesRead)
|
ulong bytesRead)
|
||||||
{
|
{
|
||||||
if (!TryGetCommandBufferState(ctx, commandBuffer, out var buffer, out var capacity, out var state) || state is null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Span<byte> record = stackalloc byte[(int)ReadFileRecordSize];
|
Span<byte> record = stackalloc byte[(int)ReadFileRecordSize];
|
||||||
record.Clear();
|
record.Clear();
|
||||||
BinaryPrimitives.WriteUInt32LittleEndian(record[0x00..], 1);
|
BinaryPrimitives.WriteUInt32LittleEndian(record[0x00..], ReadFileRecordType);
|
||||||
BinaryPrimitives.WriteUInt32LittleEndian(record[0x04..], fileId);
|
BinaryPrimitives.WriteUInt32LittleEndian(record[0x04..], fileId);
|
||||||
BinaryPrimitives.WriteUInt64LittleEndian(record[0x08..], destination);
|
BinaryPrimitives.WriteUInt64LittleEndian(record[0x08..], destination);
|
||||||
BinaryPrimitives.WriteUInt64LittleEndian(record[0x10..], size);
|
BinaryPrimitives.WriteUInt64LittleEndian(record[0x10..], size);
|
||||||
BinaryPrimitives.WriteUInt64LittleEndian(record[0x18..], fileOffset);
|
BinaryPrimitives.WriteUInt64LittleEndian(record[0x18..], fileOffset);
|
||||||
BinaryPrimitives.WriteUInt64LittleEndian(record[0x20..], bytesRead);
|
BinaryPrimitives.WriteUInt64LittleEndian(record[0x20..], bytesRead);
|
||||||
|
|
||||||
|
return AppendCommandBufferRecord(ctx, commandBuffer, record);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool AppendKernelEventQueueRecord(
|
||||||
|
CpuContext ctx,
|
||||||
|
ulong commandBuffer,
|
||||||
|
ulong equeue,
|
||||||
|
ulong ident,
|
||||||
|
ulong filter,
|
||||||
|
ulong userData,
|
||||||
|
ulong data,
|
||||||
|
ulong extra)
|
||||||
|
{
|
||||||
|
Span<byte> record = stackalloc byte[(int)KernelEventQueueRecordSize];
|
||||||
|
record.Clear();
|
||||||
|
BinaryPrimitives.WriteUInt32LittleEndian(record[0x00..], KernelEventQueueRecordType);
|
||||||
|
BinaryPrimitives.WriteUInt32LittleEndian(record[0x04..], unchecked((uint)filter));
|
||||||
|
BinaryPrimitives.WriteUInt64LittleEndian(record[0x08..], equeue);
|
||||||
|
BinaryPrimitives.WriteUInt64LittleEndian(record[0x10..], ident);
|
||||||
|
BinaryPrimitives.WriteUInt64LittleEndian(record[0x18..], userData);
|
||||||
|
BinaryPrimitives.WriteUInt64LittleEndian(record[0x20..], data);
|
||||||
|
BinaryPrimitives.WriteUInt64LittleEndian(record[0x28..], extra);
|
||||||
|
|
||||||
|
return AppendCommandBufferRecord(ctx, commandBuffer, record);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool AppendCommandBufferRecord(CpuContext ctx, ulong commandBuffer, ReadOnlySpan<byte> record)
|
||||||
|
{
|
||||||
|
if (!TryGetCommandBufferState(ctx, commandBuffer, out _, out _, out var state) || state is null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var recordSize = (ulong)record.Length;
|
||||||
lock (state)
|
lock (state)
|
||||||
{
|
{
|
||||||
if (state.Buffer == 0 || state.WriteOffset + ReadFileRecordSize > state.Size)
|
if (state.Buffer == 0 ||
|
||||||
|
state.WriteOffset > state.Size ||
|
||||||
|
recordSize > state.Size - state.WriteOffset)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -446,7 +598,7 @@ public static class AmprExports
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
state.WriteOffset += ReadFileRecordSize;
|
state.WriteOffset += recordSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -166,11 +166,18 @@ public static class KernelEventQueueCompatExports
|
|||||||
public static int KernelWaitEqueue(CpuContext ctx)
|
public static int KernelWaitEqueue(CpuContext ctx)
|
||||||
{
|
{
|
||||||
var outCountAddress = ctx[CpuRegister.Rcx];
|
var outCountAddress = ctx[CpuRegister.Rcx];
|
||||||
|
var timeoutAddress = ctx[CpuRegister.R8];
|
||||||
if (outCountAddress != 0 && !TryWriteUInt32(ctx, outCountAddress, 0))
|
if (outCountAddress != 0 && !TryWriteUInt32(ctx, outCountAddress, 0))
|
||||||
{
|
{
|
||||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (timeoutAddress == 0 && GuestThreadExecution.RequestCurrentThreadBlock("sceKernelWaitEqueue"))
|
||||||
|
{
|
||||||
|
TraceEventQueue(ctx, "wait-block", ctx[CpuRegister.Rdi]);
|
||||||
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||||
|
}
|
||||||
|
|
||||||
TraceEventQueue(ctx, "wait", ctx[CpuRegister.Rdi]);
|
TraceEventQueue(ctx, "wait", ctx[CpuRegister.Rdi]);
|
||||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,7 +55,10 @@ public static class KernelExports
|
|||||||
LibraryName = "libKernel")]
|
LibraryName = "libKernel")]
|
||||||
public static int Exit(CpuContext ctx)
|
public static int Exit(CpuContext ctx)
|
||||||
{
|
{
|
||||||
_ = ctx;
|
var status = unchecked((int)ctx[CpuRegister.Rdi]);
|
||||||
|
Console.Error.WriteLine($"[LOADER][INFO] exit(status={status})");
|
||||||
|
GuestThreadExecution.RequestCurrentEntryExit("exit", status);
|
||||||
|
ctx[CpuRegister.Rax] = unchecked((ulong)status);
|
||||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,7 +71,8 @@ public static class KernelExports
|
|||||||
{
|
{
|
||||||
var status = unchecked((int)ctx[CpuRegister.Rdi]);
|
var status = unchecked((int)ctx[CpuRegister.Rdi]);
|
||||||
Console.Error.WriteLine($"[LOADER][INFO] catchReturnFromMain(status={status})");
|
Console.Error.WriteLine($"[LOADER][INFO] catchReturnFromMain(status={status})");
|
||||||
ctx[CpuRegister.Rax] = 0;
|
GuestThreadExecution.RequestCurrentEntryExit("catchReturnFromMain", status);
|
||||||
|
ctx[CpuRegister.Rax] = unchecked((ulong)status);
|
||||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,6 +215,19 @@ public static class KernelExports
|
|||||||
$"[LOADER][TRACE] pthread_create: out=0x{threadIdAddress:X16} attr=0x{attrAddress:X16} entry=0x{entryAddress:X16} arg=0x{argument:X16} name_ptr=0x{nameAddress:X16} name='{name}' -> thread=0x{threadHandle:X16}");
|
$"[LOADER][TRACE] pthread_create: out=0x{threadIdAddress:X16} attr=0x{attrAddress:X16} entry=0x{entryAddress:X16} arg=0x{argument:X16} name_ptr=0x{nameAddress:X16} name='{name}' -> thread=0x{threadHandle:X16}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var scheduler = GuestThreadExecution.Scheduler;
|
||||||
|
if (scheduler is not null && entryAddress != 0)
|
||||||
|
{
|
||||||
|
var request = new GuestThreadStartRequest(threadHandle, entryAddress, argument, attrAddress, name);
|
||||||
|
if (!scheduler.TryStartThread(ctx, request, out var error))
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine(
|
||||||
|
$"[LOADER][ERROR] pthread_create: failed to schedule guest thread '{name}' entry=0x{entryAddress:X16}: {error}");
|
||||||
|
ctx[CpuRegister.Rax] = unchecked((ulong)(int)OrbisGen2Result.ORBIS_GEN2_ERROR_TRY_AGAIN);
|
||||||
|
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_TRY_AGAIN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ctx[CpuRegister.Rax] = 0;
|
ctx[CpuRegister.Rax] = 0;
|
||||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||||
}
|
}
|
||||||
@@ -225,6 +242,16 @@ public static class KernelExports
|
|||||||
return PthreadCreate(ctx);
|
return PthreadCreate(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SysAbiExport(
|
||||||
|
Nid = "Jmi+9w9u0E4",
|
||||||
|
ExportName = "pthread_create_name_np",
|
||||||
|
Target = Generation.Gen4 | Generation.Gen5,
|
||||||
|
LibraryName = "libKernel")]
|
||||||
|
public static int PosixPthreadCreateNameNp(CpuContext ctx)
|
||||||
|
{
|
||||||
|
return PthreadCreate(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
[SysAbiExport(
|
[SysAbiExport(
|
||||||
Nid = "onNY9Byn-W8",
|
Nid = "onNY9Byn-W8",
|
||||||
ExportName = "scePthreadJoin",
|
ExportName = "scePthreadJoin",
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ public static class KernelMemoryCompatExports
|
|||||||
private const int SeekCur = 1;
|
private const int SeekCur = 1;
|
||||||
private const int SeekEnd = 2;
|
private const int SeekEnd = 2;
|
||||||
private const ulong DirectMemorySizeBytes = 16384UL * 1024 * 1024;
|
private const ulong DirectMemorySizeBytes = 16384UL * 1024 * 1024;
|
||||||
|
private const ulong UnsetMainDirectMemoryPoolBase = ulong.MaxValue;
|
||||||
private const ulong FlexibleMemorySizeBytes = 448UL * 1024 * 1024;
|
private const ulong FlexibleMemorySizeBytes = 448UL * 1024 * 1024;
|
||||||
private const int OrbisVirtualQueryInfoSize = 72;
|
private const int OrbisVirtualQueryInfoSize = 72;
|
||||||
private const int OrbisKernelMaximumNameLength = 32;
|
private const int OrbisKernelMaximumNameLength = 32;
|
||||||
@@ -93,6 +94,7 @@ public static class KernelMemoryCompatExports
|
|||||||
private static long _nextFileDescriptor = 2;
|
private static long _nextFileDescriptor = 2;
|
||||||
private static ulong _nextPhysicalAddress;
|
private static ulong _nextPhysicalAddress;
|
||||||
private static ulong _nextVirtualAddress;
|
private static ulong _nextVirtualAddress;
|
||||||
|
private static ulong _mainDirectMemoryPoolBase = UnsetMainDirectMemoryPoolBase;
|
||||||
private static ulong _allocatedFlexibleBytes;
|
private static ulong _allocatedFlexibleBytes;
|
||||||
private static ulong _threadAtexitCountCallback;
|
private static ulong _threadAtexitCountCallback;
|
||||||
private static ulong _threadAtexitReportCallback;
|
private static ulong _threadAtexitReportCallback;
|
||||||
@@ -1298,10 +1300,12 @@ public static class KernelMemoryCompatExports
|
|||||||
var hostPath = ResolveGuestPath(guestPath);
|
var hostPath = ResolveGuestPath(guestPath);
|
||||||
if (!TryGetAprFileSize(hostPath, out var fileSize))
|
if (!TryGetAprFileSize(hostPath, out var fileSize))
|
||||||
{
|
{
|
||||||
|
LogIoTrace("apr_resolve", guestPath, $"host='{hostPath}' index={i} count={count} result=not_found");
|
||||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
|
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
|
||||||
}
|
}
|
||||||
|
|
||||||
var fileId = AmprFileRegistry.Register(guestPath, hostPath);
|
var fileId = AmprFileRegistry.Register(guestPath, hostPath);
|
||||||
|
LogIoTrace("apr_resolve", guestPath, $"host='{hostPath}' index={i} count={count} id=0x{fileId:X8} size={fileSize}");
|
||||||
|
|
||||||
if (idsAddress != 0 &&
|
if (idsAddress != 0 &&
|
||||||
!TryWriteUInt32Compat(ctx, idsAddress + (i * sizeof(uint)), fileId))
|
!TryWriteUInt32Compat(ctx, idsAddress + (i * sizeof(uint)), fileId))
|
||||||
@@ -1309,7 +1313,7 @@ public static class KernelMemoryCompatExports
|
|||||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!TryWriteUInt32Compat(ctx, sizesAddress + (i * sizeof(uint)), fileSize))
|
if (!TryWriteUInt64Compat(ctx, sizesAddress + (i * sizeof(ulong)), fileSize))
|
||||||
{
|
{
|
||||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||||
}
|
}
|
||||||
@@ -1835,7 +1839,7 @@ public static class KernelMemoryCompatExports
|
|||||||
ulong selectedAddress;
|
ulong selectedAddress;
|
||||||
lock (_memoryGate)
|
lock (_memoryGate)
|
||||||
{
|
{
|
||||||
if (!TryAllocateDirectMemoryLocked(searchStart, searchEnd, length, align, memoryType, out selectedAddress))
|
if (!TryAllocateDirectMemoryLocked(searchStart, searchEnd, length, align, memoryType, DirectMemorySizeBytes, out selectedAddress))
|
||||||
{
|
{
|
||||||
TraceDirectMemoryCall(
|
TraceDirectMemoryCall(
|
||||||
ctx,
|
ctx,
|
||||||
@@ -1904,7 +1908,31 @@ public static class KernelMemoryCompatExports
|
|||||||
ulong aligned;
|
ulong aligned;
|
||||||
lock (_memoryGate)
|
lock (_memoryGate)
|
||||||
{
|
{
|
||||||
if (!TryAllocateDirectMemoryLocked(0, DirectMemorySizeBytes, length, effectiveAlignment, memoryType, out aligned))
|
var allocationLimit = DirectMemorySizeBytes;
|
||||||
|
if (_mainDirectMemoryPoolBase != UnsetMainDirectMemoryPoolBase &&
|
||||||
|
!TryAddU64(_mainDirectMemoryPoolBase, DirectMemorySizeBytes, out allocationLimit))
|
||||||
|
{
|
||||||
|
allocationLimit = ulong.MaxValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TryAllocateDirectMemoryLocked(0, allocationLimit, length, effectiveAlignment, memoryType, allocationLimit, out aligned))
|
||||||
|
{
|
||||||
|
var poolBase = _mainDirectMemoryPoolBase == UnsetMainDirectMemoryPoolBase
|
||||||
|
? AlignUp(GetDirectMemoryHighWaterMarkLocked(), effectiveAlignment)
|
||||||
|
: _mainDirectMemoryPoolBase;
|
||||||
|
|
||||||
|
if (_mainDirectMemoryPoolBase == UnsetMainDirectMemoryPoolBase &&
|
||||||
|
TryAddU64(poolBase, DirectMemorySizeBytes, out var shiftedLimit) &&
|
||||||
|
TryAllocateDirectMemoryLocked(0, shiftedLimit, length, effectiveAlignment, memoryType, shiftedLimit, out aligned))
|
||||||
|
{
|
||||||
|
_mainDirectMemoryPoolBase = poolBase;
|
||||||
|
if (ShouldTraceDirectMemory())
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine(
|
||||||
|
$"[LOADER][TRACE] main_direct_pool: base=0x{poolBase:X16} limit=0x{shiftedLimit:X16}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
TraceDirectMemoryCall(
|
TraceDirectMemoryCall(
|
||||||
ctx,
|
ctx,
|
||||||
@@ -1917,6 +1945,7 @@ public static class KernelMemoryCompatExports
|
|||||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_TRY_AGAIN;
|
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_TRY_AGAIN;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!ctx.TryWriteUInt64(outAddress, aligned))
|
if (!ctx.TryWriteUInt64(outAddress, aligned))
|
||||||
{
|
{
|
||||||
@@ -1986,8 +2015,11 @@ public static class KernelMemoryCompatExports
|
|||||||
var flags = ctx[CpuRegister.Rcx];
|
var flags = ctx[CpuRegister.Rcx];
|
||||||
var directMemoryStart = ctx[CpuRegister.R8];
|
var directMemoryStart = ctx[CpuRegister.R8];
|
||||||
var alignment = ctx[CpuRegister.R9];
|
var alignment = ctx[CpuRegister.R9];
|
||||||
|
if (ShouldTraceDirectMemory())
|
||||||
|
{
|
||||||
Console.Error.WriteLine(
|
Console.Error.WriteLine(
|
||||||
$"[LOADER][TRACE] map_direct: inout=0x{inOutAddressPointer:X16} len=0x{length:X16} prot=0x{protection:X8} flags=0x{flags:X16} direct=0x{directMemoryStart:X16} align=0x{alignment:X16}");
|
$"[LOADER][TRACE] map_direct: inout=0x{inOutAddressPointer:X16} len=0x{length:X16} prot=0x{protection:X8} flags=0x{flags:X16} direct=0x{directMemoryStart:X16} align=0x{alignment:X16}");
|
||||||
|
}
|
||||||
if (inOutAddressPointer == 0 || length == 0)
|
if (inOutAddressPointer == 0 || length == 0)
|
||||||
{
|
{
|
||||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||||
@@ -2018,8 +2050,11 @@ public static class KernelMemoryCompatExports
|
|||||||
{
|
{
|
||||||
reserved = TryReserveGuestVirtualRange(ctx, desiredAddress, length, protection, out mappedAddress);
|
reserved = TryReserveGuestVirtualRange(ctx, desiredAddress, length, protection, out mappedAddress);
|
||||||
}
|
}
|
||||||
|
if (ShouldTraceDirectMemory())
|
||||||
|
{
|
||||||
Console.Error.WriteLine(
|
Console.Error.WriteLine(
|
||||||
$"[LOADER][TRACE] map_direct reserve: requested=0x{requestedAddress:X16} desired=0x{desiredAddress:X16} reserved={reserved} mapped=0x{mappedAddress:X16}");
|
$"[LOADER][TRACE] map_direct reserve: requested=0x{requestedAddress:X16} desired=0x{desiredAddress:X16} reserved={reserved} mapped=0x{mappedAddress:X16}");
|
||||||
|
}
|
||||||
if (!reserved)
|
if (!reserved)
|
||||||
{
|
{
|
||||||
if (mappedAddress == 0)
|
if (mappedAddress == 0)
|
||||||
@@ -2027,9 +2062,12 @@ public static class KernelMemoryCompatExports
|
|||||||
mappedAddress = requestedAddress != 0
|
mappedAddress = requestedAddress != 0
|
||||||
? requestedAddress
|
? requestedAddress
|
||||||
: AllocateMappedGuestAddress(ctx, length, effectiveAlignment);
|
: AllocateMappedGuestAddress(ctx, length, effectiveAlignment);
|
||||||
|
if (ShouldTraceDirectMemory())
|
||||||
|
{
|
||||||
Console.Error.WriteLine($"[LOADER][TRACE] map_direct fallback mapped=0x{mappedAddress:X16}");
|
Console.Error.WriteLine($"[LOADER][TRACE] map_direct fallback mapped=0x{mappedAddress:X16}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (mappedAddress == 0)
|
if (mappedAddress == 0)
|
||||||
{
|
{
|
||||||
@@ -3560,6 +3598,12 @@ public static class KernelMemoryCompatExports
|
|||||||
var app0Root = Environment.GetEnvironmentVariable("SHARPEMU_APP0_DIR");
|
var app0Root = Environment.GetEnvironmentVariable("SHARPEMU_APP0_DIR");
|
||||||
if (!string.IsNullOrWhiteSpace(app0Root))
|
if (!string.IsNullOrWhiteSpace(app0Root))
|
||||||
{
|
{
|
||||||
|
if (string.Equals(guestPath, "/app0", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
string.Equals(guestPath, "app0", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return app0Root;
|
||||||
|
}
|
||||||
|
|
||||||
if (guestPath.StartsWith("/app0/", StringComparison.OrdinalIgnoreCase))
|
if (guestPath.StartsWith("/app0/", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
var relative = guestPath["/app0/".Length..].Replace('/', Path.DirectorySeparatorChar);
|
var relative = guestPath["/app0/".Length..].Replace('/', Path.DirectorySeparatorChar);
|
||||||
@@ -3571,6 +3615,14 @@ public static class KernelMemoryCompatExports
|
|||||||
var relative = guestPath["app0/".Length..].Replace('/', Path.DirectorySeparatorChar);
|
var relative = guestPath["app0/".Length..].Replace('/', Path.DirectorySeparatorChar);
|
||||||
return Path.Combine(app0Root, relative);
|
return Path.Combine(app0Root, relative);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!Path.IsPathFullyQualified(guestPath) &&
|
||||||
|
!guestPath.StartsWith("/", StringComparison.Ordinal) &&
|
||||||
|
!guestPath.StartsWith("\\", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
var relative = guestPath.Replace('/', Path.DirectorySeparatorChar);
|
||||||
|
return Path.Combine(app0Root, relative);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return guestPath;
|
return guestPath;
|
||||||
@@ -4235,7 +4287,7 @@ public static class KernelMemoryCompatExports
|
|||||||
ulong selectedAddress = 0,
|
ulong selectedAddress = 0,
|
||||||
OrbisGen2Result? result = null)
|
OrbisGen2Result? result = null)
|
||||||
{
|
{
|
||||||
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_DIRECT_MEMORY"), "1", StringComparison.Ordinal))
|
if (!ShouldTraceDirectMemory())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -4251,12 +4303,18 @@ public static class KernelMemoryCompatExports
|
|||||||
$"[LOADER][TRACE] {operation}: ret=0x{returnRip:X16} len=0x{length:X16} align=0x{alignment:X16} type=0x{memoryType:X8} out=0x{outAddress:X16} selected=0x{selectedAddress:X16} result={result?.ToString() ?? "<pending>"}");
|
$"[LOADER][TRACE] {operation}: ret=0x{returnRip:X16} len=0x{length:X16} align=0x{alignment:X16} type=0x{memoryType:X8} out=0x{outAddress:X16} selected=0x{selectedAddress:X16} result={result?.ToString() ?? "<pending>"}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool ShouldTraceDirectMemory()
|
||||||
|
{
|
||||||
|
return string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_DIRECT_MEMORY"), "1", StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
private static bool TryAllocateDirectMemoryLocked(
|
private static bool TryAllocateDirectMemoryLocked(
|
||||||
ulong searchStart,
|
ulong searchStart,
|
||||||
ulong searchEnd,
|
ulong searchEnd,
|
||||||
ulong length,
|
ulong length,
|
||||||
ulong alignment,
|
ulong alignment,
|
||||||
int memoryType,
|
int memoryType,
|
||||||
|
ulong allocationLimit,
|
||||||
out ulong selectedAddress)
|
out ulong selectedAddress)
|
||||||
{
|
{
|
||||||
selectedAddress = 0;
|
selectedAddress = 0;
|
||||||
@@ -4266,7 +4324,7 @@ public static class KernelMemoryCompatExports
|
|||||||
}
|
}
|
||||||
|
|
||||||
var effectiveAlignment = alignment == 0 ? 0x1000UL : alignment;
|
var effectiveAlignment = alignment == 0 ? 0x1000UL : alignment;
|
||||||
if (!TryFindAllocatableDirectMemoryRangeLocked(searchStart, searchEnd, length, effectiveAlignment, out var freePosition) ||
|
if (!TryFindAllocatableDirectMemoryRangeLocked(searchStart, searchEnd, length, effectiveAlignment, allocationLimit, out var freePosition) ||
|
||||||
!TryAddU64(freePosition, length, out var endAddress))
|
!TryAddU64(freePosition, length, out var endAddress))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -4283,6 +4341,7 @@ public static class KernelMemoryCompatExports
|
|||||||
ulong searchEnd,
|
ulong searchEnd,
|
||||||
ulong length,
|
ulong length,
|
||||||
ulong alignment,
|
ulong alignment,
|
||||||
|
ulong allocationLimit,
|
||||||
out ulong selectedAddress)
|
out ulong selectedAddress)
|
||||||
{
|
{
|
||||||
selectedAddress = 0;
|
selectedAddress = 0;
|
||||||
@@ -4291,7 +4350,7 @@ public static class KernelMemoryCompatExports
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var effectiveEnd = Math.Min(searchEnd, DirectMemorySizeBytes);
|
var effectiveEnd = Math.Min(searchEnd, allocationLimit);
|
||||||
var candidate = AlignUp(searchStart, alignment);
|
var candidate = AlignUp(searchStart, alignment);
|
||||||
if (candidate >= effectiveEnd)
|
if (candidate >= effectiveEnd)
|
||||||
{
|
{
|
||||||
@@ -4411,7 +4470,7 @@ public static class KernelMemoryCompatExports
|
|||||||
{
|
{
|
||||||
if (!TryAddU64(allocation.Start, allocation.Length, out var endAddress))
|
if (!TryAddU64(allocation.Start, allocation.Length, out var endAddress))
|
||||||
{
|
{
|
||||||
return DirectMemorySizeBytes;
|
return ulong.MaxValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (endAddress > highWaterMark)
|
if (endAddress > highWaterMark)
|
||||||
@@ -4420,7 +4479,7 @@ public static class KernelMemoryCompatExports
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Math.Min(highWaterMark, DirectMemorySizeBytes);
|
return highWaterMark;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool TryReadHostMemory(ulong address, Span<byte> destination)
|
private static bool TryReadHostMemory(ulong address, Span<byte> destination)
|
||||||
@@ -4817,7 +4876,7 @@ public static class KernelMemoryCompatExports
|
|||||||
return TryWriteHostPathStat(ctx, statAddress, hostPath, isDirectory);
|
return TryWriteHostPathStat(ctx, statAddress, hostPath, isDirectory);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool TryGetAprFileSize(string hostPath, out uint size)
|
private static bool TryGetAprFileSize(string hostPath, out ulong size)
|
||||||
{
|
{
|
||||||
size = 0;
|
size = 0;
|
||||||
try
|
try
|
||||||
@@ -4834,7 +4893,7 @@ public static class KernelMemoryCompatExports
|
|||||||
}
|
}
|
||||||
|
|
||||||
var length = new FileInfo(hostPath).Length;
|
var length = new FileInfo(hostPath).Length;
|
||||||
size = length >= uint.MaxValue ? uint.MaxValue : (uint)length;
|
size = length < 0 ? 0UL : unchecked((ulong)length);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ public static class KernelPthreadCompatExports
|
|||||||
private const ulong SyntheticMutexHandleBase = 0x00006000_0000_0000;
|
private const ulong SyntheticMutexHandleBase = 0x00006000_0000_0000;
|
||||||
private const ulong SyntheticMutexAttrHandleBase = 0x00006001_0000_0000;
|
private const ulong SyntheticMutexAttrHandleBase = 0x00006001_0000_0000;
|
||||||
private const ulong SyntheticCondHandleBase = 0x00006002_0000_0000;
|
private const ulong SyntheticCondHandleBase = 0x00006002_0000_0000;
|
||||||
|
private const int DefaultSpuriousCondWakeMilliseconds = 1;
|
||||||
|
|
||||||
private static readonly object _stateGate = new();
|
private static readonly object _stateGate = new();
|
||||||
private static readonly Dictionary<ulong, PthreadMutexState> _mutexStates = new();
|
private static readonly Dictionary<ulong, PthreadMutexState> _mutexStates = new();
|
||||||
@@ -887,6 +888,7 @@ public static class KernelPthreadCompatExports
|
|||||||
}
|
}
|
||||||
|
|
||||||
var waitResult = (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
var waitResult = (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||||
|
var spuriousWake = false;
|
||||||
lock (state.SyncRoot)
|
lock (state.SyncRoot)
|
||||||
{
|
{
|
||||||
state.Waiters++;
|
state.Waiters++;
|
||||||
@@ -901,11 +903,36 @@ public static class KernelPthreadCompatExports
|
|||||||
return unlockResult;
|
return unlockResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var scheduler = GuestThreadExecution.Scheduler;
|
||||||
|
if (!timed && GuestThreadExecution.RequestCurrentThreadBlock("pthread_cond_wait"))
|
||||||
|
{
|
||||||
|
TracePthreadCond("wait-block", condAddress, mutexAddress, state, timed, waitResult);
|
||||||
|
return waitResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scheduler is not null)
|
||||||
|
{
|
||||||
|
Monitor.Exit(state.SyncRoot);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
scheduler.Pump(ctx, "pthread_cond_wait");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Monitor.Enter(state.SyncRoot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
while (state.SignalEpoch == observedEpoch)
|
while (state.SignalEpoch == observedEpoch)
|
||||||
{
|
{
|
||||||
if (!timed)
|
if (!timed)
|
||||||
{
|
{
|
||||||
Monitor.Wait(state.SyncRoot);
|
if (!Monitor.Wait(state.SyncRoot, GetCondSpuriousWakeTimeout()))
|
||||||
|
{
|
||||||
|
spuriousWake = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -917,7 +944,15 @@ public static class KernelPthreadCompatExports
|
|||||||
}
|
}
|
||||||
|
|
||||||
state.Waiters = Math.Max(0, state.Waiters - 1);
|
state.Waiters = Math.Max(0, state.Waiters - 1);
|
||||||
TracePthreadCond(waitResult == (int)OrbisGen2Result.ORBIS_GEN2_OK ? "wait-wake" : "wait-timeout", condAddress, mutexAddress, state, timed, waitResult);
|
TracePthreadCond(
|
||||||
|
spuriousWake
|
||||||
|
? "wait-spurious"
|
||||||
|
: (waitResult == (int)OrbisGen2Result.ORBIS_GEN2_OK ? "wait-wake" : "wait-timeout"),
|
||||||
|
condAddress,
|
||||||
|
mutexAddress,
|
||||||
|
state,
|
||||||
|
timed,
|
||||||
|
waitResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
var lockResult = PthreadMutexLockCore(ctx, mutexAddress, tryOnly: false);
|
var lockResult = PthreadMutexLockCore(ctx, mutexAddress, tryOnly: false);
|
||||||
@@ -927,7 +962,15 @@ public static class KernelPthreadCompatExports
|
|||||||
return lockResult;
|
return lockResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
TracePthreadCond(waitResult == (int)OrbisGen2Result.ORBIS_GEN2_OK ? "wait-exit" : "wait-exit-timeout", condAddress, mutexAddress, state, timed, waitResult);
|
TracePthreadCond(
|
||||||
|
spuriousWake
|
||||||
|
? "wait-exit-spurious"
|
||||||
|
: (waitResult == (int)OrbisGen2Result.ORBIS_GEN2_OK ? "wait-exit" : "wait-exit-timeout"),
|
||||||
|
condAddress,
|
||||||
|
mutexAddress,
|
||||||
|
state,
|
||||||
|
timed,
|
||||||
|
waitResult);
|
||||||
return waitResult;
|
return waitResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -974,6 +1017,16 @@ public static class KernelPthreadCompatExports
|
|||||||
return TimeSpan.FromTicks((long)timeoutUsec * 10L);
|
return TimeSpan.FromTicks((long)timeoutUsec * 10L);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static TimeSpan GetCondSpuriousWakeTimeout()
|
||||||
|
{
|
||||||
|
if (int.TryParse(Environment.GetEnvironmentVariable("SHARPEMU_PTHREAD_COND_SPURIOUS_WAKE_MS"), out var milliseconds))
|
||||||
|
{
|
||||||
|
return TimeSpan.FromMilliseconds(Math.Max(1, milliseconds));
|
||||||
|
}
|
||||||
|
|
||||||
|
return TimeSpan.FromMilliseconds(DefaultSpuriousCondWakeMilliseconds);
|
||||||
|
}
|
||||||
|
|
||||||
private static int NormalizeMutexType(int type)
|
private static int NormalizeMutexType(int type)
|
||||||
{
|
{
|
||||||
return type switch
|
return type switch
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ public static class KernelPthreadExtendedCompatExports
|
|||||||
private const int DefaultSchedPolicy = 0;
|
private const int DefaultSchedPolicy = 0;
|
||||||
private const int DefaultSchedPriority = 0;
|
private const int DefaultSchedPriority = 0;
|
||||||
private const ulong SyntheticRwlockHandleBase = 0x00006003_0000_0000;
|
private const ulong SyntheticRwlockHandleBase = 0x00006003_0000_0000;
|
||||||
|
private const ulong SyntheticPthreadAttrHandleBase = 0x00006004_0000_0000;
|
||||||
|
|
||||||
private static readonly object _stateGate = new();
|
private static readonly object _stateGate = new();
|
||||||
private static readonly Dictionary<ulong, ThreadState> _threadStates = new();
|
private static readonly Dictionary<ulong, ThreadState> _threadStates = new();
|
||||||
@@ -28,6 +29,7 @@ public static class KernelPthreadExtendedCompatExports
|
|||||||
private static readonly Dictionary<int, TlsKeyState> _tlsKeys = new();
|
private static readonly Dictionary<int, TlsKeyState> _tlsKeys = new();
|
||||||
private static int _nextTlsKey = 1;
|
private static int _nextTlsKey = 1;
|
||||||
private static long _nextSyntheticRwlockHandleId = 1;
|
private static long _nextSyntheticRwlockHandleId = 1;
|
||||||
|
private static long _nextSyntheticPthreadAttrHandleId = 1;
|
||||||
|
|
||||||
private static readonly Dictionary<ulong, Dictionary<int, ulong>> _threadLocalSpecific = new();
|
private static readonly Dictionary<ulong, Dictionary<int, ulong>> _threadLocalSpecific = new();
|
||||||
|
|
||||||
@@ -235,6 +237,41 @@ public static class KernelPthreadExtendedCompatExports
|
|||||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SysAbiExport(
|
||||||
|
Nid = "Xs9hdiD7sAA",
|
||||||
|
ExportName = "pthread_setschedparam",
|
||||||
|
Target = Generation.Gen4 | Generation.Gen5,
|
||||||
|
LibraryName = "libKernel")]
|
||||||
|
public static int PosixPthreadSetschedparam(CpuContext ctx)
|
||||||
|
{
|
||||||
|
var thread = ctx[CpuRegister.Rdi];
|
||||||
|
var policy = unchecked((int)ctx[CpuRegister.Rsi]);
|
||||||
|
var schedParamAddress = ctx[CpuRegister.Rdx];
|
||||||
|
if (thread == 0 || schedParamAddress == 0)
|
||||||
|
{
|
||||||
|
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TryReadInt32(ctx, schedParamAddress, out var schedPriority))
|
||||||
|
{
|
||||||
|
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (_stateGate)
|
||||||
|
{
|
||||||
|
var state = GetOrCreateThreadStateLocked(thread);
|
||||||
|
state.Priority = schedPriority;
|
||||||
|
state.Attributes = state.Attributes with
|
||||||
|
{
|
||||||
|
SchedPolicy = policy,
|
||||||
|
SchedPriority = schedPriority,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx[CpuRegister.Rax] = 0;
|
||||||
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||||
|
}
|
||||||
|
|
||||||
[SysAbiExport(
|
[SysAbiExport(
|
||||||
Nid = "nsYoNRywwNg",
|
Nid = "nsYoNRywwNg",
|
||||||
ExportName = "scePthreadAttrInit",
|
ExportName = "scePthreadAttrInit",
|
||||||
@@ -248,15 +285,32 @@ public static class KernelPthreadExtendedCompatExports
|
|||||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var syntheticHandle = AllocateSyntheticHandle(SyntheticPthreadAttrHandleBase, ref _nextSyntheticPthreadAttrHandleId);
|
||||||
|
if (!ctx.TryWriteUInt64(attrAddress, syntheticHandle))
|
||||||
|
{
|
||||||
|
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||||
|
}
|
||||||
|
|
||||||
lock (_stateGate)
|
lock (_stateGate)
|
||||||
{
|
{
|
||||||
_attrStates[attrAddress] = PthreadAttrState.Default;
|
_attrStates[attrAddress] = PthreadAttrState.Default;
|
||||||
|
_attrStates[syntheticHandle] = PthreadAttrState.Default;
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx[CpuRegister.Rax] = 0;
|
ctx[CpuRegister.Rax] = 0;
|
||||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SysAbiExport(
|
||||||
|
Nid = "wtkt-teR1so",
|
||||||
|
ExportName = "pthread_attr_init",
|
||||||
|
Target = Generation.Gen4 | Generation.Gen5,
|
||||||
|
LibraryName = "libKernel")]
|
||||||
|
public static int PosixPthreadAttrInit(CpuContext ctx)
|
||||||
|
{
|
||||||
|
return PthreadAttrInit(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
[SysAbiExport(
|
[SysAbiExport(
|
||||||
Nid = "62KCwEMmzcM",
|
Nid = "62KCwEMmzcM",
|
||||||
ExportName = "scePthreadAttrDestroy",
|
ExportName = "scePthreadAttrDestroy",
|
||||||
@@ -270,15 +324,31 @@ public static class KernelPthreadExtendedCompatExports
|
|||||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var resolvedAddress = ResolvePthreadAttrHandle(ctx, attrAddress);
|
||||||
lock (_stateGate)
|
lock (_stateGate)
|
||||||
{
|
{
|
||||||
_attrStates.Remove(attrAddress);
|
_attrStates.Remove(attrAddress);
|
||||||
|
if (resolvedAddress != attrAddress)
|
||||||
|
{
|
||||||
|
_attrStates.Remove(resolvedAddress);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_ = ctx.TryWriteUInt64(attrAddress, 0);
|
||||||
ctx[CpuRegister.Rax] = 0;
|
ctx[CpuRegister.Rax] = 0;
|
||||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SysAbiExport(
|
||||||
|
Nid = "zHchY8ft5pk",
|
||||||
|
ExportName = "pthread_attr_destroy",
|
||||||
|
Target = Generation.Gen4 | Generation.Gen5,
|
||||||
|
LibraryName = "libKernel")]
|
||||||
|
public static int PosixPthreadAttrDestroy(CpuContext ctx)
|
||||||
|
{
|
||||||
|
return PthreadAttrDestroy(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
[SysAbiExport(
|
[SysAbiExport(
|
||||||
Nid = "x1X76arYMxU",
|
Nid = "x1X76arYMxU",
|
||||||
ExportName = "scePthreadAttrGet",
|
ExportName = "scePthreadAttrGet",
|
||||||
@@ -611,16 +681,32 @@ public static class KernelPthreadExtendedCompatExports
|
|||||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var resolvedAddress = ResolvePthreadAttrHandle(ctx, attrAddress);
|
||||||
lock (_stateGate)
|
lock (_stateGate)
|
||||||
{
|
{
|
||||||
var state = GetOrCreateAttrStateLocked(attrAddress);
|
var state = GetOrCreateAttrStateLocked(resolvedAddress);
|
||||||
_attrStates[attrAddress] = state with { StackSize = stackSize };
|
var updated = state with { StackSize = stackSize };
|
||||||
|
_attrStates[resolvedAddress] = updated;
|
||||||
|
if (resolvedAddress != attrAddress)
|
||||||
|
{
|
||||||
|
_attrStates[attrAddress] = updated;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx[CpuRegister.Rax] = 0;
|
ctx[CpuRegister.Rax] = 0;
|
||||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SysAbiExport(
|
||||||
|
Nid = "2Q0z6rnBrTE",
|
||||||
|
ExportName = "pthread_attr_setstacksize",
|
||||||
|
Target = Generation.Gen4 | Generation.Gen5,
|
||||||
|
LibraryName = "libKernel")]
|
||||||
|
public static int PosixPthreadAttrSetstacksize(CpuContext ctx)
|
||||||
|
{
|
||||||
|
return PthreadAttrSetstacksize(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
[SysAbiExport(
|
[SysAbiExport(
|
||||||
Nid = "6ULAa0fq4jA",
|
Nid = "6ULAa0fq4jA",
|
||||||
ExportName = "scePthreadRwlockInit",
|
ExportName = "scePthreadRwlockInit",
|
||||||
@@ -1131,6 +1217,35 @@ public static class KernelPthreadExtendedCompatExports
|
|||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static ulong ResolvePthreadAttrHandle(CpuContext ctx, ulong attrAddress)
|
||||||
|
{
|
||||||
|
if (attrAddress == 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (_stateGate)
|
||||||
|
{
|
||||||
|
if (_attrStates.ContainsKey(attrAddress))
|
||||||
|
{
|
||||||
|
return attrAddress;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx.TryReadUInt64(attrAddress, out var pointedHandle) && pointedHandle != 0)
|
||||||
|
{
|
||||||
|
lock (_stateGate)
|
||||||
|
{
|
||||||
|
if (_attrStates.ContainsKey(pointedHandle))
|
||||||
|
{
|
||||||
|
return pointedHandle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return attrAddress;
|
||||||
|
}
|
||||||
|
|
||||||
private static bool TryWriteFixedUtf8CString(CpuContext ctx, ulong address, string value, int maxBytes)
|
private static bool TryWriteFixedUtf8CString(CpuContext ctx, ulong address, string value, int maxBytes)
|
||||||
{
|
{
|
||||||
if (maxBytes <= 0)
|
if (maxBytes <= 0)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using SharpEmu.HLE;
|
||||||
|
|
||||||
namespace SharpEmu.Libs.Kernel;
|
namespace SharpEmu.Libs.Kernel;
|
||||||
|
|
||||||
@@ -25,12 +26,24 @@ internal static class KernelPthreadState
|
|||||||
|
|
||||||
internal static ulong GetCurrentThreadHandle()
|
internal static ulong GetCurrentThreadHandle()
|
||||||
{
|
{
|
||||||
|
var guestThreadHandle = GuestThreadExecution.CurrentGuestThreadHandle;
|
||||||
|
if (guestThreadHandle != 0 && TryGetThreadIdentity(guestThreadHandle, out _))
|
||||||
|
{
|
||||||
|
return guestThreadHandle;
|
||||||
|
}
|
||||||
|
|
||||||
EnsureCurrentThreadRegistered();
|
EnsureCurrentThreadRegistered();
|
||||||
return _currentThreadHandle;
|
return _currentThreadHandle;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static ulong GetCurrentThreadUniqueId()
|
internal static ulong GetCurrentThreadUniqueId()
|
||||||
{
|
{
|
||||||
|
var guestThreadHandle = GuestThreadExecution.CurrentGuestThreadHandle;
|
||||||
|
if (guestThreadHandle != 0 && TryGetThreadIdentity(guestThreadHandle, out var identity))
|
||||||
|
{
|
||||||
|
return identity.UniqueId;
|
||||||
|
}
|
||||||
|
|
||||||
EnsureCurrentThreadRegistered();
|
EnsureCurrentThreadRegistered();
|
||||||
return _currentThreadUniqueId;
|
return _currentThreadUniqueId;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user