Skip to content

feat(libc): answer every libunwind cursor slot of an unwind context - #6819

Open
GitamarT045T3R wants to merge 6 commits into
boykopovar:mainfrom
GitamarT045T3R:feat/libc-unwind-cursor
Open

GitamarT045T3R wants to merge 6 commits into
boykopovar:mainfrom
GitamarT045T3R:feat/libc-unwind-cursor

Conversation

@GitamarT045T3R

Copy link
Copy Markdown
Contributor

What

The _Unwind_Context that _Unwind_Backtrace passes to its callback starts with a table laid out like LLVM libunwind's AbstractUnwindCursor virtual table (db3ec02), because homebrew built with the ps5-payload-dev SDK links libunwind statically and calls through that table from its own _Unwind_GetIP. Only the register slots and isSignalFrame were answered; the other nine slots threw. A title that asks its own libunwind for a frame's procedure info or name (_Unwind_GetRegionStart, _Unwind_GetLanguageSpecificData and unw_get_proc_info call getInfo; _Unwind_SetIP calls getInfo and setInfoBasedOnIPRegister; unw_get_proc_name calls getFunctionName), steps a frame (unw_step) or resumes one (unw_resume) stopped there.

Every slot now answers as UnwindCursor<LocalAddressSpace, Registers_x86_64> of LLVM 18.1.8 does with DWARF unwind information:

Slot Behaviour
~AbstractUnwindCursor (complete and deleting) nothing; libunwind's class-specific operator delete is empty
getReg, setReg as before for -2 (UNW_REG_SP) to 16 (rip); any other number prints libunwind: getRegister - unsupported x86_64 register (or setRegister) and aborts, as Registers_x86_64 does through _LIBUNWIND_ABORT
getFloatReg, setFloatReg print libunwind: getFloatRegister - no x86_64 float registers (or setFloatRegister) and abort
step UNW_STEP_END without unwind info, UNW_EBADFRAME when the rules cannot be applied, otherwise moves to the caller with the same rules as _Unwind_Backtrace and looks the new IP up, returning UNW_STEP_END when nothing covers it (IP 0 included)
getInfo start, end, LSDA and personality from the FDE, DW_CFA_GNU_args_size as gp, UNWIND_X86_64_MODE_DWARF as format, the FDE and its size as unwind_info, the start of the ELF segment or PE section holding the IP as extra; all zero without unwind info
jumpto restores the registers and jumps, as _Unwind_RaiseException installs a landing pad
getFunctionName dladdr of the IP (Linux, macOS); on Windows the nearest export at or below the IP in the image holding it
setInfoBasedOnIPRegister looks the IP up as it is, or minus one for a return address
getRegisterName Registers_x86_64::getRegisterName's names, unknown register otherwise

To share the walk with _Unwind_Backtrace, Step is split into Caller (the registers of the caller) and the end-of-stack checks, and GetRules records the FDE, the function's end, the arguments size and the segment in the context, and clears them when no FDE covers the IP. DW_CFA_GNU_args_size was parsed and dropped before.

Differences from libunwind are listed in the libc technical debt entry: Windows host frames without an FDE are stepped with RtlVirtualUnwind and report no range; a frame whose rules leave rsp and rip unchanged ends the walk instead of being returned again; a signal frame is looked up at its IP; jumpto does not restore r10 and r11.

Tested

Windows 10 Pro 22H2 (19045), MinGW-w64 GCC 15.2.0 (WinLibs r7), Ninja, Release, BUILD_TESTING=ON, on main eb901bcf4:

  • New unwind_cursor (registered on every platform): captures a context in an exported probe and calls every slot through the table: setInfoBasedOnIPRegister and getInfo against the probe's own address, its FDE and _Unwind_GetRegionStart/_Unwind_GetLanguageSpecificData, getFunctionName (full and truncated), IP 0, step to the exported caller and on to the end of the stack, register names, the destructors, jumpto back to a captured point, and the context _Unwind_Backtrace passes to its callback. Passes, 25 of 25 repeated runs.
  • unwind_cursor_get_register, _set_register, _get_float_register and _set_float_register match libunwind's libunwind: getRegister - unsupported x86_64 register and libunwind: getFloatRegister - no x86_64 float registers lines: pass.
  • Before: the same test binary run against the libc.prx of main stops at the first setInfoBasedOnIPRegister with libunwind cursor: operation other than register access or isSignalFrame not implemented.
  • The 35 exception, unwind, backtrace and signal tests (windows_exceptions, windows_backtrace_*, guest_exception_*, guest_raise_exception, guest_unwind_module_info, agc_stack_walk, linux_eh_frame_header, ...) pass.
  • Full ctest -j 12: 1324 of 1325 pass. agc_shader_disk_cache fails with Access is denied as it does on main on Windows 10 (test(agc): delete mapped shader cache entries with POSIX semantics on Windows #6415). agc_driver_gpu_indirect_arguments_misaligned_writer_ahead_tests does not build with MinGW on main (alignment 65536 above the object file maximum of 8192, test(agc): align the indirect arguments block at run time #6452), so it was excluded.
  • python tools/check_conventions.py --base origin/main passes.
  • Not built or run on Linux or macOS. There getFunctionName uses dladdr and extra the PT_LOAD segment from dl_iterate_phdr; the test exports its probe with ENABLE_EXPORTS.

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file
  • No notes, investigation .md files or images added to the repository (attach them to this PR)
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none
  • Unblocks: none
  • GPUs tested: n/a
  • AI-assisted: yes

@github-actions

github-actions Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Progress report (d6eafa5 → 3551ba1)

📈 System libraries: 43.3% (+0.07%, +11 implemented, -1 removed functions)

✅ 11 implemented
Library Name
libc CursorDestroy
libc CursorGetFloatRegister
libc CursorGetFunctionName
libc CursorGetInfo
libc CursorGetRegister
libc CursorGetRegisterName
libc CursorJumpTo
libc CursorSetFloatRegister
libc CursorSetInfoBasedOnIPRegister
libc CursorSetRegister
libc CursorStep
⚪ 1 behaviourally empty (constant return or throw)
Library Name
libc CursorDestroy
🗑️ 1 removed
Library Name
libc CursorUnsupported

@GitamarT045T3R
GitamarT045T3R force-pushed the feat/libc-unwind-cursor branch from e9fd274 to 3bf85ad Compare October 16, 2026 13:17
@github-actions

github-actions Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

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.

Pull request Shared files not compared
PR 3965 core/libs/prx/libc/CMakeLists.txt
docs/dev/debt/libc.md
PR 5021 core/libs/prx/libc/CMakeLists.txt
docs/dev/debt/libc.md
PR 5303 docs/dev/debt/libc.md
PR 6084 core/libs/prx/libc/CMakeLists.txt
PR 7081 core/libs/prx/libc/CMakeLists.txt
docs/dev/debt/libc.md
PR 7386 docs/dev/debt/libc.md
PR 7447 core/libs/prx/libc/CMakeLists.txt
docs/dev/debt/libc.md
PR 7544 docs/dev/debt/libc.md
PR 7645 core/libs/prx/libc/CMakeLists.txt
PR 7715 core/libs/prx/libc/CMakeLists.txt
docs/dev/debt/libc.md
PR 7851 core/libs/prx/libc/CMakeLists.txt
docs/dev/debt/libc.md
PR 8150 core/libs/prx/libc/CMakeLists.txt
docs/dev/debt/libc.md
PR 8417 docs/dev/debt/libc.md
PR 8965 core/libs/prx/libc/CMakeLists.txt
docs/dev/debt/libc.md
PR 9087 docs/dev/debt/libc.md
PR 9340 docs/dev/debt/libc.md
PR 9371 docs/dev/debt/libc.md
PR 9447 docs/dev/debt/libc.md
PR 9466 core/libs/prx/libc/CMakeLists.txt
docs/dev/debt/libc.md
PR 9526 docs/dev/debt/libc.md
PR 9584 core/libs/prx/libc/CMakeLists.txt
PR 9677 docs/dev/debt/libc.md
PR 9773 core/libs/prx/libc/CMakeLists.txt
PR 9775 core/libs/prx/libc/CMakeLists.txt
PR 10002 docs/dev/debt/libc.md
PR 10007 docs/dev/debt/libc.md
PR 10113 docs/dev/debt/libc.md
PR 10226 docs/dev/debt/libc.md
PR 10274 docs/dev/debt/libc.md
PR 10321 docs/dev/debt/libc.md
PR 10397 docs/dev/debt/libc.md
PR 10506 docs/dev/debt/libc.md
PR 10510 docs/dev/debt/libc.md
PR 10512 docs/dev/debt/libc.md
PR 10569 docs/dev/debt/libc.md
PR 10780 docs/dev/debt/libc.md
PR 10987 docs/dev/debt/libc.md
PR 11109 docs/dev/debt/libc.md
PR 11172 docs/dev/debt/libc.md
PR 11262 docs/dev/debt/libc.md
PR 11266 docs/dev/debt/libc.md
PR 11332 docs/dev/debt/libc.md
PR 11625 core/libs/prx/libc/CMakeLists.txt
docs/dev/debt/libc.md
PR 11634 docs/dev/debt/libc.md
PR 11661 docs/dev/debt/libc.md
PR 11664 docs/dev/debt/libc.md
PR 11689 docs/dev/debt/libc.md
PR 11698 docs/dev/debt/libc.md
PR 11724 docs/dev/debt/libc.md
PR 11726 docs/dev/debt/libc.md
PR 11831 core/libs/prx/libc/CMakeLists.txt
docs/dev/debt/libc.md
PR 11848 docs/dev/debt/libc.md
PR 11870 docs/dev/debt/libc.md
PR 11895 docs/dev/debt/libc.md
PR 12053 docs/dev/debt/libc.md
PR 12057 docs/dev/debt/libc.md
PR 12184 docs/dev/debt/libc.md
PR 12514 docs/dev/debt/libc.md
PR 12518 docs/dev/debt/libc.md
PR 12574 docs/dev/debt/libc.md
PR 12592 docs/dev/debt/libc.md
PR 12593 docs/dev/debt/libc.md
PR 12594 docs/dev/debt/libc.md
PR 12598 docs/dev/debt/libc.md
PR 12605 docs/dev/debt/libc.md
PR 12622 docs/dev/debt/libc.md
PR 12627 docs/dev/debt/libc.md
PR 12682 core/libs/prx/libc/CMakeLists.txt
docs/dev/debt/libc.md
PR 12688 core/libs/prx/libc/CMakeLists.txt
docs/dev/debt/libc.md
PR 12700 core/libs/prx/libc/CMakeLists.txt
docs/dev/debt/libc.md
PR 12704 docs/dev/debt/libc.md
PR 12717 core/libs/prx/libc/CMakeLists.txt
PR 12849 core/libs/prx/libc/CMakeLists.txt
docs/dev/debt/libc.md
PR 12920 docs/dev/debt/libc.md
PR 12947 docs/dev/debt/libc.md
PR 12965 core/libs/prx/libc/CMakeLists.txt
docs/dev/debt/libc.md
PR 13311 core/libs/prx/libc/CMakeLists.txt
PR 13312 core/libs/prx/libc/CMakeLists.txt
PR 13313 core/libs/prx/libc/CMakeLists.txt
PR 13314 core/libs/prx/libc/CMakeLists.txt
PR 13316 core/libs/prx/libc/CMakeLists.txt
PR 13317 core/libs/prx/libc/CMakeLists.txt
PR 13318 core/libs/prx/libc/CMakeLists.txt
PR 13319 core/libs/prx/libc/CMakeLists.txt
PR 13320 core/libs/prx/libc/CMakeLists.txt
PR 13381 core/libs/prx/libc/CMakeLists.txt
PR 13390 core/libs/prx/libc/CMakeLists.txt
docs/dev/debt/libc.md
PR 13449 core/libs/prx/libc/CMakeLists.txt
docs/dev/debt/libc.md
PR 13794 docs/dev/debt/libc.md
PR 13804 docs/dev/debt/libc.md
PR 13809 core/libs/prx/libc/CMakeLists.txt
docs/dev/debt/libc.md
PR 13828 docs/dev/debt/libc.md
PR 14212 docs/dev/debt/libc.md
PR 14298 core/libs/prx/libc/CMakeLists.txt
docs/dev/debt/libc.md
PR 14588 docs/dev/debt/libc.md
PR 14595 docs/dev/debt/libc.md
PR 14610 docs/dev/debt/libc.md
PR 14915 docs/dev/debt/libc.md
PR 15095 core/libs/prx/libc/CMakeLists.txt
docs/dev/debt/libc.md
PR 15206 docs/dev/debt/libc.md
PR 15236 docs/dev/debt/libc.md
PR 15336 core/libs/prx/libc/CMakeLists.txt
docs/dev/debt/libc.md
PR 15484 core/libs/prx/libc/CMakeLists.txt
docs/dev/debt/libc.md
PR 15486 docs/dev/debt/libc.md
PR 15490 core/libs/prx/libc/CMakeLists.txt
docs/dev/debt/libc.md
PR 15501 core/libs/prx/libc/CMakeLists.txt
docs/dev/debt/libc.md
PR 15519 core/libs/prx/libc/CMakeLists.txt
docs/dev/debt/libc.md
PR 15535 core/libs/prx/libc/CMakeLists.txt
docs/dev/debt/libc.md
PR 15552 docs/dev/debt/libc.md
PR 15561 core/libs/prx/libc/CMakeLists.txt
docs/dev/debt/libc.md
PR 15583 core/libs/prx/libc/CMakeLists.txt
PR 15602 core/libs/prx/libc/CMakeLists.txt
docs/dev/debt/libc.md
PR 15614 core/libs/prx/libc/CMakeLists.txt
docs/dev/debt/libc.md
PR 15683 core/libs/prx/libc/CMakeLists.txt
docs/dev/debt/libc.md
PR 15713 docs/dev/debt/libc.md
PR 15714 docs/dev/debt/libc.md
PR 15768 docs/dev/debt/libc.md
PR 15773 docs/dev/debt/libc.md
PR 15791 core/libs/prx/libc/CMakeLists.txt
docs/dev/debt/libc.md
PR 15796 docs/dev/debt/libc.md
PR 15800 core/libs/prx/libc/CMakeLists.txt
core/libs/prx/libc/src/exception/Unwind.cpp
docs/dev/debt/libc.md
PR 15801 core/libs/prx/libc/CMakeLists.txt
core/libs/prx/libc/src/exception/Unwind.cpp
docs/dev/debt/libc.md
PR 15802 core/libs/prx/libc/CMakeLists.txt
core/libs/prx/libc/src/exception/Unwind.cpp
docs/dev/debt/libc.md
PR 15803 core/libs/prx/libc/CMakeLists.txt
core/libs/prx/libc/src/exception/Unwind.cpp
docs/dev/debt/libc.md
PR 15804 core/libs/prx/libc/CMakeLists.txt
core/libs/prx/libc/src/exception/Unwind.cpp
docs/dev/debt/libc.md
PR 15807 core/libs/prx/libc/CMakeLists.txt
core/libs/prx/libc/src/exception/Unwind.cpp
docs/dev/debt/libc.md
PR 15809 core/libs/prx/libc/CMakeLists.txt
docs/dev/debt/libc.md
PR 15834 core/libs/prx/libc/CMakeLists.txt
docs/dev/debt/libc.md
PR 15879 core/libs/prx/libc/CMakeLists.txt
docs/dev/debt/libc.md
PR 15923 core/libs/prx/libc/CMakeLists.txt
PR 15973 docs/dev/debt/libc.md
PR 16004 docs/dev/debt/libc.md
PR 16017 docs/dev/debt/libc.md
PR 16107 core/libs/prx/libc/CMakeLists.txt
docs/dev/debt/libc.md
PR 16170 docs/dev/debt/libc.md
PR 16178 core/libs/prx/libc/CMakeLists.txt
docs/dev/debt/libc.md
PR 16210 docs/dev/debt/libc.md
PR 16214 core/libs/prx/libc/CMakeLists.txt
docs/dev/debt/libc.md
PR 16216 docs/dev/debt/libc.md
PR 16412 core/libs/prx/libc/CMakeLists.txt
PR 16416 core/libs/prx/libc/CMakeLists.txt
PR 16418 core/libs/prx/libc/CMakeLists.txt
PR 16428 core/libs/prx/libc/CMakeLists.txt
PR 16435 core/libs/prx/libc/CMakeLists.txt
PR 16438 core/libs/prx/libc/CMakeLists.txt
PR 16457 core/libs/prx/libc/CMakeLists.txt
PR 16516 core/libs/prx/libc/CMakeLists.txt
docs/dev/debt/libc.md

@jotitadev jotitadev left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Native Linux run on exact head 3bf85ad5c71a2feb54df83cbd5eb5673ed539bff, merged on main 82ddae75b.

Machine: Intel i7-8700 (6C/12T), Ubuntu 26.04, GCC 15.2, CMake 4.2.3, Ninja, Release, BUILD_TESTING=ON, full build.

The four abort tests fail on Linux. unwind_cursor_get_register, _set_register, _get_float_register and _set_float_register print the expected line (libunwind: getRegister - unsupported x86_64 register, ...), but std::abort() ends the process with SIGABRT here, and CTest reports a signal as Subprocess aborted***Exception and fails the test even though PASS_REGULAR_EXPRESSION matches. On Windows _set_abort_behavior turns abort() into exit code 3, which is why they pass there. The other abort-plus-regex tests in the tree (guest_call_trace_abort, windows_backtrace_*) are registered only under if(WIN32); these four are registered on every platform.

With a SIGABRT handler that exits normally on non-Windows, all four pass. I tested only this change:

#ifdef _WIN32
    _set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT);
#else
    std::signal(SIGABRT, [](int) { std::_Exit(3); });
#endif

(plus #include <csignal>). Registering the four tests under if(WIN32) would also work, but then the abort paths would go untested on Linux.

Other results:

  • unwind_cursor passes, 25 of 25 repeated runs. This covers the Linux paths you could not run: getFunctionName through dladdr, extra from the PT_LOAD segment, step to the end of the stack, and jumpto.
  • The same unwind_cursor_tests binary run against main's libc.prx aborts with libunwind cursor: operation other than register access or isSignalFrame not implemented, so the test does cover the change.
  • Non-GPU suite (ctest -E "^agc|gpu|vulkan|video_out"): 649/655. The six failures are the four above plus file_writer_binary/file_writer_text, which also fail on main on this machine (648/650). All the other exception, unwind and backtrace tests pass.

Not checked: macOS.

Written with AI assistance (Claude Code). I ran the builds and tests above on my machine.

@GitamarT045T3R

Copy link
Copy Markdown
Contributor Author

@jotitadev thank you for the Linux run, and for pinning down why the four abort tests failed there.

5514a85 adds the SIGABRT handler you tested (std::_Exit(3) outside Windows, <csignal>), so the abort paths stay covered on Linux instead of moving the tests under if(WIN32). On Windows unwind_cursor and the four abort tests still pass. The same commit moves the test registration before guest_strings, which removes the overlap with #6079; the remaining overlap with #6541 is the adjacent debt entry in docs/dev/debt/libc.md and resolves either way.

@jotitadev jotitadev left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-ran on exact head 5514a8579ed9a27b2d903d9e9c4d34f5d6548b3a, merged on main 82ddae75b. Same machine as before: Intel i7-8700, Ubuntu 26.04, GCC 15.2, CMake 4.2.3, Ninja, Release, BUILD_TESTING=ON, full build.

  • unwind_cursor and the four abort tests (unwind_cursor_get_register, _set_register, _get_float_register, _set_float_register) now pass on Linux, 25 of 25 repeated runs each (ctest -R unwind_cursor --repeat until-fail:25). The abort tests print the expected libunwind: ... unsupported x86_64 register line and exit normally through the new handler.
  • Non-GPU suite (ctest -E "^agc|gpu|vulkan|video_out" -j6): 653/655 with this PR; main 647/650 on the same machine. The two failures are file_writer_binary/file_writer_text, which also fail on main here. The third main failure was a guest_pthread_start timeout under load; it passes 5 of 5 when run alone.

Not checked: macOS, Windows.

Written with AI assistance (Claude Code). I ran the builds and tests above on my machine.

@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 19, 2026
@adriadam10 adriadam10 self-assigned this Oct 19, 2026
@adriadam10
adriadam10 force-pushed the feat/libc-unwind-cursor branch from 5514a85 to b178ec5 Compare October 19, 2026 10:02
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 19, 2026

@jotitadev jotitadev left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-ran on the new head, since b178ec5 changes the Linux-only path and the PR says it wasn't run on Linux.

Exact head b178ec5dedd774153631e0504684b8e1e1a58505, merged on main 674c25388. Linux x86_64, i7-8700, Ubuntu 26.04, GCC 15.2, CMake 4.2.3, Ninja, Release, BUILD_TESTING=ON, full build.

  • unwind_cursor and the four abort tests pass, 10 of 10 repeated runs. The 15 unwind|exception|backtrace tests pass.
  • Full non-GPU suite: 781/781.
  • With b178ec5 reverted on the same merge, unwind_cursor fails with unwind cursor check failed: getInfo gives the start of the segment holding the function. So the commit is needed on glibc now that main takes the _dl_find_object path (2a12dbd, which landed after my run on 5514a85), and the test covers it.

Written with AI assistance (Claude Code). I ran the builds and tests above on my machine.

@nikowolf123

Copy link
Copy Markdown
Contributor

docs/dev/debt/libc.md line 87: this PR closes the gap "the other slots throw", but the entry is rewritten to describe the new slots: the aborts in getReg/setReg/getFloatReg/setFloatReg, the destructors and every getInfo field, all of which match libunwind. What is still open comes only after that: the differences, and that the console's libSceLibcInternal is not checked. CONTRIBUTING.md line 73 asks for the gap first and says a pull request that closes a gap "removes its entry, or cuts it to what is still open, instead of rewriting it to describe the corrected behaviour".

The branch needs a rebase for the conflict in this file anyway (main added the RemoveTree entry just above it). After the rebase, the debt-length check that main gained in #6757 flags the entry. I merged this head into main d97361bd0, kept both entries and ran python3 tools/check_conventions.py --base d97361bd0:

docs/dev/debt/libc.md:99: warning: [debt-length] Technical debt entry over 1000 characters: state the gap first (what differs from the console, what is unverified, what throws) and keep only the behaviour it needs: 1938 characters

Cutting the current text down to its open parts gives 875 characters, and the check passes with it:

- The [slots](../../../core/libs/prx/libc/src/exception/Unwind.cpp) of the libunwind `AbstractUnwindCursor` table at the start of the [`_Unwind_Context`](../../../core/libs/prx/libc/include/exceptions/Unwind.hpp) that `_Unwind_Backtrace` passes to its callback follow `UnwindCursor<LocalAddressSpace, Registers_x86_64>` of [LLVM 18.1.8](https://github.com/llvm/llvm-project/blob/llvmorg-18.1.8/libunwind/src/UnwindCursor.hpp); the console's own `libSceLibcInternal` is not checked. Differences from LLVM 18.1.8: `step` walks Windows host frames without an FDE with `RtlVirtualUnwind` and `getInfo` reports them with no range; `step` looks a signal frame up at its IP instead of the IP minus one and ends at a frame whose rules leave `rsp` and `rip` unchanged; `getFunctionName` on Windows takes the nearest export at or below the IP; `jumpto` does not restore `r10` and `r11`

Why the table exists is already recorded in db3ec02 and in the PR description, so the entry does not need to repeat it.

@GitamarT045T3R

Copy link
Copy Markdown
Contributor Author

Rebased onto main cecb540a6 on top of @adriadam10's b178ec5, which is kept as 13232cb. Two commits are added:

On Windows unwind_cursor, the four abort tests, the five unwind_callback_abi tests and the Windows exception and backtrace tests pass, 3 of 3. Thank you @jotitadev for the glibc run that shows b178ec5 is needed.

@github-actions github-actions Bot added needs rebase Conflicts with main, rebase needed and removed needs rebase Conflicts with main, rebase needed labels Oct 21, 2026
GitamarT045T3R and others added 6 commits October 22, 2026 10:57
Homebrew built with the ps5-payload-dev SDK links LLVM libunwind
statically and calls through the AbstractUnwindCursor table at the start
of the _Unwind_Context that _Unwind_Backtrace passes to its callback.
Only the register slots and isSignalFrame were answered; getInfo,
setInfoBasedOnIPRegister, step, jumpto, getFunctionName,
getRegisterName, the float registers and the destructors threw, so a
title that asked its libunwind for a frame's procedure info or name, or
stepped or resumed that context, stopped there.

The slots now answer as UnwindCursor<LocalAddressSpace, Registers_x86_64>
of LLVM 18.1.8 does with DWARF unwind information, including its aborts
for unknown and float registers. GetRules records the FDE, the end of
the function, DW_CFA_GNU_args_size and the segment holding the IP, and
Step is split so that the cursor steps with the rules _Unwind_Backtrace
uses.
CTest fails a test that ends with a signal even when PASS_REGULAR_EXPRESSION
matches, so the four abort tests failed on Linux, where abort() raises
SIGABRT; on Windows _set_abort_behavior already turns it into an exit
code. A SIGABRT handler now exits with status 3. The test registration
moves before guest_string_tests, away from where another pull request
adds its test.
Since main's VirtualUnwind records the function start, the language data
and the personality of a host frame, getInfo reports those and only the
end address stays zero.
@GitamarT045T3R
GitamarT045T3R force-pushed the feat/libc-unwind-cursor branch from 0aab507 to 3551ba1 Compare October 22, 2026 08:01
@GitamarT045T3R

Copy link
Copy Markdown
Contributor Author

Rebased onto main d6eafa54d (3551ba1). The conflict was with 726edc5 (MSYS2 GCC runtime builds), which changed VirtualUnwind to take the Frame and record the start, language data and personality of a Windows host frame. This PR keeps that version: NearestExport stays in front of it, and FrameRules no longer sets context.cfa itself, since VirtualUnwind now does. The debt entry keeps main's new Windows unwinder line. One added commit, 3551ba1, says getInfo gives host frames no end address instead of "no range", because they now have a start.

On Windows, the 16 unwind_*, windows_backtrace_* and windows_exceptions tests pass and check_conventions passes.

@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 22, 2026

@MarcZX10 MarcZX10 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed current head 3551ba1 against LLVM libunwind 18.1.8 and the changed call paths. The 15-entry cursor slot order and x86_64 register mapping align with the reference; carries the FDE range, LSDA/personality, DWARF format, FDE pointer/size, segment base and parsed ; , end-of-stack handling, IP re-lookup, and the Windows host-frame fallback are consistent with the documented scope. The added cursor test exercises slot behavior, frame traversal, info/name queries, , and abort cases. Current hosted checks pass for conventions, eligibility, labels, macOS and Linux register-context/relinker matrices, ASan, and write-watch. The repository's full Linux and Windows product jobs are skipped by policy. The PR also documents remaining Windows/IP/jumpto gaps and lack of console-side verification.

@MarcZX10

Copy link
Copy Markdown
Contributor

Clarification to my approval summary: the intended references are getInfo (proc-info metadata), DW_CFA_GNU_args_size (the parsed value exposed through gp), step (frame traversal), and jumpto (register restoration and transfer). My review conclusion is unchanged: these behaviors match the PR's stated LLVM 18.1.8 reference within the documented platform limitations.

@jotitadev jotitadev left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merge-skew check: head 3551ba114 merged onto current main abed03546 (clean merge, git merge --no-commit). This head's CI ran on the October 22 base, before #10706 landed. Linux x86_64, i7-8700, Ubuntu 26.04, GCC 15.2, Release, BUILD_TESTING=ON, full build, then the full non-GPU suite (ctest -E "^agc|gpu|vulkan|video_out" -j6).

  • main alone: 1363/1363 pass.
  • With this PR: unwind_cursor and the four unwind_cursor_* tests pass, but public_headers fails 3/3, and its 64 public_headers_compile_* dependents don't run:
    _Unwind_Backtrace_nid_postfix: expected exactly one header declaration; found 2: core/libs/prx/libc/include/Api.hpp:1326, core/libs/tests/UnwindCursor.cpp:17
    _Unwind_GetLanguageSpecificData_nid_postfix: expected exactly one header declaration; found 2: core/libs/prx/libc/include/Api.hpp:1321, core/libs/tests/UnwindCursor.cpp:19
    _Unwind_GetRegionStart_nid_postfix: expected exactly one header declaration; found 2: core/libs/prx/libc/include/Api.hpp:1322, core/libs/tests/UnwindCursor.cpp:18
    
  • Cause: #10706 (35cf8d5 / f38d29f, merged 2026-10-24) added the public_headers audit and canonical declarations of these three exports in libc/include/Api.hpp. The new UnwindCursor.cpp declares them a second time.

Minimal fix I checked: include the canonical header and drop the three local declarations:

--- a/core/libs/tests/UnwindCursor.cpp
+++ b/core/libs/tests/UnwindCursor.cpp
@@ -1,3 +1,4 @@
+#include "prx/libc/include/Api.hpp"
 #include "prx/libc/include/exceptions/Unwind.hpp"
@@
-extern "C" _Unwind_Reason_Code APS5_VABI _Unwind_Backtrace_nid_postfix(_Unwind_Trace_Fn trace, void* argument);
-extern "C" std::uintptr_t APS5_VABI _Unwind_GetRegionStart_nid_postfix(_Unwind_Context* context);
-extern "C" std::uintptr_t APS5_VABI _Unwind_GetLanguageSpecificData_nid_postfix(_Unwind_Context* context);

The rest of the test compiles unchanged. With it, the full non-GPU suite passes 1368/1368, including public_headers, all public_headers_compile_* and the five unwind_cursor* tests.

The head still merges cleanly onto the newer main 92bfca720. Since abed03546, nothing there has touched the audit script, libc/include/Api.hpp or the tab bit.

Written with AI assistance (Claude Code). I ran the builds and tests above on my machine.

@blawesom

Copy link
Copy Markdown
Contributor

Static review plus an independent Linux run (CachyOS, kernel 7.2.10, GCC 16.2.1, Ninja 1.13.2, Release, BUILD_TESTING=ON, Ryzen 7 9800X3D). No findings so far; two runs still filling in below.

What I checked (static)

  • _Unwind_Context grew regionEnd, frameEntry, frameEntrySize, argumentsSize, segment, infoMissing — matching the LLVM libunwind AbstractUnwindCursor::getInfo field shape the PR body cites (db3ec02c5), populated in DecodeCandidate and ClearInfo.
  • The old flat 15-slot CursorSlots was deleted and rebuilt later in Unwind.cpp, in the order destroy, destroy, valid-register, get-register, set-register, valid-float, get-float, set-float, step, get-info, jump-to, is-signal-frame, get-function-name, set-info-based-on-ip, get-register-name — the observed vtable order.UnwindCursorSlots_nid_no_patch remains defined even after the flat table is replaced (found at Unwind.cpp:753 and Unwind.cpp:855 on head 3551ba114c), one per #ifdef branch, so no unresolved routing.
  • The Caller/GetRules/Step refactor preserves the old Step's movement checks (a step that does not advance pc/sp is a stop, not a loop), and GetRules/Caller split lets CursorStep use the same logic with a different shape. Rules::argumentsSize (DWARF DW_CFA_def_cfa_expression's case 46 ULEB) drops into CursorGetInfo's gp field, matching the libunwind procedure-info contract.

Test runs (PR head 3551ba114c)

  • ctest -R 'unwind_cursor' — set_float/setRegister/getRegister+getFloatRegister (5 tests): 5/5 Passed.
  • ctest -R 'exception|unwind' full surface (19 tests on head, 15 on main at base): 100% Passed both — host_libstdcxx_exception and guest_unwind_module_info_failure initially Not Run (binaries not built), rebuilt, rerun, green.

Baseline comparison (main bb04a4f1ea): ctest -R 'exception|unwind' 15/15 Passed (subset of head's 19 — same tests minus the 4 new unwind_cursor* fixtures added here). No regressions on shared unwind surface, no errors in either run. host_libstdcxx_exception and guest_unwind_module_info_failure binaries rebuilt and rerun: full set now 19/19 Passed on the PR head.

Not covered (closing next)

  • Baseline main comparison for the same fixtures (AGENTS does not require it for libc/non-GPU changes per policy — this is a plain paired-run sanity check).
  • Windows host NearestExport/PE export path and macOS dladdr/fallback branch, untested here (Linux host only)

@fogydes

fogydes commented Oct 30, 2026

Copy link
Copy Markdown
Contributor

Verdict: should-fix

  1. [verified] Freshness & Clean Merge: Head commit 3551ba114cf3000200e39d574fc2f774f5e4f99f merges cleanly with origin/main (git merge-tree --write-tree origin/main pull/6819 exited 0 with clean tree e06ab25e0feb88c018540a52a7306fa3f324c73d), despite being 8,475 commits behind origin/main.
  2. [verified] Conventions & Commits: python tools/check_conventions.py --base d6eafa54d0464258574fd2f71889754d05a5bc93 passed with 0 findings in an isolated worktree. All 6 commits adhere to Conventional Commits formatting, and the PR title matches the primary feature commit subject.
  3. [read-only] Checklist Template Compliance: The checklist in the PR description omits the - [ ] Fixes: <!-- issue it closes as Fixes #N, or none --> line from .github/pull_request_template.md. Per repository conventions, all template lines must be retained and ticked (e.g., - [x] Fixes: none).
  4. [author-reported] Validation & Execution: Author reported 25/25 runs passing for the new unwind_cursor test suite on Windows 10 (MinGW-w64 GCC 15.2.0) alongside 35 exception/unwind/signal tests, with independent Linux test verification reported by blawesom.

Freshness Envelope

  • Head SHA: 3551ba114cf3000200e39d574fc2f774f5e4f99f
  • Merge-base: d6eafa54d0464258574fd2f71889754d05a5bc93
  • Behind count: 8,475 commits behind origin/main
  • Merge-tree result: exit 0 (clean tree e06ab25e0feb88c018540a52a7306fa3f324c73d)

Cross-PR Ledger

@sherySJ

sherySJ commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

@GitamarT045T3R are you still working on this? MarcZX10 approved the head on 24 October. Two items are left: public_headers fails on a merge with main because core/libs/tests/UnwindCursor.cpp:17-19 redeclares exports that libc/include/Api.hpp already declares (jotitadev, 27 October), and docs/dev/debt/libc.md now conflicts.

If you are not, I would like to carry it as a separate PR with your six commits (and adriadam10's) kept: rebase onto main, include the public header and drop the local declarations in the test, resolve the debt conflict, add the missing Fixes: checklist line, and re-run unwind_cursor, the four unwind_cursor_* abort tests and public_headers on Linux. If you push first I will drop mine.

@B-Howell

B-Howell commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

@GitamarT045T3R Main moved and this PR conflicts in docs/dev/debt/libc.md. I merged current main (b11c7bf) into your head (3551ba1) as 0726762 on https://github.com/B-Howell/AnyPS5/tree/unstick/pr-6819, keeping both sides in docs/dev/debt/libc.md. unwind_cursor* and unwind_callback_abi_* pass 3/3 on Windows and Linux. You can fetch it and fast-forward your branch: git fetch https://github.com/B-Howell/AnyPS5 unstick/pr-6819 && git merge --ff-only FETCH_HEAD. AI-assisted: yes.

@sherySJ

sherySJ commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

@GitamarT045T3R, @B-Howell merged current main into this PR as 0726762. On top of it there is one commit, d83b892, on https://github.com/sherySJ/AnyPS5/tree/agent/anyps5-carry-6819 that applies jotitadev's public_headers fix: UnwindCursor.cpp includes prx/libc/include/Api.hpp and drops the three local _Unwind_*_nid_postfix declarations that #10706 now declares canonically.

To take it:

git fetch https://github.com/sherySJ/AnyPS5 agent/anyps5-carry-6819 && git merge --ff-only FETCH_HEAD

Tested on Linux only (Windows not tested): public_headers fails on 0726762 with "expected exactly one header declaration; found 2" for the three exports, and passes with the commit. unwind_cursor* and unwind_callback_abi* (10 tests) pass, and tools/check_conventions.py passes.

With both pieces the two open items are resolved, so we will not open a separate carry PR.

AI-assisted: yes.

This branch has not been deployed

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

Labels

build docs libc needs rebase Conflicts with main, rebase needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants