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:
123
audit-runs/phase-c7-XamTaskCloseHandle/investigation.md
Normal file
123
audit-runs/phase-c7-XamTaskCloseHandle/investigation.md
Normal file
@@ -0,0 +1,123 @@
|
||||
# Phase C+7 — XamTaskCloseHandle (idx=102158)
|
||||
|
||||
## Step 1 — Framing verification (reading-error #28 discipline)
|
||||
|
||||
### Canary source (authoritative)
|
||||
|
||||
`xenia-canary/src/xenia/kernel/xam/xam_task.cc:83-93`:
|
||||
|
||||
```cpp
|
||||
dword_result_t XamTaskCloseHandle_entry(dword_t obj_handle) {
|
||||
const X_STATUS error_code = xboxkrnl::NtClose(obj_handle);
|
||||
|
||||
if (XFAILED(error_code)) {
|
||||
const uint32_t result = xboxkrnl::xeRtlNtStatusToDosError(error_code);
|
||||
XThread::SetLastError(result);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
DECLARE_XAM_EXPORT1(XamTaskCloseHandle, kNone, kImplemented);
|
||||
```
|
||||
|
||||
`xenia-canary/src/xenia/kernel/xboxkrnl/xboxkrnl_ob.cc:414-416`:
|
||||
|
||||
```cpp
|
||||
uint32_t NtClose(uint32_t handle) {
|
||||
return kernel_state()->object_table()->ReleaseHandle(handle);
|
||||
}
|
||||
```
|
||||
|
||||
`xenia-canary/src/xenia/kernel/util/object_table.cc:189-208`:
|
||||
|
||||
```cpp
|
||||
X_STATUS ObjectTable::ReleaseHandle(X_HANDLE handle) { /* lock */ return ReleaseHandleInLock(handle); }
|
||||
X_STATUS ObjectTable::ReleaseHandleInLock(X_HANDLE handle) {
|
||||
ObjectTableEntry* entry = LookupTableInLock(handle);
|
||||
if (!entry) {
|
||||
return X_STATUS_INVALID_HANDLE;
|
||||
}
|
||||
if (--entry->handle_ref_count == 0) {
|
||||
return RemoveHandle(handle);
|
||||
}
|
||||
return X_STATUS_SUCCESS;
|
||||
}
|
||||
```
|
||||
|
||||
**Framing CONFIRMED**. The C+6½ XAM audit was accurate this time:
|
||||
|
||||
* `XamTaskCloseHandle_entry` body calls `xboxkrnl::NtClose(obj_handle)`.
|
||||
* Returns `true` (=1) on `XSUCCESS`, `false` (=0) on `XFAILED` (after setting last error).
|
||||
* Signature: `dword_result_t` — emitter records `ctx.gpr[3]` as the dword return.
|
||||
|
||||
Reading-error #28 lesson holds — and this time, the framing was right.
|
||||
The check itself is the discipline.
|
||||
|
||||
### Ours source (pre-fix)
|
||||
|
||||
`xenia-rs/crates/xenia-kernel/src/xam.rs:33`:
|
||||
|
||||
```rust
|
||||
state.register_export(Xam, 0x01B1, "XamTaskCloseHandle", stub_success);
|
||||
```
|
||||
|
||||
`stub_success` (line 103) sets `ctx.gpr[3] = 0`. That's the divergence.
|
||||
|
||||
### Phase A event context (idx 102156-102159 main chain)
|
||||
|
||||
```
|
||||
canary tid=6 [102156] import.call XamTaskCloseHandle (ord 433)
|
||||
canary tid=6 [102157] kernel.call XamTaskCloseHandle args={} args_resolved={}
|
||||
canary tid=6 [102158] kernel.return XamTaskCloseHandle return_value=1
|
||||
canary tid=6 [102159] import.call KeWaitForSingleObject
|
||||
|
||||
ours tid=1 [102156] import.call XamTaskCloseHandle (ord 433)
|
||||
ours tid=1 [102157] kernel.call XamTaskCloseHandle args={} args_resolved={}
|
||||
ours tid=1 [102158] kernel.return XamTaskCloseHandle return_value=0 ← DIVERGENT
|
||||
ours tid=1 [102159] import.call KeWaitForSingleObject
|
||||
```
|
||||
|
||||
`args={}` in both engines — the kernel.call args record is empty for XAM,
|
||||
so the handle being closed is not directly visible in the JSONL. Per
|
||||
canary's `XamTaskSchedule_entry`, the handle was returned via the
|
||||
out-pointer from the preceding `XamTaskSchedule` at idx 102154 (the
|
||||
`12345` placeholder per `xam_task.cc:48`).
|
||||
|
||||
### Step 3 — ours NtClose support for the handle
|
||||
|
||||
Ours's `xam_task_schedule` (xam.rs:216-288) mints a real Thread handle
|
||||
via `state.alloc_handle_for(KernelObject::Thread { … })`. The handle is
|
||||
a regular kernel object that ours's `nt_close` (exports.rs:2193-2225)
|
||||
release-counts identically to canary's `ObjectTable::ReleaseHandle`.
|
||||
No new infrastructure needed.
|
||||
|
||||
## Step 4 — Fix shape
|
||||
|
||||
`xenia-rs/crates/xenia-kernel/src/xam.rs`:
|
||||
|
||||
* Line 33: `register_export(... stub_success)` → `register_export(... xam_task_close_handle)`
|
||||
* New function `xam_task_close_handle` (~38 LOC) that:
|
||||
* Reads handle from `ctx.gpr[3]`.
|
||||
* Checks `state.objects.contains_key(&handle)` — if absent, returns 0 (mirrors `XFAILED(X_STATUS_INVALID_HANDLE)` branch).
|
||||
* Otherwise performs ref-counted release inline (decrement; drop on zero, scrub `async_file_handles` and `disarm_timer`) — identical body to `exports::nt_close` but written inline to avoid widening the exports API for a single XAM helper.
|
||||
* Returns 1.
|
||||
* 4 new unit tests:
|
||||
* `xam_task_close_handle_valid_handle_returns_one_and_releases`
|
||||
* `xam_task_close_handle_invalid_handle_returns_zero`
|
||||
* `xam_task_close_handle_respects_refcount`
|
||||
* `xam_task_schedule_then_close_round_trip_returns_one`
|
||||
|
||||
**Total: +158 LOC, 1 file (38 body + 120 tests).**
|
||||
|
||||
## Step 5 — Outcome
|
||||
|
||||
* Main chain (tid=6 → tid=1): matched-prefix **102158 → 102164 (+6)**.
|
||||
* New first divergence (next target): idx **102164** — `KeResetEvent return_value=1 vs 0` (same class as the C+7 predecessor's KeSetEvent fix; C+7 memo flagged this as deferred).
|
||||
* All other chains unchanged (no regressions).
|
||||
|
||||
## Discipline notes
|
||||
|
||||
* Reading-error #28 verification step took ~3 minutes (read canary source, confirm framing). Worth doing every single time.
|
||||
* Did not need to escalate. Fix scope was within the predicted ~10 LOC body + tests envelope; no handle-table refactor required.
|
||||
* Last-error semantics on the failure branch are intentionally **not** modeled — `XThread::SetLastError` is observation-only in this Phase A horizon (canary's emitter records dword return, not last-error). Note added in the code comment for future investigators.
|
||||
Reference in New Issue
Block a user