Repository navigation
Conversation
|
Independent Linux check at exact head The relinker-only suite passes 165/165 (28.88 s), and the real production getter returns the proposed default block in a separate native probe. There is a gap in the new regression, though: it remains green with the runtime fix removed, and also with a null default pointer in its own provider. Controls, rebuilt and run with
The size mutation confirms the fixture ran and rebuilt. The two false greens have separate causes: This small test-only change catches the null pointer and also checks --- a/core/relinker/elfpatcher/tests/GuestModuleArgsHost.cpp
+++ b/core/relinker/elfpatcher/tests/GuestModuleArgsHost.cpp
@@ -48,9 +48,10 @@
std::snprintf(line, sizeof(line), "args=%016zx argp=%016zx\n", args,
static_cast<std::size_t>(reinterpret_cast<std::uintptr_t>(argp)));
Write(line);
- if (args == sizeof(DefaultModuleArgs) && argp == &kDefaultArgs) {
+ if (args == sizeof(DefaultModuleArgs)) {
+ if (argp == nullptr) Fail("Missing default argument block");
const auto* defaultArgs = static_cast<const DefaultModuleArgs*>(argp);
- if (defaultArgs->size != 0x10 || defaultArgs->version != 0x200) Fail("Default arguments corruption");
+ if (defaultArgs->size != 0x10 || defaultArgs->version != 0x200 || defaultArgs->context != nullptr) Fail("Default arguments corruption");
}
}
It still needs a regression that calls the real runtime getter. I checked that independently without the full SDL build: compile the actual All temporary source edits were restored, the stock provider rebuilt, and |
jotitadev
left a comment
There was a problem hiding this comment.
macOS run of the new --load-default path, which has its own darwin branch (RTLD_GLOBAL) that the Linux runs don't cover.
Machine: Apple M2 Max, macOS 27.2, Xcode 27 toolchain, relinker-only Release.
Head: 950bc7c32, merged onto main @ ab9a47684 (clean).
The test only executes natively on macOS when the interpreter is x86_64 (platform.machine() == 'x86_64'). Under ctest on Apple silicon, both the arm64 build and the x86_64 build print native execution skipped, because /usr/bin/python3 starts as arm64. So I ran it by hand from the x86_64 build:
arch -x86_64 /usr/bin/python3 core/relinker/relinker/tests/test_guest_module_args.py <build>/core/relinker/relinker <build>/core/relinker/guest_module_args_host.prx
| Change | Result (x86_64 under Rosetta, Python 3.9.6) |
|---|---|
| None | pass |
Fixture provider GuestModuleArgsHost.cpp restored from main |
fail: args=0000000000000000 argp=0000000000000000 |
--load-default with DEFAULT_MODE instead of RTLD_GLOBAL |
fail: dlopen(...): symbol not found in flat namespace '_RecordArgs' |
So the darwin branch works and the RTLD_GLOBAL is required there. As @simonbbby showed on Linux, what the test checks is the fixture's own provider, not the production __aps5_get_pending_module_args_nid_no_patch in Module.cpp. That is the same on macOS: Module.cpp is not part of the relinker-only build.
Full relinker-only suite with the PR: arm64 163/163; x86_64 + Python 3.9.6 159/163, with the same 4 failures as main.
Written with AI assistance (Claude Code). I ran the builds and tests above on my machine.
|
Thanks @jotitadev and @simonbbby for the thorough independent validation on macOS and Linux! Carried the suggested mutation control in commit
Verified locally with a negative mutation control (setting Written with AI assistance (Gemini). |
|
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.
|
Provide a fallback 16-byte default arguments block {0x10, 0x200, nullptr} in
__aps5_get_pending_module_args_nid_no_patch() when no pending arguments are set.
On Linux, libraries listed under DT_NEEDED execute their DT_INIT at process start
before sceKernelLoadStartModule runs. Unity's UnifyPS5Plugin.prx (Cult of the Lamb,
PPSA06464) reads argp[0] unconditionally expecting {size: 0x10, version: 0x200, context}
or {0x58, 0x103}, which faulted when argp was null.
Extend guest_module_args relinker test fixture to execute native load verification
without pending arguments and assert the default block parameters. Update
docs/dev/debt/libkernel.md documenting the console divergence.
Closes boykopovar#5621.
Assert that argp is non-null when default module arguments size is passed in GuestModuleArgsHost.cpp, and check that context is null. In test_guest_module_args.py, assert that the recorded argp in events is not null.
20e3606 to
0cd9877
Compare
|
Independent Linux title check of exact PR head Compared main Both native launches fail before the executable's main entry, in the converted bundled
The first check accepts Environment: Arch Linux 7.2.9-arch1-1, GCC 16.2.1, Ryzen 9 7950X. Both regular runs exit 139; debugger runs confirm the distinct sites above. Same converted executable and guest modules, with verified library paths. No game assets, raw title logs, binaries or screenshots attached. AI-assisted investigation. |
|
I checked the independent Double Dragon result in the linked issue thread against this PR's stated fallback. On the exact earlier PR head, the {0x10, 0x200, nullptr} block gets past the plugin's header check, but NativePluginPS5Example.prx then dereferences the null context at +0x3a9 before the executable reaches main (https://github.com/boykopovar/AnyPS5/pull/6149#issuecomment-6201881775).\n\nThat does not disprove the narrower Cult of the Lamb fix, but it shows the default block does not make every needed-module DT_INIT safe. Could you clarify whether Cult of the Lamb was run past this initializer on the current head, and keep the PR's claim scoped to the reproduced case if that remains unverified? |
|
Two things before this can go further. The fallback also fires for an explicit |
|
Suggestion for this PR, with measurements (AI-assisted - note at the end). Cross-posting this from #5621 because it is about the fallback this PR installs, rather than a per-title finding. The default block here is
What I measured locally. Setting that field to a getter of that shape, returning 0 and handing back a statically built object whose To be clear: a real loader-side context object is presumably the right answer and this is not a proposal to merge - it is a throwaway probe to demonstrate the shape of the field, and it should not be committed as-is. Two things a proper version would have to do differently are reflected in the code below: no mutable static state (the module may call this from more than one thread), and a real decision about the block size. // The plugin consumes the default block's context as:
// g_context = args[8] (the {0x10, 0x200} layout)
// accessor: rc = g_context(id, &out); return rc ? 0 : out;
// caller: obj = *out; obj->vtable[0x10](obj, module_static); // virtual call
// i.e. the field is a getter function, not a data pointer - so a no-op is enough here
// only because the object it hands back is never used for anything this title needs.
//
// Everything here is statically initialised: the getter can be reached from more than one
// thread during module init, so there is deliberately no lazy-init guard and no mutable state.
// The signature of the virtual slot that gets called is not established, hence the loose one.
using ModuleContextNoop = long (*)(void*, void*);
extern "C" long moduleContextNoop(void* /*self*/, void* /*arg*/) { return 0; }
// slot +0x10 = element 2 (8-byte pointers), so every slot is filled rather than guessing
extern "C" constinit ModuleContextNoop moduleContextVtable[8] = {
moduleContextNoop, moduleContextNoop, moduleContextNoop, moduleContextNoop,
moduleContextNoop, moduleContextNoop, moduleContextNoop, moduleContextNoop,
};
struct ModuleContextObject { ModuleContextNoop const* vtable; }; // *(obj) -> vtable
extern "C" constinit ModuleContextObject moduleContextObject{moduleContextVtable};
extern "C" int moduleContextGetter(int /*id*/, void** out) {
if (out != nullptr) *out = &moduleContextObject;
return 0;
}
alignas(16) const SceKernelDefaultModuleArgs kDefaultModuleArgs{
0x10, 0x200, reinterpret_cast<const void*>(&moduleContextGetter)};The One size caveat while picking a default: the block above is the 16-byte Disassembly, register state and the thread-level evidence behind the shape claim are in my comment on #5621; happy to test anything more specific you would like tried. Note: written by an AI agent (Hermes, running on the reporter's own machine) while testing the emulator locally. The builds, runs and measurements are real and reproducible; the prose is the agent's. Not affiliated with the project. |
What
Provide a fallback 16-byte default arguments block
{size: 0x10, version: 0x200, context: nullptr}in__aps5_get_pending_module_args_nid_no_patch()when no pending arguments are set.On Linux, libraries listed under
DT_NEEDEDexecute theirDT_INITroutines at process start beforesceKernelLoadStartModuleruns. Unity'sUnifyPS5Plugin.prx(Cult of the Lamb, PPSA06464) readsargp[0]unconditionally expecting{0x10, 0x200, context}or{0x58, 0x103}, which faulted with a null pointer dereference (exit code 139).Extend
guest_module_argstest fixture with--load-defaultnative execution to verify that default arguments are delivered and validated without corruption. Record the divergence indocs/dev/debt/libkernel.md.Closes #5621.
Tested
ctest --test-dir build-relinker -R "^guest_module_args$" -V(0.19 s, passed).python3 tools/check_conventions.py --base upstream/mainpassed.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)