mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-26 20:50:53 +08:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cecf55bf2e | |||
| 0ede7c70a9 | |||
| 8c39a55501 | |||
| 0a4b36ffcb | |||
| 1a1d61b21c | |||
| 3cf96f38de | |||
| d8c44bb64f | |||
| a4b5171586 | |||
| 1f71c970d9 | |||
| 028494a83b | |||
| 73ae502e09 | |||
| 51d204775e | |||
| 7ce6b9e2af | |||
| d4cd4eb96f |
@@ -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
|
||||||
@@ -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:
|
||||||
|
Demon’s 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
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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.
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"sdk": {
|
||||||
|
"version": "10.0.103",
|
||||||
|
"rollForward": "latestFeature"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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,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; }
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 ||
|
||||||
|
|||||||
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user