[kernel] Guest-thread blocking for pthread_mutex_lock is currently disabled

This commit is contained in:
ParantezTech
2026-06-28 23:45:05 +03:00
parent 4ab614e68a
commit a5172fd2c0
10 changed files with 1148 additions and 205 deletions
@@ -65,6 +65,7 @@ public static class KernelMemoryCompatExports
private const uint HostPageExecuteWriteCopy = 0x80;
private const uint HostPageGuard = 0x100;
private const int Enomem = 12;
private const int Efault = 14;
private const int Einval = 22;
private const int Erange = 34;
private const int Struncate = 80;
@@ -96,10 +97,14 @@ public static class KernelMemoryCompatExports
private static readonly object _libcAllocGate = new();
private static readonly object _memoryGate = new();
private static readonly object _tlsGate = new();
private static readonly object _ioTraceGate = new();
private static readonly object _statCacheGate = 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 readonly HashSet<string> _tracedStatResults = new(StringComparer.Ordinal);
private static readonly HashSet<string> _negativeStatCache = new(StringComparer.OrdinalIgnoreCase);
private static long _nextFileDescriptor = 2;
private static ulong _nextPhysicalAddress;
private static ulong _nextVirtualAddress;
@@ -114,6 +119,8 @@ public static class KernelMemoryCompatExports
private static int _hostMemoryWriteFallbackCount;
private static int _hostMemoryReadFallbackCount;
private static int _nullWcscpyRecoveryCount;
private static string? _cachedApp0Root;
private static string? _cachedDownload0Root;
[StructLayout(LayoutKind.Sequential)]
private struct MemoryBasicInformation
@@ -1236,6 +1243,12 @@ public static class KernelMemoryCompatExports
var mode = ResolveOpenMode(flags, access);
try
{
if (IsMutatingOpen(flags) && IsReadOnlyGuestMutationPath(guestPath))
{
LogOpenTrace($"_open readonly path='{guestPath}' host='{hostPath}' flags=0x{flags:X8}");
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_PERMISSION_DENIED;
}
var wantsDirectory = (flags & O_DIRECTORY) != 0;
if (wantsDirectory || Directory.Exists(hostPath))
{
@@ -1280,6 +1293,11 @@ public static class KernelMemoryCompatExports
_openFiles[fd] = stream;
}
if (IsMutatingOpen(flags))
{
InvalidateNegativeStatCacheForPathAndAncestors(guestPath);
}
LogOpenTrace($"_open file path='{guestPath}' host='{hostPath}' flags=0x{flags:X8} fd={fd}");
ctx[CpuRegister.Rax] = unchecked((ulong)fd);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
@@ -1334,11 +1352,30 @@ public static class KernelMemoryCompatExports
}
var hostPath = ResolveGuestPath(guestPath);
if (!TryWriteHostPathStat(ctx, statAddress, hostPath))
var statCacheKey = GetNegativeStatCacheKey(guestPath);
if (statCacheKey is not null && IsNegativeStatCached(statCacheKey))
{
LogUniqueStatTrace(guestPath, hostPath, found: false);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
if (!TryWriteHostPathStat(ctx, statAddress, hostPath))
{
if (statCacheKey is not null)
{
AddNegativeStatCache(statCacheKey);
}
LogUniqueStatTrace(guestPath, hostPath, found: false);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
if (statCacheKey is not null)
{
RemoveNegativeStatCache(statCacheKey);
}
LogUniqueStatTrace(guestPath, hostPath, found: true);
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
@@ -1356,13 +1393,22 @@ public static class KernelMemoryCompatExports
var sizesAddress = ctx[CpuRegister.Rcx];
if (pathListAddress == 0 || count == 0 || sizesAddress == 0 || count > 1024)
{
KernelRuntimeCompatExports.TrySetErrno(ctx, Einval);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
for (ulong i = 0; i < count; i++)
{
if (idsAddress != 0 &&
!TryWriteUInt32Compat(ctx, idsAddress + (i * sizeof(uint)), uint.MaxValue))
{
KernelRuntimeCompatExports.TrySetErrno(ctx, Efault);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
if (!TryResolveAprFilepath(ctx, pathListAddress, i, out var guestPath))
{
KernelRuntimeCompatExports.TrySetErrno(ctx, Efault);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
@@ -1370,6 +1416,7 @@ public static class KernelMemoryCompatExports
if (!TryGetAprFileSize(hostPath, out var fileSize))
{
LogIoTrace("apr_resolve", guestPath, $"host='{hostPath}' index={i} count={count} result=not_found");
KernelRuntimeCompatExports.TrySetErrno(ctx, 2);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
@@ -1379,11 +1426,13 @@ public static class KernelMemoryCompatExports
if (idsAddress != 0 &&
!TryWriteUInt32Compat(ctx, idsAddress + (i * sizeof(uint)), fileId))
{
KernelRuntimeCompatExports.TrySetErrno(ctx, Efault);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
if (!TryWriteUInt64Compat(ctx, sizesAddress + (i * sizeof(ulong)), fileSize))
{
KernelRuntimeCompatExports.TrySetErrno(ctx, Efault);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
}
@@ -1415,6 +1464,170 @@ public static class KernelMemoryCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "AUXVxWeJU-A",
ExportName = "sceKernelUnlink",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelUnlink(CpuContext ctx)
{
var pathAddress = ctx[CpuRegister.Rdi];
if (pathAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
if (!TryReadNullTerminatedUtf8(ctx, pathAddress, MaxGuestStringLength, out var guestPath))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
var hostPath = ResolveGuestPath(guestPath);
if (IsReadOnlyGuestMutationPath(guestPath))
{
LogOpenTrace($"unlink readonly path='{guestPath}' host='{hostPath}'");
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_PERMISSION_DENIED;
}
try
{
if (Directory.Exists(hostPath))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_PERMISSION_DENIED;
}
if (!File.Exists(hostPath))
{
AddNegativeStatCacheForGuestPath(guestPath);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
File.Delete(hostPath);
InvalidateNegativeStatCacheForPathAndAncestors(guestPath);
AddNegativeStatCacheForGuestPath(guestPath);
LogOpenTrace($"unlink path='{guestPath}' host='{hostPath}'");
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
catch (UnauthorizedAccessException)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_PERMISSION_DENIED;
}
catch (IOException)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_BUSY;
}
}
[SysAbiExport(
Nid = "1-LFLmRFxxM",
ExportName = "sceKernelMkdir",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelMkdir(CpuContext ctx)
{
var pathAddress = ctx[CpuRegister.Rdi];
if (pathAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
if (!TryReadNullTerminatedUtf8(ctx, pathAddress, MaxGuestStringLength, out var guestPath))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
var hostPath = ResolveGuestPath(guestPath);
if (IsReadOnlyGuestMutationPath(guestPath))
{
LogOpenTrace($"mkdir readonly path='{guestPath}' host='{hostPath}'");
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_PERMISSION_DENIED;
}
try
{
if (File.Exists(hostPath) || Directory.Exists(hostPath))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_ALREADY_EXISTS;
}
var parentDirectory = Path.GetDirectoryName(hostPath);
if (string.IsNullOrWhiteSpace(parentDirectory) || !Directory.Exists(parentDirectory))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
Directory.CreateDirectory(hostPath);
if (!Directory.Exists(hostPath))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
InvalidateNegativeStatCacheForPathAndAncestors(guestPath);
LogOpenTrace($"mkdir path='{guestPath}' host='{hostPath}'");
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
catch (UnauthorizedAccessException)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_PERMISSION_DENIED;
}
catch (IOException)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
}
[SysAbiExport(
Nid = "naInUjYt3so",
ExportName = "sceKernelRmdir",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelRmdir(CpuContext ctx)
{
var pathAddress = ctx[CpuRegister.Rdi];
if (pathAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
if (!TryReadNullTerminatedUtf8(ctx, pathAddress, MaxGuestStringLength, out var guestPath))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
var hostPath = ResolveGuestPath(guestPath);
if (IsReadOnlyGuestMutationPath(guestPath))
{
LogOpenTrace($"rmdir readonly path='{guestPath}' host='{hostPath}'");
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_PERMISSION_DENIED;
}
try
{
if (!Directory.Exists(hostPath))
{
AddNegativeStatCacheForGuestPath(guestPath);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
Directory.Delete(hostPath, recursive: false);
InvalidateNegativeStatCacheForPathAndAncestors(guestPath);
AddNegativeStatCacheForGuestPath(guestPath);
LogOpenTrace($"rmdir path='{guestPath}' host='{hostPath}'");
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
catch (UnauthorizedAccessException)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_PERMISSION_DENIED;
}
catch (IOException)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_BUSY;
}
}
private static int KernelCloseCore(CpuContext ctx, int fd)
{
if (fd is 0 or 1 or 2)
@@ -3798,57 +4011,72 @@ public static class KernelMemoryCompatExports
return guestPath;
}
var devlogAppRoot = ResolveDevlogAppRoot();
if (guestPath.StartsWith("/devlog/app/", StringComparison.OrdinalIgnoreCase))
{
var relative = NormalizeMountRelativePath(guestPath["/devlog/app/".Length..]);
return Path.Combine(devlogAppRoot, relative);
return Path.Combine(ResolveDevlogAppRoot(), relative);
}
if (guestPath.StartsWith("devlog/app/", StringComparison.OrdinalIgnoreCase))
{
var relative = NormalizeMountRelativePath(guestPath["devlog/app/".Length..]);
return Path.Combine(devlogAppRoot, relative);
return Path.Combine(ResolveDevlogAppRoot(), relative);
}
if (string.Equals(guestPath, "/devlog/app", StringComparison.OrdinalIgnoreCase) ||
string.Equals(guestPath, "devlog/app", StringComparison.OrdinalIgnoreCase))
{
return devlogAppRoot;
return ResolveDevlogAppRoot();
}
var temp0Root = ResolveTemp0Root();
if (guestPath.StartsWith("/temp0/", StringComparison.OrdinalIgnoreCase))
{
var relative = NormalizeMountRelativePath(guestPath["/temp0/".Length..]);
return Path.Combine(temp0Root, relative);
return Path.Combine(ResolveTemp0Root(), relative);
}
if (string.Equals(guestPath, "/temp0", StringComparison.OrdinalIgnoreCase))
{
return temp0Root;
return ResolveTemp0Root();
}
if (guestPath.StartsWith("/download0/", StringComparison.OrdinalIgnoreCase))
{
var relative = NormalizeMountRelativePath(guestPath["/download0/".Length..]);
return Path.Combine(ResolveDownload0Root(), relative);
}
if (guestPath.StartsWith("download0/", StringComparison.OrdinalIgnoreCase))
{
var relative = NormalizeMountRelativePath(guestPath["download0/".Length..]);
return Path.Combine(ResolveDownload0Root(), relative);
}
if (string.Equals(guestPath, "/download0", StringComparison.OrdinalIgnoreCase) ||
string.Equals(guestPath, "download0", StringComparison.OrdinalIgnoreCase))
{
return ResolveDownload0Root();
}
var hostappRoot = ResolveHostappRoot();
if (guestPath.StartsWith("/hostapp/", StringComparison.OrdinalIgnoreCase))
{
var relative = NormalizeMountRelativePath(guestPath["/hostapp/".Length..]);
return Path.Combine(hostappRoot, relative);
return Path.Combine(ResolveHostappRoot(), relative);
}
if (guestPath.StartsWith("hostapp/", StringComparison.OrdinalIgnoreCase))
{
var relative = NormalizeMountRelativePath(guestPath["hostapp/".Length..]);
return Path.Combine(hostappRoot, relative);
return Path.Combine(ResolveHostappRoot(), relative);
}
if (string.Equals(guestPath, "/hostapp", StringComparison.OrdinalIgnoreCase) ||
string.Equals(guestPath, "hostapp", StringComparison.OrdinalIgnoreCase))
{
return hostappRoot;
return ResolveHostappRoot();
}
var app0Root = Environment.GetEnvironmentVariable("SHARPEMU_APP0_DIR");
var app0Root = ResolveApp0Root();
if (!string.IsNullOrWhiteSpace(app0Root))
{
if (string.Equals(guestPath, "/app0", StringComparison.OrdinalIgnoreCase) ||
@@ -3881,6 +4109,24 @@ public static class KernelMemoryCompatExports
return guestPath;
}
private static string? ResolveApp0Root()
{
var cached = Volatile.Read(ref _cachedApp0Root);
if (!string.IsNullOrWhiteSpace(cached))
{
return cached;
}
var configured = Environment.GetEnvironmentVariable("SHARPEMU_APP0_DIR");
if (string.IsNullOrWhiteSpace(configured))
{
return null;
}
Interlocked.CompareExchange(ref _cachedApp0Root, configured, null);
return _cachedApp0Root;
}
private static string NormalizeMountRelativePath(string relativePath)
{
return relativePath
@@ -3931,6 +4177,32 @@ public static class KernelMemoryCompatExports
return root;
}
private static string ResolveDownload0Root()
{
var cached = Volatile.Read(ref _cachedDownload0Root);
if (!string.IsNullOrWhiteSpace(cached))
{
return cached;
}
const string download0VariableName = "SHARPEMU_DOWNLOAD0_DIR";
var configuredRoot = Environment.GetEnvironmentVariable(download0VariableName);
string root;
if (!string.IsNullOrWhiteSpace(configuredRoot))
{
root = Path.GetFullPath(configuredRoot);
}
else
{
root = Path.Combine(GetPerAppWritableRoot(), "download0");
Environment.SetEnvironmentVariable(download0VariableName, root);
}
Directory.CreateDirectory(root);
Interlocked.CompareExchange(ref _cachedDownload0Root, root, null);
return _cachedDownload0Root;
}
private static string ResolveHostappRoot()
{
const string hostappVariableName = "SHARPEMU_HOSTAPP_DIR";
@@ -3950,6 +4222,22 @@ public static class KernelMemoryCompatExports
return root;
}
private static string GetPerAppWritableRoot()
{
var app0Root = Environment.GetEnvironmentVariable("SHARPEMU_APP0_DIR");
var appName = string.IsNullOrWhiteSpace(app0Root)
? "default"
: Path.GetFileName(Path.TrimEndingDirectorySeparator(app0Root));
if (string.IsNullOrWhiteSpace(appName))
{
appName = "default";
}
var invalidChars = Path.GetInvalidFileNameChars();
appName = new string(appName.Select(ch => invalidChars.Contains(ch) ? '_' : ch).ToArray());
return Path.Combine(Path.GetTempPath(), "SharpEmu", appName);
}
private static void EnsureOpenParentDirectoryExists(string guestPath, string hostPath, int flags)
{
if (string.IsNullOrWhiteSpace(hostPath))
@@ -3973,6 +4261,17 @@ public static class KernelMemoryCompatExports
}
}
private static bool IsMutatingOpen(int flags) =>
(flags & (O_WRONLY | O_RDWR | O_CREAT | O_TRUNC | O_APPEND)) != 0;
private static bool IsReadOnlyGuestMutationPath(string guestPath)
{
var normalized = NormalizeGuestStatCachePath(guestPath);
return normalized is not null &&
(string.Equals(normalized, "/app0", StringComparison.OrdinalIgnoreCase) ||
normalized.StartsWith("/app0/", StringComparison.OrdinalIgnoreCase));
}
private static bool TryReadCString(CpuContext ctx, ulong address, ulong maxLength, out byte[] bytes)
{
return TryReadBytesUntilNull(ctx, address, maxLength, 1_048_576, out bytes);
@@ -4350,7 +4649,7 @@ public static class KernelMemoryCompatExports
return true;
}
private static bool TryReadUInt64Compat(CpuContext ctx, ulong address, out ulong value)
internal static bool TryReadUInt64Compat(CpuContext ctx, ulong address, out ulong value)
{
Span<byte> bytes = stackalloc byte[sizeof(ulong)];
if (!TryReadCompat(ctx, address, bytes))
@@ -4397,7 +4696,7 @@ public static class KernelMemoryCompatExports
return TryWriteCompat(ctx, address, bytes);
}
private static bool TryWriteUInt64Compat(CpuContext ctx, ulong address, ulong value)
internal static bool TryWriteUInt64Compat(CpuContext ctx, ulong address, ulong value)
{
Span<byte> bytes = stackalloc byte[sizeof(ulong)];
BinaryPrimitives.WriteUInt64LittleEndian(bytes, value);
@@ -5345,19 +5644,20 @@ public static class KernelMemoryCompatExports
size = 0;
try
{
if (Directory.Exists(hostPath))
var fileInfo = new FileInfo(hostPath);
if (fileInfo.Exists)
{
size = 65536;
var length = fileInfo.Length;
size = length < 0 ? 0UL : unchecked((ulong)length);
return true;
}
if (!File.Exists(hostPath))
if (!new DirectoryInfo(hostPath).Exists)
{
return false;
}
var length = new FileInfo(hostPath).Length;
size = length < 0 ? 0UL : unchecked((ulong)length);
size = 65536;
return true;
}
catch
@@ -5542,6 +5842,120 @@ public static class KernelMemoryCompatExports
Console.Error.WriteLine($"[LOADER][TRACE] {operation} path='{path}' {detail}");
}
private static void LogUniqueStatTrace(string guestPath, string hostPath, bool found)
{
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_IO"), "1", StringComparison.Ordinal))
{
return;
}
var result = found ? "found" : "not_found";
lock (_ioTraceGate)
{
if (!_tracedStatResults.Add($"{result}\0{guestPath}"))
{
return;
}
}
LogIoTrace("stat", guestPath, $"host='{hostPath}' result={result}");
}
private static string? GetNegativeStatCacheKey(string guestPath)
{
var normalized = NormalizeGuestStatCachePath(guestPath);
return IsReadOnlyGuestStatPath(normalized) ? normalized : null;
}
private static string? NormalizeGuestStatCachePath(string guestPath)
{
var normalized = guestPath.Replace('\\', '/').TrimEnd('/');
if (normalized.Length == 0)
{
return null;
}
if (normalized[0] != '/')
{
normalized = "/" + normalized;
}
return normalized;
}
private static bool IsReadOnlyGuestStatPath(string? normalizedGuestPath) =>
normalizedGuestPath is not null &&
(string.Equals(normalizedGuestPath, "/app0", StringComparison.OrdinalIgnoreCase) ||
normalizedGuestPath.StartsWith("/app0/", StringComparison.OrdinalIgnoreCase) ||
string.Equals(normalizedGuestPath, "/hostapp", StringComparison.OrdinalIgnoreCase) ||
normalizedGuestPath.StartsWith("/hostapp/", StringComparison.OrdinalIgnoreCase) ||
string.Equals(normalizedGuestPath, "/devlog/app", StringComparison.OrdinalIgnoreCase) ||
normalizedGuestPath.StartsWith("/devlog/app/", StringComparison.OrdinalIgnoreCase) ||
string.Equals(normalizedGuestPath, "/temp0", StringComparison.OrdinalIgnoreCase) ||
normalizedGuestPath.StartsWith("/temp0/", StringComparison.OrdinalIgnoreCase) ||
string.Equals(normalizedGuestPath, "/download0", StringComparison.OrdinalIgnoreCase) ||
normalizedGuestPath.StartsWith("/download0/", StringComparison.OrdinalIgnoreCase));
private static bool IsNegativeStatCached(string cacheKey)
{
lock (_statCacheGate)
{
return _negativeStatCache.Contains(cacheKey);
}
}
private static void AddNegativeStatCache(string cacheKey)
{
lock (_statCacheGate)
{
_negativeStatCache.Add(cacheKey);
}
}
private static void RemoveNegativeStatCache(string cacheKey)
{
lock (_statCacheGate)
{
_negativeStatCache.Remove(cacheKey);
}
}
private static void AddNegativeStatCacheForGuestPath(string guestPath)
{
var cacheKey = GetNegativeStatCacheKey(guestPath);
if (cacheKey is not null)
{
AddNegativeStatCache(cacheKey);
}
}
private static void InvalidateNegativeStatCacheForPathAndAncestors(string guestPath)
{
var normalized = NormalizeGuestStatCachePath(guestPath);
if (normalized is null)
{
return;
}
lock (_statCacheGate)
{
var current = normalized;
while (true)
{
_negativeStatCache.Remove(current);
var slash = current.LastIndexOf('/');
if (slash <= 0)
{
break;
}
current = current[..slash];
}
_negativeStatCache.Remove("/");
}
}
private static string PreviewIoBytes(byte[] buffer, int count, int maxBytes)
{
if (count <= 0)