From 2fd60d3ebd309f137ae2a838af30e7c401f34ff1 Mon Sep 17 00:00:00 2001 From: orezraey Date: Sun, 25 Oct 2026 17:30:06 -0300 Subject: [PATCH] feat(libc): export __fe_dfl_env as measured on a PS5 Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libc/include/Api.hpp | 1 + core/libs/prx/libc/src/Math.cpp | 2 ++ core/libs/tests/GuestUpdateEnv.cpp | 24 +++++++++++++++++++++++- docs/dev/debt/libc.md | 2 +- 4 files changed, 27 insertions(+), 2 deletions(-) diff --git a/core/libs/prx/libc/include/Api.hpp b/core/libs/prx/libc/include/Api.hpp index 97bac3a7f3..ff256a9015 100644 --- a/core/libs/prx/libc/include/Api.hpp +++ b/core/libs/prx/libc/include/Api.hpp @@ -1382,6 +1382,7 @@ std::int64_t APS5_VABI _ZNSt8__sce_v216_Xtime_get_ticksEv_nid_postfix(); const char* APS5_VABI _ZNSt8__sce_v213_Syserror_mapEi_nid_postfix(int value); int APS5_VABI feupdateenv_nid_postfix(const GuestFloatingPointEnvironment* environment); int APS5_VABI feholdexcept_nid_postfix(GuestFloatingPointEnvironment* environment); +extern const GuestFloatingPointEnvironment __fe_dfl_env_nid_postfix; void APS5_VABI srandomdev_nid_postfix(); double APS5_VABI _Stodx_nid_postfix(const char* text, char** end, std::int64_t pten, int* error); diff --git a/core/libs/prx/libc/src/Math.cpp b/core/libs/prx/libc/src/Math.cpp index e635b0b1e1..ecb26150bf 100644 --- a/core/libs/prx/libc/src/Math.cpp +++ b/core/libs/prx/libc/src/Math.cpp @@ -1477,6 +1477,8 @@ int APS5_VABI feholdexcept_nid_postfix(GuestFloatingPointEnvironment* environmen return 0; } +const GuestFloatingPointEnvironment __fe_dfl_env_nid_postfix {0xffff037fu, 0xffff0000u, 0xffffffffu, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff}, 0x9fc0u}; + union SonyDconst { unsigned short _Word[8]; float _Float; diff --git a/core/libs/tests/GuestUpdateEnv.cpp b/core/libs/tests/GuestUpdateEnv.cpp index 5de6f53321..b6aa646785 100644 --- a/core/libs/tests/GuestUpdateEnv.cpp +++ b/core/libs/tests/GuestUpdateEnv.cpp @@ -253,6 +253,23 @@ void Pending(unsigned bit,unsigned source) { Require(false,"enabled pending exception did not trap during feupdateenv"); } +void DefaultEnvironment(const Environment* fallback) { + Require(fallback!=nullptr,"__fe_dfl_env is missing"); + constexpr unsigned char measured[32]{0x7f,0x03,0xff,0xff,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0xff,0xff,0xc0,0x9f,0x00,0x00}; + Require(std::memcmp(fallback,measured,sizeof(measured))==0,"__fe_dfl_env bytes"); + const auto original=Read(); + auto disturbed=original; + disturbed.x87[0]=(disturbed.x87[0]&~0xf3fu)|0xc00u|0x3fu; + disturbed.x87[1]&=~0xffu; + disturbed.mxcsr=(disturbed.mxcsr&~0x7fffu)|0x6000u|(0x3f<<7); + Write(disturbed); + Require(updateEnvironment(fallback)==0,"feupdateenv(__fe_dfl_env) result"); + const auto restored=Read(); + Require((restored.x87[0]&0xffffu)==0x037fu,"__fe_dfl_env x87 control"); + Require((restored.x87[1]&0x3fu)==0 && restored.mxcsr==0x9fc0u,"__fe_dfl_env leaves no flags when none were pending"); + Write(original); +} + int main(int argc,char** argv) { Require(argc==2 || argc==4,"expected libc path and optional trap bit/source"); #ifdef _WIN32 @@ -265,8 +282,13 @@ int main(int argc,char** argv) { updateEnvironment=reinterpret_cast(dlsym(library,"feupdateenv_nid_postfix")); #endif Require(updateEnvironment!=nullptr,"export is missing"); +#ifdef _WIN32 + const auto fallback=reinterpret_cast(GetProcAddress(library,"__fe_dfl_env_nid_postfix")); +#else + const auto fallback=static_cast(dlsym(library,"__fe_dfl_env_nid_postfix")); +#endif if(argc==4) Pending(static_cast(std::atoi(argv[2])),static_cast(std::atoi(argv[3]))); - Matrix(); TagsAndPointers(); BoundsAndErrors(); MasksAndThreads(); + Matrix(); TagsAndPointers(); BoundsAndErrors(); MasksAndThreads(); DefaultEnvironment(fallback); #ifdef _WIN32 Require(FreeLibrary(library)!=0,"release library lookup handle"); #else diff --git a/docs/dev/debt/libc.md b/docs/dev/debt/libc.md index 1c2a506f08..fb514292b9 100644 --- a/docs/dev/debt/libc.md +++ b/docs/dev/debt/libc.md @@ -20,7 +20,7 @@ - [feenableexcept, fedisableexcept, __feenableexcept, __fedisableexcept, fegettrapenable and fesettrapenable](../../../core/libs/prx/libc/src/FloatingPoint.cpp) (libc) change only the x87 masks under Rosetta 2: MXCSR keeps its six exception masks set there, so no SSE trap is enabled, and the enable and disable results, which report the traps enabled on both units, report none - [fegetenv](../../../core/libs/prx/libc/src/Math.cpp) (libSceLibcInternal, implemented in libc, NID `psx0YiAKm7k`) follows FreeBSD 11 amd64 [fenv.c](https://github.com/freebsd/freebsd-src/blob/4554bee35405b36ea19e9e60d6abe6ea534e36e0/lib/msun/amd64/fenv.c) with the 32-byte `fenv_t` of [x86/fenv.h](https://github.com/freebsd/freebsd-src/blob/4554bee35405b36ea19e9e60d6abe6ea534e36e0/lib/msun/x86/fenv.h): `fnstenv` fills the 28-byte x87 part, MXCSR follows at 0x1c, and the x87 control word is reloaded because `fnstenv` masks every x87 exception. That the console's `libSceLibcInternal` has the same code and layout is assumed, not read from its binary. A null pointer throws instead of faulting - [feholdexcept](../../../core/libs/prx/libc/src/Math.cpp) (libSceLibcInternal, implemented in libc, NID `1kduKXMqx7k`) - console equivalence and module/library versions are unverified. The signature and 32-byte amd64 environment layout follow the public SDK [fenv.h](https://github.com/ps5-payload-dev/sdk/blob/a7685ce8e97ce1f8a4f38253a41d54241c9124f1/include/freebsd/fenv.h); saving both units, masking exceptions and clearing pending flags follows FreeBSD 11 amd64 [fenv.c](https://github.com/freebsd/freebsd-src/blob/4554bee35405b36ea19e9e60d6abe6ea534e36e0/lib/msun/amd64/fenv.c). The NID comes from the public 3.20 stub inventory, not a module examined here. Null storage throws before modifying the environment, where the reference dereferences it -- [feupdateenv](../../../core/libs/prx/libc/src/Math.cpp) (libSceLibcInternal, implemented in libc, NID `YXQ4gXamCrY`) follows FreeBSD 11.4 amd64 [fenv.c](https://github.com/freebsd/freebsd-src/blob/d106002967eff712894ab8b5337bbbff106ce5e0/lib/msun/amd64/fenv.c): capture the old x87/MXCSR exception union (`0x3f`), restore the saved 32-byte environment, raise those flags in both units, then `fwait`. The console's layout, behavior and module versions are unmeasured; state belongs to the current host thread. A null input or MXCSR bits unsupported by the host's `FXSAVE` mask throw before changing floating-point state; the mask's architectural zero fallback is `0xffbf`. Console fault behavior for malformed environments is unknown. The separate `__fe_dfl_env` object remains unimplemented; no host `FE_DFL_ENV` sentinel is substituted. +- [feupdateenv, __fe_dfl_env](../../../core/libs/prx/libc/src/Math.cpp) (libSceLibcInternal, implemented in libc, NID `YXQ4gXamCrY`) - a null environment or MXCSR bits the host's `FXSAVE` mask does not support (zero mask: `0xffbf`) throw before changing state, where the system `libSceLibcInternal` of PS5 firmware 13.60 faults (`SIGSEGV`, and `SIGBUS` for MXCSR `0xffff9fc0`). Otherwise it matches that library: it restores the 32-byte environment and raises the union of the x87 and MXCSR flags pending before the call in both units, as FreeBSD 11.4 amd64 [fenv.c](https://github.com/freebsd/freebsd-src/blob/d106002967eff712894ab8b5337bbbff106ce5e0/lib/msun/amd64/fenv.c) does, and an unmasked pending flag traps. `__fe_dfl_env` holds the 32 bytes that library exports: x87 control `0xffff037f`, status `0xffff0000`, tag `0xffffffff`, `0xffff` at offset 26 and MXCSR `0x9fc0`. State belongs to the current host thread - [Guest arena](../../../core/libs/prx/libc/src/GuestArena.cpp) (libc) - the application map area is assumed to end at 0xFC_0000_0000, as `SCE_KERNEL_APP_MAP_AREA_END_ADDR` does on the PS4. On Windows, host allocations made in the arena before libc loads stay, and a fixed guest mapping over one throws. The console's low-address layout is not measured. Linux and Windows reserve free addresses from 64 KiB, except host mappings and low chunks the Linux address policy refuses; null hints still allocate from 8 GiB. macOS and ASan builds still start the arena at 8 GiB and cannot supply low 32-bit addresses. Guest thread creation ignores a stack address set in its attributes. A host-loaded executable is not moved, so a PIE above the arena gets no memory within 2 GiB of its code ([#5217](https://github.com/boykopovar/AnyPS5/issues/5217)) - [sceLibcInternalBacktraceForGame](../../../core/libs/prx/libc/src/HeapDiagnostics.cpp) (libSceLibcInternal, implemented in libc) - `(const char* heapName)` and its return follow the `libc.prx` ASTRO BOT (PPSA21564 01.007.000) bundles: its four calls (0x6801, 0x7F46, 0xE75E, 0xEE02) are in the `malloc_stats` and `sceLibcMspaceMallocStats` paths, each after a `[%s] ...` line printed with the same heap name buffer, and the caller ignores the result and continues. The console's backtrace output is not reproduced: the heap name and the guest return address are written to the host standard error - [sceLibcInternalHeapErrorReportForGame](../../../core/libs/prx/libc/src/HeapDiagnostics.cpp) (libSceLibcInternal, implemented in libc) - `(void* heap, void* block, uint32 error)` writes to the guest's standard output what a payload process on a PS5 (firmware 13.60) wrote: `[SceLibc] A heap error is detected.`, `[SceLibc] -- Mspace Name --`, then the C string at heap+0x3B0 followed by ` (%2u)` of the 32-bit value at heap+0x3F4 when it is not 0, the reason `segment out of range.` for error 1 only (38 other measured values print none), and `pid:<0x..> tid:<0x..>`, and returns. The pid and tid are the host's, where the console prints its own process and kernel thread ids. The block's contents did not change the console's output and it is not read here; the values and the guest return address also go to the host's standard error. A null heap faulted on the console and throws here. What the other heap fields and error values mean is unknown