Files
sharpemu/src/SharpEmu.Libs/Kernel/KernelSemaphoreCompatExports.cs
T
h4sht 2a4da8c0a9 [Kernel/Semaphore] Close race between sceKernelWaitSema and sceKernelSignalSema (#504)
When sceKernelWaitSema finds the count insufficient it increments
WaitingThreads, releases the semaphore gate, and calls
RequestCurrentThreadBlock to set the thread-static block flags. A
signal arriving before the scheduler registers the block metadata
is missed by WakeBlockedThreads — the waiter has not been
registered yet and the signal's wake iteration skips it.

The scheduler's exit handler already re-checks TryWake() after
setting the thread to Blocked, but that requires the thread to
fully exit to the scheduler and back. Instead, re-check the
semaphore count under the gate immediately after the block request:
if the count is now sufficient, consume the tokens, cancel the
pending block via TryConsumeCurrentThreadBlock, and return without
ever yielding to the scheduler.

Co-authored-by: tru3 <tru3@tru3.com>
2026-07-22 14:34:19 +03:00

709 lines
25 KiB
C#

// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using System.Buffers.Binary;
using System.Collections.Concurrent;
using System.Text;
using SharpEmu.HLE;
namespace SharpEmu.Libs.Kernel;
public static class KernelSemaphoreCompatExports
{
private const int MaxSemaphoreNameLength = 128;
private static readonly ConcurrentDictionary<uint, KernelSemaphoreState> _semaphores = new();
private static int _nextSemaphoreHandle = 1;
private sealed class KernelSemaphoreState
{
public required string Name { get; init; }
// Formatted once at creation; signal/wait/cancel/delete all wake through this key.
public required string WakeKey { get; init; }
public required int InitialCount { get; init; }
public required int MaxCount { get; init; }
public int Count { get; set; }
public int WaitingThreads { get; set; }
public object Gate { get; } = new();
}
[SysAbiExport(
Nid = "188x57JYp0g",
ExportName = "sceKernelCreateSema",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelCreateSema(CpuContext ctx)
{
var semaphoreAddress = ctx[CpuRegister.Rdi];
var nameAddress = ctx[CpuRegister.Rsi];
var attr = unchecked((uint)ctx[CpuRegister.Rdx]);
var initialCount = unchecked((int)ctx[CpuRegister.Rcx]);
var maxCount = unchecked((int)ctx[CpuRegister.R8]);
var optionAddress = ctx[CpuRegister.R9];
if (semaphoreAddress == 0 ||
nameAddress == 0 ||
attr > 2 ||
initialCount < 0 ||
maxCount <= 0 ||
initialCount > maxCount ||
optionAddress != 0)
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
if (!TryReadNullTerminatedUtf8(ctx, nameAddress, MaxSemaphoreNameLength, out var name))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
var handle = unchecked((uint)Interlocked.Increment(ref _nextSemaphoreHandle));
if (handle == 0)
{
handle = unchecked((uint)Interlocked.Increment(ref _nextSemaphoreHandle));
}
_semaphores[handle] = new KernelSemaphoreState
{
Name = name,
WakeKey = GetSemaphoreWakeKey(handle),
InitialCount = initialCount,
MaxCount = maxCount,
Count = initialCount,
};
if (!TryWriteUInt32(ctx, semaphoreAddress, handle))
{
_semaphores.TryRemove(handle, out _);
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
if (_traceSema)
{
TraceSemaphore($"create handle=0x{handle:X8} name='{name}' attr=0x{attr:X} init={initialCount} max={maxCount}");
}
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
}
[SysAbiExport(
Nid = "Zxa0VhQVTsk",
ExportName = "sceKernelWaitSema",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelWaitSema(CpuContext ctx)
{
var handle = unchecked((uint)ctx[CpuRegister.Rdi]);
var needCount = unchecked((int)ctx[CpuRegister.Rsi]);
var timeoutAddress = ctx[CpuRegister.Rdx];
if (!_semaphores.TryGetValue(handle, out var semaphore))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
}
if (needCount < 1 || needCount > semaphore.MaxCount)
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
uint timeoutUsec = 0;
if (timeoutAddress != 0 && !TryReadUInt32(ctx, timeoutAddress, out timeoutUsec))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
lock (semaphore.Gate)
{
if (semaphore.Count >= needCount)
{
semaphore.Count -= needCount;
if (timeoutAddress != 0)
{
_ = TryWriteUInt32(ctx, timeoutAddress, timeoutUsec);
}
if (_traceSema)
{
TraceSemaphore($"wait handle=0x{handle:X8} name='{semaphore.Name}' need={needCount} count={semaphore.Count} timeout={(timeoutAddress == 0 ? "infinite" : timeoutUsec)}");
}
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
}
semaphore.WaitingThreads++;
}
// Block cooperatively: the wake predicate atomically acquires the
// tokens (so a wake commits the acquisition), while the resume
// handler distinguishes a real acquisition from a deadline expiry.
var acquired = false;
var deadline = timeoutAddress != 0
? GuestThreadExecution.ComputeDeadlineTimestamp(TimeSpan.FromMicroseconds(timeoutUsec))
: 0;
bool WakePredicate()
{
lock (semaphore.Gate)
{
if (semaphore.Count >= needCount)
{
semaphore.Count -= needCount;
semaphore.WaitingThreads = Math.Max(0, semaphore.WaitingThreads - 1);
acquired = true;
return true;
}
return false;
}
}
int ResumeWait()
{
if (timeoutAddress != 0)
{
_ = TryWriteUInt32(ctx, timeoutAddress, 0);
}
if (acquired)
{
if (_traceSema)
{
TraceSemaphore($"wait-wake handle=0x{handle:X8} name='{semaphore.Name}' need={needCount} count={semaphore.Count}");
}
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
lock (semaphore.Gate)
{
semaphore.WaitingThreads = Math.Max(0, semaphore.WaitingThreads - 1);
}
if (_traceSema)
{
TraceSemaphore($"wait-timeout handle=0x{handle:X8} name='{semaphore.Name}' need={needCount} count={semaphore.Count}");
}
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_TIMED_OUT;
}
if (GuestThreadExecution.RequestCurrentThreadBlock(
ctx,
"sceKernelWaitSema",
GetSemaphoreWakeKey(handle),
ResumeWait,
WakePredicate,
deadline))
{
// A signal may have arrived between releasing the semaphore gate
// (after incrementing WaitingThreads) and the scheduler registering
// this block. When that happens WakeBlockedThreads cannot find the
// waiter yet and the exit-handler re-check runs later; a re-check
// here keeps the thread from yielding to the scheduler at all when
// the count is already sufficient.
lock (semaphore.Gate)
{
if (semaphore.Count >= needCount)
{
semaphore.Count -= needCount;
semaphore.WaitingThreads = Math.Max(0, semaphore.WaitingThreads - 1);
GuestThreadExecution.TryConsumeCurrentThreadBlock(out _);
if (_traceSema)
{
TraceSemaphore($"wait-recheck handle=0x{handle:X8} name='{semaphore.Name}' need={needCount} count={semaphore.Count} {FormatCallSite(ctx)}");
}
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
}
}
if (_traceSema)
{
TraceSemaphore($"wait-block handle=0x{handle:X8} name='{semaphore.Name}' need={needCount} count={semaphore.Count} timeout={(timeoutAddress == 0 ? "infinite" : timeoutUsec)} waiters={semaphore.WaitingThreads} {FormatCallSite(ctx)}");
}
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
}
// Not a guest thread (or no scheduler): fall back to a host-thread
// wait so the semantics still hold on non-cooperative callers.
return WaitSemaphoreOnHostThread(ctx, semaphore, handle, needCount, timeoutAddress, timeoutUsec);
}
private static int WaitSemaphoreOnHostThread(
CpuContext ctx,
KernelSemaphoreState semaphore,
uint handle,
int needCount,
ulong timeoutAddress,
uint timeoutUsec)
{
var deadlineMs = timeoutAddress != 0
? Environment.TickCount64 + Math.Max(1L, timeoutUsec / 1000L)
: long.MaxValue;
lock (semaphore.Gate)
{
if (_traceSema)
{
TraceSemaphore(
$"wait-host-block handle=0x{handle:X8} name='{semaphore.Name}' need={needCount} " +
$"count={semaphore.Count} timeout={(timeoutAddress == 0 ? "infinite" : timeoutUsec)} {FormatCallSite(ctx)}");
}
while (semaphore.Count < needCount)
{
var remaining = deadlineMs - Environment.TickCount64;
if (timeoutAddress != 0 && remaining <= 0)
{
semaphore.WaitingThreads = Math.Max(0, semaphore.WaitingThreads - 1);
_ = TryWriteUInt32(ctx, timeoutAddress, 0);
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_TIMED_OUT);
}
Monitor.Wait(semaphore.Gate, (int)Math.Min(remaining, 100));
}
semaphore.Count -= needCount;
semaphore.WaitingThreads = Math.Max(0, semaphore.WaitingThreads - 1);
if (_traceSema)
{
TraceSemaphore(
$"wait-host-wake handle=0x{handle:X8} name='{semaphore.Name}' need={needCount} count={semaphore.Count} {FormatCallSite(ctx)}");
}
if (timeoutAddress != 0)
{
_ = TryWriteUInt32(ctx, timeoutAddress, 0);
}
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
}
}
private static string GetSemaphoreWakeKey(uint handle) => $"sceKernelWaitSema:{handle:X8}";
[SysAbiExport(
Nid = "12wOHk8ywb0",
ExportName = "sceKernelPollSema",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelPollSema(CpuContext ctx, uint handle, int needCount)
{
if (!_semaphores.TryGetValue(handle, out var semaphore))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
}
if (needCount < 1 || needCount > semaphore.MaxCount)
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
lock (semaphore.Gate)
{
if (semaphore.Count < needCount)
{
if (_traceSema)
{
TraceSemaphore($"poll-busy handle=0x{handle:X8} name='{semaphore.Name}' need={needCount} count={semaphore.Count}");
}
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_BUSY);
}
semaphore.Count -= needCount;
if (_traceSema)
{
TraceSemaphore($"poll handle=0x{handle:X8} name='{semaphore.Name}' need={needCount} count={semaphore.Count}");
}
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
}
}
[SysAbiExport(
Nid = "4czppHBiriw",
ExportName = "sceKernelSignalSema",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelSignalSema(CpuContext ctx, uint handle, int signalCount)
{
if (!_semaphores.TryGetValue(handle, out var semaphore))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
}
if (signalCount <= 0)
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
lock (semaphore.Gate)
{
if (semaphore.Count > semaphore.MaxCount - signalCount)
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
semaphore.Count += signalCount;
// Wake host-thread waiters parked in the fallback path.
Monitor.PulseAll(semaphore.Gate);
if (_traceSema)
{
TraceSemaphore($"signal handle=0x{handle:X8} name='{semaphore.Name}' signal={signalCount} count={semaphore.Count} waiters={semaphore.WaitingThreads} {FormatCallSite(ctx)}");
}
}
// Wake cooperatively-blocked guest threads; their wake predicate
// acquires the tokens atomically, so this respects the new count.
_ = GuestThreadExecution.Scheduler?.WakeBlockedThreads(GetSemaphoreWakeKey(handle));
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
}
[SysAbiExport(
Nid = "4DM06U2BNEY",
ExportName = "sceKernelCancelSema",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelCancelSema(CpuContext ctx, uint handle, int setCount, ulong waitingThreadsAddress)
{
if (!_semaphores.TryGetValue(handle, out var semaphore))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
}
if (setCount > semaphore.MaxCount)
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
lock (semaphore.Gate)
{
if (waitingThreadsAddress != 0 && !TryWriteUInt32(ctx, waitingThreadsAddress, unchecked((uint)semaphore.WaitingThreads)))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
semaphore.Count = setCount < 0 ? semaphore.InitialCount : setCount;
semaphore.WaitingThreads = 0;
Monitor.PulseAll(semaphore.Gate);
if (_traceSema)
{
TraceSemaphore($"cancel handle=0x{handle:X8} name='{semaphore.Name}' set={setCount} count={semaphore.Count}");
}
}
_ = GuestThreadExecution.Scheduler?.WakeBlockedThreads(GetSemaphoreWakeKey(handle));
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
}
[SysAbiExport(
Nid = "R1Jvn8bSCW8",
ExportName = "sceKernelDeleteSema",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelDeleteSema(CpuContext ctx)
{
var handle = unchecked((uint)ctx[CpuRegister.Rdi]);
if (!_semaphores.TryRemove(handle, out var semaphore))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
}
if (_traceSema)
{
TraceSemaphore($"delete handle=0x{handle:X8} name='{semaphore.Name}'");
}
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
}
[SysAbiExport(
Nid = "pDuPEf3m4fI",
ExportName = "sem_init",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PosixSemInit(CpuContext ctx)
{
var semaphoreAddress = ctx[CpuRegister.Rdi];
var initialCountValue = ctx[CpuRegister.Rdx];
if (semaphoreAddress == 0 || initialCountValue > int.MaxValue)
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
var handle = unchecked((uint)Interlocked.Increment(ref _nextSemaphoreHandle));
if (handle == 0)
{
handle = unchecked((uint)Interlocked.Increment(ref _nextSemaphoreHandle));
}
var initialCount = unchecked((int)initialCountValue);
_semaphores[handle] = new KernelSemaphoreState
{
Name = $"posix@0x{semaphoreAddress:X16}",
WakeKey = GetSemaphoreWakeKey(handle),
InitialCount = initialCount,
MaxCount = int.MaxValue,
Count = initialCount,
};
if (!TryWriteUInt32(ctx, semaphoreAddress, handle))
{
_semaphores.TryRemove(handle, out _);
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
if (_traceSema)
{
TraceSemaphore($"posix-init address=0x{semaphoreAddress:X16} handle=0x{handle:X8} count={initialCount}");
}
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
}
[SysAbiExport(
Nid = "GEnUkDZoUwY",
ExportName = "scePthreadSemInit",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadSemInit(CpuContext ctx)
{
// scePthreadSemInit(sem, flag, value, name) seems to only support private semaphores
if (ctx[CpuRegister.Rsi] != 0)
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
return PosixSemInit(ctx);
}
[SysAbiExport(
Nid = "YCV5dGGBcCo",
ExportName = "sem_wait",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PosixSemWait(CpuContext ctx)
{
if (!TryGetPosixSemaphoreHandle(ctx, ctx[CpuRegister.Rdi], out var handle))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
ctx[CpuRegister.Rdi] = handle;
ctx[CpuRegister.Rsi] = 1;
ctx[CpuRegister.Rdx] = 0;
return KernelWaitSema(ctx);
}
[SysAbiExport(
Nid = "C36iRE0F5sE",
ExportName = "scePthreadSemWait",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadSemWait(CpuContext ctx) => PosixSemWait(ctx);
[SysAbiExport(
Nid = "WBWzsRifCEA",
ExportName = "sem_trywait",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PosixSemTryWait(CpuContext ctx)
{
if (!TryGetPosixSemaphoreHandle(ctx, ctx[CpuRegister.Rdi], out var handle))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
ctx[CpuRegister.Rdi] = handle;
ctx[CpuRegister.Rsi] = 1;
return KernelPollSema(ctx, handle, 1);
}
[SysAbiExport(
Nid = "H2a+IN9TP0E",
ExportName = "scePthreadSemTrywait",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadSemTryWait(CpuContext ctx)
{
var result = PosixSemTryWait(ctx);
return result == (int)OrbisGen2Result.ORBIS_GEN2_ERROR_BUSY
? SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_TRY_AGAIN)
: result;
}
[SysAbiExport(
Nid = "w5IHyvahg-o",
ExportName = "sem_timedwait",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PosixSemTimedWait(CpuContext ctx)
{
var timeoutAddress = ctx[CpuRegister.Rsi];
if (!TryGetPosixSemaphoreHandle(ctx, ctx[CpuRegister.Rdi], out var handle))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
ctx[CpuRegister.Rdi] = handle;
ctx[CpuRegister.Rsi] = 1;
ctx[CpuRegister.Rdx] = timeoutAddress;
return KernelWaitSema(ctx);
}
[SysAbiExport(
Nid = "IKP8typ0QUk",
ExportName = "sem_post",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PosixSemPost(CpuContext ctx)
{
if (!TryGetPosixSemaphoreHandle(ctx, ctx[CpuRegister.Rdi], out var handle))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
ctx[CpuRegister.Rdi] = handle;
ctx[CpuRegister.Rsi] = 1;
return KernelSignalSema(ctx, handle, 1);
}
[SysAbiExport(
Nid = "aishVAiFaYM",
ExportName = "scePthreadSemPost",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadSemPost(CpuContext ctx) => PosixSemPost(ctx);
[SysAbiExport(
Nid = "Bq+LRV-N6Hk",
ExportName = "sem_getvalue",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PosixSemGetValue(CpuContext ctx)
{
var semaphoreAddress = ctx[CpuRegister.Rdi];
var valueAddress = ctx[CpuRegister.Rsi];
if (valueAddress == 0 ||
!TryGetPosixSemaphoreHandle(ctx, semaphoreAddress, out var handle) ||
!_semaphores.TryGetValue(handle, out var semaphore))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
int count;
lock (semaphore.Gate)
{
count = semaphore.Count;
}
return TryWriteUInt32(ctx, valueAddress, unchecked((uint)count))
? SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK)
: SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
[SysAbiExport(
Nid = "cDW233RAwWo",
ExportName = "sem_destroy",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PosixSemDestroy(CpuContext ctx)
{
var semaphoreAddress = ctx[CpuRegister.Rdi];
if (!TryGetPosixSemaphoreHandle(ctx, semaphoreAddress, out var handle))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
ctx[CpuRegister.Rdi] = handle;
var result = KernelDeleteSema(ctx);
if (result == (int)OrbisGen2Result.ORBIS_GEN2_OK)
{
_ = TryWriteUInt32(ctx, semaphoreAddress, 0);
}
return result;
}
[SysAbiExport(
Nid = "Vwc+L05e6oE",
ExportName = "scePthreadSemDestroy",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadSemDestroy(CpuContext ctx) => PosixSemDestroy(ctx);
private static bool TryGetPosixSemaphoreHandle(CpuContext ctx, ulong semaphoreAddress, out uint handle)
{
handle = 0;
return semaphoreAddress != 0 &&
TryReadUInt32(ctx, semaphoreAddress, out handle) &&
handle != 0;
}
private static int SetReturn(CpuContext ctx, OrbisGen2Result result)
{
var value = (int)result;
ctx[CpuRegister.Rax] = unchecked((ulong)value);
return value;
}
private static bool TryReadUInt32(CpuContext ctx, ulong address, out uint value)
{
Span<byte> buffer = stackalloc byte[sizeof(uint)];
if (!ctx.Memory.TryRead(address, buffer))
{
value = 0;
return false;
}
value = BinaryPrimitives.ReadUInt32LittleEndian(buffer);
return true;
}
private static bool TryWriteUInt32(CpuContext ctx, ulong address, uint value)
{
Span<byte> buffer = stackalloc byte[sizeof(uint)];
BinaryPrimitives.WriteUInt32LittleEndian(buffer, value);
return ctx.Memory.TryWrite(address, buffer);
}
private static bool TryReadNullTerminatedUtf8(CpuContext ctx, ulong address, int maxLength, out string value)
{
value = string.Empty;
if (address == 0 || maxLength <= 0)
{
return false;
}
var bytes = new byte[Math.Min(maxLength, 4096)];
Span<byte> current = stackalloc byte[1];
for (var i = 0; i < bytes.Length; i++)
{
if (!ctx.Memory.TryRead(address + (ulong)i, current))
{
return false;
}
if (current[0] == 0)
{
value = Encoding.UTF8.GetString(bytes, 0, i);
return true;
}
bytes[i] = current[0];
}
value = Encoding.UTF8.GetString(bytes);
return true;
}
// Call sites must check this before building the interpolated message; the trace
// strings would otherwise be allocated on every semaphore op even with tracing off.
private static readonly bool _traceSema =
string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_SEMA"), "1", StringComparison.Ordinal);
private static void TraceSemaphore(string message)
{
if (!_traceSema)
{
return;
}
Console.Error.WriteLine($"[LOADER][TRACE] sema.{message}");
}
private static string FormatCallSite(CpuContext ctx)
{
_ = ctx.TryReadUInt64(ctx[CpuRegister.Rsp], out var returnAddress);
return $"guest=0x{GuestThreadExecution.CurrentGuestThreadHandle:X16} ret=0x{returnAddress:X16}";
}
}