You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Linux: PPSA28997 (Sons of Sparta) aborts at start-up with "RTLD application heap: cannot replace an active allocator" (#693 regressed after #1515) #1930
Opening this as a tracking issue because #693 is closed while its fix (#1156) was reverted by #1515. Full details are in my comment there: #693 (comment)
Title: PPSA28997 (God of War: Sons of Sparta), native Linux output. The title bundles sce_module/libc.prx.
Current main (f1451396) + #1482 (_umtx_op) + a local workaround for #918 aborts at start-up, right after opening /app0/Media/boot.config:
[libc] unhandled exception 'St13runtime_error' thrown from 0x7cac0c9be567
terminate called after throwing an instance of 'std::runtime_error'
what(): RTLD application heap: cannot replace an active allocator
This is the guard in registerApplicationHeapApi (core/libs/prx/libkernel/System/src/Rtld.cpp): sceKernelRtldSetApplicationHeapAPI runs a second time with a different allocator table, so both libc instances perform the application-heap handshake.
Older build without that guard (df16c4c): the title starts, then later glibc aborts with corrupted size vs. prev_size.
What the title needs on Linux, none of it in main yet
Test Environment (after reapplying #1156 on origin/main)
Build: AnyPS5 commit d70b8998 + revert #2096c66a + port #1482 (_umtx_op) + RecordWrittenPath stub + sceKernelRaiseException implementation + SN function stubs.
Result: The game PPSA28997 (God of War: Sons of Sparta) now boots past the initial "RTLD application heap: cannot replace an active allocator" error and reaches the Vulkan device initialization, presentation window creation (1920x1080), and shader cache loading phase.
Error after correction: After the boot sequence completes, the process exhibits the same intermittent heap corruption previously observed: corrupted size vs. prev_size at varying workload counts (typically ~92 workloads, range 60-130 depending on timing). This is a timing-dependent race condition, not an immediate crash. The ASan-run21 previously captured: SEGV on unknown address 0x004000003202 (WRITE, thread T3).
Status: Game loads and runs, but heap corruption persists as a timing-dependent issue. All 3 original missing NIDs + 1 new are now handled:
_umtx_op: implemented with FreeBSD futex semantics (mutex ops 2/3/4, wake 15/16, wait/wake 0/1/9/13/14/19; rest → ENOSYS)
sceNpUnregisterNpReachabilityStateCallback: stubbed (returns 0, logged in TechnicalDebt)
sceNpEntitlementAccessGetEntitlementKey: stubbed (returns 0, logged in TechnicalDebt)
sceKernelRaiseException: implemented via pthread_kill + SA_SIGINFO trampoline
RecordWrittenPath_nid_no_patch: stub added (previously caused symbol lookup error)
Next: Capture ASan backtrace when corruption appears, or test with -job-worker-count 1 to reduce variability.
Branch: test/1930-reapply-1156 based on origin/main (d70b8998).
Test Environment (after reapplying #1156 on origin/main)
Build: AnyPS5 commit d70b8998 + revert #2096c66a + port #1482 (_umtx_op) + RecordWrittenPath stub + sceKernelRaiseException implementation + SN function stubs.
Result: The game PPSA28997 (God of War: Sons of Sparta) now boots past the initial "RTLD application heap: cannot replace an active allocator" error and reaches the Vulkan device initialization, presentation window creation (1920x1080), and shader cache loading phase.
Error after correction: After the boot sequence completes, the process exhibits the same intermittent heap corruption previously observed: corrupted size vs. prev_size at varying workload counts (typically ~92 workloads, range 60-130 depending on timing). This is a timing-dependent race condition, not an immediate crash. The ASan-run21 previously captured: SEGV on unknown address 0x004000003202 (WRITE, thread T3).
Status: Game loads and runs, but heap corruption persists as a timing-dependent issue. All 3 original missing NIDs + 1 new are now handled:
_umtx_op: implemented with FreeBSD futex semantics (mutex ops 2/3/4, wake 15/16, wait/wake 0/1/9/13/14/19; rest → ENOSYS)
sceNpUnregisterNpReachabilityStateCallback: stubbed (returns 0, logged in TechnicalDebt)
sceNpEntitlementAccessGetEntitlementKey: stubbed (returns 0, logged in TechnicalDebt)
sceKernelRaiseException: implemented via pthread_kill + SA_SIGINFO trampoline
RecordWrittenPath_nid_no_patch: stub added (previously caused symbol lookup error)
Next: Capture ASan backtrace when corruption appears, or test with -job-worker-count 1 to reduce variability.
Branch: test/1930-reapply-1156 based on origin/main (d70b8998).
#1102 (module parameter block) is merged, and PPSA28997 is reported booting on main 942a174 with #1482 and #1431 (#1482 (comment)). Does it still stop at RTLD application heap: cannot replace an active allocator for you on current main plus #1482?
Tested exactly that: clean origin/main (a11c8f7f7, no revert), which already contains #1482, #1431 and #1102. Rebuilt libkernel.prx / libSceNpManager.prx / libSceNpEntitlementAccess.prx and ran PPSA28997 (Linux, RADV Barcelo).
The allocator error is gone: zero occurrences of RTLD application heap: cannot replace an active allocator in the log.
Boot now stops earlier at a different missing NID:
./app.elf: symbol lookup error: .../libs/libkernel.prx: undefined symbol: RecordWrittenPath_nid_no_patch
EXIT: 127, immediate, before any Vulkan init. So on current main the double-handshake no longer trips the guard, but the title needs RecordWrittenPath exported from libkernel, which main does not provide yet. Happy to add it as NotImplemented_nid_no_patch per the contributing rules and re-test if that helps.
I checked this symbol against current upstream/main (a11c8f7f7): it is defined in core/libs/prx/libc/src/General.cpp, declared in libc/include/General.hpp, and libkernel links to the libc target in its CMake file. The reported verification rebuilt libkernel.prx, libSceNpManager.prx, and libSceNpEntitlementAccess.prx, but doesn't mention rebuilding or replacing the matching libc.prx.
Could you try rebuilding/deploying libc.prx from the same checkout too, then rerun? If a clean set of PRXs still leaves RecordWrittenPath_nid_no_patch unresolved, the link/load logs from that build should help isolate why the existing definition isn't being resolved. I don't see a source-level missing-export fix to submit yet.
You were right: after rebuilding and deploying libc.prx from the same checkout (a11c8f7f7), RecordWrittenPath_nid_no_patch resolves — zero symbol errors in the log.
Boot now proceeds further (threads spawn, /app0/Media/RuntimeInitializeOnLoads.json opens) and then aborts:
[libc] unhandled exception St13runtime_error thrown from 0x7fefea9ff48b
terminate called after throwing an instance of std::runtime_error
what(): APR: truncated command buffer
EXIT: 134, only 74 log lines total — it dies before any Vulkan init, inside Apr::_execute (core/libs/prx/libkernel/Apr/src/Apr.cpp:530): the cursor walk hits a cut-off CommandHeader (cursor + sizeof(header) > buffer.offset) on the first asset-read batch after RuntimeInitializeOnLoads.json.
So current status on clean main: no allocator error, no missing NIDs — the title now dies in APR command-buffer execution during early file reads. Let me know if a trace of the APR opcodes/buffer sizes leading up to it would help, I can instrument _execute locally.
The new APR failure is at the parser boundary in Apr.cpp::_execute: it means fewer than sizeof(CommandHeader) bytes remain at cursor, before an opcode can be decoded. Could you capture the following at that throw on an unchanged a11c8f7f7 build?
buffer.offset, cursor, and buffer.offset - cursor;
buffer.base and the first up to 16 bytes from buffer.base + cursor (hex);
the last successfully parsed command offset/opcode/header.bytes and the buffer object address;
whether the same command buffer is submitted from any other thread or modified after submission.
Please keep the guard in place and log immediately before throwing. That will distinguish a genuinely truncated guest command stream from a bad/stale offset or concurrent buffer mutation; without it, relaxing the parser check risks reading beyond the captured commands.
Separate finding from my parser inspection: APR records with a valid header could declare fewer bytes than the opcode-specific struct, while _execute still copied the full struct; the old cursor + header.bytes bound could also overflow. I opened #3808 with size checks and malformed-record regressions. This addresses malformed command records only and does not diagnose or claim to fix your reported buffer ending before a complete header; the offset/cursor/raw-tail capture requested above is still needed for that.
buffer.base = 0x1000100000001. No tail bytes to show: dereferencing base to dump them faults (first attempt died with SIGSEGV instead of the throw), so the base is not host-readable.
Last parsed command: none (has=0) — it fails on the very first header. Buffer object at 0x2101a0580; size = 40, numCommands = 1048576 (0x100000).
Threading: single tid observed at the throw; values are bit-identical across runs (same obj, same fields), which argues against concurrent mutation — a racy writer would be unlikely to reproduce the exact same garbage twice.
Read: this does not look like a partially-filled valid stream. base is odd (...001) and unmapped, numCommands is 0x100000, and nothing was consumed — the CommandBufferObject itself looks stale or misread (bad guest address or layout skew) rather than truncated mid-stream. The submitter runs right after /app0/Media/RuntimeInitializeOnLoads.json is opened, before any graphics init.
Happy to dump the raw 0x18 object bytes at obj or trace the submitter (_execute caller at Apr.cpp:732 and the bytes-based path at :662) next.
The allocator error is gone on main, so I'm closing this.
The APR: truncated command buffer stop is a mixed build. Read with the object layout from before #2789, your dump is a healthy buffer: 1 MiB at 0x2..., offset 40, one command. So libSceAmpr.prx is older than libkernel.prx; #2789 changed that layout in both. Rebuild and deploy all of libs/ from one checkout, and open a new issue if PPSA28997 still stops after that.
Confirmed — it was the mixed build. Rebuilt all 122 patched .prx files from the single a11c8f7f7 checkout and deployed the full set (backup of the old mixed libs/ kept aside).
Result on PPSA28997 (Linux, RADV Barcelo): no stop at all. Zero APR: truncated, zero allocator/symbol errors. Boot proceeds through Vulkan device init, 1920x1080 presentation window, swapchain (1109x624), shader draws — and the process was still alive when timeout 300 killed it (EXIT: 124). The trailing ProcessShutdown throw is just the shutdown path reacting to SIGTERM.
So: clean main + full single-checkout libs/ runs the title past every previously reported stop. No new issue needed from my side. Thanks for the layout pointer — that obj dump decoding as a healthy buffer under the pre-#2789 layout was the giveaway.
Activity
igordev23 commentedon Oct 9, 2026
Test Environment (after reapplying #1156 on origin/main)
Build:
AnyPS5commitd70b8998+ revert#2096c66a+ port#1482(_umtx_op) +RecordWrittenPathstub +sceKernelRaiseExceptionimplementation + SN function stubs.Result: The game
PPSA28997(God of War: Sons of Sparta) now boots past the initial "RTLD application heap: cannot replace an active allocator" error and reaches the Vulkan device initialization, presentation window creation (1920x1080), and shader cache loading phase.Error after correction: After the boot sequence completes, the process exhibits the same intermittent heap corruption previously observed:
corrupted size vs. prev_sizeat varying workload counts (typically ~92 workloads, range 60-130 depending on timing). This is a timing-dependent race condition, not an immediate crash. The ASan-run21 previously captured:SEGV on unknown address 0x004000003202(WRITE, thread T3).Status: Game loads and runs, but heap corruption persists as a timing-dependent issue. All 3 original missing NIDs + 1 new are now handled:
_umtx_op: implemented with FreeBSD futex semantics (mutex ops 2/3/4, wake 15/16, wait/wake 0/1/9/13/14/19; rest → ENOSYS)sceNpUnregisterNpReachabilityStateCallback: stubbed (returns 0, logged in TechnicalDebt)sceNpEntitlementAccessGetEntitlementKey: stubbed (returns 0, logged in TechnicalDebt)sceKernelRaiseException: implemented viapthread_kill+SA_SIGINFOtrampolineRecordWrittenPath_nid_no_patch: stub added (previously causedsymbol lookup error)Next: Capture ASan backtrace when corruption appears, or test with
-job-worker-count 1to reduce variability.Branch:
test/1930-reapply-1156based onorigin/main(d70b8998).igordev23 commentedon Oct 9, 2026
Test Environment (after reapplying #1156 on origin/main)
Build:
AnyPS5commitd70b8998+ revert#2096c66a+ port#1482(_umtx_op) +RecordWrittenPathstub +sceKernelRaiseExceptionimplementation + SN function stubs.Result: The game
PPSA28997(God of War: Sons of Sparta) now boots past the initial "RTLD application heap: cannot replace an active allocator" error and reaches the Vulkan device initialization, presentation window creation (1920x1080), and shader cache loading phase.Error after correction: After the boot sequence completes, the process exhibits the same intermittent heap corruption previously observed:
corrupted size vs. prev_sizeat varying workload counts (typically ~92 workloads, range 60-130 depending on timing). This is a timing-dependent race condition, not an immediate crash. The ASan-run21 previously captured:SEGV on unknown address 0x004000003202(WRITE, thread T3).Status: Game loads and runs, but heap corruption persists as a timing-dependent issue. All 3 original missing NIDs + 1 new are now handled:
_umtx_op: implemented with FreeBSD futex semantics (mutex ops 2/3/4, wake 15/16, wait/wake 0/1/9/13/14/19; rest → ENOSYS)sceNpUnregisterNpReachabilityStateCallback: stubbed (returns 0, logged in TechnicalDebt)sceNpEntitlementAccessGetEntitlementKey: stubbed (returns 0, logged in TechnicalDebt)sceKernelRaiseException: implemented viapthread_kill+SA_SIGINFOtrampolineRecordWrittenPath_nid_no_patch: stub added (previously causedsymbol lookup error)Next: Capture ASan backtrace when corruption appears, or test with
-job-worker-count 1to reduce variability.Branch:
test/1930-reapply-1156based onorigin/main(d70b8998).oneandonlydean commentedon Oct 11, 2026
#1102 (module parameter block) is merged, and PPSA28997 is reported booting on main 942a174 with #1482 and #1431 (#1482 (comment)). Does it still stop at
RTLD application heap: cannot replace an active allocatorfor you on current main plus #1482?igordev23 commentedon Oct 11, 2026
Tested exactly that: clean
origin/main(a11c8f7f7, no revert), which already contains #1482, #1431 and #1102. Rebuiltlibkernel.prx/libSceNpManager.prx/libSceNpEntitlementAccess.prxand ran PPSA28997 (Linux, RADV Barcelo).The allocator error is gone: zero occurrences of
RTLD application heap: cannot replace an active allocatorin the log.Boot now stops earlier at a different missing NID:
EXIT: 127, immediate, before any Vulkan init. So on current main the double-handshake no longer trips the guard, but the title needsRecordWrittenPathexported fromlibkernel, which main does not provide yet. Happy to add it asNotImplemented_nid_no_patchper the contributing rules and re-test if that helps.MarcZX10 commentedon Oct 11, 2026
I checked this symbol against current
upstream/main(a11c8f7f7): it is defined incore/libs/prx/libc/src/General.cpp, declared inlibc/include/General.hpp, andlibkernellinks to thelibctarget in its CMake file. The reported verification rebuiltlibkernel.prx,libSceNpManager.prx, andlibSceNpEntitlementAccess.prx, but doesn't mention rebuilding or replacing the matchinglibc.prx.Could you try rebuilding/deploying
libc.prxfrom the same checkout too, then rerun? If a clean set of PRXs still leavesRecordWrittenPath_nid_no_patchunresolved, the link/load logs from that build should help isolate why the existing definition isn't being resolved. I don't see a source-level missing-export fix to submit yet.igordev23 commentedon Oct 11, 2026
You were right: after rebuilding and deploying
libc.prxfrom the same checkout (a11c8f7f7),RecordWrittenPath_nid_no_patchresolves — zero symbol errors in the log.Boot now proceeds further (threads spawn,
/app0/Media/RuntimeInitializeOnLoads.jsonopens) and then aborts:EXIT: 134, only 74 log lines total — it dies before any Vulkan init, insideApr::_execute(core/libs/prx/libkernel/Apr/src/Apr.cpp:530): the cursor walk hits a cut-offCommandHeader(cursor + sizeof(header) > buffer.offset) on the first asset-read batch afterRuntimeInitializeOnLoads.json.So current status on clean main: no allocator error, no missing NIDs — the title now dies in APR command-buffer execution during early file reads. Let me know if a trace of the APR opcodes/buffer sizes leading up to it would help, I can instrument
_executelocally.MarcZX10 commentedon Oct 11, 2026
The new APR failure is at the parser boundary in
Apr.cpp::_execute: it means fewer thansizeof(CommandHeader)bytes remain atcursor, before an opcode can be decoded. Could you capture the following at that throw on an unchangeda11c8f7f7build?buffer.offset,cursor, andbuffer.offset - cursor;buffer.baseand the first up to 16 bytes frombuffer.base + cursor(hex);header.bytesand the buffer object address;Please keep the guard in place and log immediately before throwing. That will distinguish a genuinely truncated guest command stream from a bad/stale
offsetor concurrent buffer mutation; without it, relaxing the parser check risks reading beyond the captured commands.MarcZX10 commentedon Oct 11, 2026
Separate finding from my parser inspection: APR records with a valid header could declare fewer bytes than the opcode-specific struct, while
_executestill copied the full struct; the oldcursor + header.bytesbound could also overflow. I opened #3808 with size checks and malformed-record regressions. This addresses malformed command records only and does not diagnose or claim to fix your reported buffer ending before a complete header; the offset/cursor/raw-tail capture requested above is still needed for that.igordev23 commentedon Oct 11, 2026
Capture from an unchanged
a11c8f7f7build with only a pre-throw log added (guard intact). Two consecutive runs, identical values:Field by field against your checklist:
buffer.offset= 2,cursor= 0,remain= 2.buffer.base=0x1000100000001. No tail bytes to show: dereferencingbaseto dump them faults (first attempt died with SIGSEGV instead of the throw), so the base is not host-readable.has=0) — it fails on the very first header. Buffer object at0x2101a0580;size= 40,numCommands= 1048576 (0x100000).tidobserved at the throw; values are bit-identical across runs (sameobj, same fields), which argues against concurrent mutation — a racy writer would be unlikely to reproduce the exact same garbage twice.Read: this does not look like a partially-filled valid stream.
baseis odd (...001) and unmapped,numCommandsis0x100000, and nothing was consumed — theCommandBufferObjectitself looks stale or misread (bad guest address or layout skew) rather than truncated mid-stream. The submitter runs right after/app0/Media/RuntimeInitializeOnLoads.jsonis opened, before any graphics init.Happy to dump the raw 0x18 object bytes at
objor trace the submitter (_executecaller atApr.cpp:732and thebytes-based path at:662) next.oneandonlydean commentedon Oct 11, 2026
The allocator error is gone on main, so I'm closing this.
The
APR: truncated command bufferstop is a mixed build. Read with the object layout from before #2789, your dump is a healthy buffer: 1 MiB at0x2..., offset 40, one command. SolibSceAmpr.prxis older thanlibkernel.prx; #2789 changed that layout in both. Rebuild and deploy all oflibs/from one checkout, and open a new issue if PPSA28997 still stops after that.igordev23 commentedon Oct 12, 2026
Confirmed — it was the mixed build. Rebuilt all 122 patched
.prxfiles from the singlea11c8f7f7checkout and deployed the full set (backup of the old mixedlibs/kept aside).Result on PPSA28997 (Linux, RADV Barcelo): no stop at all. Zero
APR: truncated, zero allocator/symbol errors. Boot proceeds through Vulkan device init, 1920x1080 presentation window, swapchain (1109x624), shader draws — and the process was still alive whentimeout 300killed it (EXIT: 124). The trailingProcessShutdownthrow is just the shutdown path reacting to SIGTERM.So: clean main + full single-checkout
libs/runs the title past every previously reported stop. No new issue needed from my side. Thanks for the layout pointer — thatobjdump decoding as a healthy buffer under the pre-#2789 layout was the giveaway.