From d7bd814fb9a5a25da45d7b6426182df16b26bce9 Mon Sep 17 00:00:00 2001 From: h4sht Date: Thu, 23 Jul 2026 14:24:59 +0200 Subject: [PATCH] [Core/Dlsym] Restore sceKernelDlsym bootstrap argument normalization from PR #94 (#565) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [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 --- .../DirectExecutionBackend.Diagnostics.cs | 78 ++++++++++++- .../Native/DirectExecutionBackend.Imports.cs | 106 ++++++++++++++---- .../Cpu/Native/DirectExecutionBackend.cs | 57 +++++++++- 3 files changed, 207 insertions(+), 34 deletions(-) diff --git a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Diagnostics.cs b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Diagnostics.cs index c741472d..6b223033 100644 --- a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Diagnostics.cs +++ b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Diagnostics.cs @@ -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 = ""; + 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() { var trace = _recentImportTrace; @@ -268,14 +333,15 @@ public sealed partial class DirectExecutionBackend ulong callRip = returnRip + (ulong)i; ulong target = unchecked((ulong)((long)(callRip + 5) + rel32)); 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; } - string nid = _importEntries[importIndex].Nid; + string nid = importEntries[importIndex].Nid; if (_moduleManager.TryGetExport(nid, out var export)) { Log.Debug( @@ -302,14 +368,14 @@ public sealed partial class DirectExecutionBackend { Log.Debug( $"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; } - string nid = _importEntries[importIndex].Nid; + string nid = importEntries[importIndex].Nid; if (_moduleManager.TryGetExport(nid, out var export)) { Log.Debug( diff --git a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Imports.cs b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Imports.cs index 345a23f3..92e53ad5 100644 --- a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Imports.cs +++ b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Imports.cs @@ -2024,38 +2024,96 @@ public sealed partial class DirectExecutionBackend { return OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; } - ulong symbolNameAddress = cpuContext[CpuRegister.Rsi]; - ulong outputAddress = cpuContext[CpuRegister.Rdx]; - if (!TryReadAsciiZ(symbolNameAddress, 512, out var symbolName)) + + NormalizeKernelDynlibDlsymArguments(cpuContext, out var symbolNameAddress, out var outputAddress); + try { - cpuContext[CpuRegister.Rax] = 18446744073709551615uL; - return OrbisGen2Result.ORBIS_GEN2_OK; + if (!TryReadAsciiZ(symbolNameAddress, 512, out var symbolName)) + { + 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]); - if (!TryResolveModuleSymbolAddress(moduleHandle, symbolName, out var resolvedAddress) && - !TryResolveRuntimeSymbolAddress(symbolName, out resolvedAddress) && - !TryResolveRuntimeSymbolAddress(ComputePsNid(symbolName), out resolvedAddress) && - !TryResolveRuntimeSymbolAlias(symbolName, out resolvedAddress)) + catch { - Console.Error.WriteLine( - $"[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; + return CompleteKernelDynlibDlsymFailure(cpuContext, outputAddress); } + cpuContext[CpuRegister.Rax] = 0uL; 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) { if (KernelModuleRegistry.TryResolveModuleSymbol(moduleHandle, symbolName, out address)) diff --git a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.cs b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.cs index 4fb10e6f..a8b1f65d 100644 --- a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.cs +++ b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.cs @@ -3,6 +3,7 @@ using System; using System.Buffers.Binary; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -88,6 +89,13 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I ulong GuestThreadHandle, int ManagedThreadId); + private readonly record struct DeferredBootstrapTraceEntry( + long DispatchIndex, + ulong Op, + ulong SymbolPointer, + ulong OutputPointer, + ulong ReturnRip); + #pragma warning disable CS0649 private struct EXCEPTION_POINTERS { @@ -303,7 +311,31 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I private KeyValuePair[] _runtimeSymbolsByAddress = Array.Empty>(); - private readonly Dictionary _runtimeSymbolsByName = new Dictionary(StringComparer.Ordinal); + private readonly ConcurrentDictionary _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 _lazyDlsymStubCache = new(StringComparer.Ordinal); + + private ulong _lazyImportStubPoolBase; + + private ulong _lazyImportStubNextSlot; + + private ulong _lazyImportStubPoolLimit; + + private bool _lazyImportStubPoolMapped; private readonly RecentImportTraceEntry[] _recentImportTrace = new RecentImportTraceEntry[64]; @@ -311,6 +343,14 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I 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 int _distinctImportNidHistoryCount; @@ -1140,8 +1180,14 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I result = OrbisGen2Result.ORBIS_GEN2_OK; LastError = null; InitializeRuntimeSymbolIndex(runtimeSymbols); + ResetLazyDlsymStubState(); _recentImportTraceCount = 0; _recentImportTraceWriteIndex = 0; + lock (_deferredBootstrapTraceGate) + { + _deferredBootstrapTraceCount = 0; + _deferredBootstrapTraceWriteIndex = 0; + } _distinctImportNidHistoryCount = 0; _distinctImportNidHistoryWriteIndex = 0; _lastDistinctImportNid = string.Empty; @@ -1224,6 +1270,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I finally { HostSessionControl.SetShutdownHandler(null); + DrainDeferredBootstrapTraces(); GuestThreadExecution.Scheduler = previousGuestThreadScheduler; 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]; 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; - 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; } - string text = _importEntries[i].Nid; + string text = importEntries[i].Nid; 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}"); @@ -6979,6 +7027,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I _importEntries = Array.Empty(); _runtimeSymbolsByName.Clear(); StopReadyThreadDispatcher(); + ResetLazyDlsymStubState(); StopStallWatchdog(); if (_exceptionHandler != 0) {