mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-25 20:28:48 +08:00
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`.
This commit is contained in:
@@ -67,6 +67,13 @@ public static class KernelMemoryCompatExports
|
|||||||
private const int Einval = 22;
|
private const int Einval = 22;
|
||||||
private const int Erange = 34;
|
private const int Erange = 34;
|
||||||
private const int Struncate = 80;
|
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 nuint DefaultLibcHeapAlignment = 16;
|
||||||
private const ushort KernelStatModeDirectory = 0x41FF;
|
private const ushort KernelStatModeDirectory = 0x41FF;
|
||||||
private const ushort KernelStatModeRegular = 0x81FF;
|
private const ushort KernelStatModeRegular = 0x81FF;
|
||||||
@@ -1993,23 +2000,55 @@ public static class KernelMemoryCompatExports
|
|||||||
LibraryName = "libKernel")]
|
LibraryName = "libKernel")]
|
||||||
public static int ClockGettime(CpuContext ctx)
|
public static int ClockGettime(CpuContext ctx)
|
||||||
{
|
{
|
||||||
|
var clockId = unchecked((int)ctx[CpuRegister.Rdi]);
|
||||||
var timespecAddress = ctx[CpuRegister.Rsi];
|
var timespecAddress = ctx[CpuRegister.Rsi];
|
||||||
if (timespecAddress == 0)
|
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;
|
long seconds;
|
||||||
var seconds = now.ToUnixTimeSeconds();
|
long nanoseconds;
|
||||||
var nanoseconds = (now.Ticks % TimeSpan.TicksPerSecond) * 100;
|
switch (clockId)
|
||||||
if (!ctx.TryWriteUInt64(timespecAddress, unchecked((ulong)seconds)) ||
|
|
||||||
!ctx.TryWriteUInt64(timespecAddress + sizeof(long), unchecked((ulong)nanoseconds)))
|
|
||||||
{
|
{
|
||||||
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<byte> 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;
|
ctx[CpuRegister.Rax] = 0;
|
||||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
[SysAbiExport(
|
[SysAbiExport(
|
||||||
|
|||||||
@@ -194,13 +194,14 @@ public static class KernelRuntimeCompatExports
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var elapsedTicks = Stopwatch.GetTimestamp() - _processStartCounter;
|
GetProcessMonotonicTime(out seconds, out nanoseconds);
|
||||||
seconds = elapsedTicks / Stopwatch.Frequency;
|
|
||||||
nanoseconds = (elapsedTicks % Stopwatch.Frequency) * 1_000_000_000L / Stopwatch.Frequency;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ctx.TryWriteUInt64(timeAddress, unchecked((ulong)seconds)) ||
|
Span<byte> timespecBuffer = stackalloc byte[16];
|
||||||
!ctx.TryWriteUInt64(timeAddress + sizeof(long), unchecked((ulong)nanoseconds)))
|
BinaryPrimitives.WriteInt64LittleEndian(timespecBuffer, seconds);
|
||||||
|
BinaryPrimitives.WriteInt64LittleEndian(timespecBuffer[sizeof(long)..], nanoseconds);
|
||||||
|
|
||||||
|
if (!ctx.Memory.TryWrite(timeAddress, timespecBuffer))
|
||||||
{
|
{
|
||||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||||
}
|
}
|
||||||
@@ -617,6 +618,13 @@ public static class KernelRuntimeCompatExports
|
|||||||
return address != 0 && ctx.TryWriteInt32(address, value);
|
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(
|
[SysAbiExport(
|
||||||
Nid = "bnZxYgAFeA0",
|
Nid = "bnZxYgAFeA0",
|
||||||
ExportName = "sceKernelGetSanitizerNewReplaceExternal",
|
ExportName = "sceKernelGetSanitizerNewReplaceExternal",
|
||||||
@@ -1713,7 +1721,7 @@ public static class KernelRuntimeCompatExports
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Span<byte> stub = stackalloc byte[]
|
ReadOnlySpan<byte> stub = stackalloc byte[]
|
||||||
{
|
{
|
||||||
0x0F, 0x31,
|
0x0F, 0x31,
|
||||||
0x48, 0xC1, 0xE2, 0x20,
|
0x48, 0xC1, 0xE2, 0x20,
|
||||||
@@ -1721,7 +1729,14 @@ public static class KernelRuntimeCompatExports
|
|||||||
0xC3,
|
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<RdtscDelegate>(stubAddress);
|
return Marshal.GetDelegateForFunctionPointer<RdtscDelegate>(stubAddress);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
|
|||||||
Reference in New Issue
Block a user