This repository has been archived on 2026-09-16. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Syplheed-Reborn/docs/re/title-crash-stl-tree.md
Sylpheed RE agent ab8a0d9e6d docs/re: the title-screen crash is an STL map/set erase, 40 s from a cold boot
The crash PC from the previous iteration resolves, and it names itself:
sub_823070B0 references the string 'invalid map/set<T> iterator', builds it with
the string helpers and throws it — which is the guest C++ exception (E06D7363)
Xenia reports one line earlier. Its node offsets are MSVC's red-black tree node
exactly (_Left 0, _Parent 4, _Right 8, _Color 24, _Isnil 25), so this is a
std::map/set erase, and the fault is the first dereference after the iterator
check.

That matters beyond this blocker. The canary handoff's Ready-Room crash is the
same shape — a guest STL exception in a cache/save path — and its bisection plan
is priced at "one build plus one Ready-Room run" per suspect. If it is the same
defect, each suspect now costs 40 seconds, and suspect #1 is already measured:
--mem_watch=false removes it.

Stated as unresolved rather than guessed: the fault address 0x1_0000000C is a
32-bit value with bit 32 set, which fits BOTH a corrupt guest tree and a stale
high word on the emulator side. The measurement that separates them (read the
node from guest memory at the throw) is written down rather than assumed.

The second, unreproduced crash PC is identified too: an unrolled 4x16-bit copy
loop faulting on the STORE, i.e. a bad destination — a different failure.
2026-08-18 21:05:39 +00:00

4.5 KiB
Raw Blame History

The title-screen crash is an STL map/set erase on a bad iterator

Status: CONFIRMED for the identification (the guest function is std::_Tree::erase-shaped, and it says so in its own diagnostic string). 🟡 PROBABLE that this is the same defect as the Ready-Room crash the Canary handoff blames on the mem_watch probe. whether the bad pointer originates in the guest or in the emulator's 64-bit register handling.

Found while trying to get past the title screen for a second UI screen's paint order (canary-scripted-input-traps.md). It is worth a page of its own because it is a 40-second reproduction of a crash the project has so far only seen after a whole mission.

The reproduction

Boot Canary on the disc with the default --mem_watch=true and a profile on disc. Roughly 40 s in — while the title screen is up — the guest dies:

Access Violation: read at 0x000000010000000C
PC: 0x82307128   guest thread 9
… preceded by  HostPathDevice::ResolvePath(\aab216c3\5\c10eae6)
   and         RtlRaiseException(702DF7F0(E06D7363), ContextArg)
               "Guest attempted to throw a C++ exception!"

Two runs, same PC, same fault address. With --mem_watch=false it does not happen at all. Xenia pauses itself and stacks crash dialogs — 991 in one run.

What the code is

0x82307128 is inside sub_823070B0 (0x823070B0..0x823074D0, has EH), and the function identifies itself: it references the string 'invalid map/set<T> iterator' at 0x82062A8C, builds it with the string helpers at 0x8216E7E8 / 0x8216E5C8, and throws it through 0x825F23D8.

The node layout in the prologue is MSVC's std::_Tree_node exactly:

823070C8  lbz  r10, 25(r5)     ; iterator->_Ptr->_Isnil      (offset 25)
…                              ; if set -> build the string and THROW
82307124  bl   0x8244E2A8      ; (iterator helper)
82307128  lwz  r11, 0(r25)     ; node->_Left                 (offset 0)   <-- FAULT
8230713C  lwz  r27, 8(r25)     ; node->_Right                (offset 8)

_Left 0, _Parent 4, _Right 8, _Color 24, _Isnil 25 — that is the MSVC red-black tree node, so this is a std::map/std::set erase (it validates the iterator, then walks the node). The crash is the very first dereference after the validation.

What the fault address says

The effective address is r25 + 0, so r25 = 0x00000001_0000000C. That is a 32-bit value 0x0000000C with bit 32 set — a pointer whose high word is garbage, not a wild 32-bit pointer. Two readings, and this note does not choose between them:

  • the tree really is corrupt and the guest is erasing through a freed/racing node — which is what the crash-oracle handoff concluded for the Ready-Room crash (a concurrent cache-add landing inside an unlocked flush iteration), and it names the mem_watch polling thread as suspect #1 for perturbing that timing. The --mem_watch=false result here is consistent with that;
  • or a high-word extension bug on the emulator side leaves a stale 1 in the upper half of a 64-bit register. Canary is the reference emulator, so this is the less likely of the two, but 0x1_0000000C is exactly the shape such a bug produces and nothing here rules it out.

What would separate them: dump the tree's root and a few nodes from guest memory (tools/re-capture/gmem.py) at the moment of the throw, and check whether the parent node's _Left field on the guest heap really contains 0x0000000C — if the guest memory holds a clean 32-bit value and only the register is wrong, it is the emulator.

The other crash PC, for completeness

The one unreproduced crash after an Ⓐ press was at 0x824578A0, in sub_82457780 (0x82457780..0x82457958, one caller, sub_82457038). It is an unrolled 4 × 16-bit copy loop:

82457890  lhz  r6, 0(r10)      82457898  lhz  r4, 4(r10)
82457894  lhz  r5, 2(r10)      8245789C  lhz  r10, 6(r10)
824578A0  sth  r6, 0(r9)   <-- FAULT (a STORE, not a load)

so a bad destination, in what looks like a small block copy — a different failure from the tree erase above, and with one observation it stays at that.

Why this matters beyond the blocker

The Ready-Room crash costs a full mission to reproduce, which is why its bisection plan in the handoff is written as "one build + one Ready-Room run" per suspect. If this title-screen crash is the same defect, each suspect costs 40 seconds instead, and suspect #1 (--mem_watch=false) is already measured here: it removes the crash.