handoff: VSync/event-wedge fixes + iterate 2.A–2.BC research notes
Source changes (dormant parity infra, retained from iterate 2.AI/2.AO): - xenia-kernel/exports.rs: nt_create_event manual_reset polarity + related event wiring - xenia-gpu/mmio_region.rs: D1MODE_VBLANK_VLINE_STATUS hardcode parity Also lands the audit-runs/ analysis notes (.md/.txt/.json digests) for the iterate 2.x VSync/0x10e8/0x1004 wedge investigation. Raw trace dumps (.jsonl/.gz/.csv/.stdout) and agent worktrees (.claude/) are gitignored as regenerable local artifacts — see memory + HANDOFF for the running findings. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
151
audit-runs/phase-c3-RtlImageXexHeaderField/fix.diff
Normal file
151
audit-runs/phase-c3-RtlImageXexHeaderField/fix.diff
Normal file
@@ -0,0 +1,151 @@
|
||||
## Phase C+3 fix — RtlImageXexHeaderField
|
||||
|
||||
Three files changed; ~80 LOC net.
|
||||
|
||||
### `xenia-rs/crates/xenia-kernel/src/state.rs` (+13 LOC)
|
||||
|
||||
Add `xex_header_guest_ptr: u32` field to `KernelState`. Initialized to 0;
|
||||
populated once at startup by `xenia-app` after copying raw XEX header
|
||||
bytes into guest memory.
|
||||
|
||||
```diff
|
||||
@@ -103,6 +103,17 @@
|
||||
/// Image base of the loaded XEX (for XexExecutableModuleHandle etc.)
|
||||
pub image_base: u32,
|
||||
+ /// Guest VA of the raw XEX header bytes copied into guest memory at
|
||||
+ /// startup (mirrors canary's `UserModule::guest_xex_header_`,
|
||||
+ /// allocated in `user_module.cc:224`). Used by `RtlImageXexHeaderField`
|
||||
+ /// to compute return values that are offsets into the in-guest header
|
||||
+ /// copy (canary's `xboxkrnl_rtl.cc:501-514` calls `UserModule::Get
|
||||
+ /// OptHeader(memory, header, key, &field_value)` which iterates
|
||||
+ /// `header->headers[]` and returns `HostToGuestVirtual(header) +
|
||||
+ /// opt_header.offset` for "else"-class keys, key low byte != 0/1). Zero
|
||||
+ /// when the executable hasn't been installed yet. Set once by
|
||||
+ /// `xenia-app` after `mem.write_bulk(base, &image_data)`.
|
||||
+ pub xex_header_guest_ptr: u32,
|
||||
/// `XEX_HEADER_SYSTEM_FLAGS` (key `0x00030000`) parsed from the loaded
|
||||
/// XEX header. ...
|
||||
|
||||
@@ -330,6 +331,7 @@
|
||||
image_base: 0,
|
||||
+ xex_header_guest_ptr: 0,
|
||||
xex_system_flags: 0,
|
||||
```
|
||||
|
||||
### `xenia-rs/crates/xenia-kernel/src/exports.rs` (~50 LOC)
|
||||
|
||||
Replace stub `rtl_image_xex_header_field` (always returned 0) with a
|
||||
proper implementation mirroring canary's `UserModule::GetOptHeader`
|
||||
(`user_module.cc:335-369`). Walks the in-guest XEX header byte array
|
||||
to find the matching key entry and returns the appropriate value per
|
||||
the key's low-byte class (0x00 inline, 0x01 ptr-to-value, else
|
||||
header-base+offset). Falls back to `state.xex_header_guest_ptr` when
|
||||
the caller passes a NULL `xex_header` arg (the common ours-side case
|
||||
because ours's `*XexExecutableModuleHandle = image_base` doesn't
|
||||
resolve through a proper LDR_DATA_TABLE_ENTRY — see investigation.md
|
||||
for why fixing that breaks Phase A alignment).
|
||||
|
||||
```diff
|
||||
-fn rtl_image_xex_header_field(ctx: &mut PpcContext, _mem: &GuestMemory, _state: &mut KernelState) {
|
||||
- // r3 = xex_header_ptr, r4 = field_id
|
||||
- // Return 0 for all fields
|
||||
- ctx.gpr[3] = 0;
|
||||
-}
|
||||
+fn rtl_image_xex_header_field(ctx: &mut PpcContext, mem: &GuestMemory, state: &mut KernelState) {
|
||||
+ // r3 = xex_header_guest_ptr (may be NULL — game's CRT often passes 0
|
||||
+ // because ours's `*XexExecutableModuleHandle = image_base` doesn't
|
||||
+ // resolve to a real LDR_DATA_TABLE_ENTRY ...). When NULL, fall back
|
||||
+ // to KernelState's recorded `xex_header_guest_ptr`.
|
||||
+ // r4 = field_key (xex2_header_keys).
|
||||
+ //
|
||||
+ // Mirror of canary's `xboxkrnl_rtl.cc:501-514` →
|
||||
+ // `UserModule::GetOptHeader(memory, header, key, &field_value)`.
|
||||
+ let mut xex_header_ptr = ctx.gpr[3] as u32;
|
||||
+ let field_key = ctx.gpr[4] as u32;
|
||||
+ if xex_header_ptr == 0 {
|
||||
+ xex_header_ptr = state.xex_header_guest_ptr;
|
||||
+ }
|
||||
+ if xex_header_ptr == 0 {
|
||||
+ ctx.gpr[3] = 0;
|
||||
+ return;
|
||||
+ }
|
||||
+ let header_count = mem.read_u32(xex_header_ptr.wrapping_add(0x14));
|
||||
+ let entries_base = xex_header_ptr.wrapping_add(0x18);
|
||||
+ let mut field_value: u32 = 0;
|
||||
+ let mut found = false;
|
||||
+ for i in 0..header_count {
|
||||
+ let entry_addr = entries_base.wrapping_add(i.wrapping_mul(8));
|
||||
+ let entry_key = mem.read_u32(entry_addr);
|
||||
+ if entry_key != field_key {
|
||||
+ continue;
|
||||
+ }
|
||||
+ found = true;
|
||||
+ let entry_value_addr = entry_addr.wrapping_add(4);
|
||||
+ match entry_key & 0xFF {
|
||||
+ 0x00 => { field_value = mem.read_u32(entry_value_addr); }
|
||||
+ 0x01 => { field_value = entry_value_addr; }
|
||||
+ _ => {
|
||||
+ let offset = mem.read_u32(entry_value_addr);
|
||||
+ field_value = xex_header_ptr.wrapping_add(offset);
|
||||
+ }
|
||||
+ }
|
||||
+ break;
|
||||
+ }
|
||||
+ if !found {
|
||||
+ ctx.gpr[3] = 0;
|
||||
+ return;
|
||||
+ }
|
||||
+ ctx.gpr[3] = field_value as u64;
|
||||
+}
|
||||
```
|
||||
|
||||
### `xenia-rs/crates/xenia-app/src/main.rs` (+15 LOC)
|
||||
|
||||
In the variable-export patcher for ordinal `0x0193`
|
||||
(`XexExecutableModuleHandle`), keep `*XexExecutableModuleHandle = base`
|
||||
(don't disturb the CRT's early branch) but additionally allocate
|
||||
guest memory for the raw XEX header bytes, copy them in via
|
||||
`mem.write_bulk`, and record the guest VA in
|
||||
`kernel.xex_header_guest_ptr` for the new
|
||||
`rtl_image_xex_header_field` implementation to use as a fallback.
|
||||
|
||||
```diff
|
||||
("xboxkrnl.exe", 0x0193) => {
|
||||
- // XexExecutableModuleHandle -> image base
|
||||
- mem.write_u32(addr, base);
|
||||
+ // (long comment block)
|
||||
+ let header_size = header.header_size as usize;
|
||||
+ if header_size > 0 && header_size <= data.len() {
|
||||
+ let xex_va = alloc_zero(header.header_size, &mut mem, &mut kernel);
|
||||
+ if xex_va != 0 {
|
||||
+ mem.write_bulk(xex_va, &data[0..header_size]);
|
||||
+ kernel.xex_header_guest_ptr = xex_va;
|
||||
+ }
|
||||
+ }
|
||||
+ mem.write_u32(addr, base);
|
||||
}
|
||||
```
|
||||
|
||||
### `xenia-rs/tools/diff-events/diff_events.py` (+13 LOC)
|
||||
|
||||
Add `RtlImageXexHeaderField` to the `ALLOCATOR_RETURN_FNS`
|
||||
canonicalization set. The function's return value for "else"-class keys
|
||||
is a guest VA inside the engine's in-guest XEX header copy, which is
|
||||
allocated at host-allocator-dependent addresses (canary's
|
||||
`SystemHeapAlloc` lands in `0x30xxxxxx`; ours's `KernelState::heap_alloc`
|
||||
lands in `0x4xxxxxxx`). Per-(tid, name) ordinal sentinels mask this VA
|
||||
divergence (same pattern as Phase C+2's allocator canonicalization).
|
||||
|
||||
```diff
|
||||
ALLOCATOR_RETURN_FNS = frozenset(
|
||||
[
|
||||
"MmAllocatePhysicalMemoryEx",
|
||||
"MmAllocatePhysicalMemory",
|
||||
"NtAllocateVirtualMemory",
|
||||
"RtlAllocateHeap",
|
||||
"MmCreateKernelStack",
|
||||
+ # Phase C+3: `RtlImageXexHeaderField` returns ... (see source).
|
||||
+ "RtlImageXexHeaderField",
|
||||
]
|
||||
)
|
||||
```
|
||||
Reference in New Issue
Block a user