fcntl and sceKernelFcntl read a third argument as a 64-bit integer for
every command on a non-socket descriptor. F_GETFD and F_GETFL pass no
third argument, and the commands that take one pass an int or a pointer.
They now read nothing, an int or a pointer, by command. An int read as
a 64-bit value kept whatever the caller left in the upper half of the
register, so F_READAHEAD -1 enabled read-ahead instead of disabling it.
ioctl and _ioctl answered EBADF for every descriptor outside the
libSceNet socket table. Decoded call sites: PS5MC (PPSA99420, FIONBIO x3,
FIONREAD x2, TIOCGWINSZ x3), ProsperoTV (PPSA99003, FIONBIO on a pipe
once poll on pipes is in), XPSemu (PPSA97358, 21 sites).
Following FreeBSD 11.4 kern_ioctl (sys/kern/sys_generic.c:758),
vn_ioctl (sys/kern/vfs_vnops.c:1488), pipe_ioctl (sys/kern/sys_pipe.c:1346)
and randomdev_ioctl (sys/dev/random/randomdev.c:301), release/11.4.0
8c639945: FIONBIO sets or clears O_NONBLOCK through the same status
flags as fcntl F_SETFL (EFAULT for a null argument before EBADF);
FIONREAD reports the size past the offset of a file, the queued bytes of
a pipe's read end, 0 on its write end, ENOTTY on the random device, and
throws on a directory or another character device; termios requests
(group 't', TIOCGWINSZ included) fail with ENOTTY, as tcgetattr already
takes these descriptors as not a terminal; any other request throws
naming the request and descriptor kind. Sockets keep their path
unchanged.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
fcntl and sceKernelFcntl answered EBADF (sceKernelFcntl threw) for
every descriptor outside the libSceNet socket table, so files, pipes,
directories and descriptors 0 to 2 could not be queried or changed.
Observed: Yamagi Quake II (PPSA99007) stops with
"Sys_Error: change stdin to blocking Bad file descriptor", from
fcntl(fileno(stdin), F_SETFL, fcntl(0, F_GETFL, 0) & ~FNDELAY)
(yquake2 src/backends/unix/system.c:81-86).
Predicted from the titles' sources and decoded call sites: XPSemu
(PPSA97358, 97 fcntl sites: F_GETFD 12, F_SETFD 20, F_GETFL 8,
F_SETFL 9, F_DUPFD 1, F_DUPFD_CLOEXEC 7, F_GETLK 9) fails in
qemu_init_main_loop, where its pipe2 (pipe plus F_SETFD FD_CLOEXEC) and
glib's g_unix_set_fd_nonblocking (F_GETFL, F_SETFL) build the event
notifier (util/event_notifier-posix.c:44-67); VLC (PPSA85300, 43 sites,
F_RDAHEAD on every file it opens), RetroArch (PPSA99169, 17),
Porpoise (PPSA99764, 13) and Ruffle (9) call it on files and pipes.
The commands follow FreeBSD 11.4 kern_fcntl and kern_dup
(sys/kern/kern_descrip.c:479-799, 812, release/11.4.0 8c639945):
F_GETFD/F_SETFD keep the close-on-exec flag per descriptor, F_GETFL
reports the host access mode with the status flags, F_SETFL changes
only O_NONBLOCK, O_APPEND, O_ASYNC, O_FSYNC, O_DIRECT and FRDAHEAD,
F_DUPFD/F_DUPFD_CLOEXEC give the lowest free descriptor at or above the
start through dup (EINVAL for a negative start), F_DUP2FD goes through
dup2, F_RDAHEAD/F_READAHEAD set FRDAHEAD on files (EBADF on pipes,
ENOTTY elsewhere), and unknown commands fail with EINVAL. Status flags
live in a guest table shared by dup, dup2 and F_DUPFD like an open file
description. O_NONBLOCK switches a pipe (PIPE_NOWAIT on Windows, where an
empty read and a full write now report EAGAIN; the host flag on Linux).
Record locks use the host POSIX locks on Linux and throw on Windows.
Changes the host cannot model throw naming the flag and descriptor kind.
Sockets keep their path unchanged.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Draws 32_ABGR exports into a tiled and a linear format 17 target and compares every texel with
the layout X in bits 0-4, Y in 5-9, Z in 10-14, W in bit 15, including the X/Z-swapped pairs, rounding
edges and texels outside the scissor. Reverting the X/Z exchange fails it.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A frame presented from guest memory with APS5_DUMP_FRAMES is read back
at the size of the display buffer, and that whole copy went into the
dump writer's queue with the scale beside it (31.6 MiB for a 3840x2160
buffer). The queue holds 64 MiB, so the third frame the writer had not
finished ended the run with "frame dump queue exceeded 64 MiB". A frame
blitted from the resident image is already reduced on the GPU and queued
at the reduced size.
The readback is now reduced by the dump scale when the slot retires and
queued at that size, the same pixels the writer kept before. With
APS5_DUMP_SCALE=1 nothing is reduced and the limit is reached as before.
video_out_frame_dump_queue presents four 3840x2160 frames from guest
memory while the writer is held on the first file and compares every
dump with the decoded display buffer.
Direct memory can already be mapped more than once, so there is no
aliasing to enable and the call returns 0. ProsperoEden (PPSA99008)
imports it and calls it before mapping the same direct memory again.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(agc): keep the driver's per-thread scratch out of thread_local teardown
With MinGW's emulated TLS, a thread_local with a destructor in a DLL is
destroyed after its storage is gone: when a std::thread ends, winpthread
runs libgcc's emutls key destructor, which frees the thread's thread_local
storage, and only then does the loader's DLL_THREAD_DETACH run the DLL's
tls_atexit list, so the destructors run on freed memory. The queue worker
touches four of them in libSceAgcDriver (the dispatch-ahead map, the
pending waits, the draw-snapshot compare vectors and the image decode
memo), and Driver::stop's join of the worker then corrupts the heap.
Under cdb's checking heap agc_driver_draw_cache_reuse stopped at the
worker's exit with heap corruption in all 127 runs, with graphics
pipeline libraries, with APS5_NO_PIPELINE_LTO=1 and with APS5_NO_GPL=1.
Without it the damage shows intermittently, as exit 0xc0000374 (2 of 143
runs with a cold NVIDIA shader cache) or as a fault in whatever allocates
next, such as NVIDIA's compiler on the optimized-link thread (#2980).
These objects now live in HostThreadLocal, fiber-local storage that owns
them, as the recorder's buffers do since e424b6b7b.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(libs): keep thread_local objects with destructors out of module teardown
The teardown order behind the driver's heap corruption holds in every
module: with MinGW's emulated TLS a thread's thread_local storage is freed
before the module's DLL_THREAD_DETACH runs its thread_local destructors,
so they run on freed objects. PreciseSleepUs's timer (used, among others,
by the video-out vblank thread) then closes a stale handle; libkernel's
directory path, socket waker and UUID random device, libSceRandom's random
device and libScePosixForWebKit's random device and host lookup free or
close stale objects. They move to HostThreadLocal. The other thread_locals
in the modules are trivially destructible or only built for Linux.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(libs): fail when a module registers a thread_local destructor
prx_thread_local_destructors lists the symbols of every built module and,
where MinGW's thread_local destructor support is linked in, disassembles
the module for calls to __cxa_thread_atexit outside libc's registration of
guest destructors. A module that keeps a thread_local with a destructor
fails it with the function that registers one: with main's libSceRandom
it reports sceRandomGetRandomNumber, and with this branch all 122 modules
pass, libc being the only one that links the support.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(agc): keep the image memo behind its pointer
The image decode memo went into HostThreadLocal as the whole array. On
Linux, where HostThreadLocal is a plain thread_local, that put 1024 memo
entries into every thread's TLS block instead of one pointer. It keeps
its unique_ptr as before, now owned by HostThreadLocal, so Linux builds
the same thread_local as main.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Dean Galvin <dean.galvin@live.com>
agc_shader_replay compiles a request through Recompile, the per-draw path.
Shaders the driver prepares at registration go through PrepareShader
instead and produce a different, larger artifact, so a failure or the size
of that artifact could not be reproduced from the dumped request.
--prepare compiles the request with PrepareShader and prints the artifact's
SPIR-V size, or the first line of the failure.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A device replaced at AttachWindow keeps its host imports in
HostImports::parked until it is destroyed, and each parked import keeps
its guest ranges pinned. A guest munmap, mprotect or free of such a range
spun in the pin waiter until APS5_PIN_WAIT_MS and then failed, because the
waiter only looked at the live imports. The replaced device was already
waited idle by PrepareForReplacement, so the waiter now drops the parked
imports over the range.
agc_parked_import_unmap imports a range on one device, again on a second
one, and unmaps it with a 3 s pin wait.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Retain weak identities and the full failed span so a replacement of either owner or a shorter remaining run retries the import. Keep unchanged failures cached across unrelated mapping changes without pinning their allocations. Existing upstream import lifetime, span windows and Windows fallback remain in place. AI-assisted implementation.
Capture the request once before rethrowing from the shared handler. Standard exceptions keep the shader address, stage, optional dump filename and full message; non-standard exceptions remain unchanged after capture.
AI-assisted: yes.
PrepareShaderWithDiagnostics dumped the request but rethrew the raw error, so a failure never said which shader it came from. Wrap it with the shader address and stage, keeping the dump. AI-assisted: yes.
FailureCapture now requires the code address and stage in the diagnosed failure, with and without the request dump. Fails before the wrap, passes after. AI-assisted: yes.
Vulkan's standard 8x sample 7 is (0.9375, 0.0625), (7, -7) in signed sixteenths; the table had (7, -8), so the
standard pattern (PPSA99902's PA_SC_AA_SAMPLE_LOCS words 0xbd153fd1 / 0x9773f95b) was refused as nonstandard.
Regression row with those words.
Reported by @Amoenus.
Co-Authored-By: Amoenus <5391396+Amoenus@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 9a1a0672cac0f8e3dcaa6e28cbe6b7b684a40a1f)
Resolves NID touqMEt6qXQ, the last import Trails in the Sky 2nd Chapter
(PPSA35169) is left missing on libSceAmpr.prx once the libkernel pair in #4214
is recorded. No reference describes the function's contract, so it throws via
the project's NotImplemented_nid_no_patch pattern rather than guessing a result
that a caller would act on.
sceAmprAmmGetUsageStatsData (KqiWXLgCVe0) is deliberately not included: #3814
exports it and #4207 answers it from a cited layout.
See issue #4214.
realloc(NULL, 0) returned NULL without allocating anything, while malloc(0) returns a unique pointer. C11 7.22.3.5p3 says realloc with a null pointer behaves like malloc for the specified size, so code that grows an empty buffer from zero elements (p = realloc(p, n * size) with n == 0) and treats NULL as out of memory failed on its first call.
FreeBSD 11.4 never returns NULL from realloc for a zero size; je_realloc rounds the size up before choosing between resizing and allocating (contrib/jemalloc/src/jemalloc.c, je_realloc).
A null pointer with size 0 now reaches the allocator's realloc, which allocates as malloc(0) does. A non-null pointer with size 0 is still freed and NULL returned, as before.
Co-authored-by: milescoviello <kitslayer29@gmail.com>
guest_avplayer_tests imports __cxa_throw from libc.prx, which is loaded
before libstdc++, but -static-libgcc gave it a private copy of
_Unwind_Resume. A failed Check threw through libc.prx's unwinder and the
landing pad of the test function resumed with the private one, so on
Linux every failure aborted in _Unwind_RaiseException_Phase2 instead of
printing its message from main. This is the unexplained Linux abort in
the intermittent guest_avplayer failures.
The test is now linked without the static runtime flags, as the AgcDriver,
Agc and VideoOut tests already are, so the unwinder comes from the same
library as __cxa_throw. A forced Check failure in TestPlayback aborted
before and now prints its message and exits with 1.
(cherry picked from commit fa5199cd8fef7f9c7ab9e83a441e15d16364a81d)
On GFX10.3 the DEPTH field of a 1D or 2D non-array linear T# carries the
row pitch minus one (Mesa's ac_descriptors.c sets it for custom pitches,
which must be multiples of 256 bytes). A single-level, uncompressed 2D
linear texture whose DEPTH equals its default 256-byte-aligned pitch now
decodes as that 2D texture; any other pitch still throws.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fdopen only took a fixed list of 15 mode strings and failed with EINVAL
for any other, so "rt", "r+t", "re", "rbe" or "rw" gave NULL. FreeBSD's
fdopen parses the mode with __sflags (lib/libc/stdio/flags.c): after
r, w or a it takes b, +, x, e and v and stops at the first other
character ("default: known = 0;"), and only a bad first character or x
on a read-only mode fail with EINVAL. fopen and freopen already parse
modes this way through ParseFileMode (#2033).
fdopen now uses ParseFileMode as well and passes the host its normalised
mode without x, which FreeBSD's fdopen ignores.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
gai_strerror returned its own wording for most codes, so a resolver
failure was described with text FreeBSD's libc does not use: EAI_NONAME
(8) gave "Name or service not known" instead of "hostname nor servname
provided, or not known", EAI_BADFLAGS (3) "Invalid flags" instead of
"Invalid value for ai_flags", and an unknown code "Unknown address
resolution error" instead of "Unknown error". Twelve of the fifteen
texts differed.
FreeBSD 11.4, lib/libc/net/gai_strerror.c:
static const char *ai_errlist[] = {
"Success", /* 0 */
"Address family for hostname not supported", /* 1 */
"Temporary failure in name resolution", /* EAI_AGAIN */
"Invalid value for ai_flags", /* EAI_BADFLAGS */
"Non-recoverable failure in name resolution", /* EAI_FAIL */
"ai_family not supported", /* EAI_FAMILY */
"Memory allocation failure", /* EAI_MEMORY */
"No address associated with hostname", /* 7 */
"hostname nor servname provided, or not known", /* EAI_NONAME */
"servname not supported for ai_socktype", /* EAI_SERVICE */
"ai_socktype not supported", /* EAI_SOCKTYPE */
"System error returned in errno", /* EAI_SYSTEM */
"Invalid value for hints", /* EAI_BADHINTS */
"Resolved protocol is unknown", /* EAI_PROTOCOL */
"Argument buffer overflow" /* EAI_OVERFLOW */
};
...
if (ecode >= 0 && ecode < EAI_MAX)
return ai_errlist[ecode];
return "Unknown error";
The table and the unknown-code text now match, as 5830d8e44 did for
strerror.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
socket only took a type of exactly SOCK_STREAM or SOCK_DGRAM, so a type
with SOCK_NONBLOCK (0x20000000) or SOCK_CLOEXEC (0x10000000) set failed
with EPROTONOSUPPORT, although socketpair already accepted both.
FreeBSD strips both flags before creating the socket (sys/kern/
uipc_syscalls.c, kern_socket: "if ((type & SOCK_CLOEXEC) != 0) { type
&= ~SOCK_CLOEXEC; oflag |= O_CLOEXEC; }" and the same for SOCK_NONBLOCK
with FNONBLOCK), and makes the new socket non-blocking with
"fo_ioctl(fp, FIONBIO, &fflag, ...)".
socket now does the same as socketpair: it creates the socket from the
plain type and, with SOCK_NONBLOCK, switches it to non-blocking.
SOCK_CLOEXEC needs nothing more, since a guest never executes another
program.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mbstowcs, and libSceAmpr's copy of it, counted at most n characters
when the destination was NULL, so the usual sizing call
mbstowcs(NULL, s, 0) returned 0 and mbstowcs(NULL, s, 1) returned 1 for
a three-character string. A caller that sizes its buffer that way got a
buffer too small for the string and no terminator.
POSIX says that with a null pwcs mbstowcs "shall return the length
required to convert the entire array regardless of the value of n", and
FreeBSD does that: lib/libc/locale/mbstowcs.c passes SIZE_T_MAX as the
source limit, and the C locale's _none_mbsnrtowcs (none.c) returns the
distance to the terminating NUL when "dst == NULL".
With a null destination, SonyMbstowcs now returns the length of the
whole string.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Kart PS5 (PPSA99611) imports __divti3 and stops at start-up on main. The guest has no signed 128-bit division; add it alongside __udivti3, dividing the magnitudes and negating the quotient, and throwing on a zero divisor like the unsigned routine.
SaveDataDirNameSearchResult.infos points at an array the title reads one
entry per returned directory name, but the search never wrote it, and
SaveDataSearchInfo was declared as 128 opaque bytes. A title that asks for
the infos read whatever its buffer held.
SaveDataSearchInfo is now the 48-byte record shadPS4 declares as
OrbisSaveDataSearchInfo (blocks, free blocks, 32 reserved bytes), and the
search fills one per returned name. Every native save reports
SAVE_DATA_BLOCKS_MAX blocks, as sceSaveDataGetMountInfo and the block sort
keys already assume, so both counts are SAVE_DATA_BLOCKS_MAX and the
reserved bytes are zero.
guest_savedata_search_tests checks the two records, their zero reserved
bytes and that the search writes nothing past them.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VFVE5WbaUJ2vyj8NAW44CS
std::random_device in libstdc++ reads RDSEED on x86 Linux. On an AMD
EPYC 9V45 (Zen 5) CI runner its 32-bit results repeat about 130 times
in 65536 draws, so guest_posix_random fails. libc now exports
HostEntropy_nid_no_patch, which uses getentropy (rand_s on Windows) and
throws when the OS cannot provide entropy. arc4random, arc4random_buf,
sceRandomGetRandomNumber, /dev/random and /dev/urandom reads,
std::_Random_device and sceKernelUuidCreate use it, and the arc4random
and random device TechnicalDebt entries say so.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The fixture read lane 63 of v0, which the program never writes. Since #3546 a wave64 vertex program on a narrower host subgroup builds that read as 0, so the synchronous registration the test expects to fail succeeds. v5 holds the vertex index, which is not 0 in lanes 32-63, so the read still throws.
On Windows agc_write_tracking has failed since #3594 with
write tracking test failed: an unwatched range reads as not stored over
from the check in CheckSharedBlock:
if (!Watched(outside, unwatched.size())) Require(StoredOver(outside, unwatched.size(), TrackerGeneration()), "an unwatched range reads as not stored over");
The host stack array lies in a host hole of the guest arena. #3594 made
Watched, collectWrites, UnchangedSince, UnchangedSinceAll and storeOwn
reject ranges overlapping such holes, but StoredOver, MarkWritten,
WrittenSince, ChangedBlocks and ImportWatched still accepted them through
WriteTracker::covers(), so StoredOver proved a range not stored over while
Watched called it unwatched (#4087).
The exclusion now lives in WriteTracker::covers() on Windows, which every
query already asks, and the per-function checks are removed. Host holes
are padded to the allocation granularity and the tracker blocks are 64 KiB
from the arena base, so no block is shared between a hole and arena
memory, and ranges outside the holes get the same answers as before.
A new case enumerates the arena for a host hole and checks every query
against it; on the previous code StoredOver, MarkWritten, StoreOwnBytes,
WrittenSince and ChangedBlocks fail it.
Fixes#4087
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
On a PS5 (firmware 13.60) scePthreadJoin on the calling thread returns
EDEADLK and scePthreadCreate with a zeroed attribute creates a thread
with the default attributes. Here both threw.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A program that uses v_movrels_b32 or v_movreld_b32 keeps every vector
register from the lowest relatively addressed one up in a per-invocation
window array, so its plain register accesses become LoadVectorWindow and
StoreVectorWindow. The half-wave reduction matcher followed only SSA values,
so a wave64 scan, v_permlanex16_b32 join and lane 31/63 read through that
array threw that lane 63 is outside the 32-lane host subgroup.
The matcher now reads a window load at a constant slot as the value of the
last store to that slot earlier in its block, unless a store at a runtime
slot lies in between; each invocation owns its array, so that is the value
the load returns. A store in another block, or a v_movreld_b32 at a runtime
M0 in between, still throws. This was the only refused lane 32-63 read left
among Ghost of Yotei's wave64 graphics programs (one pixel program).
On a PS5 (firmware 13.60) the SCE mutex attribute calls return EINVAL
for a null or uninitialized attribute, scePthreadMutexattrSetprotocol
accepts 0 to 2, scePthreadMutexInit takes an uninitialized attribute as
the default one, and destroying a destroyed mutex returns EINVAL. Here
these threw, including for the priority protection protocol.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keep the tracker initialization fix without an explanatory code comment or technical-debt entry, as requested in review.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
On a PS5 (firmware 13.60) the scePthreadAttr setters and getters return
EINVAL for a null or uninitialized attribute and for a null output,
ENOTSUP for a policy other than SCHED_FIFO and SCHED_RR, an inheritance
other than explicit and inherit, and a priority outside 256 to 767, and
EINVAL for an invalid detach state. Here they threw, or stored any
policy, inheritance and priority.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Windows path from #3788 changed the protection of any committed private host region outside the guest registry, including a range crossing a guest stack's lower bound. Ranges inside a live guest thread stack keep the path from #2982.
sceAgcDcbSetWorkloadsActive writes an 18-dword IT_NOP packet (stream id, 64-bit workload mask) and sceAgcDcbSetWorkloadComplete a 12-dword one (stream id, workload id, 64-bit clear mask), following KytyPS5. Both return null for a null buffer, an unregistered or out-of-range stream, a workload id above 63, and for active a duplicate id or a count of 0 or above 63. sceAgcDriverRegisterWorkloadStream and sceAgcDriverUnregisterWorkloadStream now record the stream ids the DCB calls check.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
sceAgcDcbSetWorkloadsActive and sceAgcDcbSetWorkloadComplete threw. They now write a plain two-dword NOP and keep no workload state.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
On a PS5 (firmware 13.60) a user with every accessibility setting at
its default reads chat transcription 0, press-and-hold delay 0, trigger
effect 1, vibration 1, zoom 0 and zoom follow focus 1; all six read 0
here. A user who is not logged in returns 0x80960009 with or without an
output, as sceUserServiceGetAgeLevel does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>