How SysAbi Exports Are Managed in SharpEmu: A Complete Technical Guide

SharpEmu manages SysAbi exports through a reflection-based pipeline that uses SysAbiExportAttribute to mark C# methods as native PS5 kernel functions, ModuleManager to scan assemblies and build concurrent dispatch tables, and ExportedFunction wrappers to handle invocation with full metadata.

SharpEmu is an open-source PlayStation 5 emulator that leverages High-Level Emulation (HLE) to intercept and service system calls from the guest firmware. The project implements a lightweight, attribute-driven architecture to expose native kernel routines to the emulated environment, allowing managed C# code to serve as drop-in replacements for low-level PS5 syscalls.

The Three Core Components of SysAbi Export Management

SysAbiExportAttribute: Declaring Native Exports

The foundation of the export system lives in src/SharpEmu.HLE/SysAbiExportAttribute.cs. This attribute decorates methods that should be exposed to the guest as kernel functions. It captures four critical pieces of metadata: the target library (defaulting to libKernel), the function's NID (Numeric ID), a human-readable export name, and the PS5 generation (Gen4 or Gen5) the export supports.

When applied to a method, the attribute signals that this C# implementation should handle calls to the corresponding native syscall. The method must conform to the delegate signature defined in SysAbiFunction.cs: returning int and accepting either zero parameters or a single CpuContext parameter.

ModuleManager: Scanning and Registration

The ModuleManager class in src/SharpEmu.HLE/ModuleManager.cs acts as the central registry and factory for all exported functions. It performs assembly scanning, signature validation, and dispatch table construction through the following internal workflow:

  • Assembly Scanning: The RegisterFromAssembly() method iterates over every type in a provided assembly, identifying methods bearing SysAbiExportAttribute.
  • Metadata Resolution: For each discovered method, ResolveExportInfo() fills in missing NID or export name values from an optional ISymbolCatalog. If both identifiers remain unresolved, registration throws a clear exception to prevent ambiguous exports.
  • Signature Validation: The ValidateSignature() method enforces that the method returns int and matches the expected parameter layout for the SysAbiFunction delegate.
  • Delegate Construction: Valid methods are wrapped in SysAbiFunction delegates and stored in two concurrent dictionaries:
    • _dispatchTable: Maps raw NID integers directly to delegates for fast syscall dispatch.
    • _exportTable: Maps NIDs to ExportedFunction instances containing metadata for debugging and name-based lookups.

ExportedFunction: The Runtime Wrapper

Defined in src/SharpEmu.HLE/ExportedFunction.cs, this wrapper aggregates the library name, NID, export name, target generation flags, and the underlying delegate. It enables the HLE layer to invoke kernel functions by NID while preserving the contextual metadata needed for logging, debugging, and generation-specific behavior.

The Registration Pipeline: From Attribute to Dispatch Table

The export registration process follows a strict five-step workflow that transforms attributed C# methods into callable native exports:

  1. Attribute Declaration: Developers apply SysAbiExportAttribute to static methods, specifying the library, NID, and generation. The method signature must match int MethodName(CpuContext) or int MethodName().

  2. Assembly Registration: At emulator startup, the core creates a ModuleManager instance and calls RegisterFromAssembly() for each HLE module (kernel, graphics, system libraries), passing the target generation to filter conditional exports.

  3. Export Resolution: The manager calls ResolveExportInfo() to populate missing metadata from symbol catalogs, ensuring every export maintains a unique addressable identity.

  4. Dispatch Table Population: After validation, the system creates a SysAbiFunction delegate and populates both _dispatchTable (for raw speed) and _exportTable (for rich metadata).

  5. Freezing and Pre-compilation: Once all modules are registered, Freeze() locks the manager, runs static constructors, and pre-JIT-compiles every exported method. This prevents CLR fail-fast errors when the guest thread's stack is hijacked during syscall execution.

Generation Targeting and Conditional Exports

SharpEmu supports multiple PS5 hardware revisions through conditional exports. The Generation enum in src/SharpEmu.HLE/Generation.cs defines Gen4 and Gen5 as bitflags.

When RegisterFromAssembly() processes an assembly, it filters methods based on the Target property of the export attribute. A method marked with Target = Generation.Gen5 will only be registered when initializing for a Gen5 console, allowing the same codebase to support different firmware behaviors without conditional compilation or code duplication.

Practical Implementation Example

The following example demonstrates declaring a kernel export and registering it with the module manager:

using SharpEmu.HLE;

// Declare a kernel export with full metadata
[SysAbiExport(
    LibraryName = "libKernel",
    Nid = "0x2A6F0A0A",
    ExportName = "sceKernelGetProcessId",
    Target = Generation.Gen5)]
public static int GetProcessId(CpuContext ctx)
{
    // Access emulated process state through the CPU context
    int pid = ctx.CurrentProcess.Id;
    ctx.WriteInt32(/* out pointer */, pid);
    return 0; // Syscall success code
}

// Register the containing assembly at emulator startup
var moduleMgr = new ModuleManager();
moduleMgr.RegisterFromAssembly(typeof(GetProcessId).Assembly, Generation.Gen5);

// Invoke from the HLE layer when the guest executes a syscall
int nid = 0x2A6F0A0A;
if (moduleMgr.TryGetExport(nid, out var exported))
{
    int result = exported.Function(cpuContextInstance);
    // Result returned to guest as syscall return value
}

After registration completes, the Freeze() method ensures all exports are JIT-compiled and ready for thread-safe execution.

Summary

  • SharpEmu uses an attribute-driven architecture where SysAbiExportAttribute marks C# methods as replacements for PS5 kernel functions in src/SharpEmu.HLE/SysAbiExportAttribute.cs.

  • The ModuleManager handles discovery and validation, scanning assemblies to build concurrent dispatch tables (_dispatchTable and _exportTable) in src/SharpEmu.HLE/ModuleManager.cs.

  • Generation flags in src/SharpEmu.HLE/Generation.cs enable conditional exports for specific PS5 hardware revisions without code duplication.

  • The Freeze() method pre-compiles all exports after registration to ensure thread-safe execution during guest syscall handling.

  • The ExportedFunction wrapper in src/SharpEmu.HLE/ExportedFunction.cs carries metadata and delegates for rich, debuggable invocation.

Frequently Asked Questions

What is the purpose of the SysAbiExportAttribute in SharpEmu?

The SysAbiExportAttribute serves as a declarative marker that binds a C# method to a specific PS5 kernel function. It stores the library name (typically libKernel), the Numeric ID (NID), the export name, and the target generation flags. When applied to methods with valid signatures, it enables the ModuleManager to discover and register the method as a high-level emulation target for guest syscalls.

How does ModuleManager validate export methods before registration?

The ModuleManager validates exports through the ValidateSignature() method, which enforces that export methods return int and accept either no parameters or a single CpuContext parameter. This ensures compatibility with the SysAbiFunction delegate signature. Additionally, ResolveExportInfo() verifies that each export has either a valid NID or export name, preventing ambiguous registrations that could cause dispatch failures.

What are the differences between _dispatchTable and _exportTable?

The _dispatchTable is a concurrent dictionary that maps raw NID integers directly to SysAbiFunction delegates, optimized for fast lookup during hot-path syscall execution. The _exportTable maps NIDs to ExportedFunction instances, which contain rich metadata including library names, export names, and generation flags. While the dispatcher uses the raw table for speed, the export table supports debugging, logging, and name-based lookups.

Why does SharpEmu pre-compile exports using the Freeze() method?

The Freeze() method pre-JIT-compiles all registered exports and warms type initializers after registration completes but before the guest begins execution. This prevents runtime compilation delays and avoids CLR fail-fast errors that could occur if the JIT compiler attempted to generate code while operating on a hijacked guest thread stack during syscall handling.

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 →