How SharpEmu Handles CPU Traps and Memory Faults in PS5 Emulation
SharpEmu detects CPU traps and memory faults in the runtime loop of SharpEmu.Core/Runtime/SharpEmuRuntime.cs, enriching raw error codes with detailed diagnostics including opcode previews, disassembly, import-stub mappings, and control-transfer history to simplify debugging of guest binaries.
The par274/sharpemu project implements a PlayStation 5 emulator that requires precise handling of CPU traps and memory faults to diagnose misbehaving guest code. When the emulation engine encounters invalid instructions or inaccessible memory, the runtime transforms low-level error signals into human-readable diagnostic traces that reveal exactly what went wrong inside the virtual CPU.
The Central Runtime Loop
After dispatching the guest entry point, SharpEmuRuntime checks the result code returned by the CPU dispatcher. If the dispatcher reports ORBIS_GEN2_ERROR_CPU_TRAP or ORBIS_GEN2_ERROR_MEMORY_FAULT, the runtime immediately constructs a comprehensive diagnostic snapshot by reading _cpuDispatcher.LastTrapInfo or _cpuDispatcher.LastMemoryFaultInfo.
Handling CPU Traps
When the dispatcher returns ORBIS_GEN2_ERROR_CPU_TRAP, SharpEmu extracts trap metadata from _cpuDispatcher.LastTrapInfo and executes a seven-step diagnostic pipeline (lines 194-242 of SharpEmu.Core/Runtime/SharpEmuRuntime.cs).
Opcode Preview and Disassembly
First, the runtime calls ReadOpcodePreview to fetch up to 8 bytes at the faulting RIP (instruction pointer), providing the raw instruction bytes that triggered the trap. If EnableDisasmDiagnostics is enabled in the execution options, the runtime invokes TryDecodeInstructionAt using the ICE decoder (IcedDecoder.cs) to translate the raw bytes into a human-readable instruction mnemonic.
Special-Case Pattern Detection
The runtime scans for the UD2 illegal-instruction pattern (0F 0B). When detected, it appends trap=ud2 to the diagnostic string, immediately flagging intentional illegal instruction traps. The system also calls IsInvalidLongModeOpcode to detect instructions that are illegal in x86-64 long mode, helping identify compatibility issues with the guest binary's architecture assumptions.
SELF Image and Import-Stub Resolution
When a trap occurs at address 0 with opcode 0xCC inside a self-encrypted ELF (SELF), the runtime adds a specific hint indicating the image may be encrypted or unresolved. Additionally, the runtime looks up the faulting RIP in the active import-stub table to determine which NID (library function identifier) caused the trap, revealing when guest code attempts to call unimplemented or unresolved imported functions.
Control-Transfer Context
Finally, if the CPU dispatcher recorded a recent control-transfer (branch, call, or jump), the diagnostic includes the source address, destination address, opcode, and decoded instruction that led to the trap, providing execution flow context.
The assembled diagnostic follows this format:
CPU trap at RIP=0xXXXXXXXXXXXX, opcode=0xYY, bytes=…, inst=…, import_stubs=…, trap=ud2, hint=…, rip_stub=…, last_transfer=…
Handling Memory Faults
For ORBIS_GEN2_ERROR_MEMORY_FAULT results, the runtime reads _cpuDispatcher.LastMemoryFaultInfo and constructs a similar diagnostic (lines 61-96). The memory fault diagnostic includes:
- Opcode Information – The first byte of the faulting instruction or its decoded mnemonic if available.
- Access Type – Whether the operation was a read or write, along with the faulting guest virtual address.
- Access Size – The size of the memory access in bytes (1, 2, 4, or 8).
- Import-Stub Mapping – Any NID mapped at the faulting RIP, indicating if the fault occurred during an import resolution attempt.
- Control-Transfer History – The most recent branch or call that preceded the fault, identical to the trap handling context.
Like CPU traps, this information is stored in LastExecutionDiagnostics as a formatted string ready for logging or display.
Configuring and Querying Diagnostics
Developers can programmatically access these diagnostics after a run fails.
Querying the Last Diagnostic
var runtime = SharpEmuRuntime.CreateDefault();
var result = runtime.Run("path/to/eboot.bin");
// If the guest crashed, the diagnostic is ready:
if (result != OrbisGen2Result.ORBIS_GEN2_OK)
{
Console.WriteLine(runtime.LastExecutionDiagnostics);
}
Enabling Disassembly Diagnostics
var options = new CpuExecutionOptions
{
EnableDisasmDiagnostics = true, // <-- turn on instruction decoding
CpuEngine = CpuEngineType.Amd64, // choose the backend you prefer
};
var runtime = new SharpEmuRuntime(..., options);
Detecting Specific Trap Types
if (runtime.LastExecutionDiagnostics?.Contains("trap=ud2") == true)
{
Console.WriteLine("Encountered an illegal instruction (UD2).");
}
Key Implementation Files
The fault handling system spans several core components:
-
SharpEmu.Core/Runtime/SharpEmuRuntime.cs– The central runtime that orchestrates CPU trap and memory fault detection, building the diagnostic strings stored inLastExecutionDiagnostics. -
SharpEmu.Core/Cpu/ICpuDispatcher.cs– Defines the interface providingLastTrapInfoandLastMemoryFaultInfoproperties that expose raw fault data from the CPU backend to the runtime. -
SharpEmu.Core/Cpu/Native/DirectExecutionBackend.cs– Implements the low-level CPU dispatch that raisesORBIS_GEN2_ERROR_CPU_TRAPandORBIS_GEN2_ERROR_MEMORY_FAULTwhen guest execution fails. -
SharpEmu.Core/Memory/IVirtualMemory.cs– Abstracts guest memory access, used by the diagnostic system to read opcode previews and verify fault addresses. -
SharpEmu.Core/Cpu/Disasm/IcedDecoder.cs– Wraps the ICE decoder library to convert raw instruction bytes intoDecodedInstobjects for human-readable disassembly output.
Summary
- SharpEmu intercepts CPU traps and memory faults in
SharpEmu.Core/Runtime/SharpEmuRuntime.csby checking dispatcher result codes after guest execution. - CPU trap diagnostics include opcode previews, optional ICE-based disassembly, UD2 detection, long-mode validation, SELF encryption hints, import-stub NID resolution, and control-transfer history.
- Memory fault diagnostics specify the access type, size, faulting address, opcode bytes, import-stub context, and recent control-transfer information.
- Both fault types store their human-readable output in
LastExecutionDiagnostics, accessible immediately after a failedRun()call. - The system supports
EnableDisasmDiagnosticsto toggle instruction decoding, allowing developers to balance diagnostic detail against performance.
Frequently Asked Questions
How does SharpEmu distinguish between a CPU trap and a memory fault?
SharpEmu distinguishes these conditions by examining the OrbisGen2Result returned by the CPU dispatcher. A return value of ORBIS_GEN2_ERROR_CPU_TRAP triggers the trap diagnostic pipeline, while ORBIS_GEN2_ERROR_MEMORY_FAULT initiates memory fault analysis. Each path extracts specific metadata from either LastTrapInfo or LastMemoryFaultInfo to build the appropriate diagnostic context.
What is the UD2 pattern detection used for?
The UD2 pattern (0F 0B) detection identifies intentional illegal instruction traps that compilers or packers insert into PlayStation 5 binaries. When SharpEmu encounters this opcode, it appends trap=ud2 to the diagnostic string, alerting developers that the guest software deliberately triggered an invalid instruction exception, often for debugging or anti-tampering purposes.
Can I enable detailed diagnostics without disassembly to improve performance?
Yes. You can instantiate SharpEmuRuntime with CpuExecutionOptions where EnableDisasmDiagnostics is set to false. This configuration still provides opcode byte previews, fault addresses, import-stub mappings, and control-transfer history, but skips the expensive ICE decoder step, reducing overhead while maintaining essential debugging information.
Where does SharpEmu store the diagnostic output after a fault occurs?
The runtime stores the complete diagnostic string in the LastExecutionDiagnostics property of the SharpEmuRuntime instance. This property is populated immediately after the dispatcher returns an error code, allowing you to inspect the fault details programmatically using runtime.LastExecutionDiagnostics following a call to Run().
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 →