Repository navigation
fix(libkernel): start thread stacks on a 16 KiB page boundary on Linux - #9684
NevesHenry wants to merge 7 commits into
Conversation
MarcZX10
left a comment
There was a problem hiding this comment.
Reviewed the Linux stack-bound calculations and thread startup path, including the native-stack reserve and the downward alignment of the guest stack start. The new test covers adopted main and created threads at three stack sizes, verifies the reported bounds against the active frame and sceKernelIsStack, and exercises guard-page protection. I ran the current-main conventions check and git diff --check; both passed. Pairwise merge-tree checks with open #7532 and #7838 are clean, and #9683 is the Game bring-up discussion documenting all three Java 25 titles and this blocker. The PR author reports 547/547 guest tests passing on a supported Linux host plus before/after title runs; local runtime reproduction is not available on this Xeon E5-1620 v2 host because the project CPU guard requires features it lacks. The automatic product jobs are skipped by repository policy. I found no remaining issue requiring changes.
|
Other open pull requests touch the same code. Merging one will break the other, or both implement the same thing. If this one builds on them, list them in Depends on.
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.
|
jotitadev
left a comment
There was a problem hiding this comment.
Ran on native Linux (the PR was tested on WSL2 / glibc 2.35): Ubuntu 26.04, kernel 7.0, glibc 2.43, x86_64 i7-8700, GCC 15.2.0, Release, head d821307 merged --no-commit onto origin/main 7e2ba73.
guest_thread_stack_guard+patched_libraries: 2/2 pass.- Control (
Thread.cppfrommain, test and CMake from the PR):guest_thread_stack_guardaborts, as described. - The PR build also passes with
ulimit -s1024, 8192, 16384, 65536 andunlimited(main thread bounds come frompthread_getattr_np, which depends on that limit).
One thing for the -DANYPS5_ENABLE_ASAN=ON build: guest_thread_stack_guard aborts there at
Require(reinterpret_cast<std::uintptr_t>(&local) > start + GUARD_SIZE && reinterpret_cast<std::uintptr_t>(&local) < start + size);on the main thread, because ASan's use-after-return fake stack puts local on the heap (start=0x7ffdc63e0000, &local=0x7097fc722130). No ASan or UBSan report. It's the same reason guest_kernel_is_stack, guest_pthread_stack, guest_thread_self and guest_kernel_stack_protect have add_asan_test_options(... detect_stack_use_after_return=0). Adding the same line after the set_tests_properties call:
add_asan_test_options(guest_thread_stack_guard detect_stack_use_after_return=0)makes it pass under ASan+UBSan with no reports, as do those three neighbours.
Written with AI assistance (Claude Code). I ran the builds and tests above on my machine.
MarcZX10
left a comment
There was a problem hiding this comment.
A new independent test report found one configuration gap in the added Linux-only guest_thread_stack_guard test. It passes in Release, but aborts in an ASan+UBSan build at the assertion that a local variable lies inside the native main-thread stack. With ASan use-after-return detection enabled, that local can be placed on ASan's fake heap stack instead. The neighboring guest_kernel_is_stack, guest_kernel_stack_protect, guest_thread_self and guest_pthread_stack tests all call add_asan_test_options with detect_stack_use_after_return=0 for this same stack-inspection reason. Please add the same option for guest_thread_stack_guard and rerun it under ASan+UBSan; the independent report says this makes the test pass without sanitizer findings. The exact PR head is unchanged, so the prior code review stands apart from this newly identified test-configuration blocker.
|
Merge-order coordination with #9482: I rebased that pthread lifetime fix onto main Proposing #9482 first, followed by rebasing this PR and keeping both updated debt entries. If this PR is ready first, I can instead rebase #9482 afterward and preserve your stack-alignment text. Please let me know which order you prefer; not treating this as an agreed dependency or copying your changes into my PR. For #9482, Windows focused pthread tests pass 27/27, with 25 attribute-test repeats and a new-main negative control. No Linux/title validation of your stack change is implied. AI-assisted investigation/report. |
|
The proposed order (#9482 first, then rebase #9684 while preserving both debt entries) looks reasonable from the overlap described here; there is no code or test dependency to add. Please coordinate the final order between the two PR authors. Separately, the ASan test-configuration request from my review remains outstanding. |
|
@MarcZX10 @jotitadev thanks. Added Rerun on Linux (Ubuntu 22.04, GCC 13, Release), with
@valenciana-nc fine with #9482 going first. Once it lands I'll rebase this one and keep both debt entries; no AI-assisted (Claude Code); the runs above were made on my machine. |
Dismissing a stale review: the requested changes were addressed after it (new head and/or updated description with the asked-for runs) and the merge queue blocks on it. Please request changes again if anything is still open.
ahmadmuj
left a comment
There was a problem hiding this comment.
Reviewed head cbb1115af0b6 merged onto main 0119103fa9ed (merges clean). WSL2 Ubuntu 24.04, GCC 13.3, i7-13700K. Two builds: Release, and Debug with -DANYPS5_ENABLE_ASAN=ON plus -fsanitize=undefined -fno-sanitize-recover=undefined.
What I ran, on this PR and with Thread.cpp from main as the control:
- Release:
guest_thread_stack_guard,guest_kernel_is_stack,guest_kernel_stack_protect,guest_thread_self,guest_pthread_stack,guest_pthread_join_detach,guest_cond_timedwait,guest_pthread_join_selfall pass.guest_thread_stack_guardpasses 50/50 repeats, and withulimit -s1024, 8192, 65536 and unlimited. On the control it aborts and the other 7 pass. - I also checked the alignment against main's new mmap guest stack (
AllocateGuestStack).nativeStackincludesHOST_STACK_MARGIN, so the aligned-down start stays about 1 MiB above the guard page, whatever the stack size.
ASan: the alloca segfaults guest threads. StartNativeThread sizes the alloca from &stackEnd (Thread.cpp L812). With ASan's default options, use-after-return detection is on. A local's address can then point into ASan's fake stack, as jotitadev saw for local in the test, so frame - stackEnd is no longer a distance on the thread stack. Tests that start a guest thread without detect_stack_use_after_return=0 crash. The three I ran:
| ASan build | guest_pthread_join_detach |
guest_cond_timedwait |
guest_pthread_join_self |
|---|---|---|---|
| this PR | SEGFAULT | SEGFAULT | SEGFAULT |
this PR, ASAN_OPTIONS=detect_stack_use_after_return=0 |
pass | pass | pass |
| control (main's Thread.cpp) | pass | pass | pass |
Taking the frame from the compiler fixes it:
if (const auto frame = reinterpret_cast<std::uintptr_t>(__builtin_frame_address(0)); frame > stackEnd)With only that line changed, all 8 tests above pass in Release and in ASan+UBSan, with no sanitizer report. The three tests above pass 20/20 under default ASan options, and so does guest_thread_stack_guard with its option.
Nit, debt entry. The rewritten line in docs/dev/debt/libkernel.md still ends with "on Linux it cannot change the unmapped lower pages". That is no longer true: since b5909a5, DoMprotect maps a host stack's unmapped pages before protecting them (MapUnmappedPages in DirectMemory.cpp, and the "Protecting a host thread's stack" entry says so). Your own test relies on this: it protects the bottom three pages of the main thread's 8 MiB range, which are unmapped, and it passes. I'd drop that clause.
Not covered: Windows (I didn't build it) and the Java titles (no dumps here).
AI-assisted (Claude Code); I ran the builds and tests above on my machine.
scePthreadAttrGet, pthread_attr_get_np and sceKernelIsStack reported a Linux thread's stack from an address the host chose: a created thread's stack began wherever the host's start frames left it (0x200107f90), and the main thread's at the 4 KiB boundary glibc reports. On the console a stack starts on a page boundary. HotSpot aligns its stack guard zone down to the 16 KiB page before protecting it, so the protect started below the reported stack and failed, and the VM stopped with "memory to guard stack pages". A created thread's stack now starts at the 16 KiB boundary below the requested size and keeps that size; the thread enters it below its top. An adopted host thread's stack starts at the first 16 KiB boundary above the bottom of its host stack. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
With ASan's use-after-return detection on, the test's local variable can live on ASan's fake stack in the heap instead of the native main-thread stack, so the check that it lies inside the reported stack aborts the test in a -DANYPS5_ENABLE_ASAN=ON build. Turn it off for this test, as guest_kernel_is_stack, guest_kernel_stack_protect, guest_thread_self and guest_pthread_stack already do. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
StartNativeThread sized the alloca that moves a created thread below the top of its reported stack from the address of a local. With ASan's use-after-return detection on, that local can live on ASan's fake stack in the heap, so the distance was not one on the thread stack and guest threads crashed in a -DANYPS5_ENABLE_ASAN=ON build with default options. Take the frame from __builtin_frame_address(0), as MeasureStackReserve does. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Since b5909a5 DoMprotect maps a host stack's unmapped pages before protecting them on Linux, so the entry no longer says it cannot change them. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
cbb1115 to
2295675
Compare
|
@ahmadmuj thanks, both points were right.
#9482 is in, so I rebased onto AI-assisted (Claude Code); the runs above were made on my machine. |
|
@MarcZX10 your review was set aside only for the ASan option on AI-assisted (Claude Code). |
jotitadev
left a comment
There was a problem hiding this comment.
Follow-up to my earlier ASan note, on head 229567502: with the add_asan_test_options line in, guest_thread_stack_guard passes under ASan. The guest-thread tests from @ahmadmuj's table pass with ASan's default options.
Machine: Linux x86_64, i7-8700, Ubuntu 26.04, glibc 2.43, GCC 15.2. Head 229567502 merged --no-commit onto main abed03546 (clean).
- Release, full build:
guest_thread_stack_guard3/3. The 64 non-GPUpthread|thread|stacktests pass,patched_librariestoo. - Control,
Thread.cppfrommainwith the rest of the PR kept:guest_thread_stack_guardaborts 3/3. - ASan + UBSan (
-DANYPS5_ENABLE_ASAN=ON,-fsanitize=undefined -fno-sanitize-recover=undefined, RelWithDebInfo):guest_thread_stack_guard,guest_kernel_is_stack,guest_kernel_stack_protect,guest_thread_self,guest_pthread_stack,guest_pthread_join_detach,guest_cond_timedwaitandguest_pthread_join_selfpass 8/8, three times, with no sanitizer report.- ctest runs
guest_thread_stack_guardwithdetect_stack_use_after_return=0. Run by hand withdetect_stack_use_after_return=1, it still aborts, so the option is still needed. - The three join/condvar tests run with ASan's default options, with no
detect_stack_use_after_returnoverride. They pass, so the__builtin_frame_address(0)change holds on this toolchain.
- ctest runs
Written with AI assistance (Claude Code). I ran the builds and tests above on my machine.
jotitadev
left a comment
There was a problem hiding this comment.
public_headers fails on this head, both on its own and merged onto current main (b7105f728). All 10 functions declared at the top of the new core/libs/tests/GuestThreadStackGuard.cpp are also declared in core/libs/prx/libkernel/include/Api.hpp:
scePthreadCreate: expected exactly one header declaration; found 2: core/libs/prx/libkernel/include/Api.hpp:559, core/libs/tests/GuestThreadStackGuard.cpp:8
... same for scePthreadJoin, scePthreadAttrInit, scePthreadAttrDestroy, scePthreadAttrGet,
scePthreadAttrSetstacksize, scePthreadAttrGetstack, scePthreadSelf, sceKernelIsStack, mprotect_nid_postfix
#10706 (merged 2026-10-24) added this audit: every export must be declared exactly once, in its library's public header. CI did not catch it because the linux/windows jobs were skipped on this head.
Fix: include the libkernel header in place of SceTypes.hpp and drop the extern "C" block. The header's signatures match the ones in the test:
-#include "SceTypes.hpp"
+#include "prx/libkernel/include/Api.hpp"
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <initializer_list>
-extern "C" {
-int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name);
-...
-int APS5_VABI mprotect_nid_postfix(void* address, std::size_t length, int protection) noexcept;
-}
-With that change the audit passes for this PR. The only remaining error is the sceKernelGetModuleInfoForUnwind one that is already on main from #9263 (#11718 fixes it). The test passes a GCC 15.2 -fsyntax-only check with the test build flags.
Checked on Linux (i7-8700, Ubuntu 26.04, GCC 15.2): core/libs/tests/test_public_headers.py --audit-only on head 229567502, alone and merged onto main b7105f728.
Written with AI assistance (Claude Code). I ran the builds and tests above on my machine.
The test declared ten libkernel exports itself, which the public header audit from boykopovar#10706 rejects: each export must be declared once, in its library's public header. Include prx/libkernel/include/Api.hpp instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@jotitadev thanks, you were right. 2d1d417 does what you suggested: the test includes
AI-assisted (Claude Code). |
|
@NevesHenry a title that needs this: ProsperoCraft (PPSA99874 01.000.117), the catalog's Minecraft Java launcher. Its Java 25 HotSpot runtime
So with #7532 for the main thread, this PR is what lets the JVM guard the stacks of the threads it creates. AI-assisted: yes (Claude Code) |
MarcZX10
left a comment
There was a problem hiding this comment.
Re-review — changes requested
The earlier ASan configuration issue is resolved: the current PR includes detect_stack_use_after_return=0 for the stack-inspection test, the thread-start frame now comes from __builtin_frame_address(0), and the declaration-only test change uses the public libkernel header. The title report is also a useful direct before/after: ProsperoCraft advances from the primordial-thread failure to the created-thread failure with #7532, then completes its 120-second flow with this PR.
I checked exact head 2d1d41779e22266275f103085a77b6a055e1c1a9 against current main f7e84c4cf1609cc10bac95a44cd431805e5ebc8a:
- Current-main merge preview is clean.
tools/check_conventions.pyandgit diff --checkpass.tools/check_main_sync.pyreports 3 changed paths on both sides since merge base504ea3b53:Thread.cpp, libkernelCMakeLists.txt, anddocs/dev/debt/libkernel.md. Main'sThread.cppchanges include thread creation/cancellation and scheduling behavior, so the earlier test runs on main504ea3b53or737f17291ado not cover the current combined thread implementation.- The reported Linux title run, ASan/UBSan run, and 50-repeat test are author-reported; I did not run them locally. Hosted product Linux is skipped.
Please merge current main, preserve the 16 KiB stack-bound behavior alongside the newer thread lifecycle changes, and rerun guest_thread_stack_guard plus the focused stack/thread tests and ASan/UBSan check on that integrated head. The earlier code findings appear addressed; this request is for current-main integration and exact-head validation.
|
@milescoviello thanks for running ProsperoCraft. The before/after with #7532 is in the description now, linked to your comment, and PPSA99874 is under Unblocks. @MarcZX10 I merged
The description has the test filter and the rest of the details. AI-assisted (Claude Code); the runs above were made on my machine. |
XairRrR
left a comment
There was a problem hiding this comment.
Reviewed — approved
On the current head, merged onto current main, the 16 KiB stack start works with the newer thread lifecycle code: the focused stack and thread tests pass in Release and in ASan + UBSan, and guest_thread_stack_guard fails when Thread.cpp comes from main.
What I ran
- Head:
790fe650fb37edeb3a89ef25adde463110e01050 main:5454cd2e455994d992aca39335ce60507eeda1eb. This is also the merge base, so the merged tree is identical to the head tree.- Arch Linux 7.2.11-zen, GCC 16.2.1, Intel Core i9-14900KF, NVIDIA GeForce RTX 4070 Ti SUPER (driver 615.78.08). I worked in a clean worktree with network access disabled.
git fetch -q origin main pull/9684/head:refs/review/9684
git checkout -q --detach origin/main
git merge -q --no-ff --no-edit refs/review/9684
git diff --check origin/main...refs/review/9684
python3 tools/check_conventions.py --base origin/main
# Release (Ninja, BUILD_TESTING=ON)
nice -n 10 cmake --build build -j10 --target libs guest_thread_stack_guard_tests guest_kernel_is_stack_tests \
guest_kernel_stack_protect_tests guest_thread_self_tests guest_pthread_stack_tests guest_pthread_join_detach_tests \
guest_cond_timedwait_tests guest_pthread_join_self_tests guest_pthread_attr_stack_tests guest_pthread_start_tests \
guest_pthread_frame_chain_tests
nice -n 10 ctest --test-dir build -j4 --output-on-failure -R '^(guest_thread_stack_guard|guest_kernel_is_stack|guest_kernel_stack_protect|guest_thread_self|guest_pthread_stack|guest_pthread_join_detach|guest_cond_timedwait|guest_pthread_join_self|guest_pthread_attr_stack|guest_pthread_start|guest_pthread_frame_chain)$'
nice -n 10 ctest --test-dir build --repeat until-fail:50 -R '^guest_thread_stack_guard$'
for s in 1024 8192 65536 unlimited; do (ulimit -s $s && build/tests/guest_thread_stack_guard_tests); done
ctest --test-dir build --output-on-failure -R '^public_headers$'
# ASan + UBSan, separate build directory
cmake -S . -B build-asan -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_TESTING=ON -DANYPS5_ENABLE_ASAN=ON \
"-DCMAKE_CXX_FLAGS=-fsanitize=undefined -fno-sanitize-recover=undefined" \
"-DCMAKE_C_FLAGS=-fsanitize=undefined -fno-sanitize-recover=undefined"
nice -n 10 cmake --build build-asan -j10 --target <the 8 test targets below>
UBSAN_OPTIONS=halt_on_error=1 nice -n 10 ctest --test-dir build-asan -j4 --output-on-failure \
-R '^(guest_thread_stack_guard|guest_kernel_is_stack|guest_kernel_stack_protect|guest_thread_self|guest_pthread_stack|guest_pthread_join_detach|guest_cond_timedwait|guest_pthread_join_self)$'
for t in guest_pthread_join_detach guest_cond_timedwait guest_pthread_join_self; do
ASAN_OPTIONS=detect_stack_use_after_return=1 UBSAN_OPTIONS=halt_on_error=1 build-asan/tests/${t}_tests
done
# Negative control
git checkout origin/main -- core/libs/prx/libkernel/Pthread/src/Thread.cpp
nice -n 10 cmake --build build -j10 --target libs guest_thread_stack_guard_tests
nice -n 10 ctest --test-dir build --output-on-failure -R '^guest_thread_stack_guard$'
git checkout HEAD -- core/libs/prx/libkernel/Pthread/src/Thread.cpp # rebuilt, test passes againResults
| Check | Result |
|---|---|
Merge onto main |
Clean. The head already merges current main. |
git diff --check |
Clean |
tools/check_conventions.py --base origin/main |
All conventions checks passed. |
Release build (libs + 11 test targets) |
OK, no warnings |
| Release: 11 focused stack/thread tests | 11/11 pass, longest 0.40 s |
guest_thread_stack_guard repeated |
50/50 pass |
guest_thread_stack_guard with ulimit -s 1024, 8192, 65536, unlimited |
Exit code 0 for each |
public_headers |
Pass |
| ASan + UBSan: 8 focused tests | 8/8 pass, no AddressSanitizer or runtime error output |
ASan + UBSan: join/detach, cond_timedwait, join_self with detect_stack_use_after_return=1 |
All exit 0, no sanitizer report |
Negative control (Thread.cpp from main) |
guest_thread_stack_guard aborts |
Release focused tests
1/11 Test #2466: guest_pthread_stack .............. Passed 0.04 sec
2/11 Test #2517: guest_pthread_join_self .......... Passed 0.00 sec
3/11 Test #2451: guest_thread_self ................ Passed 0.00 sec
4/11 Test #2458: guest_pthread_attr_stack ......... Passed 0.00 sec
5/11 Test #2459: guest_pthread_frame_chain ........ Passed 0.00 sec
6/11 Test #2446: guest_kernel_is_stack ............ Passed 0.00 sec
7/11 Test #2464: guest_pthread_join_detach ........ Passed 0.05 sec
8/11 Test #2467: guest_thread_stack_guard ......... Passed 0.00 sec
9/11 Test #2449: guest_kernel_stack_protect ....... Passed 0.01 sec
10/11 Test #2468: guest_cond_timedwait ............. Passed 0.07 sec
11/11 Test #2463: guest_pthread_start .............. Passed 0.40 sec
100% tests passed out of 11
ASan + UBSan focused tests
1/8 Test #2446: guest_kernel_is_stack ............ Passed 0.02 sec
2/8 Test #2451: guest_thread_self ................ Passed 0.02 sec
3/8 Test #2467: guest_thread_stack_guard ......... Passed 0.02 sec
4/8 Test #2464: guest_pthread_join_detach ........ Passed 0.08 sec
5/8 Test #2449: guest_kernel_stack_protect ....... Passed 0.08 sec
6/8 Test #2466: guest_pthread_stack .............. Passed 0.06 sec
7/8 Test #2517: guest_pthread_join_self .......... Passed 0.03 sec
8/8 Test #2468: guest_cond_timedwait ............. Passed 0.75 sec
100% tests passed out of 8
ctest runs guest_thread_stack_guard with ASAN_OPTIONS=string_append::detect_stack_use_after_return=0. The three join/condvar tests have no such override, and they also pass when run directly with detect_stack_use_after_return=1.
Negative control
1/1 Test #2467: guest_thread_stack_guard .........Subprocess aborted***Exception: 0.05 sec
0% tests passed, 1 tests failed out of 1
2467 - guest_thread_stack_guard (Subprocess aborted)
Negative control
I restored only core/libs/prx/libkernel/Pthread/src/Thread.cpp from main and kept the new test and the CMake entry. After a rebuild, guest_thread_stack_guard aborted. With the merged Thread.cpp restored and rebuilt, it passes again.
Findings
Blocking
None.
MarcZX10's last review asked for the stack/thread tests and ASan/UBSan to be run again on a head that merges current main. The results above are from such a head (790fe650fb, which merges main 5454cd2e45), and all of those tests pass. They also confirm ahmadmuj's __builtin_frame_address(0) fix and jotitadev's ASan option on this head.
FreeBSD's libthr supports the created-thread layout. _thr_stack_alloc places a page-aligned stack directly above its guard and keeps the requested stacksize_attr, which leaves "unused space above the beginning of the stack" when the size is not a page multiple (thr_stack.c, release/11.4.0). Thread.cpp:930 does the same: it aligns the start down and keeps stackSize. The alloca at Thread.cpp:952-954 then moves the guest frames below the reported end. nativeStack includes the 1 MiB HOST_STACK_MARGIN, so the lower start (at most 16 KiB − 1 lower) stays inside the mapping. This matches ahmadmuj's earlier reading.
Non-blocking
- Windows created threads can still start off a 16 KiB boundary, and the debt file doesn't say so.
Thread.cpp:895still setsstackAddress = high - stackSize.scePthreadAttrSetstacksizeaccepts any size of at least the minimum (Attr.cpp:72-80). So on Windows, a size that is not a multiple of 16 KiB gives the same unaligned start this PR fixes on Linux, and HotSpot's aligned-down guard protect would fail there too. The description says the Windows path is unchanged, which is fine for this PR's scope. CONTRIBUTING.md asks for known gaps to be recorded in technical debt, so consider adding a line for this todocs/dev/debt/libkernel.md, or a follow-up that aligns the Windows start the same way. - The rounding is not limited to Linux, but the wording is.
SetStackFromHost(Thread.cpp:331-339) has no platform guard, and the created-thread change sits in the#else(non-Windows)StartNativeThread, so macOS gets both changes too. The PR title and the debt entry atdocs/dev/debt/libkernel.md:197say "on Linux". Either say "outside Windows" or note that macOS was not tested. This is wording only. The code is correct on any host whose page size divides 16 KiB.
Not verified
- Windows and macOS builds. I built and ran on Linux only.
- The Java 25 titles (PPSA99866, PPSA99867, PPSA99873) and ProsperoCraft. Per the review rules I launched no titles, so the title results are the author's and milescoviello's reports.
- The full build and full test suite. I built
libsand the focused test targets and ran the 11 tests listed above pluspublic_headers. I did not runpatched_librariesor the rest of theguest_suite.
jotitadev
left a comment
There was a problem hiding this comment.
Follow-up on the public_headers failure from my last review: fixed on 790fe650f.
Linux, Ubuntu 26.04, i7-8700, GCC 15.2, Release, full build. Head merged --no-commit onto main f91137142e (clean).
public_headersandguest_thread_stack_guardpass, 3/3 each. The 102 non-GPUpthread|thread|stacktests pass.- Controls: the previous head's
GuestThreadStackGuard.cpp(229567502) makes the audit fail again (scePthreadCreate,scePthreadJoin,scePthreadSelf, ... found 2).Thread.cppfrommainwith the rest of the PR kept makesguest_thread_stack_guardabort, 3/3.
Written with AI assistance (Claude Code). I ran the builds and tests above on my machine.
docs/dev/debt/libkernel.md: main split the sceKernelInstallExceptionHandler entry into the host-fault delivery entries (boykopovar#7192); kept those with this branch's Linux 16 KiB boundary wording of the stack entry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@MarcZX10 about your change request: after your review I merged I reran the tests today on 8989d4f merged onto current
Could you take another look? AI-assisted (Claude Code); the runs above were made on my machine. |
What
On Linux,
scePthreadAttrGet,pthread_attr_get_npandsceKernelIsStackreported a thread's stack starting at an address the host picked. A created thread's stack started wherever the host's start frames ended (for example0x200107f90), and the main thread's started at glibc's 4 KiB boundary. On the console a stack starts on a 16 KiB page boundary.HotSpot (OpenJDK 25) aligns its stack guard zone down to the page size it reads (16384) before protecting it. That protect started below the reported stack,
mprotectrefused it withEINVAL(main thread) orEACCES(created threads), and the VM stopped:The VM's error report then aborted the process with
sysctl_nid_postfix: unsupported MIB 1.21(kern.boottime, read byos::print_os_infofor the report).scePthreadSelf(the main thread, when it doesn't run on the guest main stack from fix(linux): enter the guest entry on a main stack at the console's address #7532) reports its host stack from the first 16 KiB boundary above its bottom.#7532, on
mainsince 2026-11-03, runs a relinked executable's main thread on a stack at the console's address, which starts on a 16 KiB boundary. It doesn't change created threads, which is where ProsperoCraft stops with it (below).Tested
Linux (Ubuntu 22.04, WSL2), Ryzen 9 7900, lavapipe, GCC 13, Release.
Titles, from mateo-cogeanu/ps5-java25-homebrew, unwrapped and relinked with
--trap-sdk-syscall-stubs --registry, rows driven withtools/pad_input.py, run onmain504ea3b. All three report ALL PASS on a PS5 (firmware 13.60):mainCalling real JNI_CreateJavaVMwith the error aboveJNI_CreateJavaVM returned 0,FileIO25 returned normally,DestroyJavaVM completeJNI_CreateJavaVM returned 0,ZipJar25 returned normally,DestroyJavaVM completeSystem.loadLibraryof the embedded JNI fixture,Dependencies fixture returned normally,DestroyJavaVM completeAt
DestroyJavaVMHotSpot still warnsAttempt to deallocate stack guard pages failedfor the main thread; it isn't fatal and the row passes.milescoviello ran ProsperoCraft (PPSA99874, 01.000.117), the catalog's Minecraft Java launcher, on
main750cf6c (comment). With #12436 the JVM stops at start-up with themprotecterror above, in the primordial thread. With #7532 as well, the primordial thread gets past it and the same error moves to theReference Handlerthread, whose stack doesn't start on a page boundary. With #7532 and this PR it runs the full 120 s (#7532 is onmainnow): the Instances screen, the new-instance form and its on-screen keyboard, following pad input.guest_thread_stack_guard: the main thread's and created threads' (64 KiB, 1 MiB, 16 MiB) stacks start on a 16 KiB boundary, keep the requested size, contain the running frame, matchsceKernelIsStack, and their lowest three pages can be protected read/write, none, read/write. It aborts onmainand passes here.maind63781e. The one conflict was indocs/dev/debt/libkernel.md, where feat(libkernel): deliver host faults to sceKernelInstallExceptionHandler handlers #7192 split thesceKernelInstallExceptionHandlerentry: those entries aremain's, the stack entry is this branch's.main'sThread.cppchanges since 5454cd2 (the guest main stack from fix(linux): enter the guest entry on a main stack at the console's address #7532, the Windows GS state, thread references in join and detach) don't move created threads' stacks, and on the guest main stackHostStackLimitsreturns bounds that already start on a 16 KiB boundary, so the rounding changes nothing there. On that head, GCC 11.4:ctest -Rnow selects 172 tests (mainadded 22) and all pass.guest_thread_stack_guardpasses 50/50 repeats, andpublic_headerspasses.setarch -R. With address randomization on, 7 of them pass 20/20, andguest_kernel_stack_protectaborts in 25 of 40 runs at ASan start-up, before the test runs (Shadow memory range interleaves with an existing memory mapping, Workarounds for #837 (Shadow memory range interleaves with an existing memory mapping. ASan cannot proceed correctly. ABORTING.) google/sanitizers#856, with GCC 11's libasan here). It aborts the same way in 25 of 40 runs withThread.cppfrommain; the other 15 runs pass, as with this PR.Thread.cppfrommain:guest_thread_stack_guardaborts, in Release and under ASan.tools/check_conventions.py --base origin/mainandgit diff --checkpass.main5454cd2): the 150 tests then selected byctest -R '^guest_(.*pthread.*|.*thread.*|.*stack.*|cond_.*|.*sem.*|.*sleep_cancel.*|.*tls.*|rwlock_retire|timed_join.*|sigaltstack|kernel_sys.*)$', withoutult_,libressl,net_thread,ampr,savedataand the relinker TLS tests, passed in Release. ASan + UBSan, Debug (-DANYPS5_ENABLE_ASAN=ON,-fsanitize=undefined -fno-sanitize-recover=undefined,UBSAN_OPTIONS=halt_on_error=1), defaultASAN_OPTIONS:guest_thread_stack_guard(which setsdetect_stack_use_after_return=0, like its neighbours),guest_kernel_is_stack,guest_kernel_stack_protect,guest_thread_self,guest_pthread_stack,guest_pthread_join_detach,guest_cond_timedwaitandguest_pthread_join_selfpassed 20/20; of the 150, 146 passed, andguest_libc_threads,guest_posix_sem_post,guest_pthread_rwlock(LeakSanitizer leaks) andguest_force_tls_destructor(UBSan misaligned load atlibSceLibcInternal/Export.cpp:87) failed with the same reports withThread.cppfrommain.ctest -R '^guest_'passed 737/738;guest_runtime_lifecycletimed out in itsconcurrentmode, as it did withThread.cppfrommain.guest_thread_stack_guardpassed withulimit -s1024, 8192, 65536 andunlimited. With the thread start frame taken from a local's address, as before 9c1557e,guest_pthread_join_detach,guest_cond_timedwaitandguest_pthread_join_selfsegfaulted under ASan, as reported in review. The test includesprx/libkernel/include/Api.hppinstead of declaring the libkernel exports itself (2d1d417), as the public header audit from refactor(libs): add full export headers for libs #10706 requires.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)🤖 Generated with Claude Code