Repository navigation
Description
What happens. When the process exits, the guest's FMOD mixer thread keeps calling sceAudioOut2ContextPush while exit() is already destroying static objects. The push then mixes from freed memory. In one run it crashed with SIGSEGV at exit, and the faulting pointer had been read from freed heap memory.
Measured. Hades II (hub scene of #5485), Linux, RTX 3080 Ti, NVIDIA 610.57.04, commit 1e9e3ca68 (local branch on main from 2026-10-13; libSceAudioOut and the exit path in libc/src/Process.cpp differ from current main only in the pad haptics and nested-exit changes). Game in the hub for about 3 minutes, then SIGTERM, which goes through LibcRequestExit_nid_postfix → LibcExit_nid_no_patch → std::exit.
With MALLOC_PERTURB_=165 (freed memory filled with 0xa5), the core shows:
Thread 1 (LWP 2640381), SIGSEGV
#0 libc.so.6 memcpy vmovdqu (%rsi),%ymm0 rsi=0xa5a5a5a5a5a5a5a5 rdx=0x20
#1 AudioOut2MixPorts(...) (libSceAudioOut.prx +0x307ce)
#2 Render(AudioOut2Context&, ...) (libSceAudioOut.prx +0x2aff9)
#3 sceAudioOut2ContextPush (libSceAudioOut.prx +0x2d027)
#4-6 libfmod.prx.guest.prx
#7 StartNativeThread (libkernel.prx)
Thread 19 (LWP 2652484), at the same moment
#0-2 std::map<unsigned long, PhysicalPage>::~map (libkernel static destructor)
#3-4 exit()
#5 LibcExit_nid_no_patch (libc.prx)
#6 LibcRequestExit_nid_postfix lambda (libc.prx)
The source pointer is the 0xa5 fill pattern, so it was read from freed memory while thread 19 was running the exit handlers. Two other runs that exited the same way, with libc_malloc_debug.so and glibc.malloc.check=3, did not crash.
Why (reading main bb7e47a88; the second point is my inference, not confirmed in the core):
LibcExit_nid_no_patchruns the registered shutdown callbacks, thenCxaFinalizeandstd::exit. Only the AGC driver and the VideoOut driver register a callback (LibcRegisterShutdown_nid_postfix);libSceAudioOutdoes not, so nothing stops the guest's audio thread before static destructors run.AudioOut2MixPortsandAudioOut2CaptureGrainread the staticg_portsvector andg_portsLock(AudioOut2Port.cpp:36-38). Once those are destroyed, a push that is still running reads freed memory.
We have also seen double free or corruption (out) at exit after SIGTERM in this title (#5485), with and without APS5_MT_RECORD. Those aborts left no core with a stack, so I can't say they come from this, but an exit that frees memory still used by running threads would explain them.
Proposal. Register a libSceAudioOut shutdown callback that closes the SDL devices and makes sceAudioOut2ContextPush (and the AudioOut1 output path, if it has the same shape) return without touching the port tables once shutdown has started, so no audio thread is inside the mixer when static destructors run. A test in the style of core/libs/tests/GuestSystemExit.cpp could keep a thread pushing while exit runs.
I can send a PR for this unless someone already has the audio shutdown in progress.
Activity
isnisk commentedon Oct 14, 2026
I'd like to work on this unless you've already started. My plan follows your proposal:
libSceAudioOutregisters a shutdown callback withLibcRegisterShutdown_nid_postfix, like the VideoOut and AGC drivers do. The callback sets a shutdown flag and closes the SDL devices.sceAudioOut2ContextPushand the AudioOut1 output path return without touchingg_ports/g_portsLock. The callback waits for any push already inside the mixer to leave, so no audio thread is mixing when the static destructors run.core/libs/tests/GuestSystemExit.cppkeeps one thread pushing while another thread calls exit.If you already have a branch for this, say so and I'll leave it to you.
AI-assisted: yes (Claude Code).
eliyarson commentedon Oct 14, 2026
@isnisk Go ahead, I have not started. One pointer: the AGC entry points park guest threads at shutdown with
LibcAwaitExit_nid_postfixinstead of returning an error the game might retry in a loop (5550ca63d); the audio calls could do the same. I can test your branch on Hades II, where it crashed.isnisk commentedon Oct 14, 2026
@eliyarson the fix is in #6000 (branch
isnisk:fix/audio-out-shutdown, one commit onmain73efd9407):It follows your pointer: the AudioOut2 port, push and advance calls and AudioOut1 output park with
LibcAwaitExit_nid_postfixonce exit is requested, and a new shutdown callback waits until no audio call is still inside before the static destructors run. A push that waits for an advance also wakes at exit, so it can't hold the exit up.A run of the Hades II hub, then SIGTERM, with
MALLOC_PERTURB_=165as in your report would show whether the crash at exit is gone. It would also be useful to know whether thedouble free or corruption (out)from #5485 still appears. #5971 (JJenkx) covers the same crash from the other side, by keeping the port table alive; the two don't depend on each other.AI-assisted: yes (Claude Code).
hugodearaujo commentedon Oct 14, 2026
Another title with this: ASTRO BOT (PPSA21567, my own dump) aborts at exit on Linux in the same place. After SIGTERM at the title screen, one run printed
malloc(): unsorted double linked list corruptedand aborted. Under gdb, the aborting thread was the guest's audio thread inside the push:Of 13 exits after SIGTERM on
mainf0854887dwith #5905 (RX 9070 XT, Linux), 3 aborted (the stack above is from the one under gdb), 2 ended in SIGSEGV (no stack captured), 1 hung until it was killed and 7 exited cleanly. @isnisk, I can run your branch on ASTRO BOT when it is up.AI-assisted: written with Claude Code; I reviewed the stack and the runs.
eliyarson commentedon Oct 14, 2026
Ran #6000 (e7219e4) against its base (73efd94) on Hades II: hub, 60 s hold, SIGTERM,
MALLOC_PERTURB_=165, interleaved. Linux (CachyOS), Ryzen 9 5950X, RTX 3080 Ti; only the.prxfiles differ between the two game dirs.SIGTERM to process exit took 0.5 to 1.3 s in every clean run on both builds, so the shutdown wait adds no visible delay. Four more runs per build with glibc's heap checks (
libc_malloc_debug.so,glibc.malloc.check=3) all exited cleanly. Thedouble free or corruption (out)from #5485 didn't appear in any run on either build.The base crash was a SIGSEGV 29.8 s after SIGTERM, on a guest FMOD thread in
sceAudioOut2PortSetAttributes, one of the exports #6000 now guards:The crash is rare in this scene (1 in 12 here), so 10 clean runs on #6000 don't prove much on their own: with no fix at all, 10 clean runs in a row would happen about 4 times in 10. What the runs do show is that #6000 doesn't hang or slow down exit, and that the crash I hit on base is in the path it covers. If you have a way to make it deterministic (SIGTERM while FMOD is changing port attributes, say), I'm happy to run that.
LLM assistance used.
fix(libSceAudioOut): park guest audio calls at exit before the port t…
Upstream sync 11C: small-libs (50 commits) (#510)
hugodearaujo commentedon Oct 15, 2026
Correction to my count above: of the 13 exits, 3 ended in SIGSEGV, not 2, and none hung (3 aborted, 3 SIGSEGV, 7 clean). The run I counted as hung was killed by
timeout(exit 137) while its SIGSEGV core dump was still being written; systemd-coredump recorded the SIGSEGV. That run and one other also hadMALLOC_PERTURB_=165, which I left out. The abort stack is unchanged.