Skip to content

Linux: dlsym(NULL, name) is rejected and DT_NEEDED-loaded libraries are invisible to AnyPS5's dlsym, so titles cannot resolve scriptingGetMem #694

Closed

Description

@jeffrey12cali

Symptom. The title performs a process-wide lookup for its scripting-heap entry point and gets nil:

[dl] dlsym((nil), scriptingGetMem) -> (nil)

Two independent defects in dlsym_nid_postfix_impl()
(core/libs/prx/libkernel/Module/src/DynamicLoader.cpp) combine:

  1. handle == 0 is not treated as the default/global scope - only -2 is - so the call fails with
    dlsym: invalid or unsupported module handle.
  2. Even in the global branch, the search only walks the modules map, which contains modules created
    through AnyPS5's own dlopen. Everything the dynamic loader pulled in via DT_NEEDED (all of
    libs/*.prx) is invisible, so a plain-name lookup that ld.so would happily resolve is reported
    missing.

Nothing in the tree exports scriptingGetMem / scriptingFreeMem (nor the NID
ayuoL6Vjz2k), and no module imports it at link time, so it can only be satisfied as a run-time
export.

Fix we use locally.

  • handle == nullptr behaves like RTLD_DEFAULT; the global branch additionally falls back to
    ::dlsym(RTLD_DEFAULT, name) and then ::dlsym(RTLD_DEFAULT, Nid::ComputeNid(name, "")).
  • Added scriptingGetMem(alignment, bytes) and scriptingFreeMem(pointer) to
    core/libs/prx/libc/src/ApplicationHeap.cpp, wrapping ApplicationHeapAlign /
    ApplicationHeapFree.

After that the lookup resolves and the allocator behaves identically to Kyty's - same arguments, same
block size, same stride:

ours  : scriptingGetMem a=0x1000 n=0x40000 -> 0x2003b0000, 0x2003f1000, 0x200432000, 0x200473000
Kyty  : KernelApplicationHeapGetMem alignment=0x1000 size=0x40000 -> 0x2005b4000, 0x2005f5000, 0x200636000

Questions. Is scriptingGetMem/scriptingFreeMem intentionally omitted (i.e. does the engine use
a different scripting heap path on this title)? And would you accept the RTLD_DEFAULT fallback for
DT_NEEDED-loaded libraries, or should the loader register every libs/*.prx in the modules map at
start-up the way the Windows path loads them all explicitly?

Activity

  1. jeffrey12cali commented on Oct 5, 2026

    @jeffrey12cali
    ContributorAuthor

    Confirmed and drafted in #780 (third commit).

    Both defects were needed to make the lookup fail: handle 0 was rejected outright, and even the global
    branch never saw DT_NEEDED-loaded libraries. After the change:

    before   [dl] dlsym((nil), scriptingGetMem) -> (nil)
    after    [dl] dlsym((nil), scriptingGetMem) -> 0x7f03d8a71370
    

    The semantics question you will probably want answered: scriptingGetMem(alignment, size) is the right
    shape, established from KytyPS5 (KernelApplicationHeapGetMem in src/libs/libKernel.cpp, which floors
    alignment at 0x10 and rejects non-power-of-two) rather than SharpEmu's malloc(size) alias, and Raeen
    reaches the same conclusion independently from a different IL2CPP title (docs/unity-dlsym-blocker.md).
    The block sequence then matches Kyty byte for byte:

    ours     a=0x1000 n=0x40000 -> 0x2003b0000, 0x2003f1000, 0x200432000, 0x200473000   (stride 0x41000)
    KytyPS5  alignment=0x1000 size=0x40000 -> 0x2005b4000, 0x2005f5000, 0x200636000      (stride 0x41000)
    

    We deliberately did not add scriptingRealloc/scriptingCalloc: if the first argument really is an
    alignment, guessing the argument order on a resize corrupts the guest heap, and the references that
    boot this engine implement only the get/free halves. scriptingFreeMem is unambiguous (one pointer, no
    return) so it is included; note the title does not call it at this phase (4 gets, 0 frees).

    One more data point for the "should the loader register DT_NEEDED libraries in the module map"
    question: we kept the ::dlsym(RTLD_DEFAULT, ...) fallback rather than registering every libs/*.prx,
    because preloading them globally would let the bundled guest libc interpose the allocator over the
    whole process.

  2. adriadam10 commented on Oct 10, 2026

    @adriadam10
    Collaborator

    Partly addressed by #864: dlsym with handle 0 or -2 now takes the default scope (global modules, then the process scope filtered with dladdr to converted modules), asserted by guest_dynamic_loader (dlsym(nullptr, "GuestModuleAdd")); still open: scriptingGetMem/scriptingFreeMem are not exported (dropped from the PR on purpose, #780 closed) and no test exercises the process-scope fallback itself.

  3. jeffrey12cali commented on Oct 10, 2026

    @jeffrey12cali
    ContributorAuthor

    Useful split, and the guest_dynamic_loader assertion covers the part we needed for the engine's own symbols.

    For the record on scriptingGetMem and scriptingFreeMem, in case it matters when #780's decision is
    revisited: our title (PPSA28997) is a real-world case of the need. Its managed runtime resolves both by
    name through the process scope rather than importing them, and with neither present it ends up with a null
    scripting allocator and leaves managed objects unconstructed. We carry the two exports as a title-local patch
    in our own fork, so we are not asking for an engine change and this is not a request — just the data point
    that at least one shipping title requires them by name.

    On the missing test for the process-scope fallback: our title is the case that exercises it, but it is not
    something CI here can run. If a synthetic test would be useful, the smallest one I can see is a module that
    resolves a symbol which exists only in the process scope and is absent from every registered global module,
    then asserts the fallback path returns it. I would rather propose that than guess at your fixture layout, so
    say the word and I will write it against current main.

  4. oneandonlydean commented on Oct 14, 2026

    @oneandonlydean
    Collaborator

    Closing: #864 makes dlsym with handle 0 take the default scope, including the converted modules, which is what this reported. Exporting scriptingGetMem/scriptingFreeMem needs a title that reaches them on main, as its own issue. If the lookup still fails on current main, comment here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions