mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-19 17:36:15 +08:00
[kernel] Guest-thread blocking for pthread_mutex_lock is currently disabled
This commit is contained in:
@@ -11,8 +11,13 @@ namespace SharpEmu.Libs.Kernel;
|
||||
|
||||
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 _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(
|
||||
Nid = "ASoW5WE-UPo",
|
||||
@@ -37,7 +42,7 @@ public static class KernelAprCompatExports
|
||||
submissionId = unchecked((uint)Interlocked.Increment(ref _nextSubmissionId));
|
||||
}
|
||||
|
||||
_submittedCommandBuffers[submissionId] = commandBuffer;
|
||||
_submittedCommandBuffers[submissionId] = new AprSubmission(commandBuffer, priority, resultAddress);
|
||||
|
||||
var completionResult = AmprExports.CompleteCommandBuffer(ctx, commandBuffer);
|
||||
if (completionResult != (int)OrbisGen2Result.ORBIS_GEN2_OK)
|
||||
@@ -67,20 +72,23 @@ public static class KernelAprCompatExports
|
||||
public static int KernelAprWaitCommandBuffer(CpuContext ctx)
|
||||
{
|
||||
var submissionId = unchecked((uint)ctx[CpuRegister.Rdi]);
|
||||
var priority = ctx[CpuRegister.Rsi];
|
||||
var resultAddress = ctx[CpuRegister.Rdx];
|
||||
var waitArg1 = ctx[CpuRegister.Rsi];
|
||||
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;
|
||||
}
|
||||
|
||||
var resultAddress = ResolveWaitResultAddress(waitArg1, waitArg2, submission.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;
|
||||
}
|
||||
|
||||
TraceApr(ctx, "wait", submissionId, commandBuffer, priority, resultAddress);
|
||||
TraceApr(ctx, "wait", submissionId, submission.CommandBuffer, waitArg1, resultAddress);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
@@ -98,7 +106,7 @@ public static class KernelAprCompatExports
|
||||
}
|
||||
|
||||
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);
|
||||
if (completionResult != (int)OrbisGen2Result.ORBIS_GEN2_OK)
|
||||
@@ -125,7 +133,7 @@ public static class KernelAprCompatExports
|
||||
}
|
||||
|
||||
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);
|
||||
if (completionResult != (int)OrbisGen2Result.ORBIS_GEN2_OK)
|
||||
@@ -149,6 +157,27 @@ public static class KernelAprCompatExports
|
||||
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)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[sizeof(uint)];
|
||||
@@ -164,7 +193,7 @@ public static class KernelAprCompatExports
|
||||
ulong priority,
|
||||
ulong aux)
|
||||
{
|
||||
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_AMPR"), "1", StringComparison.Ordinal))
|
||||
if (!_traceApr)
|
||||
{
|
||||
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}");
|
||||
}
|
||||
}
|
||||
|
||||
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}");
|
||||
}
|
||||
|
||||
_ = GuestThreadExecution.Scheduler?.WakeBlockedThreads(GetEventFlagWakeKey(handle));
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
|
||||
}
|
||||
|
||||
@@ -226,7 +227,7 @@ public static class KernelEventFlagCompatExports
|
||||
Monitor.Enter(state.Gate);
|
||||
try
|
||||
{
|
||||
if (TryCompleteSatisfiedWait(ctx, state, pattern, waitMode, out var immediateWaitResult))
|
||||
if (TryCompleteSatisfiedWait(ctx, state, pattern, waitMode, resultAddress, out var immediateWaitResult))
|
||||
{
|
||||
return SetReturn(ctx, immediateWaitResult);
|
||||
}
|
||||
@@ -242,7 +243,28 @@ public static class KernelEventFlagCompatExports
|
||||
var currentGuestThread = GuestThreadExecution.CurrentGuestThreadHandle;
|
||||
var currentFiber = FiberExports.GetCurrentFiberAddressForDiagnostics(ctx);
|
||||
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-object handle=0x{handle:X16} name='{state.Name}' {FormatGuestWaitObject(ctx)}");
|
||||
if (!requestedBlock)
|
||||
@@ -270,7 +292,7 @@ public static class KernelEventFlagCompatExports
|
||||
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);
|
||||
releaseWaiter = false;
|
||||
@@ -369,19 +391,21 @@ public static class KernelEventFlagCompatExports
|
||||
}
|
||||
|
||||
private static bool TryCompleteSatisfiedWait(
|
||||
CpuContext ctx,
|
||||
EventFlagState state,
|
||||
ulong pattern,
|
||||
uint waitMode,
|
||||
out OrbisGen2Result result)
|
||||
CpuContext ctx,
|
||||
EventFlagState state,
|
||||
ulong pattern,
|
||||
uint waitMode,
|
||||
ulong resultAddress,
|
||||
out OrbisGen2Result result)
|
||||
{
|
||||
result = OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
|
||||
if (!IsSatisfied(state.Bits, pattern, waitMode))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryWriteResultPattern(ctx, ctx[CpuRegister.Rcx], state.Bits))
|
||||
if (!TryWriteResultPattern(ctx, resultAddress, state.Bits))
|
||||
{
|
||||
result = OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
return true;
|
||||
@@ -391,6 +415,41 @@ public static class KernelEventFlagCompatExports
|
||||
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) =>
|
||||
address == 0 || ctx.TryWriteUInt64(address, bits);
|
||||
|
||||
|
||||
@@ -10,10 +10,14 @@ namespace SharpEmu.Libs.Kernel;
|
||||
public static class KernelEventQueueCompatExports
|
||||
{
|
||||
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 HashSet<ulong> _eventQueues = 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;
|
||||
|
||||
public readonly record struct KernelQueuedEvent(
|
||||
@@ -24,6 +28,11 @@ public static class KernelEventQueueCompatExports
|
||||
ulong Data,
|
||||
ulong UserData);
|
||||
|
||||
private readonly record struct KernelEventRegistration(
|
||||
ulong Ident,
|
||||
short Filter,
|
||||
ulong UserData);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "D0OdFMjp46I",
|
||||
ExportName = "sceKernelCreateEqueue",
|
||||
@@ -42,6 +51,7 @@ public static class KernelEventQueueCompatExports
|
||||
{
|
||||
_eventQueues.Add(handle);
|
||||
_pendingEvents[handle] = new LinkedList<KernelQueuedEvent>();
|
||||
_registeredEvents[handle] = new Dictionary<(ulong Ident, short Filter), KernelEventRegistration>();
|
||||
}
|
||||
|
||||
if (!ctx.TryWriteUInt64(outAddress, handle))
|
||||
@@ -65,6 +75,7 @@ public static class KernelEventQueueCompatExports
|
||||
{
|
||||
_eventQueues.Remove(handle);
|
||||
_pendingEvents.Remove(handle);
|
||||
_registeredEvents.Remove(handle);
|
||||
}
|
||||
|
||||
TraceEventQueue(ctx, "delete", handle);
|
||||
@@ -122,8 +133,16 @@ public static class KernelEventQueueCompatExports
|
||||
LibraryName = "libKernel")]
|
||||
public static int KernelAddAmprEvent(CpuContext ctx)
|
||||
{
|
||||
TraceEventQueue(ctx, "add_ampr", ctx[CpuRegister.Rdi]);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
var handle = ctx[CpuRegister.Rdi];
|
||||
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(
|
||||
@@ -133,8 +152,16 @@ public static class KernelEventQueueCompatExports
|
||||
LibraryName = "libKernel")]
|
||||
public static int KernelAddAmprSystemEvent(CpuContext ctx)
|
||||
{
|
||||
TraceEventQueue(ctx, "add_ampr_system", ctx[CpuRegister.Rdi]);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
var handle = ctx[CpuRegister.Rdi];
|
||||
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(
|
||||
@@ -144,8 +171,15 @@ public static class KernelEventQueueCompatExports
|
||||
LibraryName = "libKernel")]
|
||||
public static int KernelDeleteAmprEvent(CpuContext ctx)
|
||||
{
|
||||
TraceEventQueue(ctx, "delete_ampr", ctx[CpuRegister.Rdi]);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
var handle = ctx[CpuRegister.Rdi];
|
||||
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(
|
||||
@@ -155,8 +189,15 @@ public static class KernelEventQueueCompatExports
|
||||
LibraryName = "libKernel")]
|
||||
public static int KernelDeleteAmprSystemEvent(CpuContext ctx)
|
||||
{
|
||||
TraceEventQueue(ctx, "delete_ampr_system", ctx[CpuRegister.Rdi]);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
var handle = ctx[CpuRegister.Rdi];
|
||||
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(
|
||||
@@ -171,6 +212,57 @@ public static class KernelEventQueueCompatExports
|
||||
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(
|
||||
Nid = "fzyMKs9kim0",
|
||||
ExportName = "sceKernelWaitEqueue",
|
||||
@@ -196,7 +288,13 @@ public static class KernelEventQueueCompatExports
|
||||
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);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
@@ -216,6 +314,7 @@ public static class KernelEventQueueCompatExports
|
||||
|
||||
public static bool EnqueueEvent(ulong handle, KernelQueuedEvent queuedEvent)
|
||||
{
|
||||
var queued = false;
|
||||
lock (_eventQueueGate)
|
||||
{
|
||||
if (!_eventQueues.Contains(handle))
|
||||
@@ -230,10 +329,100 @@ public static class KernelEventQueueCompatExports
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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(
|
||||
ulong handle,
|
||||
ulong ident,
|
||||
@@ -241,6 +430,7 @@ public static class KernelEventQueueCompatExports
|
||||
ulong eventHint,
|
||||
ulong userData)
|
||||
{
|
||||
var triggered = false;
|
||||
lock (_eventQueueGate)
|
||||
{
|
||||
if (!_eventQueues.Contains(handle))
|
||||
@@ -254,17 +444,8 @@ public static class KernelEventQueueCompatExports
|
||||
_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 pendingNode = FindPendingEvent(events, ident, filter);
|
||||
if (pendingNode is not null)
|
||||
{
|
||||
count = Math.Min(((pendingNode.Value.Data >> 12) & 0xFUL) + 1, 0xFUL);
|
||||
@@ -289,8 +470,80 @@ public static class KernelEventQueueCompatExports
|
||||
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)
|
||||
|
||||
@@ -252,6 +252,32 @@ public static class KernelExports
|
||||
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(
|
||||
Nid = "onNY9Byn-W8",
|
||||
ExportName = "scePthreadJoin",
|
||||
|
||||
@@ -65,6 +65,7 @@ public static class KernelMemoryCompatExports
|
||||
private const uint HostPageExecuteWriteCopy = 0x80;
|
||||
private const uint HostPageGuard = 0x100;
|
||||
private const int Enomem = 12;
|
||||
private const int Efault = 14;
|
||||
private const int Einval = 22;
|
||||
private const int Erange = 34;
|
||||
private const int Struncate = 80;
|
||||
@@ -96,10 +97,14 @@ public static class KernelMemoryCompatExports
|
||||
private static readonly object _libcAllocGate = new();
|
||||
private static readonly object _memoryGate = 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, LibcHeapAllocation> _libcAllocations = new();
|
||||
private static readonly Dictionary<ulong, MappedRegion> _mappedRegions = 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 ulong _nextPhysicalAddress;
|
||||
private static ulong _nextVirtualAddress;
|
||||
@@ -114,6 +119,8 @@ public static class KernelMemoryCompatExports
|
||||
private static int _hostMemoryWriteFallbackCount;
|
||||
private static int _hostMemoryReadFallbackCount;
|
||||
private static int _nullWcscpyRecoveryCount;
|
||||
private static string? _cachedApp0Root;
|
||||
private static string? _cachedDownload0Root;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct MemoryBasicInformation
|
||||
@@ -1236,6 +1243,12 @@ public static class KernelMemoryCompatExports
|
||||
var mode = ResolveOpenMode(flags, access);
|
||||
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;
|
||||
if (wantsDirectory || Directory.Exists(hostPath))
|
||||
{
|
||||
@@ -1280,6 +1293,11 @@ public static class KernelMemoryCompatExports
|
||||
_openFiles[fd] = stream;
|
||||
}
|
||||
|
||||
if (IsMutatingOpen(flags))
|
||||
{
|
||||
InvalidateNegativeStatCacheForPathAndAncestors(guestPath);
|
||||
}
|
||||
|
||||
LogOpenTrace($"_open file path='{guestPath}' host='{hostPath}' flags=0x{flags:X8} fd={fd}");
|
||||
ctx[CpuRegister.Rax] = unchecked((ulong)fd);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
@@ -1334,11 +1352,30 @@ public static class KernelMemoryCompatExports
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
@@ -1356,13 +1393,22 @@ public static class KernelMemoryCompatExports
|
||||
var sizesAddress = ctx[CpuRegister.Rcx];
|
||||
if (pathListAddress == 0 || count == 0 || sizesAddress == 0 || count > 1024)
|
||||
{
|
||||
KernelRuntimeCompatExports.TrySetErrno(ctx, Einval);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
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))
|
||||
{
|
||||
KernelRuntimeCompatExports.TrySetErrno(ctx, Efault);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
@@ -1370,6 +1416,7 @@ public static class KernelMemoryCompatExports
|
||||
if (!TryGetAprFileSize(hostPath, out var fileSize))
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1379,11 +1426,13 @@ public static class KernelMemoryCompatExports
|
||||
if (idsAddress != 0 &&
|
||||
!TryWriteUInt32Compat(ctx, idsAddress + (i * sizeof(uint)), fileId))
|
||||
{
|
||||
KernelRuntimeCompatExports.TrySetErrno(ctx, Efault);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
if (!TryWriteUInt64Compat(ctx, sizesAddress + (i * sizeof(ulong)), fileSize))
|
||||
{
|
||||
KernelRuntimeCompatExports.TrySetErrno(ctx, Efault);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
}
|
||||
@@ -1415,6 +1464,170 @@ public static class KernelMemoryCompatExports
|
||||
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)
|
||||
{
|
||||
if (fd is 0 or 1 or 2)
|
||||
@@ -3798,57 +4011,72 @@ public static class KernelMemoryCompatExports
|
||||
return guestPath;
|
||||
}
|
||||
|
||||
var devlogAppRoot = ResolveDevlogAppRoot();
|
||||
if (guestPath.StartsWith("/devlog/app/", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var relative = NormalizeMountRelativePath(guestPath["/devlog/app/".Length..]);
|
||||
return Path.Combine(devlogAppRoot, relative);
|
||||
return Path.Combine(ResolveDevlogAppRoot(), relative);
|
||||
}
|
||||
|
||||
if (guestPath.StartsWith("devlog/app/", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
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) ||
|
||||
string.Equals(guestPath, "devlog/app", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return devlogAppRoot;
|
||||
return ResolveDevlogAppRoot();
|
||||
}
|
||||
|
||||
var temp0Root = ResolveTemp0Root();
|
||||
if (guestPath.StartsWith("/temp0/", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var relative = NormalizeMountRelativePath(guestPath["/temp0/".Length..]);
|
||||
return Path.Combine(temp0Root, relative);
|
||||
return Path.Combine(ResolveTemp0Root(), relative);
|
||||
}
|
||||
|
||||
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))
|
||||
{
|
||||
var relative = NormalizeMountRelativePath(guestPath["/hostapp/".Length..]);
|
||||
return Path.Combine(hostappRoot, relative);
|
||||
return Path.Combine(ResolveHostappRoot(), relative);
|
||||
}
|
||||
|
||||
if (guestPath.StartsWith("hostapp/", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var relative = NormalizeMountRelativePath(guestPath["hostapp/".Length..]);
|
||||
return Path.Combine(hostappRoot, relative);
|
||||
return Path.Combine(ResolveHostappRoot(), relative);
|
||||
}
|
||||
|
||||
if (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.Equals(guestPath, "/app0", StringComparison.OrdinalIgnoreCase) ||
|
||||
@@ -3881,6 +4109,24 @@ public static class KernelMemoryCompatExports
|
||||
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)
|
||||
{
|
||||
return relativePath
|
||||
@@ -3931,6 +4177,32 @@ public static class KernelMemoryCompatExports
|
||||
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()
|
||||
{
|
||||
const string hostappVariableName = "SHARPEMU_HOSTAPP_DIR";
|
||||
@@ -3950,6 +4222,22 @@ public static class KernelMemoryCompatExports
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
return TryReadBytesUntilNull(ctx, address, maxLength, 1_048_576, out bytes);
|
||||
@@ -4350,7 +4649,7 @@ public static class KernelMemoryCompatExports
|
||||
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)];
|
||||
if (!TryReadCompat(ctx, address, bytes))
|
||||
@@ -4397,7 +4696,7 @@ public static class KernelMemoryCompatExports
|
||||
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)];
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(bytes, value);
|
||||
@@ -5345,19 +5644,20 @@ public static class KernelMemoryCompatExports
|
||||
size = 0;
|
||||
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;
|
||||
}
|
||||
|
||||
if (!File.Exists(hostPath))
|
||||
if (!new DirectoryInfo(hostPath).Exists)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var length = new FileInfo(hostPath).Length;
|
||||
size = length < 0 ? 0UL : unchecked((ulong)length);
|
||||
size = 65536;
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
@@ -5542,6 +5842,120 @@ public static class KernelMemoryCompatExports
|
||||
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)
|
||||
{
|
||||
if (count <= 0)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using SharpEmu.HLE;
|
||||
using System.Buffers.Binary;
|
||||
using System.Threading;
|
||||
@@ -10,7 +11,7 @@ namespace SharpEmu.Libs.Kernel;
|
||||
|
||||
public static class KernelPthreadCompatExports
|
||||
{
|
||||
private const int MutexTypeDefault = 1;
|
||||
private const int MutexTypeDefault = 0;
|
||||
private const int MutexTypeErrorCheck = 1;
|
||||
private const int MutexTypeRecursive = 2;
|
||||
private const int MutexTypeNormal = 3;
|
||||
@@ -25,11 +26,18 @@ public static class KernelPthreadCompatExports
|
||||
private const int PthreadOnceDone = 2;
|
||||
|
||||
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, PthreadCondState> _condStates = new();
|
||||
private static readonly Dictionary<ulong, object> _onceGates = 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
|
||||
{
|
||||
@@ -40,6 +48,12 @@ public static class KernelPthreadCompatExports
|
||||
public int Protocol { get; set; }
|
||||
}
|
||||
|
||||
private sealed class PthreadMutexWaiter
|
||||
{
|
||||
public required ulong ThreadId { get; init; }
|
||||
public int Reserved;
|
||||
}
|
||||
|
||||
private sealed class PthreadCondState
|
||||
{
|
||||
public object SyncRoot { get; } = new();
|
||||
@@ -463,19 +477,13 @@ public static class KernelPthreadCompatExports
|
||||
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.Remove(mutexAddress);
|
||||
_mutexStates.Remove(handle);
|
||||
}
|
||||
_mutexStates.TryRemove(mutexAddress, out _);
|
||||
_mutexStates.TryRemove(handle, out _);
|
||||
|
||||
state.Semaphore.Dispose();
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
@@ -492,15 +500,10 @@ public static class KernelPthreadCompatExports
|
||||
}
|
||||
|
||||
var resolvedAddress = ResolveMutexHandle(ctx, mutexAddress);
|
||||
PthreadMutexState? state;
|
||||
lock (_stateGate)
|
||||
_mutexStates.TryRemove(resolvedAddress, out var state);
|
||||
if (resolvedAddress != mutexAddress)
|
||||
{
|
||||
_mutexStates.TryGetValue(resolvedAddress, out state);
|
||||
_mutexStates.Remove(resolvedAddress);
|
||||
if (resolvedAddress != mutexAddress)
|
||||
{
|
||||
_mutexStates.Remove(mutexAddress);
|
||||
}
|
||||
_mutexStates.TryRemove(mutexAddress, out _);
|
||||
}
|
||||
|
||||
if (state is null)
|
||||
@@ -508,7 +511,7 @@ public static class KernelPthreadCompatExports
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
|
||||
}
|
||||
|
||||
_ = ctx.TryWriteUInt64(mutexAddress, 0);
|
||||
_ = KernelMemoryCompatExports.TryWriteUInt64Compat(ctx, mutexAddress, 0);
|
||||
state.Semaphore.Dispose();
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
@@ -538,7 +541,7 @@ public static class KernelPthreadCompatExports
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
if (state.Type is MutexTypeNormal or MutexTypeAdaptiveNp)
|
||||
if (state.Type is MutexTypeDefault or MutexTypeNormal or MutexTypeAdaptiveNp)
|
||||
{
|
||||
if (tryOnly)
|
||||
{
|
||||
@@ -563,15 +566,33 @@ public static class KernelPthreadCompatExports
|
||||
}
|
||||
}
|
||||
|
||||
var acquired = true;
|
||||
if (tryOnly)
|
||||
var acquired = state.Semaphore.Wait(0);
|
||||
if (!acquired)
|
||||
{
|
||||
acquired = state.Semaphore.Wait(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
state.Semaphore.Wait();
|
||||
// Guest-thread blocking for pthread_mutex_lock is currently disabled.
|
||||
// Demon's Souls deadlocks during PS5SyncEvent initialization.
|
||||
/* var waiter = new PthreadMutexWaiter { ThreadId = currentThreadId };
|
||||
if (!tryOnly &&
|
||||
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)
|
||||
{
|
||||
TracePthreadMutex(ctx, tryOnly ? "trylock" : "lock", mutexAddress, resolvedAddress, state, currentThreadId, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_BUSY);
|
||||
@@ -630,6 +651,7 @@ public static class KernelPthreadCompatExports
|
||||
try
|
||||
{
|
||||
state.Semaphore.Release();
|
||||
_ = GuestThreadExecution.Scheduler?.WakeBlockedThreads(GetMutexWakeKey(resolvedAddress), 1);
|
||||
}
|
||||
catch (SemaphoreFullException)
|
||||
{
|
||||
@@ -666,7 +688,7 @@ public static class KernelPthreadCompatExports
|
||||
_mutexAttrStates[handle] = initialState;
|
||||
}
|
||||
|
||||
if (!ctx.TryWriteUInt64(attrAddress, handle))
|
||||
if (!KernelMemoryCompatExports.TryWriteUInt64Compat(ctx, attrAddress, handle))
|
||||
{
|
||||
lock (_stateGate)
|
||||
{
|
||||
@@ -765,22 +787,16 @@ public static class KernelPthreadCompatExports
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -817,14 +830,11 @@ public static class KernelPthreadCompatExports
|
||||
|
||||
if (pointedHandle != 0)
|
||||
{
|
||||
lock (_stateGate)
|
||||
if (_mutexStates.TryGetValue(pointedHandle, out state))
|
||||
{
|
||||
if (_mutexStates.TryGetValue(pointedHandle, out state))
|
||||
{
|
||||
_mutexStates[mutexAddress] = state;
|
||||
resolvedAddress = pointedHandle;
|
||||
return true;
|
||||
}
|
||||
_mutexStates.TryAdd(mutexAddress, state);
|
||||
resolvedAddress = pointedHandle;
|
||||
return true;
|
||||
}
|
||||
|
||||
resolvedAddress = pointedHandle;
|
||||
@@ -847,7 +857,7 @@ public static class KernelPthreadCompatExports
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ctx.TryReadUInt64(attrAddress, out var pointedHandle) && pointedHandle != 0)
|
||||
if (KernelMemoryCompatExports.TryReadUInt64Compat(ctx, attrAddress, out var pointedHandle) && pointedHandle != 0)
|
||||
{
|
||||
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)
|
||||
{
|
||||
@@ -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;
|
||||
}
|
||||
@@ -971,7 +981,7 @@ public static class KernelPthreadCompatExports
|
||||
_condStates[handle] = createdState;
|
||||
}
|
||||
|
||||
if (!ctx.TryWriteUInt64(condAddress, handle))
|
||||
if (!KernelMemoryCompatExports.TryWriteUInt64Compat(ctx, condAddress, handle))
|
||||
{
|
||||
lock (_stateGate)
|
||||
{
|
||||
@@ -1035,7 +1045,7 @@ public static class KernelPthreadCompatExports
|
||||
_condStates[handle] = state;
|
||||
}
|
||||
|
||||
if (!ctx.TryWriteUInt64(condAddress, handle))
|
||||
if (!KernelMemoryCompatExports.TryWriteUInt64Compat(ctx, condAddress, handle))
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1202,6 +1212,63 @@ public static class KernelPthreadCompatExports
|
||||
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)
|
||||
{
|
||||
if (timeoutUsec == 0)
|
||||
@@ -1313,13 +1380,10 @@ public static class KernelPthreadCompatExports
|
||||
_mutexStates[handle] = createdState;
|
||||
}
|
||||
|
||||
if (!ctx.TryWriteUInt64(mutexAddress, handle))
|
||||
if (!KernelMemoryCompatExports.TryWriteUInt64Compat(ctx, mutexAddress, handle))
|
||||
{
|
||||
lock (_stateGate)
|
||||
{
|
||||
_mutexStates.Remove(mutexAddress);
|
||||
_mutexStates.Remove(handle);
|
||||
}
|
||||
_mutexStates.TryRemove(mutexAddress, out _);
|
||||
_mutexStates.TryRemove(handle, out _);
|
||||
|
||||
resolvedAddress = 0;
|
||||
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)
|
||||
{
|
||||
if (!ShouldTracePthread())
|
||||
if (!ShouldTracePthreadMutex(mutexAddress, resolvedAddress))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_ = ctx.TryReadUInt64(mutexAddress, out var guestWord0);
|
||||
_ = ctx.TryReadUInt64(mutexAddress + 8, out var guestWord1);
|
||||
_ = KernelMemoryCompatExports.TryReadUInt64Compat(ctx, mutexAddress, out var guestWord0);
|
||||
_ = KernelMemoryCompatExports.TryReadUInt64Compat(ctx, mutexAddress + 8, out var guestWord1);
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][TRACE] pthread_{operation}: mutex=0x{mutexAddress:X16} resolved=0x{resolvedAddress: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)
|
||||
{
|
||||
if (!ShouldTracePthread())
|
||||
if (!_tracePthreadConds)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -1385,6 +1449,45 @@ public static class KernelPthreadCompatExports
|
||||
|
||||
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 System.Buffers.Binary;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
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, PthreadAttrState> _attrStates = 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 long _nextSyntheticRwlockHandleId = 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
|
||||
{
|
||||
@@ -793,7 +794,7 @@ public static class KernelPthreadExtendedCompatExports
|
||||
_rwlockStates[syntheticHandle] = rwlock;
|
||||
}
|
||||
|
||||
_ = ctx.TryWriteUInt64(rwlockAddress, syntheticHandle);
|
||||
_ = KernelMemoryCompatExports.TryWriteUInt64Compat(ctx, rwlockAddress, syntheticHandle);
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
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;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
@@ -959,15 +960,13 @@ public static class KernelPthreadExtendedCompatExports
|
||||
}
|
||||
|
||||
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))
|
||||
@@ -994,17 +993,14 @@ public static class KernelPthreadExtendedCompatExports
|
||||
public static int PosixPthreadKeyDelete(CpuContext ctx)
|
||||
{
|
||||
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)
|
||||
{
|
||||
entry.Value.Remove(key);
|
||||
}
|
||||
foreach (var values in _threadLocalSpecific.Values)
|
||||
{
|
||||
values.TryRemove(key, out _);
|
||||
}
|
||||
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
@@ -1028,22 +1024,15 @@ public static class KernelPthreadExtendedCompatExports
|
||||
var key = unchecked((int)ctx[CpuRegister.Rdi]);
|
||||
var value = ctx[CpuRegister.Rsi];
|
||||
var currentThreadHandle = KernelPthreadState.GetCurrentThreadHandle();
|
||||
lock (_stateGate)
|
||||
if (!_tlsKeys.ContainsKey(key))
|
||||
{
|
||||
if (!_tlsKeys.TryGetValue(key, out _))
|
||||
{
|
||||
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;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
|
||||
}
|
||||
|
||||
var values = _threadLocalSpecific.GetOrAdd(
|
||||
currentThreadHandle,
|
||||
static _ => new ConcurrentDictionary<int, ulong>());
|
||||
values[key] = value;
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
@@ -1065,19 +1054,16 @@ public static class KernelPthreadExtendedCompatExports
|
||||
var key = unchecked((int)ctx[CpuRegister.Rdi]);
|
||||
var currentThreadHandle = KernelPthreadState.GetCurrentThreadHandle();
|
||||
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) &&
|
||||
values.TryGetValue(key, out var storedValue))
|
||||
{
|
||||
value = storedValue;
|
||||
}
|
||||
if (_threadLocalSpecific.TryGetValue(currentThreadHandle, out var values) &&
|
||||
values.TryGetValue(key, out var storedValue))
|
||||
{
|
||||
value = storedValue;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1230,7 +1216,7 @@ public static class KernelPthreadExtendedCompatExports
|
||||
_rwlockStates[syntheticHandle] = createdRwlock;
|
||||
}
|
||||
|
||||
_ = ctx.TryWriteUInt64(rwlockAddress, syntheticHandle);
|
||||
_ = KernelMemoryCompatExports.TryWriteUInt64Compat(ctx, rwlockAddress, syntheticHandle);
|
||||
resolvedAddress = syntheticHandle;
|
||||
rwlock = createdRwlock;
|
||||
return true;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using SharpEmu.HLE;
|
||||
@@ -11,8 +12,7 @@ internal static class KernelPthreadState
|
||||
{
|
||||
private const int ThreadObjectSize = 0x1000;
|
||||
|
||||
private static readonly object Gate = new();
|
||||
private static readonly Dictionary<ulong, ThreadIdentity> Threads = new();
|
||||
private static readonly ConcurrentDictionary<ulong, ThreadIdentity> Threads = new();
|
||||
private static readonly byte[] ZeroThreadObject = new byte[ThreadObjectSize];
|
||||
private static long _nextUniqueThreadId = 1;
|
||||
|
||||
@@ -56,10 +56,7 @@ internal static class KernelPthreadState
|
||||
|
||||
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()
|
||||
@@ -81,10 +78,7 @@ internal static class KernelPthreadState
|
||||
Marshal.Copy(ZeroThreadObject, 0, pointer, ThreadObjectSize);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -57,6 +57,13 @@ public static class KernelRuntimeCompatExports
|
||||
private static readonly (ulong Base, ulong Size)[] _prtApertures = new (ulong Base, ulong Size)[3];
|
||||
private static int _stackChkFailCount;
|
||||
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)]
|
||||
private delegate ulong RdtscDelegate();
|
||||
@@ -80,10 +87,20 @@ public static class KernelRuntimeCompatExports
|
||||
|
||||
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
|
||||
{
|
||||
_shortUsleepCount = 0;
|
||||
var sleepMilliseconds = (int)Math.Min((micros + 999UL) / 1000UL, int.MaxValue);
|
||||
Thread.Sleep(sleepMilliseconds);
|
||||
}
|
||||
@@ -94,7 +111,7 @@ public static class KernelRuntimeCompatExports
|
||||
|
||||
private static void TraceUsleepSpin(CpuContext ctx, ulong micros)
|
||||
{
|
||||
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_USLEEP"), "1", StringComparison.Ordinal))
|
||||
if (!_traceUsleep)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -142,6 +159,19 @@ public static class KernelRuntimeCompatExports
|
||||
|
||||
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}");
|
||||
|
||||
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(
|
||||
@@ -581,19 +611,16 @@ public static class KernelRuntimeCompatExports
|
||||
public static int ErrorAddress(CpuContext ctx)
|
||||
{
|
||||
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;
|
||||
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(
|
||||
Nid = "bnZxYgAFeA0",
|
||||
ExportName = "sceKernelGetSanitizerNewReplaceExternal",
|
||||
@@ -1505,30 +1532,40 @@ public static class KernelRuntimeCompatExports
|
||||
ulong.TryParse(overrideHzText, out var overrideHz) &&
|
||||
overrideHz >= minSane)
|
||||
{
|
||||
TraceKernelTscFrequency("env", overrideHz);
|
||||
return overrideHz;
|
||||
}
|
||||
|
||||
if (TryResolveCpuidTscFrequency(out ulong cpuidHz) && cpuidHz >= minSane)
|
||||
{
|
||||
return cpuidHz;
|
||||
}
|
||||
|
||||
if (TryCalibrateHostTscFrequency(out ulong calibratedHz) && calibratedHz >= minSane)
|
||||
{
|
||||
TraceKernelTscFrequency("calibrated-rdtsc", calibratedHz);
|
||||
return calibratedHz;
|
||||
}
|
||||
|
||||
if (TryResolveCpuidTscFrequency(out ulong cpuidHz) && cpuidHz >= minSane)
|
||||
{
|
||||
TraceKernelTscFrequency("cpuid", cpuidHz);
|
||||
return cpuidHz;
|
||||
}
|
||||
|
||||
var hostQpc = Stopwatch.Frequency > 0
|
||||
? unchecked((ulong)Stopwatch.Frequency)
|
||||
: DefaultKernelTscFrequency;
|
||||
if (hostQpc >= minSane)
|
||||
{
|
||||
TraceKernelTscFrequency("qpc", hostQpc);
|
||||
return hostQpc;
|
||||
}
|
||||
|
||||
TraceKernelTscFrequency("default", 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)
|
||||
{
|
||||
frequencyHz = 0;
|
||||
@@ -1788,7 +1825,7 @@ public static class KernelRuntimeCompatExports
|
||||
}
|
||||
|
||||
var invokeArgs = allocateAtHasAllowAlternativeArg
|
||||
? new object[] { desiredAddress, length, false, false }
|
||||
? new object[] { desiredAddress, length, false, allowSearch }
|
||||
: new object[] { desiredAddress, length, false };
|
||||
var result = allocateAt.Invoke(memoryObject, invokeArgs);
|
||||
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);
|
||||
}
|
||||
|
||||
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}");
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_TRY_AGAIN);
|
||||
|
||||
Reference in New Issue
Block a user