Skip to content

fix(libkernel): build the file, module and rtld calls on macOS hosts - #10552

Open
krishalivaibhav wants to merge 12 commits into
boykopovar:mainfrom
krishalivaibhav:fix/macos-libkernel-six
Open

krishalivaibhav wants to merge 12 commits into
boykopovar:mainfrom
krishalivaibhav:fix/macos-libkernel-six

Conversation

@krishalivaibhav

@krishalivaibhav krishalivaibhav commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

What

Builds libkernel on macOS hosts. Six files used Linux-only APIs and did not compile there; each now has a macOS path behind __APPLE__, and the Linux and Windows paths are unchanged:

  • File/src/NativeStat.cpp: st_atimespec/st_mtimespec/st_ctimespec, and a fstatat walk for guest paths that no longer fit PATH_MAX once the host root is prepended.
  • File/src/SharedMemory.cpp: an unlinked temp file instead of memfd_create; descriptors are recognised through F_GETPATH.
  • File/src/Stdio.cpp: statfs from sys/mount.h instead of sys/vfs.h, plus the directory-read and vector-IO differences of Darwin. The O_APPEND positioned-write path is left to fix(libkernel): append positioned writes on an O_APPEND descriptor on macOS #10543.
  • Module/src/DynamicLoader.cpp, Module/src/ModuleInfo.cpp, System/src/Rtld.cpp: dlopen/dladdr handles and Mach-O segment walks instead of link.h/dl_iterate_phdr.

A second commit makes File/src/DirectoryDescriptor.cpp resolve a descriptor's path through fcntl(F_GETPATH) on macOS, where /proc/self/fd does not exist (fdopendir returned ENOTDIR).

A third commit (rebased f023a5e691) gives guest_module_info a macOS expectation: eh_frame must match the image's __eh_frame section from getsectiondata, eh_frame_hdr is 0 (Mach-O has none), and the fixture skips the ELF-only --no-as-needed option and nid_patcher_dynamic_strings test on Apple.

Extracted from iamthehimansh's feat/macos-port (#8658); Co-authored-by on both commits. The 22 explanatory // comments of the branch version were dropped to follow the no-comments rule.

Further commits:

  • e7b3e68721 (was 997f6cfd4, iamthehimansh): long-path stat prefixes are opened with O_SEARCH on macOS, so a directory without read permission can still be walked.
  • 703e5dcf6a (was 458b30a05): sceKernelGetdirentries and HostWrite guard their guest outputs on macOS, with the S_ISDIR check the Linux path has.
  • 49c24af9a8: Rtld.cpp imageContaining returns nullptr on macOS for an address outside every image, as main does on Linux and Windows after fix(libkernel): answer the thread atexit references as measured on a PS5 #10243, instead of throwing.
  • 105e043a7c: named shm_open and named shm_unlink call NotImplemented_nid_no_patch on macOS, as docs/dev/debt/libkernel.md says. The host name /anyps5-shm- plus 40 hex digits is 52 bytes against macOS PSHMNAMLEN 31, and /dev/shm does not exist there. Anonymous shared memory is unchanged.
  • 8d601f9ec4: a debt entry for the macOS module info fields left at 0 (tls_*, init_proc_addr, fini_proc_addr, eh_frame_hdr_*) and for the non-empty Mach-O __eh_frame case the guest_module_info fixture does not cover.
  • 7e9b2f7a68: the macOS NativeIsPipe matches only FIFOs whose fstatfs fails, as pipe() descriptors do, and NativeFstatfs uses it, so a named FIFO is no longer a pipe.
  • 57ec3a52ef: a zero-length readv, writev, preadv or pwritev on macOS checks the descriptor, its access mode and, for the positioned calls, that it is seekable before returning 0, as host Linux does.
  • 3439513045: an unlinked anyps5-shm- file counts as shared memory only in the temporary directory CreateAnonymousFile uses; the not-implemented traces name shm_open and shm_unlink.
  • ea0213b6ac: debt entries for the macOS sceKernelGetdirentries (full re-read, O(n²) paging, index offsets) and for macOS opening every module with RTLD_GLOBAL.

Tested

Rebased onto main 14c99b5 (no conflicts), macOS x86_64 Release on Apple Silicon M2:

  • the seven changed libkernel files (DirectoryDescriptor, NativeStat, SharedMemory, Stdio, DynamicLoader, ModuleInfo, Rtld) compile with 0 errors;
  • Stdio.cpp, SharedMemory.cpp and DynamicLoader.cpp compile with 0 errors at ea0213b6ac; a standalone x86_64 program with the new empty-vector, shared-memory-directory and pipe checks gives the expected result in 12 cases (EBADF, ESPIPE, 0; temporary directory matches, another directory does not; pipe() is a pipe, a named FIFO is not);
  • a standalone x86_64 program linked against the Rtld.cpp and SharedMemory.cpp objects (other symbols stubbed) checks that _sceKernelRtldThreadAtexitIncrement returns -1 and Decrement returns 0 for &local and nullptr, and that named shm_open and shm_unlink on a valid name reach NotImplemented_nid_no_patch. It prints ok.

Not run on the rebased head: the guest_* test programs. On current main they do not link on macOS (libc GuestAllocations.cpp includes <link.h>), so guest_module_info, guest_filesystem, guest_kernel_sys and the other tests below were not re-run; anonymous shared memory was not exercised.

Earlier results on the pre-rebase head (macOS: local integration of main (e7e3e9c) + #10419 + #10366 + #10369 + #7631 + #7804 + #4630 + this PR + #10853): guest_module_info, guest_module_args, guest_libc_threads, guest_statvfs, guest_file_timestamps, guest_pthread_stack, guest_raise_exception, guest_fdopendir, guest_kernel_sys, guest_filesystem passed; guest_aio needs #10543 and guest_dynamic_loader needs #10627.

Linux: Ubuntu 24.04, g++ 13.3.0, x86-64 Release, on main 60465af + the first commit: libkernel builds with no warnings from the six files. The later commits are __APPLE__-only.

Checklist

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

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.

Pull request Conflicting files (lines on main) Same exports Same new files
PR 5021 sceKernelGetdirentries

Exports introduced here that are already present in main:

  • sceKernelGetdirentries

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 4563 core/libs/prx/libkernel/CMakeLists.txt
PR 4630 docs/dev/debt/libkernel.md
PR 5021 core/libs/prx/libkernel/CMakeLists.txt
core/libs/prx/libkernel/File/src/NativeStat.cpp
core/libs/prx/libkernel/File/src/SharedMemory.cpp
core/libs/prx/libkernel/File/src/Stdio.cpp
core/libs/prx/libkernel/Module/src/DynamicLoader.cpp
core/libs/prx/libkernel/Module/src/ModuleInfo.cpp
core/libs/prx/libkernel/System/src/Rtld.cpp
docs/dev/debt/libkernel.md
PR 6149 docs/dev/debt/libkernel.md
PR 6740 docs/dev/debt/libkernel.md
PR 6830 core/libs/prx/libkernel/CMakeLists.txt
PR 6936 core/libs/prx/libkernel/CMakeLists.txt
core/libs/prx/libkernel/File/src/Stdio.cpp
docs/dev/debt/libkernel.md
PR 7088 docs/dev/debt/libkernel.md
PR 7090 core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libkernel.md
PR 7105 docs/dev/debt/libkernel.md
PR 7121 core/libs/prx/libkernel/File/src/Stdio.cpp
docs/dev/debt/libkernel.md
PR 7135 docs/dev/debt/libkernel.md
PR 7149 docs/dev/debt/libkernel.md
PR 7156 core/libs/prx/libkernel/CMakeLists.txt
PR 7192 core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libkernel.md
PR 7201 core/libs/prx/libkernel/CMakeLists.txt
core/libs/prx/libkernel/File/src/DirectoryDescriptor.cpp
core/libs/prx/libkernel/File/src/NativeStat.cpp
core/libs/prx/libkernel/File/src/SharedMemory.cpp
core/libs/prx/libkernel/File/src/Stdio.cpp
core/libs/prx/libkernel/Module/src/DynamicLoader.cpp
core/libs/prx/libkernel/Module/src/ModuleInfo.cpp
core/libs/prx/libkernel/System/src/Rtld.cpp
core/libs/tests/GuestModuleInfo.cpp
PR 7202 core/libs/prx/libkernel/File/src/NativeStat.cpp
PR 7447 core/libs/prx/libkernel/File/src/Stdio.cpp
docs/dev/debt/libkernel.md
PR 7464 docs/dev/debt/libkernel.md
PR 7509 docs/dev/debt/libkernel.md
PR 7530 docs/dev/debt/libkernel.md
PR 7532 core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libkernel.md
PR 7565 docs/dev/debt/libkernel.md
PR 7804 docs/dev/debt/libkernel.md
PR 8041 core/libs/prx/libkernel/CMakeLists.txt
PR 8463 core/libs/prx/libkernel/CMakeLists.txt
PR 8477 core/libs/prx/libkernel/CMakeLists.txt
PR 8497 docs/dev/debt/libkernel.md
PR 8605 docs/dev/debt/libkernel.md
PR 8725 docs/dev/debt/libkernel.md
PR 8916 core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libkernel.md
PR 9019 core/libs/prx/libkernel/CMakeLists.txt
core/libs/prx/libkernel/File/src/SharedMemory.cpp
core/libs/prx/libkernel/File/src/Stdio.cpp
PR 9156 core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libkernel.md
PR 9161 core/libs/prx/libkernel/File/src/Stdio.cpp
PR 9187 core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libkernel.md
PR 9199 docs/dev/debt/libkernel.md
PR 9242 docs/dev/debt/libkernel.md
PR 9263 core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libkernel.md
PR 9284 core/libs/prx/libkernel/CMakeLists.txt
core/libs/prx/libkernel/File/src/Stdio.cpp
docs/dev/debt/libkernel.md
PR 9293 core/libs/prx/libkernel/CMakeLists.txt
core/libs/prx/libkernel/File/src/Stdio.cpp
docs/dev/debt/libkernel.md
PR 9297 core/libs/prx/libkernel/CMakeLists.txt
core/libs/prx/libkernel/File/src/Stdio.cpp
docs/dev/debt/libkernel.md
PR 9302 core/libs/prx/libkernel/CMakeLists.txt
core/libs/prx/libkernel/File/src/Stdio.cpp
docs/dev/debt/libkernel.md
PR 9326 core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libkernel.md
PR 9383 core/libs/prx/libkernel/CMakeLists.txt
PR 9389 core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libkernel.md
PR 9415 core/libs/prx/libkernel/Module/src/DynamicLoader.cpp
PR 9442 core/libs/prx/libkernel/File/src/Stdio.cpp
docs/dev/debt/libkernel.md
PR 9473 core/libs/prx/libkernel/File/src/Stdio.cpp
PR 9491 core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libkernel.md
PR 9515 docs/dev/debt/libkernel.md
PR 9557 core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libkernel.md
PR 9566 core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libkernel.md
PR 9575 core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libkernel.md
PR 9578 docs/dev/debt/libkernel.md
PR 9684 core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libkernel.md
PR 9782 core/libs/prx/libkernel/CMakeLists.txt
PR 9843 docs/dev/debt/libkernel.md
PR 9882 docs/dev/debt/libkernel.md
PR 9938 core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libkernel.md
PR 9947 core/libs/prx/libkernel/CMakeLists.txt
core/libs/prx/libkernel/File/src/DirectoryDescriptor.cpp
docs/dev/debt/libkernel.md
PR 10014 core/libs/prx/libkernel/File/src/Stdio.cpp
PR 10016 core/libs/prx/libkernel/File/src/Stdio.cpp
PR 10025 docs/dev/debt/libkernel.md
PR 10061 docs/dev/debt/libkernel.md
PR 10133 docs/dev/debt/libkernel.md
PR 10192 docs/dev/debt/libkernel.md
PR 10319 docs/dev/debt/libkernel.md
PR 10442 docs/dev/debt/libkernel.md
PR 10543 core/libs/prx/libkernel/File/src/Stdio.cpp
docs/dev/debt/libkernel.md
PR 10558 docs/dev/debt/libkernel.md
PR 10627 core/libs/prx/libkernel/Module/src/DynamicLoader.cpp
PR 10645 core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libkernel.md
PR 10660 core/libs/prx/libkernel/CMakeLists.txt
PR 10666 core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libkernel.md
PR 10725 docs/dev/debt/libkernel.md
PR 10749 core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libkernel.md
PR 10755 core/libs/prx/libkernel/CMakeLists.txt
core/libs/prx/libkernel/File/src/Stdio.cpp
PR 10767 docs/dev/debt/libkernel.md
PR 10789 docs/dev/debt/libkernel.md
PR 10806 core/libs/prx/libkernel/CMakeLists.txt
PR 10809 docs/dev/debt/libkernel.md
PR 10810 core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libkernel.md
PR 10816 core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libkernel.md
PR 10817 core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libkernel.md
PR 10826 docs/dev/debt/libkernel.md
PR 10848 docs/dev/debt/libkernel.md
PR 10855 core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libkernel.md
PR 10856 core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libkernel.md
PR 10898 docs/dev/debt/libkernel.md
PR 10901 docs/dev/debt/libkernel.md
PR 10953 core/libs/prx/libkernel/CMakeLists.txt
PR 10975 core/libs/prx/libkernel/CMakeLists.txt
PR 10979 core/libs/prx/libkernel/CMakeLists.txt
core/libs/prx/libkernel/File/src/Stdio.cpp
PR 11002 core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libkernel.md
PR 11005 docs/dev/debt/libkernel.md
PR 11039 docs/dev/debt/libkernel.md
PR 11042 docs/dev/debt/libkernel.md
PR 11047 core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libkernel.md
PR 11048 core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libkernel.md
PR 11060 core/libs/prx/libkernel/File/src/Stdio.cpp
PR 11085 docs/dev/debt/libkernel.md
PR 11116 docs/dev/debt/libkernel.md
PR 11130 core/libs/prx/libkernel/CMakeLists.txt
PR 11173 docs/dev/debt/libkernel.md
PR 11215 docs/dev/debt/libkernel.md
PR 11219 docs/dev/debt/libkernel.md
PR 11223 docs/dev/debt/libkernel.md
PR 11230 docs/dev/debt/libkernel.md
PR 11236 docs/dev/debt/libkernel.md
PR 11247 docs/dev/debt/libkernel.md
PR 11253 docs/dev/debt/libkernel.md
PR 11257 docs/dev/debt/libkernel.md
PR 11263 docs/dev/debt/libkernel.md
PR 11264 docs/dev/debt/libkernel.md
PR 11267 docs/dev/debt/libkernel.md
PR 11298 core/libs/prx/libkernel/File/src/Stdio.cpp
PR 11311 core/libs/prx/libkernel/File/src/DirectoryDescriptor.cpp
PR 11341 core/libs/prx/libkernel/File/src/NativeStat.cpp
core/libs/prx/libkernel/File/src/Stdio.cpp
docs/dev/debt/libkernel.md
PR 11403 core/libs/prx/libkernel/File/src/SharedMemory.cpp
docs/dev/debt/libkernel.md
PR 11431 docs/dev/debt/libkernel.md
PR 11438 docs/dev/debt/libkernel.md
PR 11446 core/libs/prx/libkernel/CMakeLists.txt
PR 11462 core/libs/prx/libkernel/CMakeLists.txt
PR 11476 docs/dev/debt/libkernel.md
PR 11500 core/libs/prx/libkernel/CMakeLists.txt
PR 11592 docs/dev/debt/libkernel.md

@krishalivaibhav

Copy link
Copy Markdown
Contributor Author

Linux check: Ubuntu 24.04, g++ 13.3.0, x86-64 Release (x86-64 chroot under Rosetta in an Apple Silicon VM), on main 60465af + this commit (cherry-picked cleanly) + #10562. libkernel builds with no warnings or errors from the six files.

Tests: guest_libc_threads, guest_statvfs, guest_file_timestamps and guest_pthread_stack pass. guest_raise_exception (the subprocess aborts) and guest_module_args (relinker assertion first load segment not at file offset 0, ElfMapper.cpp:399) fail the same way on plain main 60465af in this VM, so they come from the VM setup, not from this PR.

@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.

The macOS AppendingWritev fallback has a correctness hole and overlaps directly with the helper in open PR #10543. Here it treats any descriptor with O_APPEND as an appendable regular file and calls writev even when the saved lseek position is negative. For example, an O_APPEND pipe/non-regular descriptor can be written successfully here although pwritev should fail with ESPIPE; if saving the offset fails, the code also performs the write without being able to restore the shared descriptor position. #10543’s WritevAtEnd checks fstat/S_ISREG, refuses the fallback when the offset cannot be saved, and covers the same pwrite/pwritev behavior. Please coordinate this shared path with #10543 (or make this PR depend on/reuse its helper) and preserve the native failure behavior for non-regular or non-seekable descriptors. A regression for an O_APPEND pipe and a regular-file offset-preservation case would cover the boundary.

@maximilianfv

Copy link
Copy Markdown
Contributor

@krishalivaibhav #10627 touches the same loaderError buffer in DynamicLoader.cpp that this PR raises to 4096 bytes on macOS: it removes the limit on every host instead. A reviewer asked us to agree a merge order. Would you be OK with #10627 landing first, so this PR drops its loaderError hunk on its next rebase? If yours merges first, I'll rebase mine.

AI-assisted: yes

@krishalivaibhav

Copy link
Copy Markdown
Contributor Author

@maximilianfv yes, fine with #10627 landing first. I'll drop the loaderError hunk from this PR on its next rebase.

@imellag imellag 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.

Tested head 972a2c6525 on Apple M4 Pro, macOS 27.0.1, Apple clang, x86-64 Release under Rosetta 2. Two points for the rebase.

Conflict in ModuleInfo.cpp. On main 3fcb5e4d76, NativeStat.cpp, SharedMemory.cpp, Stdio.cpp, DynamicLoader.cpp and Rtld.cpp compile with this PR, but ModuleInfo.cpp conflicts. #10230 rewrote sceKernelGetModuleInfoFromAddr:

  • it accepts flags 1 and 2;
  • it leaves st_size 0;
  • it builds a GuestImage on every host for the flags == 2 SystemImage check.

The macOS branch needs the same. Locally I used this, taking the image header from dladdr, which is what the Apple SystemImage reads:

#elif defined(__APPLE__)
    Dl_info symbol{};
    const bool found = Fill(static_cast<std::uintptr_t>(address), result) &&
        ::dladdr(reinterpret_cast<const void*>(address), &symbol) && symbol.dli_fbase != nullptr;
    const GuestImage image{nullptr, found ? reinterpret_cast<std::uintptr_t>(symbol.dli_fbase) : 0};

Test results. With that and the other open macOS build PRs (the same stack as in my review of #7631), guest_dynamic_loader, guest_module_args, guest_libc_threads, guest_statvfs and guest_file_timestamps pass.

guest_module_info aborts at GuestModuleInfo.cpp:68 (info.eh_frame_addr != 0). Apple ld gives the fixture only compact unwind information: otool -l libguest_module_fixture.dylib lists __text and __unwind_info, with no __eh_frame. So the Mach-O branch of Fill leaves the eh_frame fields 0. The eh_frame_hdr check on the next lines can't hold for Mach-O either.

AI-assisted: yes

@krishalivaibhav

Copy link
Copy Markdown
Contributor Author

@MarcZX10 thanks. I removed the macOS AppendingWritev fallback from Stdio.cpp completely (the function, the NativePwrite retry and the sceKernelPwritev call), so this PR no longer touches the O_APPEND positioned-write path, and #10543's WritevAtEnd covers it. I also dropped the loaderError size hunk in favour of #10627, as agreed with maximilianfv.

Since the push I also added a second commit: DirectoryDescriptorPath resolves a descriptor through fcntl(F_GETPATH) on macOS, because /proc/self/fd does not exist there. This is the same change as in iamthehimansh's feat/macos-port, and he is credited as co-author. The branch is rebased on main 5ed5427. That rebase changed sceKernelGetModuleInfoFromAddr: it now follows main's GuestImage flow, with an __APPLE__ dladdr branch.

Tested on M2, x86_64 Release, main + #10419 + #10366 + #10369 + #7631 + #7804 + #4630 + this PR:

@krishalivaibhav

Copy link
Copy Markdown
Contributor Author

@imellag thanks for testing. The rebased head e60d9dfa3 (on main 5ed5427) has the same ModuleInfo.cpp change: it follows main's GuestImage flow and uses an __APPLE__ dladdr branch with dli_fbase as the image, and it leaves st_size 0.

About guest_module_info: agreed. Apple ld emits only __unwind_info for the fixture, so a Mach-O image has no eh_frame for Fill to report. Those checks need a macOS expectation in the test, or a decision on what the fields should hold for Mach-O images. I'd keep that out of this PR and handle it with the module-info fixture work.

AI-assisted: yes (Claude Code)

@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.

Re-reviewed exact head e60d9dfa3a911ebdc0847f229eebae1878e4e487. The earlier O_APPEND fallback concern is resolved by removing that path, and the F_GETPATH addition matches the macOS descriptor-path need. I cannot approve this head yet for two concrete reasons:

  • GitHub currently reports the branch as DIRTY against main; please rebase and rerun checks.
  • The existing cross-platform guest_module_info test still requires nonzero eh_frame and eh_frame_hdr for every non-Windows host. The reported macOS fixture has only __unwind_info, and the new Mach-O Fill path leaves both fields zero. The author’s own test note identifies this mismatch. Please add an explicit macOS expectation/fixture for these fields (or otherwise make the test pass with documented semantics) and run guest_module_info on macOS.

The hosted product jobs are skipped by path policy, so they do not cover this macOS behavior.

@MarcZX10

Copy link
Copy Markdown
Contributor

Thanks for confirming the macOS fixture has no .eh_frame and that the Mach-O Fill path therefore returns zero for eh_frame_addr/eh_frame_hdr. The review request remains open: the shared guest_module_info test currently asserts both are nonzero on every non-Windows host, so this head cannot pass that test on macOS. Please either add an explicit Mach-O/macOS expectation (with a fixture or platform condition that reflects the format) and run guest_module_info on macOS, or document a concrete decision to keep this behavior out of the PR and ensure the test does not fail for this supported configuration. The branch also remains CONFLICTING against current main; please refresh it after the test change so the final checks apply to a mergeable head. I have not attempted a rebase or run a local macOS build.

@krishalivaibhav

Copy link
Copy Markdown
Contributor Author

@MarcZX10 thanks. I rebased onto main e7e3e9c. There were two conflicts: in SharedMemory.cpp I kept main's new Windows named-shared-memory block unchanged and put the macOS branch after it; in GuestModuleInfo.cpp I kept main's new Api.hpp include. All files compile against the new SDK headers.

I added a third commit for guest_module_info:

  • Fill already reports __eh_frame through getsectiondata when the image has one, so ModuleInfo.cpp is unchanged.
  • On macOS the test now requires eh_frame_addr/eh_frame_size to equal what getsectiondata(header, "__TEXT", "__eh_frame") returns for the fixture, and eh_frame_hdr_* to be 0 (Mach-O has no such section). The Linux and Windows expectations are unchanged.
  • The fixture's -Wl,--no-as-needed link option and the ELF-only nid_patcher_dynamic_strings test are now skipped on Apple as well as Windows, so libguest_module_fixture.dylib links on macOS.

Tested on M2, x86_64 Release, main e7e3e9c + #10419 + #10366 + #10369 + #7631 + #7804 + #4630 + this PR + #10853 (O_CREAT on a directory returns EISDIR), with 0 failed objects:

  • pass: guest_module_info, guest_module_args, guest_libc_threads, guest_statvfs, guest_file_timestamps, guest_pthread_stack, guest_raise_exception, guest_fdopendir, guest_kernel_sys, guest_filesystem;
  • guest_dynamic_loader (from the previous run, on main 2dc8560) aborts at GuestDynamicLoader.cpp:66: the 512-byte loaderError buffer truncates the "was not relinked" message because the build path is long. That is what fix(libkernel): keep dlerror messages whole #10627 fixes, so I left it out of this PR as agreed with maximilianfv. With a 4096-byte buffer (local only, not committed) the test passes.

AI-assisted: yes (Claude Code)

@imellag

imellag commented Oct 26, 2026

Copy link
Copy Markdown
Contributor

macOS test report on head ea0213b6ac. Apple M4 Pro, macOS 27.0.1, Apple clang 21, x86_64 Release under Rosetta 2, MoltenVK 1.4.2.

I looked for the smallest set of open PRs that boots titles on macOS from current main (035ac96edd). The set is #10366, #10552, #7631, #7804, #11249, #10669, #10540, #11447, #11047, #11048, #10876 and #9263, plus a local VulkanDevice.cpp patch that no open PR covers yet (it loads libvulkan.1.dylib and enables portability enumeration). With it, Planet of Lana (PPSA19450) and God of War Sons of Sparta (PPSA28997), relinked with that tree (--macos --to-rosetta), start from New Game: Lana reaches gameplay and runs the full 120 s, GoW reaches the Cyclops fight and runs the full 145 s, and both exit cleanly. This head gives the same result as the earlier head 3206fbd270.

This PR is needed: with it removed from that set, the libraries don't build:

core/libs/prx/libkernel/Module/src/ModuleInfo.cpp:26:10: fatal error: 'link.h' file not found

One heads-up: this head now conflicts with the latest main (32b7331c44) in File/src/DirectoryDescriptor.cpp, where 0a64d23bdf ("enumerate archived app directories") changed the same part of the file as this PR's F_GETPATH block.

krishalivaibhav and others added 12 commits October 26, 2026 21:52
NativeStat, SharedMemory, Stdio, DynamicLoader, ModuleInfo and Rtld used
Linux-only APIs (st_atim, memfd_create, sys/vfs.h, link.h and dl_iterate_phdr),
so libkernel did not compile on macOS. Add macOS paths behind __APPLE__:
st_*timespec and a long-path fstatat walk, an unlinked temp file for shared
memory, statfs, dlopen/dladdr and Mach-O segment walks for module and rtld
queries. The Linux and Windows paths are unchanged.

Extracted from feat/macos-port (boykopovar#8658).

Co-authored-by: Himansh Raj <iamthehimansh@gmail.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…on macOS

Co-authored-by: Himansh Raj <iamthehimansh@gmail.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… on macOS

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The long-path fallback in NativeStat opened each intermediate directory with
O_RDONLY | O_DIRECTORY, which needs read permission on it. stat and fstatat
only need search permission, so a path through an execute-only directory
failed with EACCES once it crossed PATH_MAX. O_SEARCH (O_EXEC | O_DIRECTORY
on Darwin) needs only search permission and still rejects a non-directory.
The macOS sceKernelGetdirentries wrote *basep and the entry records into buf
without entering GuestArena::HostWrite, so the write-watch host-write path
never saw those writes. It now opens HostWrite guards for buf over nbytes and
for basep before it moves the descriptor offset, and holds them through every
write, as the Linux path does. It also rejects a non-directory with EINVAL
before the guards, matching Linux.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ed on macOS

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rite on macOS

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tory

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@adriadam10
adriadam10 force-pushed the fix/macos-libkernel-six branch from ea0213b to 6a68918 Compare October 26, 2026 20:57
@adriadam10
adriadam10 dismissed MarcZX10’s stale review October 26, 2026 20:59

Dismissing a stale review: the requested changes were addressed after it (new head) and the merge queue blocks on it. Please request changes again if anything is still open.

@maximilianfv maximilianfv 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-review of 6a689187b. The new commits fix all four items and both smaller points from my review of 8d601f9ec, and the A/B below shows no new failures. I am still requesting changes for three defects, items 1 to 3 below. None of them comes from the new commits. Items 1 and 2 were already there at 8d601f9ec, and item 3 comes from the rebase onto main.

I tested ea0213b6a, and you rebased while my runs were going. Between the two heads only the F_GETPATH commit changed: it is now the HostDescriptorTarget helper, which covers both of main's /proc/self/fd reads. Every other file this PR changes is the same on this tree at both heads (git merge-file of each head onto the tree's main gives identical files). I did not rebuild at 6a689187b, because the new DirectoryDescriptor.cpp needs main's archived-directory changes, which this tree does not have yet.

Earlier items

  1. Zero-length readv/writev/preadv/pwritev: fixed (f0bb0ae73). I called them through lldb on the "with" build:
    • fd -1, writev on a read-only descriptor and readv on a write-only one return EBADF (0x80020009).
    • preadv and pwritev on a pipe return ESPIPE (0x8002001d).
    • Valid descriptors return 0.
  2. anyps5-shm- outside the temporary directory: fixed (88b344f1c). The check uses the same temp_directory_path() as CreateAnonymousFile, and canonical resolves symbolic links the same way F_GETPATH does.
  3. Named FIFOs: fixed (f8bd9c3ae). NativeFstatfs behaves as before. The change a title can see is fchflags on a named FIFO, which now throws, as on Linux.
  4. sceKernelGetdirentries debt entry: added (6a689187b), with the RTLD_GLOBAL question and both trace names.

Tested on Apple M1 Pro, macOS 27.0.1 (x86_64 build under Rosetta 2)

Applied at ea0213b6a to mugurc's macOS series (27e968974) merged with main at 2742004ed2, with other open macOS fixes, then toggled only this PR. As before, the head does not git apply there, so "with" is the file set from my last round plus git diff 8d601f9ec ea0213b6a. The series turns guest_module_info off on Apple, so I turned it back on in both states. Both states build with 0 errors, and the PR's lines add no warnings.

I ran the same 44 tests as last time, three times in each state, one run after another:

guest_filesystem guest_kernel_sys_module_internal guest_module_info guest_shm_open (the series' copy) 44 tests
Without this PR Fails Fails module info check failed at line 34 Passed 36, 36, 36 pass
With this PR Passed Passed Passed Passed 39, 39, 39 pass

The other five failures stop at the same point in both states:

  • guest_aio at :437 (needs #10543)
  • guest_dynamic_loader at :57 (needs #10627)
  • guest_append_writes at :79
  • guest_host_standard_error
  • guest_kernel_app_archive_mmap (mmap on a file read from app0.zar ... not implemented)

guest_refused_connect passed all six runs this time. No log has a Validation Error line.

Findings

  1. core/libs/prx/libkernel/Module/src/DynamicLoader.cpp:423: dlclose unregisters a relinked image under its Mach-O header, but mprotect registered it under its guest load bias.

    • mprotect registers the image under search.base = image->dlpi_addr (DirectMemory/DirectMemory.cpp:1285 and :1369).
    • The macOS dl_iterate_phdr sets dlpi_addr to the __APS5DATA,__meta load bias (libc/src/specifics/macos/GuestImages.cpp:33).
    • The relinker writes that load bias as imageBase + rva(first PT_LOAD) - p_vaddr (relinker/elfpatcher/src/macos/MacOsImage.cpp:511).
    • __TEXT takes the first LoadRva (0x10000) bytes, and GuestAllocations.cpp:336 requires the image's first loaded page to be its base. The key is therefore never the header.
    • ImageBase (:113) returns _dyld_get_image_header(index). UnregisterImage (GuestAllocations.cpp:343) finds no entry under that key and returns without doing anything.
    • Effect: when a title mprotects inside a .guest.prx and then unloads it, the image's pages stay in the allocation registry.
    • The guest_flexible_memory_budget fixture cannot catch this, because it writes dli_fbase as its load bias.
    • Fix: pass the dlpi_addr that dl_iterate_phdr reports for this image, as the macOS series' ImageBase does. Keep the header for the DefaultScopeSymbol comparison with dli_fbase.
    • I found this by reading the code and did not run it: no test here unloads a relinked image.
  2. DynamicLoader.cpp:134-145: DefaultScopeSymbol returns null when the first dlsym(RTLD_DEFAULT) hit is in a local module, even if a global module exports the same name.

    • Example: module A is loaded with flags 0 and exports foo, then module B is loaded with 0x100 and also exports foo. dyld returns A's foo first, the filter drops it, and the lookup returns null.
    • On Linux A is RTLD_LOCAL, so the same lookup returns B's foo.
    • Fix: when the first hit is hidden, try dlsym on each global module's handle in load order. Otherwise, state this limit in the new debt entry (docs/dev/debt/libkernel.md:145).
  3. core/libs/prx/libkernel/File/src/Stdio.cpp:1267: the macOS sceKernelGetdirentries does not have main's directory-descriptor branch.

    • 0a64d23bd (feat(libkernel): enumerate archived app directories) added if (File::IsDirectoryDescriptor(fd)) return File::ReadDirectoryDescriptor(fd, buf, nbytes, basep); to the non-Windows copy. After the rebase, that branch is only in the Linux copy (:1354).
    • On POSIX an archived app0 directory is opened as /dev/null (DirectoryDescriptor.cpp:246). On macOS, getdirentries on it therefore reaches fstat, sees a file that is not a directory, and returns EINVAL instead of the archive's entries.
    • Fix: add the same branch to the macOS copy, after the HostWrite checks.
    • I found this by reading the code: this tree does not have main's archived directories yet.

Smaller points:

  • SharedMemory.cpp:133-135 calls temp_directory_path and canonical (realpath) on every check of an unlinked anyps5-shm- file. A function-local static const would do this once.
  • Body, f0bb0ae73: "as host Linux does" is not quite right. Linux preadv checks that the descriptor is seekable first, so a write-only pipe end gives ESPIPE on Linux and EBADF here. The code's order (EBADF first) is the FreeBSD 11.4 order, so only the wording needs to change.
  • Question: ImageBase throws std::runtime_error (DynamicLoader.cpp:131) when no RTLD_NOLOAD probe returns the module's handle. dlclose_nid_postfix does not catch it, so the exception would leave an extern "C" export. I have not found an input that reaches the throw. If the fix for item 1 stores the base when the module is opened, ImageBase and its throw are no longer needed.

Not checked here: Linux, Windows, a build at 6a689187b, and this PR together with #10543 or #10627.

AI-assisted: yes (Claude Code applied the PR, ran the tests, read the code and wrote this review).

@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 26, 2026
@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 26, 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

build docs libkernel needs rebase Conflicts with main, rebase needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants