[WIP] Audio/threading fixes + crash investigation; NEW ORACLE: crash is ours not the game
Some checks failed
Orchestrator / Commit Message Validation (push) Has been skipped
Orchestrator / Lint (push) Failing after 1m34s
Orchestrator / Windows (x86-64) (push) Has been skipped
Orchestrator / Linux (x86-64) (push) Has been skipped
Orchestrator / Create Release (push) Has been skipped

Snapshot for handoff. Contains the mission-audio + threading fixes and the
crash-investigation instrumentation (all diagnostic cvars default-OFF).

Fixes (behavioral):
- threading_posix.cc: reap-once guard on PosixCondition<Thread>::post_execution
  (double pthread_join at mission teardown -> fault loop -> audio death + freeze).
- xma_decoder.cc: work_event_->Set() in Pause() so the idle XMA worker observes
  paused_ and signals pause_fence_ (Pause() deadlock -> permanent audio death).
- audio_system / xma_context_master / xboxkrnl_audio / apu_flags / alsa: mission
  audio keepalive + guest_audio_flags + watchdogs.

Instrumentation (additive, default-off): xboxkrnl_debug cache-throw diag +
guest-catch dispatcher, xex_module PE/PDATA/EH scans, kernel_state mem_watch
(NOTE: mem_watch DEFAULTS TRUE -- an always-on host poll thread; prime crash suspect).

NEW ORACLE (see HANDOFF-crash-oracle-2026-07-16.md): stock 6e5b8324f built with
our toolchain + zero custom code = NO crash, NO sound-stop, plays the Ready Room.
=> the Ready-Room out_of_range crash is introduced by THESE changes, not the game
and not the (LTO-broken) build chain. Bisection plan + suspect ranking in the note.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-07-16 22:43:50 +02:00
parent 0aa3eadca7
commit 7b6902e08f
20 changed files with 1371 additions and 8 deletions

View File

@@ -1568,18 +1568,101 @@ std::vector<uint32_t> XexModule::PreanalyzeCode() {
}
}
for (auto&& sec : pe_sections_) {
XELOGE("PESEC name={} addr={:08X} size={}", sec.name, sec.address,
sec.size);
}
// EH-CONFIRM: scan .rdata for MSVC C++ FuncInfo (magic 0x19930520) and, for
// any whose IP2State map points into the cache-flush chain, dump its catch
// TypeDescriptor names -- this tells us if the flush's out_of_range is
// CAUGHT (vs only cleaned up). Layout (Xenon, 32-bit full VAs, big-endian):
// FuncInfo: +0 magic, +4 maxState, +8 pUnwindMap, +12 nTryBlocks,
// +16 pTryBlockMap, +20 nIPMap, +24 pIP2StateMap
// TryBlockMapEntry(20B): +0 tryLow,+4 tryHigh,+8 catchHigh,+12 nCatches,
// +16 pHandlerArray
// HandlerType(16B): +0 adjectives,+4 pType(TypeDescriptor),+8 dispCatch,
// +12 addressOfHandler
// TypeDescriptor: +0 vfptr,+4 spare,+8 name(mangled, e.g. ".?AV...@")
// IP2StateMapEntry(8B): +0 Ip(VA), +4 State
{
auto rd = [&](uint32_t va) -> uint32_t {
auto p = memory()->TranslateVirtual<xe::be<uint32_t>*>(va);
return p ? (uint32_t)*p : 0;
};
auto valid = [](uint32_t va) { return va >= 0x82000000 && va < 0x82920000; };
auto rdata = GetPESection(".rdata");
if (rdata) {
uint32_t magic_hits = 0, raw_logged = 0;
for (uint32_t a = rdata->address; a < rdata->address + rdata->size;
a += 4) {
if (rd(a) != 0x19930522u) continue; // FuncInfo magic (newer MSVC)
uint32_t nTry = rd(a + 12), pTry = rd(a + 16);
uint32_t nIP = rd(a + 20), pIP = rd(a + 24);
++magic_hits;
if (!valid(pIP) || nIP > 4096 || !nIP) continue;
// which function? use first + last IP in the IP2State map.
uint32_t firstIp = rd(pIP), lastIp = rd(pIP + (nIP - 1) * 8);
if (raw_logged < 6) {
XELOGE("EH-RAW FuncInfo@{:08X} nTry={} ip[{:08X}..{:08X}]", a, nTry,
firstIp, lastIp);
++raw_logged;
}
// Log EVERY function that has a CATCH (nTry>=1) across the whole
// binary, so any stack frame can be mapped to a catch. (cleanup-only
// nTry=0 frames just re-propagate -- skip them to cut noise.)
if (!nTry || !valid(pTry) || nTry > 64) continue;
std::string cat;
for (uint32_t t = 0; t < nTry; ++t) {
uint32_t te = pTry + t * 20;
uint32_t nCatch = rd(te + 12), pH = rd(te + 16);
for (uint32_t c = 0; c < nCatch && c < 32 && valid(pH); ++c) {
uint32_t pType = rd(pH + c * 16 + 4);
uint32_t dispCatchObj = rd(pH + c * 16 + 8);
uint32_t funclet = rd(pH + c * 16 + 12);
std::string nm;
if (!pType) {
nm = "...";
} else {
auto s = memory()->TranslateVirtual<const char*>(pType + 8);
for (int i = 0; s && i < 96 && s[i]; ++i) nm.push_back(s[i]);
}
cat += fmt::format(" catch({} obj@fp+{:X} funclet={:08X})", nm,
dispCatchObj, funclet);
}
}
XELOGE("EH-CONFIRM FuncInfo@{:08X} ip[{:08X}..{:08X}] nTry={}{}", a,
firstIp, lastIp, nTry, cat);
}
XELOGE("EH-CONFIRM scan done; magic_hits={}", magic_hits);
}
}
auto pdata = this->GetPESection(".pdata");
XELOGE("PDATA-SCAN reached; pdata_section={}",
pdata ? "FOUND" : "NULL");
if (pdata) {
uint32_t* pdata_base =
(uint32_t*)this->memory()->TranslateVirtual(pdata->address);
uint32_t n_pdata_entries = pdata->raw_size / 8;
XELOGE("PDATA-SCAN addr={:08X} raw_size={} entries={}", pdata->address,
pdata->raw_size, n_pdata_entries);
for (uint32_t i = 0; i < n_pdata_entries; ++i) {
uint32_t funcaddr = xe::load_and_swap<uint32_t>(&pdata_base[i * 2]);
if (funcaddr >= low_address_ && funcaddr <= highest_exec_addr) {
add_new_func(funcaddr);
// DIAGNOSTIC: dump the unwind Flags word for the cache-flush region
// (Xbox360 PPC RUNTIME_FUNCTION: BeginAddress, then Flags with
// PrologLen:8, FuncLen:22, 32bit:1, ExceptionFlag:1[bit31]). The
// ExceptionFlag tells us whether a function has a language handler
// (C++ catch/cleanup) -- i.e. whether the game can CATCH the flush's
// out_of_range throw. Remove once the A-vs-B fix decision is settled.
if (funcaddr >= 0x82459000u && funcaddr <= 0x8245B200u) {
uint32_t flags = xe::load_and_swap<uint32_t>(&pdata_base[i * 2 + 1]);
XELOGE("PDATA fn={:08X} flags={:08X} funclen={} excflag={}",
funcaddr, flags, (flags >> 2) & 0x3FFFFF, (flags & 1));
}
} else {
// we hit 0 for func addr, that means we're done
break;