From e581fe41f406cae506d8b7e6120abc20d8c26923 Mon Sep 17 00:00:00 2001 From: ParantezTech Date: Tue, 23 Jun 2026 19:29:29 +0300 Subject: [PATCH] [kernel] pthread improvements --- .../Kernel/KernelPthreadCompatExports.cs | 151 ++++++++++++++++++ .../KernelPthreadExtendedCompatExports.cs | 29 ++++ .../Kernel/KernelRuntimeCompatExports.cs | 23 ++- 3 files changed, 202 insertions(+), 1 deletion(-) diff --git a/src/SharpEmu.Libs/Kernel/KernelPthreadCompatExports.cs b/src/SharpEmu.Libs/Kernel/KernelPthreadCompatExports.cs index 74e0027..803d47f 100644 --- a/src/SharpEmu.Libs/Kernel/KernelPthreadCompatExports.cs +++ b/src/SharpEmu.Libs/Kernel/KernelPthreadCompatExports.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later using SharpEmu.HLE; +using System.Buffers.Binary; using System.Threading; using System.Diagnostics.CodeAnalysis; @@ -19,11 +20,15 @@ public static class KernelPthreadCompatExports private const int MutexAttrObjectSize = 0x40; private const int CondObjectSize = 0x100; private const int DefaultSpuriousCondWakeMilliseconds = 1; + private const int PthreadOnceUninitialized = 0; + private const int PthreadOnceInProgress = 1; + private const int PthreadOnceDone = 2; private static readonly object _stateGate = new(); private static readonly Dictionary _mutexStates = new(); private static readonly Dictionary _mutexAttrStates = new(); private static readonly Dictionary _condStates = new(); + private static readonly Dictionary _onceGates = new(); private static readonly HashSet _condAttrStates = new(); private sealed class PthreadMutexState @@ -57,6 +62,13 @@ public static class KernelPthreadCompatExports return (int)OrbisGen2Result.ORBIS_GEN2_OK; } + [SysAbiExport( + Nid = "EotR8a3ASf4", + ExportName = "pthread_self", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libKernel")] + public static int PosixPthreadSelf(CpuContext ctx) => PthreadSelf(ctx); + [SysAbiExport( Nid = "3PtV6p3QNX4", ExportName = "scePthreadEqual", @@ -340,6 +352,93 @@ public static class KernelPthreadCompatExports return (int)OrbisGen2Result.ORBIS_GEN2_OK; } + [SysAbiExport( + Nid = "14bOACANTBo", + ExportName = "scePthreadOnce", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libKernel")] + public static int PthreadOnce(CpuContext ctx) + { + var onceAddress = ctx[CpuRegister.Rdi]; + var initRoutine = ctx[CpuRegister.Rsi]; + if (onceAddress == 0 || initRoutine == 0) + { + return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT); + } + + if (!TryReadInt32(ctx, onceAddress, out var onceValue)) + { + return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT); + } + + if (onceValue == PthreadOnceDone) + { + return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK); + } + + var gate = GetPthreadOnceGate(onceAddress); + var shouldCall = false; + lock (gate) + { + if (!TryReadInt32(ctx, onceAddress, out onceValue)) + { + return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT); + } + + while (onceValue == PthreadOnceInProgress) + { + Monitor.Wait(gate, TimeSpan.FromMilliseconds(1)); + if (!TryReadInt32(ctx, onceAddress, out onceValue)) + { + return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT); + } + } + + if (onceValue != PthreadOnceDone) + { + if (!TryWriteInt32(ctx, onceAddress, PthreadOnceInProgress)) + { + return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT); + } + + shouldCall = true; + } + } + + if (shouldCall) + { + var scheduler = GuestThreadExecution.Scheduler; + string? error = null; + if (scheduler is null || + !scheduler.TryCallGuestFunction(ctx, initRoutine, 0, 0, 0, 0, "pthread_once", out error)) + { + lock (gate) + { + _ = TryWriteInt32(ctx, onceAddress, PthreadOnceUninitialized); + Monitor.PulseAll(gate); + } + + TracePthreadOnce(onceAddress, initRoutine, "failed", error); + return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_TRY_AGAIN); + } + + lock (gate) + { + if (!TryWriteInt32(ctx, onceAddress, PthreadOnceDone)) + { + _ = TryWriteInt32(ctx, onceAddress, PthreadOnceUninitialized); + Monitor.PulseAll(gate); + return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT); + } + + Monitor.PulseAll(gate); + } + } + + TracePthreadOnce(onceAddress, initRoutine, shouldCall ? "call" : "done", null); + return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK); + } + private static int PthreadMutexInitCore(CpuContext ctx, ulong mutexAddress, ulong attrAddress) { if (mutexAddress == 0) @@ -1136,6 +1235,46 @@ public static class KernelPthreadCompatExports }; } + private static object GetPthreadOnceGate(ulong onceAddress) + { + lock (_stateGate) + { + if (!_onceGates.TryGetValue(onceAddress, out var gate)) + { + gate = new object(); + _onceGates[onceAddress] = gate; + } + + return gate; + } + } + + private static int SetReturn(CpuContext ctx, OrbisGen2Result result) + { + ctx[CpuRegister.Rax] = unchecked((ulong)(int)result); + return (int)result; + } + + private static bool TryReadInt32(CpuContext ctx, ulong address, out int value) + { + Span bytes = stackalloc byte[sizeof(int)]; + if (!ctx.Memory.TryRead(address, bytes)) + { + value = 0; + return false; + } + + value = BinaryPrimitives.ReadInt32LittleEndian(bytes); + return true; + } + + private static bool TryWriteInt32(CpuContext ctx, ulong address, int value) + { + Span bytes = stackalloc byte[sizeof(int)]; + BinaryPrimitives.WriteInt32LittleEndian(bytes, value); + return ctx.Memory.TryWrite(address, bytes); + } + private static bool CreateImplicitMutexState(CpuContext ctx, ulong mutexAddress, int type, out ulong resolvedAddress, [NotNullWhen(true)] out PthreadMutexState? state) { var createdState = new PthreadMutexState @@ -1204,6 +1343,18 @@ public static class KernelPthreadCompatExports $"[LOADER][TRACE] pthread_self: stale_rdi=0x{ctx[CpuRegister.Rdi]:X16} thread=0x{currentThreadHandle:X16} tid=0x{currentThreadId:X16}"); } + private static void TracePthreadOnce(ulong onceAddress, ulong initRoutine, string operation, string? error) + { + if (!ShouldTracePthread()) + { + return; + } + + var suffix = string.IsNullOrWhiteSpace(error) ? string.Empty : $" error={error}"; + Console.Error.WriteLine( + $"[LOADER][TRACE] pthread_once_{operation}: once=0x{onceAddress:X16} init=0x{initRoutine:X16}{suffix}"); + } + private static void TracePthreadMutex(CpuContext ctx, string operation, ulong mutexAddress, ulong resolvedAddress, PthreadMutexState? state, ulong currentThreadId, int result) { if (!ShouldTracePthread()) diff --git a/src/SharpEmu.Libs/Kernel/KernelPthreadExtendedCompatExports.cs b/src/SharpEmu.Libs/Kernel/KernelPthreadExtendedCompatExports.cs index 17e88a8..2491c62 100644 --- a/src/SharpEmu.Libs/Kernel/KernelPthreadExtendedCompatExports.cs +++ b/src/SharpEmu.Libs/Kernel/KernelPthreadExtendedCompatExports.cs @@ -185,6 +185,35 @@ public static class KernelPthreadExtendedCompatExports return (int)OrbisGen2Result.ORBIS_GEN2_OK; } + [SysAbiExport( + Nid = "rcrVFJsQWRY", + ExportName = "scePthreadGetaffinity", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libKernel")] + public static int PthreadGetaffinity(CpuContext ctx) + { + var thread = ctx[CpuRegister.Rdi]; + var outMaskAddress = ctx[CpuRegister.Rsi]; + if (thread == 0 || outMaskAddress == 0) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; + } + + ulong affinityMask; + lock (_stateGate) + { + affinityMask = GetOrCreateThreadStateLocked(thread).AffinityMask; + } + + if (!ctx.TryWriteUInt64(outMaskAddress, affinityMask)) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + + ctx[CpuRegister.Rax] = 0; + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + [SysAbiExport( Nid = "1tKyG7RlMJo", ExportName = "scePthreadGetprio", diff --git a/src/SharpEmu.Libs/Kernel/KernelRuntimeCompatExports.cs b/src/SharpEmu.Libs/Kernel/KernelRuntimeCompatExports.cs index eb7858e..215fe8b 100644 --- a/src/SharpEmu.Libs/Kernel/KernelRuntimeCompatExports.cs +++ b/src/SharpEmu.Libs/Kernel/KernelRuntimeCompatExports.cs @@ -106,6 +106,8 @@ public static class KernelRuntimeCompatExports } var rbx = ctx[CpuRegister.Rbx]; + var r12 = ctx[CpuRegister.R12]; + var r13 = ctx[CpuRegister.R13]; var lockAddress = rbx == 0 ? 0 : rbx + 0xF78; var lockText = "unreadable"; if (lockAddress != 0 && ctx.TryReadUInt64(lockAddress, out var lockValue)) @@ -113,6 +115,25 @@ public static class KernelRuntimeCompatExports lockText = $"0x{lockValue:X16}"; } + var schedulerText = "unreadable"; + if (r12 != 0 && ctx.TryReadUInt64(r12 + 8, out var schedulerAddress)) + { + schedulerText = $"0x{schedulerAddress:X16}"; + } + + var waitValueText = "unreadable"; + if (r13 != 0 && ctx.TryReadUInt64(r13, out var waitValue)) + { + waitValueText = $"0x{waitValue:X16}"; + } + + var callerReturnText = "unreadable"; + var rbp = ctx[CpuRegister.Rbp]; + if (rbp != 0 && ctx.TryReadUInt64(rbp + 8, out var callerReturn)) + { + callerReturnText = $"0x{callerReturn:X16}"; + } + var returnRip = GuestThreadExecution.TryGetCurrentImportCallFrame(out var frame) ? frame.ReturnRip : 0UL; @@ -120,7 +141,7 @@ public static class KernelRuntimeCompatExports var fiber = FiberExports.GetCurrentFiberAddressForDiagnostics(ctx); Console.Error.WriteLine( - $"[LOADER][TRACE] usleep#{count}: usec={micros} ret=0x{returnRip:X16} thread=0x{thread:X16} fiber=0x{fiber:X16} rbx=0x{rbx:X16} lock@+F78=0x{lockAddress:X16}:{lockText} r13=0x{ctx[CpuRegister.R13]:X16} 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}"); } [SysAbiExport(