From 65a40773fa733a722e13553eea30b1418a218925 Mon Sep 17 00:00:00 2001 From: Mike Saito Date: Sat, 11 Jul 2026 17:32:48 +0300 Subject: [PATCH] core: expand clock_gettime fast clocks, unify timespec writes, fix NULL EINVAL (#62) Refactored parts of the time subsystem to improve POSIX/Orbis compliance and secure guest memory boundaries. **1. POSIX `clock_gettime` updates:** - Added `CLOCK_REALTIME_FAST` (10) and `CLOCK_MONOTONIC_FAST` (12) support for games using FreeBSD fast clock extensions. - Fixed `NULL` pointer handling for `timespecAddress == 0`. It now returns `-1` with `EINVAL` (22) instead of `EFAULT` to match Orbis runtime behavior. - Invalid `clock_id` values now properly fallback to `default` -> `-1` + `EINVAL`. **2. Memory safety & monotonic tracking:** - Replaced dual 8-byte scalar writes in both POSIX `clock_gettime` and Orbis `sceKernelClockGettime` with a single 16-byte write via `stackalloc byte[16]` and `BinaryPrimitives`. This prevents partial memory corruption at page boundaries. - Bad non-NULL guest addresses now fail cleanly as `EFAULT` (POSIX) or `MEMORY_FAULT` (Orbis). - Extracted core monotonic math into `GetProcessMonotonicTime()` in `KernelRuntimeCompatExports.cs` so both clock paths share the exact same `_processStartCounter` base. **3. Host RDTSC optimization:** - Fixed `CreateRdtscReader()` to copy stack-allocated opcode bytes into host `VirtualAlloc` memory via `unsafe { Buffer.MemoryCopy(...) }`. This completely gets rid of the redundant `.ToArray()` allocation on the hot path. **Out of scope:** `sceKernelGettimeofday` / POSIX `gettimeofday` partial-write hardening; stricter clock validation in `sceKernelClockGettime`. --- .../Kernel/KernelMemoryCompatExports.cs | 55 ++++++++++++++++--- .../Kernel/KernelRuntimeCompatExports.cs | 29 +++++++--- 2 files changed, 69 insertions(+), 15 deletions(-) diff --git a/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.cs b/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.cs index 4835376..f884a59 100644 --- a/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.cs +++ b/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.cs @@ -67,6 +67,13 @@ public static class KernelMemoryCompatExports private const int Einval = 22; private const int Erange = 34; private const int Struncate = 80; + private const int ClockRealtime = 0; + private const int ClockVirtual = 1; + private const int ClockProf = 2; + private const int ClockMonotonic = 4; + private const int ClockUptime = 5; + private const int ClockRealtimeFast = 10; + private const int ClockMonotonicFast = 12; private const nuint DefaultLibcHeapAlignment = 16; private const ushort KernelStatModeDirectory = 0x41FF; private const ushort KernelStatModeRegular = 0x81FF; @@ -1993,23 +2000,55 @@ public static class KernelMemoryCompatExports LibraryName = "libKernel")] public static int ClockGettime(CpuContext ctx) { + var clockId = unchecked((int)ctx[CpuRegister.Rdi]); var timespecAddress = ctx[CpuRegister.Rsi]; if (timespecAddress == 0) { - return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; + KernelRuntimeCompatExports.TrySetErrno(ctx, Einval); + ctx[CpuRegister.Rax] = unchecked((ulong)-1); + return -1; } - var now = DateTimeOffset.UtcNow; - var seconds = now.ToUnixTimeSeconds(); - var nanoseconds = (now.Ticks % TimeSpan.TicksPerSecond) * 100; - if (!ctx.TryWriteUInt64(timespecAddress, unchecked((ulong)seconds)) || - !ctx.TryWriteUInt64(timespecAddress + sizeof(long), unchecked((ulong)nanoseconds))) + long seconds; + long nanoseconds; + switch (clockId) { - return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + case ClockRealtime: + case ClockRealtimeFast: + case ClockVirtual: + case ClockProf: + { + var now = DateTimeOffset.UtcNow; + seconds = now.ToUnixTimeSeconds(); + nanoseconds = (now.Ticks % TimeSpan.TicksPerSecond) * 100; + break; + } + + case ClockMonotonic: + case ClockMonotonicFast: + case ClockUptime: + KernelRuntimeCompatExports.GetProcessMonotonicTime(out seconds, out nanoseconds); + break; + + default: + KernelRuntimeCompatExports.TrySetErrno(ctx, Einval); + ctx[CpuRegister.Rax] = unchecked((ulong)-1); + return -1; + } + + Span timespecBuffer = stackalloc byte[16]; + BinaryPrimitives.WriteInt64LittleEndian(timespecBuffer, seconds); + BinaryPrimitives.WriteInt64LittleEndian(timespecBuffer[sizeof(long)..], nanoseconds); + + if (!ctx.Memory.TryWrite(timespecAddress, timespecBuffer)) + { + KernelRuntimeCompatExports.TrySetErrno(ctx, Efault); + ctx[CpuRegister.Rax] = unchecked((ulong)-1); + return -1; } ctx[CpuRegister.Rax] = 0; - return (int)OrbisGen2Result.ORBIS_GEN2_OK; + return 0; } [SysAbiExport( diff --git a/src/SharpEmu.Libs/Kernel/KernelRuntimeCompatExports.cs b/src/SharpEmu.Libs/Kernel/KernelRuntimeCompatExports.cs index 0b6efaa..54ea069 100644 --- a/src/SharpEmu.Libs/Kernel/KernelRuntimeCompatExports.cs +++ b/src/SharpEmu.Libs/Kernel/KernelRuntimeCompatExports.cs @@ -194,13 +194,14 @@ public static class KernelRuntimeCompatExports } else { - var elapsedTicks = Stopwatch.GetTimestamp() - _processStartCounter; - seconds = elapsedTicks / Stopwatch.Frequency; - nanoseconds = (elapsedTicks % Stopwatch.Frequency) * 1_000_000_000L / Stopwatch.Frequency; + GetProcessMonotonicTime(out seconds, out nanoseconds); } - if (!ctx.TryWriteUInt64(timeAddress, unchecked((ulong)seconds)) || - !ctx.TryWriteUInt64(timeAddress + sizeof(long), unchecked((ulong)nanoseconds))) + Span timespecBuffer = stackalloc byte[16]; + BinaryPrimitives.WriteInt64LittleEndian(timespecBuffer, seconds); + BinaryPrimitives.WriteInt64LittleEndian(timespecBuffer[sizeof(long)..], nanoseconds); + + if (!ctx.Memory.TryWrite(timeAddress, timespecBuffer)) { return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; } @@ -617,6 +618,13 @@ public static class KernelRuntimeCompatExports return address != 0 && ctx.TryWriteInt32(address, value); } + internal static void GetProcessMonotonicTime(out long seconds, out long nanoseconds) + { + var elapsedTicks = Stopwatch.GetTimestamp() - _processStartCounter; + seconds = elapsedTicks / Stopwatch.Frequency; + nanoseconds = (elapsedTicks % Stopwatch.Frequency) * 1_000_000_000L / Stopwatch.Frequency; + } + [SysAbiExport( Nid = "bnZxYgAFeA0", ExportName = "sceKernelGetSanitizerNewReplaceExternal", @@ -1713,7 +1721,7 @@ public static class KernelRuntimeCompatExports return null; } - Span stub = stackalloc byte[] + ReadOnlySpan stub = stackalloc byte[] { 0x0F, 0x31, 0x48, 0xC1, 0xE2, 0x20, @@ -1721,7 +1729,14 @@ public static class KernelRuntimeCompatExports 0xC3, }; - Marshal.Copy(stub.ToArray(), 0, stubAddress, stub.Length); + unsafe + { + fixed (byte* src = stub) + { + Buffer.MemoryCopy(src, (void*)stubAddress, stub.Length, stub.Length); + } + } + return Marshal.GetDelegateForFunctionPointer(stubAddress); } catch