Step 09 · Emulation foundations

Virtual memory, briefly

Because guest code runs natively, the emulator cannot instrument a memory access. What it can do is take away permission to a page and let the CPU tell it when something touched it. Page protection is not background knowledge here — it is the primary tool.

Before this page: Objects, owners and borrowed memory · Why there's no CPU emulator. View the learning route.

Read

Page protection is not a side topic in this project — it is the mechanism by which the emulator detects that the game has touched something the GPU cares about. You need to understand it properly.

Addresses are lies

When a program reads address 0x900001000, that is a virtual address. The CPU's memory management unit translates it, through page tables the OS maintains, into a physical address in RAM. The translation happens per page — a fixed-size block, 4 KB on PC, 16 KB on PS5.

Each page table entry carries permission bits: readable, writable, executable. If a program accesses a page in a way the bits forbid, the CPU raises a fault. The OS delivers it to the process as an exception (Windows) or a signal (Linux, macOS).

Jargon · reserve versus commit

Reserving address space claims a range of virtual addresses so nothing else can use them, without allocating any physical memory. Committing backs part of that range with real memory.

This split is why an emulator can claim a terabyte of address space up front (see the memory map below) while using only a few gigabytes of RAM. Kyty reserves entire bands and hands out committed pieces from inside them.

The three things Kyty does with protection

1 · Enforce the guest's own protectionskytyWhen the game calls sceKernelMprotect, Kyty applies the equivalent host protection so that a genuine guest bug still faults the way it would on console.
2 · Detect CPU writes to GPU datakytyMemory the GPU has cached as a Vulkan image is deliberately write-protected. When the game writes to it, the fault tells the renderer its copy is stale. The fault is the notification.
3 · Commit reserved ranges lazilykytyEligible faults in the guest backing ranges can commit missing backing. A reserved address alone does not make an access valid; guest mappings, permissions and ownership still matter.

All three funnel into one function — the exception handler installed by the loader — which is why Page faults & CPU/GPU coherency exists.

The page-size mismatch

The PS5 uses 16 KB pages; PCs use 4 KB. Kyty's guest allocator works in 0x4000-byte units:

src/loader/runtimeLinker.cpp
constexpr uint64_t GUEST_PAGE_SIZE = 0x4000;
program->base_size_aligned = AlignUp(program->base_size, GUEST_PAGE_SIZE);

One 16 KB guest page spans four 4 KB host pages. Guest VM alignment and renderer tracking are separate: TRACKER_PAGE_SIZE is 4 KB, so a tracking fault does not inherently dirty the entire 16 KB guest page. Host protection calls accept ranges and can cover several pages in one call.

The address map Kyty imposes

Kyty does not let the host OS choose where things go. It divides the address space into bands and enforces them:

Guest address space (Windows / Linux)click a band
0x00_0004_0000log scale — the user area is ~16× the others combined0xFB_FFFF_FFFF

macOS shifts these ranges and pins them with linker flags — -segaddr SYSTEM_RESERVED,0x7ffffc000 -segaddr USER_AREA,0x7000000000 — so the dynamic loader can never place a system library where the guest needs to allocate. CMakeLists.txt

Why bands, not just "ask the OS"

Console software hard-codes addresses and assumes allocations land in particular ranges. It also passes pointers to the GPU, which in Kyty means those pointers must be distinguishable from emulator pointers. Fixing the bands makes both problems tractable: use the actual range bounds and loaded-segment metadata to classify an address. A hexadecimal prefix alone does not prove that a value is guest code, heap data or a host pointer.

Watch

The detail that poisons everything downstream: the guest is certain pages are 16 KB, the host insists they are 4 KB. Watch what happens to a single guest-level protection change.

Try

Take an address apart by hand. Change it and watch which page it lands in, and where the boundary sits under each page size.

Do

This topic is groundwork, but it is groundwork you will use in the memory and coherency topics constantly.

Checkpoint