Compare commits

...

14 Commits

Author SHA1 Message Date
ParantezTech cecf55bf2e fix: dereference of a possibly null reference. 2026-03-16 21:19:49 +03:00
ParantezTech 0ede7c70a9 [ci] update actions Node 20 deprecation 2026-03-16 21:18:58 +03:00
ParantezTech 8c39a55501 reuse 2026-03-16 20:49:40 +03:00
ParantezTech 0a4b36ffcb [dotnet/ci] constant version 2026-03-16 20:46:47 +03:00
ParantezTech 1a1d61b21c rework TLS allocations, more HLE's, increase import loop history 2026-03-16 20:22:58 +03:00
ParantezTech 3cf96f38de [github] bug_report fix 2026-03-16 20:14:58 +03:00
ParantezTech d8c44bb64f [github] added bug report issue template 2026-03-16 20:11:56 +03:00
ParantezTech a4b5171586 [github] added issue config & reuse 2026-03-16 20:01:08 +03:00
ParantezTech 1f71c970d9 A dozen changes; new HLEs, AV fixes, ELF loader fixes, new return codes, etc. 2026-03-14 20:38:25 +03:00
ParantezTech 028494a83b another hle implements, especially for mutex calls 2026-03-12 18:01:42 +03:00
ParantezTech 73ae502e09 update readme 2026-03-12 16:32:32 +03:00
ParantezTech 51d204775e [github] update compatibility template 2026-03-12 16:17:54 +03:00
ParantezTech 7ce6b9e2af [github] added compatibility template 2026-03-12 16:06:29 +03:00
ParantezTech d4cd4eb96f update readme 2026-03-11 17:03:34 +03:00
32 changed files with 3722 additions and 473 deletions
+59
View File
@@ -0,0 +1,59 @@
# Copyright (C) 2026 SharpEmu Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later
name: Bug Report
description: Report a bug in the emulator
labels: ["bug"]
body:
- type: markdown
attributes:
value: |
### Before creating an issue
- Do not create issues involving **software piracy**. Our rules specifically prohibit this.
Issues related to piracy will be closed and the author may be banned from the repository.
- The issue tracker is **not for asking questions**.
Question-type issues will be closed.
- Only open an issue if you are reporting an actual **bug in the emulator**.
- type: input
id: emulator_version
attributes:
label: Emulator Version / Commit
description: The emulator version or git commit where the bug occurred
placeholder: e.g. v0.0.2 / a1b2c3d
validations:
required: true
- type: textarea
id: description
attributes:
label: Bug Description
description: Describe the problem and how to reproduce it
placeholder: |
Steps to reproduce:
1. Launch emulator
2. Load game
3. Crash occurs
Expected behavior:
...
Actual behavior:
...
validations:
required: true
- type: textarea
id: logs
attributes:
label: Log File
description: |
Drag & drop your log file here, or paste the log contents.
Supported: .log / .txt / .zip
Large logs should be uploaded as a file.
render: shell
+4
View File
@@ -0,0 +1,4 @@
# Copyright (C) 2026 SharpEmu Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later
blank_issues_enabled: false
@@ -0,0 +1,144 @@
# Copyright (C) 2026 SharpEmu Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later
name: Game Compatibility Report
description: Report compatibility status of a game
labels: ["game-compatibility"]
body:
- type: markdown
attributes:
value: |
Use this template to report the compatibility status of a game.
Please include the **game name in the issue title**.
Example title:
Demons Souls (PPSA01341)
- type: input
id: game_name
attributes:
label: Game Name
placeholder: e.g. Demon's Souls
validations:
required: true
- type: input
id: title_id
attributes:
label: Title ID
description: Game title ID (PPSA / CUSA / PCSA etc.)
placeholder: e.g. PPSA01341
validations:
required: true
- type: input
id: game_version
attributes:
label: Game Version
placeholder: e.g. 1.00 / 1.09
validations:
required: true
- type: dropdown
id: status
attributes:
label: Current Status
description: What happens when running the game?
options:
- Nothing happens
- Crashes on boot
- Boots to black screen
- Shows intro / menus
- Ingame but unstable
- Playable
validations:
required: true
- type: textarea
id: description
attributes:
label: Description
description: Describe what happens when running the game
placeholder: |
Example:
- Game boots
- Shows intro logos
- Crashes when entering gameplay
validations:
required: true
- type: upload
id: logs
attributes:
label: Log File
description: Drag and drop your log file here, or click to browse
validations:
required: false
accept: ".log,.txt,.zip"
- type: dropdown
id: operating_system
attributes:
label: Operating System
options:
- Windows 11
- Windows 10
- Linux
- macOS
- Other
validations:
required: true
- type: input
id: cpu
attributes:
label: CPU
placeholder: e.g. AMD Ryzen 7 5800X3D
validations:
required: true
- type: input
id: gpu
attributes:
label: GPU
placeholder: e.g. NVIDIA GeForce RTX 4070 Super
validations:
required: true
- type: input
id: ram
attributes:
label: RAM
placeholder: e.g. 32 GB
validations:
required: true
- type: input
id: emulator_version
attributes:
label: Emulator Version / Commit
placeholder: e.g. v0.0.1 / a1b2c3d
validations:
required: true
- type: dropdown
id: renderer
attributes:
label: Renderer
options:
- Vulkan
- OpenGL
- DirectX
- Software
- N/A
- Other
validations:
required: true
- type: textarea
id: additional
attributes:
label: Additional Notes
description: Screenshots, settings, patches, workarounds, or anything else relevant
+6 -3
View File
@@ -1,3 +1,6 @@
# Copyright (C) 2026 SharpEmu Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later
name: Build and Release name: Build and Release
on: on:
@@ -79,7 +82,7 @@ jobs:
- name: Setup .NET SDK - name: Setup .NET SDK
uses: actions/setup-dotnet@v5 uses: actions/setup-dotnet@v5
with: with:
dotnet-version: 10.0.x dotnet-version: 10.0.103
cache: true cache: true
cache-dependency-path: | cache-dependency-path: |
Directory.Packages.props Directory.Packages.props
@@ -106,7 +109,7 @@ jobs:
Compress-Archive -Path (Join-Path $env:PUBLISH_DIR '*') -DestinationPath $archivePath -CompressionLevel Optimal Compress-Archive -Path (Join-Path $env:PUBLISH_DIR '*') -DestinationPath $archivePath -CompressionLevel Optimal
- name: Upload build artifact - name: Upload build artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v6
with: with:
name: ${{ needs.init.outputs.artifact-name }} name: ${{ needs.init.outputs.artifact-name }}
path: ${{ env.RELEASE_DIR }}\${{ needs.init.outputs.archive-name }} path: ${{ env.RELEASE_DIR }}\${{ needs.init.outputs.archive-name }}
@@ -123,7 +126,7 @@ jobs:
contents: write contents: write
steps: steps:
- name: Download build artifact - name: Download build artifact
uses: actions/download-artifact@v4 uses: actions/download-artifact@v8
with: with:
name: ${{ needs.init.outputs.artifact-name }} name: ${{ needs.init.outputs.artifact-name }}
path: release path: release
BIN
View File
Binary file not shown.
+1
View File
@@ -3,6 +3,7 @@ version = 1
[[annotations]] [[annotations]]
path = [ path = [
"REUSE.toml", "REUSE.toml",
"global.json",
"**/packages.lock.json", "**/packages.lock.json",
"scripts/ps5_names.txt", "scripts/ps5_names.txt",
"src/SharpEmu.HLE/Aerolib/aerolib.bin", "src/SharpEmu.HLE/Aerolib/aerolib.bin",
Binary file not shown.
Binary file not shown.
+6
View File
@@ -0,0 +1,6 @@
{
"sdk": {
"version": "10.0.103",
"rollForward": "latestFeature"
}
}
+73 -15
View File
@@ -12,6 +12,12 @@ namespace SharpEmu.Core.Cpu;
public sealed class CpuDispatcher : ICpuDispatcher, IDisposable public sealed class CpuDispatcher : ICpuDispatcher, IDisposable
{ {
private enum EntryFrameKind
{
ProcessEntry,
ModuleInitializer,
}
private const ulong StackBaseAddress = 0x7FFF_F000_0000UL; private const ulong StackBaseAddress = 0x7FFF_F000_0000UL;
private const ulong StackSize = 0x0020_0000UL; private const ulong StackSize = 0x0020_0000UL;
private const ulong TlsBaseAddress = 0x7FFE_0000_0000UL; private const ulong TlsBaseAddress = 0x7FFE_0000_0000UL;
@@ -88,13 +94,44 @@ public sealed class CpuDispatcher : ICpuDispatcher, IDisposable
} }
} }
public OrbisGen2Result DispatchModuleInitializer(
ulong entryPoint,
Generation generation,
IReadOnlyDictionary<ulong, string>? importStubs = null,
IReadOnlyDictionary<string, ulong>? runtimeSymbols = null,
string moduleName = "module",
CpuExecutionOptions executionOptions = default)
{
Console.Error.WriteLine("[DISPATCHER] === DispatchModuleInitializer START ===");
Console.Error.WriteLine($"[DISPATCHER] moduleInit=0x{entryPoint:X16}, generation={generation}, module={moduleName}");
try
{
return DispatchEntryCore(
entryPoint,
generation,
importStubs,
runtimeSymbols,
moduleName,
executionOptions,
EntryFrameKind.ModuleInitializer);
}
catch (Exception ex)
{
Console.Error.WriteLine($"[DISPATCHER] FATAL EXCEPTION in DispatchModuleInitializer: {ex.GetType().Name}: {ex.Message}");
Console.Error.WriteLine($"[DISPATCHER] Stack trace: {ex.StackTrace}");
throw;
}
}
private OrbisGen2Result DispatchEntryCore( private OrbisGen2Result DispatchEntryCore(
ulong entryPoint, ulong entryPoint,
Generation generation, Generation generation,
IReadOnlyDictionary<ulong, string>? importStubs = null, IReadOnlyDictionary<ulong, string>? importStubs = null,
IReadOnlyDictionary<string, ulong>? runtimeSymbols = null, IReadOnlyDictionary<string, ulong>? runtimeSymbols = null,
string processImageName = "eboot.bin", string processImageName = "eboot.bin",
CpuExecutionOptions executionOptions = default) CpuExecutionOptions executionOptions = default,
EntryFrameKind frameKind = EntryFrameKind.ProcessEntry)
{ {
Console.Error.WriteLine("[DISPATCHER] DispatchEntryCore STARTING..."); Console.Error.WriteLine("[DISPATCHER] DispatchEntryCore STARTING...");
@@ -164,23 +201,33 @@ public sealed class CpuDispatcher : ICpuDispatcher, IDisposable
var effectiveImportStubs = importStubs is null var effectiveImportStubs = importStubs is null
? new Dictionary<ulong, string>() ? new Dictionary<ulong, string>()
: new Dictionary<ulong, string>(importStubs); : new Dictionary<ulong, string>(importStubs);
var programExitHandlerStubAddress = TryMapDynlibFallbackStubRegion(); var entryParamsConfigured = false;
if (programExitHandlerStubAddress == 0) if (frameKind == EntryFrameKind.ProcessEntry)
{ {
return FailEarly(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT); var programExitHandlerStubAddress = TryMapDynlibFallbackStubRegion();
} if (programExitHandlerStubAddress == 0)
if (!InitializeProcessEntryFrame(context, processImageName, programExitHandlerStubAddress))
{
return FailEarly(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
if (ShouldInjectBootstrapPayload(entryPoint))
{
if (!TryInstallBootstrapPayload(context, effectiveImportStubs))
{ {
return FailEarly(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT); return FailEarly(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
} }
if (!InitializeProcessEntryFrame(context, processImageName, programExitHandlerStubAddress))
{
return FailEarly(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
entryParamsConfigured = true;
if (ShouldInjectBootstrapPayload(entryPoint))
{
if (!TryInstallBootstrapPayload(context, effectiveImportStubs))
{
return FailEarly(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
}
}
else if (!InitializeModuleInitializerFrame(context))
{
return FailEarly(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
} }
var entryFrameDiagnostic = BuildEntryFrameDiagnostic( var entryFrameDiagnostic = BuildEntryFrameDiagnostic(
@@ -188,7 +235,7 @@ public sealed class CpuDispatcher : ICpuDispatcher, IDisposable
context, context,
sentinelEnabled: true, sentinelEnabled: true,
sentinelValue: returnToHostStubAddress, sentinelValue: returnToHostStubAddress,
entryParamsConfigured: true); entryParamsConfigured: entryParamsConfigured);
if (executionOptions.CpuEngine != CpuExecutionEngine.NativeOnly) if (executionOptions.CpuEngine != CpuExecutionEngine.NativeOnly)
{ {
@@ -360,6 +407,17 @@ public sealed class CpuDispatcher : ICpuDispatcher, IDisposable
return true; return true;
} }
private static bool InitializeModuleInitializerFrame(CpuContext context)
{
context[CpuRegister.Rdi] = 0;
context[CpuRegister.Rsi] = 0;
context[CpuRegister.Rdx] = 0;
context[CpuRegister.Rcx] = 0;
context[CpuRegister.R8] = 0;
context[CpuRegister.R9] = 0;
return true;
}
private static ulong AlignDown(ulong value, ulong alignment) private static ulong AlignDown(ulong value, ulong alignment)
{ {
return value & ~(alignment - 1); return value & ~(alignment - 1);
+8
View File
@@ -34,4 +34,12 @@ public interface ICpuDispatcher
IReadOnlyDictionary<string, ulong>? runtimeSymbols = null, IReadOnlyDictionary<string, ulong>? runtimeSymbols = null,
string processImageName = "eboot.bin", string processImageName = "eboot.bin",
CpuExecutionOptions executionOptions = default); CpuExecutionOptions executionOptions = default);
OrbisGen2Result DispatchModuleInitializer(
ulong entryPoint,
Generation generation,
IReadOnlyDictionary<ulong, string>? importStubs = null,
IReadOnlyDictionary<string, ulong>? runtimeSymbols = null,
string moduleName = "module",
CpuExecutionOptions executionOptions = default);
} }
@@ -4,6 +4,7 @@
using System; using System;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.Reflection; using System.Reflection;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SharpEmu.Core.Cpu.Disasm; using SharpEmu.Core.Cpu.Disasm;
@@ -254,6 +255,9 @@ public sealed partial class DirectExecutionBackend
Console.Error.WriteLine("[LOADER][ERROR] Could not read code at RIP"); Console.Error.WriteLine("[LOADER][ERROR] Could not read code at RIP");
} }
DumpRecentImportTrace(); DumpRecentImportTrace();
DumpGuestDisasmDiagnostics(rip, rbp);
DumpGuestReferenceDiagnostics();
DumpGuestPointerWindowDiagnostics();
break; break;
case 2147483651u: case 2147483651u:
Console.Error.WriteLine("[LOADER][WARNING] Type: Breakpoint (int3)"); Console.Error.WriteLine("[LOADER][WARNING] Type: Breakpoint (int3)");
@@ -322,6 +326,239 @@ public sealed partial class DirectExecutionBackend
} }
} }
private void DumpGuestDisasmDiagnostics(ulong rip, ulong rbp)
{
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_DISASM"), "1", StringComparison.Ordinal))
{
return;
}
if (rip >= 0x20)
{
DumpGuestInstructionStream("fault-prelude", rip - 0x20, 24);
}
try
{
ulong frame = rbp;
for (int i = 0; i < 3; i++)
{
if (frame < 140733193388032L || frame > 140737488355327L)
{
break;
}
ulong ret = (ulong)Marshal.ReadInt64((nint)(frame + 8));
if (ret >= 0x40)
{
DumpGuestInstructionStream($"frame#{i}-ret-prelude", ret - 0x40, 24);
}
ulong next = (ulong)Marshal.ReadInt64((nint)frame);
if (next <= frame)
{
break;
}
frame = next;
}
}
catch
{
Console.Error.WriteLine("[LOADER][WARNING] Could not dump disasm diagnostics.");
}
var extraAddresses = Environment.GetEnvironmentVariable("SHARPEMU_LOG_DISASM_ADDRS");
if (string.IsNullOrWhiteSpace(extraAddresses))
{
return;
}
foreach (var token in extraAddresses.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
{
var normalized = token.StartsWith("0x", StringComparison.OrdinalIgnoreCase)
? token[2..]
: token;
if (!ulong.TryParse(normalized, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var address) || address < 0x20)
{
continue;
}
DumpGuestInstructionStream($"extra-0x{address:X16}", address, 48);
}
}
private unsafe void DumpGuestReferenceDiagnostics()
{
var targetList = ParseDiagnosticAddresses(Environment.GetEnvironmentVariable("SHARPEMU_LOG_REFSCAN_ADDRS"));
if (targetList.Count == 0 || _cpuContext == null)
{
return;
}
const ulong scanBase = 0x0000000800000000UL;
const ulong scanEnd = 0x0000000810000000UL;
const int maxHitsPerTarget = 24;
Console.Error.WriteLine(
$"[LOADER][INFO] Ref scan targets: {string.Join(", ", targetList.ConvertAll(static addr => $"0x{addr:X16}"))}");
var hitCounts = new Dictionary<ulong, int>(targetList.Count);
for (var i = 0; i < targetList.Count; i++)
{
hitCounts[targetList[i]] = 0;
}
ulong address = scanBase;
while (address < scanEnd)
{
if (VirtualQuery((void*)address, out var mbi, (nuint)sizeof(MEMORY_BASIC_INFORMATION64)) == 0)
{
break;
}
ulong regionBase = mbi.BaseAddress;
ulong regionEnd = regionBase + mbi.RegionSize;
if (regionEnd <= address)
{
break;
}
if (mbi.State == MEM_COMMIT &&
IsReadableProtection(mbi.Protect) &&
IsExecutableProtection(mbi.Protect))
{
ScanExecutableRegionForTargetReferences(regionBase, regionEnd, targetList, hitCounts, maxHitsPerTarget);
}
var allTargetsSatisfied = true;
for (var i = 0; i < targetList.Count; i++)
{
if (hitCounts[targetList[i]] < maxHitsPerTarget)
{
allTargetsSatisfied = false;
break;
}
}
if (allTargetsSatisfied)
{
break;
}
address = regionEnd;
}
for (var i = 0; i < targetList.Count; i++)
{
var target = targetList[i];
if (!hitCounts.TryGetValue(target, out var count) || count == 0)
{
Console.Error.WriteLine($"[LOADER][INFO] Ref scan 0x{target:X16}: none");
}
}
}
private void DumpGuestPointerWindowDiagnostics()
{
var targetList = ParseDiagnosticAddresses(Environment.GetEnvironmentVariable("SHARPEMU_LOG_POINTER_WINDOWS"));
if (targetList.Count == 0)
{
return;
}
var windowSize = 0x80;
var rawWindowSize = Environment.GetEnvironmentVariable("SHARPEMU_LOG_POINTER_WINDOW_SIZE");
if (!string.IsNullOrWhiteSpace(rawWindowSize))
{
var normalized = rawWindowSize.StartsWith("0x", StringComparison.OrdinalIgnoreCase)
? rawWindowSize[2..]
: rawWindowSize;
if (int.TryParse(normalized, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var parsedWindowSize) &&
parsedWindowSize > 0)
{
windowSize = parsedWindowSize;
}
}
foreach (var target in targetList)
{
DumpPointerWindow($"ptrwin-0x{target:X16}", target, windowSize);
}
}
private void ScanExecutableRegionForTargetReferences(
ulong regionBase,
ulong regionEnd,
IReadOnlyList<ulong> targets,
IDictionary<ulong, int> hitCounts,
int maxHitsPerTarget)
{
if (_cpuContext == null || regionEnd <= regionBase)
{
return;
}
ulong rip = regionBase;
while (rip < regionEnd)
{
if (!IcedDecoder.TryReadGuestBytes(_cpuContext.Memory, rip, maxLen: 15, out var bytes) ||
!IcedDecoder.TryDecode(rip, bytes, out var instruction) ||
instruction.Length <= 0)
{
rip++;
continue;
}
if (instruction.MemoryAddress is { } memoryAddress)
{
for (var i = 0; i < targets.Count; i++)
{
var target = targets[i];
if (memoryAddress != target ||
!hitCounts.TryGetValue(target, out var count) ||
count >= maxHitsPerTarget)
{
continue;
}
hitCounts[target] = count + 1;
Console.Error.WriteLine(
$"[LOADER][INFO] Ref scan hit target=0x{target:X16} rip=0x{instruction.Rip:X16} text={instruction.Text} bytes={IcedDecoder.FormatBytes(instruction.Bytes)}");
}
}
rip += (ulong)instruction.Length;
}
}
private static List<ulong> ParseDiagnosticAddresses(string? rawValue)
{
var result = new List<ulong>();
if (string.IsNullOrWhiteSpace(rawValue))
{
return result;
}
foreach (var token in rawValue.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
{
var normalized = token.StartsWith("0x", StringComparison.OrdinalIgnoreCase)
? token[2..]
: token;
if (!ulong.TryParse(normalized, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var address))
{
continue;
}
if (!result.Contains(address))
{
result.Add(address);
}
}
return result;
}
private void DumpUnresolvedSentinelWindow(string name, ulong baseAddress, int size) private void DumpUnresolvedSentinelWindow(string name, ulong baseAddress, int size)
{ {
if (baseAddress < 0x10000 || size <= 0) if (baseAddress < 0x10000 || size <= 0)
@@ -231,12 +231,10 @@ public sealed partial class DirectExecutionBackend
ProbeReturnRip(num7, num); ProbeReturnRip(num7, num);
Console.Error.Flush(); Console.Error.Flush();
} }
ProbeReturnRip(0x0000000800EA020Eul, 999001);
ProbeReturnRip(0x0000000800EA0213ul, 999002);
ProbeReturnRip(0x0000000800EA040Aul, 999003);
try try
{ {
OrbisGen2Result orbisGen2Result; OrbisGen2Result orbisGen2Result;
bool dispatchResolved = true;
if (string.Equals(importStubEntry.Nid, RuntimeStubNids.BootstrapBridge, StringComparison.Ordinal)) if (string.Equals(importStubEntry.Nid, RuntimeStubNids.BootstrapBridge, StringComparison.Ordinal))
{ {
orbisGen2Result = DispatchBootstrapBridge(); orbisGen2Result = DispatchBootstrapBridge();
@@ -247,34 +245,31 @@ public sealed partial class DirectExecutionBackend
} }
else else
{ {
orbisGen2Result = _moduleManager.Dispatch(importStubEntry.Nid, _cpuContext); dispatchResolved = _moduleManager.TryDispatch(importStubEntry.Nid, _cpuContext, out orbisGen2Result);
} }
switch (orbisGen2Result) if (!dispatchResolved)
{ {
case OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND: LastError = "Missing HLE export for NID: " + importStubEntry.Nid;
LastError = "Missing HLE export for NID: " + importStubEntry.Nid; Console.Error.WriteLine($"[LOADER][WARN] Import#{num} unresolved: nid={importStubEntry.Nid} ret=0x{num7:X16}");
Console.Error.WriteLine($"[LOADER][WARN] Import#{num} unresolved: nid={importStubEntry.Nid} ret=0x{num7:X16}"); if (importStubEntry.Nid == "L-Q3LEjIbgA")
if (importStubEntry.Nid == "L-Q3LEjIbgA") {
string value18 = string.Join(" ", importStubEntry.Nid.Select(delegate (char c)
{ {
string value18 = string.Join(" ", importStubEntry.Nid.Select(delegate (char c) int num10 = c;
{ return num10.ToString("X2");
int num10 = c; }));
return num10.ToString("X2"); Console.Error.WriteLine($"[LOADER][WARN] map_direct nid raw len={importStubEntry.Nid.Length} chars=[{value18}]");
})); Delegate function;
Console.Error.WriteLine($"[LOADER][WARN] map_direct nid raw len={importStubEntry.Nid.Length} chars=[{value18}]"); bool value19 = _moduleManager.TryGetFunction(importStubEntry.Nid, out function);
Delegate function; ExportedFunction export2;
bool value19 = _moduleManager.TryGetFunction(importStubEntry.Nid, out function); bool value20 = _moduleManager.TryGetExport(importStubEntry.Nid, out export2);
ExportedFunction export2; Console.Error.WriteLine($"[LOADER][WARN] map_direct lookup with import nid: function={value19}, export={value20}");
bool value20 = _moduleManager.TryGetExport(importStubEntry.Nid, out export2); Console.Error.WriteLine(_moduleManager.TryGetExport("L-Q3LEjIbgA", out ExportedFunction export3) ? $"[LOADER][WARN] Canonical map_direct exists as {export3.LibraryName}:{export3.Name}, target={export3.Target}, ctx_target={_cpuContext.TargetGeneration}" : "[LOADER][WARN] Canonical map_direct export lookup also missing");
Console.Error.WriteLine($"[LOADER][WARN] map_direct lookup with import nid: function={value19}, export={value20}"); }
Console.Error.WriteLine(_moduleManager.TryGetExport("L-Q3LEjIbgA", out ExportedFunction export3) ? $"[LOADER][WARN] Canonical map_direct exists as {export3.LibraryName}:{export3.Name}, target={export3.Target}, ctx_target={_cpuContext.TargetGeneration}" : "[LOADER][WARN] Canonical map_direct export lookup also missing"); }
} else if (orbisGen2Result != OrbisGen2Result.ORBIS_GEN2_OK)
break; {
default: Console.Error.WriteLine($"[LOADER][WARN] Import#{num} result: {orbisGen2Result} ({importStubEntry.Nid})");
Console.Error.WriteLine($"[LOADER][WARN] Import#{num} result: {orbisGen2Result} ({importStubEntry.Nid})");
break;
case OrbisGen2Result.ORBIS_GEN2_OK:
break;
} }
_cpuContext[CpuRegister.Rbx] = value3; _cpuContext[CpuRegister.Rbx] = value3;
_cpuContext[CpuRegister.Rbp] = value4; _cpuContext[CpuRegister.Rbp] = value4;
@@ -334,7 +329,12 @@ public sealed partial class DirectExecutionBackend
{ {
return false; return false;
} }
RecordImportLoopSignature(BuildImportLoopSignature(nid, returnRip, arg0, arg1)); if (!_importNidHashCache.TryGetValue(nid, out var value))
{
value = StableHash64(nid);
_importNidHashCache[nid] = value;
}
RecordImportLoopSignature(value, returnRip, BuildImportLoopSignature(value, returnRip, arg0, arg1));
if (!HasRepeatingImportLoopPattern()) if (!HasRepeatingImportLoopPattern())
{ {
if (_importLoopPatternHits > 0) if (_importLoopPatternHits > 0)
@@ -347,21 +347,18 @@ public sealed partial class DirectExecutionBackend
return _importLoopPatternHits >= 6; return _importLoopPatternHits >= 6;
} }
private ulong BuildImportLoopSignature(string nid, ulong returnRip, ulong arg0, ulong arg1) private ulong BuildImportLoopSignature(ulong nidHash, ulong returnRip, ulong arg0, ulong arg1)
{ {
if (!_importNidHashCache.TryGetValue(nid, out var value))
{
value = StableHash64(nid);
_importNidHashCache[nid] = value;
}
ulong num = returnRip >> 2; ulong num = returnRip >> 2;
ulong num2 = ((arg0 >> 4) * 11400714819323198485uL) ^ ((arg1 >> 4) * 14029467366897019727uL); ulong num2 = ((arg0 >> 4) * 11400714819323198485uL) ^ ((arg1 >> 4) * 14029467366897019727uL);
return num ^ value * 11400714819323198485uL ^ num2; return num ^ nidHash * 11400714819323198485uL ^ num2;
} }
private void RecordImportLoopSignature(ulong signature) private void RecordImportLoopSignature(ulong nidHash, ulong returnRip, ulong signature)
{ {
_importLoopSignatures[_importLoopSignatureWriteIndex] = signature; _importLoopSignatures[_importLoopSignatureWriteIndex] = signature;
_importLoopNidHashes[_importLoopSignatureWriteIndex] = nidHash;
_importLoopReturnRips[_importLoopSignatureWriteIndex] = returnRip;
_importLoopSignatureWriteIndex = (_importLoopSignatureWriteIndex + 1) % _importLoopSignatures.Length; _importLoopSignatureWriteIndex = (_importLoopSignatureWriteIndex + 1) % _importLoopSignatures.Length;
if (_importLoopSignatureCount < _importLoopSignatures.Length) if (_importLoopSignatureCount < _importLoopSignatures.Length)
{ {
@@ -405,7 +402,7 @@ public sealed partial class DirectExecutionBackend
} }
} }
} }
return true; return IsSevereImportLoopPattern(num);
} }
private ulong GetImportLoopSignatureFromTail(int offset) private ulong GetImportLoopSignatureFromTail(int offset)
@@ -418,6 +415,64 @@ public sealed partial class DirectExecutionBackend
return _importLoopSignatures[num % _importLoopSignatures.Length]; return _importLoopSignatures[num % _importLoopSignatures.Length];
} }
private bool IsSevereImportLoopPattern(int sampleCount)
{
int num = CountDistinctImportLoopValuesFromTail(_importLoopNidHashes, sampleCount, 3);
if (num > 2)
{
return false;
}
int num2 = CountDistinctImportLoopValuesFromTail(_importLoopReturnRips, sampleCount, 3);
if (num2 > 2)
{
return false;
}
int num3 = Math.Min(_importLoopSignatureCount, Math.Max(sampleCount * 8, ImportLoopWideDiversityWindow));
if (num3 <= sampleCount)
{
return true;
}
if (CountDistinctImportLoopValuesFromTail(_importLoopNidHashes, num3, 3) > 2)
{
return false;
}
return CountDistinctImportLoopValuesFromTail(_importLoopReturnRips, num3, 3) <= 2;
}
private int CountDistinctImportLoopValuesFromTail(ulong[] source, int sampleCount, int stopAfter)
{
int num = Math.Min(sampleCount, _importLoopSignatureCount);
int num2 = 0;
for (int i = 0; i < num; i++)
{
ulong importLoopValueFromTail = GetImportLoopValueFromTail(source, i);
bool flag = false;
for (int j = 0; j < i; j++)
{
if (GetImportLoopValueFromTail(source, j) == importLoopValueFromTail)
{
flag = true;
break;
}
}
if (!flag && ++num2 >= stopAfter)
{
return num2;
}
}
return num2;
}
private ulong GetImportLoopValueFromTail(ulong[] source, int offset)
{
int num = _importLoopSignatureWriteIndex - 1 - offset;
while (num < 0)
{
num += source.Length;
}
return source[num % source.Length];
}
private bool ShouldSuppressStrlenTrace(string nid) private bool ShouldSuppressStrlenTrace(string nid)
{ {
return string.Equals(nid, "j4ViWNHEgww", StringComparison.Ordinal) && !_logStrlenImports; return string.Equals(nid, "j4ViWNHEgww", StringComparison.Ordinal) && !_logStrlenImports;
@@ -13,6 +13,10 @@ namespace SharpEmu.Core.Cpu.Native;
public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, IDisposable public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, IDisposable
{ {
private const int ImportLoopHistoryLength = 2048;
private const int ImportLoopWideDiversityWindow = 768;
private readonly struct ImportStubEntry private readonly struct ImportStubEntry
{ {
public ulong Address { get; } public ulong Address { get; }
@@ -89,6 +93,8 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
private const uint PAGE_EXECUTE_READ = 32u; private const uint PAGE_EXECUTE_READ = 32u;
private const int TlsHandlerRegionSize = 16384;
private const ulong TlsModuleAllocStart = 140726751354880uL; private const ulong TlsModuleAllocStart = 140726751354880uL;
private const ulong TlsModuleAllocStride = 65536uL; private const ulong TlsModuleAllocStride = 65536uL;
@@ -101,6 +107,8 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
private bool _ownsTlsBaseAddress; private bool _ownsTlsBaseAddress;
private int _tlsPatchStubOffset;
private nint _unresolvedReturnStub; private nint _unresolvedReturnStub;
private nint _rawExceptionHandler; private nint _rawExceptionHandler;
@@ -175,7 +183,11 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
private ulong _entryReturnSentinelRip; private ulong _entryReturnSentinelRip;
private readonly ulong[] _importLoopSignatures = new ulong[192]; private readonly ulong[] _importLoopSignatures = new ulong[ImportLoopHistoryLength];
private readonly ulong[] _importLoopNidHashes = new ulong[ImportLoopHistoryLength];
private readonly ulong[] _importLoopReturnRips = new ulong[ImportLoopHistoryLength];
private int _importLoopSignatureCount; private int _importLoopSignatureCount;
@@ -509,6 +521,14 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
Console.Error.WriteLine($"[LOADER][DEBUG] TryResolveDirectImportTarget: {nid} not in HLE table, checking runtime symbols..."); Console.Error.WriteLine($"[LOADER][DEBUG] TryResolveDirectImportTarget: {nid} not in HLE table, checking runtime symbols...");
if (TryResolveRuntimeSymbolAddress(nid, out var directValue) && IsDirectImportTargetUsable(directValue))
{
targetAddress = directValue;
resolvedSymbol = nid;
Console.Error.WriteLine($"[LOADER][DEBUG] TryResolveDirectImportTarget: {nid} -> runtime symbol 0x{targetAddress:X16}");
return true;
}
if (Aerolib.Instance.TryGetByNid(nid, out var symbolByNid)) if (Aerolib.Instance.TryGetByNid(nid, out var symbolByNid))
{ {
if (!PreferLleForLibcExport(symbolByNid.ExportName)) if (!PreferLleForLibcExport(symbolByNid.ExportName))
@@ -585,14 +605,6 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
{ {
return exportName switch return exportName switch
{ {
"_init_env" or
"atexit" or
"strlen" or
"strnlen" or
"strcmp" or
"strncmp" or
"strcpy" or
"strncpy" or
"memcpy" or "memcpy" or
"memmove" or "memmove" or
"memset" or "memset" or
@@ -838,10 +850,10 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
private unsafe void CreateTlsHandler() private unsafe void CreateTlsHandler()
{ {
_tlsHandlerAddress = (nint)TryAllocateNearEntry(256u); _tlsHandlerAddress = (nint)TryAllocateNearEntry(TlsHandlerRegionSize);
if (_tlsHandlerAddress == 0) if (_tlsHandlerAddress == 0)
{ {
_tlsHandlerAddress = (nint)VirtualAlloc(null, 256u, 12288u, 64u); _tlsHandlerAddress = (nint)VirtualAlloc(null, TlsHandlerRegionSize, 12288u, 64u);
} }
if (_tlsHandlerAddress == 0) if (_tlsHandlerAddress == 0)
{ {
@@ -854,9 +866,10 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
*(long*)(tlsHandlerAddress + num) = _tlsBaseAddress; *(long*)(tlsHandlerAddress + num) = _tlsBaseAddress;
num += 8; num += 8;
tlsHandlerAddress[num++] = 195; tlsHandlerAddress[num++] = 195;
_tlsPatchStubOffset = (num + 15) & ~15;
uint num2 = default(uint); uint num2 = default(uint);
VirtualProtect((void*)_tlsHandlerAddress, 256u, 32u, &num2); VirtualProtect((void*)_tlsHandlerAddress, TlsHandlerRegionSize, 32u, &num2);
FlushInstructionCache(GetCurrentProcess(), (void*)_tlsHandlerAddress, 256u); FlushInstructionCache(GetCurrentProcess(), (void*)_tlsHandlerAddress, TlsHandlerRegionSize);
Console.Error.WriteLine($"[LOADER][INFO] TLS handler at 0x{_tlsHandlerAddress:X16}"); Console.Error.WriteLine($"[LOADER][INFO] TLS handler at 0x{_tlsHandlerAddress:X16}");
} }
@@ -936,6 +949,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
ulong num2 = num + MaxScanBytes; ulong num2 = num + MaxScanBytes;
int num3 = 0; int num3 = 0;
int num4 = 0; int num4 = 0;
int num9 = 0;
while (num < num2) while (num < num2)
{ {
if (VirtualQuery((void*)num, out var lpBuffer, (nuint)sizeof(MEMORY_BASIC_INFORMATION64)) == 0 || lpBuffer.RegionSize == 0) if (VirtualQuery((void*)num, out var lpBuffer, (nuint)sizeof(MEMORY_BASIC_INFORMATION64)) == 0 || lpBuffer.RegionSize == 0)
@@ -964,6 +978,10 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
num3++; num3++;
PatchTlsInstruction(address); PatchTlsInstruction(address);
} }
else if (TryPatchTlsImmediateStoreInstruction(address, ptr + i))
{
num9++;
}
else if (TryPatchStackCanaryInstruction(address, ptr + i)) else if (TryPatchStackCanaryInstruction(address, ptr + i))
{ {
num4++; num4++;
@@ -972,7 +990,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
} }
num = num6 > num ? num6 : num + 4096uL; num = num6 > num ? num6 : num + 4096uL;
} }
Console.Error.WriteLine($"[LOADER][INFO] Patched {num3} TLS patterns, {num4} stack-canary accesses"); Console.Error.WriteLine($"[LOADER][INFO] Patched {num3} TLS loads, {num9} TLS stores, {num4} stack-canary accesses");
} }
private unsafe bool IsPatternMatch(byte* ptr, byte[] pattern) private unsafe bool IsPatternMatch(byte* ptr, byte[] pattern)
@@ -1086,6 +1104,115 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
} }
} }
private unsafe bool TryPatchTlsImmediateStoreInstruction(nint address, byte* source)
{
if (source[0] != 100 || source[1] != 199 || source[2] != 4 || source[3] != 37)
{
return false;
}
int tlsOffset = *(int*)(source + 4);
int immediateValue = *(int*)(source + 8);
nint num = CreateTlsImmediateStoreHelper(tlsOffset, immediateValue);
if (num == 0)
{
return false;
}
return PatchCallSite(address, 12, num);
}
private unsafe nint CreateTlsImmediateStoreHelper(int tlsOffset, int immediateValue)
{
nint num = AllocateTlsPatchStub(32);
if (num == 0)
{
return 0;
}
byte* ptr = (byte*)num;
int num2 = 0;
ptr[num2++] = 80;
ptr[num2++] = 232;
long num3 = _tlsHandlerAddress - (num + num2 + 4);
if (num3 < int.MinValue || num3 > int.MaxValue)
{
Console.Error.WriteLine($"[LOADER][WARNING] TLS store helper out of rel32 range at 0x{num:X16}");
return 0;
}
*(int*)(ptr + num2) = (int)num3;
num2 += 4;
ptr[num2++] = 199;
ptr[num2++] = 128;
*(int*)(ptr + num2) = tlsOffset;
num2 += 4;
*(int*)(ptr + num2) = immediateValue;
num2 += 4;
ptr[num2++] = 88;
ptr[num2++] = 195;
while (num2 < 32)
{
ptr[num2++] = 144;
}
uint flNewProtect = default(uint);
VirtualProtect((void*)num, 32u, 32u, &flNewProtect);
FlushInstructionCache(GetCurrentProcess(), (void*)num, 32u);
return num;
}
private unsafe nint AllocateTlsPatchStub(int size)
{
if (_tlsHandlerAddress == 0 || size <= 0)
{
return 0;
}
int num = (size + 15) & -16;
if (_tlsPatchStubOffset + num > TlsHandlerRegionSize)
{
Console.Error.WriteLine("[LOADER][WARNING] TLS patch stub region exhausted.");
return 0;
}
nint result = _tlsHandlerAddress + _tlsPatchStubOffset;
_tlsPatchStubOffset += num;
uint flNewProtect = default(uint);
if (!VirtualProtect((void*)result, (nuint)num, 64u, &flNewProtect))
{
return 0;
}
return result;
}
private unsafe bool PatchCallSite(nint address, int instructionLength, nint target)
{
if (instructionLength < 5)
{
return false;
}
uint flNewProtect = default(uint);
if (!VirtualProtect((void*)address, (nuint)instructionLength, 64u, &flNewProtect))
{
return false;
}
try
{
long num = target - (address + 5);
if (num < int.MinValue || num > int.MaxValue)
{
Console.Error.WriteLine($"[LOADER][WARNING] TLS patch out of rel32 range at 0x{address:X16}");
return false;
}
*(byte*)address = 232;
*(int*)(address + 1) = (int)num;
for (int i = 5; i < instructionLength; i++)
{
*(byte*)(address + i) = 144;
}
}
finally
{
VirtualProtect((void*)address, (nuint)instructionLength, flNewProtect, &flNewProtect);
FlushInstructionCache(GetCurrentProcess(), (void*)address, (nuint)instructionLength);
}
return true;
}
private unsafe void TryPreReservePrtAperture(ulong baseAddress, ulong size) private unsafe void TryPreReservePrtAperture(ulong baseAddress, ulong size)
{ {
if (VirtualQuery((void*)baseAddress, out var lpBuffer, (nuint)sizeof(MEMORY_BASIC_INFORMATION64)) != 0 && lpBuffer.State != 65536) if (VirtualQuery((void*)baseAddress, out var lpBuffer, (nuint)sizeof(MEMORY_BASIC_INFORMATION64)) != 0 && lpBuffer.State != 65536)
@@ -0,0 +1,10 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
namespace SharpEmu.Core.Loader;
public readonly record struct ImportedSymbolRelocation(
ulong TargetAddress,
long Addend,
string Nid,
bool IsData);
+16
View File
@@ -16,6 +16,10 @@ public sealed class SelfImage
IReadOnlyList<VirtualMemoryRegion> mappedRegions, IReadOnlyList<VirtualMemoryRegion> mappedRegions,
IReadOnlyDictionary<ulong, string>? importStubs = null, IReadOnlyDictionary<ulong, string>? importStubs = null,
IReadOnlyDictionary<string, ulong>? runtimeSymbols = null, IReadOnlyDictionary<string, ulong>? runtimeSymbols = null,
IReadOnlyList<ImportedSymbolRelocation>? importedRelocations = null,
IReadOnlyList<ulong>? preInitializerFunctions = null,
IReadOnlyList<ulong>? initializerFunctions = null,
ulong initFunctionEntryPoint = 0,
ulong imageBase = 0, ulong imageBase = 0,
ulong procParamAddress = 0) ulong procParamAddress = 0)
{ {
@@ -28,6 +32,10 @@ public sealed class SelfImage
MappedRegions = mappedRegions; MappedRegions = mappedRegions;
ImportStubs = importStubs ?? new Dictionary<ulong, string>(); ImportStubs = importStubs ?? new Dictionary<ulong, string>();
RuntimeSymbols = runtimeSymbols ?? new Dictionary<string, ulong>(StringComparer.Ordinal); RuntimeSymbols = runtimeSymbols ?? new Dictionary<string, ulong>(StringComparer.Ordinal);
ImportedRelocations = importedRelocations ?? Array.Empty<ImportedSymbolRelocation>();
PreInitializerFunctions = preInitializerFunctions ?? Array.Empty<ulong>();
InitializerFunctions = initializerFunctions ?? Array.Empty<ulong>();
InitFunctionEntryPoint = initFunctionEntryPoint;
_imageBase = imageBase; _imageBase = imageBase;
ProcParamAddress = procParamAddress; ProcParamAddress = procParamAddress;
} }
@@ -44,6 +52,14 @@ public sealed class SelfImage
public IReadOnlyDictionary<string, ulong> RuntimeSymbols { get; } public IReadOnlyDictionary<string, ulong> RuntimeSymbols { get; }
public IReadOnlyList<ImportedSymbolRelocation> ImportedRelocations { get; }
public IReadOnlyList<ulong> PreInitializerFunctions { get; }
public IReadOnlyList<ulong> InitializerFunctions { get; }
public ulong InitFunctionEntryPoint { get; }
public ulong EntryPoint => ElfHeader.EntryPoint + _imageBase; public ulong EntryPoint => ElfHeader.EntryPoint + _imageBase;
public ulong ProcParamAddress { get; } public ulong ProcParamAddress { get; }
+561 -176
View File
@@ -29,6 +29,7 @@ public sealed class SelfLoader : ISelfLoader
private const int ElfRelocationSize = 24; private const int ElfRelocationSize = 24;
private const int ElfSectionHeaderSize = 64; private const int ElfSectionHeaderSize = 64;
private const uint SectionTypeSymbolTable = 2; private const uint SectionTypeSymbolTable = 2;
private const uint SectionTypeRela = 4;
private const long DtNull = 0; private const long DtNull = 0;
private const long DtPltRelSize = 0x02; private const long DtPltRelSize = 0x02;
@@ -37,8 +38,13 @@ public sealed class SelfLoader : ISelfLoader
private const long DtSymTab = 0x06; private const long DtSymTab = 0x06;
private const long DtRela = 0x07; private const long DtRela = 0x07;
private const long DtRelaSize = 0x08; private const long DtRelaSize = 0x08;
private const long DtInit = 0x0C;
private const long DtStrSize = 0x0A; private const long DtStrSize = 0x0A;
private const long DtJmpRel = 0x17; private const long DtJmpRel = 0x17;
private const long DtInitArray = 0x19;
private const long DtInitArraySize = 0x1B;
private const long DtPreInitArray = 0x20;
private const long DtPreInitArraySize = 0x21;
private const long DtSceJmpRel = 0x61000029; private const long DtSceJmpRel = 0x61000029;
private const long DtScePltRelSize = 0x6100002D; private const long DtScePltRelSize = 0x6100002D;
private const long DtSceRela = 0x6100002F; private const long DtSceRela = 0x6100002F;
@@ -60,11 +66,12 @@ public sealed class SelfLoader : ISelfLoader
private const ulong Ps4ModuleSearchStart = 0x0000000002000000UL; private const ulong Ps4ModuleSearchStart = 0x0000000002000000UL;
private const ulong Ps4ModuleSearchEnd = 0x0000000040000000UL; private const ulong Ps4ModuleSearchEnd = 0x0000000040000000UL;
private const ulong ModulePlacementStep = 0x00200000UL; private const ulong ModulePlacementStep = 0x00200000UL;
private const ulong FocusRelocGuestStart = 0x00000008030FC300UL; private const ulong FocusRelocGuestStart = 0x0000000807BA25B0UL;
private const ulong FocusRelocGuestEnd = 0x00000008030FC3F0UL; private const ulong FocusRelocGuestEnd = 0x0000000807BA2608UL;
private const byte SymbolBindLocal = 0; private const byte SymbolBindLocal = 0;
private const byte SymbolBindGlobal = 1; private const byte SymbolBindGlobal = 1;
private const byte SymbolBindWeak = 2; private const byte SymbolBindWeak = 2;
private const byte SymbolTypeObject = 1;
private IModuleManager? _moduleManager; private IModuleManager? _moduleManager;
private uint _nextTlsModuleId = 1; private uint _nextTlsModuleId = 1;
@@ -72,6 +79,7 @@ public sealed class SelfLoader : ISelfLoader
private static readonly IReadOnlyDictionary<ulong, string> EmptyImportStubs = new Dictionary<ulong, string>(); private static readonly IReadOnlyDictionary<ulong, string> EmptyImportStubs = new Dictionary<ulong, string>();
private static readonly IReadOnlyDictionary<string, ulong> EmptyRuntimeSymbols = private static readonly IReadOnlyDictionary<string, ulong> EmptyRuntimeSymbols =
new Dictionary<string, ulong>(StringComparer.Ordinal); new Dictionary<string, ulong>(StringComparer.Ordinal);
private static readonly IReadOnlyList<ulong> EmptyInitializerFunctions = Array.Empty<ulong>();
private static readonly int SelfHeaderSize = Unsafe.SizeOf<SelfHeader>(); private static readonly int SelfHeaderSize = Unsafe.SizeOf<SelfHeader>();
private static readonly int SelfSegmentSize = Unsafe.SizeOf<SelfSegment>(); private static readonly int SelfSegmentSize = Unsafe.SizeOf<SelfSegment>();
private static readonly int ProgramHeaderSize = Unsafe.SizeOf<ProgramHeader>(); private static readonly int ProgramHeaderSize = Unsafe.SizeOf<ProgramHeader>();
@@ -190,11 +198,13 @@ public sealed class SelfLoader : ISelfLoader
var importStubs = ResolveAndPatchImportStubs( var importStubs = ResolveAndPatchImportStubs(
imageData, imageData,
loadContext, loadContext,
elfHeader,
programHeaders, programHeaders,
virtualMemory, virtualMemory,
imageBase, imageBase,
_moduleManager, _moduleManager,
tlsModuleId); tlsModuleId,
out var importedRelocations);
var effectiveImportStubs = importStubs.Count == 0 var effectiveImportStubs = importStubs.Count == 0
? new Dictionary<ulong, string>() ? new Dictionary<ulong, string>()
: new Dictionary<ulong, string>(importStubs); : new Dictionary<ulong, string>(importStubs);
@@ -214,6 +224,15 @@ public sealed class SelfLoader : ISelfLoader
var finalizedRuntimeSymbols = runtimeSymbols.Count == 0 var finalizedRuntimeSymbols = runtimeSymbols.Count == 0
? EmptyRuntimeSymbols ? EmptyRuntimeSymbols
: runtimeSymbols; : runtimeSymbols;
CollectInitializerFunctions(
imageData,
loadContext,
programHeaders,
virtualMemory,
imageBase,
out var initFunctionEntryPoint,
out var preInitializerFunctions,
out var initializerFunctions);
var procParamAddress = ResolveProcParamAddress(programHeaders, imageBase); var procParamAddress = ResolveProcParamAddress(programHeaders, imageBase);
Console.WriteLine($"[LOADER] ELF e_entry: 0x{elfHeader.EntryPoint:X16}"); Console.WriteLine($"[LOADER] ELF e_entry: 0x{elfHeader.EntryPoint:X16}");
@@ -246,6 +265,10 @@ public sealed class SelfLoader : ISelfLoader
virtualMemory.SnapshotRegions(), virtualMemory.SnapshotRegions(),
finalizedImportStubs, finalizedImportStubs,
finalizedRuntimeSymbols, finalizedRuntimeSymbols,
importedRelocations,
preInitializerFunctions,
initializerFunctions,
initFunctionEntryPoint,
imageBase, imageBase,
procParamAddress); procParamAddress);
} }
@@ -426,12 +449,15 @@ public sealed class SelfLoader : ISelfLoader
private static IReadOnlyDictionary<ulong, string> ResolveAndPatchImportStubs( private static IReadOnlyDictionary<ulong, string> ResolveAndPatchImportStubs(
ReadOnlySpan<byte> imageData, ReadOnlySpan<byte> imageData,
LoadContext loadContext, LoadContext loadContext,
ElfHeader elfHeader,
IReadOnlyList<ProgramHeader> programHeaders, IReadOnlyList<ProgramHeader> programHeaders,
IVirtualMemory virtualMemory, IVirtualMemory virtualMemory,
ulong imageBase, ulong imageBase,
IModuleManager? moduleManager, IModuleManager? moduleManager,
uint tlsModuleId) uint tlsModuleId,
out IReadOnlyList<ImportedSymbolRelocation> importedRelocations)
{ {
importedRelocations = Array.Empty<ImportedSymbolRelocation>();
if (!TryGetProgramHeader(programHeaders, ProgramHeaderType.Dynamic, out var dynamicHeader, out var dynamicHeaderIndex)) if (!TryGetProgramHeader(programHeaders, ProgramHeaderType.Dynamic, out var dynamicHeader, out var dynamicHeaderIndex))
{ {
return EmptyImportStubs; return EmptyImportStubs;
@@ -447,10 +473,18 @@ public sealed class SelfLoader : ISelfLoader
throw new NotSupportedException("Dynamic metadata segments larger than 2 GB are not currently supported."); throw new NotSupportedException("Dynamic metadata segments larger than 2 GB are not currently supported.");
} }
var dynamicOffset = ResolvePhysicalSegmentOffset(imageData.Length, loadContext, dynamicHeader, dynamicHeaderIndex); if (!TryLoadDynamicTableBytes(
EnsureRange(imageData.Length, dynamicOffset, dynamicHeader.FileSize); imageData,
loadContext,
virtualMemory,
imageBase,
dynamicHeader,
dynamicHeaderIndex,
out var dynamicTable))
{
return EmptyImportStubs;
}
var dynamicTable = imageData.Slice((int)dynamicOffset, (int)dynamicHeader.FileSize);
var elfData = imageData; var elfData = imageData;
var dynamicInfo = ParseDynamicInfo(dynamicTable); var dynamicInfo = ParseDynamicInfo(dynamicTable);
@@ -463,13 +497,6 @@ public sealed class SelfLoader : ISelfLoader
Console.WriteLine($"[LOADER] TLS module id: {tlsModuleId}"); Console.WriteLine($"[LOADER] TLS module id: {tlsModuleId}");
Console.WriteLine($"[LOADER] HasImportMetadata: {dynamicInfo.HasImportMetadata}"); Console.WriteLine($"[LOADER] HasImportMetadata: {dynamicInfo.HasImportMetadata}");
if (!dynamicInfo.HasImportMetadata)
{
Console.WriteLine($"[LOADER] No import metadata found in ELF!");
return EmptyImportStubs;
}
Console.WriteLine($"[LOADER] ImageBase runtime: 0x{imageBase:X16}");
var relocations = new List<ElfRelocation>(512); var relocations = new List<ElfRelocation>(512);
if (dynamicInfo.RelaSize != 0 && if (dynamicInfo.RelaSize != 0 &&
@@ -484,13 +511,16 @@ public sealed class SelfLoader : ISelfLoader
CollectRelocations(jmpRelBytes, relocations); CollectRelocations(jmpRelBytes, relocations);
} }
if (relocations.Count == 0) if (!dynamicInfo.HasImportMetadata)
{ {
Console.WriteLine($"[LOADER] No relocations found!"); Console.WriteLine($"[LOADER] No import metadata found in ELF!");
return EmptyImportStubs;
} }
Console.WriteLine($"[LOADER] Processing {relocations.Count} relocations..."); if (relocations.Count != 0)
{
Console.WriteLine($"[LOADER] ImageBase runtime: 0x{imageBase:X16}");
Console.WriteLine($"[LOADER] Processing {relocations.Count} relocations...");
}
uint maxSymbolIndex = 0; uint maxSymbolIndex = 0;
foreach (var relocation in relocations) foreach (var relocation in relocations)
@@ -544,164 +574,37 @@ public sealed class SelfLoader : ISelfLoader
var descriptors = new List<RelocationDescriptor>(256); var descriptors = new List<RelocationDescriptor>(256);
var orderedImportNids = new List<string>(128); var orderedImportNids = new List<string>(128);
var seenImportNids = new HashSet<string>(StringComparer.Ordinal); var seenImportNids = new HashSet<string>(StringComparer.Ordinal);
var skippedUnsupported = 0; AppendRelocationDescriptors(
var skippedUnmapped = 0; relocations,
var skippedSymbolRead = 0; symbolTable,
var skippedNonImportBind = 0; stringTable,
var skippedNameRead = 0; virtualMemory,
var skippedEmptyNid = 0; imageBase,
foreach (var relocation in relocations) tlsModuleId,
descriptors,
orderedImportNids,
seenImportNids);
if (descriptors.Count == 0)
{ {
if (IsFocusRelocationOffset(relocation.Offset, imageBase)) var sectionFallbackRelocCount = AppendSectionRelocationDescriptors(
imageData,
loadContext,
elfHeader,
virtualMemory,
imageBase,
tlsModuleId,
descriptors,
orderedImportNids,
seenImportNids);
if (sectionFallbackRelocCount != 0)
{ {
Console.Error.WriteLine( Console.WriteLine(
$"[LOADER][FOCUS][SCAN] off=0x{relocation.Offset:X16} type={relocation.Type} sym={relocation.SymbolIndex} addend=0x{relocation.Addend:X}"); $"[LOADER] Section relocation fallback recovered {sectionFallbackRelocCount} relocation entries, {orderedImportNids.Count} unique NIDs, {descriptors.Count} descriptors");
} }
if (!IsSupportedRelocationType(relocation.Type))
{
skippedUnsupported++;
if (IsFocusRelocationOffset(relocation.Offset, imageBase))
{
Console.Error.WriteLine($"[LOADER][FOCUS][SKIP] unsupported type={relocation.Type}");
}
continue;
}
if (!TryResolveMappedAddress(virtualMemory, relocation.Offset, imageBase, sizeof(ulong), out var targetAddress))
{
skippedUnmapped++;
if (IsFocusRelocationOffset(relocation.Offset, imageBase))
{
Console.Error.WriteLine("[LOADER][FOCUS][SKIP] target address not mapped");
}
continue;
}
if (relocation.Type == RelocationTypeRelative)
{
descriptors.Add(new RelocationDescriptor(
targetAddress,
relocation.Addend,
null,
imageBase,
RelocationValueKind.Pointer));
continue;
}
if (relocation.Type == RelocationTypeTlsModuleId)
{
var dtpmodValue = tlsModuleId == 0 ? 1u : tlsModuleId;
descriptors.Add(new RelocationDescriptor(
targetAddress,
0,
null,
dtpmodValue,
RelocationValueKind.TlsModuleId));
continue;
}
var symbolIndex = relocation.SymbolIndex;
ElfSymbol symbol;
if (symbolIndex == 0)
{
symbol = default;
}
else if (!TryReadSymbol(symbolTable, symbolIndex, out symbol))
{
skippedSymbolRead++;
if (targetAddress >= FocusRelocGuestStart && targetAddress <= FocusRelocGuestEnd)
{
Console.Error.WriteLine($"[LOADER][FOCUS][SKIP] symbol read failed index={symbolIndex}");
}
continue;
}
var addend = relocation.Type is RelocationTypeGlobalData or RelocationTypeJumpSlot ? 0 : relocation.Addend;
var symbolBind = GetSymbolBind(symbol.Info);
if (symbolBind == SymbolBindLocal)
{
var symbolAddress = ResolveMappedAddressOrFallback(virtualMemory, symbol.Value, imageBase);
if (symbolAddress == 0)
{
Console.Error.WriteLine(
$"[LOADER] Skipping local relocation with invalid symbol value 0x{symbol.Value:X} " +
$"at target 0x{targetAddress:X16}, type={relocation.Type}, sym={symbolIndex}");
continue;
}
descriptors.Add(new RelocationDescriptor(
targetAddress,
addend,
null,
symbolAddress,
RelocationValueKind.Pointer));
continue;
}
if (symbol.Value != 0)
{
var symbolAddress = ResolveMappedAddressOrFallback(virtualMemory, symbol.Value, imageBase);
if (symbolAddress == 0)
{
Console.Error.WriteLine(
$"[LOADER] Skipping relocation with invalid symbol value 0x{symbol.Value:X} " +
$"at target 0x{targetAddress:X16}, type={relocation.Type}, sym={symbolIndex}");
continue;
}
descriptors.Add(new RelocationDescriptor(
targetAddress,
addend,
null,
symbolAddress,
RelocationValueKind.Pointer));
continue;
}
if (symbolBind is not (SymbolBindGlobal or SymbolBindWeak))
{
skippedNonImportBind++;
if (targetAddress >= FocusRelocGuestStart && targetAddress <= FocusRelocGuestEnd)
{
Console.Error.WriteLine($"[LOADER][FOCUS][SKIP] bind={symbolBind} not importable");
}
continue;
}
if (!TryReadNullTerminatedAscii(stringTable, symbol.NameOffset, out var symbolName))
{
skippedNameRead++;
if (targetAddress >= FocusRelocGuestStart && targetAddress <= FocusRelocGuestEnd)
{
Console.Error.WriteLine($"[LOADER][FOCUS][SKIP] symbol name read failed offset={symbol.NameOffset}");
}
continue;
}
var nid = ExtractNid(symbolName);
if (string.IsNullOrWhiteSpace(nid))
{
skippedEmptyNid++;
continue;
}
if (seenImportNids.Add(nid))
{
orderedImportNids.Add(nid);
}
descriptors.Add(new RelocationDescriptor(
targetAddress,
addend,
nid,
0,
RelocationValueKind.Pointer));
} }
Console.WriteLine($"[LOADER] Found {orderedImportNids.Count} unique NIDs, {descriptors.Count} descriptors"); Console.WriteLine($"[LOADER] Found {orderedImportNids.Count} unique NIDs, {descriptors.Count} descriptors");
Console.WriteLine(
$"[LOADER] Reloc skip stats: unsupported={skippedUnsupported}, unmapped={skippedUnmapped}, symbolRead={skippedSymbolRead}, nonImportBind={skippedNonImportBind}, nameRead={skippedNameRead}, emptyNid={skippedEmptyNid}");
if (descriptors.Count == 0) if (descriptors.Count == 0)
{ {
@@ -709,6 +612,8 @@ public sealed class SelfLoader : ISelfLoader
return EmptyImportStubs; return EmptyImportStubs;
} }
importedRelocations = BuildImportedRelocations(descriptors);
var stubsByAddress = CreateImportStubMapping(virtualMemory, orderedImportNids); var stubsByAddress = CreateImportStubMapping(virtualMemory, orderedImportNids);
Console.WriteLine($"[LOADER] Created {stubsByAddress.Count} import stubs"); Console.WriteLine($"[LOADER] Created {stubsByAddress.Count} import stubs");
@@ -784,6 +689,237 @@ public sealed class SelfLoader : ISelfLoader
return stubsByAddress; return stubsByAddress;
} }
private static int AppendSectionRelocationDescriptors(
ReadOnlySpan<byte> imageData,
LoadContext loadContext,
ElfHeader elfHeader,
IVirtualMemory virtualMemory,
ulong imageBase,
uint tlsModuleId,
ICollection<RelocationDescriptor> descriptors,
IList<string> orderedImportNids,
ISet<string> seenImportNids)
{
if (elfHeader.SectionHeaderOffset == 0 ||
elfHeader.SectionHeaderCount == 0 ||
elfHeader.SectionHeaderEntrySize < ElfSectionHeaderSize)
{
return 0;
}
var appendedRelocations = 0;
for (var sectionIndex = 0; sectionIndex < elfHeader.SectionHeaderCount; sectionIndex++)
{
if (!TryReadSectionHeader(imageData, loadContext, elfHeader, sectionIndex, out var relocationHeader) ||
relocationHeader.Type != SectionTypeRela ||
relocationHeader.Size == 0 ||
relocationHeader.EntrySize < ElfRelocationSize)
{
continue;
}
if (!TryReadElfRelativeSlice(imageData, loadContext, relocationHeader.Offset, relocationHeader.Size, out var relocationTable))
{
continue;
}
var relocations = new List<ElfRelocation>(checked((int)(relocationHeader.Size / relocationHeader.EntrySize)));
CollectRelocations(relocationTable, relocations);
if (relocations.Count == 0)
{
continue;
}
ReadOnlySpan<byte> symbolTable = ReadOnlySpan<byte>.Empty;
ReadOnlySpan<byte> stringTable = ReadOnlySpan<byte>.Empty;
if (relocationHeader.Link < elfHeader.SectionHeaderCount &&
TryReadSectionHeader(imageData, loadContext, elfHeader, (int)relocationHeader.Link, out var symbolHeader) &&
symbolHeader.Size != 0 &&
symbolHeader.EntrySize >= ElfSymbolSize &&
TryReadElfRelativeSlice(imageData, loadContext, symbolHeader.Offset, symbolHeader.Size, out symbolTable) &&
symbolHeader.Link < elfHeader.SectionHeaderCount &&
TryReadSectionHeader(imageData, loadContext, elfHeader, (int)symbolHeader.Link, out var stringHeader) &&
stringHeader.Size != 0 &&
TryReadElfRelativeSlice(imageData, loadContext, stringHeader.Offset, stringHeader.Size, out stringTable))
{
}
AppendRelocationDescriptors(
relocations,
symbolTable,
stringTable,
virtualMemory,
imageBase,
tlsModuleId,
descriptors,
orderedImportNids,
seenImportNids);
appendedRelocations += relocations.Count;
}
return appendedRelocations;
}
private static void AppendRelocationDescriptors(
IReadOnlyList<ElfRelocation> relocations,
ReadOnlySpan<byte> symbolTable,
ReadOnlySpan<byte> stringTable,
IVirtualMemory virtualMemory,
ulong imageBase,
uint tlsModuleId,
ICollection<RelocationDescriptor> descriptors,
IList<string> orderedImportNids,
ISet<string> seenImportNids)
{
foreach (var relocation in relocations)
{
if (IsFocusRelocationOffset(relocation.Offset, imageBase))
{
Console.Error.WriteLine(
$"[LOADER][FOCUS][SCAN] off=0x{relocation.Offset:X16} type={relocation.Type} sym={relocation.SymbolIndex} addend=0x{relocation.Addend:X}");
}
if (!IsSupportedRelocationType(relocation.Type))
{
if (IsFocusRelocationOffset(relocation.Offset, imageBase))
{
Console.Error.WriteLine($"[LOADER][FOCUS][SKIP] unsupported type={relocation.Type}");
}
continue;
}
if (!TryResolveMappedAddress(virtualMemory, relocation.Offset, imageBase, sizeof(ulong), out var targetAddress))
{
if (IsFocusRelocationOffset(relocation.Offset, imageBase))
{
Console.Error.WriteLine("[LOADER][FOCUS][SKIP] target address not mapped");
}
continue;
}
if (relocation.Type == RelocationTypeRelative)
{
descriptors.Add(new RelocationDescriptor(
targetAddress,
relocation.Addend,
null,
imageBase,
RelocationValueKind.Pointer,
IsDataImport: false));
continue;
}
if (relocation.Type == RelocationTypeTlsModuleId)
{
var dtpmodValue = tlsModuleId == 0 ? 1u : tlsModuleId;
descriptors.Add(new RelocationDescriptor(
targetAddress,
0,
null,
dtpmodValue,
RelocationValueKind.TlsModuleId,
IsDataImport: false));
continue;
}
var symbolIndex = relocation.SymbolIndex;
ElfSymbol symbol;
if (symbolIndex == 0)
{
symbol = default;
}
else if (!TryReadSymbol(symbolTable, symbolIndex, out symbol))
{
if (targetAddress >= FocusRelocGuestStart && targetAddress <= FocusRelocGuestEnd)
{
Console.Error.WriteLine($"[LOADER][FOCUS][SKIP] symbol read failed index={symbolIndex}");
}
continue;
}
var addend = relocation.Type is RelocationTypeGlobalData or RelocationTypeJumpSlot ? 0 : relocation.Addend;
var symbolBind = GetSymbolBind(symbol.Info);
if (symbolBind == SymbolBindLocal)
{
var symbolAddress = ResolveMappedAddressOrFallback(virtualMemory, symbol.Value, imageBase);
if (symbolAddress == 0)
{
Console.Error.WriteLine(
$"[LOADER] Skipping local relocation with invalid symbol value 0x{symbol.Value:X} " +
$"at target 0x{targetAddress:X16}, type={relocation.Type}, sym={symbolIndex}");
continue;
}
descriptors.Add(new RelocationDescriptor(
targetAddress,
addend,
null,
symbolAddress,
RelocationValueKind.Pointer,
IsDataImport: false));
continue;
}
if (symbol.Value != 0)
{
var symbolAddress = ResolveMappedAddressOrFallback(virtualMemory, symbol.Value, imageBase);
if (symbolAddress == 0)
{
Console.Error.WriteLine(
$"[LOADER] Skipping relocation with invalid symbol value 0x{symbol.Value:X} " +
$"at target 0x{targetAddress:X16}, type={relocation.Type}, sym={symbolIndex}");
continue;
}
descriptors.Add(new RelocationDescriptor(
targetAddress,
addend,
null,
symbolAddress,
RelocationValueKind.Pointer,
IsDataImport: false));
continue;
}
if (symbolBind is not (SymbolBindGlobal or SymbolBindWeak))
{
if (targetAddress >= FocusRelocGuestStart && targetAddress <= FocusRelocGuestEnd)
{
Console.Error.WriteLine($"[LOADER][FOCUS][SKIP] bind={symbolBind} not importable");
}
continue;
}
if (!TryReadNullTerminatedAscii(stringTable, symbol.NameOffset, out var symbolName))
{
if (targetAddress >= FocusRelocGuestStart && targetAddress <= FocusRelocGuestEnd)
{
Console.Error.WriteLine($"[LOADER][FOCUS][SKIP] symbol name read failed offset={symbol.NameOffset}");
}
continue;
}
var nid = ExtractNid(symbolName);
if (string.IsNullOrWhiteSpace(nid))
{
continue;
}
if (seenImportNids.Add(nid))
{
orderedImportNids.Add(nid);
}
descriptors.Add(new RelocationDescriptor(
targetAddress,
addend,
nid,
0,
RelocationValueKind.Pointer,
IsDataImport: GetSymbolType(symbol.Info) == SymbolTypeObject));
}
}
private static void RegisterRuntimeSymbolsAndHooks( private static void RegisterRuntimeSymbolsAndHooks(
ReadOnlySpan<byte> imageData, ReadOnlySpan<byte> imageData,
LoadContext loadContext, LoadContext loadContext,
@@ -811,6 +947,159 @@ public sealed class SelfLoader : ISelfLoader
} }
} }
private static void CollectInitializerFunctions(
ReadOnlySpan<byte> imageData,
LoadContext loadContext,
IReadOnlyList<ProgramHeader> programHeaders,
IVirtualMemory virtualMemory,
ulong imageBase,
out ulong initFunctionEntryPoint,
out IReadOnlyList<ulong> preInitializerFunctions,
out IReadOnlyList<ulong> initializerFunctions)
{
initFunctionEntryPoint = 0;
preInitializerFunctions = EmptyInitializerFunctions;
initializerFunctions = EmptyInitializerFunctions;
if (!TryGetProgramHeader(programHeaders, ProgramHeaderType.Dynamic, out var dynamicHeader, out var dynamicHeaderIndex) ||
dynamicHeader.FileSize == 0)
{
return;
}
if (!TryLoadDynamicTableBytes(
imageData,
loadContext,
virtualMemory,
imageBase,
dynamicHeader,
dynamicHeaderIndex,
out var dynamicTable))
{
return;
}
var dynamicInfo = ParseDynamicInfo(dynamicTable);
var preInitializers = new List<ulong>(4);
var initializers = new List<ulong>(8);
initFunctionEntryPoint = ResolveMappedAddressOrFallback(virtualMemory, dynamicInfo.InitOffset, imageBase);
if (initFunctionEntryPoint < 0x10000)
{
initFunctionEntryPoint = 0;
}
AppendInitializerArrayEntries(
preInitializers,
imageData,
virtualMemory,
imageBase,
dynamicInfo.PreInitArrayOffset,
dynamicInfo.PreInitArraySize);
AppendResolvedInitializer(initializers, dynamicInfo.InitOffset, virtualMemory, imageBase);
AppendInitializerArrayEntries(
initializers,
imageData,
virtualMemory,
imageBase,
dynamicInfo.InitArrayOffset,
dynamicInfo.InitArraySize);
if (preInitializers.Count != 0)
{
preInitializerFunctions = preInitializers;
}
if (initializers.Count != 0)
{
initializerFunctions = initializers;
}
if (preInitializers.Count != 0 || initializers.Count != 0)
{
Console.Error.WriteLine(
$"[LOADER] Initializers discovered: preinit={preInitializers.Count}, init={initializers.Count}");
}
}
private static void AppendInitializerArrayEntries(
ICollection<ulong> destination,
ReadOnlySpan<byte> imageData,
IVirtualMemory virtualMemory,
ulong imageBase,
ulong arrayOffset,
ulong arraySize)
{
if (arrayOffset == 0 || arraySize < sizeof(ulong))
{
return;
}
if (!TryLoadTableBytes(imageData, virtualMemory, imageBase, arrayOffset, arraySize, out var arrayBytes))
{
return;
}
var entryCount = arrayBytes.Length / sizeof(ulong);
for (var i = 0; i < entryCount; i++)
{
var entryOffset = i * sizeof(ulong);
var entryAddress = BinaryPrimitives.ReadUInt64LittleEndian(arrayBytes.AsSpan(entryOffset, sizeof(ulong)));
AppendResolvedInitializer(destination, entryAddress, virtualMemory, imageBase);
}
}
private static void AppendResolvedInitializer(
ICollection<ulong> destination,
ulong functionAddress,
IVirtualMemory virtualMemory,
ulong imageBase)
{
var resolvedAddress = ResolveMappedAddressOrFallback(virtualMemory, functionAddress, imageBase);
if (resolvedAddress < 0x10000)
{
return;
}
foreach (var existing in destination)
{
if (existing == resolvedAddress)
{
return;
}
}
destination.Add(resolvedAddress);
}
private static IReadOnlyList<ImportedSymbolRelocation> BuildImportedRelocations(
IReadOnlyList<RelocationDescriptor> descriptors)
{
if (descriptors.Count == 0)
{
return Array.Empty<ImportedSymbolRelocation>();
}
var importedRelocations = new List<ImportedSymbolRelocation>(descriptors.Count);
foreach (var descriptor in descriptors)
{
if (descriptor.ImportNid is null || descriptor.ValueKind != RelocationValueKind.Pointer)
{
continue;
}
importedRelocations.Add(new ImportedSymbolRelocation(
descriptor.TargetAddress,
descriptor.Addend,
descriptor.ImportNid,
descriptor.IsDataImport));
}
return importedRelocations.Count == 0
? Array.Empty<ImportedSymbolRelocation>()
: importedRelocations;
}
private static int RegisterSectionRuntimeSymbols( private static int RegisterSectionRuntimeSymbols(
ReadOnlySpan<byte> imageData, ReadOnlySpan<byte> imageData,
LoadContext loadContext, LoadContext loadContext,
@@ -909,9 +1198,18 @@ public sealed class SelfLoader : ISelfLoader
return 0; return 0;
} }
var dynamicOffset = ResolvePhysicalSegmentOffset(imageData.Length, loadContext, dynamicHeader, dynamicHeaderIndex); if (!TryLoadDynamicTableBytes(
EnsureRange(imageData.Length, dynamicOffset, dynamicHeader.FileSize); imageData,
var dynamicTable = imageData.Slice((int)dynamicOffset, (int)dynamicHeader.FileSize); loadContext,
virtualMemory,
imageBase,
dynamicHeader,
dynamicHeaderIndex,
out var dynamicTable))
{
return 0;
}
var dynamicInfo = ParseDynamicInfo(dynamicTable); var dynamicInfo = ParseDynamicInfo(dynamicTable);
if (dynamicInfo.SymTabOffset == 0 || dynamicInfo.StrTabOffset == 0) if (dynamicInfo.SymTabOffset == 0 || dynamicInfo.StrTabOffset == 0)
{ {
@@ -1044,6 +1342,37 @@ public sealed class SelfLoader : ISelfLoader
return true; return true;
} }
private static bool TryLoadDynamicTableBytes(
ReadOnlySpan<byte> imageData,
LoadContext loadContext,
IVirtualMemory virtualMemory,
ulong imageBase,
ProgramHeader dynamicHeader,
int dynamicHeaderIndex,
out ReadOnlySpan<byte> dynamicTable)
{
if (TryLoadTableBytes(
imageData,
virtualMemory,
imageBase,
dynamicHeader.VirtualAddress,
dynamicHeader.FileSize,
out var loadedDynamicTable))
{
dynamicTable = loadedDynamicTable;
return true;
}
var dynamicOffset = ResolvePhysicalSegmentOffset(imageData.Length, loadContext, dynamicHeader, dynamicHeaderIndex);
if (!TrySlice(imageData, dynamicOffset, dynamicHeader.FileSize, out dynamicTable))
{
dynamicTable = default;
return false;
}
return true;
}
private static bool TryReadElfRelativeSlice( private static bool TryReadElfRelativeSlice(
ReadOnlySpan<byte> imageData, ReadOnlySpan<byte> imageData,
LoadContext loadContext, LoadContext loadContext,
@@ -1185,11 +1514,16 @@ public sealed class SelfLoader : ISelfLoader
ulong strTabSize = 0; ulong strTabSize = 0;
ulong symTabOffset = 0; ulong symTabOffset = 0;
ulong symTabSize = 0; ulong symTabSize = 0;
ulong initOffset = 0;
ulong relaOffset = 0; ulong relaOffset = 0;
ulong relaSize = 0; ulong relaSize = 0;
ulong jmpRelOffset = 0; ulong jmpRelOffset = 0;
ulong jmpRelSize = 0; ulong jmpRelSize = 0;
ulong pltGotOffset = 0; ulong pltGotOffset = 0;
ulong initArrayOffset = 0;
ulong initArraySize = 0;
ulong preInitArrayOffset = 0;
ulong preInitArraySize = 0;
for (var offset = 0; offset + DynamicEntrySize <= dynamicTable.Length; offset += DynamicEntrySize) for (var offset = 0; offset + DynamicEntrySize <= dynamicTable.Length; offset += DynamicEntrySize)
{ {
@@ -1236,6 +1570,13 @@ public sealed class SelfLoader : ISelfLoader
relaSize = value; relaSize = value;
} }
break;
case DtInit:
if (initOffset == 0)
{
initOffset = value;
}
break; break;
case DtJmpRel: case DtJmpRel:
if (jmpRelOffset == 0) if (jmpRelOffset == 0)
@@ -1243,6 +1584,34 @@ public sealed class SelfLoader : ISelfLoader
jmpRelOffset = value; jmpRelOffset = value;
} }
break;
case DtInitArray:
if (initArrayOffset == 0)
{
initArrayOffset = value;
}
break;
case DtInitArraySize:
if (initArraySize == 0)
{
initArraySize = value;
}
break;
case DtPreInitArray:
if (preInitArrayOffset == 0)
{
preInitArrayOffset = value;
}
break;
case DtPreInitArraySize:
if (preInitArraySize == 0)
{
preInitArraySize = value;
}
break; break;
case DtPltRelSize: case DtPltRelSize:
if (jmpRelSize == 0) if (jmpRelSize == 0)
@@ -1290,11 +1659,16 @@ public sealed class SelfLoader : ISelfLoader
strTabSize, strTabSize,
symTabOffset, symTabOffset,
symTabSize, symTabSize,
initOffset,
relaOffset, relaOffset,
relaSize, relaSize,
jmpRelOffset, jmpRelOffset,
jmpRelSize, jmpRelSize,
pltGotOffset); pltGotOffset,
initArrayOffset,
initArraySize,
preInitArrayOffset,
preInitArraySize);
} }
private static bool IsSupportedRelocationType(uint relocationType) private static bool IsSupportedRelocationType(uint relocationType)
@@ -1512,6 +1886,11 @@ public sealed class SelfLoader : ISelfLoader
return (byte)(info >> 4); return (byte)(info >> 4);
} }
private static byte GetSymbolType(byte info)
{
return (byte)(info & 0x0F);
}
private static bool IsFocusRelocationOffset(ulong relocationOffset, ulong imageBase) private static bool IsFocusRelocationOffset(ulong relocationOffset, ulong imageBase)
{ {
if (relocationOffset >= FocusRelocGuestStart && relocationOffset <= FocusRelocGuestEnd) if (relocationOffset >= FocusRelocGuestStart && relocationOffset <= FocusRelocGuestEnd)
@@ -1896,11 +2275,16 @@ public sealed class SelfLoader : ISelfLoader
ulong StrTabSize, ulong StrTabSize,
ulong SymTabOffset, ulong SymTabOffset,
ulong SymTabSize, ulong SymTabSize,
ulong InitOffset,
ulong RelaOffset, ulong RelaOffset,
ulong RelaSize, ulong RelaSize,
ulong JmpRelOffset, ulong JmpRelOffset,
ulong JmpRelSize, ulong JmpRelSize,
ulong PltGotOffset) ulong PltGotOffset,
ulong InitArrayOffset,
ulong InitArraySize,
ulong PreInitArrayOffset,
ulong PreInitArraySize)
{ {
public bool HasImportMetadata => public bool HasImportMetadata =>
StrTabOffset != 0 && StrTabOffset != 0 &&
@@ -1947,7 +2331,8 @@ public sealed class SelfLoader : ISelfLoader
long Addend, long Addend,
string? ImportNid, string? ImportNid,
ulong SymbolValue, ulong SymbolValue,
RelocationValueKind ValueKind); RelocationValueKind ValueKind,
bool IsDataImport);
private enum SelfSegmentResolveStatus private enum SelfSegmentResolveStatus
{ {
@@ -11,6 +11,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IDisposable
{ {
private readonly object _gate = new(); private readonly object _gate = new();
private readonly List<MemoryRegion> _regions = new(); private readonly List<MemoryRegion> _regions = new();
private readonly Dictionary<ulong, ProgramHeaderFlags> _pageProtections = new();
private bool _disposed; private bool _disposed;
private const ulong PageSize = 0x1000; private const ulong PageSize = 0x1000;
private const ulong LargeDataReserveThreshold = 0x4000_0000UL; // 1 GiB private const ulong LargeDataReserveThreshold = 0x4000_0000UL; // 1 GiB
@@ -24,6 +25,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IDisposable
private const uint PAGE_EXECUTE_READWRITE = 0x40; private const uint PAGE_EXECUTE_READWRITE = 0x40;
private const uint PAGE_EXECUTE = 0x10; private const uint PAGE_EXECUTE = 0x10;
private const uint PAGE_EXECUTE_WRITECOPY = 0x80; private const uint PAGE_EXECUTE_WRITECOPY = 0x80;
private const uint PAGE_NOACCESS = 0x01;
private const uint PAGE_READWRITE = 0x04; private const uint PAGE_READWRITE = 0x04;
private const uint PAGE_READONLY = 0x02; private const uint PAGE_READONLY = 0x02;
@@ -38,6 +40,9 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IDisposable
[return: MarshalAs(UnmanagedType.Bool)] [return: MarshalAs(UnmanagedType.Bool)]
private static extern bool VirtualProtect(void* lpAddress, nuint dwSize, uint flNewProtect, out uint lpflOldProtect); private static extern bool VirtualProtect(void* lpAddress, nuint dwSize, uint flNewProtect, out uint lpflOldProtect);
[DllImport("kernel32.dll")]
private static extern nuint VirtualQuery(void* lpAddress, out MemoryBasicInformation64 lpBuffer, nuint dwLength);
[DllImport("kernel32.dll")] [DllImport("kernel32.dll")]
private static extern void FlushInstructionCache(void* hProcess, void* lpBaseAddress, nuint dwSize); private static extern void FlushInstructionCache(void* hProcess, void* lpBaseAddress, nuint dwSize);
@@ -219,6 +224,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IDisposable
VirtualFree((void*)region.VirtualAddress, 0, MEM_RELEASE); VirtualFree((void*)region.VirtualAddress, 0, MEM_RELEASE);
} }
_regions.Clear(); _regions.Clear();
_pageProtections.Clear();
} }
} }
@@ -264,17 +270,32 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IDisposable
NativeMemory.Clear((void*)(virtualAddress + (ulong)fileData.Length), (nuint)zeroFillSize); NativeMemory.Clear((void*)(virtualAddress + (ulong)fileData.Length), (nuint)zeroFillSize);
} }
SetProtection(mapStart, mapSize, protection); ApplySegmentProtection(mapStart, mapEnd, protection);
Console.Error.WriteLine($"[VMEM] Mapped segment: 0x{virtualAddress:X16} - 0x{virtualAddress + memorySize:X16} (file: {fileData.Length} bytes, prot: {protection})"); Console.Error.WriteLine($"[VMEM] Mapped segment: 0x{virtualAddress:X16} - 0x{virtualAddress + memorySize:X16} (file: {fileData.Length} bytes, prot: {protection})");
} }
} }
private void ApplySegmentProtection(ulong mapStart, ulong mapEnd, ProgramHeaderFlags flags)
{
for (var pageAddress = mapStart; pageAddress < mapEnd; pageAddress += PageSize)
{
_pageProtections.TryGetValue(pageAddress, out var existingFlags);
var mergedFlags = existingFlags | flags;
_pageProtections[pageAddress] = mergedFlags;
SetProtection(pageAddress, PageSize, mergedFlags);
}
}
private void SetProtection(ulong address, ulong size, ProgramHeaderFlags flags) private void SetProtection(ulong address, ulong size, ProgramHeaderFlags flags)
{ {
uint protection; uint protection;
if ((flags & ProgramHeaderFlags.Execute) != 0) if (flags == ProgramHeaderFlags.None)
{
protection = PAGE_NOACCESS;
}
else if ((flags & ProgramHeaderFlags.Execute) != 0)
{ {
protection = (flags & ProgramHeaderFlags.Write) != 0 protection = (flags & ProgramHeaderFlags.Write) != 0
? PAGE_EXECUTE_READWRITE ? PAGE_EXECUTE_READWRITE
@@ -328,10 +349,33 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IDisposable
if (TryResolveRegionOffset(virtualAddress, (ulong)destination.Length, region, out var offset)) if (TryResolveRegionOffset(virtualAddress, (ulong)destination.Length, region, out var offset))
{ {
var srcPtr = (void*)(region.VirtualAddress + offset); var srcPtr = (void*)(region.VirtualAddress + offset);
fixed (byte* destPtr = destination) if (destination.IsEmpty)
{ {
Buffer.MemoryCopy(srcPtr, destPtr, (nuint)destination.Length, (nuint)destination.Length); return true;
} }
if (!EnsureRangeCommitted((ulong)srcPtr, (ulong)destination.Length, region))
{
return false;
}
if (!TryTemporarilyProtectForRead((ulong)srcPtr, (ulong)destination.Length, region, out var touchedPages))
{
return false;
}
try
{
fixed (byte* destPtr = destination)
{
Buffer.MemoryCopy(srcPtr, destPtr, (nuint)destination.Length, (nuint)destination.Length);
}
}
finally
{
RestorePageProtections(touchedPages);
}
return true; return true;
} }
} }
@@ -353,6 +397,11 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IDisposable
return true; return true;
} }
if (!EnsureRangeCommitted((ulong)destPtr, (ulong)source.Length, region))
{
return false;
}
if (!VirtualProtect(destPtr, (nuint)source.Length, PAGE_EXECUTE_READWRITE, out var oldProtect)) if (!VirtualProtect(destPtr, (nuint)source.Length, PAGE_EXECUTE_READWRITE, out var oldProtect))
{ {
return false; return false;
@@ -458,6 +507,83 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IDisposable
return protection is PAGE_EXECUTE or PAGE_EXECUTE_READ or PAGE_EXECUTE_READWRITE or PAGE_EXECUTE_WRITECOPY; return protection is PAGE_EXECUTE or PAGE_EXECUTE_READ or PAGE_EXECUTE_READWRITE or PAGE_EXECUTE_WRITECOPY;
} }
private static uint GetCommitProtection(MemoryRegion region)
{
return region.IsExecutable ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE;
}
private static unsafe bool EnsureRangeCommitted(ulong address, ulong size, MemoryRegion region)
{
if (size == 0 || !region.IsReservedOnly)
{
return true;
}
var startPage = AlignDown(address, PageSize);
var endPage = AlignUp(address + size, PageSize);
var commitProtection = GetCommitProtection(region);
for (var pageAddress = startPage; pageAddress < endPage; pageAddress += PageSize)
{
if (VirtualQuery((void*)pageAddress, out var info, (nuint)sizeof(MemoryBasicInformation64)) == 0)
{
return false;
}
if (info.State == MEM_COMMIT)
{
continue;
}
if (info.State != MEM_RESERVE)
{
return false;
}
if (VirtualAlloc((void*)pageAddress, (nuint)PageSize, MEM_COMMIT, commitProtection) == null)
{
return false;
}
}
return true;
}
private bool TryTemporarilyProtectForRead(
ulong address,
ulong size,
MemoryRegion region,
out List<(ulong Address, uint Protection)> touchedPages)
{
touchedPages = new List<(ulong Address, uint Protection)>();
var startPage = AlignDown(address, PageSize);
var endPage = AlignUp(address + size, PageSize);
var temporaryProtection = region.IsExecutable ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE;
for (var pageAddress = startPage; pageAddress < endPage; pageAddress += PageSize)
{
if (!VirtualProtect((void*)pageAddress, (nuint)PageSize, temporaryProtection, out var oldProtection))
{
RestorePageProtections(touchedPages);
touchedPages.Clear();
return false;
}
touchedPages.Add((pageAddress, oldProtection));
}
return true;
}
private static void RestorePageProtections(List<(ulong Address, uint Protection)> touchedPages)
{
foreach (var (pageAddress, protection) in touchedPages)
{
VirtualProtect((void*)pageAddress, (nuint)PageSize, protection, out _);
}
}
private static ulong AlignDown(ulong value, ulong alignment) private static ulong AlignDown(ulong value, ulong alignment)
{ {
var mask = alignment - 1; var mask = alignment - 1;
@@ -487,4 +613,17 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IDisposable
public bool IsReservedOnly { get; set; } public bool IsReservedOnly { get; set; }
public uint Protection { get; set; } public uint Protection { get; set; }
} }
private struct MemoryBasicInformation64
{
public ulong BaseAddress;
public ulong AllocationBase;
public uint AllocationProtect;
public uint Alignment1;
public ulong RegionSize;
public uint State;
public uint Protect;
public uint Type;
public uint Alignment2;
}
} }
+283 -5
View File
@@ -19,6 +19,8 @@ namespace SharpEmu.Core.Runtime;
public sealed class SharpEmuRuntime : ISharpEmuRuntime public sealed class SharpEmuRuntime : ISharpEmuRuntime
{ {
private readonly record struct LoadedModuleImage(string Path, SelfImage Image);
private static readonly HashSet<string> PreloadSkipModules = new(StringComparer.OrdinalIgnoreCase) private static readonly HashSet<string> PreloadSkipModules = new(StringComparer.OrdinalIgnoreCase)
{ {
"libkernel.prx", "libkernel.prx",
@@ -138,13 +140,33 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
var generation = image.ElfHeader.AbiVersion == 2 ? Generation.Gen5 : Generation.Gen4; var generation = image.ElfHeader.AbiVersion == 2 ? Generation.Gen5 : Generation.Gen4;
var activeImportStubs = new Dictionary<ulong, string>(image.ImportStubs); var activeImportStubs = new Dictionary<ulong, string>(image.ImportStubs);
var activeRuntimeSymbols = new Dictionary<string, ulong>(image.RuntimeSymbols, StringComparer.Ordinal); var activeRuntimeSymbols = new Dictionary<string, ulong>(image.RuntimeSymbols, StringComparer.Ordinal);
LoadAdjacentSceModules(ebootPath, activeImportStubs, activeRuntimeSymbols);
var processImageName = Path.GetFileName(ebootPath); var processImageName = Path.GetFileName(ebootPath);
if (string.IsNullOrWhiteSpace(processImageName)) if (string.IsNullOrWhiteSpace(processImageName))
{ {
processImageName = "eboot.bin"; processImageName = "eboot.bin";
} }
HleDataSymbols.ConfigureProcessImageName(processImageName);
MergeKnownHleDataSymbols(activeRuntimeSymbols);
var loadedModuleImages = LoadAdjacentSceModules(ebootPath, activeImportStubs, activeRuntimeSymbols);
RebindImportedDataSymbols(image, loadedModuleImages, activeRuntimeSymbols);
var initializerResult = RunAllInitializers(
image,
loadedModuleImages,
generation,
activeImportStubs,
activeRuntimeSymbols,
processImageName);
if (initializerResult is { } failedInitializerResult)
{
Console.Error.WriteLine($"[RUNTIME] Initializer dispatch failed: {failedInitializerResult}");
LastExecutionTrace = _cpuDispatcher.LastImportResolutionTrace;
LastMilestoneLog = _cpuDispatcher.LastMilestoneLog;
LastSessionSummary = BuildSessionSummary(_cpuDispatcher.LastSessionSummary);
LastBasicBlockTrace = _cpuDispatcher.LastBasicBlockTrace;
return failedInitializerResult;
}
Console.Error.WriteLine($"[RUNTIME] Dispatching, gen: {generation}"); Console.Error.WriteLine($"[RUNTIME] Dispatching, gen: {generation}");
Console.Error.WriteLine($"[RUNTIME] About to call DispatchEntry with entryPoint=0x{image.EntryPoint:X16}"); Console.Error.WriteLine($"[RUNTIME] About to call DispatchEntry with entryPoint=0x{image.EntryPoint:X16}");
@@ -328,15 +350,154 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
return result; return result;
} }
private void LoadAdjacentSceModules( private OrbisGen2Result? RunAllInitializers(
SelfImage mainImage,
IReadOnlyList<LoadedModuleImage> loadedModuleImages,
Generation generation,
IReadOnlyDictionary<ulong, string> activeImportStubs,
IReadOnlyDictionary<string, ulong> activeRuntimeSymbols,
string processImageName)
{
var moduleStartResult = RunPreloadedModuleInitializers(
loadedModuleImages,
generation,
activeImportStubs,
activeRuntimeSymbols);
if (moduleStartResult is not null)
{
return moduleStartResult;
}
// On current PS5 dumps DT_INIT commonly resolves to imageBase+0x10, which is inside
// the mapped ELF header rather than a callable guest routine. Startup must remain
// guest-driven until the PS5 init/module ABI is identified precisely.
return null;
}
private OrbisGen2Result? RunPreloadedModuleInitializers(
IReadOnlyList<LoadedModuleImage> loadedModuleImages,
Generation generation,
IReadOnlyDictionary<ulong, string> activeImportStubs,
IReadOnlyDictionary<string, ulong> activeRuntimeSymbols)
{
for (var i = 0; i < loadedModuleImages.Count; i++)
{
var loadedModule = loadedModuleImages[i];
var initEntryPoint = loadedModule.Image.InitFunctionEntryPoint;
if (initEntryPoint < 0x10000)
{
continue;
}
var moduleName = Path.GetFileName(loadedModule.Path);
if (string.IsNullOrWhiteSpace(moduleName))
{
moduleName = $"module#{i}";
}
Console.Error.WriteLine(
$"[RUNTIME] Starting module {moduleName}: dt_init=0x{initEntryPoint:X16}");
var result = _cpuDispatcher.DispatchModuleInitializer(
initEntryPoint,
generation,
activeImportStubs,
activeRuntimeSymbols,
moduleName,
_cpuExecutionOptions);
if (result != OrbisGen2Result.ORBIS_GEN2_OK)
{
Console.Error.WriteLine(
$"[RUNTIME] Module start failed: {moduleName} -> {result}");
return result;
}
}
return null;
}
private OrbisGen2Result? RunImageInitializers(
string label,
SelfImage image,
Generation generation,
IReadOnlyDictionary<ulong, string> activeImportStubs,
IReadOnlyDictionary<string, ulong> activeRuntimeSymbols,
string processImageName)
{
if (image.PreInitializerFunctions.Count == 0 && image.InitializerFunctions.Count == 0)
{
return null;
}
Console.Error.WriteLine(
$"[RUNTIME] Running initializers for {label}: preinit={image.PreInitializerFunctions.Count}, init={image.InitializerFunctions.Count}");
var result = RunInitializerList(
$"{label}:preinit",
image.PreInitializerFunctions,
generation,
activeImportStubs,
activeRuntimeSymbols,
processImageName);
if (result is not null)
{
return result;
}
return RunInitializerList(
$"{label}:init",
image.InitializerFunctions,
generation,
activeImportStubs,
activeRuntimeSymbols,
processImageName);
}
private OrbisGen2Result? RunInitializerList(
string label,
IReadOnlyList<ulong> initializerFunctions,
Generation generation,
IReadOnlyDictionary<ulong, string> activeImportStubs,
IReadOnlyDictionary<string, ulong> activeRuntimeSymbols,
string processImageName)
{
for (var i = 0; i < initializerFunctions.Count; i++)
{
var initializerAddress = initializerFunctions[i];
if (initializerAddress < 0x10000)
{
continue;
}
Console.Error.WriteLine(
$"[RUNTIME] Initializer {label}[{i}] -> 0x{initializerAddress:X16}");
var result = _cpuDispatcher.DispatchEntry(
initializerAddress,
generation,
activeImportStubs,
activeRuntimeSymbols,
processImageName,
_cpuExecutionOptions);
if (result != OrbisGen2Result.ORBIS_GEN2_OK)
{
return result;
}
}
return null;
}
private List<LoadedModuleImage> LoadAdjacentSceModules(
string ebootPath, string ebootPath,
IDictionary<ulong, string> importStubs, IDictionary<ulong, string> importStubs,
IDictionary<string, ulong> runtimeSymbols) IDictionary<string, ulong> runtimeSymbols)
{ {
var loadedImages = new List<LoadedModuleImage>();
var ebootDirectory = Path.GetDirectoryName(ebootPath); var ebootDirectory = Path.GetDirectoryName(ebootPath);
if (string.IsNullOrWhiteSpace(ebootDirectory)) if (string.IsNullOrWhiteSpace(ebootDirectory))
{ {
return; return loadedImages;
} }
var moduleDirectories = new[] var moduleDirectories = new[]
@@ -350,7 +511,7 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
if (moduleDirectories.Length == 0) if (moduleDirectories.Length == 0)
{ {
return; return loadedImages;
} }
var allModulePaths = moduleDirectories var allModulePaths = moduleDirectories
@@ -380,7 +541,7 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
if (modulePaths.Length == 0) if (modulePaths.Length == 0)
{ {
return; return loadedImages;
} }
Console.Error.WriteLine($"[RUNTIME] Module search directories: {string.Join(", ", moduleDirectories)}"); Console.Error.WriteLine($"[RUNTIME] Module search directories: {string.Join(", ", moduleDirectories)}");
@@ -416,6 +577,7 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
mergedImportCount += MergeImportStubs(importStubs, moduleImage.ImportStubs, modulePath); mergedImportCount += MergeImportStubs(importStubs, moduleImage.ImportStubs, modulePath);
mergedSymbolCount += MergeRuntimeSymbols(runtimeSymbols, moduleImage.RuntimeSymbols); mergedSymbolCount += MergeRuntimeSymbols(runtimeSymbols, moduleImage.RuntimeSymbols);
RegisterLoadedModule(modulePath, moduleImage, isMain: false, isSystemModule: false); RegisterLoadedModule(modulePath, moduleImage, isMain: false, isSystemModule: false);
loadedImages.Add(new LoadedModuleImage(modulePath, moduleImage));
loadedModules++; loadedModules++;
Console.Error.WriteLine( Console.Error.WriteLine(
@@ -430,6 +592,104 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
Console.Error.WriteLine( Console.Error.WriteLine(
$"[RUNTIME] Module preload summary: loaded={loadedModules}, failed={failedModules}, merged_imports={mergedImportCount}, merged_symbols={mergedSymbolCount}"); $"[RUNTIME] Module preload summary: loaded={loadedModules}, failed={failedModules}, merged_imports={mergedImportCount}, merged_symbols={mergedSymbolCount}");
return loadedImages;
}
private void RebindImportedDataSymbols(
SelfImage mainImage,
IReadOnlyList<LoadedModuleImage> loadedModuleImages,
IReadOnlyDictionary<string, ulong> runtimeSymbols)
{
var rebound = 0;
var unresolved = 0;
rebound += RebindImportedDataSymbols(mainImage, runtimeSymbols, ref unresolved);
for (var i = 0; i < loadedModuleImages.Count; i++)
{
rebound += RebindImportedDataSymbols(loadedModuleImages[i].Image, runtimeSymbols, ref unresolved);
}
if (rebound != 0 || unresolved != 0)
{
Console.Error.WriteLine(
$"[RUNTIME] Imported data rebind: rebound={rebound}, unresolved={unresolved}");
}
}
private int RebindImportedDataSymbols(
SelfImage image,
IReadOnlyDictionary<string, ulong> runtimeSymbols,
ref int unresolved)
{
if (image.ImportedRelocations.Count == 0)
{
return 0;
}
var rebound = 0;
var logRebind = string.Equals(
Environment.GetEnvironmentVariable("SHARPEMU_LOG_DATA_REBIND"),
"1",
StringComparison.Ordinal);
for (var i = 0; i < image.ImportedRelocations.Count; i++)
{
var relocation = image.ImportedRelocations[i];
if (!relocation.IsData)
{
continue;
}
if (!runtimeSymbols.TryGetValue(relocation.Nid, out var symbolAddress) ||
!IsUsableRuntimeSymbolAddress(symbolAddress))
{
if (logRebind)
{
Console.Error.WriteLine(
$"[RUNTIME] Imported data unresolved: nid={relocation.Nid} target=0x{relocation.TargetAddress:X16} addend=0x{unchecked((ulong)relocation.Addend):X16}");
}
unresolved++;
continue;
}
var reboundValue = AddSigned(symbolAddress, relocation.Addend);
if (!TryWriteUInt64(_virtualMemory, relocation.TargetAddress, reboundValue))
{
if (logRebind)
{
Console.Error.WriteLine(
$"[RUNTIME] Imported data write-failed: nid={relocation.Nid} target=0x{relocation.TargetAddress:X16} value=0x{reboundValue:X16}");
}
unresolved++;
continue;
}
if (logRebind)
{
Console.Error.WriteLine(
$"[RUNTIME] Imported data rebound: nid={relocation.Nid} target=0x{relocation.TargetAddress:X16} value=0x{reboundValue:X16}");
}
rebound++;
}
return rebound;
}
private static void MergeKnownHleDataSymbols(IDictionary<string, ulong> runtimeSymbols)
{
foreach (var nid in HleDataSymbols.EnumerateKnownNids())
{
if (runtimeSymbols.ContainsKey(nid) ||
!HleDataSymbols.TryGetAddress(nid, out var symbolAddress) ||
!IsUsableRuntimeSymbolAddress(symbolAddress))
{
continue;
}
runtimeSymbols[nid] = symbolAddress;
}
} }
private static int MergeImportStubs( private static int MergeImportStubs(
@@ -498,6 +758,24 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
return address >= 0x10000 && !IsUnresolvedRuntimeSentinel(address); return address >= 0x10000 && !IsUnresolvedRuntimeSentinel(address);
} }
private static bool TryWriteUInt64(IVirtualMemory virtualMemory, ulong address, ulong value)
{
Span<byte> bytes = stackalloc byte[sizeof(ulong)];
BinaryPrimitives.WriteUInt64LittleEndian(bytes, value);
return virtualMemory.TryWrite(address, bytes);
}
private static ulong AddSigned(ulong value, long addend)
{
if (addend >= 0)
{
return unchecked(value + (ulong)addend);
}
var magnitude = unchecked((ulong)(-(addend + 1))) + 1;
return unchecked(value - magnitude);
}
private static bool IsUnresolvedRuntimeSentinel(ulong value) private static bool IsUnresolvedRuntimeSentinel(ulong value)
{ {
return value == 0xFFFEUL || return value == 0xFFFEUL ||
+130
View File
@@ -0,0 +1,130 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using System.Runtime.InteropServices;
using System.Text;
namespace SharpEmu.HLE;
public static class HleDataSymbols
{
private const string StackChkGuardNid = "f7uOxY9mM1U";
private const string ProgNameNid = "djxxOmW6-aw";
private const string LibcNeedFlagNid = "P330P3dFF68";
private const string LibcInternalNeedFlagNid = "ZT4ODD2Ts9o";
private const int ProgNameMaxBytes = 511;
private const ulong StackChkGuardValue = 0xC0DEC0DECAFEBABEUL;
private static readonly object _gate = new();
private static readonly nint _stackChkGuardAddress = Allocate(sizeof(ulong) * 2);
private static readonly nint _progNameBufferAddress = Allocate(ProgNameMaxBytes + 1);
private static readonly nint _progNamePointerAddress = Allocate(nint.Size);
private static readonly nint _libcNeedFlagAddress = Allocate(sizeof(uint));
private static readonly nint _libcInternalNeedFlagAddress = Allocate(sizeof(uint));
static HleDataSymbols()
{
if (_stackChkGuardAddress != 0)
{
Marshal.WriteInt64(_stackChkGuardAddress, unchecked((long)StackChkGuardValue));
Marshal.WriteInt64(
IntPtr.Add(_stackChkGuardAddress, sizeof(ulong)),
unchecked((long)StackChkGuardValue));
}
if (_libcNeedFlagAddress != 0)
{
Marshal.WriteInt32(_libcNeedFlagAddress, 1);
}
if (_libcInternalNeedFlagAddress != 0)
{
Marshal.WriteInt32(_libcInternalNeedFlagAddress, 1);
}
ConfigureProcessImageName("eboot.bin");
}
public static IEnumerable<string> EnumerateKnownNids()
{
yield return StackChkGuardNid;
yield return ProgNameNid;
yield return LibcNeedFlagNid;
yield return LibcInternalNeedFlagNid;
}
public static void ConfigureProcessImageName(string? processImageName)
{
var effectiveName = string.IsNullOrWhiteSpace(processImageName)
? "eboot.bin"
: processImageName;
var encodedName = Encoding.UTF8.GetBytes(effectiveName);
var byteCount = Math.Min(encodedName.Length, ProgNameMaxBytes);
lock (_gate)
{
if (_progNameBufferAddress == 0 || _progNamePointerAddress == 0)
{
return;
}
for (var i = 0; i <= ProgNameMaxBytes; i++)
{
Marshal.WriteByte(_progNameBufferAddress, i, 0);
}
Marshal.Copy(encodedName, 0, _progNameBufferAddress, byteCount);
WritePointer(_progNamePointerAddress, _progNameBufferAddress);
}
}
public static bool TryGetAddress(string nid, out ulong address)
{
var pointer = nid switch
{
StackChkGuardNid => _stackChkGuardAddress,
ProgNameNid => _progNamePointerAddress,
LibcNeedFlagNid => _libcNeedFlagAddress,
LibcInternalNeedFlagNid => _libcInternalNeedFlagAddress,
_ => 0,
};
if (pointer == 0)
{
address = 0;
return false;
}
address = unchecked((ulong)pointer);
return true;
}
private static nint Allocate(int size)
{
try
{
var memory = Marshal.AllocHGlobal(size);
for (var i = 0; i < size; i++)
{
Marshal.WriteByte(memory, i, 0);
}
return memory;
}
catch
{
return 0;
}
}
private static void WritePointer(nint target, nint value)
{
if (nint.Size == sizeof(int))
{
Marshal.WriteInt32(target, value.ToInt32());
return;
}
Marshal.WriteInt64(target, value.ToInt64());
}
}
+2
View File
@@ -17,5 +17,7 @@ public interface IModuleManager
bool TryGetExportByName(string exportName, out ExportedFunction export); bool TryGetExportByName(string exportName, out ExportedFunction export);
bool TryDispatch(string nid, CpuContext context, out OrbisGen2Result result);
OrbisGen2Result Dispatch(string nid, CpuContext context); OrbisGen2Result Dispatch(string nid, CpuContext context);
} }
+12 -3
View File
@@ -93,6 +93,12 @@ public sealed class ModuleManager : IModuleManager
} }
public OrbisGen2Result Dispatch(string nid, CpuContext context) public OrbisGen2Result Dispatch(string nid, CpuContext context)
{
TryDispatch(nid, context, out var result);
return result;
}
public bool TryDispatch(string nid, CpuContext context, out OrbisGen2Result result)
{ {
ArgumentException.ThrowIfNullOrWhiteSpace(nid); ArgumentException.ThrowIfNullOrWhiteSpace(nid);
ArgumentNullException.ThrowIfNull(context); ArgumentNullException.ThrowIfNull(context);
@@ -100,13 +106,15 @@ public sealed class ModuleManager : IModuleManager
if (!_dispatchTable.TryGetValue(nid, out var function) || !_exportTable.TryGetValue(nid, out var export)) if (!_dispatchTable.TryGetValue(nid, out var function) || !_exportTable.TryGetValue(nid, out var export))
{ {
context[CpuRegister.Rax] = unchecked((ulong)(int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND); context[CpuRegister.Rax] = unchecked((ulong)(int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
return OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND; result = OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
return false;
} }
if ((export.Target & context.TargetGeneration) == 0) if ((export.Target & context.TargetGeneration) == 0)
{ {
context[CpuRegister.Rax] = unchecked((ulong)(int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND); context[CpuRegister.Rax] = unchecked((ulong)(int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
return OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND; result = OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
return false;
} }
context.ClearRaxWriteFlag(); context.ClearRaxWriteFlag();
@@ -117,7 +125,8 @@ public sealed class ModuleManager : IModuleManager
context[CpuRegister.Rax] = unchecked((ulong)ret); context[CpuRegister.Rax] = unchecked((ulong)ret);
} }
return (OrbisGen2Result)ret; result = (OrbisGen2Result)ret;
return true;
} }
private static Delegate CreateHandler(Type ownerType, MethodInfo method, IDictionary<Type, object> instances) private static Delegate CreateHandler(Type ownerType, MethodInfo method, IDictionary<Type, object> instances)
+25
View File
@@ -15,6 +15,11 @@ public enum OrbisGen2Result : int
/// </summary> /// </summary>
ORBIS_GEN2_OK = 0, ORBIS_GEN2_OK = 0,
/// <summary>
/// Indicates that the operation is not permitted for the calling thread.
/// </summary>
ORBIS_GEN2_ERROR_PERMISSION_DENIED = unchecked((int)0x80020001),
/// <summary> /// <summary>
/// Indicates that the requested export was not found. /// Indicates that the requested export was not found.
/// </summary> /// </summary>
@@ -30,11 +35,31 @@ public enum OrbisGen2Result : int
/// </summary> /// </summary>
ORBIS_GEN2_ERROR_ALREADY_EXISTS = unchecked((int)0x80020004), ORBIS_GEN2_ERROR_ALREADY_EXISTS = unchecked((int)0x80020004),
/// <summary>
/// Indicates that completing the operation would deadlock.
/// </summary>
ORBIS_GEN2_ERROR_DEADLOCK = unchecked((int)0x8002000B),
/// <summary>
/// Indicates that the target resource is busy.
/// </summary>
ORBIS_GEN2_ERROR_BUSY = unchecked((int)0x80020010),
/// <summary>
/// Indicates that the operation should be retried later.
/// </summary>
ORBIS_GEN2_ERROR_TRY_AGAIN = unchecked((int)0x80020023),
/// <summary> /// <summary>
/// Indicates that behavior is recognized but not implemented yet. /// Indicates that behavior is recognized but not implemented yet.
/// </summary> /// </summary>
ORBIS_GEN2_ERROR_NOT_IMPLEMENTED = unchecked((int)0x8002FFFF), ORBIS_GEN2_ERROR_NOT_IMPLEMENTED = unchecked((int)0x8002FFFF),
/// <summary>
/// Indicates that the operation timed out.
/// </summary>
ORBIS_GEN2_ERROR_TIMED_OUT = unchecked((int)0x8002003C),
/// <summary> /// <summary>
/// Indicates that memory access failed. /// Indicates that memory access failed.
/// </summary> /// </summary>
+46 -29
View File
@@ -10,9 +10,14 @@ namespace SharpEmu.Libs.CxxAbi;
public static class CxaGuardExports public static class CxaGuardExports
{ {
private const ulong GuardCompleteValue = 0x0000_0000_0000_0001;
private const ulong GuardPendingValue = 0x0000_0000_0000_0100;
private const ulong GuardStateMask = 0x0000_0000_0000_FFFF;
private sealed class GuardState private sealed class GuardState
{ {
public int OwnerThreadId { get; set; } public int OwnerThreadId { get; set; }
public int RecursionDepth { get; set; }
} }
private static readonly ConcurrentDictionary<ulong, GuardState> _inProgress = new(); private static readonly ConcurrentDictionary<ulong, GuardState> _inProgress = new();
@@ -35,13 +40,13 @@ public static class CxaGuardExports
var spinner = new SpinWait(); var spinner = new SpinWait();
while (true) while (true)
{ {
if (!TryReadGuardInitialized(ctx, guardPtr, out var initialized)) if (!TryReadGuardState(ctx, guardPtr, out _, out var initialized, out var inProgress))
{ {
ctx[CpuRegister.Rax] = 0; ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
} }
LogGuardState(ctx, "guard_acquire", guardPtr, initialized); LogGuardState(ctx, "guard_acquire", guardPtr, initialized, inProgress);
if (initialized) if (initialized)
{ {
@@ -53,9 +58,17 @@ public static class CxaGuardExports
var newState = new GuardState var newState = new GuardState
{ {
OwnerThreadId = currentThreadId, OwnerThreadId = currentThreadId,
RecursionDepth = 1,
}; };
if (_inProgress.TryAdd(guardPtr, newState)) if (_inProgress.TryAdd(guardPtr, newState))
{ {
if (!TryWriteGuardState(ctx, guardPtr, GuardPendingValue))
{
_inProgress.TryRemove(guardPtr, out _);
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = 1; ctx[CpuRegister.Rax] = 1;
LogGuardResult("guard_acquire", guardPtr, result: 1, initialized, inProgress: true, ownerThreadId: currentThreadId); LogGuardResult("guard_acquire", guardPtr, result: 1, initialized, inProgress: true, ownerThreadId: currentThreadId);
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
@@ -101,14 +114,28 @@ public static class CxaGuardExports
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
} }
if (!TryWriteGuardInitialized(ctx, guardPtr, initialized: true)) if (state is not null)
{
lock (state)
{
if (state.RecursionDepth > 1)
{
state.RecursionDepth--;
ctx[CpuRegister.Rax] = 0;
LogGuardResult("guard_release", guardPtr, result: 0, initialized: false, inProgress: true, ownerThreadId: state.OwnerThreadId);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
}
}
if (!TryWriteGuardState(ctx, guardPtr, GuardCompleteValue))
{ {
ctx[CpuRegister.Rax] = 0; ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
} }
_inProgress.TryRemove(guardPtr, out _); _inProgress.TryRemove(guardPtr, out _);
LogGuardState(ctx, "guard_release", guardPtr, initialized: true); LogGuardState(ctx, "guard_release", guardPtr, initialized: true, inProgress: false);
ctx[CpuRegister.Rax] = 0; ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
@@ -136,60 +163,50 @@ public static class CxaGuardExports
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
} }
_ = TryWriteGuardInitialized(ctx, guardPtr, initialized: false); _ = TryWriteGuardState(ctx, guardPtr, 0);
_inProgress.TryRemove(guardPtr, out _); _inProgress.TryRemove(guardPtr, out _);
LogGuardState(ctx, "guard_abort", guardPtr, initialized: false); LogGuardState(ctx, "guard_abort", guardPtr, initialized: false, inProgress: false);
ctx[CpuRegister.Rax] = 0; ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
private static bool TryReadGuardInitialized(CpuContext ctx, ulong guardPtr, out bool initialized) private static bool TryReadGuardState(CpuContext ctx, ulong guardPtr, out ulong word, out bool initialized, out bool inProgress)
{ {
word = 0;
initialized = false; initialized = false;
inProgress = false;
var aligned = guardPtr & ~7UL; if (!ctx.TryReadUInt64(guardPtr, out word))
var shift = (int)((guardPtr & 7UL) * 8);
if (!ctx.TryReadUInt64(aligned, out var word))
{ {
return false; return false;
} }
var b0 = (byte)((word >> shift) & 0xFF); initialized = (word & GuardCompleteValue) != 0;
initialized = (b0 & 0x01) != 0; inProgress = (word & 0x0000_0000_0000_FF00) != 0;
return true; return true;
} }
private static bool TryWriteGuardInitialized(CpuContext ctx, ulong guardPtr, bool initialized) private static bool TryWriteGuardState(CpuContext ctx, ulong guardPtr, ulong stateValue)
{ {
var aligned = guardPtr & ~7UL; if (!ctx.TryReadUInt64(guardPtr, out var word))
var shift = (int)((guardPtr & 7UL) * 8);
var mask = 0xFFUL << shift;
if (!ctx.TryReadUInt64(aligned, out var word))
{ {
return false; return false;
} }
var b0 = (byte)((word >> shift) & 0xFF); var newWord = (word & ~GuardStateMask) | (stateValue & GuardStateMask);
b0 = initialized ? (byte)(b0 | 0x01) : (byte)(b0 & ~0x01); return ctx.TryWriteUInt64(guardPtr, newWord);
var newWord = (word & ~mask) | ((ulong)b0 << shift);
return ctx.TryWriteUInt64(aligned, newWord);
} }
private static void LogGuardState(CpuContext ctx, string op, ulong guardPtr, bool initialized) private static void LogGuardState(CpuContext ctx, string op, ulong guardPtr, bool initialized, bool inProgress)
{ {
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_GUARDS"), "1", StringComparison.Ordinal)) if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_GUARDS"), "1", StringComparison.Ordinal))
{ {
return; return;
} }
var aligned = guardPtr & ~7UL; var readable = ctx.TryReadUInt64(guardPtr, out var word);
var readable = ctx.TryReadUInt64(aligned, out var word);
Console.Error.WriteLine( Console.Error.WriteLine(
$"[LOADER][TRACE] {op}: guard=0x{guardPtr:X16} aligned=0x{aligned:X16} init={initialized} word={(readable ? $"0x{word:X16}" : "<unreadable>")}"); $"[LOADER][TRACE] {op}: guard=0x{guardPtr:X16} init={initialized} in_progress={inProgress} word={(readable ? $"0x{word:X16}" : "<unreadable>")}");
} }
private static void LogGuardResult(string op, ulong guardPtr, int result, bool initialized, bool inProgress, int ownerThreadId) private static void LogGuardResult(string op, ulong guardPtr, int result, bool initialized, bool inProgress, int ownerThreadId)
+47 -3
View File
@@ -8,10 +8,12 @@ namespace SharpEmu.Libs.Kernel;
public static class KernelExports public static class KernelExports
{ {
private static long _nextThreadId = 1;
private static int _nextFileDescriptor = 2; private static int _nextFileDescriptor = 2;
private static readonly object _cxaGate = new(); private static readonly object _cxaGate = new();
private static readonly List<CxaDestructorEntry> _cxaDestructors = 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( private readonly record struct CxaDestructorEntry(
ulong Function, ulong Function,
@@ -29,6 +31,23 @@ public static class KernelExports
return (int)OrbisGen2Result.ORBIS_GEN2_OK; 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( [SysAbiExport(
Nid = "uMei1W9uyNo", Nid = "uMei1W9uyNo",
ExportName = "exit", ExportName = "exit",
@@ -175,12 +194,24 @@ public static class KernelExports
public static int PthreadCreate(CpuContext ctx) public static int PthreadCreate(CpuContext ctx)
{ {
var threadIdAddress = ctx[CpuRegister.Rdi]; var threadIdAddress = ctx[CpuRegister.Rdi];
var nextThreadId = unchecked((ulong)Interlocked.Increment(ref _nextThreadId)); var attrAddress = ctx[CpuRegister.Rsi];
if (threadIdAddress != 0 && !ctx.TryWriteUInt64(threadIdAddress, nextThreadId)) 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);
if (threadIdAddress != 0 && !ctx.TryWriteUInt64(threadIdAddress, threadHandle))
{ {
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; 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}' -> thread=0x{threadHandle:X16}");
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
@@ -201,12 +232,20 @@ public static class KernelExports
LibraryName = "libKernel")] LibraryName = "libKernel")]
public static int PthreadJoin(CpuContext ctx) public static int PthreadJoin(CpuContext ctx)
{ {
var threadId = ctx[CpuRegister.Rdi];
var returnValueAddress = ctx[CpuRegister.Rsi]; var returnValueAddress = ctx[CpuRegister.Rsi];
if (returnValueAddress != 0 && !ctx.TryWriteUInt64(returnValueAddress, 0)) if (returnValueAddress != 0 && !ctx.TryWriteUInt64(returnValueAddress, 0))
{ {
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
} }
if (ShouldTracePthread())
{
Console.Error.WriteLine(
$"[LOADER][TRACE] pthread_join: thread=0x{threadId:X16} retval_out=0x{returnValueAddress:X16}");
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
@@ -370,4 +409,9 @@ public static class KernelExports
try { return System.Text.Encoding.UTF8.GetString(buf.Slice(0, len)); } try { return System.Text.Encoding.UTF8.GetString(buf.Slice(0, len)); }
catch { return System.Text.Encoding.ASCII.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);
}
} }
File diff suppressed because it is too large Load Diff
@@ -3,39 +3,42 @@
using SharpEmu.HLE; using SharpEmu.HLE;
using System.Threading; using System.Threading;
using System.Diagnostics.CodeAnalysis;
namespace SharpEmu.Libs.Kernel; namespace SharpEmu.Libs.Kernel;
public static class KernelPthreadCompatExports public static class KernelPthreadCompatExports
{ {
private const int MutexTypeNormal = 0; private const int MutexTypeDefault = 1;
private const int MutexTypeRecursive = 1; private const int MutexTypeErrorCheck = 1;
private const int MutexTypeRecursive = 2;
private const int MutexTypeNormal = 4;
private const ulong SyntheticMutexHandleBase = 0x00006000_0000_0000; private const ulong SyntheticMutexHandleBase = 0x00006000_0000_0000;
private const ulong SyntheticMutexAttrHandleBase = 0x00006001_0000_0000; private const ulong SyntheticMutexAttrHandleBase = 0x00006001_0000_0000;
private const ulong SyntheticCondHandleBase = 0x00006002_0000_0000;
private static readonly object _stateGate = new(); private static readonly object _stateGate = new();
private static readonly Dictionary<ulong, PthreadMutexState> _mutexStates = new(); private static readonly Dictionary<ulong, PthreadMutexState> _mutexStates = new();
private static readonly Dictionary<ulong, PthreadMutexAttrState> _mutexAttrStates = new(); private static readonly Dictionary<ulong, PthreadMutexAttrState> _mutexAttrStates = new();
private static readonly Dictionary<ulong, PthreadCondState> _condStates = new(); private static readonly Dictionary<ulong, PthreadCondState> _condStates = new();
private static readonly HashSet<ulong> _condAttrStates = new(); private static readonly HashSet<ulong> _condAttrStates = new();
private static long _nextSyntheticThreadId = 1;
private static long _nextSyntheticMutexHandleId = 1; private static long _nextSyntheticMutexHandleId = 1;
private static long _nextSyntheticMutexAttrHandleId = 1; private static long _nextSyntheticMutexAttrHandleId = 1;
[ThreadStatic] private static long _nextSyntheticCondHandleId = 1;
private static ulong _currentThreadId;
private sealed class PthreadMutexState private sealed class PthreadMutexState
{ {
public SemaphoreSlim Semaphore { get; } = new(1, 1); public SemaphoreSlim Semaphore { get; } = new(1, 1);
public ulong OwnerThreadId { get; set; } public ulong OwnerThreadId { get; set; }
public int RecursionCount { get; set; } public int RecursionCount { get; set; }
public int Type { get; set; } = MutexTypeNormal; public int Type { get; set; } = MutexTypeDefault;
public int Protocol { get; set; } public int Protocol { get; set; }
} }
private sealed class PthreadCondState private sealed class PthreadCondState
{ {
public int PendingSignals { get; set; } public object SyncRoot { get; } = new();
public ulong SignalEpoch { get; set; }
public int Waiters { get; set; } public int Waiters { get; set; }
} }
@@ -48,9 +51,9 @@ public static class KernelPthreadCompatExports
LibraryName = "libKernel")] LibraryName = "libKernel")]
public static int PthreadSelf(CpuContext ctx) public static int PthreadSelf(CpuContext ctx)
{ {
var currentThreadId = GetCurrentThreadId(); var currentThreadHandle = KernelPthreadState.GetCurrentThreadHandle();
ctx[CpuRegister.Rax] = currentThreadId; ctx[CpuRegister.Rax] = currentThreadHandle;
TracePthreadSelf(ctx, currentThreadId); TracePthreadSelf(ctx, currentThreadHandle);
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
@@ -86,7 +89,7 @@ public static class KernelPthreadCompatExports
LibraryName = "libKernel")] LibraryName = "libKernel")]
public static int PthreadGetthreadid(CpuContext ctx) public static int PthreadGetthreadid(CpuContext ctx)
{ {
var currentThreadId = GetCurrentThreadId(); var currentThreadId = KernelPthreadState.GetCurrentThreadUniqueId();
var outAddress = ctx[CpuRegister.Rdi]; var outAddress = ctx[CpuRegister.Rdi];
if (outAddress != 0 && !ctx.TryWriteUInt64(outAddress, currentThreadId)) if (outAddress != 0 && !ctx.TryWriteUInt64(outAddress, currentThreadId))
{ {
@@ -216,19 +219,26 @@ public static class KernelPthreadCompatExports
LibraryName = "libKernel")] LibraryName = "libKernel")]
public static int PosixPthreadMutexattrSettype(CpuContext ctx) => PthreadMutexattrSettypeCore(ctx, ctx[CpuRegister.Rdi], unchecked((int)ctx[CpuRegister.Rsi])); public static int PosixPthreadMutexattrSettype(CpuContext ctx) => PthreadMutexattrSettypeCore(ctx, ctx[CpuRegister.Rdi], unchecked((int)ctx[CpuRegister.Rsi]));
[SysAbiExport(
Nid = "5txKfcMUAok",
ExportName = "pthread_mutexattr_setprotocol",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PosixPthreadMutexattrSetprotocol(CpuContext ctx) => PthreadMutexattrSetprotocolCore(ctx, ctx[CpuRegister.Rdi], unchecked((int)ctx[CpuRegister.Rsi]));
[SysAbiExport( [SysAbiExport(
Nid = "2Tb92quprl0", Nid = "2Tb92quprl0",
ExportName = "scePthreadCondInit", ExportName = "scePthreadCondInit",
Target = Generation.Gen4 | Generation.Gen5, Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")] LibraryName = "libKernel")]
public static int PthreadCondInit(CpuContext ctx) => PthreadCondInitCore(ctx[CpuRegister.Rdi]); public static int PthreadCondInit(CpuContext ctx) => PthreadCondInitCore(ctx, ctx[CpuRegister.Rdi]);
[SysAbiExport( [SysAbiExport(
Nid = "g+PZd2hiacg", Nid = "g+PZd2hiacg",
ExportName = "scePthreadCondDestroy", ExportName = "scePthreadCondDestroy",
Target = Generation.Gen4 | Generation.Gen5, Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")] LibraryName = "libKernel")]
public static int PthreadCondDestroy(CpuContext ctx) => PthreadCondDestroyCore(ctx[CpuRegister.Rdi]); public static int PthreadCondDestroy(CpuContext ctx) => PthreadCondDestroyCore(ctx, ctx[CpuRegister.Rdi]);
[SysAbiExport( [SysAbiExport(
Nid = "WKAXJ4XBPQ4", Nid = "WKAXJ4XBPQ4",
@@ -242,21 +252,21 @@ public static class KernelPthreadCompatExports
ExportName = "scePthreadCondTimedwait", ExportName = "scePthreadCondTimedwait",
Target = Generation.Gen4 | Generation.Gen5, Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")] LibraryName = "libKernel")]
public static int PthreadCondTimedwait(CpuContext ctx) => PthreadCondWaitCore(ctx, ctx[CpuRegister.Rdi], ctx[CpuRegister.Rsi], timed: true); public static int PthreadCondTimedwait(CpuContext ctx) => PthreadCondWaitCore(ctx, ctx[CpuRegister.Rdi], ctx[CpuRegister.Rsi], timed: true, timeoutUsec: unchecked((uint)ctx[CpuRegister.Rdx]));
[SysAbiExport( [SysAbiExport(
Nid = "kDh-NfxgMtE", Nid = "kDh-NfxgMtE",
ExportName = "scePthreadCondSignal", ExportName = "scePthreadCondSignal",
Target = Generation.Gen4 | Generation.Gen5, Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")] LibraryName = "libKernel")]
public static int PthreadCondSignal(CpuContext ctx) => PthreadCondSignalCore(ctx[CpuRegister.Rdi], broadcast: false); public static int PthreadCondSignal(CpuContext ctx) => PthreadCondSignalCore(ctx, ctx[CpuRegister.Rdi], broadcast: false);
[SysAbiExport( [SysAbiExport(
Nid = "JGgj7Uvrl+A", Nid = "JGgj7Uvrl+A",
ExportName = "scePthreadCondBroadcast", ExportName = "scePthreadCondBroadcast",
Target = Generation.Gen4 | Generation.Gen5, Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")] LibraryName = "libKernel")]
public static int PthreadCondBroadcast(CpuContext ctx) => PthreadCondSignalCore(ctx[CpuRegister.Rdi], broadcast: true); public static int PthreadCondBroadcast(CpuContext ctx) => PthreadCondSignalCore(ctx, ctx[CpuRegister.Rdi], broadcast: true);
[SysAbiExport( [SysAbiExport(
Nid = "Op8TBGY5KHg", Nid = "Op8TBGY5KHg",
@@ -270,7 +280,7 @@ public static class KernelPthreadCompatExports
ExportName = "pthread_cond_broadcast", ExportName = "pthread_cond_broadcast",
Target = Generation.Gen4 | Generation.Gen5, Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")] LibraryName = "libKernel")]
public static int PosixPthreadCondBroadcast(CpuContext ctx) => PthreadCondSignalCore(ctx[CpuRegister.Rdi], broadcast: true); public static int PosixPthreadCondBroadcast(CpuContext ctx) => PthreadCondSignalCore(ctx, ctx[CpuRegister.Rdi], broadcast: true);
[SysAbiExport( [SysAbiExport(
Nid = "m5-2bsNfv7s", Nid = "m5-2bsNfv7s",
@@ -363,6 +373,7 @@ public static class KernelPthreadCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
} }
_ = ctx.TryWriteUInt64(mutexAddress, 0);
state.Semaphore.Dispose(); state.Semaphore.Dispose();
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
@@ -374,23 +385,13 @@ public static class KernelPthreadCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
} }
var resolvedAddress = ResolveMutexHandle(ctx, mutexAddress); if (!TryResolveMutexState(ctx, mutexAddress, createIfZero: true, out var resolvedAddress, out var state))
PthreadMutexState state;
lock (_stateGate)
{ {
if (!_mutexStates.TryGetValue(resolvedAddress, out state!)) TracePthreadMutex(ctx, tryOnly ? "trylock" : "lock", mutexAddress, resolvedAddress, null, KernelPthreadState.GetCurrentThreadHandle(), (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
{ return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
state = new PthreadMutexState();
_mutexStates[resolvedAddress] = state;
}
if (resolvedAddress != mutexAddress)
{
_mutexStates[mutexAddress] = state;
}
} }
var currentThreadId = GetCurrentThreadId(); var currentThreadId = KernelPthreadState.GetCurrentThreadHandle();
lock (state) lock (state)
{ {
if (state.OwnerThreadId == currentThreadId) if (state.OwnerThreadId == currentThreadId)
@@ -402,8 +403,11 @@ public static class KernelPthreadCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
TracePthreadMutex(ctx, tryOnly ? "trylock" : "lock", mutexAddress, resolvedAddress, state, currentThreadId, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_ALREADY_EXISTS); var ownedResult = tryOnly
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_ALREADY_EXISTS; ? (int)OrbisGen2Result.ORBIS_GEN2_ERROR_BUSY
: (int)OrbisGen2Result.ORBIS_GEN2_ERROR_DEADLOCK;
TracePthreadMutex(ctx, tryOnly ? "trylock" : "lock", mutexAddress, resolvedAddress, state, currentThreadId, ownedResult);
return ownedResult;
} }
} }
@@ -418,8 +422,8 @@ public static class KernelPthreadCompatExports
} }
if (!acquired) if (!acquired)
{ {
TracePthreadMutex(ctx, tryOnly ? "trylock" : "lock", mutexAddress, resolvedAddress, state, currentThreadId, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_ALREADY_EXISTS); TracePthreadMutex(ctx, tryOnly ? "trylock" : "lock", mutexAddress, resolvedAddress, state, currentThreadId, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_BUSY);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_ALREADY_EXISTS; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_BUSY;
} }
lock (state) lock (state)
@@ -439,20 +443,13 @@ public static class KernelPthreadCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
} }
var resolvedAddress = ResolveMutexHandle(ctx, mutexAddress); if (!TryResolveMutexState(ctx, mutexAddress, createIfZero: true, out var resolvedAddress, out var state))
PthreadMutexState? state;
lock (_stateGate)
{ {
_mutexStates.TryGetValue(resolvedAddress, out state); TracePthreadMutex(ctx, "unlock", mutexAddress, resolvedAddress, null, KernelPthreadState.GetCurrentThreadHandle(), (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
}
if (state is null)
{
TracePthreadMutex(ctx, "unlock", mutexAddress, resolvedAddress, null, GetCurrentThreadId(), (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
} }
var currentThreadId = GetCurrentThreadId(); var currentThreadId = KernelPthreadState.GetCurrentThreadHandle();
var shouldRelease = false; var shouldRelease = false;
lock (state) lock (state)
{ {
@@ -464,8 +461,8 @@ public static class KernelPthreadCompatExports
if (requireOwner && state.OwnerThreadId != currentThreadId) if (requireOwner && state.OwnerThreadId != currentThreadId)
{ {
TracePthreadMutex(ctx, "unlock", mutexAddress, resolvedAddress, state, currentThreadId, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT); TracePthreadMutex(ctx, "unlock", mutexAddress, resolvedAddress, state, currentThreadId, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_PERMISSION_DENIED);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_PERMISSION_DENIED;
} }
state.RecursionCount--; state.RecursionCount--;
@@ -503,8 +500,8 @@ public static class KernelPthreadCompatExports
var syntheticHandle = AllocateSyntheticHandle(SyntheticMutexAttrHandleBase, ref _nextSyntheticMutexAttrHandleId); var syntheticHandle = AllocateSyntheticHandle(SyntheticMutexAttrHandleBase, ref _nextSyntheticMutexAttrHandleId);
lock (_stateGate) lock (_stateGate)
{ {
_mutexAttrStates[attrAddress] = new PthreadMutexAttrState(MutexTypeNormal, 0); _mutexAttrStates[attrAddress] = new PthreadMutexAttrState(MutexTypeDefault, 0);
_mutexAttrStates[syntheticHandle] = new PthreadMutexAttrState(MutexTypeNormal, 0); _mutexAttrStates[syntheticHandle] = new PthreadMutexAttrState(MutexTypeDefault, 0);
} }
_ = ctx.TryWriteUInt64(attrAddress, syntheticHandle); _ = ctx.TryWriteUInt64(attrAddress, syntheticHandle);
@@ -543,10 +540,10 @@ public static class KernelPthreadCompatExports
{ {
if (!_mutexAttrStates.TryGetValue(resolvedAddress, out var state)) if (!_mutexAttrStates.TryGetValue(resolvedAddress, out var state))
{ {
state = new PthreadMutexAttrState(MutexTypeNormal, 0); state = new PthreadMutexAttrState(MutexTypeDefault, 0);
} }
_mutexAttrStates[resolvedAddress] = state with { Type = type }; _mutexAttrStates[resolvedAddress] = state with { Type = NormalizeMutexType(type) };
if (resolvedAddress != attrAddress) if (resolvedAddress != attrAddress)
{ {
_mutexAttrStates[attrAddress] = _mutexAttrStates[resolvedAddress]; _mutexAttrStates[attrAddress] = _mutexAttrStates[resolvedAddress];
@@ -568,7 +565,7 @@ public static class KernelPthreadCompatExports
{ {
if (!_mutexAttrStates.TryGetValue(resolvedAddress, out var state)) if (!_mutexAttrStates.TryGetValue(resolvedAddress, out var state))
{ {
state = new PthreadMutexAttrState(MutexTypeNormal, 0); state = new PthreadMutexAttrState(MutexTypeDefault, 0);
} }
_mutexAttrStates[resolvedAddress] = state with { Protocol = protocol }; _mutexAttrStates[resolvedAddress] = state with { Protocol = protocol };
@@ -610,6 +607,65 @@ public static class KernelPthreadCompatExports
return mutexAddress; return mutexAddress;
} }
private static bool TryResolveMutexState(CpuContext ctx, ulong mutexAddress, bool createIfZero, out ulong resolvedAddress, [NotNullWhen(true)] out PthreadMutexState? state)
{
resolvedAddress = 0;
state = null;
if (mutexAddress == 0)
{
return false;
}
lock (_stateGate)
{
if (_mutexStates.TryGetValue(mutexAddress, out state))
{
resolvedAddress = mutexAddress;
return true;
}
}
if (!ctx.TryReadUInt64(mutexAddress, out var pointedHandle))
{
return false;
}
if (pointedHandle != 0)
{
lock (_stateGate)
{
if (_mutexStates.TryGetValue(pointedHandle, out state))
{
_mutexStates[mutexAddress] = state;
resolvedAddress = pointedHandle;
return true;
}
}
resolvedAddress = pointedHandle;
return false;
}
if (!createIfZero)
{
resolvedAddress = mutexAddress;
return false;
}
var createdState = new PthreadMutexState();
var syntheticHandle = AllocateSyntheticHandle(SyntheticMutexHandleBase, ref _nextSyntheticMutexHandleId);
lock (_stateGate)
{
_mutexStates[mutexAddress] = createdState;
_mutexStates[syntheticHandle] = createdState;
}
_ = ctx.TryWriteUInt64(mutexAddress, syntheticHandle);
resolvedAddress = syntheticHandle;
state = createdState;
return true;
}
private static ulong ResolveMutexAttrHandle(CpuContext ctx, ulong attrAddress) private static ulong ResolveMutexAttrHandle(CpuContext ctx, ulong attrAddress)
{ {
if (attrAddress == 0) if (attrAddress == 0)
@@ -651,145 +707,267 @@ public static class KernelPthreadCompatExports
{ {
return _mutexAttrStates.TryGetValue(resolvedAddress, out var state) return _mutexAttrStates.TryGetValue(resolvedAddress, out var state)
? state ? state
: default; : new PthreadMutexAttrState(MutexTypeDefault, 0);
} }
} }
private static ulong ResolveCondHandle(CpuContext ctx, ulong condAddress)
{
if (condAddress == 0)
{
return 0;
}
lock (_stateGate)
{
if (_condStates.ContainsKey(condAddress))
{
return condAddress;
}
}
if (ctx.TryReadUInt64(condAddress, out var pointedHandle) && pointedHandle != 0)
{
lock (_stateGate)
{
if (_condStates.ContainsKey(pointedHandle))
{
return pointedHandle;
}
}
}
return condAddress;
}
private static bool TryResolveCondState(CpuContext? ctx, ulong condAddress, bool createIfZero, out ulong resolvedAddress, [NotNullWhen(true)] out PthreadCondState? state)
{
resolvedAddress = 0;
state = null;
if (condAddress == 0)
{
return false;
}
lock (_stateGate)
{
if (_condStates.TryGetValue(condAddress, out state))
{
resolvedAddress = condAddress;
return true;
}
}
if (ctx is null || !ctx.TryReadUInt64(condAddress, out var pointedHandle))
{
return false;
}
if (pointedHandle != 0)
{
lock (_stateGate)
{
if (_condStates.TryGetValue(pointedHandle, out state))
{
_condStates[condAddress] = state;
resolvedAddress = pointedHandle;
return true;
}
}
resolvedAddress = pointedHandle;
return false;
}
if (!createIfZero)
{
resolvedAddress = condAddress;
return false;
}
var createdState = new PthreadCondState();
var syntheticHandle = AllocateSyntheticHandle(SyntheticCondHandleBase, ref _nextSyntheticCondHandleId);
lock (_stateGate)
{
_condStates[condAddress] = createdState;
_condStates[syntheticHandle] = createdState;
}
_ = ctx.TryWriteUInt64(condAddress, syntheticHandle);
resolvedAddress = syntheticHandle;
state = createdState;
return true;
}
private static ulong AllocateSyntheticHandle(ulong baseAddress, ref long nextId) private static ulong AllocateSyntheticHandle(ulong baseAddress, ref long nextId)
{ {
var id = unchecked((ulong)Interlocked.Increment(ref nextId)); var id = unchecked((ulong)Interlocked.Increment(ref nextId));
return baseAddress + (id << 4); return baseAddress + (id << 4);
} }
private static int PthreadCondInitCore(ulong condAddress) private static int PthreadCondInitCore(CpuContext ctx, ulong condAddress)
{ {
if (condAddress == 0) if (condAddress == 0)
{ {
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
} }
var syntheticHandle = AllocateSyntheticHandle(SyntheticCondHandleBase, ref _nextSyntheticCondHandleId);
lock (_stateGate) lock (_stateGate)
{ {
_condStates[condAddress] = new PthreadCondState(); var state = new PthreadCondState();
_condStates[condAddress] = state;
_condStates[syntheticHandle] = state;
} }
_ = ctx.TryWriteUInt64(condAddress, syntheticHandle);
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
private static int PthreadCondDestroyCore(ulong condAddress) private static int PthreadCondDestroyCore(CpuContext ctx, ulong condAddress)
{ {
if (condAddress == 0) if (condAddress == 0)
{ {
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
} }
var resolvedAddress = ResolveCondHandle(ctx, condAddress);
lock (_stateGate) lock (_stateGate)
{ {
_condStates.Remove(condAddress); _condStates.Remove(resolvedAddress);
if (resolvedAddress != condAddress)
{
_condStates.Remove(condAddress);
}
} }
_ = ctx.TryWriteUInt64(condAddress, 0);
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
private static int PthreadCondWaitCore(CpuContext ctx, ulong condAddress, ulong mutexAddress, bool timed) private static int PthreadCondWaitCore(CpuContext ctx, ulong condAddress, ulong mutexAddress, bool timed, uint timeoutUsec = 0)
{ {
if (condAddress == 0 || mutexAddress == 0) if (condAddress == 0 || mutexAddress == 0)
{ {
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
} }
lock (_stateGate) if (!TryResolveCondState(ctx, condAddress, createIfZero: true, out _, out var state))
{ {
if (!_condStates.TryGetValue(condAddress, out var state)) return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
{ }
state = new PthreadCondState();
_condStates[condAddress] = state;
}
var waitResult = (int)OrbisGen2Result.ORBIS_GEN2_OK;
lock (state.SyncRoot)
{
state.Waiters++; state.Waiters++;
if (state.PendingSignals > 0) var observedEpoch = state.SignalEpoch;
TracePthreadCond("wait-enter", condAddress, mutexAddress, state, timed, waitResult);
var unlockResult = PthreadMutexUnlockCore(ctx, mutexAddress, requireOwner: true);
if (unlockResult != (int)OrbisGen2Result.ORBIS_GEN2_OK)
{ {
state.PendingSignals--;
state.Waiters--; state.Waiters--;
return (int)OrbisGen2Result.ORBIS_GEN2_OK; TracePthreadCond("wait-unlock-fail", condAddress, mutexAddress, state, timed, unlockResult);
return unlockResult;
} }
}
var unlockResult = PthreadMutexUnlockCore(ctx, mutexAddress, requireOwner: true); while (state.SignalEpoch == observedEpoch)
if (unlockResult != (int)OrbisGen2Result.ORBIS_GEN2_OK) {
{ if (!timed)
return unlockResult; {
} Monitor.Wait(state.SyncRoot);
continue;
}
if (timed) if (!Monitor.Wait(state.SyncRoot, GetCondWaitTimeout(timeoutUsec)))
{ {
Thread.Sleep(1); waitResult = (int)OrbisGen2Result.ORBIS_GEN2_ERROR_TIMED_OUT;
} break;
else }
{ }
Thread.Yield();
state.Waiters = Math.Max(0, state.Waiters - 1);
TracePthreadCond(waitResult == (int)OrbisGen2Result.ORBIS_GEN2_OK ? "wait-wake" : "wait-timeout", condAddress, mutexAddress, state, timed, waitResult);
} }
var lockResult = PthreadMutexLockCore(ctx, mutexAddress, tryOnly: false); var lockResult = PthreadMutexLockCore(ctx, mutexAddress, tryOnly: false);
lock (_stateGate) if (lockResult != (int)OrbisGen2Result.ORBIS_GEN2_OK)
{ {
if (_condStates.TryGetValue(condAddress, out var state)) TracePthreadCond("wait-relock-fail", condAddress, mutexAddress, state, timed, lockResult);
{ return lockResult;
state.Waiters = Math.Max(0, state.Waiters - 1);
}
} }
return lockResult; TracePthreadCond(waitResult == (int)OrbisGen2Result.ORBIS_GEN2_OK ? "wait-exit" : "wait-exit-timeout", condAddress, mutexAddress, state, timed, waitResult);
return waitResult;
} }
private static int PthreadCondSignalCore(ulong condAddress, bool broadcast) private static int PthreadCondSignalCore(CpuContext ctx, ulong condAddress, bool broadcast)
{ {
if (condAddress == 0) if (condAddress == 0)
{ {
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
} }
lock (_stateGate) if (!TryResolveCondState(ctx, condAddress, createIfZero: true, out _, out var state))
{ {
if (!_condStates.TryGetValue(condAddress, out var state)) return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
lock (state.SyncRoot)
{
if (state.Waiters > 0)
{ {
state = new PthreadCondState(); state.SignalEpoch++;
_condStates[condAddress] = state; if (broadcast)
{
Monitor.PulseAll(state.SyncRoot);
}
else
{
Monitor.Pulse(state.SyncRoot);
}
} }
if (broadcast) TracePthreadCond(broadcast ? "broadcast" : "signal", condAddress, mutexAddress: 0, state, timed: false, (int)OrbisGen2Result.ORBIS_GEN2_OK);
{
state.PendingSignals += Math.Max(1, state.Waiters);
}
else
{
state.PendingSignals++;
}
} }
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
private static ulong GetCurrentThreadId() private static TimeSpan GetCondWaitTimeout(uint timeoutUsec)
{ {
if (_currentThreadId != 0) if (timeoutUsec == 0)
{ {
return _currentThreadId; return TimeSpan.Zero;
} }
_currentThreadId = unchecked((ulong)Interlocked.Increment(ref _nextSyntheticThreadId)); return TimeSpan.FromTicks((long)timeoutUsec * 10L);
return _currentThreadId;
} }
private static void TracePthreadSelf(CpuContext ctx, ulong currentThreadId) private static int NormalizeMutexType(int type)
{
return type switch
{
0 => MutexTypeDefault,
1 => MutexTypeErrorCheck,
2 => MutexTypeRecursive,
3 => MutexTypeNormal,
4 => MutexTypeNormal,
_ => MutexTypeDefault,
};
}
private static void TracePthreadSelf(CpuContext ctx, ulong currentThreadHandle)
{ {
if (!ShouldTracePthread()) if (!ShouldTracePthread())
{ {
return; return;
} }
var currentThreadId = KernelPthreadState.GetCurrentThreadUniqueId();
Console.Error.WriteLine( Console.Error.WriteLine(
$"[LOADER][TRACE] pthread_self: stale_rdi=0x{ctx[CpuRegister.Rdi]:X16} thread=0x{currentThreadId:X16}"); $"[LOADER][TRACE] pthread_self: stale_rdi=0x{ctx[CpuRegister.Rdi]:X16} thread=0x{currentThreadHandle:X16} tid=0x{currentThreadId:X16}");
} }
private static void TracePthreadMutex(CpuContext ctx, string operation, ulong mutexAddress, ulong resolvedAddress, PthreadMutexState? state, ulong currentThreadId, int result) private static void TracePthreadMutex(CpuContext ctx, string operation, ulong mutexAddress, ulong resolvedAddress, PthreadMutexState? state, ulong currentThreadId, int result)
@@ -808,6 +986,18 @@ public static class KernelPthreadCompatExports
$"recursion={(state?.RecursionCount ?? 0)} type={(state?.Type ?? 0)} result=0x{unchecked((uint)result):X8}"); $"recursion={(state?.RecursionCount ?? 0)} type={(state?.Type ?? 0)} result=0x{unchecked((uint)result):X8}");
} }
private static void TracePthreadCond(string operation, ulong condAddress, ulong mutexAddress, PthreadCondState? state, bool timed, int result)
{
if (!ShouldTracePthread())
{
return;
}
Console.Error.WriteLine(
$"[LOADER][TRACE] pthread_cond_{operation}: cond=0x{condAddress:X16} mutex=0x{mutexAddress:X16} " +
$"waiters={(state?.Waiters ?? 0)} epoch=0x{(state?.SignalEpoch ?? 0):X} timed={timed} result=0x{unchecked((uint)result):X8}");
}
private static bool ShouldTracePthread() private static bool ShouldTracePthread()
{ {
return string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_PTHREADS"), "1", StringComparison.Ordinal); return string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_PTHREADS"), "1", StringComparison.Ordinal);
@@ -5,6 +5,7 @@ using SharpEmu.HLE;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Diagnostics.CodeAnalysis;
namespace SharpEmu.Libs.Kernel; namespace SharpEmu.Libs.Kernel;
@@ -18,6 +19,7 @@ public static class KernelPthreadExtendedCompatExports
private const int DefaultInheritSched = 0; private const int DefaultInheritSched = 0;
private const int DefaultSchedPolicy = 0; private const int DefaultSchedPolicy = 0;
private const int DefaultSchedPriority = 0; private const int DefaultSchedPriority = 0;
private const ulong SyntheticRwlockHandleBase = 0x00006003_0000_0000;
private static readonly object _stateGate = new(); private static readonly object _stateGate = new();
private static readonly Dictionary<ulong, ThreadState> _threadStates = new(); private static readonly Dictionary<ulong, ThreadState> _threadStates = new();
@@ -25,6 +27,7 @@ public static class KernelPthreadExtendedCompatExports
private static readonly Dictionary<ulong, ReaderWriterLockSlim> _rwlockStates = new(); private static readonly Dictionary<ulong, ReaderWriterLockSlim> _rwlockStates = new();
private static readonly Dictionary<int, TlsKeyState> _tlsKeys = new(); private static readonly Dictionary<int, TlsKeyState> _tlsKeys = new();
private static int _nextTlsKey = 1; private static int _nextTlsKey = 1;
private static long _nextSyntheticRwlockHandleId = 1;
[ThreadStatic] [ThreadStatic]
private static Dictionary<int, ulong>? _threadLocalSpecific; private static Dictionary<int, ulong>? _threadLocalSpecific;
@@ -591,20 +594,32 @@ public static class KernelPthreadExtendedCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
} }
var syntheticHandle = AllocateSyntheticHandle(SyntheticRwlockHandleBase, ref _nextSyntheticRwlockHandleId);
lock (_stateGate) lock (_stateGate)
{ {
if (_rwlockStates.Remove(rwlockAddress, out var existing)) var resolvedAddress = ResolveRwlockHandle(ctx, rwlockAddress);
if (_rwlockStates.Remove(resolvedAddress, out var existing))
{ {
existing.Dispose(); existing.Dispose();
} }
_rwlockStates[rwlockAddress] = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); var rwlock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
_rwlockStates[rwlockAddress] = rwlock;
_rwlockStates[syntheticHandle] = rwlock;
} }
_ = ctx.TryWriteUInt64(rwlockAddress, syntheticHandle);
ctx[CpuRegister.Rax] = 0; ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
[SysAbiExport(
Nid = "ytQULN-nhL4",
ExportName = "pthread_rwlock_init",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PosixPthreadRwlockInit(CpuContext ctx) => PthreadRwlockInit(ctx);
[SysAbiExport( [SysAbiExport(
Nid = "BB+kb08Tl9A", Nid = "BB+kb08Tl9A",
ExportName = "scePthreadRwlockDestroy", ExportName = "scePthreadRwlockDestroy",
@@ -618,10 +633,15 @@ public static class KernelPthreadExtendedCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
} }
var resolvedAddress = ResolveRwlockHandle(ctx, rwlockAddress);
ReaderWriterLockSlim? state; ReaderWriterLockSlim? state;
lock (_stateGate) lock (_stateGate)
{ {
_rwlockStates.Remove(rwlockAddress, out state); _rwlockStates.Remove(resolvedAddress, out state);
if (resolvedAddress != rwlockAddress)
{
_rwlockStates.Remove(rwlockAddress);
}
} }
if (state is null) if (state is null)
@@ -629,24 +649,46 @@ public static class KernelPthreadExtendedCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
} }
_ = ctx.TryWriteUInt64(rwlockAddress, 0);
state.Dispose(); state.Dispose();
ctx[CpuRegister.Rax] = 0; ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
[SysAbiExport(
Nid = "1471ajPzxh0",
ExportName = "pthread_rwlock_destroy",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PosixPthreadRwlockDestroy(CpuContext ctx) => PthreadRwlockDestroy(ctx);
[SysAbiExport( [SysAbiExport(
Nid = "Ox9i0c7L5w0", Nid = "Ox9i0c7L5w0",
ExportName = "scePthreadRwlockRdlock", ExportName = "scePthreadRwlockRdlock",
Target = Generation.Gen4 | Generation.Gen5, Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")] LibraryName = "libKernel")]
public static int PthreadRwlockRdlock(CpuContext ctx) => PthreadRwlockLockCore(ctx[CpuRegister.Rdi], write: false); public static int PthreadRwlockRdlock(CpuContext ctx) => PthreadRwlockLockCore(ctx, ctx[CpuRegister.Rdi], write: false);
[SysAbiExport(
Nid = "iGjsr1WAtI0",
ExportName = "pthread_rwlock_rdlock",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PosixPthreadRwlockRdlock(CpuContext ctx) => PthreadRwlockRdlock(ctx);
[SysAbiExport( [SysAbiExport(
Nid = "mqdNorrB+gI", Nid = "mqdNorrB+gI",
ExportName = "scePthreadRwlockWrlock", ExportName = "scePthreadRwlockWrlock",
Target = Generation.Gen4 | Generation.Gen5, Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")] LibraryName = "libKernel")]
public static int PthreadRwlockWrlock(CpuContext ctx) => PthreadRwlockLockCore(ctx[CpuRegister.Rdi], write: true); public static int PthreadRwlockWrlock(CpuContext ctx) => PthreadRwlockLockCore(ctx, ctx[CpuRegister.Rdi], write: true);
[SysAbiExport(
Nid = "sIlRvQqsN2Y",
ExportName = "pthread_rwlock_wrlock",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PosixPthreadRwlockWrlock(CpuContext ctx) => PthreadRwlockWrlock(ctx);
[SysAbiExport( [SysAbiExport(
Nid = "+L98PIbGttk", Nid = "+L98PIbGttk",
@@ -661,13 +703,7 @@ public static class KernelPthreadExtendedCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
} }
ReaderWriterLockSlim? rwlock; if (!TryResolveRwlockState(ctx, rwlockAddress, createIfZero: true, out _, out var rwlock))
lock (_stateGate)
{
_rwlockStates.TryGetValue(rwlockAddress, out rwlock);
}
if (rwlock is null)
{ {
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
} }
@@ -695,6 +731,13 @@ public static class KernelPthreadExtendedCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
[SysAbiExport(
Nid = "EgmLo6EWgso",
ExportName = "pthread_rwlock_unlock",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PosixPthreadRwlockUnlock(CpuContext ctx) => PthreadRwlockUnlock(ctx);
[SysAbiExport( [SysAbiExport(
Nid = "mqULNdimTn0", Nid = "mqULNdimTn0",
ExportName = "pthread_key_create", ExportName = "pthread_key_create",
@@ -730,6 +773,13 @@ public static class KernelPthreadExtendedCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
[SysAbiExport(
Nid = "geDaqgH9lTg",
ExportName = "scePthreadKeyCreate",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int OrbisPthreadKeyCreate(CpuContext ctx) => PosixPthreadKeyCreate(ctx);
[SysAbiExport( [SysAbiExport(
Nid = "6BpEZuDT7YI", Nid = "6BpEZuDT7YI",
ExportName = "pthread_key_delete", ExportName = "pthread_key_delete",
@@ -751,6 +801,13 @@ public static class KernelPthreadExtendedCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
[SysAbiExport(
Nid = "PrdHuuDekhY",
ExportName = "scePthreadKeyDelete",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int OrbisPthreadKeyDelete(CpuContext ctx) => PosixPthreadKeyDelete(ctx);
[SysAbiExport( [SysAbiExport(
Nid = "WrOLvHU0yQM", Nid = "WrOLvHU0yQM",
ExportName = "pthread_setspecific", ExportName = "pthread_setspecific",
@@ -774,6 +831,13 @@ public static class KernelPthreadExtendedCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
[SysAbiExport(
Nid = "+BzXYkqYeLE",
ExportName = "scePthreadSetspecific",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int OrbisPthreadSetspecific(CpuContext ctx) => PosixPthreadSetspecific(ctx);
[SysAbiExport( [SysAbiExport(
Nid = "0-KXaS70xy4", Nid = "0-KXaS70xy4",
ExportName = "pthread_getspecific", ExportName = "pthread_getspecific",
@@ -798,21 +862,23 @@ public static class KernelPthreadExtendedCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
private static int PthreadRwlockLockCore(ulong rwlockAddress, bool write) [SysAbiExport(
Nid = "eoht7mQOCmo",
ExportName = "scePthreadGetspecific",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int OrbisPthreadGetspecific(CpuContext ctx) => PosixPthreadGetspecific(ctx);
private static int PthreadRwlockLockCore(CpuContext ctx, ulong rwlockAddress, bool write)
{ {
if (rwlockAddress == 0) if (rwlockAddress == 0)
{ {
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
} }
ReaderWriterLockSlim rwlock; if (!TryResolveRwlockState(ctx, rwlockAddress, createIfZero: true, out _, out var rwlock))
lock (_stateGate)
{ {
if (!_rwlockStates.TryGetValue(rwlockAddress, out rwlock!)) return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
{
rwlock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
_rwlockStates[rwlockAddress] = rwlock;
}
} }
try try
@@ -834,6 +900,100 @@ public static class KernelPthreadExtendedCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
private static ulong ResolveRwlockHandle(CpuContext ctx, ulong rwlockAddress)
{
if (rwlockAddress == 0)
{
return 0;
}
lock (_stateGate)
{
if (_rwlockStates.ContainsKey(rwlockAddress))
{
return rwlockAddress;
}
}
if (ctx.TryReadUInt64(rwlockAddress, out var pointedHandle) && pointedHandle != 0)
{
lock (_stateGate)
{
if (_rwlockStates.ContainsKey(pointedHandle))
{
return pointedHandle;
}
}
}
return rwlockAddress;
}
private static bool TryResolveRwlockState(CpuContext ctx, ulong rwlockAddress, bool createIfZero, out ulong resolvedAddress, [NotNullWhen(true)] out ReaderWriterLockSlim? rwlock)
{
resolvedAddress = 0;
rwlock = null;
if (rwlockAddress == 0)
{
return false;
}
lock (_stateGate)
{
if (_rwlockStates.TryGetValue(rwlockAddress, out rwlock))
{
resolvedAddress = rwlockAddress;
return true;
}
}
if (!ctx.TryReadUInt64(rwlockAddress, out var pointedHandle))
{
return false;
}
if (pointedHandle != 0)
{
lock (_stateGate)
{
if (_rwlockStates.TryGetValue(pointedHandle, out rwlock))
{
_rwlockStates[rwlockAddress] = rwlock;
resolvedAddress = pointedHandle;
return true;
}
}
resolvedAddress = pointedHandle;
return false;
}
if (!createIfZero)
{
resolvedAddress = rwlockAddress;
return false;
}
var createdRwlock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
var syntheticHandle = AllocateSyntheticHandle(SyntheticRwlockHandleBase, ref _nextSyntheticRwlockHandleId);
lock (_stateGate)
{
_rwlockStates[rwlockAddress] = createdRwlock;
_rwlockStates[syntheticHandle] = createdRwlock;
}
_ = ctx.TryWriteUInt64(rwlockAddress, syntheticHandle);
resolvedAddress = syntheticHandle;
rwlock = createdRwlock;
return true;
}
private static ulong AllocateSyntheticHandle(ulong baseAddress, ref long nextId)
{
var id = unchecked((ulong)Interlocked.Increment(ref nextId));
return baseAddress + (id << 4);
}
private static ThreadState GetOrCreateThreadStateLocked(ulong thread) private static ThreadState GetOrCreateThreadStateLocked(ulong thread)
{ {
if (_threadStates.TryGetValue(thread, out var state)) if (_threadStates.TryGetValue(thread, out var state))
@@ -841,9 +1001,13 @@ public static class KernelPthreadExtendedCompatExports
return state; return state;
} }
var name = KernelPthreadState.TryGetThreadIdentity(thread, out var identity)
? identity.Name
: $"Thread-{thread:X}";
state = new ThreadState state = new ThreadState
{ {
Name = $"Thread-{thread:X}", Name = name,
Priority = DefaultThreadPriority, Priority = DefaultThreadPriority,
AffinityMask = DefaultThreadAffinityMask, AffinityMask = DefaultThreadAffinityMask,
DetachState = DefaultDetachState, DetachState = DefaultDetachState,
@@ -0,0 +1,78 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using System.Runtime.InteropServices;
using System.Threading;
namespace SharpEmu.Libs.Kernel;
internal static class KernelPthreadState
{
private const int ThreadObjectSize = 0x1000;
private static readonly object Gate = new();
private static readonly Dictionary<ulong, ThreadIdentity> Threads = new();
private static readonly byte[] ZeroThreadObject = new byte[ThreadObjectSize];
private static long _nextUniqueThreadId = 1;
[ThreadStatic]
private static ulong _currentThreadHandle;
[ThreadStatic]
private static ulong _currentThreadUniqueId;
internal readonly record struct ThreadIdentity(ulong UniqueId, string Name);
internal static ulong GetCurrentThreadHandle()
{
EnsureCurrentThreadRegistered();
return _currentThreadHandle;
}
internal static ulong GetCurrentThreadUniqueId()
{
EnsureCurrentThreadRegistered();
return _currentThreadUniqueId;
}
internal static ulong CreateThreadHandle(string name)
{
var uniqueId = unchecked((ulong)Interlocked.Increment(ref _nextUniqueThreadId));
return AllocateThreadHandle(uniqueId, name);
}
internal static bool TryGetThreadIdentity(ulong threadHandle, out ThreadIdentity identity)
{
lock (Gate)
{
return Threads.TryGetValue(threadHandle, out identity);
}
}
private static void EnsureCurrentThreadRegistered()
{
if (_currentThreadHandle != 0)
{
return;
}
var uniqueId = unchecked((ulong)Interlocked.Increment(ref _nextUniqueThreadId));
var name = $"Thread-{uniqueId:X}";
_currentThreadHandle = AllocateThreadHandle(uniqueId, name);
_currentThreadUniqueId = uniqueId;
}
private static ulong AllocateThreadHandle(ulong uniqueId, string name)
{
var pointer = Marshal.AllocHGlobal(ThreadObjectSize);
Marshal.Copy(ZeroThreadObject, 0, pointer, ThreadObjectSize);
var handle = unchecked((ulong)pointer.ToInt64());
lock (Gate)
{
Threads[handle] = new ThreadIdentity(uniqueId, string.IsNullOrWhiteSpace(name) ? $"Thread-{uniqueId:X}" : name);
}
return handle;
}
}
@@ -39,7 +39,10 @@ public static class KernelRuntimeCompatExports
private static readonly RdtscDelegate? _rdtscReader = CreateRdtscReader(); private static readonly RdtscDelegate? _rdtscReader = CreateRdtscReader();
private static readonly ulong _kernelTscFrequency = ResolveKernelTscFrequency(); private static readonly ulong _kernelTscFrequency = ResolveKernelTscFrequency();
private static readonly ulong _stackChkGuardValue = 0xC0DEC0DECAFEBABEUL; private static readonly ulong _stackChkGuardValue = 0xC0DEC0DECAFEBABEUL;
private static readonly nint _stackChkGuardObjectAddress = AllocateStackChkGuardObject(); private static readonly nint _stackChkGuardObjectAddress =
HleDataSymbols.TryGetAddress("f7uOxY9mM1U", out var stackChkGuardAddress)
? unchecked((nint)stackChkGuardAddress)
: AllocateStackChkGuardObject();
private static ulong _applicationHeapApiAddress; private static ulong _applicationHeapApiAddress;
private static ulong _processProcParamAddress; private static ulong _processProcParamAddress;
private static ulong _nextReservedVirtualBase = 0x6000_0000_0UL; private static ulong _nextReservedVirtualBase = 0x6000_0000_0UL;
+78
View File
@@ -0,0 +1,78 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using System.Runtime.InteropServices;
using SharpEmu.HLE;
namespace SharpEmu.Libs.LibcInternal;
public static class LibcInternalExports
{
private const ulong HeapTraceInfoSize = 32;
private const int HeapTraceTableEntryCount = 64;
private const int HeapTraceMaskOffset = 0;
private const int HeapTraceTableOffset = HeapTraceMaskOffset + sizeof(ulong);
private const int HeapTraceStorageSize = HeapTraceTableOffset + (HeapTraceTableEntryCount * sizeof(ulong));
private static readonly object _heapTraceGate = new();
private static nint _heapTraceStorage;
[SysAbiExport(
Nid = "NWtTN10cJzE",
ExportName = "LibcHeapGetTraceInfo",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "LibcInternalExt")]
public static int LibcHeapGetTraceInfo(CpuContext ctx)
{
var infoAddress = ctx[CpuRegister.Rdi];
if (infoAddress == 0 || !ctx.TryReadUInt64(infoAddress, out var size) || size != HeapTraceInfoSize)
{
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
var storage = EnsureHeapTraceStorage();
if (storage == 0)
{
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
var maskAddress = unchecked((ulong)(storage + HeapTraceMaskOffset));
var tableAddress = unchecked((ulong)(storage + HeapTraceTableOffset));
if (!ctx.TryWriteUInt64(infoAddress + 16, maskAddress) ||
!ctx.TryWriteUInt64(infoAddress + 24, tableAddress))
{
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
private static nint EnsureHeapTraceStorage()
{
lock (_heapTraceGate)
{
if (_heapTraceStorage != 0)
{
return _heapTraceStorage;
}
var storage = Marshal.AllocHGlobal(HeapTraceStorageSize);
if (storage == 0)
{
return 0;
}
unsafe
{
NativeMemory.Clear((void*)storage, (nuint)HeapTraceStorageSize);
}
_heapTraceStorage = storage;
return storage;
}
}
}
+119
View File
@@ -0,0 +1,119 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
using System.Buffers.Binary;
namespace SharpEmu.Libs.Rtc;
public static class RtcExports
{
[SysAbiExport(
Nid = "ZPD1YOKI+Kw",
ExportName = "sceRtcGetCurrentClockLocalTime",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceRtc")]
public static int RtcGetCurrentClockLocalTime(CpuContext ctx)
{
var timeAddress = ctx[CpuRegister.Rdi];
if (timeAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
var now = DateTimeOffset.Now;
Span<byte> rtcDateTime = stackalloc byte[16];
BinaryPrimitives.WriteUInt16LittleEndian(rtcDateTime[0..2], checked((ushort)now.Year));
BinaryPrimitives.WriteUInt16LittleEndian(rtcDateTime[2..4], checked((ushort)now.Month));
BinaryPrimitives.WriteUInt16LittleEndian(rtcDateTime[4..6], checked((ushort)now.Day));
BinaryPrimitives.WriteUInt16LittleEndian(rtcDateTime[6..8], checked((ushort)now.Hour));
BinaryPrimitives.WriteUInt16LittleEndian(rtcDateTime[8..10], checked((ushort)now.Minute));
BinaryPrimitives.WriteUInt16LittleEndian(rtcDateTime[10..12], checked((ushort)now.Second));
BinaryPrimitives.WriteUInt32LittleEndian(
rtcDateTime[12..16],
checked((uint)((now.Ticks % TimeSpan.TicksPerSecond) / 10)));
if (!ctx.Memory.TryWrite(timeAddress, rtcDateTime))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "8w-H19ip48I",
ExportName = "sceRtcGetTick",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceRtc")]
public static int RtcGetTick(CpuContext ctx)
{
var dateTimeAddress = ctx[CpuRegister.Rdi];
var tickAddress = ctx[CpuRegister.Rsi];
if (dateTimeAddress == 0 || tickAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
if (!TryReadRtcDateTime(ctx, dateTimeAddress, out var rtcDateTime))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ulong tickValue;
try
{
var baseDateTime = new DateTime(
rtcDateTime.Year,
rtcDateTime.Month,
rtcDateTime.Day,
rtcDateTime.Hour,
rtcDateTime.Minute,
rtcDateTime.Second,
DateTimeKind.Utc);
tickValue = checked((ulong)((baseDateTime.Ticks / 10) + rtcDateTime.Microsecond));
}
catch (ArgumentOutOfRangeException)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
if (!ctx.TryWriteUInt64(tickAddress, tickValue))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
private static bool TryReadRtcDateTime(CpuContext ctx, ulong address, out RtcDateTime rtcDateTime)
{
Span<byte> buffer = stackalloc byte[16];
if (!ctx.Memory.TryRead(address, buffer))
{
rtcDateTime = default;
return false;
}
rtcDateTime = new RtcDateTime(
BinaryPrimitives.ReadUInt16LittleEndian(buffer[0..2]),
BinaryPrimitives.ReadUInt16LittleEndian(buffer[2..4]),
BinaryPrimitives.ReadUInt16LittleEndian(buffer[4..6]),
BinaryPrimitives.ReadUInt16LittleEndian(buffer[6..8]),
BinaryPrimitives.ReadUInt16LittleEndian(buffer[8..10]),
BinaryPrimitives.ReadUInt16LittleEndian(buffer[10..12]),
BinaryPrimitives.ReadUInt32LittleEndian(buffer[12..16]));
return true;
}
private readonly record struct RtcDateTime(
ushort Year,
ushort Month,
ushort Day,
ushort Hour,
ushort Minute,
ushort Second,
uint Microsecond);
}