[Core/Dlsym] Restore sceKernelDlsym bootstrap argument normalization from PR #94 (#565)

* [Core/Dlsym] Restore normalize-dlsym-arguments and deferred bootstrap tracing from PR #94

PR #216 regressed two critical features from PR #94:
1. NormalizeKernelDynlibDlsymArguments — handles argument reordering
   when standalone bootstrap loaders call sceKernelDlsym through the
   bridge with (symbol_ptr, handle, out) instead of the standard
   (handle, symbol_ptr, out). Without this, payloads like elfldr-ps5
   and websrv-ps5 fail with a deterministic UnmanagedCallersOnly
   fail-fast.
2. Deferred bootstrap tracing — ring-buffered import logging that
   drains after the hot path, avoiding per-call Console.Error I/O.

Also restored:
- CompleteKernelDynlibDlsymFailure — centralized error handling
- IsPlausibleDynlibSymbolPointer — pointer bounds validation
- COW snapshot of _importEntries in ProbeReturnRip
- ResetLazyDlsymStubState and lazy-dlsym field infrastructure
- DraftDrainDeferredBootstrapTraces in Execute() finally block

Fixes #530, fixes #531

* fix: remove orphaned _importNidHashCache.Clear() reference

The field _importNidHashCache no longer exists on main (removed post PR #94).
The 3-way merge incorrectly restored the .Clear() call without the field
declaration, causing a build failure on all platforms.

---------

Co-authored-by: tru3 <tru3@tru3.com>
This commit is contained in:
h4sht
2026-07-23 14:24:59 +02:00
committed by GitHub
parent 96fde5764f
commit d7bd814fb9
3 changed files with 207 additions and 34 deletions
@@ -91,6 +91,71 @@ public sealed partial class DirectExecutionBackend
} }
} }
private void RecordDeferredBootstrapTrace(
long dispatchIndex,
ulong op,
ulong symbolPointer,
ulong outputPointer,
ulong returnRip)
{
lock (_deferredBootstrapTraceGate)
{
_deferredBootstrapTrace[_deferredBootstrapTraceWriteIndex] = new DeferredBootstrapTraceEntry(
dispatchIndex,
op,
symbolPointer,
outputPointer,
returnRip);
_deferredBootstrapTraceWriteIndex =
(_deferredBootstrapTraceWriteIndex + 1) % _deferredBootstrapTrace.Length;
if (_deferredBootstrapTraceCount < _deferredBootstrapTrace.Length)
{
_deferredBootstrapTraceCount++;
}
}
}
private void DrainDeferredBootstrapTraces()
{
if (!_logBootstrap)
{
return;
}
DeferredBootstrapTraceEntry[] pending;
lock (_deferredBootstrapTraceGate)
{
if (_deferredBootstrapTraceCount == 0)
{
return;
}
pending = new DeferredBootstrapTraceEntry[_deferredBootstrapTraceCount];
var readIndex = (_deferredBootstrapTraceWriteIndex - _deferredBootstrapTraceCount +
_deferredBootstrapTrace.Length) % _deferredBootstrapTrace.Length;
for (var i = 0; i < _deferredBootstrapTraceCount; i++)
{
pending[i] = _deferredBootstrapTrace[(readIndex + i) % _deferredBootstrapTrace.Length];
}
_deferredBootstrapTraceCount = 0;
}
foreach (var entry in pending)
{
var symbolText = "<unreadable>";
if (TryReadAsciiZ(entry.SymbolPointer, 256, out var sym))
{
symbolText = sym;
}
Console.Error.WriteLine(
$"[LOADER][TRACE] bootstrap_call#{entry.DispatchIndex}: op=0x{entry.Op:X16} " +
$"sym_ptr=0x{entry.SymbolPointer:X16} sym='{symbolText}' " +
$"out_ptr=0x{entry.OutputPointer:X16} ret=0x{entry.ReturnRip:X16}");
}
}
private void DumpRecentImportTrace() private void DumpRecentImportTrace()
{ {
var trace = _recentImportTrace; var trace = _recentImportTrace;
@@ -268,14 +333,15 @@ public sealed partial class DirectExecutionBackend
ulong callRip = returnRip + (ulong)i; ulong callRip = returnRip + (ulong)i;
ulong target = unchecked((ulong)((long)(callRip + 5) + rel32)); ulong target = unchecked((ulong)((long)(callRip + 5) + rel32));
Log.Debug($"Import#{dispatchIndex} near-call @{callRip:X16}: target=0x{target:X16}"); Log.Debug($"Import#{dispatchIndex} near-call @{callRip:X16}: target=0x{target:X16}");
for (int importIndex = 0; importIndex < _importEntries.Length; importIndex++) var importEntries = _importEntries;
for (int importIndex = 0; importIndex < importEntries.Length; importIndex++)
{ {
if (_importEntries[importIndex].Address != target) if (importEntries[importIndex].Address != target)
{ {
continue; continue;
} }
string nid = _importEntries[importIndex].Nid; string nid = importEntries[importIndex].Nid;
if (_moduleManager.TryGetExport(nid, out var export)) if (_moduleManager.TryGetExport(nid, out var export))
{ {
Log.Debug( Log.Debug(
@@ -302,14 +368,14 @@ public sealed partial class DirectExecutionBackend
{ {
Log.Debug( Log.Debug(
$"Import#{dispatchIndex} near-call PLT slot: [0x{slot:X16}] = 0x{slotTarget:X16}"); $"Import#{dispatchIndex} near-call PLT slot: [0x{slot:X16}] = 0x{slotTarget:X16}");
for (int importIndex = 0; importIndex < _importEntries.Length; importIndex++) for (int importIndex = 0; importIndex < importEntries.Length; importIndex++)
{ {
if (_importEntries[importIndex].Address != slotTarget) if (importEntries[importIndex].Address != slotTarget)
{ {
continue; continue;
} }
string nid = _importEntries[importIndex].Nid; string nid = importEntries[importIndex].Nid;
if (_moduleManager.TryGetExport(nid, out var export)) if (_moduleManager.TryGetExport(nid, out var export))
{ {
Log.Debug( Log.Debug(
@@ -2024,38 +2024,96 @@ public sealed partial class DirectExecutionBackend
{ {
return OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; return OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
} }
ulong symbolNameAddress = cpuContext[CpuRegister.Rsi];
ulong outputAddress = cpuContext[CpuRegister.Rdx]; NormalizeKernelDynlibDlsymArguments(cpuContext, out var symbolNameAddress, out var outputAddress);
if (!TryReadAsciiZ(symbolNameAddress, 512, out var symbolName)) try
{ {
cpuContext[CpuRegister.Rax] = 18446744073709551615uL; if (!TryReadAsciiZ(symbolNameAddress, 512, out var symbolName))
return OrbisGen2Result.ORBIS_GEN2_OK; {
return CompleteKernelDynlibDlsymFailure(cpuContext, outputAddress);
}
var moduleHandle = unchecked((int)cpuContext[CpuRegister.Rdi]);
if (!TryResolveModuleSymbolAddress(moduleHandle, symbolName, out var resolvedAddress) &&
!TryResolveRuntimeSymbolAddress(symbolName, out resolvedAddress) &&
!TryResolveRuntimeSymbolAddress(ComputePsNid(symbolName), out resolvedAddress) &&
!TryResolveRuntimeSymbolAlias(symbolName, out resolvedAddress))
{
Console.Error.WriteLine(
$"[LOADER][WARN] sceKernelDlsym failed: handle=0x{cpuContext[CpuRegister.Rdi]:X} symbol='{symbolName}'");
return CompleteKernelDynlibDlsymFailure(cpuContext, outputAddress);
}
if (string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_DLSYM"), "1", StringComparison.Ordinal))
{
Console.Error.WriteLine(
$"[LOADER][TRACE] sceKernelDlsym: handle=0x{moduleHandle:X} symbol='{symbolName}' -> 0x{resolvedAddress:X16}");
}
if (outputAddress == 0L || !TryWriteUInt64Compat(outputAddress, resolvedAddress))
{
return CompleteKernelDynlibDlsymFailure(cpuContext, outputAddress);
}
} }
var moduleHandle = unchecked((int)cpuContext[CpuRegister.Rdi]); catch
if (!TryResolveModuleSymbolAddress(moduleHandle, symbolName, out var resolvedAddress) &&
!TryResolveRuntimeSymbolAddress(symbolName, out resolvedAddress) &&
!TryResolveRuntimeSymbolAddress(ComputePsNid(symbolName), out resolvedAddress) &&
!TryResolveRuntimeSymbolAlias(symbolName, out resolvedAddress))
{ {
Console.Error.WriteLine( return CompleteKernelDynlibDlsymFailure(cpuContext, outputAddress);
$"[LOADER][WARN] sceKernelDlsym failed: handle=0x{cpuContext[CpuRegister.Rdi]:X} symbol='{symbolName}'");
cpuContext[CpuRegister.Rax] = 18446744073709551615uL;
return OrbisGen2Result.ORBIS_GEN2_OK;
}
if (string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_DLSYM"), "1", StringComparison.Ordinal))
{
Console.Error.WriteLine(
$"[LOADER][TRACE] sceKernelDlsym: handle=0x{moduleHandle:X} symbol='{symbolName}' -> 0x{resolvedAddress:X16}");
}
if (outputAddress == 0L || !TryWriteUInt64Compat(outputAddress, resolvedAddress))
{
cpuContext[CpuRegister.Rax] = 18446744073709551615uL;
return OrbisGen2Result.ORBIS_GEN2_OK;
} }
cpuContext[CpuRegister.Rax] = 0uL; cpuContext[CpuRegister.Rax] = 0uL;
return OrbisGen2Result.ORBIS_GEN2_OK; return OrbisGen2Result.ORBIS_GEN2_OK;
} }
private static void NormalizeKernelDynlibDlsymArguments(
CpuContext cpuContext,
out ulong symbolNameAddress,
out ulong outputAddress)
{
var handle = cpuContext[CpuRegister.Rdi];
symbolNameAddress = cpuContext[CpuRegister.Rsi];
outputAddress = cpuContext[CpuRegister.Rdx];
// Standalone bootstrap loaders sometimes call through the bridge with
// (symbol_ptr, handle, out) while sceKernelDlsym is (handle, symbol_ptr, out).
// Heuristic only: valid when RSI looks like a small handle and RDI is a guest pointer.
if (symbolNameAddress < 0x10000 &&
IsPlausibleDynlibSymbolPointer(handle))
{
symbolNameAddress = handle;
handle = cpuContext[CpuRegister.Rsi];
cpuContext[CpuRegister.Rdi] = handle;
cpuContext[CpuRegister.Rsi] = symbolNameAddress;
}
}
private static bool IsPlausibleDynlibSymbolPointer(ulong address)
{
return address >= 0x10000 && address < 0x0000_8000_0000_0000UL;
}
private OrbisGen2Result CompleteKernelDynlibDlsymFailure(CpuContext cpuContext, ulong outputAddress)
{
if (outputAddress != 0)
{
_ = TryWriteUInt64Compat(outputAddress, 0);
}
cpuContext[CpuRegister.Rax] = ulong.MaxValue;
return OrbisGen2Result.ORBIS_GEN2_OK;
}
private void ResetLazyDlsymStubState()
{
lock (_lazyDlsymStubGate)
{
_lazyDlsymStubCache.Clear();
_lazyImportStubPoolMapped = false;
_lazyImportStubPoolBase = 0;
_lazyImportStubNextSlot = 0;
_lazyImportStubPoolLimit = 0;
}
}
private static bool TryResolveModuleSymbolAddress(int moduleHandle, string symbolName, out ulong address) private static bool TryResolveModuleSymbolAddress(int moduleHandle, string symbolName, out ulong address)
{ {
if (KernelModuleRegistry.TryResolveModuleSymbol(moduleHandle, symbolName, out address)) if (KernelModuleRegistry.TryResolveModuleSymbol(moduleHandle, symbolName, out address))
@@ -3,6 +3,7 @@
using System; using System;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
@@ -88,6 +89,13 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
ulong GuestThreadHandle, ulong GuestThreadHandle,
int ManagedThreadId); int ManagedThreadId);
private readonly record struct DeferredBootstrapTraceEntry(
long DispatchIndex,
ulong Op,
ulong SymbolPointer,
ulong OutputPointer,
ulong ReturnRip);
#pragma warning disable CS0649 #pragma warning disable CS0649
private struct EXCEPTION_POINTERS private struct EXCEPTION_POINTERS
{ {
@@ -303,7 +311,31 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
private KeyValuePair<string, ulong>[] _runtimeSymbolsByAddress = Array.Empty<KeyValuePair<string, ulong>>(); private KeyValuePair<string, ulong>[] _runtimeSymbolsByAddress = Array.Empty<KeyValuePair<string, ulong>>();
private readonly Dictionary<string, ulong> _runtimeSymbolsByName = new Dictionary<string, ulong>(StringComparer.Ordinal); private readonly ConcurrentDictionary<string, ulong> _runtimeSymbolsByName =
new(StringComparer.Ordinal);
// Keep in sync with SelfLoader import-stub mapping constants.
private const ulong ImportStubRegionCanonicalBase = 0x0000_7000_0000_0000UL;
private const ulong ImportStubRegionAddressStride = 0x0000_0000_0100_0000UL;
private const ulong LazyImportStubSlotSize = 0x10;
private const ulong ImportStubRegionPageSize = 0x1000UL;
private const string KernelDynlibDlsymAerolibNid = "LwG8g3niqwA";
private readonly object _lazyDlsymStubGate = new();
private readonly Dictionary<string, ulong> _lazyDlsymStubCache = new(StringComparer.Ordinal);
private ulong _lazyImportStubPoolBase;
private ulong _lazyImportStubNextSlot;
private ulong _lazyImportStubPoolLimit;
private bool _lazyImportStubPoolMapped;
private readonly RecentImportTraceEntry[] _recentImportTrace = new RecentImportTraceEntry[64]; private readonly RecentImportTraceEntry[] _recentImportTrace = new RecentImportTraceEntry[64];
@@ -311,6 +343,14 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
private int _recentImportTraceWriteIndex; private int _recentImportTraceWriteIndex;
private readonly DeferredBootstrapTraceEntry[] _deferredBootstrapTrace = new DeferredBootstrapTraceEntry[32];
private int _deferredBootstrapTraceCount;
private int _deferredBootstrapTraceWriteIndex;
private readonly object _deferredBootstrapTraceGate = new();
private readonly string[] _distinctImportNidHistory = new string[128]; private readonly string[] _distinctImportNidHistory = new string[128];
private int _distinctImportNidHistoryCount; private int _distinctImportNidHistoryCount;
@@ -1140,8 +1180,14 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
result = OrbisGen2Result.ORBIS_GEN2_OK; result = OrbisGen2Result.ORBIS_GEN2_OK;
LastError = null; LastError = null;
InitializeRuntimeSymbolIndex(runtimeSymbols); InitializeRuntimeSymbolIndex(runtimeSymbols);
ResetLazyDlsymStubState();
_recentImportTraceCount = 0; _recentImportTraceCount = 0;
_recentImportTraceWriteIndex = 0; _recentImportTraceWriteIndex = 0;
lock (_deferredBootstrapTraceGate)
{
_deferredBootstrapTraceCount = 0;
_deferredBootstrapTraceWriteIndex = 0;
}
_distinctImportNidHistoryCount = 0; _distinctImportNidHistoryCount = 0;
_distinctImportNidHistoryWriteIndex = 0; _distinctImportNidHistoryWriteIndex = 0;
_lastDistinctImportNid = string.Empty; _lastDistinctImportNid = string.Empty;
@@ -1224,6 +1270,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
finally finally
{ {
HostSessionControl.SetShutdownHandler(null); HostSessionControl.SetShutdownHandler(null);
DrainDeferredBootstrapTraces();
GuestThreadExecution.Scheduler = previousGuestThreadScheduler; GuestThreadExecution.Scheduler = previousGuestThreadScheduler;
Console.Error.WriteLine("[LOADER][INFO] === Execute END (LastError: " + (LastError ?? "null") + ") ==="); Console.Error.WriteLine("[LOADER][INFO] === Execute END (LastError: " + (LastError ?? "null") + ") ===");
} }
@@ -6680,13 +6727,14 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
ulong rsp = cpuContext[CpuRegister.Rsp]; ulong rsp = cpuContext[CpuRegister.Rsp];
Console.Error.WriteLine($"[LOADER][ERROR] Stall snapshot: rip=0x{cpuContext.Rip:X16} rsp=0x{rsp:X16} rbp=0x{cpuContext[CpuRegister.Rbp]:X16} rax=0x{cpuContext[CpuRegister.Rax]:X16} rbx=0x{cpuContext[CpuRegister.Rbx]:X16} rcx=0x{cpuContext[CpuRegister.Rcx]:X16} rdx=0x{cpuContext[CpuRegister.Rdx]:X16} rsi=0x{cpuContext[CpuRegister.Rsi]:X16} rdi=0x{cpuContext[CpuRegister.Rdi]:X16}"); Console.Error.WriteLine($"[LOADER][ERROR] Stall snapshot: rip=0x{cpuContext.Rip:X16} rsp=0x{rsp:X16} rbp=0x{cpuContext[CpuRegister.Rbp]:X16} rax=0x{cpuContext[CpuRegister.Rax]:X16} rbx=0x{cpuContext[CpuRegister.Rbx]:X16} rcx=0x{cpuContext[CpuRegister.Rcx]:X16} rdx=0x{cpuContext[CpuRegister.Rdx]:X16} rsi=0x{cpuContext[CpuRegister.Rsi]:X16} rdi=0x{cpuContext[CpuRegister.Rdi]:X16}");
ulong num = cpuContext.Rip & 0xFFFFFFFFFFFFFFF0uL; ulong num = cpuContext.Rip & 0xFFFFFFFFFFFFFFF0uL;
for (int i = 0; i < _importEntries.Length; i++) var importEntries = _importEntries;
for (int i = 0; i < importEntries.Length; i++)
{ {
if (_importEntries[i].Address != num) if (importEntries[i].Address != num)
{ {
continue; continue;
} }
string text = _importEntries[i].Nid; string text = importEntries[i].Nid;
if (_moduleManager.TryGetExport(text, out ExportedFunction export)) if (_moduleManager.TryGetExport(text, out ExportedFunction export))
{ {
Console.Error.WriteLine($"[LOADER][ERROR] Stall import-stub: rip=0x{num:X16} nid={text} -> {export.LibraryName}:{export.Name}"); Console.Error.WriteLine($"[LOADER][ERROR] Stall import-stub: rip=0x{num:X16} nid={text} -> {export.LibraryName}:{export.Name}");
@@ -6979,6 +7027,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
_importEntries = Array.Empty<ImportStubEntry>(); _importEntries = Array.Empty<ImportStubEntry>();
_runtimeSymbolsByName.Clear(); _runtimeSymbolsByName.Clear();
StopReadyThreadDispatcher(); StopReadyThreadDispatcher();
ResetLazyDlsymStubState();
StopStallWatchdog(); StopStallWatchdog();
if (_exceptionHandler != 0) if (_exceptionHandler != 0)
{ {