Skip to content

Linux: sceKernelGetModuleInfoForUnwind reports no unwind tables, so titles with a bundled libc.prx cannot catch C++ exceptions (PPSA12544) #2419

Closed

Description

@hugodearaujo

On Linux, sceKernelGetModuleInfoForUnwind (core/libs/prx/libkernel/Module/src/Module.cpp) reads /proc/self/maps and always returns eh_frame_hdr_addr, eh_frame_addr and eh_frame_size as 0. A title that ships its own libc.prx unwinds with the console's unwinder, which asks this function for each frame's tables. With no tables it finds no handler, so a C++ exception thrown in guest code reaches terminate even when the title catches it. Windows already reports the tables from the guest module's .ehmeta section.

I opened #2401 with a fix. The intake check closed it because I have fewer than 20 merged PRs, so I'm filing it here with the patch for whoever can carry it.

Title: Hollow Knight: Silksong (PPSA12544)

After a new game's opening video, the title stops:

Terminating due to uncaught exception of type Il2CppExceptionWrapper
  • The exception, read from the object: System.Reflection.AmbiguousMatchException.
  • The path, from the stack and a breakpoint on RuntimeType.GetMethodImplCommon: PlayMakerFSM.Init → Fsm.Init → CallMethodProper.PreCache → Type.GetMethod("CancelAttackNotDownspikeBounce").
  • HeroController has two overloads with that name, so the throw is expected.
  • The LSDA of CallMethodProper.PreCache in Il2cppUserAssemblies.prx has a catch clause over that call: call site 0x123+0xd, landing pad 0x1e1, action 3. On the console the exception is caught there.
  • The bundled libc.prx imports sceKernelGetModuleInfoForUnwind.

With the patch below, the title gets past the opening into gameplay. It was played for several minutes, including dying and respawning, with no exception reaching terminate. Base: main + #1836 + #1431 + #846 + #2323 + #861, which are needed to reach the opening.

Measured on a PS5 (firmware 13.60)

A payload calls the function for addresses in libkernel and libSceLibcInternal. It checks each field on its own: it decodes the returned header, compares the eh_frame pointer, and walks the records to the terminator.

libkernel libSceLibcInternal
return (flags 1) 0 0
st_size 0x130 0x130
eh_frame_hdr_addr header, version 1, encodings 1b/03/3b same
eh_frame_addr equals the header's decoded eh_frame_ptr same
eh_frame_size 0x1391c = walk 0x13918 + 4 0x2ccc0 = walk 0x2ccbc + 4
seg0 0x800000000 + 0x4c000, the code segment; the header lies outside it 0x8000a8000 + 0x128000
name empty empty
  • Flags 0, 3 and 8 return 0x80020016, and flags 2 returns 0x80020003.
  • An address outside every module returns 0x80020003.
  • Any failure zeroes the whole structure.
  • So the console's eh_frame_size includes the 4-byte zero terminator. That settles the open point in the current TechnicalDebt entry.

Patch

Commit f5c9a833 on hugodearaujo:fix/linux-module-unwind-info, based on main f8c2f072:

  • Linux. The module is found with dl_iterate_phdr, as the macOS branch already does. A guest module (the main program or a *.guest.prx) with a PT_GNU_EH_FRAME gets that header, the eh_frame it points to, and the record size. seg0 is the first PT_LOAD. Host libraries still get no tables, as on Windows.
  • Both platforms. The header parsing and the record walk become one helper. eh_frame_size includes the terminator, as measured. The Windows test expectation moves from 4 + 12 to 4 + 12 + 4.
  • Test. guest_unwind_module_info gets a Linux version. It compares every field against dl_iterate_phdr and its own header decode and record walk, for the main program, a module loaded as *.guest.prx, a host library and an unmapped address. It aborts on main's Module.cpp.
  • TechnicalDebt. The entry now records what still differs from the console: flags is ignored, and name gets the host path.

Results: Linux 514/514. Windows (MinGW, CI flags, no GPU) 507/507. #2193 touches the same file in a different function, which causes a text conflict near the includes.

AI-assisted: yes.

Activity

  1. MarcZX10 commented on Oct 9, 2026

    @MarcZX10
    Contributor

    I updated the existing sceKernelGetModuleInfoForUnwind TechnicalDebt entry with the firmware 13.60 measurements from this issue: flags/results, zeroing on failure, the terminator-inclusive size, and the measured name/seg0 behavior. The entry also states the current Linux gap.\n\nThe documentation-only commit is 2a58b076ec1d530a288edf40c39c40523ec0f9fc on fork branch codex/unwind-info-console-evidence. git diff --check and the repository conventions check pass. I have not opened a PR.

  2. MarcZX10 commented on Oct 9, 2026

    @MarcZX10
    Contributor

    I carried the Linux implementation from closed PR #2401 onto current upstream main as a reviewable branch, without opening a new PR:

    • 1d139108 preserves the original implementation commit and author.
    • 61b4a924 limits the new Linux-specific CTest registration to Linux, so macOS does not try to compile the link.h test.

    On current main 8b393b6e, libkernel and guest_unwind_module_info_tests build, and guest_unwind_module_info passes (1/1). The conventions checker and git diff --check pass. I reviewed the complete five-file diff; it still needs the official current-base Linux/Windows build matrix and runtime confirmation on the reported title. This branch is pushed to the fork as codex/unwind-info-linux-current; no PR was opened.

  3. MarcZX10 commented on Oct 9, 2026

    @MarcZX10
    Contributor

    Correction to my previous update: an open PR already covers this same Linux unwind implementation: #1600. Its author has also received a focused follow-up commit (132c3a50) for the failed-lookup review request, with Linux regression coverage. My branch codex/unwind-info-linux-current added no distinct fix, so I removed that redundant branch from my fork. Please use #1600 and its follow-up as the review path; I have not opened a PR.

  4. oneandonlydean commented on Oct 10, 2026

    @oneandonlydean
    Collaborator

    #1600 is merged: on Linux, sceKernelGetModuleInfoForUnwind now fills the eh_frame header, address and size for the main program and *.guest.prx modules. Could you retest Silksong past the opening on current main?

  5. hugodearaujo commented on Oct 10, 2026

    @hugodearaujo
    ContributorAuthor

    Thanks. #2401 duplicated #1600, which I missed.

    I can't retest past the opening on current main yet. Two of the PRs the title needs no longer merge cleanly:

    Once #846 and #1431 are rebased, I'll rerun past the opening and report here.

    For reference, the earlier run that got past the opening used #2401's implementation of the same change, on main d70b89989 + #1836 + #1431 + #846 + #2323 + #861.

  6. oneandonlydean commented on Oct 10, 2026

    @oneandonlydean
    Collaborator

    Closing: #1600 is on main, and on Linux it fills the eh_frame fields for relinked images (core/libs/prx/libkernel/Module/src/Module.cpp:190). Please reopen if the title still loses its exceptions once #846 and #1431 are rebased.

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