Kernel Services¶
On the Xbox 360, the kernel manages the threads, synchronization objects, files and memory of the title, and the title reaches it by calling functions that the kernel exports. In ReXGlue each of these functions is a kernel export, a host function in rexkernel or rexxam. The kernel services are the host objects behind the exports. They hold the state that the exports share, such as the table of open handles and the list of guest threads.
TitleRuntime::Load creates the kernel services, and they stay valid until TitleRuntime::Unload. See Runtime Lifecycle for both calls, and TitleRuntime in the API reference for the call that returns each service.
Reaching the Runtime¶
Export bodies and hooks find the runtime in one of two ways:
kernel::GetCurrentreturns the runtime that the calling thread belongs to. It works on guest threads and on the host worker threads of the runtime, and aborts on any other thread.REX_RUNTIME(), the same askernel::GetProcessRuntime, works on any thread. It returns null beforeTitleRuntime::Loadhas created the services and onceUnloadhas stopped the guest threads.
Kernel Objects and Handles¶
A kernel object is a host object that stands for a console kernel object, such as an event, a thread or an open file. The title refers to an object by a handle, a u32 that the object table assigns. IObjectTable, from TitleRuntime::GetObjectTable, holds every object and its handles.
Handles¶
- A handle is
0xF8000000(kHandleBase) plus four times the slot of the object in the table. - The pseudo handle
0xFFFFFFFFstands for the current process and translates to 0.0xFFFFFFFEtranslates to the handle of the calling thread. Addcreates the first handle of an object.Duplicate,OpenandOpenByNameeach create another handle with its own value, andClosefrees one handle.OpenByNameopens an object by a name thatAddNameregistered, ignoring case.ReferenceandDereferencecount pointer references, which keep an object alive after its last handle is closed.- The table destroys an object when it has no handle, no pointer reference and no pin.
auto added = kernel::GetCurrent().GetObjectTable().Emplace<kernel::EventObject>(
kernel::AddOptions{}, true, false);
if (added) {
X_HANDLE handle = *added;
}
The example creates a manual reset event that starts not signaled, and returns its first handle.
Host code that uses an object for the length of a call pins it. IObjectTable::Get returns a pin, an ObjectPin or a typed Pinned<T>, that keeps the object alive until the pin is destroyed, even if the title closes the last handle in the meantime. An invalid handle, or an object of a type other than T, gives an empty pin.
Resolving a Guest Address¶
Every console synchronization object starts with an X_DISPATCH_HEADER, a small block in guest memory that holds its type and signal state. A title can embed such an object in its own structures and pass its guest address where a handle is expected. IObjectTable::ResolveObject takes the address and returns a pin on the kernel object for it. For an event, mutant, semaphore or timer, the first call creates that object. An object created this way has no handle. A KeInitialize* call over the body of an object that NtCreate* made resets that same object, and threads waiting on it stay blocked.
Object Types¶
ObjectType |
Contents |
|---|---|
kEvent, kSemaphore, kMutant, kTimer |
The host primitive of the same name from rex::platform. |
kFile |
A filesystem::IFile. |
kNotifyListener |
A host event. |
kIOCompletion |
A host semaphore. |
kSymbolicLink, kModule |
No host primitive. |
kThread |
A ThreadRef, which refers to a KernelThread in the thread registry. |
kXAM |
An IXAMObject from object/xam_object.h, such as an enumerator. |
The console keeps a guest block for each object, an X_OBJECT_HEADER followed by the object body. AllocObjectBlock allocates this block from the system heap or from a workspace. XAM enumerator objects take their blocks from the title workspace. NtCreateEvent, NtCreateSemaphore, NtCreateMutant and NtCreateTimer allocate this block, so ObReferenceObjectByHandle returns the body address for their handles. Every dispatcher body starts with an empty wait list, as the console writes it.
Threads¶
Each guest thread is a host thread. A KernelThread, from rex/kernel/object/kernel_thread.h, holds the host thread, the guest register state, the guest X_KTHREAD block, the guest stack and a PCR, the processor control region that guest code reads through register r13. A KernelThread built with a host function instead of a guest entry point is a host worker of the runtime, such as Kernel Dispatch.
IThreadRegistry, from TitleRuntime::GetThreadRegistry, holds every KernelThread:
Launchtakes ownership of aKernelThread, starts it and returns its handle.Find,FindByHandleandAllreturnThreadPinvalues that keep a thread alive while you hold them.RegisterFiberandFindFibertrack the host fibers that run the guest fiber routines.RegisterThreadNotifyregisters the address of anXTHREAD_NOTIFICATIONblock of the title, whose routine runs when a thread starts or ends.
See Thread List for every thread of the process.
Stopping a Thread¶
A guest thread cannot be killed from outside, because it may hold locks or be inside host code. The runtime asks it to stop and waits:
RequestStopmarks the thread. The thread stops at its next call toCheckStopor its next alertable wait, which throwThreadStopto unwind the thread.- If the thread has not stopped after
runtime.terminate_timeout_ms, 2000 ms by default, the runtime logs a warning and callsForceStopevery 5 ms.ForceStopmakes a thread that is inside recompiled code throwThreadStopimmediately. The warning repeats each time the timeout passes again.
A guest thread moves between these states:
stateDiagram-v2
[*] --> Suspended : Launch suspended
[*] --> Running : Launch
Suspended --> Running : Resume
Running --> Suspended : Suspend
Running --> StopRequested : RequestStop
StopRequested --> Finishing : ThreadStop
Running --> Finishing : Return or Exit
Finishing --> Exited : ReleaseSelf
Exited --> [*]
When a guest thread finishes, KernelThread::Finish runs the thread notification routines, unless the whole title is ending.
ITitleExit, from TitleRuntime::GetTitleExit, ends the whole title:
TerminateTitleruns the terminate notifications of the title, then stops and waits for every guest thread.ExitTitlefirst reports aTitleExitReason, then terminates the title. See Title Exit.
Process Model¶
The console kernel runs three processes, each kept in an X_KPROCESS block in guest memory. KernelGuestGlobals from rex/kernel/types/thread.h holds the three blocks, the ten object type descriptors, the TLS lock and the ECC curve table.
| Process | Type | Details |
|---|---|---|
| Idle | kXProcTypeIdle (0) |
Has a quantum of 0x7F. |
| Title | kXProcTypeUser (1) |
Takes the TLS layout and the default stack size from the executable headers. |
| System | kXProcTypeSystem (2) |
Has 32 TLS slots. |
In the tls_slot_bitmap of each process, a set bit marks a free TLS slot.
Dispatcher¶
Two services handle work that the console finishes after the call that starts it.
An overlapped operation is a file or XAM call that returns before its work is done. The title passes an XOVERLAPPED block in guest memory, and the kernel later writes the result, the extended error and the length into the block and signals the event in it. IOverlappedDispatcher completes these operations:
Completewrites the completion on the calling thread.CompleteDeferredmarks the operation pending and returns. TheKernel Dispatchthread then runs theprecallback, waits 100 ms, runs the work, writes the completion and runs thepostcallback.Publishwrites the result, then signals the event.Observereads the block so that a reader sees both writes or neither.CompletionOptionsholds theresult, anX_RESULT, and the optionalextended_errorandlength. The extended error defaults to the result. The length defaults to 0 on success and0xFFFFFFFFon failure.
A DPC, a deferred procedure call, is a routine that the kernel queues to run later at dispatch IRQL, the interrupt priority level at which the console does not switch threads. Timers and interrupt handlers queue DPCs. IDPCQueue manages the queue:
Queueadds a DPC of the title unless it is queued, and returns whether it added it.Removeremoves a queued DPC and returns whether it was queued.Drainruns the queued DPCs one at a time at dispatch IRQL. It does nothing inside an interrupt or a DPC, on a thread with no title state, or while another thread drains.
Guest Audio¶
The audio part of rexkernel is the guest side of audio. The title calls it through the XAudio kernel exports. It does not play sound. It passes the frames of the title to the audio system of the app, which plays them through its driver.
XAudioRegisterRenderDriverClientregisters a render client, a guest callback that produces audio frames. The runtime has eight client slots, and each client gets a stream in the audio system.- The
Audio Workerthread calls the callback of a client each time the audio system needs another frame, andXAudioSubmitRenderDriverFramehands the frame to the stream. - The
XMA Decoderthread decodes XMA, the compressed audio format of the console, for the contexts that the title sets up through the APU register block. See MMIO Blocks. TitleRuntime::SetAudioPausedparks the worker and the decoder, and resumes them.
TitleRuntime::Launch starts guest audio, and the title runs without audio when it fails to start. With no audio system in the app, every client registration fails.
Downloadable Content¶
A host menu manages downloadable content through IContentManager, from TitleRuntime::GetContentManager. Each package can be enabled or disabled for one profile or for all profiles.
- A profile flag overrides the all profiles flag, and a package with neither is enabled.
- A disabled package is absent from
ListContent,HasContentandOpenContentthe next time the title lists or opens content. - The flags are saved in
content_state.tomlin the user data root.
Title Workspace¶
The title workspace is a range of title memory that XAM uses on behalf of the title, for example for enumerator buffers. XamAlloc uses it when the title asks for workspace memory, and XamFree returns memory to it. ITitleWorkspace, from TitleRuntime::GetGuestWorkspace, allocates from it.
- The size comes from the
kXEXHeaderTitleWorkspaceSizeheader of the executable. Without the header the workspace has a default size. ITitleWorkspace::Allocatezeroes the block when the caller asks for it, and returns an error when the workspace is full.- A block is sized to the request, rounded up to 16 bytes. The workspace takes the smallest free range that fits, so a freed block can be taken again by a request of the same size.
ITitleWorkspace::Freeignores an address outside the workspace, or one that is not a live block, with a log message.XamAllocreturns0,E_INVALIDARGfor a null out pointer,E_UNEXPECTEDfor flags that select no heap, orE_OUTOFMEMORYwhen the heap is full.XamAllocExtakes the same path, or allocates physical memory when flags bit0x40000000is set.
Kernel Exports¶
A kernel export is a host function that takes the place of a function that the title imports from the console's kernel, XAM or XBDM. The title reaches it through its import table. See Export Macros for the macros that define exports.
Export Tables¶
rex/kernel/export.h declares the export tables of xboxkrnl, xam and xbdm and the calls that search them.
ExportEntryholds the ordinal, the type (kFunctionorkVariable), the name and the host function.- Each table must be sorted by ordinal, with no ordinal twice. A table that breaks this rule fails to compile.
ModuleFromNameaccepts the short name (xam) or the long name (xam.xex).FindByOrdinalreturns the entry of an ordinal.IsNoReturnExportreturns true for the exports that end the calling thread or restart the console.
XAM Export Forwarding¶
The XAM exports live in two libraries. rexkernel holds the __imp__ symbols that the generated code calls, and the stub bodies. rexxam holds the implemented bodies and the XAM shims.
- Each
__imp__symbol orrexshim_function of XAM inrexkernelforwards to therexxambody of the same name. - The
Initofrexkernelfails and reports the export when a forwarder has no body, a body has no forwarder, or two bodies have the same name. - A forwarder with no bound body logs an error and leaves
r3unchanged.
XBDM Export Forwarding¶
The XBDM exports follow the same split. rexkernel holds the __imp__ symbols and rexxbdm holds the bodies, which log that the export was called and return a fixed value where the export has one.
- The
Initofrexkernelbinds the forwarders whenrexxbdmis loaded and fails and reports the export on a mismatch, as for XAM. - A forwarder with no bound body does what the body does: it logs and sets
r3to the same value.
A hook on an XAM __imp__ symbol in your program replaces the forwarder.
Shims¶
A shim is an SDK function that replaces one of the title's own library routines, such as memcpy, CreateFileA or RtlAllocateHeap. See Shims for how to enable one.