mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-30 06:29:46 +08:00
442 lines
14 KiB
C#
442 lines
14 KiB
C#
// Copyright (C) 2026 SharpEmu Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
using SharpEmu.HLE;
|
|
using System.Threading;
|
|
|
|
namespace SharpEmu.Libs.Kernel;
|
|
|
|
public static class KernelExports
|
|
{
|
|
private static int _nextFileDescriptor = 2;
|
|
private static readonly object _cxaGate = new();
|
|
private static readonly List<CxaDestructorEntry> _cxaDestructors = new();
|
|
private static readonly object _coredumpGate = new();
|
|
private static ulong _coredumpHandler;
|
|
private static ulong _coredumpHandlerContext;
|
|
|
|
private readonly record struct CxaDestructorEntry(
|
|
ulong Function,
|
|
ulong Argument,
|
|
ulong ModuleHandle);
|
|
|
|
[SysAbiExport(
|
|
Nid = "WB66evu8bsU",
|
|
ExportName = "sceKernelGetCompiledSdkVersion",
|
|
Target = Generation.Gen4 | Generation.Gen5,
|
|
LibraryName = "libKernel")]
|
|
public static int KernelGetCompiledSdkVersion(CpuContext ctx)
|
|
{
|
|
_ = ctx;
|
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
|
}
|
|
|
|
[SysAbiExport(
|
|
Nid = "8zLSfEfW5AU",
|
|
ExportName = "sceCoredumpRegisterCoredumpHandler",
|
|
Target = Generation.Gen4 | Generation.Gen5,
|
|
LibraryName = "libSceCoredump")]
|
|
public static int CoredumpRegisterHandler(CpuContext ctx)
|
|
{
|
|
lock (_coredumpGate)
|
|
{
|
|
_coredumpHandler = ctx[CpuRegister.Rdi];
|
|
_coredumpHandlerContext = ctx[CpuRegister.Rsi];
|
|
}
|
|
|
|
ctx[CpuRegister.Rax] = 0;
|
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
|
}
|
|
|
|
[SysAbiExport(
|
|
Nid = "uMei1W9uyNo",
|
|
ExportName = "exit",
|
|
Target = Generation.Gen4 | Generation.Gen5,
|
|
LibraryName = "libKernel")]
|
|
public static int Exit(CpuContext ctx)
|
|
{
|
|
var status = unchecked((int)ctx[CpuRegister.Rdi]);
|
|
Console.Error.WriteLine($"[LOADER][INFO] exit(status={status})");
|
|
GuestThreadExecution.RequestCurrentEntryExit("exit", status);
|
|
ctx[CpuRegister.Rax] = unchecked((ulong)status);
|
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
|
}
|
|
|
|
[SysAbiExport(
|
|
Nid = "XKRegsFpEpk",
|
|
ExportName = "catchReturnFromMain",
|
|
Target = Generation.Gen4 | Generation.Gen5,
|
|
LibraryName = "libc")]
|
|
public static int CatchReturnFromMain(CpuContext ctx)
|
|
{
|
|
var status = unchecked((int)ctx[CpuRegister.Rdi]);
|
|
Console.Error.WriteLine($"[LOADER][INFO] catchReturnFromMain(status={status})");
|
|
GuestThreadExecution.RequestCurrentEntryExit("catchReturnFromMain", status);
|
|
ctx[CpuRegister.Rax] = unchecked((ulong)status);
|
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
|
}
|
|
|
|
[SysAbiExport(
|
|
Nid = "bzQExy189ZI",
|
|
ExportName = "_init_env",
|
|
Target = Generation.Gen4 | Generation.Gen5,
|
|
LibraryName = "libc")]
|
|
public static int InitEnv(CpuContext ctx)
|
|
{
|
|
ctx[CpuRegister.Rax] = 0;
|
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
|
}
|
|
|
|
[SysAbiExport(
|
|
Nid = "8G2LB+A3rzg",
|
|
ExportName = "atexit",
|
|
Target = Generation.Gen4 | Generation.Gen5,
|
|
LibraryName = "libc")]
|
|
public static int Atexit(CpuContext ctx)
|
|
{
|
|
ctx[CpuRegister.Rax] = 0;
|
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
|
}
|
|
|
|
[SysAbiExport(
|
|
Nid = "tsvEmnenz48",
|
|
ExportName = "__cxa_atexit",
|
|
Target = Generation.Gen4 | Generation.Gen5,
|
|
LibraryName = "libc")]
|
|
public static int CxaAtexit(CpuContext ctx)
|
|
{
|
|
var destructorFunction = ctx[CpuRegister.Rdi];
|
|
var destructorArgument = ctx[CpuRegister.Rsi];
|
|
var moduleHandle = ctx[CpuRegister.Rdx];
|
|
if (destructorFunction == 0)
|
|
{
|
|
ctx[CpuRegister.Rax] = 0;
|
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
|
}
|
|
|
|
lock (_cxaGate)
|
|
{
|
|
_cxaDestructors.Add(new CxaDestructorEntry(
|
|
destructorFunction,
|
|
destructorArgument,
|
|
moduleHandle));
|
|
}
|
|
|
|
ctx[CpuRegister.Rax] = 0;
|
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
|
}
|
|
|
|
[SysAbiExport(
|
|
Nid = "H2e8t5ScQGc",
|
|
ExportName = "__cxa_finalize",
|
|
Target = Generation.Gen4 | Generation.Gen5,
|
|
LibraryName = "libc")]
|
|
public static int CxaFinalize(CpuContext ctx)
|
|
{
|
|
var moduleHandle = ctx[CpuRegister.Rdi];
|
|
|
|
lock (_cxaGate)
|
|
{
|
|
if (moduleHandle == 0)
|
|
{
|
|
_cxaDestructors.Clear();
|
|
}
|
|
else
|
|
{
|
|
for (var i = _cxaDestructors.Count - 1; i >= 0; i--)
|
|
{
|
|
if (_cxaDestructors[i].ModuleHandle == moduleHandle)
|
|
{
|
|
_cxaDestructors.RemoveAt(i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ctx[CpuRegister.Rax] = 0;
|
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
|
}
|
|
|
|
[SysAbiExport(
|
|
Nid = "kbw4UHHSYy0",
|
|
ExportName = "__pthread_cxa_finalize",
|
|
Target = Generation.Gen4 | Generation.Gen5,
|
|
LibraryName = "libKernel")]
|
|
public static int PthreadCxaFinalize(CpuContext ctx)
|
|
{
|
|
_ = ctx;
|
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
|
}
|
|
|
|
[SysAbiExport(
|
|
Nid = "6Z83sYWFlA8",
|
|
ExportName = "_exit",
|
|
Target = Generation.Gen4 | Generation.Gen5,
|
|
LibraryName = "libKernel")]
|
|
public static int UnderscoreExit(CpuContext ctx)
|
|
{
|
|
_ = ctx;
|
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
|
}
|
|
|
|
[SysAbiExport(
|
|
Nid = "Ac86z8q7T8A",
|
|
ExportName = "sceKernelExitSblock",
|
|
Target = Generation.Gen4 | Generation.Gen5,
|
|
LibraryName = "libKernel")]
|
|
public static int KernelExitSblock(CpuContext ctx)
|
|
{
|
|
_ = ctx;
|
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
|
}
|
|
|
|
[SysAbiExport(
|
|
Nid = "6UgtwV+0zb4",
|
|
ExportName = "scePthreadCreate",
|
|
Target = Generation.Gen4 | Generation.Gen5,
|
|
LibraryName = "libKernel")]
|
|
public static int PthreadCreate(CpuContext ctx)
|
|
{
|
|
var threadIdAddress = ctx[CpuRegister.Rdi];
|
|
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);
|
|
KernelPthreadExtendedCompatExports.GetThreadStartScheduling(
|
|
ctx,
|
|
attrAddress,
|
|
out var priority,
|
|
out var affinityMask);
|
|
KernelPthreadExtendedCompatExports.RegisterThreadStart(
|
|
threadHandle,
|
|
name,
|
|
priority,
|
|
affinityMask);
|
|
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}' priority={priority} affinity=0x{affinityMask:X} -> thread=0x{threadHandle:X16}");
|
|
}
|
|
|
|
var scheduler = GuestThreadExecution.Scheduler;
|
|
if (scheduler is not null && entryAddress != 0)
|
|
{
|
|
var request = new GuestThreadStartRequest(
|
|
threadHandle,
|
|
entryAddress,
|
|
argument,
|
|
attrAddress,
|
|
name,
|
|
priority,
|
|
affinityMask);
|
|
if (!scheduler.TryStartThread(ctx, request, out var error))
|
|
{
|
|
Console.Error.WriteLine(
|
|
$"[LOADER][ERROR] pthread_create: failed to schedule guest thread '{name}' entry=0x{entryAddress:X16}: {error}");
|
|
ctx[CpuRegister.Rax] = unchecked((ulong)(int)OrbisGen2Result.ORBIS_GEN2_ERROR_TRY_AGAIN);
|
|
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_TRY_AGAIN;
|
|
}
|
|
}
|
|
|
|
ctx[CpuRegister.Rax] = 0;
|
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
|
}
|
|
|
|
[SysAbiExport(
|
|
Nid = "OxhIB8LB-PQ",
|
|
ExportName = "pthread_create",
|
|
Target = Generation.Gen4 | Generation.Gen5,
|
|
LibraryName = "libKernel")]
|
|
public static int PosixPthreadCreate(CpuContext ctx)
|
|
{
|
|
return PthreadCreate(ctx);
|
|
}
|
|
|
|
[SysAbiExport(
|
|
Nid = "Jmi+9w9u0E4",
|
|
ExportName = "pthread_create_name_np",
|
|
Target = Generation.Gen4 | Generation.Gen5,
|
|
LibraryName = "libKernel")]
|
|
public static int PosixPthreadCreateNameNp(CpuContext ctx)
|
|
{
|
|
return PthreadCreate(ctx);
|
|
}
|
|
|
|
[SysAbiExport(
|
|
Nid = "3kg7rT0NQIs",
|
|
ExportName = "scePthreadExit",
|
|
Target = Generation.Gen4 | Generation.Gen5,
|
|
LibraryName = "libKernel")]
|
|
public static int PthreadExit(CpuContext ctx)
|
|
{
|
|
var value = ctx[CpuRegister.Rdi];
|
|
GuestThreadExecution.RequestCurrentEntryExit("scePthreadExit", value);
|
|
ctx[CpuRegister.Rax] = value;
|
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
|
}
|
|
|
|
[SysAbiExport(
|
|
Nid = "FJrT5LuUBAU",
|
|
ExportName = "pthread_exit",
|
|
Target = Generation.Gen4 | Generation.Gen5,
|
|
LibraryName = "libScePosix")]
|
|
public static int PosixPthreadExit(CpuContext ctx)
|
|
{
|
|
var value = ctx[CpuRegister.Rdi];
|
|
GuestThreadExecution.RequestCurrentEntryExit("pthread_exit", value);
|
|
ctx[CpuRegister.Rax] = value;
|
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
|
}
|
|
|
|
[SysAbiExport(
|
|
Nid = "onNY9Byn-W8",
|
|
ExportName = "scePthreadJoin",
|
|
Target = Generation.Gen4 | Generation.Gen5,
|
|
LibraryName = "libKernel")]
|
|
public static int PthreadJoin(CpuContext ctx)
|
|
{
|
|
var threadId = ctx[CpuRegister.Rdi];
|
|
var returnValueAddress = ctx[CpuRegister.Rsi];
|
|
|
|
if (ShouldTracePthread())
|
|
{
|
|
Console.Error.WriteLine(
|
|
$"[LOADER][TRACE] pthread_join: thread=0x{threadId:X16} retval_out=0x{returnValueAddress:X16}");
|
|
}
|
|
|
|
var returnValue = 0UL;
|
|
if (GuestThreadExecution.Scheduler is { } scheduler &&
|
|
!scheduler.TryJoinThread(ctx, threadId, out returnValue, out var error))
|
|
{
|
|
Console.Error.WriteLine(
|
|
$"[LOADER][ERROR] pthread_join: thread=0x{threadId:X16}: {error}");
|
|
var result = string.Equals(
|
|
error,
|
|
"thread cannot join itself",
|
|
StringComparison.Ordinal)
|
|
? OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT
|
|
: OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
|
|
ctx[CpuRegister.Rax] = unchecked((ulong)(int)result);
|
|
return (int)result;
|
|
}
|
|
|
|
if (returnValueAddress != 0 &&
|
|
!ctx.TryWriteUInt64(returnValueAddress, returnValue))
|
|
{
|
|
ctx[CpuRegister.Rax] =
|
|
unchecked((ulong)(int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
|
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
|
}
|
|
|
|
ctx[CpuRegister.Rax] = 0;
|
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
|
}
|
|
|
|
[SysAbiExport(
|
|
Nid = "h9CcP3J0oVM",
|
|
ExportName = "pthread_join",
|
|
Target = Generation.Gen4 | Generation.Gen5,
|
|
LibraryName = "libKernel")]
|
|
public static int PosixPthreadJoin(CpuContext ctx)
|
|
{
|
|
return PthreadJoin(ctx);
|
|
}
|
|
|
|
[SysAbiExport(
|
|
Nid = "wuCroIGjt2g",
|
|
ExportName = "open",
|
|
Target = Generation.Gen4 | Generation.Gen5,
|
|
LibraryName = "libKernel")]
|
|
public static int Open(CpuContext ctx) => KernelMemoryCompatExports.KernelOpenUnderscore(ctx);
|
|
|
|
[SysAbiExport(
|
|
Nid = "1G3lF1Gg1k8",
|
|
ExportName = "sceKernelOpen",
|
|
Target = Generation.Gen4 | Generation.Gen5,
|
|
LibraryName = "libKernel")]
|
|
public static int KernelOpen(CpuContext ctx) => KernelMemoryCompatExports.KernelOpenUnderscore(ctx);
|
|
|
|
[SysAbiExport(
|
|
Nid = "mqQMh1zPPT8",
|
|
ExportName = "fstat",
|
|
Target = Generation.Gen4 | Generation.Gen5,
|
|
LibraryName = "libc")]
|
|
public static int Fstat(CpuContext ctx) => KernelMemoryCompatExports.KernelFstat(ctx);
|
|
|
|
[SysAbiExport(
|
|
Nid = "hcuQgD53UxM",
|
|
ExportName = "printf",
|
|
Target = Generation.Gen4 | Generation.Gen5,
|
|
LibraryName = "libc")]
|
|
public static int Printf(CpuContext ctx)
|
|
{
|
|
ulong fmtPtr = ctx[CpuRegister.Rdi];
|
|
string fmt = ReadCString(ctx, fmtPtr, 4096);
|
|
string outStr = KernelMemoryCompatExports.FormatStringFromVarArgs(ctx, fmt, firstGpArgIndex: 1);
|
|
if (outStr.EndsWith('\n') || outStr.EndsWith('\r'))
|
|
{
|
|
Console.Write($"[DEBUG][PRINF] {outStr}");
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine($"[DEBUG][PRINF] {outStr}");
|
|
}
|
|
|
|
ctx[CpuRegister.Rax] = (ulong)System.Text.Encoding.UTF8.GetByteCount(outStr);
|
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
|
}
|
|
|
|
[SysAbiExport(
|
|
Nid = "EMutwaQ34Jo",
|
|
ExportName = "perror",
|
|
Target = Generation.Gen4 | Generation.Gen5,
|
|
LibraryName = "libc")]
|
|
public static int Perror(CpuContext ctx)
|
|
{
|
|
ulong sPtr = ctx[CpuRegister.Rdi];
|
|
|
|
string msg;
|
|
if (sPtr == 0)
|
|
{
|
|
msg = "perror(NULL)";
|
|
}
|
|
else
|
|
{
|
|
msg = ReadCString(ctx, sPtr, 2048);
|
|
msg = $"perror(\"{msg}\")";
|
|
}
|
|
|
|
Console.WriteLine(msg);
|
|
|
|
ctx[CpuRegister.Rax] = 0;
|
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
|
}
|
|
|
|
private static string ReadCString(CpuContext ctx, ulong address, int maxLen)
|
|
{
|
|
Span<byte> buf = stackalloc byte[maxLen];
|
|
if (!ctx.Memory.TryRead(address, buf))
|
|
return $"<unreadable 0x{address:X16}>";
|
|
|
|
int len = 0;
|
|
while (len < buf.Length && buf[len] != 0) len++;
|
|
|
|
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);
|
|
}
|
|
}
|