Merge pull request #8863 from UnhealthyKraken/feat/libc-feholdexcept

feat(libc): implement feholdexcept
This commit is contained in:
Dean authored and GitHub committed 2026-10-25 07:37:11 +13:00
commit 0ea0f25e1b
4 files changed
+132 -4

No files matched your search

+2 -2
View File
@@ -142,9 +142,9 @@ target_sources(libc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/FloatingPoint.cpp)
if(BUILD_TESTING)
add_test_executable(guest_floating_point_tests ${LIBS_INCLUDE_DIR}/tests/GuestFloatingPoint.cpp)
target_include_directories(guest_floating_point_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_floating_point_tests PRIVATE libc)
target_link_libraries(guest_floating_point_tests PRIVATE libc ${CMAKE_DL_LIBS})
configure_windows_unwind(guest_floating_point_tests)
add_test(NAME guest_floating_point COMMAND guest_floating_point_tests)
add_test(NAME guest_floating_point COMMAND guest_floating_point_tests $<TARGET_FILE:libc>)
set_tests_properties(guest_floating_point PROPERTIES TIMEOUT 10)
if(WIN32)
set_tests_properties(guest_floating_point PROPERTIES ENVIRONMENT_MODIFICATION
+10 -1
View File
@@ -1313,7 +1313,7 @@ struct GuestFloatingPointEnvironment {
unsigned char other[16];
std::uint32_t mxcsr;
};
static_assert(sizeof(GuestFloatingPointEnvironment) == 32 && offsetof(GuestFloatingPointEnvironment, mxcsr) == 28);
static_assert(sizeof(GuestFloatingPointEnvironment) == 32 && alignof(GuestFloatingPointEnvironment) == 4 && offsetof(GuestFloatingPointEnvironment, mxcsr) == 28);
int APS5_VABI feupdateenv_nid_postfix(const GuestFloatingPointEnvironment* environment) {
if (environment == nullptr) throw std::invalid_argument("feupdateenv: null environment");
@@ -1344,6 +1344,15 @@ int APS5_VABI fegetenv_nid_postfix(GuestFloatingPointEnvironment* environment) {
return 0;
}
int APS5_VABI feholdexcept_nid_postfix(GuestFloatingPointEnvironment* environment) {
if (environment == nullptr) throw std::invalid_argument("feholdexcept: null environment");
const auto csr = _mm_getcsr();
asm volatile("fnstenv %0\n\tfnclex" : "=m"(*environment));
environment->mxcsr = csr;
_mm_setcsr((csr & ~0x3fu) | 0x1f80u);
return 0;
}
union SonyDconst {
unsigned short _Word[8];
float _Float;
+119 -1
View File
@@ -2,11 +2,20 @@
#include <array>
#include <barrier>
#include <cstdint>
#include <cstddef>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <stdexcept>
#include <thread>
#include <xmmintrin.h>
#ifdef _WIN32
#include <windows.h>
#else
#include <dlfcn.h>
#include <sys/mman.h>
#include <unistd.h>
#endif
extern "C" {
int* APS5_VABI __error_nid_postfix();
@@ -245,13 +254,122 @@ void CheckTraps() {
WriteEnvironment(seeded);
}
int main() {
using HoldExcept = int (APS5_VABI*)(Environment*);
void CheckHold(HoldExcept hold, Environment* saved) {
const auto before = ReadEnvironment();
*__error_nid_postfix() = 123;
Require(hold(saved) == 0, "feholdexcept succeeds");
const auto after = ReadEnvironment();
Require(std::memcmp(saved, &before, sizeof(before)) == 0, "feholdexcept saves the full original environment");
Require((after.x87[0] & 0x3f) == 0x3f && (after.sse & 0x1f80) == 0x1f80, "feholdexcept masks both units");
Require((after.x87[1] & 0x80ff) == 0 && (after.sse & 0x3f) == 0, "feholdexcept clears pending exceptions");
Require((after.x87[1] & ~0x80ffu) == (before.x87[1] & ~0x80ffu), "feholdexcept preserves other x87 status bits");
Require((after.x87[0] & ~0x3fu) == (before.x87[0] & ~0x3fu), "feholdexcept preserves x87 control settings");
Require((after.sse & ~0x1fbfu) == (before.sse & ~0x1fbfu), "feholdexcept preserves SSE control settings");
Require(after.x87[2] == before.x87[2] && std::memcmp(after.x87.data() + 3, before.x87.data() + 3, 16) == 0, "feholdexcept preserves x87 tags and instruction/data state");
Require(*__error_nid_postfix() == 123, "feholdexcept preserves errno");
}
void CheckHoldExcept(HoldExcept hold) {
Require(hold != nullptr, "feholdexcept export is missing");
static_assert(sizeof(Environment) == 32 && alignof(Environment) == 4 && offsetof(Environment, sse) == 28);
const auto original = ReadEnvironment();
struct {
std::array<unsigned char, 8> before;
Environment value;
std::array<unsigned char, 8> after;
} output;
for (unsigned rounding = 0; rounding < 4; ++rounding) {
for (unsigned modes = 0; modes < 4; ++modes) {
for (unsigned mask = 0; mask < 64; ++mask) {
auto state = original;
const unsigned sseMask = (mask * 13 + 7) & 0x3f;
state.x87[0] = (state.x87[0] & ~0xc3fu) | (rounding << 10) | mask;
state.x87[1] = (state.x87[1] & ~0x80ffu) | (mask & 0x25);
state.sse = (state.sse & ~0xffffu) | (sseMask << 7) | (sseMask & 0x1a) | (((rounding + 1) & 3) << 13) | ((modes & 1) << 6) | ((modes & 2) << 14);
WriteEnvironment(state);
std::memset(&output, 0xa5, sizeof(output));
CheckHold(hold, &output.value);
for (const auto byte : output.before) Require(byte == 0xa5, "feholdexcept underwrites output");
for (const auto byte : output.after) Require(byte == 0xa5, "feholdexcept overwrites output");
}
}
}
WriteEnvironment(original);
bool threw = false;
try { hold(nullptr); } catch (const std::invalid_argument&) { threw = true; }
Require(threw, "feholdexcept rejects null storage");
const auto afterNull = ReadEnvironment();
Require(std::memcmp(&original, &afterNull, sizeof(original)) == 0, "null feholdexcept preserves the environment");
#ifdef _WIN32
SYSTEM_INFO info{};
GetSystemInfo(&info);
const std::size_t page = info.dwPageSize;
auto* memory = static_cast<unsigned char*>(VirtualAlloc(nullptr, page * 2, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
Require(memory != nullptr, "allocate feholdexcept guard pages");
DWORD oldProtection;
Require(VirtualProtect(memory + page, page, PAGE_NOACCESS, &oldProtection) != 0, "protect feholdexcept trailing page");
#else
const auto page = static_cast<std::size_t>(sysconf(_SC_PAGESIZE));
auto* memory = static_cast<unsigned char*>(mmap(nullptr, page * 2, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0));
Require(memory != MAP_FAILED, "allocate feholdexcept guard pages");
Require(mprotect(memory + page, page, PROT_NONE) == 0, "protect feholdexcept trailing page");
#endif
CheckHold(hold, reinterpret_cast<Environment*>(memory + page - sizeof(Environment)));
#ifdef _WIN32
Require(VirtualFree(memory, 0, MEM_RELEASE) != 0, "release feholdexcept guard pages");
#else
Require(munmap(memory, page * 2) == 0, "release feholdexcept guard pages");
#endif
WriteEnvironment(original);
std::barrier ready(2);
const auto worker = [&](unsigned mode) {
const auto saved = ReadEnvironment();
auto state = saved;
state.x87[0] = (state.x87[0] & ~0xc00u) | (mode << 10) | 0x3f;
state.x87[1] &= ~0x80ffu;
state.sse |= 0x1f80;
state.sse &= ~0x3fu;
WriteEnvironment(state);
ready.arrive_and_wait();
Environment output;
CheckHold(hold, &output);
ready.arrive_and_wait();
Require(fegetround_nid_postfix() == static_cast<int>(mode << 10), "feholdexcept keeps thread-local rounding");
WriteEnvironment(saved);
};
std::thread first(worker, 1);
std::thread second(worker, 2);
first.join();
second.join();
const auto afterThreads = ReadEnvironment();
Require(std::memcmp(&original, &afterThreads, sizeof(original)) == 0, "feholdexcept workers preserve the main environment");
}
int main(int argc, char** argv) {
Require(argc == 2, "expected libc path");
#ifdef _WIN32
const auto library = LoadLibraryA(argv[1]);
Require(library != nullptr, "load libc for feholdexcept");
const auto hold = reinterpret_cast<HoldExcept>(GetProcAddress(library, "feholdexcept_nid_postfix"));
#else
const auto library = dlopen(argv[1], RTLD_NOW);
Require(library != nullptr, "load libc for feholdexcept");
const auto hold = reinterpret_cast<HoldExcept>(dlsym(library, "feholdexcept_nid_postfix"));
#endif
const auto original = ReadEnvironment();
CheckRounding();
CheckEnabledExceptions();
CheckFlags();
CheckTraps();
CheckThreads();
CheckHoldExcept(hold);
WriteEnvironment(original);
#ifdef _WIN32
FreeLibrary(library);
#else
dlclose(library);
#endif
std::puts("floating-point environment checks passed");
}
+1
View File
@@ -19,6 +19,7 @@
- [fegetround, fesetround, fetestexcept, feclearexcept, fegetexceptflag and fesetexceptflag](../../../core/libs/prx/libc/src/FloatingPoint.cpp) (libSceLibcInternal, implemented in libc) follow FreeBSD 11.4 amd64 [fenv.h](https://github.com/freebsd/freebsd-src/blob/4554bee35405b36ea19e9e60d6abe6ea534e36e0/lib/msun/x86/fenv.h): rounding modes use x87 control bits 10-11, exception flags include denormals (`0x3f`), and `fexcept_t` is 16 bits. They access the current host thread's x87 and MXCSR state. Under Rosetta 2 the six MXCSR exception masks always read back set, so an SSE exception the guest unmasks stays masked; the x87 masks and the other MXCSR bits are kept. This console ABI is assumed, as for `fegetenv`, not measured on hardware. Null flag pointers throw instead of faulting.
- [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.
- [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