[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
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:
@@ -9,6 +9,14 @@
|
||||
|
||||
#include <ranges>
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <thread>
|
||||
#if defined(__linux__)
|
||||
#include <malloc.h>
|
||||
#endif
|
||||
|
||||
#include "xenia/kernel/kernel_state.h"
|
||||
|
||||
#include "xenia/base/byte_stream.h"
|
||||
@@ -41,9 +49,69 @@ DEFINE_uint32(kernel_build_version, 1888, "Define current kernel version",
|
||||
|
||||
DECLARE_string(cl);
|
||||
|
||||
DEFINE_bool(mem_watch, true,
|
||||
"Periodically log host RSS / malloc-vs-mmap / guest-physical / "
|
||||
"cache-deque size to localize the mission memory leak.", "Kernel");
|
||||
DEFINE_uint32(mem_watch_secs, 3, "MEM-WATCH sample interval (seconds).",
|
||||
"Kernel");
|
||||
|
||||
namespace xe {
|
||||
namespace kernel {
|
||||
|
||||
// Diagnostic: a detached sampler that periodically logs where host memory is
|
||||
// going, so a SHORT mission run localizes the leak (host malloc vs mmap vs
|
||||
// guest-physical vs the cache work-queue). Gated by --mem_watch.
|
||||
static std::atomic<bool> g_mem_watch_run{false};
|
||||
|
||||
static void MemWatchLoop(Memory* memory) {
|
||||
uint64_t peak_rss_kb = 0;
|
||||
while (g_mem_watch_run.load(std::memory_order_relaxed)) {
|
||||
// Host RSS / VmSize from /proc/self/statm (fields in pages).
|
||||
uint64_t rss_kb = 0, vsz_kb = 0;
|
||||
if (FILE* f = std::fopen("/proc/self/statm", "r")) {
|
||||
unsigned long vsz_pg = 0, rss_pg = 0;
|
||||
if (std::fscanf(f, "%lu %lu", &vsz_pg, &rss_pg) == 2) {
|
||||
rss_kb = static_cast<uint64_t>(rss_pg) * 4; // 4KB pages
|
||||
vsz_kb = static_cast<uint64_t>(vsz_pg) * 4;
|
||||
}
|
||||
std::fclose(f);
|
||||
}
|
||||
if (rss_kb > peak_rss_kb) peak_rss_kb = rss_kb;
|
||||
|
||||
// Host allocator breakdown: malloc-in-use vs mmap'd bytes.
|
||||
uint64_t malloc_kb = 0, mmap_kb = 0;
|
||||
#if defined(__linux__)
|
||||
struct mallinfo2 mi = mallinfo2();
|
||||
malloc_kb = static_cast<uint64_t>(mi.uordblks) / 1024;
|
||||
mmap_kb = static_cast<uint64_t>(mi.hblkhd) / 1024;
|
||||
#endif
|
||||
|
||||
// Cache work-queue (guest): deque count OBJ+0x58 / list count OBJ+0x38,
|
||||
// only once the singleton is initialized (once-flag 0x828F48B4 bit0).
|
||||
uint32_t deque = 0, listc = 0;
|
||||
auto rd = [&](uint32_t va) -> uint32_t {
|
||||
auto p = memory->TranslateVirtual<xe::be<uint32_t>*>(va);
|
||||
return p ? static_cast<uint32_t>(*p) : 0;
|
||||
};
|
||||
if (rd(0x828F48B4u) & 1u) {
|
||||
deque = rd(0x828F4890u);
|
||||
listc = rd(0x828F4870u);
|
||||
}
|
||||
|
||||
XELOGE(
|
||||
"MEM-WATCH rss={}MB (peak {}MB) vsz={}MB malloc_inuse={}MB mmap={}MB "
|
||||
"cache_deque={} cache_list={}",
|
||||
rss_kb / 1024, peak_rss_kb / 1024, vsz_kb / 1024, malloc_kb / 1024,
|
||||
mmap_kb / 1024, deque, listc);
|
||||
|
||||
for (uint32_t i = 0; i < cvars::mem_watch_secs * 10 &&
|
||||
g_mem_watch_run.load(std::memory_order_relaxed);
|
||||
++i) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constexpr std::chrono::milliseconds kDeferredOverlappedDelayMillis(25);
|
||||
|
||||
// This is a global object initialized with the XboxkrnlModule.
|
||||
@@ -78,9 +146,16 @@ KernelState::KernelState(Emulator* emulator)
|
||||
kMemoryProtectRead | kMemoryProtectWrite);
|
||||
|
||||
xenia_assert(fixed_alloc_worked);
|
||||
|
||||
if (cvars::mem_watch) {
|
||||
g_mem_watch_run.store(true, std::memory_order_relaxed);
|
||||
Memory* mem = memory_;
|
||||
std::thread([mem]() { MemWatchLoop(mem); }).detach();
|
||||
}
|
||||
}
|
||||
|
||||
KernelState::~KernelState() {
|
||||
g_mem_watch_run.store(false, std::memory_order_relaxed);
|
||||
SetExecutableModule(nullptr);
|
||||
|
||||
if (dispatch_thread_running_) {
|
||||
|
||||
Reference in New Issue
Block a user