AF_UNIX pairs as FreeBSD 11 makes them: SOCK_STREAM, SOCK_DGRAM and
SOCK_SEQPACKET with protocol 0, SOCK_NONBLOCK applied to both ends and
SOCK_CLOEXEC accepted, EAFNOSUPPORT for an unknown family, EOPNOTSUPP
for AF_INET and AF_INET6, EPROTONOSUPPORT for type 0 or a protocol,
EPROTOTYPE for another type, EFAULT for a null output, and nothing
written on failure. The ends are guest sockets, so send, recv,
shutdown, fcntl, poll and close work on them; getsockname and
getpeername report the unnamed AF_UNIX address, recvfrom and recvmsg
no address for a stream and the unnamed one for a datagram. On Linux
the pair is the host's socketpair; on Windows a loopback TCP connection
or two connected UDP sockets, and SOCK_SEQPACKET throws. The remaining
differences from the console are recorded in TechnicalDebt.
OpenSSL's QUIC notifier in 15 catalog titles creates such a pair.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Titles pass handle 0 (RTLD_DEFAULT) for a process-wide lookup, and the loader rejected it: the handle is
not in the module map, so every such lookup failed even when a loaded module exports the symbol. Handle 0
and -2 now take the default scope, searching the global modules by plain name, NID and `#guest` form, and
then the process-wide scope.
The process-wide scope of the real loader also contains glibc and libstdc++, so an unfiltered fallback
would answer `malloc`, `dlopen` and `pthread_create` where PS5 reports the symbol as missing: handle 0 is
the main program, whose lookup sees the program's exports, not the host C++ runtime. A result from the
process scope is therefore accepted only when `dladdr` attributes it to a converted or replacement module
(a `.prx` library or the executable), which keeps the fallback for guest symbols and removes the host
leak. `guest_dynamic_loader` asserts the three host names are not found; disabling the filter makes that
test fail.
The scripting heap that an earlier revision of this branch added has been dropped: the title reaches the
same boot milestone with the lookup failing honestly, so the export was an inference rather than a
requirement, and prosper's bring-up notes report `ESRCH` for that name on hardware.
send, sendto and sendmsg rejected every flag with EOPNOTSUPP, so callers
that pass FreeBSD's MSG_NOSIGNAL (0x20000) could not send at all. Accept
it: Windows sockets never raise SIGPIPE, and on Linux it maps to the
native MSG_NOSIGNAL. Other flags still fail with EOPNOTSUPP.
DOOM (PPSA99666) sends its upload server replies with MSG_NOSIGNAL and
closed every connection without an answer.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Translate the FreeBSD pollfd layout and flags to WSAPoll on Windows and
poll on Linux. Closed socket descriptors report POLLNVAL, a timeout below
-1 fails with EINVAL and a null array with EFAULT. Non-socket
descriptors, unknown event bits, POLLPRI on Windows and an infinite wait
without sockets throw; these are recorded in TechnicalDebt.
DOOM (PPSA99666) imports poll for its upload server.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On macOS the host keeps libc++abi: libc++ binds __cxa_throw two-level,
so moving only the personality into this runtime would leave the two
halves disagreeing on the exception header. The host personalities stay
libc++abi's and libunwind's, and libc.prx serves guest code:
- the guest's libstdc++ std::exception_ptr is modelled directly, since
libc++'s differs, and the Itanium type_info layouts that libc++abi
keeps private are declared for the runtime;
- guest frames are found through their PT_GNU_EH_FRAME, using a
dl_iterate_phdr emulation over the ELF program headers that relinked
images keep in __APS5DATA,__meta (written by relinker --macos, which
comes in a later pull request);
- host frames are found by scanning __TEXT,__eh_frame;
- sceKernelGetModuleInfoForUnwind uses the same emulation and falls back
to dladdr for host images.
The libc++ paths compile only with libc++/libc++abi and the rest only
on __APPLE__, so Linux and Windows builds are unchanged.
exception_c_cleanup throws from host code, which this runtime does not
serve on macOS, so it is not built there.
A condition wait on a recursive mutex locked more than once unlocked the
native mutex only once, so the thread that should signal blocked forever
on it. Unlock the remaining levels before the wait and lock them again
after it, as FreeBSD libthr does in _mutex_cv_unlock and _mutex_cv_lock.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HeC9bA3n74TzJRYv9AgBE8
A thread that sceKernelRaiseException redirects resumes through
RtlRestoreContext with a CONTEXT that has no XSTATE, so the upper halves
of ymm0-ymm15 came back as the handler and the delivery path left them.
A handler or host routine that runs vzeroupper or any VEX instruction
(Zen 2 guest code, the CRT's memcpy) cleared the upper lanes of a thread
interrupted in the middle of AVX code. The entry stub now stores
ymm0-ymm15 next to the delivery when the CPU and the OS enable AVX, and
the exit loads them back right before RtlRestoreContext, which restores
only the low halves.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HeC9bA3n74TzJRYv9AgBE8
sceKernelGetCurrentCpu returned the raw host CPU index. Guest affinities
are bookkeeping only and sceKernelGetAvailableCpumask advertises CPUs 0
to 12, so on a host with more than 13 logical CPUs a title could get 13
or higher, and a thread pinned to one CPU could get another one.
The host index now selects a CPU of the calling thread's affinity
clipped to the available mask, so the result is always an advertised
CPU and a thread pinned to one CPU reports that CPU.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HeC9bA3n74TzJRYv9AgBE8
A thread had one TimedWait waiter for every wait. When
sceKernelRaiseException reached a thread blocked in a TimedWait
condition, its handler ran as an APC inside that wait, and a wait in
the handler queued the same waiter on a second condition while it was
still linked into the first. The links of the first queue were
overwritten, so waiters behind it were never woken, and a wake of the
outer wait set the shared event and was consumed by the inner wait.
A wait now takes the thread's first waiter that is not in use, and
creates a deeper one for a wait nested inside another.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HeC9bA3n74TzJRYv9AgBE8
On Windows each AIO request seeked a _dup of the descriptor and then read
or wrote through it. A duplicated descriptor shares the file position, and
the seek and the transfer are separate calls, so two threads submitting
requests on one descriptor could transfer at each other's offset. Each
request is now a single ReadFile/WriteFile with an OVERLAPPED offset on
the OS handle, as pread and pwrite already do in Stdio.cpp.
GuestAio reads four distinct blocks of one descriptor from four threads
and checks every result.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HeC9bA3n74TzJRYv9AgBE8
Opening a volume needs administrator rights, so the volume flush skipped
every volume in a normal user session. The libc, libkernel, save data and
app content file functions now record each host file and directory they
change. On Windows sceKernelSync flushes the recorded paths that changed
since the previous call, plus their changed parent directories, through
handles a normal user can open. Linux still calls sync().
Gathering every iovec into one host buffer threw for a total above
INT_MAX, which a console readv/writev accepts, and copied the whole
transfer twice. Each iovec is now read or written in place, in chunks
of at most INT_MAX bytes. A readv on a pipe returns after one read so
it does not block once the available bytes are consumed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UTjpbMX7BUFMZZaxJE98zV
sceKernelReadv, sceKernelWritev, sceKernelPreadv and sceKernelPwritev threw on Windows. They now gather or scatter through one host buffer and issue a single read or write, with the same argument checks and error codes as the Linux path. Positioned transfers restore the file offset afterwards, as pread and pwrite do on POSIX, and report ESPIPE for handles that are not disk files. guest_file_vector now runs on Windows as well.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UTjpbMX7BUFMZZaxJE98zV
mprotect changes the protection of guest memory through the same path
as sceKernelMprotect and reports failures through errno: EINVAL for a
null or overflowing range or a range the allocation tracker rejects,
ENOMEM when allocation fails. A zero length succeeds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sceAppContentAppParamGetInt numbers the SKU flag 0 and
USER_DEFINED_PARAM_1..4 as 1..4 (fpPS4 ps4_libsceappcontent). Id 1
returned the SKU flag and 2..5 returned zero. UFC 6 (PPSA23566) checks
that param 1 is its title number, userDefinedParam1 in param.json, and
crashes otherwise. Absent params read as zero.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The patcher rewrites .dynstr in place and reorders .dynsym for
.gnu.hash, but .gnu.version_d kept pointing at name offsets that the
rewrite had recycled: a library carrying symbol version definitions
came out with renamed or blanked version names while its symbols
still advertised a version.
No library in the build defines symbol versions, so fail on
.gnu.version_d during the section scan and leave the input untouched
instead of carrying version remapping that nothing exercises. The
test covers the rejection and that a library without version
definitions is still patched.
Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
libSceNet registers a socket poller with libkernel; select polls the requested descriptors through it until the timeout and rewrites the descriptor sets.
A fixed mapping without no-overwrite unmaps the registered pieces it overlaps before mapping, as it already did when the range lay inside registered allocations.
AMPR events were registered and raised as EVFILT_USER. Titles that
dispatch their AMPR queue by filter ignore those events, so reads that
complete through WriteKernelEventQueue never finish. Add, delete and
trigger them with EVFILT_AMPR (-25).
Once an image has .ehmeta, an eh_frame_hdr version other than 1, an
eh_frame_ptr encoding other than absptr or pcrel, a 64-bit record length,
an eh_frame_ptr outside the image or a missing terminator now throw
instead of returning the empty answer. The test queries a fixture DLL
whose .ehmeta it can break, since the unwinder reads the same section.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>