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:

  1. Opcode Information – The first byte of the faulting instruction or its decoded mnemonic if available.
  2. Access Type – Whether the operation was a read or write, along with the faulting guest virtual address.
  3. Access Size – The size of the memory access in bytes (1, 2, 4, or 8).
  4. Import-Stub Mapping – Any NID mapped at the faulting RIP, indicating if the fault occurred during an import resolution attempt.
  5. 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:

Summary

  • SharpEmu intercepts CPU traps and memory faults in SharpEmu.Core/Runtime/SharpEmuRuntime.cs by 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 failed Run() call.
  • The system supports EnableDisasmDiagnostics to 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →