Repository navigation
fix(relinker): run a guest DT_INIT of 0 as the module initializer - #6684
Conversation
The guest reader stored DT_INIT as a plain address, and every user of it read 0 as "no initializer". The libc.prx and libSceFios2.prx that Until Dawn (CUSA02636) bundles have DT_INIT present with value 0, the start of their text segment. So libc.prx, which also has a preinit array, failed with "Guest PREINIT_ARRAY has no module initializer", and libSceFios2.prx's initializer never ran. GuestImage now records whether the tag is present (HasInit). The reader, the Linux, macOS and Windows writers and the code analysis use it instead of Init != 0, so an initializer at 0 is mapped, started and analysed like any other. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Independent Linux validation at PR head |
Retain DT_INIT presence when emitting the deferred Linux lifecycle slot. Check zero-valued initializers with and without preinit, native argument forwarding and return status, and the absent-tag case. Addresses the interaction with PR boykopovar#6684 identified in review.
|
Other open pull requests touch the same code. Merging one will break the other, or both implement the same thing. If this one builds on them, list them in Depends on.
Some comparisons could not be completed because a pull request conflicts with the base branch. Review these shared files manually before concluding that there is no overlap.
|
|
Second-title data point: Hasbro Family Fun Pack (CUSA03312, my own dump) needs this PR, on Linux and Windows. Relinker-only Release build, Windows 11 Pro 26300, WinLibs GCC 15.2.0 r7. Starting from The other members each stop at their own step, so every one of them is needed for this title; the whole table is on #6977. AI-assisted: yes (Claude Code) |
|
Thanks for the detailed leave-one-out run. It isolates this PR as necessary for Hasbro Family Fun Pack (CUSA03312) in the cumulative Linux/Windows conversion stack. Without #6684, both Linux and Windows conversions stop in libc.prx with “Guest PREINIT_ARRAY has no module initializer.” I tried to add the setup and result to the PR's Tested section, but GitHub denied the body edit with an admin-rights error; the full evidence remains here in your comment. |
… option boykopovar#7147 added add_asan_test_options so the CPU-side tests pass in an ANYPS5_ENABLE_ASAN build, and gave the relinker tests that load the instrumented guest_module_args_host.prx into Python verify_asan_link_order=0. guest_zero_initializer's Linux run loads the same host library with ctypes.CDLL in a Python subprocess, so under ASan it failed with "ASan runtime does not come first in initial library list". It now runs with the same option. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
boykopovar#6106 made a Linux guest module with an initializer depend on libkernel.prx, which exports the module-argument getter and setter that its DT_INIT stub imports. That check still tested Init != 0, so a module whose DT_INIT is 0 got the stub but not the dependency. With no other libkernel.prx loaded, the executable then stopped with a symbol lookup error, and a --module-dir module failed to load with "undefined symbol: __aps5_get_pending_module_args_nid_no_patch". The check now uses HasInit, like the writers. guest_zero_initializer now checks that the Linux module lists libkernel.prx, and its native load provides one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Retain DT_INIT presence when emitting the deferred Linux lifecycle slot. Check zero-valued initializers with and without preinit, native argument forwarding and return status, and the absent-tag case. Addresses the interaction with PR boykopovar#6684 identified in review.
Retain DT_INIT presence when emitting the deferred Linux lifecycle slot. Check zero-valued initializers with and without preinit, native argument forwarding and return status, and the absent-tag case. Addresses the interaction with PR boykopovar#6684 identified in review.
|
Tested on Until Dawn (CUSA02636, 1.03 eboot) on Windows 11: this PR works, and with it plus a few others the game now reaches its own loading screen. Setup: Windows 11, i9-12900KF, RTX 3090 Ti, MinGW (WinLibs GCC 15.2.0), cmake 4.3.1, ninja 1.13.2. Branch: This PR (#6684)
To answer the question in the description, I disassembled
There is no What else Until Dawn needed after #6684 / #6595 / #6977The patches are in the attached
Where it is nowThe game boots, creates and reads its save ( That looks like the next real gap: GCN Still known gaps (they throw if hit):
Full logs are available if useful. |
|
@SuperCatElvis thank you, that's exactly what this needed, and great progress on the title. I've added your run to the description and replaced the open question with your disassembly. It settles the preinit question: the code at 0 walks the preinit array itself, like the Wind Waker Recompiled One note on item 6 of your patch, in case you upstream it: HQasper's #8057, opened today, changes the same function ( AI-assisted: yes (Claude Code) |
Retain DT_INIT presence when emitting the deferred Linux lifecycle slot. Check zero-valued initializers with and without preinit, native argument forwarding and return status, and the absent-tag case. Addresses the interaction with PR boykopovar#6684 identified in review.
Retain DT_INIT presence when emitting the deferred Linux lifecycle slot. Check zero-valued initializers with and without preinit, native argument forwarding and return status, and the absent-tag case. Addresses the interaction with PR boykopovar#6684 identified in review.
Retain DT_INIT presence when emitting the deferred Linux lifecycle slot. Check zero-valued initializers with and without preinit, native argument forwarding and return status, and the absent-tag case. Addresses the interaction with PR boykopovar#6684 identified in review.
Retain DT_INIT presence when emitting the deferred Linux lifecycle slot. Check zero-valued initializers with and without preinit, native argument forwarding and return status, and the absent-tag case. Addresses the interaction with PR boykopovar#6684 identified in review.
Retain DT_INIT presence when emitting the deferred Linux lifecycle slot. Check zero-valued initializers with and without preinit, native argument forwarding and return status, and the absent-tag case. Addresses the interaction with PR boykopovar#6684 identified in review.
Retain DT_INIT presence when emitting the deferred Linux lifecycle slot. Check zero-valued initializers with and without preinit, native argument forwarding and return status, and the absent-tag case. Addresses the interaction with PR boykopovar#6684 identified in review.
Retain DT_INIT presence when emitting the deferred Linux lifecycle slot. Check zero-valued initializers with and without preinit, native argument forwarding and return status, and the absent-tag case. Addresses the interaction with PR boykopovar#6684 identified in review.
What
Until Dawn (CUSA02636) bundles
libc.prxandlibSceFios2.prx. SuperCatElvis reported stop 7 of the title's conversion on #3093, inlibc.prx:They then confirmed that both modules have DT_INIT (tag 12) present with value 0. Only
libc.prxhas a non-emptyDT_PREINIT_ARRAY(0xc2040, one entry).The guest reader stores DT_INIT as a plain address, and every user of it reads 0 as "no initializer" (GuestImageReader.cpp L263-L265 and L299, and since #6106 GuestModuleBuilder.cpp L313). In these modules 0 is the start of the text segment, ahead of the PLT at 0xc8, and no unwind function starts before the PLT. The loaders below call base + DT_INIT there. On
main, past the earlier stops:libc.prxfails with the message above, because the reader rejects a preinit array without an initializer;libSceFios2.prxconverts, but its initializer never runs. The Linux output gets noDT_INITstub, and the Windows and macOS startups skip it. Nothing reports this.What loaders do with a DT_INIT of 0:
obj->init = relocbase + d_ptrwhenever the tag is present (rtld.c L1533-L1536) and calls it when that sum is nonzero (L3292-L3298). For a loaded module that is code at its base. 9.0 does the same (L913-L914, L1480-L1481).kernbranch) stores relocbase + DT_INIT the same way, rejects a DT_INIT outside the text segment withENOEXEC(subr_dynlib.pas L1636-L1646), and reports that address to libkernel (kern_dynlib.pas L482-L486, L641-L644).sce_module/libc.prxandlibSceFios2.prxthis way (sysmodule_internal.cpp L137-L141).GuestImagenow hasHasInit, set when tag 12 is present. The reader, the writers and the module builder test it instead ofInit != 0:DT_INITstub, which passes the module arguments, for an initializer at 0.libkernel.prxdependency that fix(relinker): scope host dependencies to required guest modules #6106 adds for a module with an initializer, since the stub imports the module-argument getter and setter from it..ginitentry record it. Its RVA is 0x10000, so their own "RVA 0 means none" stays valid.A module whose DT_INIT is 0 while address 0 is not in an executable segment now fails with
Unmapped or inaccessible guest address 0.mainignored that DT_INIT.Not changed:
libc.prx, 0x3d040 inlibSceFios2.prx), so 0 still means none there.EntryPointCollector,StrictNidFilter) still skip a DT_INIT of 0. They only run on the executable.DT_PREINIT_ARRAYentries itself. It leaves them to the module's own initializer, and this PR only makes a DT_INIT of 0 count as that initializer. fix(relinker): run guest preinit arrays before the module initializer #6112 ran the entries before the initializer; it was merged and then reverted in revert(relinker): run guest preinit arrays before the module initializer (#6112) #7646, because a title's ownlibc.prxalready runs its preinit entry from its initializer, so the entry ran twice (Since #6112, every title aborts at start-up on Linux: "Thread destructor callback is already registered" #7636, details below).create-fselfwrites DT_INIT only when it is nonzero (OELFGenDynlibData.go L956-L958), so modules built with it are not affected.Tested
Ubuntu 24.04, GCC 13.3, CMake 3.28, Ninja, Release relinker-only build (
-DANYPS5_RELINKER_ONLY=ON -DBUILD_TESTING=ON), onmain324e0e4, which this branch merges.The new
guest_zero_initializertest (relinker/tests/test_guest_zero_initializer.py) converts a synthetic module whose text segment maps file offset 0x1000 at address 0, with DT_INIT 0. The initializer at 0 readsfs:[0], calls the host'sRecordArgsand returns a value. There are two shapes, one with a one-entry preinit array (likelibc.prx) and one without (likelibSceFios2.prx). For each:libkernel.prxand has aDT_INITstub. On this x86-64 host the converted module loads, records the pending arguments, then records the initializer's return value.fs:load is left.fs:load is left. A--module-dirmodule's.ginitentry is nonzero.A third shape puts the text segment at 0x1000, so 0 is unmapped. It fails with
Unmapped or inaccessible guest address 0for all three targets.On
mainthe test fails withGuest PREINIT_ARRAY has no module initializer. Reverting one part at a time also fails it:libc.prx;.ginitline: entry 0;fs:load stays in the macOS and Windows modules.For the builder line I also ran a module with no dependencies and DT_INIT 0 under an executable that imports nothing from
libkernel.prx, with the host library aslibs/libkernel.prx. Without the line, the executable stops with a symbol lookup error, and the same module from--module-dirfails to load withundefined symbol: __aps5_get_pending_module_args_nid_no_patch. With it, both load and the initializer's result is recorded.Full relinker-only
ctest: 209/209 (208 onmain, plus this test). The new test passed 5 runs out of 5.Release ASan build (
-DANYPS5_ENABLE_ASAN=ON,ASAN_OPTIONS=detect_leaks=0, as in #7147): 209/209, and the new test passed 5 runs out of 5. Its Linux run loads the host library into Python with ctypes, so it now hasadd_asan_test_options(guest_zero_initializer verify_asan_link_order=0)like the other tests that do. Without that line it fails withASan runtime does not come first in initial library list.On a title: savvaniss ran a leave-one-out on Hasbro Family Fun Pack (CUSA03312) (comment, full table on #6977). On
mainf305942 (before the revert) with #7660, #6619, #5138, #6381, 3b158c1 from #6977, #6354, #6357, this PR and #6618 merged, the Linux and--windowsconversions of the eboot and its 9 bundled modules complete. Without this PR, both stop inlibc.prxwithGuest PREINIT_ARRAY has no module initializer.On Until Dawn (CUSA02636): SuperCatElvis ran this PR on Windows 11 (RTX 3090 Ti, WinLibs GCC 15.2.0), on
mainof 2026-10-19 with #6595, #6618, #6977 and their own local fixes merged:libc.prxno longer stops withGuest PREINIT_ARRAY has no module initializer.libSceFios2.prx's initializer runs: itsSceFiosIOandSceFiosCallbackthreads come up, and file I/O works.With their other local fixes the title boots, creates and reads its save, and reaches its own loading screen. It then stops at a GCN
v_cmpxthat the recompiler doesn't implement yet.Merges:
main, this branch mergesmain324e0e4 as is, with no conflicts (40a2e87).add_asan_test_options), and f77a9ea is the module builder change (fix(relinker): scope host dependencies to required guest modules #6106 added that check tomainafter this PR was opened).mainplus this branch, five apply cleanly and25266b497has a conflict intest_guest_zero_initializer.pyonly, where both add lines around the native load. Its copy doesn't have f77a9ea, so it picks up thelibkernel.prxdependency from here.main, and this PR adds no conflict to any of them. Where one conflicts in a file this PR touches (fix(relinker): accept section-relative guest module relocations #5138 inGuestImageReader.cpp, feat(relinker): lower MCOMMIT and RDPRU for Intel with execution tests #2780 inGuestModuleBuilder.cpp, five incore/relinker/CMakeLists.txt), the conflicting hunks are the same with and without this PR.Why the preinit array is left to the initializer. SuperCatElvis disassembled Until Dawn's
libc.prxfrom 0 up to the PLT at 0xc8 (same comment). It is a plain module initializer taking(rdi, rsi, rdx):-1sentinel, and calls each entry.rdxis non-null, it tail-calls it with(rdi, rsi). Otherwise, if the qword at 0xbc890 is nonzero, it tail-jumps to 0x600; else it returns 0.So the module runs its own preinit entry, like the Wind Waker Recompiled (PPSA99309)
libc.prxthat milescoviello traced on #7636, where #6112 ran that entry a second time. That is why this PR only makes a DT_INIT of 0 count as the initializer and runs nothing else.The code from 0 to 0xc8 has no
fs:access. So for this module, the code-analysis entry point at 0 is not what makes it work. It covers an initializer at 0 that does use TLS, which the test checks.Checklist
main; no other open PR implements the same functionsNotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file.mdfiles or images added to the repository (attach them to this PR)libc.prxandlibSceFios2.prx); CUSA03312 Hasbro Family Fun Pack (CUSA03312) #7198 (Hasbro Family Fun Pack: stop 6 in savvaniss's conversion; in their leave-one-out, both the Linux and--windowsconversions stop inlibc.prxwithout this PR); CUSA02636 (no Game bring-up discussion): stop 7 of SuperCatElvis's conversion on Relinker rejects PS4 executables: SCE dynamic tables and data in the code segment (two ready branches) + remaining PS4 work #3093, in the bundledlibc.prx; this also runslibSceFios2.prx's initializer, whichmainskips. SuperCatElvis confirmed both on Windows with this PR, fix(relinker): exclude trailing PS4 relative jump tables #6595, fix(relinker): skip jump tables placed after a function's code #6618, feat: run PS4 games on the AGC driver — Limbo (CUSA01664) in game (integration branch) #6977 and local fixes. The title does not reach it onmain: stop 1, the eboot's jump tables (fix(relinker): exclude trailing PS4 relative jump tables #6595), still stops it first. In the modules, stops 4 and 5 come before it (fix(relinker): accept section-relative guest module relocations #5138; stop 5, a DTPMOD64 against the index-1 symbol, is fixed by a commit in feat: run PS4 games on the AGC driver — Limbo (CUSA01664) in game (integration branch) #6977). Stops 6, 8, 9 and 10 (fix(relinker): accept matching defined relative symbols #6357, fix(relinker): read the PS4 needed-module tag in guest modules #6381, fix(relinker): find the PLT before the code in PS4 modules #6354) are now fixed onmain.🤖 Generated with Claude Code