How SharpEmu Import Stub Resolution Works: PlayStation NID Handling Explained
SharpEmu resolves imported PlayStation NIDs by creating trap-based import stubs in a dedicated virtual address range, then patching ELF relocations to reference these stubs until the host implements the actual function.
The par274/sharpemu project implements a sophisticated import stub resolution system that bridges the gap between encrypted PlayStation binaries and host execution. When loading a decrypted SELF/ELF image, SharpEmu parses the dynamic section to build relocation descriptors, then generates safe import stubs for unresolved symbols. This mechanism isolates external dependencies while allowing the host to deterministically trap and later resolve native PlayStation function calls.
The Import Stub Resolution Workflow
SharpEmu's resolution process follows a strict eight-step pipeline that transforms raw ELF relocations into executable import stubs.
Locating the Dynamic Segment
The loader begins by identifying the ProgramHeaderType.Dynamic segment in the loaded image. In SelfLoader.cs, the parser reads the dynamic tables—including the string table, symbol table, and RELA/JMPREL tables—to collect all relocations that require external symbol resolution.
Extracting PlayStation NIDs
For each relocation entry that references an imported symbol, SharpEmu extracts the NID (Name Identifier) from the symbol name using the ExtractNid method. The system builds an ordered list of unique NIDs (orderedImportNids) to ensure each imported function receives exactly one stub allocation.
Determining Stub Requirements
Not every NID automatically receives a stub. The ShouldCreateImportStub method evaluates each symbol against two criteria:
- Non-weak relocations: Always require a stub.
- Weak relocations: Only receive a stub if
IModuleManager.TryGetExportcan resolve the symbol to a host implementation.
This selective approach prevents unnecessary memory allocation for symbols that may never be invoked.
Allocating Stub Memory
Approved stubs are allocated in a dedicated virtual address range starting at ImportStubBaseAddress (0x0000_7000_0000_0000). The memory layout uses a fixed stride of ImportStubAddressStride (0x1000_00) with each stub occupying ImportStubSlotSize (0x10) bytes. This sparse allocation scheme prevents accidental execution flow between stubs while maintaining predictable addresses.
Writing Import Stub Bytes
Each stub consists of a minimal trap sequence: a single int 3 instruction (0xCC) followed by a ret (0xC3). This 0xCC … 0xC3 pattern ensures that if guest code jumps to the stub before the host provides a real implementation, the emulator traps safely rather than executing garbage instructions. The stub addresses are stored in the stubsByAddress dictionary for fast lookup.
Patching Relocations
The final phase patches the original ELF relocations to point at the newly created stubs. In ComputeRelocationValue, when processing a relocation that references an imported NID, the loader substitutes the stub address as the symbol value. The result is written back into guest memory via TryWriteRelocationValue. Special handling exists for R_X86_64_TLS_MODID relocations, which patch the TLS module identifier directly, while unsupported types like R_X86_64_COPY trigger exceptions indicating they require full runtime linker support.
Key Source Files and Implementation Details
The import stub system spans several core files in the repository:
SelfLoader.cs(src/SharpEmu.Core/Loader/SelfLoader.cs): Contains the core loading logic, including dynamic section parsing, stub creation, and relocation patching.ImportedSymbolRelocation.cs(src/SharpEmu.Core/Loader/ImportedSymbolRelocation.cs): Defines the data structure representing relocations that reference import stubs, tracking the relationship between guest addresses and NIDs.WindowsHostSymbolResolver.cs(src/SharpEmu.HLE/Host/Windows/WindowsHostSymbolResolver.cs): ImplementsIHostSymbolResolverto map resolved NIDs to actual host function implementations viaIModuleManager.Aerolib.cs(src/SharpEmu.HLE/Aerolib.cs): Provides the NID database (Aerolib.Instance.GetAllNidNames()) that translates raw numeric NIDs into human-readable function names during the stub creation process.
Practical Example: Hooking NIDs at Runtime
The following example demonstrates loading a PlayStation binary and interacting with the import stub system:
// Load an ELF image (simplified)
var image = File.ReadAllBytes("eboot.bin");
var vm = new PhysicalVirtualMemory();
var loader = new SelfLoader();
SelfImage loaded = loader.Load(image, vm, moduleManager);
// Access the import stub mapping after loading
IReadOnlyDictionary<ulong, string> importStubs = loaded.ImportStubs;
// Find the stub address for a specific NID
ulong stubAddr = importStubs.First(kv => kv.Value == "sceKernelOpen").Key;
Console.WriteLine($"Stub for sceKernelOpen → 0x{stubAddr:X}");
// Register a host implementation to replace the stub
moduleManager.RegisterExport("sceKernelOpen", addr =>
{
// Called when guest execution reaches the stub
Console.WriteLine($"Guest called sceKernelOpen @ 0x{addr:X}");
return 0; // Return success to guest
});
This pattern allows developers to implement PlayStation APIs incrementally, with unimplemented functions safely trapping at their stub addresses until host support is added.
Summary
- SharpEmu creates import stubs in the virtual address range
0x0000_7000_0000_0000to handle unresolved PlayStation NIDs. - Each stub contains a trap sequence (
0xCC 0xC3) that safely halts execution if called before the host provides an implementation. - The
ShouldCreateImportStublogic allocates stubs only for non-weak relocations or resolvable weak symbols. - Relocation patching substitutes stub addresses for symbol values via
ComputeRelocationValueandTryWriteRelocationValue. - The system supports TLS-specific relocations (
R_X86_64_TLS_MODID) while rejecting unsupported types likeR_X86_64_COPY.
Frequently Asked Questions
What is a PlayStation NID?
A NID (Name Identifier) is a 64-bit hash used by PlayStation binaries to reference imported system functions. SharpEmu extracts these NIDs from ELF symbol names during the loading process, then uses them to look up human-readable names via the Aerolib database and to create corresponding import stubs.
Why does SharpEmu use int 3 (0xCC) for import stubs?
The int 3 instruction triggers a deterministic trap that the emulator can catch immediately. If guest code calls an unimplemented import, execution hits the 0xCC byte first, allowing the host to intercept the call, log the missing implementation, or redirect to a debugger. The following 0xC3 (ret) provides a safe fallback boundary.
How does the host resolve a stub to a real implementation?
The host implements IHostSymbolResolver and registers concrete functions through IModuleManager.RegisterExport. When the guest reaches a stub, the emulator checks IModuleManager.TryGetExport using the NID as a key. If a host implementation exists, the emulator redirects execution to the host function; otherwise, the trap remains active.
What happens when a relocation type is unsupported?
Unsupported relocation types trigger warnings during the loading phase. For types that require complex runtime linker support—such as R_X86_64_COPY, which copies data between segments—the loader throws an exception indicating that the relocation cannot be resolved statically. This prevents silent memory corruption and alerts developers to implement the specific relocation handling in SelfLoader.cs.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →