mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-28 00:35:47 +00:00
Merge pull request #8863 from UnhealthyKraken/feat/libc-feholdexcept
feat(libc): implement feholdexcept
This commit is contained in:
4 files changed
+132
-4
No files matched your search
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user