more HLE's and fix cpu execution some titles

This commit is contained in:
ParantezTech
2026-05-10 19:51:57 +03:00
parent 0c859f04ad
commit bbf5ff7be8
11 changed files with 1275 additions and 65 deletions
+22
View File
@@ -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;
var offset = 5;
if (destinationRegister != 0)
{ {
*(int*)(address + 1) = (int)num3; *(byte*)(address + offset++) = (byte)(0x48 | (destinationRegister >= 8 ? 1 : 0));
*(sbyte*)(address + 5) = 72; *(byte*)(address + offset++) = 0x89;
*(sbyte*)(address + 6) = -119; *(byte*)(address + offset++) = (byte)(0xC0 | (destinationRegister & 7));
*(sbyte*)(address + 7) = -64;
*(sbyte*)(address + 8) = -112;
} }
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)
+107
View File
@@ -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;
}
}
+160 -8
View File
@@ -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;
} }
+29 -2
View File
@@ -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,17 +1908,42 @@ 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))
{ {
TraceDirectMemoryCall( allocationLimit = ulong.MaxValue;
ctx, }
"allocate_main_direct",
length, if (!TryAllocateDirectMemoryLocked(0, allocationLimit, length, effectiveAlignment, memoryType, allocationLimit, out aligned))
effectiveAlignment, {
memoryType, var poolBase = _mainDirectMemoryPoolBase == UnsetMainDirectMemoryPoolBase
outAddress, ? AlignUp(GetDirectMemoryHighWaterMarkLocked(), effectiveAlignment)
result: OrbisGen2Result.ORBIS_GEN2_ERROR_TRY_AGAIN); : _mainDirectMemoryPoolBase;
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_TRY_AGAIN;
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(
ctx,
"allocate_main_direct",
length,
effectiveAlignment,
memoryType,
outAddress,
result: OrbisGen2Result.ORBIS_GEN2_ERROR_TRY_AGAIN);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_TRY_AGAIN;
}
} }
} }
@@ -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];
Console.Error.WriteLine( if (ShouldTraceDirectMemory())
$"[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}"); {
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}");
}
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);
} }
Console.Error.WriteLine( if (ShouldTraceDirectMemory())
$"[LOADER][TRACE] map_direct reserve: requested=0x{requestedAddress:X16} desired=0x{desiredAddress:X16} reserved={reserved} mapped=0x{mappedAddress:X16}"); {
Console.Error.WriteLine(
$"[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,7 +2062,10 @@ public static class KernelMemoryCompatExports
mappedAddress = requestedAddress != 0 mappedAddress = requestedAddress != 0
? requestedAddress ? requestedAddress
: AllocateMappedGuestAddress(ctx, length, effectiveAlignment); : AllocateMappedGuestAddress(ctx, length, effectiveAlignment);
Console.Error.WriteLine($"[LOADER][TRACE] map_direct fallback mapped=0x{mappedAddress:X16}"); if (ShouldTraceDirectMemory())
{
Console.Error.WriteLine($"[LOADER][TRACE] map_direct fallback mapped=0x{mappedAddress:X16}");
}
} }
} }
@@ -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;
} }