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:
179
audit-runs/phase-c12-NtQueryFullAttributesFile/investigation.md
Normal file
179
audit-runs/phase-c12-NtQueryFullAttributesFile/investigation.md
Normal file
@@ -0,0 +1,179 @@
|
||||
# Phase C+12 — investigation (canary's NtQueryFullAttributesFile)
|
||||
|
||||
## Canary's verified behavior
|
||||
|
||||
### Entry point
|
||||
`NtQueryFullAttributesFile_entry` lives in
|
||||
`xenia-canary/src/xenia/kernel/xboxkrnl/xboxkrnl_io.cc:474-513`. It
|
||||
takes `(X_OBJECT_ATTRIBUTES*, X_FILE_NETWORK_OPEN_INFORMATION*)` and:
|
||||
|
||||
1. Translates the ANSI object name to a guest VFS path
|
||||
(`util::TranslateAnsiPath`).
|
||||
2. Calls `kernel_state()->file_system()->ResolvePath(target_path)`.
|
||||
3. On hit, populates the OUT struct from `entry->{create,access,
|
||||
write}_timestamp()` + `entry->allocation_size()` + `entry->size()`
|
||||
+ `entry->attributes()`. Returns `X_STATUS_SUCCESS`.
|
||||
4. On miss, returns `X_STATUS_NO_SUCH_FILE` (NOT
|
||||
`OBJECT_NAME_NOT_FOUND`).
|
||||
|
||||
`xboxkrnl_io.cc:498-512`:
|
||||
|
||||
```cpp
|
||||
auto entry = kernel_state()->file_system()->ResolvePath(target_path);
|
||||
if (entry) {
|
||||
file_info->creation_time = entry->create_timestamp();
|
||||
file_info->last_access_time= entry->access_timestamp();
|
||||
file_info->last_write_time = entry->write_timestamp();
|
||||
file_info->change_time = entry->write_timestamp(); // <-- same as write
|
||||
file_info->allocation_size = entry->allocation_size();
|
||||
file_info->end_of_file = entry->size();
|
||||
file_info->attributes = entry->attributes();
|
||||
return X_STATUS_SUCCESS;
|
||||
}
|
||||
return X_STATUS_NO_SUCH_FILE;
|
||||
```
|
||||
|
||||
### Resolution mechanism
|
||||
|
||||
`VirtualFileSystem::ResolvePath`
|
||||
(`xenia-canary/src/xenia/vfs/virtual_file_system.cc:129-158`) walks
|
||||
the in-memory `Entry` tree. It does NOT re-stat the host FS. The
|
||||
tree is populated:
|
||||
|
||||
* **Eagerly at mount time** by
|
||||
`HostPathDevice::Initialize` →
|
||||
`HostPathDevice::PopulateEntry`
|
||||
(`xenia-canary/src/xenia/vfs/devices/host_path_device.cc:31-75`)
|
||||
which recursively walks the host directory and builds
|
||||
`HostPathEntry` instances via `HostPathEntry::Create`.
|
||||
* **Per-create** by `Entry::CreateEntry` /
|
||||
`HostPathEntry::CreateEntryInternal`
|
||||
(`xenia-canary/src/xenia/vfs/entry.cc:88` /
|
||||
`host_path_entry.cc:78-98`), called from
|
||||
`VirtualFileSystem::OpenFile` → `CreatePath`
|
||||
(`virtual_file_system.cc:160-187`) on successful NtCreateFile create.
|
||||
|
||||
### Struct layout
|
||||
|
||||
`X_FILE_NETWORK_OPEN_INFORMATION` in
|
||||
`xenia-canary/src/xenia/kernel/info/file.h:117-127`:
|
||||
|
||||
```cpp
|
||||
struct X_FILE_NETWORK_OPEN_INFORMATION { // 56 bytes
|
||||
be<uint64_t> creation_time;
|
||||
be<uint64_t> last_access_time;
|
||||
be<uint64_t> last_write_time;
|
||||
be<uint64_t> change_time;
|
||||
be<uint64_t> allocation_size;
|
||||
be<uint64_t> end_of_file;
|
||||
be<uint32_t> attributes;
|
||||
be<uint32_t> pad;
|
||||
};
|
||||
```
|
||||
|
||||
All multibyte fields are `be<>`, i.e. byte-swapped on write to guest
|
||||
memory (Xbox 360 is big-endian).
|
||||
|
||||
### Attribute / size derivation
|
||||
|
||||
`HostPathEntry::Create`
|
||||
(`xenia-canary/src/xenia/vfs/devices/host_path_entry.cc:32-54`):
|
||||
|
||||
* `attributes_ = kFileAttributeDirectory (0x10)` if host is a dir.
|
||||
* `attributes_ = kFileAttributeNormal (0x80)` for regular files.
|
||||
`kFileAttributeReadOnly (0x01)` is OR'd in iff
|
||||
`device->is_read_only()`. `cache:` mounts are read-WRITE
|
||||
(`xenia_main.cc:641-651`), so the bit is NOT set.
|
||||
* `size_ = file_info.total_size`.
|
||||
* `allocation_size_ = round_up(total_size, device->bytes_per_sector())`.
|
||||
The base `Device` ctor defaults `bytes_per_sector_ = 512`
|
||||
(`xenia-canary/src/xenia/vfs/device.h`); host-path devices don't
|
||||
override.
|
||||
|
||||
### Timestamp format
|
||||
|
||||
Windows FILETIME — 100-ns ticks since 1601-01-01 UTC. On the Windows
|
||||
debug build, canary derives these via
|
||||
`COMBINE_TIME(WIN32_FILE_ATTRIBUTE_DATA::ftCreationTime)`
|
||||
(`filesystem_win.cc:226-228`), which is already FILETIME-format.
|
||||
|
||||
### Why ours diverged at idx 102404
|
||||
|
||||
ours's `nt_query_full_attributes_file` previously consulted
|
||||
`std::fs::metadata` directly. On TRUE cold-vs-cold (both engines'
|
||||
cache wiped), the host file `cache/d4ea4615/e/46ee8ca` doesn't
|
||||
exist → ours returned `STATUS_NO_SUCH_FILE`. So why does canary
|
||||
return SUCCESS?
|
||||
|
||||
The original Phase C+11.1 protocol wiped `~/.local/share/Xenia/cache`
|
||||
but the Windows-debug canary running under wine actually stores its
|
||||
cache under
|
||||
`xenia-canary/build-cross/bin/Windows/Debug/cache/` (canary's
|
||||
storage root resolves to `GetUserFolder()` when no `portable.txt`
|
||||
is present, but the build-cross binary writes there anyway — verified
|
||||
by `Storage root: Z:\home\fabi\RE - Project Sylpheed\xenia-canary\
|
||||
build-cross\bin\Windows\Debug` in the canary stdout). So the
|
||||
"cold-vs-cold" baseline at C+11.1 was actually
|
||||
ours-cold-vs-canary-warm: canary's binary-dir cache survived the
|
||||
wipe and the pre-existing `46ee8ca` file (May 12 timestamp) made
|
||||
canary's mount-time scan populate the in-memory tree.
|
||||
|
||||
A TRUE cold-vs-cold (Phase C+12 protocol, see `re-validation.md`)
|
||||
that ALSO wipes the binary-dir cache shows canary ALSO returns
|
||||
NO_SUCH_FILE for `cache:\d4ea4615\e\46ee8ca` at idx 102404 — and
|
||||
the divergence at 102404 dissolves.
|
||||
|
||||
But the underlying gap remains: canary maintains an in-memory entry
|
||||
tree that ours did not, and any boot where canary has a populated
|
||||
host cache (Sylpheed's normal warm/persistent operation) would see
|
||||
canary succeed where ours misses. Fixing this is in scope per the
|
||||
user directive.
|
||||
|
||||
## Architecture decision
|
||||
|
||||
Architecture **B** (in-memory entry tree mirroring canary):
|
||||
|
||||
* Add `cache_entries: HashMap<String, CacheEntryMeta>` to `KernelState`.
|
||||
* Eager mount-time scan via `populate_cache_entries_from_host`
|
||||
(mirrors canary's `HostPathDevice::PopulateEntry`).
|
||||
* Per-create registration via `register_cache_entry` (mirrors canary's
|
||||
`Entry::CreateEntry`).
|
||||
* `nt_query_full_attributes_file` consults the tree first;
|
||||
defensive host-FS fallback only fires when the tree missed but
|
||||
the file is on disk (refreshes tree as a side effect).
|
||||
|
||||
Architecture A alone (just stat host FS) would technically work for
|
||||
the specific 102404 case after the corrected cold-vs-cold protocol,
|
||||
but would diverge from canary on any sequence where the in-memory
|
||||
tree's freshness matters (e.g. an entry created earlier this same
|
||||
boot whose host write hasn't flushed yet — rare but possible if a
|
||||
future game pre-allocates entries before the corresponding write).
|
||||
Architecture B keeps semantic parity with canary regardless of the
|
||||
host-FS flush timing.
|
||||
|
||||
## Cold-vs-cold protocol correction
|
||||
|
||||
The C+11.1 protocol wiped `~/.local/share/Xenia/cache`. The Phase
|
||||
C+12 protocol additionally wipes
|
||||
`xenia-canary/build-cross/bin/Windows/Debug/cache/`
|
||||
(plus `cache0/`, `cache1/`) because that's where the Windows-debug
|
||||
canary actually writes its cache under wine. Backup is preserved at
|
||||
`/tmp/canary-binary-cache-backup.tar.gz` (4.7 MB, 23 files) and a
|
||||
copy lives in this audit-run dir as
|
||||
`canary-binary-cache-pre-wipe.tar.gz`.
|
||||
|
||||
## File:line citations
|
||||
|
||||
| canary source | line(s) | what |
|
||||
|---|---|---|
|
||||
| `xboxkrnl_io.cc` | 474-513 | `NtQueryFullAttributesFile_entry` body |
|
||||
| `xboxkrnl_io.cc` | 504 | `change_time = write_timestamp()` |
|
||||
| `xboxkrnl_io.cc` | 512 | `return X_STATUS_NO_SUCH_FILE` |
|
||||
| `info/file.h` | 117-127 | `X_FILE_NETWORK_OPEN_INFORMATION` layout |
|
||||
| `vfs/entry.h` | 67-95 | `kFileAttribute*` consts + accessors |
|
||||
| `vfs/entry.cc` | 88-104 | `Entry::CreateEntry` |
|
||||
| `vfs/virtual_file_system.cc` | 129-158 | `ResolvePath` |
|
||||
| `vfs/devices/host_path_device.cc` | 31-75 | `Initialize` + `PopulateEntry` |
|
||||
| `vfs/devices/host_path_entry.cc` | 32-54 | `HostPathEntry::Create` (attrs + size) |
|
||||
| `base/filesystem_win.cc` | 208-230 | `GetInfo` (FILETIME timestamps) |
|
||||
| `app/xenia_main.cc` | 641-651 | `cache:` mount registration |
|
||||
Reference in New Issue
Block a user