Skip to content

fix(core): lazy dlsym stub materialization, COW snapshots and deferred bootstrap logging#94

Merged
par274 merged 2 commits into
par274:mainfrom
MikeSaito:feature/core-lazy-stub-engine
Jul 13, 2026
Merged

fix(core): lazy dlsym stub materialization, COW snapshots and deferred bootstrap logging#94
par274 merged 2 commits into
par274:mainfrom
MikeSaito:feature/core-lazy-stub-engine

Conversation

@MikeSaito

Copy link
Copy Markdown
Contributor

Standalone ELF loaders call sceKernelDlsym through the internal bootstrap bridge before system libraries are imported. Those binaries often have no import-table entry for sceKernelDlsym itself, so the second bootstrap dlsym used to fault inside the native import gateway. This change adds on-demand stub materialization for missing symbols and tightens diagnostics so bootstrap tracing does not disturb gateway execution.

Lazy dlsym resolution:
When DispatchKernelDynlibDlsym looks up a symbol, resolution now walks a fixed fallback chain: runtime symbols and aliases first, then existing import stubs (to avoid duplicates), then Aerolib (rejecting data symbols and re-checking earlier tiers). Only if all of that misses does the backend allocate a new import stub in the slack at the end of the already-mapped stub region at 0x7000_0000_0000 — the same layout SelfLoader uses. The host trampoline is patched first; _importEntries is replaced only after that succeeds, under lock, via copy-on-write.

Bootstrap register layout:
Some standalone loaders pass (symbol_ptr, handle, out) instead of the canonical (handle, symbol_ptr, out). NormalizeKernelDynlibDlsymArguments swaps RDI and RSI only when RSI looks like a small handle and RDI is a plausible guest pointer. Normal game paths are left alone. The swap now runs at the top of DispatchImport for bootstrap bridge calls, before any logging or trace code reads those registers.

Failure paths and materialization warnings:
On resolve failure the output buffer is zeroed and Rax is set to -1 without managed logging on the hot path. If lazy materialization cannot proceed, the backend emits a distinct [LOADER][WARN]: either the import stub region could not be resolved, or the lazy pool within that region is full.

Diagnostics follow-up:
ProbeReturnRip snapshots _importEntries before near-call and PLT loops so a concurrent lazy stub publish cannot cause torn iteration. SHARPEMU_LOG_BOOTSTRAP is deferred: the gateway only records raw register values in a small ring buffer; string reads and console output happen after the handler returns. Runtime symbol registration uses ConcurrentDictionary so concurrent dlsym from guest threads does not race the lazy writer.

Lazy pool sizing:
Pool bounds come from VirtualQuery over the full SelfLoader-mapped import region, not a fixed 4 KiB window. The primary path walks the same 64 candidate bases as SelfLoader; a fallback derives limits from existing stub addresses when VirtualQuery is unavailable.

Out of scope: post-bootstrap module loading (.sprx pipeline after dlsym bootstrap); VFS changes in PR #77; fallback coverage for stride-shifted stub regions without VirtualQuery (not used with PhysicalVirtualMemory today).

Files: DirectExecutionBackend.Imports.cs, DirectExecutionBackend.cs, DirectExecutionBackend.Diagnostics.cs

…ent 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.
…ed 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.
@par274 par274 merged commit 3d2c30b into par274:main Jul 13, 2026
4 checks passed
@MikeSaito MikeSaito deleted the feature/core-lazy-stub-engine branch July 14, 2026 08:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants