Skip to content

fix(libkernel): lay out user contexts as the console's getcontext writes them - #15794

Open
root-over wants to merge 4 commits into
boykopovar:mainfrom
root-over:fix/libkernel-console-ucontext-layout
Open

root-over wants to merge 4 commits into
boykopovar:mainfrom
root-over:fix/libkernel-console-ucontext-layout

Conversation

@root-over

Copy link
Copy Markdown
Contributor

What

Two catalog Xbox 360 ports by the same author, Naruto: Rise of a Ninja (PPSA99680) and Forza Horizon (PPSA99690), crash about 2 s after start on main with a jump to 0xa3/0xad.

Both build their fibers without makecontext (port patch, Fiber::Create):

  1. They call getcontext into a 2 KiB buffer.
  2. They store the entry stack at offset 0xf8, 0 at 0x88 and the entry function at 0xe0.
  3. They switch with swapcontext.

The same patch records what firmware 9.00 does: "getcontext reports mc_len=1152 and writes 1216 bytes".

That is the layout AnyPS5's exception path already uses (GuestUcontext in Exception.cpp): mcontext at 0x40, mc_rip at 0xe0, mc_rsp at 0xf8, sizeof(GuestMcontext) 0x480 (1152), uc_link at 0x4c0 (1216). getcontext, setcontext, swapcontext and makecontext instead used FreeBSD 11's layout: mcontext at 0x10, 800 bytes, mc_rip at 0xb0. So swapcontext resumed the fiber at getcontext's old return address, on a stack frame that had already returned. That frame's saved r13 had since been overwritten by a return address, and the next call *%r13 crashed. I traced it with gdb, single-stepping from the last pthread_mutex_unlock to the crash: getcontext at 0x4abf7d, swapcontext from 0x12a5e9a, then a second return at 0x4abf82.

Four commits:

  1. fix: UserMcontext and UserUcontext take the console layout:

    • lbrfrom, lbrto, aux1 and aux2 after mc_ownedfp;
    • an 832-byte mc_fpstate at mcontext + 0x100, still holding the 512-byte FXSAVE image at its start;
    • fsbase, gsbase, spare[6];
    • uc_link at 0x4c0.

    guest_user_context checks the new fields in place of mc_xfpustate.

  2. refactor: Exception.cpp uses UserMcontext/UserUcontext through aliases instead of its own copy of the same layout. No behaviour change.

  3. test: guest_user_context sets up a fiber the way the ports do, with raw offsets 0xf8/0x88/0xe0, and switches into it and back three times. It checks mc_len 1152 at 0x108 and that nothing is written from 0x4c0 on.

  4. docs: the debt entry gives the layout and its source, and keeps uc_stack at 0x4c8 (FreeBSD's order after uc_link) as not measured.

The previous layout came from the sizes PPSA99039 (EVO Player)'s OpenSSL ASYNC passes. EVO Player makes no getcontext/makecontext/swapcontext/setcontext call in its first 45 s (gdb breakpoints on all four), and still runs.

Tested

Ubuntu, kernel 7.0, GCC 15.2.0, Release.

Title main this PR
PPSA99680 Naruto: Rise of a Ninja SIGSEGV at 0xa3 after 2 s logs native fiber round-trip check: passed, then looks for /app0/game/naruto.module and exits 0 (no game data here)
PPSA99690 Forza Horizon SIGSEGV at 0xad after 2 s logs native fiber round-trip check: passed, then exits 0 the same way
PPSA99039 EVO Player alive at 30 s alive at 30 s

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
  • Fixes: none
  • Unblocks: PPSA99680 and PPSA99690, no Game bring-up discussion yet (both get past their fiber self-check instead of crashing at start)
  • GPUs tested: n/a
  • AI-assisted: yes

@github-actions github-actions Bot added unblocks title Names a title ID on the Unblocks line libkernel docs labels Nov 4, 2026
@TechLevelZero

Copy link
Copy Markdown
Contributor

✅ AMD GPU test: passed

PR head d7d38c7 merged onto main b11c7bf · tested 2026-11-04 16:54 UTC · 27 min

GPU AMD Radeon RX 6300 (RADV NAVI24), driver Mesa 25.2.8-0ubuntu0.24.04.4, Vulkan 1.4.318
Host Ubuntu 26.04.1 LTS, kernel 7.0.0-42-generic, container ubuntu:24.04
Logs build and test logs for this run
Build ✅ passed (Release, -DANYPS5_ENABLE_SPIRV_TOOLS=ON)
Tests ✅ 3,769 passed · 14 failed (0 new, 12 also fail on main, 2 unstable on main) · 62 skipped (ANYPS5_REQUIRE_VULKAN=1)
Test setup CPU-only tests without a GPU; GPU tests (and any CPU-only test that failed or skipped without one) once on the GPU with VK_LAYER_KHRONOS_validation and ANYPS5_NO_SHADER_CACHE=1; main ran the same way
Test selection 3,845 of 3,894 tests run: the ones this change can reach, chosen by tools/select_tests.py from main's build graph
Vulkan validation ✅ no new validation IDs vs main (1384 tests run on the GPU)
Slow tests (>30 s, GPU tests timed with validation layers) ⚠️ agc_driver_pipeline_journal 120.2 s, ⚠️ agc_driver_graphics_maps_fallback 119.3 s, ⚠️ agc_driver_graphics 108.0 s, ⚠️ public_headers 74.0 s, ⚠️ agc_driver_image_sample_unnormalized 46.0 s
Unstable on main (2, these also failed in recent main runs on this machine; not counted against this PR)
agc_driver_pipeline_journal: failed in 8 of the last 10 main runs
agc_driver_two_sample_alpha_to_coverage_sample_mask_export: failed in 3 of the last 10 main runs
Failing on main too (12, not counted against this PR)
agc_driver_alpha_to_coverage_sample_mask_export
agc_driver_guest_memory_lifetime_direct
agc_driver_image_table
agc_driver_native_depth_bda_clear
agc_driver_native_depth_buffer_clear
agc_driver_native_depth_clears
agc_driver_native_multisample_depth
agc_driver_submit_progress
agc_submit_progress
agc_submit_progress_no_timeline
guest_mmap_executable
guest_socket_options_multicast
Validation IDs already present on main (8 IDs in 802 tests)
VUID-VkDeviceCreateInfo-pNext-pNext (802 tests): agc_compute_pipeline_replay, agc_driver, agc_driver_address_draw_space_reuse, agc_driver_address_draw_space_reuse_held, agc_driver_address_draw_space_reuse_off, agc_driver_address_draw_space_reuse_transitions, agc_driver_address_draw_space_reuse_verify, agc_driver_address_passes, and 794 more
VUID-vkCmdCopyImage-srcImage-01548 (11 tests): agc_driver_depth_clear_hle, agc_driver_depth_plane_cpu_write_after_memo, agc_driver_depth_plane_cpu_write_after_page_unwatch, agc_driver_depth_plane_cpu_write_after_partial_unwatch, agc_driver_depth_plane_partial_store_image_copy, agc_driver_depth_plane_partial_store_unimported, agc_driver_depth_plane_partially_unmapped_plane, agc_driver_depth_plane_unmapped_plane, and 3 more
VUID-vkCmdCopyImage-srcImage-01551 (11 tests): agc_driver_depth_clear_hle, agc_driver_depth_plane_cpu_write_after_memo, agc_driver_depth_plane_cpu_write_after_page_unwatch, agc_driver_depth_plane_cpu_write_after_partial_unwatch, agc_driver_depth_plane_partial_store_image_copy, agc_driver_depth_plane_partial_store_unimported, agc_driver_depth_plane_partially_unmapped_plane, agc_driver_depth_plane_unmapped_plane, and 3 more
VUID-VkPipelineColorBlendStateCreateInfo-pAttachments-00605 (2 tests): agc_driver_prepared_exports, agc_driver_srgb8_color_target
VUID-VkSamplerCreateInfo-addressModeU-01079 (1 test): agc_driver_image_gather_volume
VUID-VkSamplerCreateInfo-addressModeU-parameter (1 test): agc_driver_image_gather_volume
VUID-VkSamplerCreateInfo-addressModeV-parameter (1 test): agc_driver_image_gather_volume
VUID-VkSamplerCreateInfo-addressModeW-parameter (1 test): agc_driver_image_gather_volume
Skipped (62)
agc_driver_bda_reads_shadowed_results: skipped, the device reports watched host memory written when it imports or reads it
agc_driver_bda_reads_shadowed_results_per_build: skipped, the device reports watched host memory written when it imports or reads it
agc_driver_copied_writer_label_order: skipped, the device reports a minStorageBufferOffsetAlignment of 4, so the dispatch binds its record at +0x1004 in place and nothing is written back; the test needs an alignment above 4
agc_driver_copied_writer_label_order_drain: skipped, the device reports a minStorageBufferOffsetAlignment of 4, so the dispatch binds its record at +0x1004 in place and nothing is written back; the test needs an alignment above 4
agc_driver_float16_unused_nan_payloads: skipped, the device has no native f16 conversions with RTE rounding and kept f16 denormals
agc_driver_gcn_display_present: skipped, no display: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
agc_driver_image_table_vouched_lookups: skipped, the device reports 4 of 4 host-imported pages written after a GPU read, so a watched imported table cannot be vouched for
agc_driver_linear_target_direct_write_completion: skipped, the device does not keep host imports write-watched, so no completion stamp over the target is tracked
agc_driver_ngg_vertex_wave_reduction: skipped, s3 describes a 32-lane wave only on 32-lane subgroups, and the device has 64 lanes
agc_driver_scattered_runs: skipped, host imports are unavailable: /dev/udmabuf is not accessible
agc_driver_sparse_dma_buf_import: skipped, host imports are unavailable: /dev/udmabuf is not accessible
agc_driver_unit_shadow: skipped, the device does not import watched host memory for unit shadows
agc_driver_unit_shadow_budget: skipped, the device does not import watched host memory for unit shadows
agc_driver_vertex_elected_atomic_max: skipped, the device runs vertex programs in subgroups, so they keep their cross-lane reductions
agc_host_import_sparse: skipped, host imports are unavailable: /dev/udmabuf is not accessible
agc_idle_publish_wait: skipped, the device reports 4 of 4 host-imported pages written after a GPU read, so a forced watch of the import reads its submissions as CPU stores
agc_import_settle_initial_dcc: skipped, the device has no deferred settle of host imports: imports under 1 MiB wait for one only on Windows
agc_shadowed_key_stores: skipped, the device imports no watched host memory under a unit shadow
agc_small_tiled_shadow_label: skipped, host imports are unavailable: the test block is not imported and watched
agc_storage_clear_hand_over: skipped, the device imports no watched host memory for the clear-key hand-over
agc_storage_pending_key_refresh: skipped, refreshes over a pending key store are not testable here
agc_unit_shadow: skipped, the device does not import watched host memory for unit shadows
agc_unit_shadow_completion_store: skipped, the device reports watched host memory written when it imports or reads it
guest_http_uri_locale: 
guest_net_ctl: skipped, the host has no IPv4 address
guest_video_out_color_space: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_hdr: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_high_frame_rate: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_output_mode: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_resolution_status: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_wqhd_detection: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
video_out_flip_aftergp
Reproduce
git fetch origin d7d38c778685c6e024c3ff2c4c06732a90e96d88 b11c7bf84f5d2f636ff60166cc3371bd071e24d5
git checkout b11c7bf84f5d2f636ff60166cc3371bd071e24d5 && git merge --no-ff d7d38c778685c6e024c3ff2c4c06732a90e96d88
git submodule update --init --recursive
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=ON -DANYPS5_ENABLE_SPIRV_TOOLS=ON
cmake --build build && cmake --build build --target libs
VK_DRIVER_FILES=/nonexistent/icd.json ctest --test-dir build -E '^(agc|shader|recompiler|videoout|video_out|guest_video_out|vulkan)|gpu' --timeout 120 --parallel 12 --output-on-failure
ANYPS5_REQUIRE_VULKAN=1 ANYPS5_NO_SHADER_CACHE=1 VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation \
  ctest --test-dir build -R '^(agc|shader|recompiler|videoout|video_out|guest_video_out|vulkan)|gpu' --timeout 120 --parallel 16 --output-on-failure
# failures are then re-run alone: --timeout 360 --parallel 2; new validation IDs: the test alone, twice, --parallel 1

Automated build and test on a contributor's AMD machine; it does not replace upstream CI. A new comment is posted for each new push. Full logs.

@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Nov 4, 2026
…tes them

getcontext, setcontext, swapcontext and makecontext used FreeBSD 11's
ucontext_t, with mcontext_t at 0x10 and 800 bytes long. The console's is
the layout the exception handler path already uses: mcontext_t at 0x40,
1152 bytes (mc_len 0x480), with lbrfrom, lbrto, aux1 and aux2 after
mc_ownedfp, an 832-byte mc_fpstate, and uc_link at 0x4c0. The Naruto: Rise
of a Ninja and Forza Horizon ports (PPSA99680, PPSA99690) read it so on
firmware 9.00: getcontext reports mc_len 1152 and writes 1216 bytes, and
their fibers set mc_rip at 0xe0, mc_rsp at 0xf8 and mc_rbp at 0x88 after
getcontext. Here swapcontext read mc_rip at 0xb0 instead, resumed the
fiber at getcontext's return address on a stack that had moved on, and
both titles crashed at start.

UserMcontext and UserUcontext now take that layout. getcontext still
copies the 512-byte FXSAVE image to the start of mc_fpstate and zeroes the
new fields; guest_user_context checks those fields in place of
mc_xfpustate and mc_xfpustate_len.
…tion path

Exception.cpp kept its own GuestMcontext and GuestUcontext, the layout
UserContext.hpp now has. It uses UserMcontext and UserUcontext through
aliases, so the exception handlers and getcontext/setcontext cannot drift
apart again. The static assertions on the offsets live in UserContext.hpp.
No behaviour changes.
The Naruto and Forza Horizon ports build their fibers without makecontext:
after getcontext they store the entry stack at offset 0xf8 (mc_rsp), 0 at
0x88 (mc_rbp) and the entry function at 0xe0 (mc_rip) of a 2 KiB buffer,
then switch with swapcontext. guest_user_context now does the same with
raw offsets, checks that getcontext reports mc_len 1152 at 0x108 and
writes nothing from uc_link at 0x4c0 on, and switches into the fiber and
back three times.
…s from

The getcontext entry described FreeBSD 11's layout, taken from the sizes
PPSA99039's OpenSSL ASYNC passes, and called the console's own layout
unverified. It now gives the layout the exception path and getcontext
share, the firmware 9.00 readings from the Naruto and Forza Horizon ports
behind it, and the part that is still not measured: uc_stack after
uc_link. PPSA99039 does not call these functions at start. The FXSAVE
entry names mc_lbrfrom, mc_lbrto, mc_aux1 and mc_aux2, which getcontext
leaves 0, in place of mc_xfpustate and mc_xfpustate_len, which the
console layout does not have.
@root-over
root-over force-pushed the fix/libkernel-console-ucontext-layout branch from d7d38c7 to 90c30d5 Compare November 5, 2026 08:00
@github-actions github-actions Bot added review queue Next in the maintainer review queue: checks pass, no open change request, no conflict and removed needs rebase Conflicts with main, rebase needed labels Nov 5, 2026

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

docs libkernel review queue Next in the maintainer review queue: checks pass, no open change request, no conflict unblocks title Names a title ID on the Unblocks line

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants