From 3d2c30b1513d10dbc852b218f94934f73c500515 Mon Sep 17 00:00:00 2001 From: Mike Saito Date: Mon, 13 Jul 2026 12:25:37 +0300 Subject: [PATCH] fix(core): lazy dlsym stub materialization, COW snapshots and deferred bootstrap logging (#94) * fix(core): implement 4-tier lazy dlsym stub materialization and argument normalization Enforce transactional and thread-safe resolution for standalone ELF bootstrapper pipelines. - Implement a 4-tier additive fallback cascade (T0: runtime symbols, T1: import entries scan, T2: Aerolib mapping, T3: runtime slack-pool lazy stub allocation at 0x7000_0000_0000). - Fix UnmanagedCallersOnly CLR runtime crashes on second bootstrap by adding NormalizeKernelDynlibDlsymArguments to detect and swap mirrored (symbol, handle) register inputs via rigorous pointer bounds verification. - Protect failure paths via CompleteKernelDynlibDlsymFailure, cleanly zero-filling target outputAddress buffers and returns Rax = -1 with zero managed logging execution in hot native paths. * fix(core): harden lazy-stub diagnostics with COW snapshots and deferred bootstrap logging Follow-up to lazy stub pool copy-on-write publishing in TryGetOrCreateLazyImportStub. - Snapshot _importEntries in ProbeReturnRip before near-call and PLT import lookup loops to prevent torn iteration during concurrent array replacement. - Defer SHARPEMU_LOG_BOOTSTRAP output: hot path records raw register slots in a ring buffer under lock; TryReadAsciiZ and Console.Error run only after import handler completion via DrainDeferredBootstrapTraces. - Normalize bootstrap dynlib register order at DispatchImport gateway entry before any logging or trace reads, so swapped RDI/RSI on Import#2 cannot fault the native gateway when bootstrap tracing is enabled. - Resolve lazy stub pool bounds from the full SelfLoader-mapped import region via VirtualQuery instead of a hardcoded 4 KiB cap. - Use ConcurrentDictionary for runtime symbol registration during concurrent dlsym. - Emit distinct [LOADER][WARN] reasons when import stub region resolution fails versus lazy stub pool exhaustion. --- .../DirectExecutionBackend.Diagnostics.cs | 78 ++- .../Native/DirectExecutionBackend.Imports.cs | 544 ++++++++++++++++-- .../Cpu/Native/DirectExecutionBackend.cs | 57 +- 3 files changed, 624 insertions(+), 55 deletions(-) diff --git a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Diagnostics.cs b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Diagnostics.cs index b279054..f93f881 100644 --- a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Diagnostics.cs +++ b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Diagnostics.cs @@ -38,6 +38,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() { if (_recentImportTraceCount == 0) @@ -170,14 +235,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( @@ -204,14 +270,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 cf209f3..e245dab 100644 --- a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Imports.cs +++ b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Imports.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Runtime.InteropServices; using System.Threading; using SharpEmu.Core.Cpu; +using SharpEmu.Core.Loader; using SharpEmu.HLE; namespace SharpEmu.Core.Cpu.Native; @@ -110,12 +111,13 @@ public sealed partial class DirectExecutionBackend LastError = "Import dispatch called without active CPU context"; return 18446744071562199298uL; } - if ((uint)importIndex >= (uint)_importEntries.Length) + var importEntries = _importEntries; + if ((uint)importIndex >= (uint)importEntries.Length) { LastError = $"Import dispatch index out of range: {importIndex}"; return 18446744071562199042uL; } - ImportStubEntry importStubEntry = _importEntries[importIndex]; + ImportStubEntry importStubEntry = importEntries[importIndex]; int num2 = Volatile.Read(in _rawSentinelRecoveries); if (num2 != _lastReportedRawSentinelRecoveries) { @@ -159,6 +161,14 @@ public sealed partial class DirectExecutionBackend *(ulong*)(xmmSlot + 8)); } cpuContext[CpuRegister.Rsp] = (ulong)argPackPtr + 96uL; + if (string.Equals(importStubEntry.Nid, RuntimeStubNids.BootstrapBridge, StringComparison.Ordinal)) + { + NormalizeKernelDynlibDlsymArguments(cpuContext, out _, out _); + *(ulong*)argPackPtr = cpuContext[CpuRegister.Rdi]; + *(ulong*)(argPackPtr + 8) = cpuContext[CpuRegister.Rsi]; + *(ulong*)(argPackPtr + 16) = cpuContext[CpuRegister.Rdx]; + } + ulong value = cpuContext[CpuRegister.Rdi]; ulong value2 = cpuContext[CpuRegister.Rsi]; ulong num3 = cpuContext[CpuRegister.Rdx]; @@ -209,16 +219,6 @@ public sealed partial class DirectExecutionBackend TraceGuestContext( $"import dispatch={num} nid={importStubEntry.Nid} ret=0x{num7:X16} managed={Environment.CurrentManagedThreadId} guest=0x{GuestThreadExecution.CurrentGuestThreadHandle:X16} fiber=0x{GuestThreadExecution.CurrentFiberAddress:X16} active={HasActiveExecutionThread}"); } - if (_logBootstrap && string.Equals(importStubEntry.Nid, RuntimeStubNids.BootstrapBridge, StringComparison.Ordinal)) - { - string symbolText = ""; - if (TryReadAsciiZ(value2, 256, out var sym)) - { - symbolText = sym; - } - Console.Error.WriteLine( - $"[LOADER][TRACE] bootstrap_call#{num}: op=0x{value:X16} sym_ptr=0x{value2:X16} sym='{symbolText}' out_ptr=0x{num3:X16} ret=0x{num7:X16}"); - } if (!isGuestWorker && !ActiveForcedGuestExit && ShouldForceGuestExitOnImportLoop(importStubEntry.Nid, num7, num, value, value2) && @@ -383,6 +383,11 @@ public sealed partial class DirectExecutionBackend { if (string.Equals(importStubEntry.Nid, RuntimeStubNids.BootstrapBridge, StringComparison.Ordinal)) { + if (_logBootstrap) + { + RecordDeferredBootstrapTrace(num, value, value2, num3, num7); + } + orbisGen2Result = DispatchBootstrapBridge(); } else if (string.Equals(importStubEntry.Nid, RuntimeStubNids.KernelDynlibDlsym, StringComparison.Ordinal) || @@ -545,6 +550,7 @@ public sealed partial class DirectExecutionBackend cpuContext.GetXmmRegister(0, out var returnXmm0Low, out var returnXmm0High); *(ulong*)(argPackPtr - 0x80) = returnXmm0Low; *(ulong*)(argPackPtr - 0x80 + 8) = returnXmm0High; + DrainDeferredBootstrapTraces(); return cpuContext[CpuRegister.Rax]; } catch (Exception ex) @@ -553,6 +559,7 @@ public sealed partial class DirectExecutionBackend Console.Error.WriteLine($"[LOADER][ERROR] {LastError}"); Console.Error.WriteLine($"[LOADER][ERROR] {ex.StackTrace}"); cpuContext[CpuRegister.Rax] = 18446744071562199298uL; + DrainDeferredBootstrapTraces(); return 18446744071562199298uL; } } @@ -1311,30 +1318,485 @@ 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) || + !TryResolveDlsymGuestAddress(symbolName, out var resolvedAddress) || + outputAddress == 0 || + !TryWriteUInt64Compat(outputAddress, resolvedAddress)) + { + return CompleteKernelDynlibDlsymFailure(cpuContext, outputAddress); + } } - if (!TryResolveRuntimeSymbolAddress(symbolName, out var 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 (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 bool TryResolveDlsymGuestAddress(string symbolName, out ulong guestAddress) + { + guestAddress = 0; + if (string.IsNullOrWhiteSpace(symbolName)) + { + return false; + } + + // Tier 0: ELF runtime symbols and aliases. + if (TryResolveRuntimeSymbolAddress(symbolName, out guestAddress) || + TryResolveRuntimeSymbolAlias(symbolName, out guestAddress)) + { + return true; + } + + // Tier 1: existing import stub entries (duplicate prevention). + if (TryFindImportStubGuestAddress(symbolName, out guestAddress)) + { + return true; + } + + var hasAerolibSymbol = Aerolib.Instance.TryGetByExportName(symbolName, out var hleSymbol); + if (hasAerolibSymbol) + { + if (HleDataSymbols.TryGetAddress(hleSymbol.Nid, out _)) + { + return false; + } + + if (TryResolveRuntimeSymbolAddress(hleSymbol.Nid, out guestAddress) || + TryResolveRuntimeSymbolAddress(hleSymbol.ExportName, out guestAddress) || + TryFindImportStubGuestAddress(hleSymbol.Nid, out guestAddress) || + TryFindImportStubGuestAddress(hleSymbol.ExportName, out guestAddress)) + { + return true; + } + } + else if (Aerolib.Instance.TryGetByNid(symbolName, out hleSymbol)) + { + if (HleDataSymbols.TryGetAddress(hleSymbol.Nid, out _)) + { + return false; + } + + if (TryFindImportStubGuestAddress(hleSymbol.Nid, out guestAddress) || + TryFindImportStubGuestAddress(hleSymbol.ExportName, out guestAddress)) + { + return true; + } + + hasAerolibSymbol = true; + } + + // Tier 3: lazy materialization for callable HLE symbols missing from ELF imports. + if (!TryResolveLazyDispatchTarget(symbolName, hasAerolibSymbol, in hleSymbol, out var dispatchNid, out var export)) + { + return false; + } + + return TryGetOrCreateLazyImportStub( + dispatchNid, + symbolName, + hasAerolibSymbol ? hleSymbol : null, + export, + out guestAddress); + } + + private bool TryFindImportStubGuestAddress(string identifier, out ulong guestAddress) + { + guestAddress = 0; + if (string.IsNullOrWhiteSpace(identifier)) + { + return false; + } + + var importEntries = _importEntries; + for (var i = 0; i < importEntries.Length; i++) + { + var entry = importEntries[i]; + if (!ImportStubEntryMatchesIdentifier(entry, identifier)) + { + continue; + } + + if (entry.Address >= 0x10000) + { + guestAddress = entry.Address; + return true; + } + } + + return false; + } + + private static bool ImportStubEntryMatchesIdentifier(in ImportStubEntry entry, string identifier) + { + if (string.Equals(entry.Nid, identifier, StringComparison.Ordinal)) + { + return true; + } + + if (entry.Export is not { } export) + { + return false; + } + + return string.Equals(export.Name, identifier, StringComparison.Ordinal) || + string.Equals(export.Nid, identifier, StringComparison.Ordinal); + } + + private static bool IsKernelDynlibDlsymIdentifier(string identifier) + { + return string.Equals(identifier, "sceKernelDlsym", StringComparison.Ordinal) || + string.Equals(identifier, KernelDynlibDlsymAerolibNid, StringComparison.Ordinal) || + string.Equals(identifier, RuntimeStubNids.KernelDynlibDlsym, StringComparison.Ordinal); + } + + private bool TryResolveLazyDispatchTarget( + string symbolName, + bool hasAerolibSymbol, + in SysAbiSymbol hleSymbol, + out string dispatchNid, + out ExportedFunction? export) + { + export = null; + dispatchNid = string.Empty; + + if (IsKernelDynlibDlsymIdentifier(symbolName)) + { + dispatchNid = KernelDynlibDlsymAerolibNid; + _ = _moduleManager.TryGetExport(dispatchNid, out export); + return true; + } + + if (!hasAerolibSymbol) + { + return false; + } + + if (HleDataSymbols.TryGetAddress(hleSymbol.Nid, out _)) + { + return false; + } + + if (_moduleManager.TryGetExport(hleSymbol.Nid, out export)) + { + dispatchNid = hleSymbol.Nid; + return true; + } + + return false; + } + + private bool TryGetOrCreateLazyImportStub( + string dispatchNid, + string symbolName, + SysAbiSymbol? hleSymbol, + ExportedFunction? export, + out ulong guestAddress) + { + guestAddress = 0; + if (string.IsNullOrWhiteSpace(dispatchNid)) + { + return false; + } + + lock (_lazyDlsymStubGate) + { + if (TryGetCachedLazyDlsymAddress(dispatchNid, symbolName, out guestAddress)) + { + return true; + } + + if (!EnsureLazyImportStubPoolMapped()) + { + LogLazyImportStubMaterializationFailure( + "import stub region unresolved", + dispatchNid, + symbolName); + return false; + } + + if (!TryAllocateLazyImportStubSlot(out guestAddress)) + { + LogLazyImportStubMaterializationFailure( + "lazy import stub pool exhausted", + dispatchNid, + symbolName); + return false; + } + + var previousEntries = _importEntries; + var importIndex = previousEntries.Length; + var newEntries = new ImportStubEntry[importIndex + 1]; + if (importIndex > 0) + { + Array.Copy(previousEntries, newEntries, importIndex); + } + + newEntries[importIndex] = new ImportStubEntry(guestAddress, dispatchNid, export); + + var hostTrampoline = CreateImportHandlerTrampoline(importIndex); + if (hostTrampoline == 0 || + !PatchImportStub((nint)(long)guestAddress, hostTrampoline)) + { + guestAddress = 0; + LogLazyImportStubMaterializationFailure( + "failed to patch lazy import stub trampoline", + dispatchNid, + symbolName); + return false; + } + + _importEntries = newEntries; + + RegisterDlsymRuntimeSymbolKeys(symbolName, dispatchNid, hleSymbol, guestAddress); + return true; + } + } + + private bool TryGetCachedLazyDlsymAddress(string dispatchNid, string symbolName, out ulong guestAddress) + { + if (_lazyDlsymStubCache.TryGetValue(dispatchNid, out guestAddress) || + _lazyDlsymStubCache.TryGetValue(symbolName, out guestAddress)) + { + return guestAddress >= 0x10000; + } + + return false; + } + + private void RegisterDlsymRuntimeSymbolKeys( + string symbolName, + string dispatchNid, + SysAbiSymbol? hleSymbol, + ulong guestAddress) + { + RegisterDlsymRuntimeSymbolKey(symbolName, guestAddress); + RegisterDlsymRuntimeSymbolKey(dispatchNid, guestAddress); + + if (IsKernelDynlibDlsymIdentifier(symbolName) || IsKernelDynlibDlsymIdentifier(dispatchNid)) + { + RegisterDlsymRuntimeSymbolKey(RuntimeStubNids.KernelDynlibDlsym, guestAddress); + RegisterDlsymRuntimeSymbolKey(KernelDynlibDlsymAerolibNid, guestAddress); + RegisterDlsymRuntimeSymbolKey("sceKernelDlsym", guestAddress); + } + + if (hleSymbol.HasValue) + { + RegisterDlsymRuntimeSymbolKey(hleSymbol.Value.Nid, guestAddress); + RegisterDlsymRuntimeSymbolKey(hleSymbol.Value.ExportName, guestAddress); + } + } + + private void RegisterDlsymRuntimeSymbolKey(string key, ulong guestAddress) + { + if (string.IsNullOrWhiteSpace(key) || !IsRuntimeSymbolAddressUsable(guestAddress)) + { + return; + } + + _runtimeSymbolsByName[key] = guestAddress; + _lazyDlsymStubCache[key] = guestAddress; + } + + private void LogLazyImportStubMaterializationFailure(string reason, string dispatchNid, string symbolName) + { + Console.Error.WriteLine( + $"[LOADER][WARN] Lazy import stub materialization failed ({reason}): " + + $"nid={dispatchNid} symbol='{symbolName}'"); + } + + private unsafe bool TryResolveImportStubRegionBounds( + ImportStubEntry[] importEntries, + out ulong regionBase, + out ulong regionLimit) + { + regionBase = 0; + regionLimit = 0; + + for (var candidateIndex = 0; candidateIndex < 64; candidateIndex++) + { + var candidateBase = ImportStubRegionCanonicalBase - + (ulong)candidateIndex * ImportStubRegionAddressStride; + if (VirtualQuery((void*)candidateBase, out var memoryInfo, (nuint)sizeof(MEMORY_BASIC_INFORMATION64)) == 0 || + memoryInfo.RegionSize == 0 || + memoryInfo.State != 4096) + { + continue; + } + + var candidateLimit = candidateBase + memoryInfo.RegionSize; + var hasStub = false; + for (var i = 0; i < importEntries.Length; i++) + { + var entryAddress = importEntries[i].Address; + if (entryAddress < candidateBase || entryAddress >= candidateLimit) + { + continue; + } + + if ((entryAddress - candidateBase) % LazyImportStubSlotSize != 0) + { + continue; + } + + hasStub = true; + break; + } + + if (!hasStub) + { + continue; + } + + regionBase = candidateBase; + regionLimit = candidateLimit; + return true; + } + + ulong maxStubEnd = 0; + for (var i = 0; i < importEntries.Length; i++) + { + var entryAddress = importEntries[i].Address; + if (entryAddress < ImportStubRegionCanonicalBase) + { + continue; + } + + if ((entryAddress - ImportStubRegionCanonicalBase) % LazyImportStubSlotSize != 0) + { + continue; + } + + var entryEnd = entryAddress + LazyImportStubSlotSize; + if (entryEnd > maxStubEnd) + { + maxStubEnd = entryEnd; + regionBase = ImportStubRegionCanonicalBase; + } + } + + if (regionBase == 0 || maxStubEnd <= regionBase) + { + return false; + } + + var spanBytes = maxStubEnd - regionBase; + regionLimit = regionBase + AlignUp(spanBytes, ImportStubRegionPageSize); + return regionLimit > regionBase; + } + + private bool EnsureLazyImportStubPoolMapped() + { + if (_lazyImportStubPoolMapped && _lazyImportStubPoolBase != 0) + { + return true; + } + + var importEntries = _importEntries; + if (!TryResolveImportStubRegionBounds(importEntries, out var importStubRegionBase, out var importStubRegionLimit)) + { + return false; + } + + ulong nextSlot = importStubRegionBase; + for (var i = 0; i < importEntries.Length; i++) + { + var entryAddress = importEntries[i].Address; + if (entryAddress < importStubRegionBase || entryAddress >= importStubRegionLimit) + { + continue; + } + + var entryEnd = entryAddress + LazyImportStubSlotSize; + if (entryEnd > nextSlot) + { + nextSlot = entryEnd; + } + } + + if (nextSlot >= importStubRegionLimit) + { + return false; + } + + _lazyImportStubPoolBase = importStubRegionBase; + _lazyImportStubNextSlot = nextSlot; + _lazyImportStubPoolLimit = importStubRegionLimit; + _lazyImportStubPoolMapped = true; + return true; + } + + private bool TryAllocateLazyImportStubSlot(out ulong guestAddress) + { + guestAddress = 0; + if (!_lazyImportStubPoolMapped || + _lazyImportStubNextSlot < _lazyImportStubPoolBase || + _lazyImportStubNextSlot + LazyImportStubSlotSize > _lazyImportStubPoolLimit) + { + return false; + } + + guestAddress = _lazyImportStubNextSlot; + _lazyImportStubNextSlot += LazyImportStubSlotSize; + return true; + } + private bool TryResolveRuntimeSymbolAlias(string symbolName, out ulong address) { address = 0; @@ -1399,29 +1861,20 @@ public sealed partial class DirectExecutionBackend return OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; } - ulong bridgeHandle = cpuContext[CpuRegister.Rdi]; - ulong symbolNameAddress = cpuContext[CpuRegister.Rsi]; ulong outputAddress = cpuContext[CpuRegister.Rdx]; - _ = TryReadAsciiZ(symbolNameAddress, 512, out var symbolName); - - OrbisGen2Result result = DispatchKernelDynlibDlsym(); - if (result != OrbisGen2Result.ORBIS_GEN2_OK) + try { - return result; + OrbisGen2Result result = DispatchKernelDynlibDlsym(); + if (result != OrbisGen2Result.ORBIS_GEN2_OK) + { + return result; + } } - if (_logBootstrap) + catch { - Console.Error.WriteLine( - $"[LOADER][TRACE] bootstrap_dispatch: handle=0x{bridgeHandle:X16} symbol='{symbolName}' out=0x{outputAddress:X16} rax=0x{cpuContext[CpuRegister.Rax]:X16}"); + return CompleteKernelDynlibDlsymFailure(cpuContext, outputAddress); } - if (cpuContext[CpuRegister.Rax] == 0uL) - { - return OrbisGen2Result.ORBIS_GEN2_OK; - } - - Console.Error.WriteLine( - $"[LOADER][WARN] bootstrap_bridge unresolved: handle=0x{bridgeHandle:X} symbol='{symbolName}' out=0x{outputAddress:X16}"); return OrbisGen2Result.ORBIS_GEN2_OK; } @@ -1462,6 +1915,7 @@ public sealed partial class DirectExecutionBackend { return false; } + List list = new List(Math.Min(maxLength, 256)); Span destination = stackalloc byte[1]; for (int i = 0; i < maxLength; i++) diff --git a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.cs b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.cs index f8ba82e..a4edef7 100644 --- a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.cs +++ b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -48,6 +49,13 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I ulong Arg1, ulong Arg2); + private readonly record struct DeferredBootstrapTraceEntry( + long DispatchIndex, + ulong Op, + ulong SymbolPointer, + ulong OutputPointer, + ulong ReturnRip); + #pragma warning disable CS0649 private struct EXCEPTION_POINTERS { @@ -232,7 +240,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]; @@ -240,6 +272,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; @@ -855,8 +895,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; @@ -930,6 +976,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I } finally { + DrainDeferredBootstrapTraces(); GuestThreadExecution.Scheduler = previousGuestThreadScheduler; Console.Error.WriteLine("[LOADER][INFO] === Execute END (LastError: " + (LastError ?? "null") + ") ==="); } @@ -4456,13 +4503,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}"); @@ -4653,6 +4701,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I ClearImportHandlerTrampolines(); _importEntries = Array.Empty(); _runtimeSymbolsByName.Clear(); + ResetLazyDlsymStubState(); _importNidHashCache.Clear(); StopStallWatchdog(); if (_exceptionHandler != 0)