How SharpEmu Handles Native CPU Exceptions: Inside the Vectored Exception Handler System

SharpEmu catches hardware-level CPU faults during native PlayStation 5 code execution using a dual-layer Windows SEH integration that translates host exceptions into guest-visible signals through vectored exception handlers and managed callback delegates.

The par274/sharpemu repository implements a high-performance native execution backend that runs PS5 code directly on the host CPU. When guest instructions trigger hardware exceptions—such as page faults, divide-by-zero errors, or invalid instructions—the emulator must intercept these host-level faults, preserve the emulated CPU state, and translate them into exceptions the guest operating system understands. This article examines the exception handling system implemented in src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.cs, which bridges Windows Structured Exception Handling (SEH) with the emulator's High-Level Emulation (HLE) layer.

The Architecture of Native Exception Handling in SharpEmu

SharpEmu's native CPU backend executes guest code directly on the host processor, making it vulnerable to the same memory access violations and arithmetic exceptions as native applications. Unlike interpreted emulation, which traps faults in software, this system requires registering hardware-level exception handlers with the Windows kernel.

Windows SEH Integration and Vectored Handlers

The backend installs two complementary handlers during initialization in the SetupExceptionHandler() method (called from the constructor at line 1037). First, a Vectored Exception Handler (VEH) receives first-chance notification of all exceptions via AddVectoredExceptionHandler. Second, an Unhandled Exception Filter serves as a final safety net through SetUnhandledExceptionFilter.

These handlers are not installed directly as managed methods. Instead, the backend allocates executable memory using VirtualAlloc with PAGE_EXECUTE_READWRITE permissions and generates small native stubs that marshal the EXCEPTION_POINTERS structure from the Windows kernel to the managed runtime.

EXCEPTION_POINTERS and Context Record Structures

To interpret hardware faults, SharpEmu defines blittable structures matching the Windows native SEH layout at lines 86-108 of DirectExecutionBackend.cs:

#pragma warning disable CS0649
private struct EXCEPTION_POINTERS
{
    public unsafe EXCEPTION_RECORD* ExceptionRecord;
    public unsafe void* ContextRecord;
}

private struct EXCEPTION_RECORD
{
    public uint ExceptionCode;
    public uint ExceptionFlags;
    public unsafe EXCEPTION_RECORD* ExceptionRecord;
    public unsafe void* ExceptionAddress;
    public uint NumberParameters;
    public unsafe fixed ulong ExceptionInformation[15];
}
#pragma warning restore CS0649

These structures allow the managed handler to dereference the exception code and CPU context registers without marshaling overhead.

Implementation in DirectExecutionBackend.cs

The exception handling infrastructure spans several thousand lines in the native backend implementation, organized into delegate definitions, stub generation, and registration logic.

Managed Delegate Callbacks

SharpEmu uses unmanaged function pointers to bridge native and managed code. Two critical delegates are defined:

private delegate int ExceptionHandlerDelegate(void* exceptionInfo);
private static readonly ExceptionHandlerDelegate RawVectoredHandlerDelegateInstance = RawVectoredHandlerManaged;
private static readonly nint RawVectoredHandlerPtrManaged =
    Marshal.GetFunctionPointerForDelegate(RawVectoredHandlerDelegateInstance);

The RawVectoredHandlerManaged method (around line 444) serves as the primary entry point for all hardware exceptions, while RawUnhandledFilterManaged handles fatal errors that slip past the vectored handler.

Stub Generation and OS Registration

The SetupExceptionHandler() method creates executable stubs that forward the exception pointer to the managed delegates:

private void SetupExceptionHandler()
{
    // Allocate native stubs around lines 4800-5300
    _rawExceptionHandlerStub = CreateExceptionHandlerStub(RawVectoredHandlerPtrManaged);
    _unhandledFilterStub = CreateExceptionHandlerStub(RawUnhandledFilterPtrManaged);
    
    // Register with Windows APIs
    AddVectoredExceptionHandler(1, _rawExceptionHandlerStub);
    SetUnhandledExceptionFilter(_unhandledFilterStub);
}

After writing the stub machine code, the backend calls FlushInstructionCache to ensure CPU coherence, then registers the handlers with the OS.

The Exception Handling Flow

When guest code triggers a hardware fault, the system processes the exception through a precise six-stage pipeline:

  1. Hardware Trap: The CPU executes JIT-compiled guest code and raises a fault (e.g., STATUS_ACCESS_VIOLATION 0xC0000005), causing Windows to create an EXCEPTION_POINTERS structure on the stack.

  2. Vectored Handler Invocation: The registered VEH stub (_rawExceptionHandlerStub) receives control immediately. This assembly stub extracts the EXCEPTION_POINTERS pointer and jumps to RawVectoredHandlerManaged.

  3. Exception Classification: Inside the managed handler, the code reads exceptionInfo->ExceptionRecord->ExceptionCode and maps Windows codes to internal GuestNativeCallExitReason values:

    • 0xC0000005 (Access Violation) → Exception
    • 0xC0000094 (Integer Divide by Zero) → Exception
    • DBG_PRINTEXCEPTION_C → Ignored/debug output
  4. Context Preservation: For recoverable faults, the handler copies the saved CPU registers from exceptionInfo->ContextRecord into the thread-static ActiveCpuContext. It populates a PendingGuestException record with the fault address, exception type, and guest stack pointer.

  5. HLE Delivery: When the guest thread resumes, the backend checks _pendingGuestExceptions and invokes the appropriate HLE fault handler (e.g., sceKernelRaiseException) so the emulated PS5 code observes the expected POSIX signal behavior.

  6. Continuation: The handler returns EXCEPTION_CONTINUE_EXECUTION to resume with the patched context, or EXCEPTION_CONTINUE_SEARCH if the fault originated outside the emulation boundary.

Safety Mechanisms and Thread State Management

SharpEmu implements several guards to prevent handler corruption and re-entrancy deadlocks.

Thread-Static State Isolation

The backend maintains per-thread execution state using thread-static fields that the vectored handler accesses to determine which guest context to patch:

[ThreadStatic]
private static DirectExecutionBackend? _activeExecutionBackend;

[ThreadStatic]
private static CpuContext? _activeCpuContext;

These fields ensure that when multiple PS5 threads run concurrently, exception handlers route faults to the correct virtual CPU instance.

Re-entrancy Guards and Memory Protection

The handler tracks recursion depth using _vectoredHandlerDepth and _nestedVehTraceCount to prevent infinite loops if the exception handler itself triggers a fault. Before executing generated stubs, the backend verifies memory protection using VirtualProtect and ensures allocated stub pages remain PAGE_EXECUTE_READ after initialization to prevent code injection.

Summary

  • SharpEmu registers vectored exception handlers via AddVectoredExceptionHandler in DirectExecutionBackend.cs to catch hardware faults during native PS5 code execution.
  • Managed callbacks bridge native SEH through executable stubs that marshal EXCEPTION_POINTERS to RawVectoredHandlerManaged and RawUnhandledFilterManaged.
  • Exception translation maps Windows error codes (0xC0000005, 0xC0000094) to guest-visible GuestNativeCallExitReason values, preserving the emulated CPU state in ActiveCpuContext.
  • Thread-static isolation ensures multi-threaded safety, while re-entrancy counters prevent handler deadlock.
  • Fault delivery ultimately routes through the HLE layer to present standard POSIX signals to the guest operating system.

Frequently Asked Questions

How does SharpEmu prevent host crashes when guest code causes a page fault?

SharpEmu registers a first-chance vectored exception handler that intercepts STATUS_ACCESS_VIOLATION (0xC0000005) before the Windows kernel terminates the process. The handler in RawVectoredHandlerManaged checks thread-static state to determine if the fault occurred within emulated memory, translates the violation into a guest exception record, and returns EXCEPTION_CONTINUE_EXECUTION to resume the thread safely.

What is the difference between the vectored handler and the unhandled exception filter in SharpEmu?

The vectored exception handler receives all exceptions first via AddVectoredExceptionHandler, allowing SharpEmu to handle guest faults transparently. The unhandled exception filter serves as a final safety net via SetUnhandledExceptionFilter, catching any faults that bypass the VEH (such as corruption or bugs in the handler itself) and logging the crash before terminating the emulator gracefully.

Why does SharpEmu use executable memory stubs instead of direct managed callbacks?

Windows requires exception handlers to be native code addresses, not managed function pointers. SharpEmu allocates executable memory using VirtualAlloc, writes small assembly stubs that conform to the EXCEPTION_POINTERS calling convention, and marshals the pointer to the managed delegate. This indirection satisfies the OS API requirements while allowing the handler logic to remain in type-safe C# code.

How does the exception handler distinguish between emulator bugs and legitimate guest exceptions?

The handler checks the ExceptionCode field against known hardware fault signatures and validates the fault address against the current guest memory map. If the exception originates from emulator code (outside the JIT-compiled guest regions) or indicates an unrecoverable state (like stack overflow), it routes to RawUnhandledFilterManaged for fatal error handling. Otherwise, it treats the fault as a legitimate guest signal requiring HLE delivery.

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 →