another hle implements, especially for mutex calls

This commit is contained in:
ParantezTech
2026-03-12 18:01:42 +03:00
parent 73ae502e09
commit 028494a83b
12 changed files with 1133 additions and 252 deletions
+16
View File
@@ -13,6 +13,7 @@ public static class CxaGuardExports
private sealed class GuardState
{
public int OwnerThreadId { get; set; }
public int RecursionDepth { get; set; }
}
private static readonly ConcurrentDictionary<ulong, GuardState> _inProgress = new();
@@ -53,6 +54,7 @@ public static class CxaGuardExports
var newState = new GuardState
{
OwnerThreadId = currentThreadId,
RecursionDepth = 1,
};
if (_inProgress.TryAdd(guardPtr, newState))
{
@@ -101,6 +103,20 @@ public static class CxaGuardExports
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
if (state is not null)
{
lock (state)
{
if (state.RecursionDepth > 1)
{
state.RecursionDepth--;
ctx[CpuRegister.Rax] = 0;
LogGuardResult("guard_release", guardPtr, result: 0, initialized: false, inProgress: true, ownerThreadId: state.OwnerThreadId);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
}
}
if (!TryWriteGuardInitialized(ctx, guardPtr, initialized: true))
{
ctx[CpuRegister.Rax] = 0;
+27 -3
View File
@@ -8,7 +8,6 @@ namespace SharpEmu.Libs.Kernel;
public static class KernelExports
{
private static long _nextThreadId = 1;
private static int _nextFileDescriptor = 2;
private static readonly object _cxaGate = new();
private static readonly List<CxaDestructorEntry> _cxaDestructors = new();
@@ -175,12 +174,24 @@ public static class KernelExports
public static int PthreadCreate(CpuContext ctx)
{
var threadIdAddress = ctx[CpuRegister.Rdi];
var nextThreadId = unchecked((ulong)Interlocked.Increment(ref _nextThreadId));
if (threadIdAddress != 0 && !ctx.TryWriteUInt64(threadIdAddress, nextThreadId))
var attrAddress = ctx[CpuRegister.Rsi];
var entryAddress = ctx[CpuRegister.Rdx];
var argument = ctx[CpuRegister.Rcx];
var nameAddress = ctx[CpuRegister.R8];
var name = nameAddress == 0 ? string.Empty : ReadCString(ctx, nameAddress, 256);
var threadHandle = KernelPthreadState.CreateThreadHandle(name);
if (threadIdAddress != 0 && !ctx.TryWriteUInt64(threadIdAddress, threadHandle))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
if (ShouldTracePthread())
{
Console.Error.WriteLine(
$"[LOADER][TRACE] pthread_create: out=0x{threadIdAddress:X16} attr=0x{attrAddress:X16} entry=0x{entryAddress:X16} arg=0x{argument:X16} name_ptr=0x{nameAddress:X16} name='{name}' -> thread=0x{threadHandle:X16}");
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
@@ -201,12 +212,20 @@ public static class KernelExports
LibraryName = "libKernel")]
public static int PthreadJoin(CpuContext ctx)
{
var threadId = ctx[CpuRegister.Rdi];
var returnValueAddress = ctx[CpuRegister.Rsi];
if (returnValueAddress != 0 && !ctx.TryWriteUInt64(returnValueAddress, 0))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
if (ShouldTracePthread())
{
Console.Error.WriteLine(
$"[LOADER][TRACE] pthread_join: thread=0x{threadId:X16} retval_out=0x{returnValueAddress:X16}");
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
@@ -370,4 +389,9 @@ public static class KernelExports
try { return System.Text.Encoding.UTF8.GetString(buf.Slice(0, len)); }
catch { return System.Text.Encoding.ASCII.GetString(buf.Slice(0, len)); }
}
private static bool ShouldTracePthread()
{
return string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_PTHREADS"), "1", StringComparison.Ordinal);
}
}
@@ -35,13 +35,18 @@ public static class KernelMemoryCompatExports
private const uint PageExecuteReadWrite = 0x40;
private const uint PageExecuteWriteCopy = 0x80;
private const uint PageGuard = 0x100;
private const int Enomem = 12;
private const int Einval = 22;
private const nuint DefaultLibcHeapAlignment = 16;
private static readonly object _fdGate = new();
private static readonly Dictionary<int, FileStream> _openFiles = new();
private static readonly Dictionary<int, OpenDirectory> _openDirectories = new();
private static readonly object _libcAllocGate = new();
private static readonly object _memoryGate = new();
private static readonly object _tlsGate = 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 long _nextFileDescriptor = 2;
@@ -78,7 +83,9 @@ public static class KernelMemoryCompatExports
public required string[] Entries { get; init; }
public int NextIndex { get; set; }
}
private readonly record struct DirectAllocation(ulong Start, ulong Length, int MemoryType);
private readonly record struct LibcHeapAllocation(nint BaseAddress, nuint Size, nuint Alignment);
private readonly record struct MappedRegion(ulong Address, ulong Length, int Protection, bool IsFlexible, ulong DirectStart);
[SysAbiExport(
@@ -310,7 +317,7 @@ public static class KernelMemoryCompatExports
var payload = new byte[bytes.Length + 1];
bytes.CopyTo(payload.AsSpan());
if (!ctx.Memory.TryWrite(destination, payload))
if (!TryWriteCompat(ctx, destination, payload))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
@@ -339,7 +346,7 @@ public static class KernelMemoryCompatExports
var copied = 0;
while (copied < count)
{
if (!ctx.Memory.TryRead(source + (ulong)copied, one))
if (!TryReadCompat(ctx, source + (ulong)copied, one))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
@@ -352,7 +359,7 @@ public static class KernelMemoryCompatExports
}
}
if (!ctx.Memory.TryWrite(destination, payload))
if (!TryWriteCompat(ctx, destination, payload))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
@@ -377,7 +384,7 @@ public static class KernelMemoryCompatExports
}
var payload = GC.AllocateUninitializedArray<byte>(count);
if (count > 0 && (!ctx.Memory.TryRead(source, payload) || !ctx.Memory.TryWrite(destination, payload)))
if (count > 0 && (!TryReadCompat(ctx, source, payload) || !TryWriteCompat(ctx, destination, payload)))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
@@ -396,6 +403,161 @@ public static class KernelMemoryCompatExports
return Memcpy(ctx);
}
[SysAbiExport(
Nid = "gQX+4GDQjpM",
ExportName = "malloc",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int Malloc(CpuContext ctx)
{
ctx[CpuRegister.Rax] =
TryAllocateLibcHeap(ctx[CpuRegister.Rdi], DefaultLibcHeapAlignment, zeroFill: false, out var address)
? address
: 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "tIhsqj0qsFE",
ExportName = "free",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int Free(CpuContext ctx)
{
FreeLibcHeap(ctx[CpuRegister.Rdi]);
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "2X5agFjKxMc",
ExportName = "calloc",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int Calloc(CpuContext ctx)
{
ctx[CpuRegister.Rax] =
TryMultiplyAllocationSize(ctx[CpuRegister.Rdi], ctx[CpuRegister.Rsi], out var totalSize) &&
TryAllocateLibcHeapCore(totalSize, DefaultLibcHeapAlignment, zeroFill: true, out var address)
? address
: 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "Y7aJ1uydPMo",
ExportName = "realloc",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int Realloc(CpuContext ctx)
{
var existingAddress = ctx[CpuRegister.Rdi];
var requestedSize = ctx[CpuRegister.Rsi];
if (existingAddress == 0)
{
ctx[CpuRegister.Rax] =
TryAllocateLibcHeap(requestedSize, DefaultLibcHeapAlignment, zeroFill: false, out var freshAddress)
? freshAddress
: 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
if (requestedSize == 0)
{
FreeLibcHeap(existingAddress);
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
ctx[CpuRegister.Rax] =
TryReallocateLibcHeap(existingAddress, requestedSize, out var resizedAddress)
? resizedAddress
: 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "Ujf3KzMvRmI",
ExportName = "memalign",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int Memalign(CpuContext ctx)
{
ctx[CpuRegister.Rax] =
TryAllocateAlignedLibcHeap(
alignmentValue: ctx[CpuRegister.Rdi],
requestedSize: ctx[CpuRegister.Rsi],
requireSizeMultiple: false,
out var address)
? address
: 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "2Btkg8k24Zg",
ExportName = "aligned_alloc",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int AlignedAlloc(CpuContext ctx)
{
ctx[CpuRegister.Rax] =
TryAllocateAlignedLibcHeap(
alignmentValue: ctx[CpuRegister.Rdi],
requestedSize: ctx[CpuRegister.Rsi],
requireSizeMultiple: true,
out var address)
? address
: 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "cVSk9y8URbc",
ExportName = "posix_memalign",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int PosixMemalign(CpuContext ctx)
{
var outPointerAddress = ctx[CpuRegister.Rdi];
if (outPointerAddress == 0)
{
ctx[CpuRegister.Rax] = Einval;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
if (!TryValidateAlignedAllocation(
ctx[CpuRegister.Rsi],
ctx[CpuRegister.Rdx],
requireSizeMultiple: false,
requirePointerSizedAlignment: true,
out var alignment,
out var requestedSize))
{
_ = TryWriteUInt64Compat(ctx, outPointerAddress, 0);
ctx[CpuRegister.Rax] = Einval;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
if (!TryAllocateLibcHeapCore(requestedSize, alignment, zeroFill: false, out var address))
{
_ = TryWriteUInt64Compat(ctx, outPointerAddress, 0);
ctx[CpuRegister.Rax] = Enomem;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
if (!TryWriteUInt64Compat(ctx, outPointerAddress, address))
{
FreeLibcHeap(address);
ctx[CpuRegister.Rax] = Einval;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "DfivPArhucg",
ExportName = "memcmp",
@@ -415,8 +577,8 @@ public static class KernelMemoryCompatExports
Span<byte> rightByte = stackalloc byte[1];
for (var i = 0; i < count; i++)
{
if (!ctx.Memory.TryRead(left + (ulong)i, leftByte) ||
!ctx.Memory.TryRead(right + (ulong)i, rightByte))
if (!TryReadCompat(ctx, left + (ulong)i, leftByte) ||
!TryReadCompat(ctx, right + (ulong)i, rightByte))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
@@ -2318,6 +2480,236 @@ public static class KernelMemoryCompatExports
}
}
private static bool TryAllocateLibcHeap(ulong requestedSize, nuint alignment, bool zeroFill, out ulong address)
{
address = 0;
return TryConvertAllocationSize(requestedSize, out var size) &&
TryAllocateLibcHeapCore(size, alignment, zeroFill, out address);
}
private static unsafe bool TryAllocateLibcHeapCore(nuint requestedSize, nuint alignment, bool zeroFill, out ulong address)
{
address = 0;
alignment = NormalizeLibcAlignment(alignment);
var actualSize = requestedSize == 0 ? 1u : requestedSize;
nuint totalSize;
try
{
checked
{
totalSize = actualSize + alignment - 1 + (nuint)IntPtr.Size;
}
}
catch (OverflowException)
{
return false;
}
nint baseAddress;
try
{
baseAddress = Marshal.AllocHGlobal(checked((nint)totalSize));
}
catch (OutOfMemoryException)
{
return false;
}
catch (OverflowException)
{
return false;
}
if (baseAddress == 0)
{
return false;
}
var alignedAddress = AlignUp(unchecked((ulong)baseAddress) + (ulong)IntPtr.Size, (ulong)alignment);
lock (_libcAllocGate)
{
_libcAllocations[alignedAddress] = new LibcHeapAllocation(baseAddress, actualSize, alignment);
}
try
{
if (zeroFill)
{
NativeMemory.Clear((void*)alignedAddress, actualSize);
}
}
catch
{
FreeLibcHeap(alignedAddress);
return false;
}
address = alignedAddress;
return true;
}
private static unsafe bool TryReallocateLibcHeap(ulong existingAddress, ulong requestedSize, out ulong resizedAddress)
{
resizedAddress = 0;
if (existingAddress == 0)
{
return TryAllocateLibcHeap(requestedSize, DefaultLibcHeapAlignment, zeroFill: false, out resizedAddress);
}
if (requestedSize == 0)
{
FreeLibcHeap(existingAddress);
return true;
}
LibcHeapAllocation allocation;
lock (_libcAllocGate)
{
if (!_libcAllocations.TryGetValue(existingAddress, out allocation))
{
return false;
}
}
if (!TryAllocateLibcHeap(requestedSize, allocation.Alignment, zeroFill: false, out resizedAddress))
{
return false;
}
var bytesToCopy = Math.Min(allocation.Size, (nuint)requestedSize);
Buffer.MemoryCopy(
source: (void*)existingAddress,
destination: (void*)resizedAddress,
destinationSizeInBytes: checked((long)Math.Max(bytesToCopy, 1u)),
sourceBytesToCopy: checked((long)bytesToCopy));
FreeLibcHeap(existingAddress);
return true;
}
private static bool TryAllocateAlignedLibcHeap(ulong alignmentValue, ulong requestedSize, bool requireSizeMultiple, out ulong address)
{
address = 0;
return TryValidateAlignedAllocation(
alignmentValue,
requestedSize,
requireSizeMultiple,
requirePointerSizedAlignment: false,
out var alignment,
out var size) &&
TryAllocateLibcHeapCore(size, alignment, zeroFill: false, out address);
}
private static bool TryValidateAlignedAllocation(
ulong alignmentValue,
ulong requestedSize,
bool requireSizeMultiple,
bool requirePointerSizedAlignment,
out nuint alignment,
out nuint size)
{
alignment = 0;
size = 0;
if (!TryConvertAllocationSize(requestedSize, out size) ||
alignmentValue == 0 ||
alignmentValue > (ulong)nint.MaxValue)
{
return false;
}
alignment = (nuint)alignmentValue;
if (!IsPowerOfTwo(alignment))
{
return false;
}
if (requirePointerSizedAlignment && alignment % (nuint)IntPtr.Size != 0)
{
return false;
}
if (alignment < (nuint)IntPtr.Size)
{
alignment = (nuint)IntPtr.Size;
}
if (requireSizeMultiple && size % alignment != 0)
{
return false;
}
return true;
}
private static void FreeLibcHeap(ulong address)
{
if (address == 0)
{
return;
}
LibcHeapAllocation allocation;
lock (_libcAllocGate)
{
if (!_libcAllocations.Remove(address, out allocation))
{
return;
}
}
Marshal.FreeHGlobal(allocation.BaseAddress);
}
private static bool TryMultiplyAllocationSize(ulong left, ulong right, out nuint size)
{
size = 0;
if (!TryConvertAllocationSize(left, out var leftSize) ||
!TryConvertAllocationSize(right, out var rightSize))
{
return false;
}
try
{
checked
{
size = leftSize * rightSize;
}
}
catch (OverflowException)
{
return false;
}
return true;
}
private static bool TryConvertAllocationSize(ulong requestedSize, out nuint size)
{
size = 0;
if (requestedSize > (ulong)nint.MaxValue)
{
return false;
}
size = (nuint)requestedSize;
return true;
}
private static nuint NormalizeLibcAlignment(nuint alignment)
{
if (alignment < DefaultLibcHeapAlignment)
{
return DefaultLibcHeapAlignment;
}
return alignment;
}
private static bool IsPowerOfTwo(nuint value)
{
return value != 0 && (value & (value - 1)) == 0;
}
private static bool TryWriteHostMemory(ulong address, ReadOnlySpan<byte> source)
{
if (source.IsEmpty || !IsHostRangeAccessible(address, (ulong)source.Length, writeAccess: true))
@@ -8,8 +8,10 @@ namespace SharpEmu.Libs.Kernel;
public static class KernelPthreadCompatExports
{
private const int MutexTypeNormal = 0;
private const int MutexTypeRecursive = 1;
private const int MutexTypeDefault = 1;
private const int MutexTypeErrorCheck = 1;
private const int MutexTypeRecursive = 2;
private const int MutexTypeNormal = 4;
private const ulong SyntheticMutexHandleBase = 0x00006000_0000_0000;
private const ulong SyntheticMutexAttrHandleBase = 0x00006001_0000_0000;
@@ -18,24 +20,22 @@ public static class KernelPthreadCompatExports
private static readonly Dictionary<ulong, PthreadMutexAttrState> _mutexAttrStates = new();
private static readonly Dictionary<ulong, PthreadCondState> _condStates = new();
private static readonly HashSet<ulong> _condAttrStates = new();
private static long _nextSyntheticThreadId = 1;
private static long _nextSyntheticMutexHandleId = 1;
private static long _nextSyntheticMutexAttrHandleId = 1;
[ThreadStatic]
private static ulong _currentThreadId;
private sealed class PthreadMutexState
{
public SemaphoreSlim Semaphore { get; } = new(1, 1);
public ulong OwnerThreadId { get; set; }
public int RecursionCount { get; set; }
public int Type { get; set; } = MutexTypeNormal;
public int Type { get; set; } = MutexTypeDefault;
public int Protocol { get; set; }
}
private sealed class PthreadCondState
{
public int PendingSignals { get; set; }
public object SyncRoot { get; } = new();
public ulong SignalEpoch { get; set; }
public int Waiters { get; set; }
}
@@ -48,9 +48,9 @@ public static class KernelPthreadCompatExports
LibraryName = "libKernel")]
public static int PthreadSelf(CpuContext ctx)
{
var currentThreadId = GetCurrentThreadId();
ctx[CpuRegister.Rax] = currentThreadId;
TracePthreadSelf(ctx, currentThreadId);
var currentThreadHandle = KernelPthreadState.GetCurrentThreadHandle();
ctx[CpuRegister.Rax] = currentThreadHandle;
TracePthreadSelf(ctx, currentThreadHandle);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
@@ -86,7 +86,7 @@ public static class KernelPthreadCompatExports
LibraryName = "libKernel")]
public static int PthreadGetthreadid(CpuContext ctx)
{
var currentThreadId = GetCurrentThreadId();
var currentThreadId = KernelPthreadState.GetCurrentThreadUniqueId();
var outAddress = ctx[CpuRegister.Rdi];
if (outAddress != 0 && !ctx.TryWriteUInt64(outAddress, currentThreadId))
{
@@ -216,6 +216,13 @@ public static class KernelPthreadCompatExports
LibraryName = "libKernel")]
public static int PosixPthreadMutexattrSettype(CpuContext ctx) => PthreadMutexattrSettypeCore(ctx, ctx[CpuRegister.Rdi], unchecked((int)ctx[CpuRegister.Rsi]));
[SysAbiExport(
Nid = "5txKfcMUAok",
ExportName = "pthread_mutexattr_setprotocol",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PosixPthreadMutexattrSetprotocol(CpuContext ctx) => PthreadMutexattrSetprotocolCore(ctx, ctx[CpuRegister.Rdi], unchecked((int)ctx[CpuRegister.Rsi]));
[SysAbiExport(
Nid = "2Tb92quprl0",
ExportName = "scePthreadCondInit",
@@ -242,7 +249,7 @@ public static class KernelPthreadCompatExports
ExportName = "scePthreadCondTimedwait",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadCondTimedwait(CpuContext ctx) => PthreadCondWaitCore(ctx, ctx[CpuRegister.Rdi], ctx[CpuRegister.Rsi], timed: true);
public static int PthreadCondTimedwait(CpuContext ctx) => PthreadCondWaitCore(ctx, ctx[CpuRegister.Rdi], ctx[CpuRegister.Rsi], timed: true, timeoutUsec: unchecked((uint)ctx[CpuRegister.Rdx]));
[SysAbiExport(
Nid = "kDh-NfxgMtE",
@@ -390,7 +397,7 @@ public static class KernelPthreadCompatExports
}
}
var currentThreadId = GetCurrentThreadId();
var currentThreadId = KernelPthreadState.GetCurrentThreadHandle();
lock (state)
{
if (state.OwnerThreadId == currentThreadId)
@@ -402,8 +409,11 @@ public static class KernelPthreadCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
TracePthreadMutex(ctx, tryOnly ? "trylock" : "lock", mutexAddress, resolvedAddress, state, currentThreadId, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_ALREADY_EXISTS);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_ALREADY_EXISTS;
var ownedResult = tryOnly
? (int)OrbisGen2Result.ORBIS_GEN2_ERROR_BUSY
: (int)OrbisGen2Result.ORBIS_GEN2_ERROR_DEADLOCK;
TracePthreadMutex(ctx, tryOnly ? "trylock" : "lock", mutexAddress, resolvedAddress, state, currentThreadId, ownedResult);
return ownedResult;
}
}
@@ -418,8 +428,8 @@ public static class KernelPthreadCompatExports
}
if (!acquired)
{
TracePthreadMutex(ctx, tryOnly ? "trylock" : "lock", mutexAddress, resolvedAddress, state, currentThreadId, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_ALREADY_EXISTS);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_ALREADY_EXISTS;
TracePthreadMutex(ctx, tryOnly ? "trylock" : "lock", mutexAddress, resolvedAddress, state, currentThreadId, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_BUSY);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_BUSY;
}
lock (state)
@@ -448,11 +458,11 @@ public static class KernelPthreadCompatExports
if (state is null)
{
TracePthreadMutex(ctx, "unlock", mutexAddress, resolvedAddress, null, GetCurrentThreadId(), (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
TracePthreadMutex(ctx, "unlock", mutexAddress, resolvedAddress, null, KernelPthreadState.GetCurrentThreadHandle(), (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
var currentThreadId = GetCurrentThreadId();
var currentThreadId = KernelPthreadState.GetCurrentThreadHandle();
var shouldRelease = false;
lock (state)
{
@@ -464,8 +474,8 @@ public static class KernelPthreadCompatExports
if (requireOwner && state.OwnerThreadId != currentThreadId)
{
TracePthreadMutex(ctx, "unlock", mutexAddress, resolvedAddress, state, currentThreadId, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
TracePthreadMutex(ctx, "unlock", mutexAddress, resolvedAddress, state, currentThreadId, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_PERMISSION_DENIED);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_PERMISSION_DENIED;
}
state.RecursionCount--;
@@ -503,8 +513,8 @@ public static class KernelPthreadCompatExports
var syntheticHandle = AllocateSyntheticHandle(SyntheticMutexAttrHandleBase, ref _nextSyntheticMutexAttrHandleId);
lock (_stateGate)
{
_mutexAttrStates[attrAddress] = new PthreadMutexAttrState(MutexTypeNormal, 0);
_mutexAttrStates[syntheticHandle] = new PthreadMutexAttrState(MutexTypeNormal, 0);
_mutexAttrStates[attrAddress] = new PthreadMutexAttrState(MutexTypeDefault, 0);
_mutexAttrStates[syntheticHandle] = new PthreadMutexAttrState(MutexTypeDefault, 0);
}
_ = ctx.TryWriteUInt64(attrAddress, syntheticHandle);
@@ -543,10 +553,10 @@ public static class KernelPthreadCompatExports
{
if (!_mutexAttrStates.TryGetValue(resolvedAddress, out var state))
{
state = new PthreadMutexAttrState(MutexTypeNormal, 0);
state = new PthreadMutexAttrState(MutexTypeDefault, 0);
}
_mutexAttrStates[resolvedAddress] = state with { Type = type };
_mutexAttrStates[resolvedAddress] = state with { Type = NormalizeMutexType(type) };
if (resolvedAddress != attrAddress)
{
_mutexAttrStates[attrAddress] = _mutexAttrStates[resolvedAddress];
@@ -568,7 +578,7 @@ public static class KernelPthreadCompatExports
{
if (!_mutexAttrStates.TryGetValue(resolvedAddress, out var state))
{
state = new PthreadMutexAttrState(MutexTypeNormal, 0);
state = new PthreadMutexAttrState(MutexTypeDefault, 0);
}
_mutexAttrStates[resolvedAddress] = state with { Protocol = protocol };
@@ -651,7 +661,7 @@ public static class KernelPthreadCompatExports
{
return _mutexAttrStates.TryGetValue(resolvedAddress, out var state)
? state
: default;
: new PthreadMutexAttrState(MutexTypeDefault, 0);
}
}
@@ -691,55 +701,66 @@ public static class KernelPthreadCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
private static int PthreadCondWaitCore(CpuContext ctx, ulong condAddress, ulong mutexAddress, bool timed)
private static int PthreadCondWaitCore(CpuContext ctx, ulong condAddress, ulong mutexAddress, bool timed, uint timeoutUsec = 0)
{
if (condAddress == 0 || mutexAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
PthreadCondState state;
lock (_stateGate)
{
if (!_condStates.TryGetValue(condAddress, out var state))
if (!_condStates.TryGetValue(condAddress, out state!))
{
state = new PthreadCondState();
_condStates[condAddress] = state;
}
}
var waitResult = (int)OrbisGen2Result.ORBIS_GEN2_OK;
lock (state.SyncRoot)
{
state.Waiters++;
if (state.PendingSignals > 0)
var observedEpoch = state.SignalEpoch;
TracePthreadCond("wait-enter", condAddress, mutexAddress, state, timed, waitResult);
var unlockResult = PthreadMutexUnlockCore(ctx, mutexAddress, requireOwner: true);
if (unlockResult != (int)OrbisGen2Result.ORBIS_GEN2_OK)
{
state.PendingSignals--;
state.Waiters--;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
TracePthreadCond("wait-unlock-fail", condAddress, mutexAddress, state, timed, unlockResult);
return unlockResult;
}
}
var unlockResult = PthreadMutexUnlockCore(ctx, mutexAddress, requireOwner: true);
if (unlockResult != (int)OrbisGen2Result.ORBIS_GEN2_OK)
{
return unlockResult;
}
while (state.SignalEpoch == observedEpoch)
{
if (!timed)
{
Monitor.Wait(state.SyncRoot);
continue;
}
if (timed)
{
Thread.Sleep(1);
}
else
{
Thread.Yield();
if (!Monitor.Wait(state.SyncRoot, GetCondWaitTimeout(timeoutUsec)))
{
waitResult = (int)OrbisGen2Result.ORBIS_GEN2_ERROR_TIMED_OUT;
break;
}
}
state.Waiters = Math.Max(0, state.Waiters - 1);
TracePthreadCond(waitResult == (int)OrbisGen2Result.ORBIS_GEN2_OK ? "wait-wake" : "wait-timeout", condAddress, mutexAddress, state, timed, waitResult);
}
var lockResult = PthreadMutexLockCore(ctx, mutexAddress, tryOnly: false);
lock (_stateGate)
if (lockResult != (int)OrbisGen2Result.ORBIS_GEN2_OK)
{
if (_condStates.TryGetValue(condAddress, out var state))
{
state.Waiters = Math.Max(0, state.Waiters - 1);
}
TracePthreadCond("wait-relock-fail", condAddress, mutexAddress, state, timed, lockResult);
return lockResult;
}
return lockResult;
TracePthreadCond(waitResult == (int)OrbisGen2Result.ORBIS_GEN2_OK ? "wait-exit" : "wait-exit-timeout", condAddress, mutexAddress, state, timed, waitResult);
return waitResult;
}
private static int PthreadCondSignalCore(ulong condAddress, bool broadcast)
@@ -749,47 +770,70 @@ public static class KernelPthreadCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
PthreadCondState state;
lock (_stateGate)
{
if (!_condStates.TryGetValue(condAddress, out var state))
if (!_condStates.TryGetValue(condAddress, out state!))
{
state = new PthreadCondState();
_condStates[condAddress] = state;
}
}
if (broadcast)
lock (state.SyncRoot)
{
if (state.Waiters > 0)
{
state.PendingSignals += Math.Max(1, state.Waiters);
}
else
{
state.PendingSignals++;
state.SignalEpoch++;
if (broadcast)
{
Monitor.PulseAll(state.SyncRoot);
}
else
{
Monitor.Pulse(state.SyncRoot);
}
}
TracePthreadCond(broadcast ? "broadcast" : "signal", condAddress, mutexAddress: 0, state, timed: false, (int)OrbisGen2Result.ORBIS_GEN2_OK);
}
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
private static ulong GetCurrentThreadId()
private static TimeSpan GetCondWaitTimeout(uint timeoutUsec)
{
if (_currentThreadId != 0)
if (timeoutUsec == 0)
{
return _currentThreadId;
return TimeSpan.Zero;
}
_currentThreadId = unchecked((ulong)Interlocked.Increment(ref _nextSyntheticThreadId));
return _currentThreadId;
return TimeSpan.FromTicks((long)timeoutUsec * 10L);
}
private static void TracePthreadSelf(CpuContext ctx, ulong currentThreadId)
private static int NormalizeMutexType(int type)
{
return type switch
{
0 => MutexTypeDefault,
1 => MutexTypeErrorCheck,
2 => MutexTypeRecursive,
3 => MutexTypeNormal,
4 => MutexTypeNormal,
_ => MutexTypeDefault,
};
}
private static void TracePthreadSelf(CpuContext ctx, ulong currentThreadHandle)
{
if (!ShouldTracePthread())
{
return;
}
var currentThreadId = KernelPthreadState.GetCurrentThreadUniqueId();
Console.Error.WriteLine(
$"[LOADER][TRACE] pthread_self: stale_rdi=0x{ctx[CpuRegister.Rdi]:X16} thread=0x{currentThreadId:X16}");
$"[LOADER][TRACE] pthread_self: stale_rdi=0x{ctx[CpuRegister.Rdi]:X16} thread=0x{currentThreadHandle:X16} tid=0x{currentThreadId:X16}");
}
private static void TracePthreadMutex(CpuContext ctx, string operation, ulong mutexAddress, ulong resolvedAddress, PthreadMutexState? state, ulong currentThreadId, int result)
@@ -808,6 +852,18 @@ public static class KernelPthreadCompatExports
$"recursion={(state?.RecursionCount ?? 0)} type={(state?.Type ?? 0)} result=0x{unchecked((uint)result):X8}");
}
private static void TracePthreadCond(string operation, ulong condAddress, ulong mutexAddress, PthreadCondState? state, bool timed, int result)
{
if (!ShouldTracePthread())
{
return;
}
Console.Error.WriteLine(
$"[LOADER][TRACE] pthread_cond_{operation}: cond=0x{condAddress:X16} mutex=0x{mutexAddress:X16} " +
$"waiters={(state?.Waiters ?? 0)} epoch=0x{(state?.SignalEpoch ?? 0):X} timed={timed} result=0x{unchecked((uint)result):X8}");
}
private static bool ShouldTracePthread()
{
return string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_PTHREADS"), "1", StringComparison.Ordinal);
@@ -841,9 +841,13 @@ public static class KernelPthreadExtendedCompatExports
return state;
}
var name = KernelPthreadState.TryGetThreadIdentity(thread, out var identity)
? identity.Name
: $"Thread-{thread:X}";
state = new ThreadState
{
Name = $"Thread-{thread:X}",
Name = name,
Priority = DefaultThreadPriority,
AffinityMask = DefaultThreadAffinityMask,
DetachState = DefaultDetachState,
@@ -0,0 +1,78 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using System.Runtime.InteropServices;
using System.Threading;
namespace SharpEmu.Libs.Kernel;
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 byte[] ZeroThreadObject = new byte[ThreadObjectSize];
private static long _nextUniqueThreadId = 1;
[ThreadStatic]
private static ulong _currentThreadHandle;
[ThreadStatic]
private static ulong _currentThreadUniqueId;
internal readonly record struct ThreadIdentity(ulong UniqueId, string Name);
internal static ulong GetCurrentThreadHandle()
{
EnsureCurrentThreadRegistered();
return _currentThreadHandle;
}
internal static ulong GetCurrentThreadUniqueId()
{
EnsureCurrentThreadRegistered();
return _currentThreadUniqueId;
}
internal static ulong CreateThreadHandle(string name)
{
var uniqueId = unchecked((ulong)Interlocked.Increment(ref _nextUniqueThreadId));
return AllocateThreadHandle(uniqueId, name);
}
internal static bool TryGetThreadIdentity(ulong threadHandle, out ThreadIdentity identity)
{
lock (Gate)
{
return Threads.TryGetValue(threadHandle, out identity);
}
}
private static void EnsureCurrentThreadRegistered()
{
if (_currentThreadHandle != 0)
{
return;
}
var uniqueId = unchecked((ulong)Interlocked.Increment(ref _nextUniqueThreadId));
var name = $"Thread-{uniqueId:X}";
_currentThreadHandle = AllocateThreadHandle(uniqueId, name);
_currentThreadUniqueId = uniqueId;
}
private static ulong AllocateThreadHandle(ulong uniqueId, string name)
{
var pointer = Marshal.AllocHGlobal(ThreadObjectSize);
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);
}
return handle;
}
}