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:
146
audit-runs/phase-c9-XamContentCreateEnumerator/investigation.md
Normal file
146
audit-runs/phase-c9-XamContentCreateEnumerator/investigation.md
Normal file
@@ -0,0 +1,146 @@
|
||||
# Phase C+9 — XamContentCreateEnumerator investigation
|
||||
|
||||
## Phase A oracle (predecessor C+8 baseline)
|
||||
|
||||
`canary tid=6 → ours tid=1` main chain, `tid_event_idx = 102197`:
|
||||
|
||||
```
|
||||
canary: kernel.return XamContentCreateEnumerator return_value=1317 status=0x00000525 side_effects=[]
|
||||
ours: kernel.return XamContentCreateEnumerator return_value=0 status=0x00000000 side_effects=[]
|
||||
```
|
||||
|
||||
`args` and `args_resolved` are empty per the current Phase A emitter
|
||||
scope (kernel.call args are not yet wired for dword-only signatures).
|
||||
|
||||
## Framing verification (reading-error #28 — canary source supersedes
|
||||
NT-doc semantics or value-name guesses)
|
||||
|
||||
**The plan's framing was wrong on the constant identity**:
|
||||
`0x525 = 1317` is `X_ERROR_NO_SUCH_USER`, NOT `X_ERROR_NO_CONTENT`
|
||||
(`xbox.h:113`):
|
||||
|
||||
```cpp
|
||||
#define X_ERROR_NO_SUCH_USER X_RESULT_FROM_WIN32(0x00000525L)
|
||||
```
|
||||
|
||||
`X_ERROR_NO_CONTENT` is not even defined in canary's xbox.h. Always
|
||||
verify constants against canary source, never the plan's inline
|
||||
annotations.
|
||||
|
||||
## Canary body (`xenia-canary/src/xenia/kernel/xam/xam_content.cc:129-220`)
|
||||
|
||||
```cpp
|
||||
dword_result_t XamContentCreateEnumerator_entry(
|
||||
dword_t user_index, dword_t device_id, dword_t content_type,
|
||||
dword_t content_flags, dword_t items_per_enumerate,
|
||||
lpdword_t buffer_size_ptr, lpdword_t handle_out) {
|
||||
assert_not_null(handle_out);
|
||||
|
||||
auto device_info = device_id == 0 ? nullptr : GetDummyDeviceInfo(device_id);
|
||||
if ((device_id && device_info == nullptr) || !handle_out) {
|
||||
if (buffer_size_ptr) *buffer_size_ptr = 0;
|
||||
return X_E_INVALIDARG; // 0x80070057
|
||||
}
|
||||
|
||||
if (buffer_size_ptr) {
|
||||
*buffer_size_ptr = sizeof(XCONTENT_DATA) * items_per_enumerate;
|
||||
} // sizeof(XCONTENT_DATA) = 0x134
|
||||
|
||||
uint64_t xuid = 0;
|
||||
if (user_index != XUserIndexNone /* 0xFE */) {
|
||||
const auto& user = kernel_state()->xam_state()->GetUserProfile(user_index);
|
||||
if (!user) return X_ERROR_NO_SUCH_USER; // 0x525 ← THIS BRANCH
|
||||
xuid = user->xuid();
|
||||
}
|
||||
|
||||
// ... enumerator init, ListContent over HDD/ODD, fill items ...
|
||||
*handle_out = e->handle();
|
||||
return X_ERROR_SUCCESS; // 0
|
||||
}
|
||||
```
|
||||
|
||||
Only ONE control-flow path returns `0x525`:
|
||||
|
||||
1. `user_index != 0xFE` (i.e. a specific signed-in user is being queried)
|
||||
2. `XamState::GetUserProfile(user_index)` returns `nullptr` — i.e. no
|
||||
profile is installed at that slot.
|
||||
|
||||
Under canary's default `--mute=true` config (no `--profile_slot_*`
|
||||
flags), `ProfileManager` has zero profiles, so `GetProfile(N)` returns
|
||||
`nullptr` for every `N`.
|
||||
|
||||
## Side-effects check (rule out classification C)
|
||||
|
||||
Canary's `kernel.return` payload has `side_effects=[]`. Combined with
|
||||
the body shape: the `*handle_out` write and the `*buffer_size_ptr`
|
||||
write happen **inside the function** but are not handle-creation
|
||||
side effects (no Phase A `handle.create` emit because no `XEnumerator`
|
||||
was minted). The empty `side_effects` confirms canary reached the
|
||||
`X_ERROR_NO_SUCH_USER` early-return at line 153-155, **before** the
|
||||
`new ContentEnumerator(...)` allocation at line 160-161. So:
|
||||
|
||||
* No handle-table mutation to mirror.
|
||||
* The `*buffer_size_ptr` write happens BEFORE the user check (line
|
||||
145-147), so it does fire on the 0x525 path. Ours must mirror.
|
||||
|
||||
## Classification
|
||||
|
||||
**Class A — Unconditional (under ours's state model).**
|
||||
|
||||
Ours has no profile manager; all `user_index < 4` queries return
|
||||
"no such user". This is effectively unconditional from Sylpheed's
|
||||
perspective. Mirror canary's early-return.
|
||||
|
||||
**NOT class B** (state-dependent over real disk content) — we don't
|
||||
reach the `ListContent()` enumeration path, so we don't need a
|
||||
filesystem scan. No escalation.
|
||||
|
||||
**NOT class C** (side-effect mismatch) — both engines have empty
|
||||
`side_effects` for this idx (above).
|
||||
|
||||
**NOT class D** (Phase A coverage asymmetry) — both engines emit the
|
||||
same 3-event sequence (import.call, kernel.call, kernel.return).
|
||||
|
||||
## Ours pre-fix state
|
||||
|
||||
`crates/xenia-kernel/src/xam.rs:59`:
|
||||
```rust
|
||||
state.register_export(Xam, 0x025C, "XamContentCreateEnumerator", stub_success);
|
||||
```
|
||||
|
||||
`stub_success` sets `r3 = 0`. Hence `return_value=0`, `status=0x00000000`,
|
||||
no buffer_size_ptr write, no handle_out write.
|
||||
|
||||
## Fix shape
|
||||
|
||||
Replace the `stub_success` registration with a dedicated handler that:
|
||||
|
||||
1. Reads r3..r9 (user_index, device_id, content_type, content_flags,
|
||||
items_per_enumerate, buffer_size_ptr, handle_out).
|
||||
2. Returns `X_E_INVALIDARG` (0x80070057) when device_id is unknown
|
||||
(>2; canary's GetDummyDeviceInfo accepts HDD=1, ODD=2) or
|
||||
handle_out is NULL; writes `*buffer_size_ptr = 0` in that arm.
|
||||
3. Otherwise writes `*buffer_size_ptr = 0x134 * items_per_enumerate`
|
||||
(the sizeof(XCONTENT_DATA) computation).
|
||||
4. If `user_index != 0xFE`: return `X_ERROR_NO_SUCH_USER` (0x525) —
|
||||
ours has no profile manager.
|
||||
5. Otherwise (user_index == 0xFE, XUserIndexNone): proceed to a
|
||||
stubbed enumerator-success path — write `*handle_out = 0` and
|
||||
return 0. Defensive; not exercised by current Phase A.
|
||||
|
||||
LOC: ~85 body + ~125 tests = ~210 additive in xam.rs only.
|
||||
|
||||
## Internal-consistency caveat (deferred)
|
||||
|
||||
Ours's `xam_user_get_signin_state` (xam.rs:380-384) returns 1 for
|
||||
`user_index == 0`, claiming user 0 is signed in. This contradicts the
|
||||
"no profile manager" model used by the C+9 fix. The conflict surfaces
|
||||
at Phase A idx 107996 (canary returns 0 for `XamUserGetSigninState`)
|
||||
and will be a separate fix in a future session. Single-fix discipline:
|
||||
do not widen scope here.
|
||||
|
||||
## Internal-consistency caveat #2 (cosmetic)
|
||||
|
||||
The doc comment on `xam_user_get_signin_state` says "user0 signed in
|
||||
locally" but canary returns 0 here. That's evidence of an older fix
|
||||
that needs reconciliation. Out of scope for C+9.
|
||||
Reference in New Issue
Block a user