[kernel] Guest-thread blocking for pthread_mutex_lock is currently disabled

This commit is contained in:
ParantezTech
2026-06-28 23:45:05 +03:00
parent 4ab614e68a
commit a5172fd2c0
10 changed files with 1148 additions and 205 deletions
@@ -11,8 +11,13 @@ namespace SharpEmu.Libs.Kernel;
public static class KernelAprCompatExports public static class KernelAprCompatExports
{ {
private static readonly ConcurrentDictionary<uint, ulong> _submittedCommandBuffers = new(); private static readonly ConcurrentDictionary<uint, AprSubmission> _submittedCommandBuffers = new();
private static int _nextSubmissionId; private static int _nextSubmissionId;
private static int _aprWaitTraceCount;
private static readonly bool _traceApr =
string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_AMPR"), "1", StringComparison.Ordinal);
private readonly record struct AprSubmission(ulong CommandBuffer, ulong Priority, ulong ResultAddress);
[SysAbiExport( [SysAbiExport(
Nid = "ASoW5WE-UPo", Nid = "ASoW5WE-UPo",
@@ -37,7 +42,7 @@ public static class KernelAprCompatExports
submissionId = unchecked((uint)Interlocked.Increment(ref _nextSubmissionId)); submissionId = unchecked((uint)Interlocked.Increment(ref _nextSubmissionId));
} }
_submittedCommandBuffers[submissionId] = commandBuffer; _submittedCommandBuffers[submissionId] = new AprSubmission(commandBuffer, priority, resultAddress);
var completionResult = AmprExports.CompleteCommandBuffer(ctx, commandBuffer); var completionResult = AmprExports.CompleteCommandBuffer(ctx, commandBuffer);
if (completionResult != (int)OrbisGen2Result.ORBIS_GEN2_OK) if (completionResult != (int)OrbisGen2Result.ORBIS_GEN2_OK)
@@ -67,20 +72,23 @@ public static class KernelAprCompatExports
public static int KernelAprWaitCommandBuffer(CpuContext ctx) public static int KernelAprWaitCommandBuffer(CpuContext ctx)
{ {
var submissionId = unchecked((uint)ctx[CpuRegister.Rdi]); var submissionId = unchecked((uint)ctx[CpuRegister.Rdi]);
var priority = ctx[CpuRegister.Rsi]; var waitArg1 = ctx[CpuRegister.Rsi];
var resultAddress = ctx[CpuRegister.Rdx]; var waitArg2 = ctx[CpuRegister.Rdx];
if (!_submittedCommandBuffers.TryRemove(submissionId, out var commandBuffer)) if (!_submittedCommandBuffers.TryRemove(submissionId, out var submission))
{ {
TraceAprWaitFailure(ctx, "wait_missing", submissionId, commandBuffer: 0, waitArg1, waitArg2);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
} }
var resultAddress = ResolveWaitResultAddress(waitArg1, waitArg2, submission.ResultAddress);
if (resultAddress != 0 && !TryWriteAprResult(ctx, resultAddress)) if (resultAddress != 0 && !TryWriteAprResult(ctx, resultAddress))
{ {
TraceAprWaitFailure(ctx, "wait_result_fault", submissionId, submission.CommandBuffer, waitArg1, waitArg2);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
} }
TraceApr(ctx, "wait", submissionId, commandBuffer, priority, resultAddress); TraceApr(ctx, "wait", submissionId, submission.CommandBuffer, waitArg1, resultAddress);
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
@@ -98,7 +106,7 @@ public static class KernelAprCompatExports
} }
var submissionId = unchecked((uint)Interlocked.Increment(ref _nextSubmissionId)); var submissionId = unchecked((uint)Interlocked.Increment(ref _nextSubmissionId));
_submittedCommandBuffers[submissionId] = commandBuffer; _submittedCommandBuffers[submissionId] = new AprSubmission(commandBuffer, ctx[CpuRegister.Rsi], ResultAddress: 0);
var completionResult = AmprExports.CompleteCommandBuffer(ctx, commandBuffer); var completionResult = AmprExports.CompleteCommandBuffer(ctx, commandBuffer);
if (completionResult != (int)OrbisGen2Result.ORBIS_GEN2_OK) if (completionResult != (int)OrbisGen2Result.ORBIS_GEN2_OK)
@@ -125,7 +133,7 @@ public static class KernelAprCompatExports
} }
var submissionId = unchecked((uint)Interlocked.Increment(ref _nextSubmissionId)); var submissionId = unchecked((uint)Interlocked.Increment(ref _nextSubmissionId));
_submittedCommandBuffers[submissionId] = commandBuffer; _submittedCommandBuffers[submissionId] = new AprSubmission(commandBuffer, ctx[CpuRegister.Rsi], ResultAddress: 0);
var completionResult = AmprExports.CompleteCommandBuffer(ctx, commandBuffer); var completionResult = AmprExports.CompleteCommandBuffer(ctx, commandBuffer);
if (completionResult != (int)OrbisGen2Result.ORBIS_GEN2_OK) if (completionResult != (int)OrbisGen2Result.ORBIS_GEN2_OK)
@@ -149,6 +157,27 @@ public static class KernelAprCompatExports
return ctx.Memory.TryWrite(resultAddress, result); return ctx.Memory.TryWrite(resultAddress, result);
} }
private static ulong ResolveWaitResultAddress(ulong waitArg1, ulong waitArg2, ulong submittedResultAddress)
{
if (waitArg2 == 0)
{
return submittedResultAddress;
}
if (IsAmprCompletionToken(waitArg1) && waitArg2 <= 0xFFFF)
{
return submittedResultAddress;
}
return waitArg2;
}
private static bool IsAmprCompletionToken(ulong value)
{
var tag = value >> 56;
return tag is 0x0C or 0x10;
}
private static bool TryWriteUInt32(CpuContext ctx, ulong address, uint value) private static bool TryWriteUInt32(CpuContext ctx, ulong address, uint value)
{ {
Span<byte> buffer = stackalloc byte[sizeof(uint)]; Span<byte> buffer = stackalloc byte[sizeof(uint)];
@@ -164,7 +193,7 @@ public static class KernelAprCompatExports
ulong priority, ulong priority,
ulong aux) ulong aux)
{ {
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_AMPR"), "1", StringComparison.Ordinal)) if (!_traceApr)
{ {
return; return;
} }
@@ -181,4 +210,46 @@ public static class KernelAprCompatExports
$"[LOADER][TRACE] apr.{operation}.result: addr=0x{aux:X16} q0=0x{result0:X16} q1=0x{result1:X16}"); $"[LOADER][TRACE] apr.{operation}.result: addr=0x{aux:X16} q0=0x{result0:X16} q1=0x{result1:X16}");
} }
} }
private static void TraceAprWaitFailure(
CpuContext ctx,
string operation,
uint submissionId,
ulong commandBuffer,
ulong priority,
ulong resultAddress)
{
if (!_traceApr)
{
return;
}
var traceCount = Interlocked.Increment(ref _aprWaitTraceCount);
if (traceCount > 32 && (traceCount & 0x3FF) != 0)
{
return;
}
var returnRip = 0UL;
_ = ctx.TryReadUInt64(ctx[CpuRegister.Rsp], out returnRip);
Console.Error.WriteLine(
$"[LOADER][TRACE] apr.{operation}: id=0x{submissionId:X8} cmd=0x{commandBuffer:X16} " +
$"rsi=0x{priority:X16} rdx=0x{resultAddress:X16} rcx=0x{ctx[CpuRegister.Rcx]:X16} " +
$"r8=0x{ctx[CpuRegister.R8]:X16} r9=0x{ctx[CpuRegister.R9]:X16} ret=0x{returnRip:X16}");
TraceReadableQword(ctx, operation, "rsi", priority);
TraceReadableQword(ctx, operation, "rdx", resultAddress);
TraceReadableQword(ctx, operation, "rcx", ctx[CpuRegister.Rcx]);
TraceReadableQword(ctx, operation, "r8", ctx[CpuRegister.R8]);
}
private static void TraceReadableQword(CpuContext ctx, string operation, string name, ulong address)
{
if (address == 0 || !ctx.TryReadUInt64(address, out var value))
{
return;
}
Console.Error.WriteLine(
$"[LOADER][TRACE] apr.{operation}.{name}: addr=0x{address:X16} q0=0x{value:X16}");
}
} }
@@ -127,6 +127,7 @@ public static class KernelEventFlagCompatExports
TraceEventFlag($"set handle=0x{handle:X16} pattern=0x{pattern:X16} bits=0x{state.Bits:X16} ret=0x{returnRip:X16}"); TraceEventFlag($"set handle=0x{handle:X16} pattern=0x{pattern:X16} bits=0x{state.Bits:X16} ret=0x{returnRip:X16}");
} }
_ = GuestThreadExecution.Scheduler?.WakeBlockedThreads(GetEventFlagWakeKey(handle));
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK); return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
} }
@@ -226,7 +227,7 @@ public static class KernelEventFlagCompatExports
Monitor.Enter(state.Gate); Monitor.Enter(state.Gate);
try try
{ {
if (TryCompleteSatisfiedWait(ctx, state, pattern, waitMode, out var immediateWaitResult)) if (TryCompleteSatisfiedWait(ctx, state, pattern, waitMode, resultAddress, out var immediateWaitResult))
{ {
return SetReturn(ctx, immediateWaitResult); return SetReturn(ctx, immediateWaitResult);
} }
@@ -242,7 +243,28 @@ public static class KernelEventFlagCompatExports
var currentGuestThread = GuestThreadExecution.CurrentGuestThreadHandle; var currentGuestThread = GuestThreadExecution.CurrentGuestThreadHandle;
var currentFiber = FiberExports.GetCurrentFiberAddressForDiagnostics(ctx); var currentFiber = FiberExports.GetCurrentFiberAddressForDiagnostics(ctx);
var managedThread = Environment.CurrentManagedThreadId; var managedThread = Environment.CurrentManagedThreadId;
var requestedBlock = GuestThreadExecution.RequestCurrentThreadBlock("sceKernelWaitEventFlag"); var blockedWaitResult = OrbisGen2Result.ORBIS_GEN2_OK;
var requestedBlock = GuestThreadExecution.RequestCurrentThreadBlock(
ctx,
"sceKernelWaitEventFlag",
GetEventFlagWakeKey(handle),
() => (int)blockedWaitResult,
() =>
{
if (!TryPrepareBlockedWait(
ctx,
state,
pattern,
waitMode,
resultAddress,
out var preparedResult))
{
return false;
}
blockedWaitResult = preparedResult;
return true;
});
TraceEventFlag($"wait-unsatisfied handle=0x{handle:X16} pattern=0x{pattern:X16} bits=0x{state.Bits:X16} guest_thread=0x{currentGuestThread:X16} fiber=0x{currentFiber:X16} managed={managedThread} block={requestedBlock} ret=0x{returnRip:X16} frames={FormatFrameChain(ctx)}"); TraceEventFlag($"wait-unsatisfied handle=0x{handle:X16} pattern=0x{pattern:X16} bits=0x{state.Bits:X16} guest_thread=0x{currentGuestThread:X16} fiber=0x{currentFiber:X16} managed={managedThread} block={requestedBlock} ret=0x{returnRip:X16} frames={FormatFrameChain(ctx)}");
TraceEventFlag($"wait-object handle=0x{handle:X16} name='{state.Name}' {FormatGuestWaitObject(ctx)}"); TraceEventFlag($"wait-object handle=0x{handle:X16} name='{state.Name}' {FormatGuestWaitObject(ctx)}");
if (!requestedBlock) if (!requestedBlock)
@@ -270,7 +292,7 @@ public static class KernelEventFlagCompatExports
Monitor.Enter(state.Gate); Monitor.Enter(state.Gate);
} }
if (TryCompleteSatisfiedWait(ctx, state, pattern, waitMode, out var pumpedWaitResult)) if (TryCompleteSatisfiedWait(ctx, state, pattern, waitMode, resultAddress, out var pumpedWaitResult))
{ {
state.WaitingThreads = Math.Max(0, state.WaitingThreads - 1); state.WaitingThreads = Math.Max(0, state.WaitingThreads - 1);
releaseWaiter = false; releaseWaiter = false;
@@ -369,19 +391,21 @@ public static class KernelEventFlagCompatExports
} }
private static bool TryCompleteSatisfiedWait( private static bool TryCompleteSatisfiedWait(
CpuContext ctx, CpuContext ctx,
EventFlagState state, EventFlagState state,
ulong pattern, ulong pattern,
uint waitMode, uint waitMode,
out OrbisGen2Result result) ulong resultAddress,
out OrbisGen2Result result)
{ {
result = OrbisGen2Result.ORBIS_GEN2_OK; result = OrbisGen2Result.ORBIS_GEN2_OK;
if (!IsSatisfied(state.Bits, pattern, waitMode)) if (!IsSatisfied(state.Bits, pattern, waitMode))
{ {
return false; return false;
} }
if (!TryWriteResultPattern(ctx, ctx[CpuRegister.Rcx], state.Bits)) if (!TryWriteResultPattern(ctx, resultAddress, state.Bits))
{ {
result = OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; result = OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
return true; return true;
@@ -391,6 +415,41 @@ public static class KernelEventFlagCompatExports
return true; return true;
} }
private static bool TryPrepareBlockedWait(
CpuContext ctx,
EventFlagState state,
ulong pattern,
uint waitMode,
ulong resultAddress,
out OrbisGen2Result result)
{
lock (state.Gate)
{
result = OrbisGen2Result.ORBIS_GEN2_OK;
if (!IsSatisfied(state.Bits, pattern, waitMode))
{
return false;
}
if (!TryWriteResultPattern(ctx, resultAddress, state.Bits))
{
result = OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
else
{
ApplyClearMode(state, pattern, waitMode);
}
state.WaitingThreads = Math.Max(0, state.WaitingThreads - 1);
TraceEventFlag(
$"wait-wake pattern=0x{pattern:X16} mode=0x{waitMode:X2} bits=0x{state.Bits:X16} waiters={state.WaitingThreads}");
return true;
}
}
private static string GetEventFlagWakeKey(ulong handle) =>
$"event_flag:0x{handle:X16}";
private static bool TryWriteResultPattern(CpuContext ctx, ulong address, ulong bits) => private static bool TryWriteResultPattern(CpuContext ctx, ulong address, ulong bits) =>
address == 0 || ctx.TryWriteUInt64(address, bits); address == 0 || ctx.TryWriteUInt64(address, bits);
@@ -10,10 +10,14 @@ namespace SharpEmu.Libs.Kernel;
public static class KernelEventQueueCompatExports public static class KernelEventQueueCompatExports
{ {
private const int KernelEventSize = 0x20; private const int KernelEventSize = 0x20;
public const short KernelEventFilterGraphics = -14;
public const short KernelEventFilterAmpr = -16;
public const short KernelEventFilterAmprSystem = -17;
private static readonly object _eventQueueGate = new(); private static readonly object _eventQueueGate = new();
private static readonly HashSet<ulong> _eventQueues = new(); private static readonly HashSet<ulong> _eventQueues = new();
private static readonly Dictionary<ulong, LinkedList<KernelQueuedEvent>> _pendingEvents = new(); private static readonly Dictionary<ulong, LinkedList<KernelQueuedEvent>> _pendingEvents = new();
private static readonly Dictionary<ulong, Dictionary<(ulong Ident, short Filter), KernelEventRegistration>> _registeredEvents = new();
private static long _nextEventQueueHandle = 1; private static long _nextEventQueueHandle = 1;
public readonly record struct KernelQueuedEvent( public readonly record struct KernelQueuedEvent(
@@ -24,6 +28,11 @@ public static class KernelEventQueueCompatExports
ulong Data, ulong Data,
ulong UserData); ulong UserData);
private readonly record struct KernelEventRegistration(
ulong Ident,
short Filter,
ulong UserData);
[SysAbiExport( [SysAbiExport(
Nid = "D0OdFMjp46I", Nid = "D0OdFMjp46I",
ExportName = "sceKernelCreateEqueue", ExportName = "sceKernelCreateEqueue",
@@ -42,6 +51,7 @@ public static class KernelEventQueueCompatExports
{ {
_eventQueues.Add(handle); _eventQueues.Add(handle);
_pendingEvents[handle] = new LinkedList<KernelQueuedEvent>(); _pendingEvents[handle] = new LinkedList<KernelQueuedEvent>();
_registeredEvents[handle] = new Dictionary<(ulong Ident, short Filter), KernelEventRegistration>();
} }
if (!ctx.TryWriteUInt64(outAddress, handle)) if (!ctx.TryWriteUInt64(outAddress, handle))
@@ -65,6 +75,7 @@ public static class KernelEventQueueCompatExports
{ {
_eventQueues.Remove(handle); _eventQueues.Remove(handle);
_pendingEvents.Remove(handle); _pendingEvents.Remove(handle);
_registeredEvents.Remove(handle);
} }
TraceEventQueue(ctx, "delete", handle); TraceEventQueue(ctx, "delete", handle);
@@ -122,8 +133,16 @@ public static class KernelEventQueueCompatExports
LibraryName = "libKernel")] LibraryName = "libKernel")]
public static int KernelAddAmprEvent(CpuContext ctx) public static int KernelAddAmprEvent(CpuContext ctx)
{ {
TraceEventQueue(ctx, "add_ampr", ctx[CpuRegister.Rdi]); var handle = ctx[CpuRegister.Rdi];
return (int)OrbisGen2Result.ORBIS_GEN2_OK; var registered = RegisterEvent(
handle,
unchecked((uint)ctx[CpuRegister.Rsi]),
KernelEventFilterAmpr,
ctx[CpuRegister.Rdx]);
TraceEventQueue(ctx, "add_ampr", handle);
return registered
? (int)OrbisGen2Result.ORBIS_GEN2_OK
: (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
} }
[SysAbiExport( [SysAbiExport(
@@ -133,8 +152,16 @@ public static class KernelEventQueueCompatExports
LibraryName = "libKernel")] LibraryName = "libKernel")]
public static int KernelAddAmprSystemEvent(CpuContext ctx) public static int KernelAddAmprSystemEvent(CpuContext ctx)
{ {
TraceEventQueue(ctx, "add_ampr_system", ctx[CpuRegister.Rdi]); var handle = ctx[CpuRegister.Rdi];
return (int)OrbisGen2Result.ORBIS_GEN2_OK; var registered = RegisterEvent(
handle,
unchecked((uint)ctx[CpuRegister.Rsi]),
KernelEventFilterAmprSystem,
ctx[CpuRegister.Rdx]);
TraceEventQueue(ctx, "add_ampr_system", handle);
return registered
? (int)OrbisGen2Result.ORBIS_GEN2_OK
: (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
} }
[SysAbiExport( [SysAbiExport(
@@ -144,8 +171,15 @@ public static class KernelEventQueueCompatExports
LibraryName = "libKernel")] LibraryName = "libKernel")]
public static int KernelDeleteAmprEvent(CpuContext ctx) public static int KernelDeleteAmprEvent(CpuContext ctx)
{ {
TraceEventQueue(ctx, "delete_ampr", ctx[CpuRegister.Rdi]); var handle = ctx[CpuRegister.Rdi];
return (int)OrbisGen2Result.ORBIS_GEN2_OK; var deleted = DeleteRegisteredEvent(
handle,
unchecked((uint)ctx[CpuRegister.Rsi]),
KernelEventFilterAmpr);
TraceEventQueue(ctx, "delete_ampr", handle);
return deleted
? (int)OrbisGen2Result.ORBIS_GEN2_OK
: (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
} }
[SysAbiExport( [SysAbiExport(
@@ -155,8 +189,15 @@ public static class KernelEventQueueCompatExports
LibraryName = "libKernel")] LibraryName = "libKernel")]
public static int KernelDeleteAmprSystemEvent(CpuContext ctx) public static int KernelDeleteAmprSystemEvent(CpuContext ctx)
{ {
TraceEventQueue(ctx, "delete_ampr_system", ctx[CpuRegister.Rdi]); var handle = ctx[CpuRegister.Rdi];
return (int)OrbisGen2Result.ORBIS_GEN2_OK; var deleted = DeleteRegisteredEvent(
handle,
unchecked((uint)ctx[CpuRegister.Rsi]),
KernelEventFilterAmprSystem);
TraceEventQueue(ctx, "delete_ampr_system", handle);
return deleted
? (int)OrbisGen2Result.ORBIS_GEN2_OK
: (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
} }
[SysAbiExport( [SysAbiExport(
@@ -171,6 +212,57 @@ public static class KernelEventQueueCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
[SysAbiExport(
Nid = "vz+pg2zdopI",
ExportName = "sceKernelGetEventUserData",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelGetEventUserData(CpuContext ctx)
{
_ = ctx.TryReadUInt64(ctx[CpuRegister.Rdi] + 0x18, out var userData);
ctx[CpuRegister.Rax] = userData;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "mJ7aghmgvfc",
ExportName = "sceKernelGetEventId",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelGetEventId(CpuContext ctx)
{
_ = ctx.TryReadUInt64(ctx[CpuRegister.Rdi], out var ident);
ctx[CpuRegister.Rax] = ident;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "23CPPI1tyBY",
ExportName = "sceKernelGetEventFilter",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelGetEventFilter(CpuContext ctx)
{
Span<byte> filterBytes = stackalloc byte[sizeof(short)];
var filter = ctx.Memory.TryRead(ctx[CpuRegister.Rdi] + 0x08, filterBytes)
? BinaryPrimitives.ReadInt16LittleEndian(filterBytes)
: (short)0;
ctx[CpuRegister.Rax] = unchecked((uint)filter);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "kwGyyjohI50",
ExportName = "sceKernelGetEventData",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelGetEventData(CpuContext ctx)
{
_ = ctx.TryReadUInt64(ctx[CpuRegister.Rdi] + 0x10, out var data);
ctx[CpuRegister.Rax] = data;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport( [SysAbiExport(
Nid = "fzyMKs9kim0", Nid = "fzyMKs9kim0",
ExportName = "sceKernelWaitEqueue", ExportName = "sceKernelWaitEqueue",
@@ -196,7 +288,13 @@ public static class KernelEventQueueCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
if (timeoutAddress == 0 && GuestThreadExecution.RequestCurrentThreadBlock("sceKernelWaitEqueue")) if (timeoutAddress == 0 &&
GuestThreadExecution.RequestCurrentThreadBlock(
ctx,
"sceKernelWaitEqueue",
GetEventQueueWakeKey(handle),
() => ResumeWaitEqueue(ctx, handle, eventsAddress, eventCapacity, outCountAddress),
() => HasPendingEvents(handle)))
{ {
TraceEventQueue(ctx, "wait-block", handle); TraceEventQueue(ctx, "wait-block", handle);
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
@@ -216,6 +314,7 @@ public static class KernelEventQueueCompatExports
public static bool EnqueueEvent(ulong handle, KernelQueuedEvent queuedEvent) public static bool EnqueueEvent(ulong handle, KernelQueuedEvent queuedEvent)
{ {
var queued = false;
lock (_eventQueueGate) lock (_eventQueueGate)
{ {
if (!_eventQueues.Contains(handle)) if (!_eventQueues.Contains(handle))
@@ -230,10 +329,100 @@ public static class KernelEventQueueCompatExports
} }
queue.AddLast(queuedEvent); queue.AddLast(queuedEvent);
queued = true;
}
if (queued)
{
WakeEventQueue(handle);
}
return queued;
}
public static bool RegisterEvent(
ulong handle,
ulong ident,
short filter,
ulong userData)
{
lock (_eventQueueGate)
{
if (!_eventQueues.Contains(handle))
{
return false;
}
if (!_registeredEvents.TryGetValue(handle, out var events))
{
events = new Dictionary<(ulong Ident, short Filter), KernelEventRegistration>();
_registeredEvents[handle] = events;
}
events[(ident, filter)] = new KernelEventRegistration(ident, filter, userData);
return true; return true;
} }
} }
public static bool DeleteRegisteredEvent(
ulong handle,
ulong ident,
short filter)
{
lock (_eventQueueGate)
{
return _registeredEvents.TryGetValue(handle, out var events) &&
events.Remove((ident, filter));
}
}
public static int TriggerRegisteredEvents(
ulong ident,
short filter,
ulong data)
{
List<ulong>? wakeHandles = null;
var triggeredCount = 0;
lock (_eventQueueGate)
{
foreach (var (handle, registrations) in _registeredEvents)
{
if (!registrations.TryGetValue((ident, filter), out var registration))
{
continue;
}
if (!_pendingEvents.TryGetValue(handle, out var queue))
{
queue = new LinkedList<KernelQueuedEvent>();
_pendingEvents[handle] = queue;
}
QueueOrUpdateEvent(
queue,
new KernelQueuedEvent(
registration.Ident,
registration.Filter,
0,
1,
data,
registration.UserData));
(wakeHandles ??= new List<ulong>()).Add(handle);
triggeredCount++;
}
}
if (wakeHandles is not null)
{
foreach (var handle in wakeHandles)
{
WakeEventQueue(handle);
}
}
return triggeredCount;
}
public static bool TriggerDisplayEvent( public static bool TriggerDisplayEvent(
ulong handle, ulong handle,
ulong ident, ulong ident,
@@ -241,6 +430,7 @@ public static class KernelEventQueueCompatExports
ulong eventHint, ulong eventHint,
ulong userData) ulong userData)
{ {
var triggered = false;
lock (_eventQueueGate) lock (_eventQueueGate)
{ {
if (!_eventQueues.Contains(handle)) if (!_eventQueues.Contains(handle))
@@ -254,17 +444,8 @@ public static class KernelEventQueueCompatExports
_pendingEvents[handle] = events; _pendingEvents[handle] = events;
} }
LinkedListNode<KernelQueuedEvent>? pendingNode = null;
for (var node = events.First; node is not null; node = node.Next)
{
if (node.Value.Ident == ident && node.Value.Filter == filter)
{
pendingNode = node;
break;
}
}
var count = 1UL; var count = 1UL;
var pendingNode = FindPendingEvent(events, ident, filter);
if (pendingNode is not null) if (pendingNode is not null)
{ {
count = Math.Min(((pendingNode.Value.Data >> 12) & 0xFUL) + 1, 0xFUL); count = Math.Min(((pendingNode.Value.Data >> 12) & 0xFUL) + 1, 0xFUL);
@@ -289,8 +470,80 @@ public static class KernelEventQueueCompatExports
events.AddLast(triggeredEvent); events.AddLast(triggeredEvent);
} }
return true; triggered = true;
} }
if (triggered)
{
WakeEventQueue(handle);
}
return triggered;
}
private static int ResumeWaitEqueue(
CpuContext ctx,
ulong handle,
ulong eventsAddress,
int eventCapacity,
ulong outCountAddress)
{
var deliveredCount = DequeueEvents(ctx, handle, eventsAddress, eventCapacity);
if (outCountAddress != 0 && !TryWriteUInt32(ctx, outCountAddress, (uint)deliveredCount))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
private static bool HasPendingEvents(ulong handle)
{
lock (_eventQueueGate)
{
return _pendingEvents.TryGetValue(handle, out var events) && events.Count != 0;
}
}
private static void QueueOrUpdateEvent(
LinkedList<KernelQueuedEvent> queue,
KernelQueuedEvent queuedEvent)
{
var pendingNode = FindPendingEvent(queue, queuedEvent.Ident, queuedEvent.Filter);
if (pendingNode is null)
{
queue.AddLast(queuedEvent);
return;
}
pendingNode.Value = queuedEvent with
{
Fflags = Math.Max(pendingNode.Value.Fflags + 1, queuedEvent.Fflags),
};
}
private static LinkedListNode<KernelQueuedEvent>? FindPendingEvent(
LinkedList<KernelQueuedEvent> queue,
ulong ident,
short filter)
{
for (var node = queue.First; node is not null; node = node.Next)
{
if (node.Value.Ident == ident && node.Value.Filter == filter)
{
return node;
}
}
return null;
}
private static string GetEventQueueWakeKey(ulong handle) =>
$"sceKernelWaitEqueue:{handle:X16}";
private static void WakeEventQueue(ulong handle)
{
_ = GuestThreadExecution.Scheduler?.WakeBlockedThreads(GetEventQueueWakeKey(handle));
} }
private static int DequeueEvents(CpuContext ctx, ulong handle, ulong eventsAddress, int eventCapacity) private static int DequeueEvents(CpuContext ctx, ulong handle, ulong eventsAddress, int eventCapacity)
+26
View File
@@ -252,6 +252,32 @@ public static class KernelExports
return PthreadCreate(ctx); return PthreadCreate(ctx);
} }
[SysAbiExport(
Nid = "3kg7rT0NQIs",
ExportName = "scePthreadExit",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadExit(CpuContext ctx)
{
var value = ctx[CpuRegister.Rdi];
GuestThreadExecution.RequestCurrentEntryExit("scePthreadExit", value);
ctx[CpuRegister.Rax] = value;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "FJrT5LuUBAU",
ExportName = "pthread_exit",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libScePosix")]
public static int PosixPthreadExit(CpuContext ctx)
{
var value = ctx[CpuRegister.Rdi];
GuestThreadExecution.RequestCurrentEntryExit("pthread_exit", value);
ctx[CpuRegister.Rax] = value;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport( [SysAbiExport(
Nid = "onNY9Byn-W8", Nid = "onNY9Byn-W8",
ExportName = "scePthreadJoin", ExportName = "scePthreadJoin",
@@ -65,6 +65,7 @@ public static class KernelMemoryCompatExports
private const uint HostPageExecuteWriteCopy = 0x80; private const uint HostPageExecuteWriteCopy = 0x80;
private const uint HostPageGuard = 0x100; private const uint HostPageGuard = 0x100;
private const int Enomem = 12; private const int Enomem = 12;
private const int Efault = 14;
private const int Einval = 22; private const int Einval = 22;
private const int Erange = 34; private const int Erange = 34;
private const int Struncate = 80; private const int Struncate = 80;
@@ -96,10 +97,14 @@ public static class KernelMemoryCompatExports
private static readonly object _libcAllocGate = new(); private static readonly object _libcAllocGate = new();
private static readonly object _memoryGate = new(); private static readonly object _memoryGate = new();
private static readonly object _tlsGate = new(); private static readonly object _tlsGate = new();
private static readonly object _ioTraceGate = new();
private static readonly object _statCacheGate = new();
private static readonly Dictionary<ulong, DirectAllocation> _directAllocations = new(); private static readonly Dictionary<ulong, DirectAllocation> _directAllocations = new();
private static readonly Dictionary<ulong, LibcHeapAllocation> _libcAllocations = new(); private static readonly Dictionary<ulong, LibcHeapAllocation> _libcAllocations = new();
private static readonly Dictionary<ulong, MappedRegion> _mappedRegions = new(); private static readonly Dictionary<ulong, MappedRegion> _mappedRegions = new();
private static readonly Dictionary<ulong, ulong> _tlsModuleBlocks = new(); private static readonly Dictionary<ulong, ulong> _tlsModuleBlocks = new();
private static readonly HashSet<string> _tracedStatResults = new(StringComparer.Ordinal);
private static readonly HashSet<string> _negativeStatCache = new(StringComparer.OrdinalIgnoreCase);
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;
@@ -114,6 +119,8 @@ public static class KernelMemoryCompatExports
private static int _hostMemoryWriteFallbackCount; private static int _hostMemoryWriteFallbackCount;
private static int _hostMemoryReadFallbackCount; private static int _hostMemoryReadFallbackCount;
private static int _nullWcscpyRecoveryCount; private static int _nullWcscpyRecoveryCount;
private static string? _cachedApp0Root;
private static string? _cachedDownload0Root;
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
private struct MemoryBasicInformation private struct MemoryBasicInformation
@@ -1236,6 +1243,12 @@ public static class KernelMemoryCompatExports
var mode = ResolveOpenMode(flags, access); var mode = ResolveOpenMode(flags, access);
try try
{ {
if (IsMutatingOpen(flags) && IsReadOnlyGuestMutationPath(guestPath))
{
LogOpenTrace($"_open readonly path='{guestPath}' host='{hostPath}' flags=0x{flags:X8}");
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_PERMISSION_DENIED;
}
var wantsDirectory = (flags & O_DIRECTORY) != 0; var wantsDirectory = (flags & O_DIRECTORY) != 0;
if (wantsDirectory || Directory.Exists(hostPath)) if (wantsDirectory || Directory.Exists(hostPath))
{ {
@@ -1280,6 +1293,11 @@ public static class KernelMemoryCompatExports
_openFiles[fd] = stream; _openFiles[fd] = stream;
} }
if (IsMutatingOpen(flags))
{
InvalidateNegativeStatCacheForPathAndAncestors(guestPath);
}
LogOpenTrace($"_open file path='{guestPath}' host='{hostPath}' flags=0x{flags:X8} fd={fd}"); LogOpenTrace($"_open file path='{guestPath}' host='{hostPath}' flags=0x{flags:X8} fd={fd}");
ctx[CpuRegister.Rax] = unchecked((ulong)fd); ctx[CpuRegister.Rax] = unchecked((ulong)fd);
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
@@ -1334,11 +1352,30 @@ public static class KernelMemoryCompatExports
} }
var hostPath = ResolveGuestPath(guestPath); var hostPath = ResolveGuestPath(guestPath);
if (!TryWriteHostPathStat(ctx, statAddress, hostPath)) var statCacheKey = GetNegativeStatCacheKey(guestPath);
if (statCacheKey is not null && IsNegativeStatCached(statCacheKey))
{ {
LogUniqueStatTrace(guestPath, hostPath, found: false);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
} }
if (!TryWriteHostPathStat(ctx, statAddress, hostPath))
{
if (statCacheKey is not null)
{
AddNegativeStatCache(statCacheKey);
}
LogUniqueStatTrace(guestPath, hostPath, found: false);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
if (statCacheKey is not null)
{
RemoveNegativeStatCache(statCacheKey);
}
LogUniqueStatTrace(guestPath, hostPath, found: true);
ctx[CpuRegister.Rax] = 0; ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
@@ -1356,13 +1393,22 @@ public static class KernelMemoryCompatExports
var sizesAddress = ctx[CpuRegister.Rcx]; var sizesAddress = ctx[CpuRegister.Rcx];
if (pathListAddress == 0 || count == 0 || sizesAddress == 0 || count > 1024) if (pathListAddress == 0 || count == 0 || sizesAddress == 0 || count > 1024)
{ {
KernelRuntimeCompatExports.TrySetErrno(ctx, Einval);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
} }
for (ulong i = 0; i < count; i++) for (ulong i = 0; i < count; i++)
{ {
if (idsAddress != 0 &&
!TryWriteUInt32Compat(ctx, idsAddress + (i * sizeof(uint)), uint.MaxValue))
{
KernelRuntimeCompatExports.TrySetErrno(ctx, Efault);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
if (!TryResolveAprFilepath(ctx, pathListAddress, i, out var guestPath)) if (!TryResolveAprFilepath(ctx, pathListAddress, i, out var guestPath))
{ {
KernelRuntimeCompatExports.TrySetErrno(ctx, Efault);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
} }
@@ -1370,6 +1416,7 @@ public static class KernelMemoryCompatExports
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"); LogIoTrace("apr_resolve", guestPath, $"host='{hostPath}' index={i} count={count} result=not_found");
KernelRuntimeCompatExports.TrySetErrno(ctx, 2);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
} }
@@ -1379,11 +1426,13 @@ public static class KernelMemoryCompatExports
if (idsAddress != 0 && if (idsAddress != 0 &&
!TryWriteUInt32Compat(ctx, idsAddress + (i * sizeof(uint)), fileId)) !TryWriteUInt32Compat(ctx, idsAddress + (i * sizeof(uint)), fileId))
{ {
KernelRuntimeCompatExports.TrySetErrno(ctx, Efault);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
} }
if (!TryWriteUInt64Compat(ctx, sizesAddress + (i * sizeof(ulong)), fileSize)) if (!TryWriteUInt64Compat(ctx, sizesAddress + (i * sizeof(ulong)), fileSize))
{ {
KernelRuntimeCompatExports.TrySetErrno(ctx, Efault);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
} }
} }
@@ -1415,6 +1464,170 @@ public static class KernelMemoryCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
[SysAbiExport(
Nid = "AUXVxWeJU-A",
ExportName = "sceKernelUnlink",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelUnlink(CpuContext ctx)
{
var pathAddress = ctx[CpuRegister.Rdi];
if (pathAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
if (!TryReadNullTerminatedUtf8(ctx, pathAddress, MaxGuestStringLength, out var guestPath))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
var hostPath = ResolveGuestPath(guestPath);
if (IsReadOnlyGuestMutationPath(guestPath))
{
LogOpenTrace($"unlink readonly path='{guestPath}' host='{hostPath}'");
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_PERMISSION_DENIED;
}
try
{
if (Directory.Exists(hostPath))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_PERMISSION_DENIED;
}
if (!File.Exists(hostPath))
{
AddNegativeStatCacheForGuestPath(guestPath);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
File.Delete(hostPath);
InvalidateNegativeStatCacheForPathAndAncestors(guestPath);
AddNegativeStatCacheForGuestPath(guestPath);
LogOpenTrace($"unlink path='{guestPath}' host='{hostPath}'");
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
catch (UnauthorizedAccessException)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_PERMISSION_DENIED;
}
catch (IOException)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_BUSY;
}
}
[SysAbiExport(
Nid = "1-LFLmRFxxM",
ExportName = "sceKernelMkdir",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelMkdir(CpuContext ctx)
{
var pathAddress = ctx[CpuRegister.Rdi];
if (pathAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
if (!TryReadNullTerminatedUtf8(ctx, pathAddress, MaxGuestStringLength, out var guestPath))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
var hostPath = ResolveGuestPath(guestPath);
if (IsReadOnlyGuestMutationPath(guestPath))
{
LogOpenTrace($"mkdir readonly path='{guestPath}' host='{hostPath}'");
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_PERMISSION_DENIED;
}
try
{
if (File.Exists(hostPath) || Directory.Exists(hostPath))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_ALREADY_EXISTS;
}
var parentDirectory = Path.GetDirectoryName(hostPath);
if (string.IsNullOrWhiteSpace(parentDirectory) || !Directory.Exists(parentDirectory))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
Directory.CreateDirectory(hostPath);
if (!Directory.Exists(hostPath))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
InvalidateNegativeStatCacheForPathAndAncestors(guestPath);
LogOpenTrace($"mkdir path='{guestPath}' host='{hostPath}'");
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
catch (UnauthorizedAccessException)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_PERMISSION_DENIED;
}
catch (IOException)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
}
[SysAbiExport(
Nid = "naInUjYt3so",
ExportName = "sceKernelRmdir",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelRmdir(CpuContext ctx)
{
var pathAddress = ctx[CpuRegister.Rdi];
if (pathAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
if (!TryReadNullTerminatedUtf8(ctx, pathAddress, MaxGuestStringLength, out var guestPath))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
var hostPath = ResolveGuestPath(guestPath);
if (IsReadOnlyGuestMutationPath(guestPath))
{
LogOpenTrace($"rmdir readonly path='{guestPath}' host='{hostPath}'");
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_PERMISSION_DENIED;
}
try
{
if (!Directory.Exists(hostPath))
{
AddNegativeStatCacheForGuestPath(guestPath);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
Directory.Delete(hostPath, recursive: false);
InvalidateNegativeStatCacheForPathAndAncestors(guestPath);
AddNegativeStatCacheForGuestPath(guestPath);
LogOpenTrace($"rmdir path='{guestPath}' host='{hostPath}'");
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
catch (UnauthorizedAccessException)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_PERMISSION_DENIED;
}
catch (IOException)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_BUSY;
}
}
private static int KernelCloseCore(CpuContext ctx, int fd) private static int KernelCloseCore(CpuContext ctx, int fd)
{ {
if (fd is 0 or 1 or 2) if (fd is 0 or 1 or 2)
@@ -3798,57 +4011,72 @@ public static class KernelMemoryCompatExports
return guestPath; return guestPath;
} }
var devlogAppRoot = ResolveDevlogAppRoot();
if (guestPath.StartsWith("/devlog/app/", StringComparison.OrdinalIgnoreCase)) if (guestPath.StartsWith("/devlog/app/", StringComparison.OrdinalIgnoreCase))
{ {
var relative = NormalizeMountRelativePath(guestPath["/devlog/app/".Length..]); var relative = NormalizeMountRelativePath(guestPath["/devlog/app/".Length..]);
return Path.Combine(devlogAppRoot, relative); return Path.Combine(ResolveDevlogAppRoot(), relative);
} }
if (guestPath.StartsWith("devlog/app/", StringComparison.OrdinalIgnoreCase)) if (guestPath.StartsWith("devlog/app/", StringComparison.OrdinalIgnoreCase))
{ {
var relative = NormalizeMountRelativePath(guestPath["devlog/app/".Length..]); var relative = NormalizeMountRelativePath(guestPath["devlog/app/".Length..]);
return Path.Combine(devlogAppRoot, relative); return Path.Combine(ResolveDevlogAppRoot(), relative);
} }
if (string.Equals(guestPath, "/devlog/app", StringComparison.OrdinalIgnoreCase) || if (string.Equals(guestPath, "/devlog/app", StringComparison.OrdinalIgnoreCase) ||
string.Equals(guestPath, "devlog/app", StringComparison.OrdinalIgnoreCase)) string.Equals(guestPath, "devlog/app", StringComparison.OrdinalIgnoreCase))
{ {
return devlogAppRoot; return ResolveDevlogAppRoot();
} }
var temp0Root = ResolveTemp0Root();
if (guestPath.StartsWith("/temp0/", StringComparison.OrdinalIgnoreCase)) if (guestPath.StartsWith("/temp0/", StringComparison.OrdinalIgnoreCase))
{ {
var relative = NormalizeMountRelativePath(guestPath["/temp0/".Length..]); var relative = NormalizeMountRelativePath(guestPath["/temp0/".Length..]);
return Path.Combine(temp0Root, relative); return Path.Combine(ResolveTemp0Root(), relative);
} }
if (string.Equals(guestPath, "/temp0", StringComparison.OrdinalIgnoreCase)) if (string.Equals(guestPath, "/temp0", StringComparison.OrdinalIgnoreCase))
{ {
return temp0Root; return ResolveTemp0Root();
}
if (guestPath.StartsWith("/download0/", StringComparison.OrdinalIgnoreCase))
{
var relative = NormalizeMountRelativePath(guestPath["/download0/".Length..]);
return Path.Combine(ResolveDownload0Root(), relative);
}
if (guestPath.StartsWith("download0/", StringComparison.OrdinalIgnoreCase))
{
var relative = NormalizeMountRelativePath(guestPath["download0/".Length..]);
return Path.Combine(ResolveDownload0Root(), relative);
}
if (string.Equals(guestPath, "/download0", StringComparison.OrdinalIgnoreCase) ||
string.Equals(guestPath, "download0", StringComparison.OrdinalIgnoreCase))
{
return ResolveDownload0Root();
} }
var hostappRoot = ResolveHostappRoot();
if (guestPath.StartsWith("/hostapp/", StringComparison.OrdinalIgnoreCase)) if (guestPath.StartsWith("/hostapp/", StringComparison.OrdinalIgnoreCase))
{ {
var relative = NormalizeMountRelativePath(guestPath["/hostapp/".Length..]); var relative = NormalizeMountRelativePath(guestPath["/hostapp/".Length..]);
return Path.Combine(hostappRoot, relative); return Path.Combine(ResolveHostappRoot(), relative);
} }
if (guestPath.StartsWith("hostapp/", StringComparison.OrdinalIgnoreCase)) if (guestPath.StartsWith("hostapp/", StringComparison.OrdinalIgnoreCase))
{ {
var relative = NormalizeMountRelativePath(guestPath["hostapp/".Length..]); var relative = NormalizeMountRelativePath(guestPath["hostapp/".Length..]);
return Path.Combine(hostappRoot, relative); return Path.Combine(ResolveHostappRoot(), relative);
} }
if (string.Equals(guestPath, "/hostapp", StringComparison.OrdinalIgnoreCase) || if (string.Equals(guestPath, "/hostapp", StringComparison.OrdinalIgnoreCase) ||
string.Equals(guestPath, "hostapp", StringComparison.OrdinalIgnoreCase)) string.Equals(guestPath, "hostapp", StringComparison.OrdinalIgnoreCase))
{ {
return hostappRoot; return ResolveHostappRoot();
} }
var app0Root = Environment.GetEnvironmentVariable("SHARPEMU_APP0_DIR"); var app0Root = ResolveApp0Root();
if (!string.IsNullOrWhiteSpace(app0Root)) if (!string.IsNullOrWhiteSpace(app0Root))
{ {
if (string.Equals(guestPath, "/app0", StringComparison.OrdinalIgnoreCase) || if (string.Equals(guestPath, "/app0", StringComparison.OrdinalIgnoreCase) ||
@@ -3881,6 +4109,24 @@ public static class KernelMemoryCompatExports
return guestPath; return guestPath;
} }
private static string? ResolveApp0Root()
{
var cached = Volatile.Read(ref _cachedApp0Root);
if (!string.IsNullOrWhiteSpace(cached))
{
return cached;
}
var configured = Environment.GetEnvironmentVariable("SHARPEMU_APP0_DIR");
if (string.IsNullOrWhiteSpace(configured))
{
return null;
}
Interlocked.CompareExchange(ref _cachedApp0Root, configured, null);
return _cachedApp0Root;
}
private static string NormalizeMountRelativePath(string relativePath) private static string NormalizeMountRelativePath(string relativePath)
{ {
return relativePath return relativePath
@@ -3931,6 +4177,32 @@ public static class KernelMemoryCompatExports
return root; return root;
} }
private static string ResolveDownload0Root()
{
var cached = Volatile.Read(ref _cachedDownload0Root);
if (!string.IsNullOrWhiteSpace(cached))
{
return cached;
}
const string download0VariableName = "SHARPEMU_DOWNLOAD0_DIR";
var configuredRoot = Environment.GetEnvironmentVariable(download0VariableName);
string root;
if (!string.IsNullOrWhiteSpace(configuredRoot))
{
root = Path.GetFullPath(configuredRoot);
}
else
{
root = Path.Combine(GetPerAppWritableRoot(), "download0");
Environment.SetEnvironmentVariable(download0VariableName, root);
}
Directory.CreateDirectory(root);
Interlocked.CompareExchange(ref _cachedDownload0Root, root, null);
return _cachedDownload0Root;
}
private static string ResolveHostappRoot() private static string ResolveHostappRoot()
{ {
const string hostappVariableName = "SHARPEMU_HOSTAPP_DIR"; const string hostappVariableName = "SHARPEMU_HOSTAPP_DIR";
@@ -3950,6 +4222,22 @@ public static class KernelMemoryCompatExports
return root; return root;
} }
private static string GetPerAppWritableRoot()
{
var app0Root = Environment.GetEnvironmentVariable("SHARPEMU_APP0_DIR");
var appName = string.IsNullOrWhiteSpace(app0Root)
? "default"
: Path.GetFileName(Path.TrimEndingDirectorySeparator(app0Root));
if (string.IsNullOrWhiteSpace(appName))
{
appName = "default";
}
var invalidChars = Path.GetInvalidFileNameChars();
appName = new string(appName.Select(ch => invalidChars.Contains(ch) ? '_' : ch).ToArray());
return Path.Combine(Path.GetTempPath(), "SharpEmu", appName);
}
private static void EnsureOpenParentDirectoryExists(string guestPath, string hostPath, int flags) private static void EnsureOpenParentDirectoryExists(string guestPath, string hostPath, int flags)
{ {
if (string.IsNullOrWhiteSpace(hostPath)) if (string.IsNullOrWhiteSpace(hostPath))
@@ -3973,6 +4261,17 @@ public static class KernelMemoryCompatExports
} }
} }
private static bool IsMutatingOpen(int flags) =>
(flags & (O_WRONLY | O_RDWR | O_CREAT | O_TRUNC | O_APPEND)) != 0;
private static bool IsReadOnlyGuestMutationPath(string guestPath)
{
var normalized = NormalizeGuestStatCachePath(guestPath);
return normalized is not null &&
(string.Equals(normalized, "/app0", StringComparison.OrdinalIgnoreCase) ||
normalized.StartsWith("/app0/", StringComparison.OrdinalIgnoreCase));
}
private static bool TryReadCString(CpuContext ctx, ulong address, ulong maxLength, out byte[] bytes) private static bool TryReadCString(CpuContext ctx, ulong address, ulong maxLength, out byte[] bytes)
{ {
return TryReadBytesUntilNull(ctx, address, maxLength, 1_048_576, out bytes); return TryReadBytesUntilNull(ctx, address, maxLength, 1_048_576, out bytes);
@@ -4350,7 +4649,7 @@ public static class KernelMemoryCompatExports
return true; return true;
} }
private static bool TryReadUInt64Compat(CpuContext ctx, ulong address, out ulong value) internal static bool TryReadUInt64Compat(CpuContext ctx, ulong address, out ulong value)
{ {
Span<byte> bytes = stackalloc byte[sizeof(ulong)]; Span<byte> bytes = stackalloc byte[sizeof(ulong)];
if (!TryReadCompat(ctx, address, bytes)) if (!TryReadCompat(ctx, address, bytes))
@@ -4397,7 +4696,7 @@ public static class KernelMemoryCompatExports
return TryWriteCompat(ctx, address, bytes); return TryWriteCompat(ctx, address, bytes);
} }
private static bool TryWriteUInt64Compat(CpuContext ctx, ulong address, ulong value) internal static bool TryWriteUInt64Compat(CpuContext ctx, ulong address, ulong value)
{ {
Span<byte> bytes = stackalloc byte[sizeof(ulong)]; Span<byte> bytes = stackalloc byte[sizeof(ulong)];
BinaryPrimitives.WriteUInt64LittleEndian(bytes, value); BinaryPrimitives.WriteUInt64LittleEndian(bytes, value);
@@ -5345,19 +5644,20 @@ public static class KernelMemoryCompatExports
size = 0; size = 0;
try try
{ {
if (Directory.Exists(hostPath)) var fileInfo = new FileInfo(hostPath);
if (fileInfo.Exists)
{ {
size = 65536; var length = fileInfo.Length;
size = length < 0 ? 0UL : unchecked((ulong)length);
return true; return true;
} }
if (!File.Exists(hostPath)) if (!new DirectoryInfo(hostPath).Exists)
{ {
return false; return false;
} }
var length = new FileInfo(hostPath).Length; size = 65536;
size = length < 0 ? 0UL : unchecked((ulong)length);
return true; return true;
} }
catch catch
@@ -5542,6 +5842,120 @@ public static class KernelMemoryCompatExports
Console.Error.WriteLine($"[LOADER][TRACE] {operation} path='{path}' {detail}"); Console.Error.WriteLine($"[LOADER][TRACE] {operation} path='{path}' {detail}");
} }
private static void LogUniqueStatTrace(string guestPath, string hostPath, bool found)
{
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_IO"), "1", StringComparison.Ordinal))
{
return;
}
var result = found ? "found" : "not_found";
lock (_ioTraceGate)
{
if (!_tracedStatResults.Add($"{result}\0{guestPath}"))
{
return;
}
}
LogIoTrace("stat", guestPath, $"host='{hostPath}' result={result}");
}
private static string? GetNegativeStatCacheKey(string guestPath)
{
var normalized = NormalizeGuestStatCachePath(guestPath);
return IsReadOnlyGuestStatPath(normalized) ? normalized : null;
}
private static string? NormalizeGuestStatCachePath(string guestPath)
{
var normalized = guestPath.Replace('\\', '/').TrimEnd('/');
if (normalized.Length == 0)
{
return null;
}
if (normalized[0] != '/')
{
normalized = "/" + normalized;
}
return normalized;
}
private static bool IsReadOnlyGuestStatPath(string? normalizedGuestPath) =>
normalizedGuestPath is not null &&
(string.Equals(normalizedGuestPath, "/app0", StringComparison.OrdinalIgnoreCase) ||
normalizedGuestPath.StartsWith("/app0/", StringComparison.OrdinalIgnoreCase) ||
string.Equals(normalizedGuestPath, "/hostapp", StringComparison.OrdinalIgnoreCase) ||
normalizedGuestPath.StartsWith("/hostapp/", StringComparison.OrdinalIgnoreCase) ||
string.Equals(normalizedGuestPath, "/devlog/app", StringComparison.OrdinalIgnoreCase) ||
normalizedGuestPath.StartsWith("/devlog/app/", StringComparison.OrdinalIgnoreCase) ||
string.Equals(normalizedGuestPath, "/temp0", StringComparison.OrdinalIgnoreCase) ||
normalizedGuestPath.StartsWith("/temp0/", StringComparison.OrdinalIgnoreCase) ||
string.Equals(normalizedGuestPath, "/download0", StringComparison.OrdinalIgnoreCase) ||
normalizedGuestPath.StartsWith("/download0/", StringComparison.OrdinalIgnoreCase));
private static bool IsNegativeStatCached(string cacheKey)
{
lock (_statCacheGate)
{
return _negativeStatCache.Contains(cacheKey);
}
}
private static void AddNegativeStatCache(string cacheKey)
{
lock (_statCacheGate)
{
_negativeStatCache.Add(cacheKey);
}
}
private static void RemoveNegativeStatCache(string cacheKey)
{
lock (_statCacheGate)
{
_negativeStatCache.Remove(cacheKey);
}
}
private static void AddNegativeStatCacheForGuestPath(string guestPath)
{
var cacheKey = GetNegativeStatCacheKey(guestPath);
if (cacheKey is not null)
{
AddNegativeStatCache(cacheKey);
}
}
private static void InvalidateNegativeStatCacheForPathAndAncestors(string guestPath)
{
var normalized = NormalizeGuestStatCachePath(guestPath);
if (normalized is null)
{
return;
}
lock (_statCacheGate)
{
var current = normalized;
while (true)
{
_negativeStatCache.Remove(current);
var slash = current.LastIndexOf('/');
if (slash <= 0)
{
break;
}
current = current[..slash];
}
_negativeStatCache.Remove("/");
}
}
private static string PreviewIoBytes(byte[] buffer, int count, int maxBytes) private static string PreviewIoBytes(byte[] buffer, int count, int maxBytes)
{ {
if (count <= 0) if (count <= 0)
@@ -1,6 +1,7 @@
// Copyright (C) 2026 SharpEmu Emulator Project // Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
using System.Collections.Concurrent;
using SharpEmu.HLE; using SharpEmu.HLE;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Threading; using System.Threading;
@@ -10,7 +11,7 @@ namespace SharpEmu.Libs.Kernel;
public static class KernelPthreadCompatExports public static class KernelPthreadCompatExports
{ {
private const int MutexTypeDefault = 1; private const int MutexTypeDefault = 0;
private const int MutexTypeErrorCheck = 1; private const int MutexTypeErrorCheck = 1;
private const int MutexTypeRecursive = 2; private const int MutexTypeRecursive = 2;
private const int MutexTypeNormal = 3; private const int MutexTypeNormal = 3;
@@ -25,11 +26,18 @@ public static class KernelPthreadCompatExports
private const int PthreadOnceDone = 2; private const int PthreadOnceDone = 2;
private static readonly object _stateGate = new(); private static readonly object _stateGate = new();
private static readonly Dictionary<ulong, PthreadMutexState> _mutexStates = new(); private static readonly ConcurrentDictionary<ulong, PthreadMutexState> _mutexStates = new();
private static readonly Dictionary<ulong, PthreadMutexAttrState> _mutexAttrStates = new(); private static readonly Dictionary<ulong, PthreadMutexAttrState> _mutexAttrStates = new();
private static readonly Dictionary<ulong, PthreadCondState> _condStates = new(); private static readonly Dictionary<ulong, PthreadCondState> _condStates = new();
private static readonly Dictionary<ulong, object> _onceGates = new(); private static readonly Dictionary<ulong, object> _onceGates = new();
private static readonly HashSet<ulong> _condAttrStates = new(); private static readonly HashSet<ulong> _condAttrStates = new();
private static readonly bool _tracePthreads =
string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_PTHREADS"), "1", StringComparison.Ordinal);
private static readonly bool _tracePthreadConds =
_tracePthreads ||
string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_PTHREAD_CONDS"), "1", StringComparison.Ordinal);
private static readonly HashSet<ulong>? _tracePthreadMutexFilter = ParseTraceAddressFilter(
Environment.GetEnvironmentVariable("SHARPEMU_LOG_PTHREAD_MUTEX_FILTER"));
private sealed class PthreadMutexState private sealed class PthreadMutexState
{ {
@@ -40,6 +48,12 @@ public static class KernelPthreadCompatExports
public int Protocol { get; set; } public int Protocol { get; set; }
} }
private sealed class PthreadMutexWaiter
{
public required ulong ThreadId { get; init; }
public int Reserved;
}
private sealed class PthreadCondState private sealed class PthreadCondState
{ {
public object SyncRoot { get; } = new(); public object SyncRoot { get; } = new();
@@ -463,19 +477,13 @@ public static class KernelPthreadCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
} }
lock (_stateGate) _mutexStates[mutexAddress] = state;
{ _mutexStates[handle] = state;
_mutexStates[mutexAddress] = state;
_mutexStates[handle] = state;
}
if (!ctx.TryWriteUInt64(mutexAddress, handle)) if (!KernelMemoryCompatExports.TryWriteUInt64Compat(ctx, mutexAddress, handle))
{ {
lock (_stateGate) _mutexStates.TryRemove(mutexAddress, out _);
{ _mutexStates.TryRemove(handle, out _);
_mutexStates.Remove(mutexAddress);
_mutexStates.Remove(handle);
}
state.Semaphore.Dispose(); state.Semaphore.Dispose();
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
@@ -492,15 +500,10 @@ public static class KernelPthreadCompatExports
} }
var resolvedAddress = ResolveMutexHandle(ctx, mutexAddress); var resolvedAddress = ResolveMutexHandle(ctx, mutexAddress);
PthreadMutexState? state; _mutexStates.TryRemove(resolvedAddress, out var state);
lock (_stateGate) if (resolvedAddress != mutexAddress)
{ {
_mutexStates.TryGetValue(resolvedAddress, out state); _mutexStates.TryRemove(mutexAddress, out _);
_mutexStates.Remove(resolvedAddress);
if (resolvedAddress != mutexAddress)
{
_mutexStates.Remove(mutexAddress);
}
} }
if (state is null) if (state is null)
@@ -508,7 +511,7 @@ public static class KernelPthreadCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
} }
_ = ctx.TryWriteUInt64(mutexAddress, 0); _ = KernelMemoryCompatExports.TryWriteUInt64Compat(ctx, mutexAddress, 0);
state.Semaphore.Dispose(); state.Semaphore.Dispose();
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
@@ -538,7 +541,7 @@ public static class KernelPthreadCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
if (state.Type is MutexTypeNormal or MutexTypeAdaptiveNp) if (state.Type is MutexTypeDefault or MutexTypeNormal or MutexTypeAdaptiveNp)
{ {
if (tryOnly) if (tryOnly)
{ {
@@ -563,15 +566,33 @@ public static class KernelPthreadCompatExports
} }
} }
var acquired = true; var acquired = state.Semaphore.Wait(0);
if (tryOnly) if (!acquired)
{ {
acquired = state.Semaphore.Wait(0); // Guest-thread blocking for pthread_mutex_lock is currently disabled.
} // Demon's Souls deadlocks during PS5SyncEvent initialization.
else /* var waiter = new PthreadMutexWaiter { ThreadId = currentThreadId };
{ if (!tryOnly &&
state.Semaphore.Wait(); GuestThreadExecution.IsGuestThread &&
GuestThreadExecution.TryGetCurrentImportCallFrame(out _) &&
GuestThreadExecution.RequestCurrentThreadBlock(
ctx,
"pthread_mutex_lock",
GetMutexWakeKey(resolvedAddress),
() => CompleteBlockedMutexLock(ctx, mutexAddress, resolvedAddress, state, waiter),
() => TryReserveBlockedMutexLock(ctx, mutexAddress, resolvedAddress, state, waiter)))
{
TracePthreadMutex(ctx, "lock-block", mutexAddress, resolvedAddress, state, currentThreadId, (int)OrbisGen2Result.ORBIS_GEN2_OK);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} */
if (!tryOnly)
{
state.Semaphore.Wait();
acquired = true;
}
} }
if (!acquired) if (!acquired)
{ {
TracePthreadMutex(ctx, tryOnly ? "trylock" : "lock", mutexAddress, resolvedAddress, state, currentThreadId, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_BUSY); TracePthreadMutex(ctx, tryOnly ? "trylock" : "lock", mutexAddress, resolvedAddress, state, currentThreadId, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_BUSY);
@@ -630,6 +651,7 @@ public static class KernelPthreadCompatExports
try try
{ {
state.Semaphore.Release(); state.Semaphore.Release();
_ = GuestThreadExecution.Scheduler?.WakeBlockedThreads(GetMutexWakeKey(resolvedAddress), 1);
} }
catch (SemaphoreFullException) catch (SemaphoreFullException)
{ {
@@ -666,7 +688,7 @@ public static class KernelPthreadCompatExports
_mutexAttrStates[handle] = initialState; _mutexAttrStates[handle] = initialState;
} }
if (!ctx.TryWriteUInt64(attrAddress, handle)) if (!KernelMemoryCompatExports.TryWriteUInt64Compat(ctx, attrAddress, handle))
{ {
lock (_stateGate) lock (_stateGate)
{ {
@@ -765,22 +787,16 @@ public static class KernelPthreadCompatExports
return 0; return 0;
} }
lock (_stateGate) if (_mutexStates.ContainsKey(mutexAddress))
{ {
if (_mutexStates.ContainsKey(mutexAddress)) return mutexAddress;
{
return mutexAddress;
}
} }
if (ctx.TryReadUInt64(mutexAddress, out var pointedHandle) && pointedHandle != 0) if (KernelMemoryCompatExports.TryReadUInt64Compat(ctx, mutexAddress, out var pointedHandle) && pointedHandle != 0)
{ {
lock (_stateGate) if (_mutexStates.ContainsKey(pointedHandle))
{ {
if (_mutexStates.ContainsKey(pointedHandle)) return pointedHandle;
{
return pointedHandle;
}
} }
} }
@@ -796,16 +812,13 @@ public static class KernelPthreadCompatExports
return false; return false;
} }
lock (_stateGate) if (_mutexStates.TryGetValue(mutexAddress, out state))
{ {
if (_mutexStates.TryGetValue(mutexAddress, out state)) resolvedAddress = mutexAddress;
{ return true;
resolvedAddress = mutexAddress;
return true;
}
} }
if (!ctx.TryReadUInt64(mutexAddress, out var pointedHandle)) if (!KernelMemoryCompatExports.TryReadUInt64Compat(ctx, mutexAddress, out var pointedHandle))
{ {
return false; return false;
} }
@@ -817,14 +830,11 @@ public static class KernelPthreadCompatExports
if (pointedHandle != 0) if (pointedHandle != 0)
{ {
lock (_stateGate) if (_mutexStates.TryGetValue(pointedHandle, out state))
{ {
if (_mutexStates.TryGetValue(pointedHandle, out state)) _mutexStates.TryAdd(mutexAddress, state);
{ resolvedAddress = pointedHandle;
_mutexStates[mutexAddress] = state; return true;
resolvedAddress = pointedHandle;
return true;
}
} }
resolvedAddress = pointedHandle; resolvedAddress = pointedHandle;
@@ -847,7 +857,7 @@ public static class KernelPthreadCompatExports
return 0; return 0;
} }
if (ctx.TryReadUInt64(attrAddress, out var pointedHandle) && pointedHandle != 0) if (KernelMemoryCompatExports.TryReadUInt64Compat(ctx, attrAddress, out var pointedHandle) && pointedHandle != 0)
{ {
lock (_stateGate) lock (_stateGate)
{ {
@@ -900,7 +910,7 @@ public static class KernelPthreadCompatExports
} }
} }
if (ctx.TryReadUInt64(condAddress, out var pointedHandle) && pointedHandle != 0) if (KernelMemoryCompatExports.TryReadUInt64Compat(ctx, condAddress, out var pointedHandle) && pointedHandle != 0)
{ {
lock (_stateGate) lock (_stateGate)
{ {
@@ -932,7 +942,7 @@ public static class KernelPthreadCompatExports
} }
} }
if (ctx is null || !ctx.TryReadUInt64(condAddress, out var pointedHandle)) if (ctx is null || !KernelMemoryCompatExports.TryReadUInt64Compat(ctx, condAddress, out var pointedHandle))
{ {
return false; return false;
} }
@@ -971,7 +981,7 @@ public static class KernelPthreadCompatExports
_condStates[handle] = createdState; _condStates[handle] = createdState;
} }
if (!ctx.TryWriteUInt64(condAddress, handle)) if (!KernelMemoryCompatExports.TryWriteUInt64Compat(ctx, condAddress, handle))
{ {
lock (_stateGate) lock (_stateGate)
{ {
@@ -1035,7 +1045,7 @@ public static class KernelPthreadCompatExports
_condStates[handle] = state; _condStates[handle] = state;
} }
if (!ctx.TryWriteUInt64(condAddress, handle)) if (!KernelMemoryCompatExports.TryWriteUInt64Compat(ctx, condAddress, handle))
{ {
lock (_stateGate) lock (_stateGate)
{ {
@@ -1066,7 +1076,7 @@ public static class KernelPthreadCompatExports
} }
} }
_ = ctx.TryWriteUInt64(condAddress, 0); _ = KernelMemoryCompatExports.TryWriteUInt64Compat(ctx, condAddress, 0);
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
@@ -1202,6 +1212,63 @@ public static class KernelPthreadCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
private static string GetMutexWakeKey(ulong resolvedMutexAddress) =>
$"pthread_mutex:0x{resolvedMutexAddress:X16}";
private static bool TryReserveBlockedMutexLock(
CpuContext ctx,
ulong mutexAddress,
ulong resolvedAddress,
PthreadMutexState state,
PthreadMutexWaiter waiter)
{
lock (state)
{
if (state.OwnerThreadId != 0 || state.RecursionCount != 0)
{
TracePthreadMutex(ctx, "lock-reserve-busy", mutexAddress, resolvedAddress, state, waiter.ThreadId, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_BUSY);
return false;
}
state.OwnerThreadId = waiter.ThreadId;
state.RecursionCount = 1;
Interlocked.Exchange(ref waiter.Reserved, 1);
}
TracePthreadMutex(ctx, "lock-reserve", mutexAddress, resolvedAddress, state, waiter.ThreadId, (int)OrbisGen2Result.ORBIS_GEN2_OK);
return true;
}
private static int CompleteBlockedMutexLock(
CpuContext ctx,
ulong mutexAddress,
ulong resolvedAddress,
PthreadMutexState state,
PthreadMutexWaiter waiter)
{
var currentThreadId = KernelPthreadState.GetCurrentThreadHandle();
if (Interlocked.Exchange(ref waiter.Reserved, 0) == 1)
{
TracePthreadMutex(ctx, "lock-resume", mutexAddress, resolvedAddress, state, currentThreadId, (int)OrbisGen2Result.ORBIS_GEN2_OK);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
if (!state.Semaphore.Wait(0))
{
TracePthreadMutex(ctx, "lock-resume-busy", mutexAddress, resolvedAddress, state, currentThreadId, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_BUSY);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_BUSY;
}
lock (state)
{
state.OwnerThreadId = currentThreadId;
state.RecursionCount = 1;
}
TracePthreadMutex(ctx, "lock-resume", mutexAddress, resolvedAddress, state, currentThreadId, (int)OrbisGen2Result.ORBIS_GEN2_OK);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
private static TimeSpan GetCondWaitTimeout(uint timeoutUsec) private static TimeSpan GetCondWaitTimeout(uint timeoutUsec)
{ {
if (timeoutUsec == 0) if (timeoutUsec == 0)
@@ -1313,13 +1380,10 @@ public static class KernelPthreadCompatExports
_mutexStates[handle] = createdState; _mutexStates[handle] = createdState;
} }
if (!ctx.TryWriteUInt64(mutexAddress, handle)) if (!KernelMemoryCompatExports.TryWriteUInt64Compat(ctx, mutexAddress, handle))
{ {
lock (_stateGate) _mutexStates.TryRemove(mutexAddress, out _);
{ _mutexStates.TryRemove(handle, out _);
_mutexStates.Remove(mutexAddress);
_mutexStates.Remove(handle);
}
resolvedAddress = 0; resolvedAddress = 0;
state = null; state = null;
@@ -1357,13 +1421,13 @@ public static class KernelPthreadCompatExports
private static void TracePthreadMutex(CpuContext ctx, string operation, ulong mutexAddress, ulong resolvedAddress, PthreadMutexState? state, ulong currentThreadId, int result) private static void TracePthreadMutex(CpuContext ctx, string operation, ulong mutexAddress, ulong resolvedAddress, PthreadMutexState? state, ulong currentThreadId, int result)
{ {
if (!ShouldTracePthread()) if (!ShouldTracePthreadMutex(mutexAddress, resolvedAddress))
{ {
return; return;
} }
_ = ctx.TryReadUInt64(mutexAddress, out var guestWord0); _ = KernelMemoryCompatExports.TryReadUInt64Compat(ctx, mutexAddress, out var guestWord0);
_ = ctx.TryReadUInt64(mutexAddress + 8, out var guestWord1); _ = KernelMemoryCompatExports.TryReadUInt64Compat(ctx, mutexAddress + 8, out var guestWord1);
Console.Error.WriteLine( Console.Error.WriteLine(
$"[LOADER][TRACE] pthread_{operation}: mutex=0x{mutexAddress:X16} resolved=0x{resolvedAddress:X16} " + $"[LOADER][TRACE] pthread_{operation}: mutex=0x{mutexAddress:X16} resolved=0x{resolvedAddress:X16} " +
$"guest[0]=0x{guestWord0:X16} guest[8]=0x{guestWord1:X16} " + $"guest[0]=0x{guestWord0:X16} guest[8]=0x{guestWord1:X16} " +
@@ -1373,7 +1437,7 @@ public static class KernelPthreadCompatExports
private static void TracePthreadCond(string operation, ulong condAddress, ulong mutexAddress, PthreadCondState? state, bool timed, int result) private static void TracePthreadCond(string operation, ulong condAddress, ulong mutexAddress, PthreadCondState? state, bool timed, int result)
{ {
if (!ShouldTracePthread()) if (!_tracePthreadConds)
{ {
return; return;
} }
@@ -1385,6 +1449,45 @@ public static class KernelPthreadCompatExports
private static bool ShouldTracePthread() private static bool ShouldTracePthread()
{ {
return string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_PTHREADS"), "1", StringComparison.Ordinal); return _tracePthreads;
}
private static bool ShouldTracePthreadMutex(ulong mutexAddress, ulong resolvedAddress)
{
if (_tracePthreadMutexFilter is null || _tracePthreadMutexFilter.Count == 0)
{
return _tracePthreads;
}
return _tracePthreadMutexFilter.Contains(mutexAddress) ||
_tracePthreadMutexFilter.Contains(resolvedAddress);
}
private static HashSet<ulong>? ParseTraceAddressFilter(string? filter)
{
if (string.IsNullOrWhiteSpace(filter))
{
return null;
}
var addresses = new HashSet<ulong>();
foreach (var token in filter.Split(new[] { ',', ';', ' ' }, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
{
var normalized = token.StartsWith("0x", StringComparison.OrdinalIgnoreCase)
? token[2..]
: token;
normalized = normalized.TrimStart('0');
if (ulong.TryParse(
normalized.Length == 0 ? "0" : normalized,
System.Globalization.NumberStyles.HexNumber,
System.Globalization.CultureInfo.InvariantCulture,
out var address))
{
addresses.Add(address);
}
}
return addresses.Count == 0 ? null : addresses;
} }
} }
@@ -3,6 +3,7 @@
using SharpEmu.HLE; using SharpEmu.HLE;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Collections.Concurrent;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
@@ -26,12 +27,12 @@ public static class KernelPthreadExtendedCompatExports
private static readonly Dictionary<ulong, ThreadState> _threadStates = new(); private static readonly Dictionary<ulong, ThreadState> _threadStates = new();
private static readonly Dictionary<ulong, PthreadAttrState> _attrStates = new(); private static readonly Dictionary<ulong, PthreadAttrState> _attrStates = new();
private static readonly Dictionary<ulong, PthreadRwlockState> _rwlockStates = new(); private static readonly Dictionary<ulong, PthreadRwlockState> _rwlockStates = new();
private static readonly Dictionary<int, TlsKeyState> _tlsKeys = new(); private static readonly ConcurrentDictionary<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 long _nextSyntheticPthreadAttrHandleId = 1;
private static readonly Dictionary<ulong, Dictionary<int, ulong>> _threadLocalSpecific = new(); private static readonly ConcurrentDictionary<ulong, ConcurrentDictionary<int, ulong>> _threadLocalSpecific = new();
private sealed class ThreadState private sealed class ThreadState
{ {
@@ -793,7 +794,7 @@ public static class KernelPthreadExtendedCompatExports
_rwlockStates[syntheticHandle] = rwlock; _rwlockStates[syntheticHandle] = rwlock;
} }
_ = ctx.TryWriteUInt64(rwlockAddress, syntheticHandle); _ = KernelMemoryCompatExports.TryWriteUInt64Compat(ctx, rwlockAddress, syntheticHandle);
ctx[CpuRegister.Rax] = 0; ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
@@ -847,7 +848,7 @@ public static class KernelPthreadExtendedCompatExports
} }
} }
_ = ctx.TryWriteUInt64(rwlockAddress, 0); _ = KernelMemoryCompatExports.TryWriteUInt64Compat(ctx, rwlockAddress, 0);
ctx[CpuRegister.Rax] = 0; ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
@@ -959,15 +960,13 @@ public static class KernelPthreadExtendedCompatExports
} }
int key; int key;
lock (_stateGate) while (true)
{ {
while (_tlsKeys.ContainsKey(_nextTlsKey)) key = Interlocked.Increment(ref _nextTlsKey) - 1;
if (_tlsKeys.TryAdd(key, new TlsKeyState(destructor)))
{ {
_nextTlsKey++; break;
} }
key = _nextTlsKey++;
_tlsKeys[key] = new TlsKeyState(destructor);
} }
if (!TryWriteInt32(ctx, outKeyAddress, key)) if (!TryWriteInt32(ctx, outKeyAddress, key))
@@ -994,17 +993,14 @@ public static class KernelPthreadExtendedCompatExports
public static int PosixPthreadKeyDelete(CpuContext ctx) public static int PosixPthreadKeyDelete(CpuContext ctx)
{ {
var key = unchecked((int)ctx[CpuRegister.Rdi]); var key = unchecked((int)ctx[CpuRegister.Rdi]);
lock (_stateGate) if (!_tlsKeys.TryRemove(key, out _))
{ {
if (!_tlsKeys.Remove(key)) return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
{ }
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
foreach (var entry in _threadLocalSpecific) foreach (var values in _threadLocalSpecific.Values)
{ {
entry.Value.Remove(key); values.TryRemove(key, out _);
}
} }
ctx[CpuRegister.Rax] = 0; ctx[CpuRegister.Rax] = 0;
@@ -1028,22 +1024,15 @@ public static class KernelPthreadExtendedCompatExports
var key = unchecked((int)ctx[CpuRegister.Rdi]); var key = unchecked((int)ctx[CpuRegister.Rdi]);
var value = ctx[CpuRegister.Rsi]; var value = ctx[CpuRegister.Rsi];
var currentThreadHandle = KernelPthreadState.GetCurrentThreadHandle(); var currentThreadHandle = KernelPthreadState.GetCurrentThreadHandle();
lock (_stateGate) if (!_tlsKeys.ContainsKey(key))
{ {
if (!_tlsKeys.TryGetValue(key, out _)) return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
if (!_threadLocalSpecific.TryGetValue(currentThreadHandle, out var values))
{
values = new Dictionary<int, ulong>();
_threadLocalSpecific[currentThreadHandle] = values;
}
values[key] = value;
} }
var values = _threadLocalSpecific.GetOrAdd(
currentThreadHandle,
static _ => new ConcurrentDictionary<int, ulong>());
values[key] = value;
ctx[CpuRegister.Rax] = 0; ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
@@ -1065,19 +1054,16 @@ public static class KernelPthreadExtendedCompatExports
var key = unchecked((int)ctx[CpuRegister.Rdi]); var key = unchecked((int)ctx[CpuRegister.Rdi]);
var currentThreadHandle = KernelPthreadState.GetCurrentThreadHandle(); var currentThreadHandle = KernelPthreadState.GetCurrentThreadHandle();
ulong value = 0; ulong value = 0;
lock (_stateGate) if (!_tlsKeys.ContainsKey(key))
{ {
if (!_tlsKeys.TryGetValue(key, out _)) ctx[CpuRegister.Rax] = 0;
{ return (int)OrbisGen2Result.ORBIS_GEN2_OK;
ctx[CpuRegister.Rax] = 0; }
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
if (_threadLocalSpecific.TryGetValue(currentThreadHandle, out var values) && if (_threadLocalSpecific.TryGetValue(currentThreadHandle, out var values) &&
values.TryGetValue(key, out var storedValue)) values.TryGetValue(key, out var storedValue))
{ {
value = storedValue; value = storedValue;
}
} }
ctx[CpuRegister.Rax] = value; ctx[CpuRegister.Rax] = value;
@@ -1163,7 +1149,7 @@ public static class KernelPthreadExtendedCompatExports
} }
} }
if (ctx.TryReadUInt64(rwlockAddress, out var pointedHandle) && pointedHandle != 0) if (KernelMemoryCompatExports.TryReadUInt64Compat(ctx, rwlockAddress, out var pointedHandle) && pointedHandle != 0)
{ {
lock (_stateGate) lock (_stateGate)
{ {
@@ -1195,7 +1181,7 @@ public static class KernelPthreadExtendedCompatExports
} }
} }
if (!ctx.TryReadUInt64(rwlockAddress, out var pointedHandle)) if (!KernelMemoryCompatExports.TryReadUInt64Compat(ctx, rwlockAddress, out var pointedHandle))
{ {
return false; return false;
} }
@@ -1230,7 +1216,7 @@ public static class KernelPthreadExtendedCompatExports
_rwlockStates[syntheticHandle] = createdRwlock; _rwlockStates[syntheticHandle] = createdRwlock;
} }
_ = ctx.TryWriteUInt64(rwlockAddress, syntheticHandle); _ = KernelMemoryCompatExports.TryWriteUInt64Compat(ctx, rwlockAddress, syntheticHandle);
resolvedAddress = syntheticHandle; resolvedAddress = syntheticHandle;
rwlock = createdRwlock; rwlock = createdRwlock;
return true; return true;
+4 -10
View File
@@ -1,6 +1,7 @@
// Copyright (C) 2026 SharpEmu Emulator Project // Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
using System.Collections.Concurrent;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
using SharpEmu.HLE; using SharpEmu.HLE;
@@ -11,8 +12,7 @@ internal static class KernelPthreadState
{ {
private const int ThreadObjectSize = 0x1000; private const int ThreadObjectSize = 0x1000;
private static readonly object Gate = new(); private static readonly ConcurrentDictionary<ulong, ThreadIdentity> Threads = new();
private static readonly Dictionary<ulong, ThreadIdentity> Threads = new();
private static readonly byte[] ZeroThreadObject = new byte[ThreadObjectSize]; private static readonly byte[] ZeroThreadObject = new byte[ThreadObjectSize];
private static long _nextUniqueThreadId = 1; private static long _nextUniqueThreadId = 1;
@@ -56,10 +56,7 @@ internal static class KernelPthreadState
internal static bool TryGetThreadIdentity(ulong threadHandle, out ThreadIdentity identity) internal static bool TryGetThreadIdentity(ulong threadHandle, out ThreadIdentity identity)
{ {
lock (Gate) return Threads.TryGetValue(threadHandle, out identity);
{
return Threads.TryGetValue(threadHandle, out identity);
}
} }
private static void EnsureCurrentThreadRegistered() private static void EnsureCurrentThreadRegistered()
@@ -81,10 +78,7 @@ internal static class KernelPthreadState
Marshal.Copy(ZeroThreadObject, 0, pointer, ThreadObjectSize); Marshal.Copy(ZeroThreadObject, 0, pointer, ThreadObjectSize);
var handle = unchecked((ulong)pointer.ToInt64()); var handle = unchecked((ulong)pointer.ToInt64());
lock (Gate) Threads[handle] = new ThreadIdentity(uniqueId, string.IsNullOrWhiteSpace(name) ? $"Thread-{uniqueId:X}" : name);
{
Threads[handle] = new ThreadIdentity(uniqueId, string.IsNullOrWhiteSpace(name) ? $"Thread-{uniqueId:X}" : name);
}
return handle; return handle;
} }
@@ -57,6 +57,13 @@ public static class KernelRuntimeCompatExports
private static readonly (ulong Base, ulong Size)[] _prtApertures = new (ulong Base, ulong Size)[3]; private static readonly (ulong Base, ulong Size)[] _prtApertures = new (ulong Base, ulong Size)[3];
private static int _stackChkFailCount; private static int _stackChkFailCount;
private static long _usleepTraceCount; private static long _usleepTraceCount;
private static readonly bool _traceUsleep =
string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_USLEEP"), "1", StringComparison.Ordinal);
private static readonly bool _traceGuestThreads =
string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_GUEST_THREADS"), "1", StringComparison.Ordinal);
[ThreadStatic]
private static int _shortUsleepCount;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate ulong RdtscDelegate(); private delegate ulong RdtscDelegate();
@@ -80,10 +87,20 @@ public static class KernelRuntimeCompatExports
if (micros < 1000) if (micros < 1000)
{ {
Thread.Yield(); // Guest worker pools use usleep(1) as a polling backoff. Periodically
// relinquish a full host time slice so spin workers cannot starve producers.
if ((++_shortUsleepCount & 31) == 0)
{
Thread.Sleep(1);
}
else
{
Thread.Yield();
}
} }
else else
{ {
_shortUsleepCount = 0;
var sleepMilliseconds = (int)Math.Min((micros + 999UL) / 1000UL, int.MaxValue); var sleepMilliseconds = (int)Math.Min((micros + 999UL) / 1000UL, int.MaxValue);
Thread.Sleep(sleepMilliseconds); Thread.Sleep(sleepMilliseconds);
} }
@@ -94,7 +111,7 @@ public static class KernelRuntimeCompatExports
private static void TraceUsleepSpin(CpuContext ctx, ulong micros) private static void TraceUsleepSpin(CpuContext ctx, ulong micros)
{ {
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_USLEEP"), "1", StringComparison.Ordinal)) if (!_traceUsleep)
{ {
return; return;
} }
@@ -142,6 +159,19 @@ public static class KernelRuntimeCompatExports
Console.Error.WriteLine( Console.Error.WriteLine(
$"[LOADER][TRACE] usleep#{count}: usec={micros} ret=0x{returnRip:X16} caller={callerReturnText} thread=0x{thread:X16} fiber=0x{fiber:X16} rbx=0x{rbx:X16} lock@+F78=0x{lockAddress:X16}:{lockText} r12=0x{r12:X16} scheduler@+8={schedulerText} r13=0x{r13:X16}:{waitValueText} r14=0x{ctx[CpuRegister.R14]:X16} r15=0x{ctx[CpuRegister.R15]:X16}"); $"[LOADER][TRACE] usleep#{count}: usec={micros} ret=0x{returnRip:X16} caller={callerReturnText} thread=0x{thread:X16} fiber=0x{fiber:X16} rbx=0x{rbx:X16} lock@+F78=0x{lockAddress:X16}:{lockText} r12=0x{r12:X16} scheduler@+8={schedulerText} r13=0x{r13:X16}:{waitValueText} r14=0x{ctx[CpuRegister.R14]:X16} r15=0x{ctx[CpuRegister.R15]:X16}");
if (count % 100000 == 0 &&
_traceGuestThreads &&
GuestThreadExecution.Scheduler is { } scheduler)
{
foreach (var snapshot in scheduler.SnapshotThreads())
{
Console.Error.WriteLine(
$"[LOADER][TRACE] guest_thread.snapshot handle=0x{snapshot.ThreadHandle:X16} name='{snapshot.Name}' " +
$"state={snapshot.State} imports={snapshot.ImportCount} nid={snapshot.LastImportNid ?? "none"} " +
$"ret=0x{snapshot.LastReturnRip:X16} block={snapshot.BlockReason ?? "none"}");
}
}
} }
[SysAbiExport( [SysAbiExport(
@@ -581,19 +611,16 @@ public static class KernelRuntimeCompatExports
public static int ErrorAddress(CpuContext ctx) public static int ErrorAddress(CpuContext ctx)
{ {
var address = GetTlsScratchAddress(ctx, TlsErrnoOffset); var address = GetTlsScratchAddress(ctx, TlsErrnoOffset);
if (address != 0)
{
Span<byte> zero = stackalloc byte[sizeof(int)];
if (!ctx.Memory.TryWrite(address, zero))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
}
ctx[CpuRegister.Rax] = address; ctx[CpuRegister.Rax] = address;
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
internal static bool TrySetErrno(CpuContext ctx, int value)
{
var address = GetTlsScratchAddress(ctx, TlsErrnoOffset);
return address != 0 && TryWriteInt32(ctx, address, value);
}
[SysAbiExport( [SysAbiExport(
Nid = "bnZxYgAFeA0", Nid = "bnZxYgAFeA0",
ExportName = "sceKernelGetSanitizerNewReplaceExternal", ExportName = "sceKernelGetSanitizerNewReplaceExternal",
@@ -1505,30 +1532,40 @@ public static class KernelRuntimeCompatExports
ulong.TryParse(overrideHzText, out var overrideHz) && ulong.TryParse(overrideHzText, out var overrideHz) &&
overrideHz >= minSane) overrideHz >= minSane)
{ {
TraceKernelTscFrequency("env", overrideHz);
return overrideHz; return overrideHz;
} }
if (TryResolveCpuidTscFrequency(out ulong cpuidHz) && cpuidHz >= minSane)
{
return cpuidHz;
}
if (TryCalibrateHostTscFrequency(out ulong calibratedHz) && calibratedHz >= minSane) if (TryCalibrateHostTscFrequency(out ulong calibratedHz) && calibratedHz >= minSane)
{ {
TraceKernelTscFrequency("calibrated-rdtsc", calibratedHz);
return calibratedHz; return calibratedHz;
} }
if (TryResolveCpuidTscFrequency(out ulong cpuidHz) && cpuidHz >= minSane)
{
TraceKernelTscFrequency("cpuid", cpuidHz);
return cpuidHz;
}
var hostQpc = Stopwatch.Frequency > 0 var hostQpc = Stopwatch.Frequency > 0
? unchecked((ulong)Stopwatch.Frequency) ? unchecked((ulong)Stopwatch.Frequency)
: DefaultKernelTscFrequency; : DefaultKernelTscFrequency;
if (hostQpc >= minSane) if (hostQpc >= minSane)
{ {
TraceKernelTscFrequency("qpc", hostQpc);
return hostQpc; return hostQpc;
} }
TraceKernelTscFrequency("default", DefaultKernelTscFrequency);
return DefaultKernelTscFrequency; return DefaultKernelTscFrequency;
} }
private static void TraceKernelTscFrequency(string source, ulong frequencyHz)
{
Console.Error.WriteLine($"[LOADER][INFO] Kernel TSC frequency: {frequencyHz} Hz ({source})");
}
private static bool TryResolveCpuidTscFrequency(out ulong frequencyHz) private static bool TryResolveCpuidTscFrequency(out ulong frequencyHz)
{ {
frequencyHz = 0; frequencyHz = 0;
@@ -1788,7 +1825,7 @@ public static class KernelRuntimeCompatExports
} }
var invokeArgs = allocateAtHasAllowAlternativeArg var invokeArgs = allocateAtHasAllowAlternativeArg
? new object[] { desiredAddress, length, false, false } ? new object[] { desiredAddress, length, false, allowSearch }
: new object[] { desiredAddress, length, false }; : new object[] { desiredAddress, length, false };
var result = allocateAt.Invoke(memoryObject, invokeArgs); var result = allocateAt.Invoke(memoryObject, invokeArgs);
if (result is not ulong allocated || allocated == 0) if (result is not ulong allocated || allocated == 0)
@@ -120,7 +120,7 @@ public static class KernelSemaphoreCompatExports
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_TIMED_OUT); return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_TIMED_OUT);
} }
if (!GuestThreadExecution.RequestCurrentThreadBlock("sceKernelWaitSema")) if (!GuestThreadExecution.RequestCurrentThreadBlock(ctx, "sceKernelWaitSema"))
{ {
TraceSemaphore($"wait-would-block handle=0x{handle:X8} name='{semaphore.Name}' need={needCount} count={semaphore.Count}"); TraceSemaphore($"wait-would-block handle=0x{handle:X8} name='{semaphore.Name}' need={needCount} count={semaphore.Count}");
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_TRY_AGAIN); return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_TRY_AGAIN);