Repository navigation
EA Sports UFC 5 (PPSA03541): null function pointer call in Engine.Render.Core2.PlatformPs5 about 20 s after start #3123
Copy link
Copy link
Open
Description
Summary
EA Sports UFC 5 (PPSA03541, EUR, v01.031) converts with the relinker, loads, creates the Vulkan device and swapchain, and then crashes about 20 s after start. A Job0 thread jumps to address 0 from inside the game's renderer module (Engine.Render.Core2.PlatformPs5.prx).
Not a request for support. This is a data point on a title that is not in the compatibility list, plus what I already ruled out.
Environment
- Windows 11, RTX 3060 (Vulkan device selected and swapchain created, 2226x1252), 24 GB RAM
- Built from
mainat 9206c3e (2026-10-11) with MinGW-w64 GCC 16.1.0 (not the documented 15.2.0); relinker and full build both succeeded - Relinker:
--windows --to-intel, input is the game's decryptedeboot.bin(plain ELF). Its 4 modules (libc.prx,libSceNpCppWebApi.prx,Engine.BuildInfo.prx,Engine.Render.Core2.PlatformPs5.prx) were converted together as bundled modules Engine.*.prxsit next toeboot.binrather than insce_module/, so I staged all four in aprx/directory to convert them, and copied the outputs to where the game loads them (/app0/Engine.*.prx). Before that the game called a null pointer much earlier, because the unconverted module was not found
What happens
- Startup is normal: Vulkan device, swapchain, pad,
/app0/Data/...,/app0/Patch/package.mftopened - Last log line before the crash:
[libSceSysmodule/Export.cpp:127 sceSysmoduleLoadModule] unknown id: 267 FATAL: unhandled exception 0xc0000005 on thread 'Job0',rip 0x0000000000000000,execute of 0x0000000000000000- Return address on the stack:
Engine.Render.Core2.PlatformPs5.prx.guest.prx+0x47b43, so the renderer module calls a null function pointer
FATAL: unhandled exception 0xc0000005 on thread 'Job0'
rip 0x0000000000000000
execute of 0x0000000000000000
rax 0000000000000000 rbx 0000000000000000 rdx 0000000000000000
rsi 0000000100000001 r14 0000000000000000
[rdi] 0000000000000000 0000000000000000 0000000000000000 0000000000000000
[r13] deadbeefcafebabe 0000000000000000 0000000000000001 0000000000000001
stack return address candidates:
[rsp+0x0] Engine.Render.Core2.PlatformPs5.prx.guest.prx+0x47b43
Reproduced on every run, and unchanged after pulling 6 newer commits.
What I ruled out
- Missing imports:
tools/import_audit.pyover the executable and all 4 bundled modules reports 984 unique imports, 0 absent, 732 implemented, 25 stubs. The stubs are voice chat (sceVoice*,sceProprietaryVoiceChatHelper*),sceAudioInOpen,sceNpEAAccess*,sceHttpWaitRequest,sceSslGetPem,sceAgcDebugRaiseExceptionand a few kernel debug functions. They throw when called, so none explains a jump to 0 - Failed
sceKernelDlsym: I added a log on the failure path ofsceKernelDlsymand it printed nothing before the crash, so the null pointer is not from a symbol lookup the game made through it - Sysmodule id 267 (0x10B):
libSceSysmodule/ModuleTable.hpphas no entry for 0x10A or 0x10B, andsceSysmoduleLoadModulereturns success for unknown ids. It is the last thing logged before the crash, but I could not tie it to the null call
Guess
A pointer the game expects a system function to fill (a callback or function table) stays zero, possibly because an "implemented" function returns an incomplete result or because module 267 is not provided. I have not disassembled the module around +0x47b43 to confirm.
Questions
- Does anyone know which system module id 267 (0x10B) is on PS5?
- Is a title that keeps its own modules next to
eboot.bin(outsidesce_module/) expected to work with theprx/staging approach above, or is there a supported layout?
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels
Activity
LeandroSSB commentedon Oct 11, 2026
Your module-id question has an answer now: 267 (
0x10b) is the Acm co-processor module — identified in #3710 (Demon's Souls start-up crash), which cites #3240/#3203 for the id.That thread also isolates an enabler your null-pointer crash likely shares:
sceSysmoduleLoadModulereturns 0 for ids it does not know, so a title that probes for Acm and has a fallback (yours: a callback/table the game expects a system function to fill stays zero) takes the Acm path believing it loaded, whilelibSceAcmis a no-op — the same "returns success, delivers nothing" shape you ruled imports out for. If your renderer module turns out to gate on another unknown id the same way, the fix direction is probably at the sysmodule table (fail or map unknown ids) rather than at the null call site.The-IOMIOU commentedon Oct 11, 2026
Thanks @LeandroSSB, that was useful. I tried it.
main(a11c8f7). The module table there has no0x10Bentry, sosceSysmoduleLoadModulestill logsunknown id: 267and returns 0.sceSysmoduleLoadModule(0x10B)returns0x805A0001instead of 0, so the title sees Acm as not loadable. Nothing else changed.Result: no difference. Same crash on a
Job0thread,rip 0, same return addressEngine.Render.Core2.PlatformPs5.prx.guest.prx+0x47b43, and the same register pattern as before. Theunknown id: 267line is still the last log line before it.So for UFC 5 the Acm id does not look like the cause: either the renderer does not check that return value, or the null pointer comes from somewhere else. Acm may still be the cause in the Demon's Souls case.
Earlier checks, for completeness:
import_audit.pyover the executable and all 4 bundled modules reports 0 absent imports, andsceKernelDlsymlogged no failed lookup before the crash.Next I plan to disassemble the renderer module around
+0x47b43to see which pointer is null. If anyone already knows what that module expects a system function to fill in, that would save time.The-IOMIOU commentedon Oct 15, 2026
Update, and a correction: the null-pointer crash I reported was caused by my own setup, not by the project. UFC 5 now gets much further.
What was wrong
Engine.*.prxsit next toeboot.bin, outsidesce_module/. The relinker only writes bundled modules into the directory they were staged from (prx/orsce_module/), so I also put plain copies of the relinked files at the paths the game asks for (app0/Engine.*.prx.guest.prx,app0/sce_module/*.prx.guest.prx).The startup stub loads and binds the instances under
app0/prx/. The game then opens/app0/Engine.Render.Core2.PlatformPs5.prx,RelinkedModulePath_nid_no_patchreturns my copy, and Windows loads it as a second instance whose imports nothing ever bound. The first call through an unbound slot was__cxa_guard_acquire(3GPpjQdAMTw), which is thecalljust before the crash address+0x47b43: a function-local static guard, jumping through an empty import slot to 0.Fix on my side
Hard links instead of copies:
app0/Engine.*.prx.guest.prxandapp0/sce_module/*.prx.guest.prxare hard links to the files underapp0/prx/. Windows then reuses the already-bound instance. With that the null call is gone.What happens now (current
main, 0029e62, Windows, RTX 3060)EA SPORTS UFC 5A
WAIT_REG_MEM_64on queue 0x29 also logs a timeout shortly before the abort. I do not know what the game was showing on screen at that point.So this is now a feature gap in the AGC driver (
COPY_DATAto a GDS or performance-counter destination), not a crash from missing imports.Two notes for the project
0x10B: now registered upstream and no longer logged as unknown, but it made no difference here. It was never the cause for this title.eboot.bincannot be laid out by the relinker. TheRelinkedModulePathlookup only checks for<requested path>.guest.prxin the requested directory and does not match modules that are already loaded. A short line indocs/user/USAGE.mdabout the hard-link workaround, or a relinker option to write those modules to theapp0/root, would save the next person the same detour.The-IOMIOU commentedon Oct 15, 2026
Second update. UFC 5 now reaches its loading screen on
main(0029e62, Windows, RTX 3060) with two local patches tolibSceAgcDriver. I saw the loading screen with the loading badge animating. It then crashed with heap corruption. Details below, in case any of it helps.What each stage looked like (after the module layout fix from my previous comment)
First abort, about frame 267 (renders black frames at 23 to 51 FPS until then):
The six rejected packets are COPY_DATA with a GDS source or destination. Decoded:
00106203 00000020 00000000 <lo> <hi>: src_sel 3 (GDS, offset 0x20) to memory06106305 00000000 00000000 00000020 00000000: src_sel 5 (immediate 0) to dst_sel 3 (GDS, offset 0x20)DMA_DATAalready treats GDS asGdsAddress() + offset, so I did the same for COPY_DATA (validation with agdsRangecheck, the source read incopyDataSource, the destination inExecute, andResolveStoreandDecodeStoresskip a GDS destination asDMA_DATAdoes).Second abort, about frame 360:
singleSampleMsaaRejectioninGraphics/src/State.cppaccepts VGT_PRIMITIVE_TYPE 4, 5, 6. Type 7 isTRI_WITH_WFLAGS, still a triangle primitive, so I added it.After both patches the game reaches about 790 frames at about 45 FPS, then the queue stalls: a queue (0x29) logs repeated
The label stays 0 while the CPU side waits. FPS drops to about 0.03 to 1. The title's own hang detector starts writing
eadp.errorsdata.*_hang.st.dat. The title was also loading its UI scripts (ui/lua/...) at that point. I did not find what should write that label.The process then dies without a project crash handler message. Windows logged an
APPCRASHwith exception0xc0000374(heap corruption) in ntdll, at about 13 GB working set. I do not know if my patches contribute to it.Caveats
DMA_DATA, so it may be wrong for GDS contents that shaders also write (no synchronisation with GPU work, asDMA_DATAhas none either).Execution/src/Pm4.cppand 1 line inGraphics/src/State.cpp, described above. I can paste the diff if that helps.tests/Pm4.cppthat expects a GDS destinationCOPY_DATAto fail validation would need updating if this were upstreamed.Happy to test other builds or patches on this title.
notaxeltv commentedon Oct 15, 2026
Confirmed on current
main(fa9365c) +_Cnd_init_with_default_name_override(#6088), Windows 10, RTX 3080, PPSA03541 v01.031:maincannot load: unresolvedjBOZAv6CwkMfromlibc.prx(_Cnd_init_with_default_name_override).rip 0/execute of 0insideEngine.Render.Core2.PlatformPs5(same signature as this issue). NosceSysmoduleLoadModule unknown id: 267line on this run before the fault.notaxeltv commentedon Oct 15, 2026
Follow-up: rebuilt
main@ 44df216 plus the open PPSA34015/libc stack (incl. #6088_Cnd_init_with_default_name_override). PPSA03541 still dies on Job0rip 0immediately after threads start /_replay0.binopens — no newNotImplemented/ absent import on the path. Crash stacks also showlibSceAgc/libSceVideoOutframes besideEngine.Render.Core2.PlatformPs5. Same signature on FC27 (PPSA34015) with the same stack.notaxeltv commentedon Oct 15, 2026
Layout confirmation (PPSA03541): the Job0
rip 0was a secondLoadLibraryof/app0/Engine.Render.Core2.PlatformPs5.prxwhileapp0/prx/Engine.Render.Core2.PlatformPs5.prx.guest.prxwas already loaded. Replacing the root copies with hardlinks to the.guest.prxfiles removes the null call.With that layout (plus
_Cnd_init_with_default_name_override), the title creates the swapchain, presents HDR10, compiles thousands of shaders, then stops on:AGC driver: COPY_DATA ... GDS or performance-counter destination is not implementedSo #3123's null is a duplicate-module layout issue; the next blocker is COPY_DATA→GDS in AgcDriver.
The-IOMIOU commentedon Oct 16, 2026
@notaxeltv thanks for confirming, same layout fix and same wall here. The next two blockers after
COPY_DATAto GDS, in case you want to reproduce:Build:
main@ 0029e62 (2026-10-15 morning), Windows, RTX 3060. It loaded without #6088 for me, so that unresolved_Cnd_init_with_default_name_overridemay be new on latermain.1.
COPY_DATAwith GDS source or destination (libSceAgcDriver/Execution/src/Pm4.cpp). The rejected packets decode as GDS offset 0x20 to memory (src_sel 3) and an immediate to GDS offset 0x20 (dst_sel 3). I mirrored whatDMA_DATAalready does: GDS isGdsAddress() + offset.2. Next abort, about frame 360:
MSAA_ENABLE on a single-sample target for primitives other than triangles is unsupported (register 0x242 = 0x00000007). Type 7 isTRI_WITH_WFLAGS, still a triangle primitive, sosingleSampleMsaaRejectioninGraphics/src/State.cppcan accept it.After both: about 790 frames at around 45 FPS, a loading screen with the loading badge animating, the title starts loading its UI scripts, then a queue stalls on
WAIT_REG_MEM_64 ... ref 0x312 ... value 0x0(label never written), FPS drops to about 0.03 and the process finally dies with Windows0xc0000374heap corruption. Details are in my comment above.These are experiments. They get the game further but I have not checked that anything renders correctly, and I do not know if they contribute to the heap corruption.
Patch (15 lines changed, against 0029e62)
The-IOMIOU commentedon Oct 17, 2026
Third update, on
main@ b4f05ab (2026-10-18), Windows, RTX 3060, PPSA03541 v01.031, with the hard-link layout from above. The title now goes quite a bit further, then dies with heap corruption. Plus a separate bug in the new pipeline journal.How far it gets (screenshots taken from the window)
ui/features/main/vvms/mainmenu/mainmenuhandler.lreq,movieutils.lreq,notifications.lreq), then/download0/postlaunchassets/postlaunchassets.catalogis opened for write, and about 7 s after the intro video the process dies with Windows0xc0000374(heap corruption). Same stage as the earlier run on 2026-10-15.Three local changes I needed to get that far
These are experiments. They get past aborts, I have not verified rendering correctness beyond the screens above.
COPY_DATAwith a GDS source or destination (Execution/src/Pm4.cpp), as in my earlier comment: GDS offset 0x20 to memory (src_sel 3) and immediate to GDS (dst_sel 3), done likeDMA_DATAdoes it withGdsAddress() + offset.MSAA_ENABLE on a single-sample target for primitives other than triangles is unsupported (register 0x242 = 0x00000007). On currentmain,singleSampleMsaaRejectioncallsnonTriangleRejection(queue, "...")without therectanglesargument, so VGT_PRIMITIVE_TYPE 7 is still rejected there, whilesingleSampleDepthScanRejectionpassestrue. Passingtrueat that call lets the title through. (I called type 7TRI_WITH_WFLAGSearlier; upstream treats 7 and 17 as rectangle primitives.)unsupported PA_CL_CLIP_CNTL flags is unsupported (register 0x204 = 0x0c090000).ClipControlMaskinGraphics/src/State.cppallows bits 19, 24, 26, 27 but not bit 16 (CLIP_DISABLE). The title sets 16 together with 19, 26 and 27. I added0x10000uto the mask, which in practice means "depth clamp from 26/27, ignore the rest". Not a faithful implementation of CLIP_DISABLE.New bug: pipeline journal crashes the title on a warm start
With the cache from a previous run present, the process dies within seconds, every time, before a window appears:
Windows logs exception
0x80000001(guard page violation) inufc5.exe, fault offset0x3c48300, identical in both runs, no project crash message. Renamingpipelines.journalout of the way makes the same build run normally (and fast, since the rest of the cache is warm). The first run, with no journal yet, also worked. So this looks like a bug in the journal's replay or in what it triggers, not in the title.Heap corruption (0xc0000374) in the driver worker
I read the minidump (no debugger here, so by hand). The faulting thread is a driver worker (
Driver::enqueuelambda →AgcDriver::DriverDetail::Driver::run(unsigned int)at +0x3113). The stack showsstd::_Sp_counted_ptr_inplace<std::map<unsigned long long, std::vector<std::shared_ptr<AgcDriver::DriverDetail::ShaderSnapshot const>>>>(that isShaderRegistry) in_M_release_last_use_cold, then a long recursion ofstd::_Rb_tree<...ShaderSnapshot...>::_M_erase, then ntdll's heap check. So Windows detects the damage while the last reference to a copy of the shader registry is released at the end of a submission. The write that corrupted the heap most likely happened earlier, somewhere else. I could not find it without a heap checker (page heap or ASan).I do not know whether my three changes above contribute to it. If someone can run this title under page heap or a sanitizer build, that would pin it down.
Happy to test patches on this title.
The-IOMIOU commentedon Oct 18, 2026
Fourth update, PPSA03541 v01.031, Windows, RTX 3060. Short version: the heap corruption from my last comment was my toolchain, not AnyPS5, and with the supported compiler the title now goes through the menus (consent, difficulty) and loads into the ring.
Root cause of the 0xc0000374: unsupported GCC 16.1
I had WinLibs GCC 16.1.0 r2 instead of the supported 15.2.0 r7 from
docs/dev/BUILD.md. In that build'slibstdc++-6.dll,__gnu_cxx::__exchange_and_addis the generic mutex fallback (lock, load, add, store, unlock) instead oflock xadd. The shared_ptr paths that are inlined from the headers (weak_ptr::lock's CAS, the_M_release"both counts are 1" fast path) use real atomics and do not take that mutex, so concurrent updates get lost.How it showed up: the AGC driver's
ShaderRegistrycopies were freed while a submission orDriver::shadersstill referenced them (registry nodes overwritten, double erase, heap check inDriver::runreleasing a submission). I caught it with a local diagnostic that puts each published registry copy in its own read-only pages: the current registry's deleter ran fromDriver::run→_M_release_last_use_coldwhile its control block still read use count 83. With 15.2.0 r7 (__exchange_and_addis a singlelock xadd) the same diagnostic reports nothing, and the crash is gone.Maybe worth a configure-time check or a note, since winget currently installs 16.1 as "WinLibs POSIX UCRT".
Real fix: warm-start crash was a stack-walk into the main thread's guard page
The 0x80000001 I blamed on the pipeline journal is not the journal. The title runs a thread (entry in the eboot) that walks the main thread's stack from its top down to the bounds libkernel reports. Guest threads have fully committed stacks, but the adopted host main thread's bounds come from
GetCurrentThreadStackLimits, which includes uncommitted pages and the guard page. Touching another thread's guard page raises STATUS_GUARD_PAGE_VIOLATION. With the journal the title just starts faster and gets there before the main stack has grown. I commit the whole adopted host stack inSetStackFromHost(libkernel/Pthread/src/Thread.cpp), asStartNativeThreadalready requires for guest threads. The warm start with the journal works after that.Local workarounds still needed (not proposals as they are)
COPY_DATAwith a GDS source or destination (Pm4.cpp), like DMA_DATA's GDS handling.singleSampleMsaaRejectionpassesrectangles = true(VGT_PRIMITIVE_TYPE 7).PA_CL_CLIP_CNTLbit 16 added toClipControlMask(approximated by depth clamp).shader info collection failed: interpolation parameter reference is out of range (attribute 0 component 0 mode 0, 0 inputs). The title draws withSPI_PS_IN_CONTROL.NUM_INTERP = 0and a pixel shader that interpolates attribute 0. I replace out-of-range interpolation parameters with 0 before info collection to see what comes next. On hardware that read returns whatever is in parameter memory, so I would not upstream this as is.Happy to test patches or provide traces.
The-IOMIOU commentedon Oct 18, 2026
Performance numbers from the fight (in the Octagon, warm shader cache, same build as my last comment, Windows, RTX 3060). It renders correctly but runs at about 1 FPS. From
APS5_TRACE_SUMMARY_EVERY=15andAPS5_PROFILE_GPU=1:So the GPU is mostly idle. Queue 0 spends most of its time in
memory_wait, and the batch count suggests each wait on an end-of-pipe label turns into a submit plus a CPU wait. The rest is single-threaded draw translation on queue worker 0. Separately, compute program0x11426ce000is unusually expensive for one dispatch.I can capture a full
APS5_TRACE_FRAMESwindow of the fight, or run with any other switches, if that helps.Logan1h commentedon Oct 21, 2026
Current state on a local tree, with the fixes the title needs applied, for whoever is working on the frame cost: the title no longer stops and reaches a fight, and here is where its frame goes.
Tree:
main2f161d4f3plus #6549 (COPY_DATA GDS, required), #6604 (missing pixel interpolators, required), #7048 (write-watch refusal, required – it removes the abort this issue used to hit), and the perf PRs #8484, #8735, #8672, #8727, #8888, #8911, #8912, #8913, #8928, #8117. Draft #6946/#6947 are not included.Host: Windows 10.0.26300, Ryzen 5 7500F (12 threads), 32 GiB, RTX 4070 SUPER / NVIDIA 617.14, WinLibs GCC 15.2.0, Release, 1920x1080.
Switches:
APS5_TRACE_SUMMARY_EVERY=10,APS5_PROFILE_GPU=1.So the front end runs at ~13 FPS, a fight at ~2.8 FPS, and the GPU is idle for three quarters of the fight frame.
Where a fight frame goes (
top_phases_ms_per_frame, summed over traced threads):Driver.Packet626 msDriver.Packet.memory_wait238 msGraphics.Draw230 ms, of whichGraphics.Draw.draw_and_resource_release217 msDriver.Packet.draw209 ms,Driver.Draw.draw_enqueued125 msDriver.Packet.dispatch120 msDriver.Draw.shader_compile_and_link223 ms in the intervals that hit a new program (absent from the steady ones)Serialization is the main cost, not GPU work. With 8 queue workers,
queue worker 0x0sits at 97 % for the whole fight while the other seven are at 10–13 %. Per 10 s the main queue accepts ~10 300DISPATCH_DIRECTand ~9 500DRAW_INDEX_AUTOpackets, so a fight frame is ~1 000 guest commands recorded on one thread, andmemory_waiton that thread is time it cannot spend recording. That is exactly the shape #6946 (move the second half of a draw off the worker) and #6947 (stop rebuilding descriptors per draw on the CPU) are meant to cut, so those two look like the right next step for this title.The front end, for comparison, is CPU-bound but flat:
Driver.Packet102 ms,dispatch55 ms,Graphics.Draw48 ms, andmemory_waithas dropped out of the top phases entirely.