diff --git a/crates/xenia-kernel/src/silph_synth.rs b/crates/xenia-kernel/src/silph_synth.rs index f32d6ca..14c21a0 100644 --- a/crates/xenia-kernel/src/silph_synth.rs +++ b/crates/xenia-kernel/src/silph_synth.rs @@ -53,9 +53,40 @@ const SILPH_WORKER_ENTRIES: [u32; 4] = [ 0x8250_65B8, ]; -/// Round 0x440 up to a page so the ctx alloc never straddles a page boundary -/// in heap_alloc's bookkeeping. -const SILPH_CTX_SIZE: u32 = 0x500; +/// Round 0x440 up to a page-ish so the ctx alloc never straddles a page +/// boundary in heap_alloc's bookkeeping. Round 20 grew the alloc from 0x500 +/// to 0x800 to make room for a synthesised sub-object at +0x300 and its +/// 32-slot vtable at +0x500 (= ctx + 0x500..0x580). Both are required because +/// the worker bodies dereference `[ctx+0x2C]` to obtain a sub-object pointer +/// and then virtual-dispatch through its slot 15. +const SILPH_CTX_SIZE: u32 = 0x800; + +/// Offset within the ctx allocation of the synthetic sub-object referenced +/// at `[ctx+0x2C]`. Canary's sub-object sits ~0x300 bytes above the ctx and +/// varies per-instance; we keep it embedded in the same alloc so a single +/// `heap_alloc` covers everything. +const SILPH_SUBOBJ_OFFSET: u32 = 0x300; + +/// Offset within the ctx allocation of the synthetic sub-object vtable. +const SILPH_SUBVTBL_OFFSET: u32 = 0x500; + +/// Number of slots in the synthetic sub-vtable. The worker bodies access +/// slots 15 and 17 (per audit-059 round 19); 32 slots is generous and gives +/// headroom for any downstream vtable index we haven't surveyed yet. +const SILPH_SUBVTBL_SLOTS: u32 = 32; + +/// Trivial `li r3, 0; blr` stub at VA `0x8216CAA4` in the XEX `.text` +/// section — the first 4-byte-aligned standalone instance located by the +/// round-20 PE scan (preceded by a `blr` ending the previous function). +/// We populate every slot of the synthetic sub-vtable with this address so +/// any virtual call through the sub-object idles, returning NULL work and +/// keeping the worker live. +const SILPH_STUB_RETURN_NULL_VA: u32 = 0x8216_CAA4; + +/// Round-19 XEX-resident wrapper constant observed at `[ctx+0x30]` in every +/// canary ctx (audit-059 round 7). Same value for all four ctxes — opaque +/// pointer / handle the worker passes through without dereferencing. +const SILPH_CTX_FIELD_30_CONST: u32 = 0xBE56_8F00; /// 64 KiB worker stack (mirrors AUDIT-048 audio worker), half of canary's /// 128 KiB default. @@ -109,9 +140,35 @@ pub fn spawn_silph_workers( mem.write_u32(ctx + 0x18, 0x3F7F_CCCC); // float ~1.0 (UI rate A) mem.write_u32(ctx + 0x1C, 0x3F80_2D83); // float ~1.0 (UI rate B) mem.write_u32(ctx + 0x24, 0x0000_0001); - // +0x28..+0x30 = three foreign pointers (heap arenas BE/701C/BCA4/B1B6 - // per audit-059 round 7). Left NULL — if any worker dereferences these - // we'll see a guest fault and treat that as the next gate. + + // +0x28..+0x30 = three foreign pointers. + // +0x28 — canary's first-fire snapshot has NULL here. Round-19 fault + // analysis shows worker bodies don't dereference this on + // first entry, so we leave it NULL too. + // +0x2C — sub-object pointer. Worker bodies do + // `lwz r3,44(rN); lwz r11,0(r3); lwz r11,60(r11); bctrl`, + // i.e. virtual-dispatch through slot 15 of the sub-object's + // vtable. Point this at our synthesised sub-object embedded + // at ctx + SILPH_SUBOBJ_OFFSET. + // +0x30 — XEX-resident wrapper constant 0xBE568F00 (round 7). Opaque + // but identical across all four canary ctxes. + let subobj_ptr = ctx + SILPH_SUBOBJ_OFFSET; + let subvtbl_ptr = ctx + SILPH_SUBVTBL_OFFSET; + mem.write_u32(ctx + 0x2C, subobj_ptr); + mem.write_u32(ctx + 0x30, SILPH_CTX_FIELD_30_CONST); + + // ---- Synthetic sub-object at +0x300 ---- + // Round-19 disassembly shows worker bodies only touch the sub-object's + // vtable (slots 15/17) and treat the rest as opaque. Zero-fill is + // sufficient. The only required field is `[+0]` = sub-vtable pointer. + mem.write_u32(subobj_ptr, subvtbl_ptr); + + // ---- Synthetic sub-vtable at +0x500 ---- + // Every slot points at SILPH_STUB_RETURN_NULL_VA so any virtual call + // through the sub-object idles, returning NULL work without crashing. + for slot in 0..SILPH_SUBVTBL_SLOTS { + mem.write_u32(subvtbl_ptr + slot * 4, SILPH_STUB_RETURN_NULL_VA); + } // ---- 4× X_KEVENT auto-reset at +0x54/+0x64/+0x74/+0x84, state = 0 ---- // X_DISPATCH_HEADER layout (canary xobject.h:35):