mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-27 00:28:14 +00:00
Merge pull request #8903 from UnhealthyKraken/feat/libc-feraiseexcept
feat(libc): implement feraiseexcept
This commit is contained in:
5 files changed
+198
No files matched your search
@@ -140,6 +140,21 @@ endif()
|
||||
target_sources(libc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/FloatingPoint.cpp)
|
||||
|
||||
if(BUILD_TESTING)
|
||||
add_test_executable(guest_raise_except_tests ${LIBS_INCLUDE_DIR}/tests/GuestRaiseExcept.cpp)
|
||||
target_include_directories(guest_raise_except_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_raise_except_tests PRIVATE libc ${CMAKE_DL_LIBS})
|
||||
configure_windows_unwind(guest_raise_except_tests)
|
||||
add_test(NAME guest_raise_except COMMAND guest_raise_except_tests $<TARGET_FILE:libc>)
|
||||
set(raiseExceptTests guest_raise_except)
|
||||
foreach(exceptionBit RANGE 0 5)
|
||||
add_test(NAME guest_raise_except_trap_${exceptionBit} COMMAND guest_raise_except_tests $<TARGET_FILE:libc> ${exceptionBit})
|
||||
list(APPEND raiseExceptTests guest_raise_except_trap_${exceptionBit})
|
||||
endforeach()
|
||||
set_tests_properties(${raiseExceptTests} PROPERTIES TIMEOUT 10)
|
||||
if(WIN32)
|
||||
set_tests_properties(${raiseExceptTests} PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
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 ${CMAKE_DL_LIBS})
|
||||
|
||||
@@ -334,6 +334,7 @@ int APS5_VABI fegetexceptflag_nid_postfix(std::uint16_t* flags, int exceptions);
|
||||
void APS5_VABI _Feraise_nid_postfix(int exceptions);
|
||||
int APS5_VABI fesetexceptflag_nid_postfix(const std::uint16_t* flags, int exceptions);
|
||||
int APS5_VABI feclearexcept_nid_postfix(int exceptions);
|
||||
int APS5_VABI feraiseexcept_nid_postfix(int exceptions);
|
||||
int APS5_VABI feenableexcept_nid_postfix(int exceptions);
|
||||
int APS5_VABI fedisableexcept_nid_postfix(int exceptions);
|
||||
int APS5_VABI __feenableexcept_nid_postfix(int exceptions);
|
||||
|
||||
@@ -84,6 +84,14 @@ int APS5_VABI fesetexceptflag_nid_postfix(const std::uint16_t* flags, int except
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI feraiseexcept_nid_postfix(int exceptions) {
|
||||
const auto raised = static_cast<unsigned>(exceptions);
|
||||
if ((raised & ~Exceptions) != 0) throw std::invalid_argument("feraiseexcept: unsupported exception bits");
|
||||
ReplaceFlags(raised, raised);
|
||||
asm volatile("fwait");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI feclearexcept_nid_postfix(int exceptions) {
|
||||
const auto mask = static_cast<unsigned>(exceptions) & Exceptions;
|
||||
if (mask == Exceptions) {
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
#include "prx/libc/include/Api.hpp"
|
||||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include <array>
|
||||
#include <barrier>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
#include <xmmintrin.h>
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <csignal>
|
||||
#include <dlfcn.h>
|
||||
#endif
|
||||
|
||||
using Raise = int (APS5_VABI*)(int);
|
||||
Raise raiseFlags;
|
||||
|
||||
void Require(bool condition, const char* message) {
|
||||
if (!condition) { std::fprintf(stderr,"feraiseexcept: %s\n",message); std::abort(); }
|
||||
}
|
||||
|
||||
struct Environment { std::array<std::uint32_t,7> x87; std::uint32_t sse; };
|
||||
Environment Read() {
|
||||
Environment state;
|
||||
asm volatile("fnstenv %0\n\tfldenv %0" : "=m"(state.x87));
|
||||
state.sse=_mm_getcsr();
|
||||
return state;
|
||||
}
|
||||
void Write(const Environment& state) {
|
||||
asm volatile("fldenv %0" : : "m"(state.x87));
|
||||
_mm_setcsr(state.sse);
|
||||
}
|
||||
|
||||
void Flags() {
|
||||
const auto original=Read();
|
||||
for (const unsigned precision: {0u,0x200u,0x300u})
|
||||
for (unsigned rounding=0;rounding<16;++rounding)
|
||||
for (unsigned old=0;old<64;++old)
|
||||
for (unsigned raised=0;raised<64;++raised) {
|
||||
auto before=original;
|
||||
before.x87[0]=(before.x87[0]&~0xf3fu)|0x3f|precision|((rounding&3)<<10);
|
||||
before.x87[1]=(before.x87[1]&~0xffu)|old;
|
||||
before.sse=(before.sse&~0x603fu)|(0x3f<<7)|((rounding>>2)<<13)|(old^0x3f);
|
||||
Write(before);
|
||||
before=Read();
|
||||
*__error_nid_postfix()=123;
|
||||
Require(raiseFlags(static_cast<int>(raised))==0,"return value");
|
||||
const auto after=Read();
|
||||
Require(*__error_nid_postfix()==123,"errno preserved");
|
||||
Require((after.x87[1]&0x3f)==(old|raised),"x87 flags accumulate");
|
||||
Require((after.sse&0x3f)==((old^0x3f)|raised),"SSE flags accumulate independently");
|
||||
Require((after.x87[1]&~0x3fu)==(before.x87[1]&~0x3fu),"other x87 status preserved");
|
||||
Require((after.sse&~0x3fu)==(before.sse&~0x3fu),"SSE controls preserved");
|
||||
for (unsigned word: {0u,2u,3u,4u,5u,6u})
|
||||
Require(after.x87[word]==before.x87[word],"x87 controls, tags and pointers preserved");
|
||||
}
|
||||
Write(original);
|
||||
for (const int invalid: {-1,0x40,0x10000,std::numeric_limits<int>::min(),std::numeric_limits<int>::max()}) {
|
||||
bool rejected=false;
|
||||
try { raiseFlags(invalid); } catch (const std::exception&) { rejected=true; }
|
||||
Require(rejected,"unverified exception bits rejected");
|
||||
const auto after=Read();
|
||||
Require(after.x87==original.x87 && after.sse==original.sse,"rejected mask preserves environment");
|
||||
}
|
||||
}
|
||||
|
||||
void DifferentMasks() {
|
||||
const auto original=Read();
|
||||
for (unsigned bit=0;bit<6;++bit) {
|
||||
auto seed=original;
|
||||
seed.x87[0]|=0x3f;
|
||||
seed.x87[1]&=~0xffu;
|
||||
seed.sse=(seed.sse&~0x3fu)|(0x3f<<7);
|
||||
seed.sse&=~(1u<<(bit+7));
|
||||
Write(seed);
|
||||
const int result=raiseFlags(static_cast<int>(1u<<bit));
|
||||
const auto after=Read();
|
||||
Write(original);
|
||||
Require(result==0,"SSE-only unmask does not deliver an x87 trap");
|
||||
Require((after.x87[1]&0x3f)==(1u<<bit) && (after.sse&0x3f)==(1u<<bit),"flags with differing exception masks");
|
||||
Require(after.x87[0]==seed.x87[0] && (after.sse&~0x3fu)==(seed.sse&~0x3fu),"differing masks preserved");
|
||||
seed.x87[0]&=~(1u<<bit);
|
||||
seed.sse|=0x3f<<7;
|
||||
Write(seed);
|
||||
const int noFlags=raiseFlags(0);
|
||||
const auto unchanged=Read();
|
||||
Write(original);
|
||||
Require(noFlags==0 && (unchanged.x87[1]&0x3f)==0 && (unchanged.sse&0x3f)==0,"empty mask does not trap with x87 enabled");
|
||||
}
|
||||
}
|
||||
|
||||
void Threads() {
|
||||
std::barrier ready(2);
|
||||
auto worker=[&](unsigned flag) {
|
||||
const auto original=Read();
|
||||
auto seed=original;
|
||||
seed.x87[0]|=0x3f;
|
||||
seed.x87[1]&=~0xffu;
|
||||
seed.sse=(seed.sse&~0x3fu)|(0x3f<<7);
|
||||
Write(seed);
|
||||
ready.arrive_and_wait();
|
||||
*__error_nid_postfix()=static_cast<int>(100+flag);
|
||||
Require(raiseFlags(static_cast<int>(flag))==0,"thread return");
|
||||
const int raisedError=*__error_nid_postfix();
|
||||
ready.arrive_and_wait();
|
||||
const auto after=Read();
|
||||
Require((after.x87[1]&0x3f)==flag && (after.sse&0x3f)==flag,"thread-local exception flags");
|
||||
Require(raisedError==static_cast<int>(100+flag),"thread-local errno");
|
||||
Write(original);
|
||||
};
|
||||
std::thread a(worker,1),b(worker,4);
|
||||
a.join(); b.join();
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
DWORD expectedException;
|
||||
unsigned expectedFlag;
|
||||
LONG CALLBACK Trap(EXCEPTION_POINTERS* exception) {
|
||||
const auto code=exception->ExceptionRecord->ExceptionCode;
|
||||
const auto& state=exception->ContextRecord->FltSave;
|
||||
const auto active=state.StatusWord & ~state.ControlWord & 0x3f;
|
||||
const bool denormalCode=expectedFlag==2 && code==EXCEPTION_FLT_INVALID_OPERATION;
|
||||
if ((code==expectedException || denormalCode) && active==expectedFlag) std::_Exit(0);
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
#else
|
||||
void Trap(int) { std::_Exit(0); }
|
||||
#endif
|
||||
|
||||
void CheckTrap(unsigned bit) {
|
||||
Require(bit<6,"trap bit");
|
||||
#ifdef _WIN32
|
||||
constexpr DWORD codes[]{EXCEPTION_FLT_INVALID_OPERATION,EXCEPTION_FLT_DENORMAL_OPERAND,EXCEPTION_FLT_DIVIDE_BY_ZERO,EXCEPTION_FLT_OVERFLOW,EXCEPTION_FLT_UNDERFLOW,EXCEPTION_FLT_INEXACT_RESULT};
|
||||
expectedException=codes[bit];
|
||||
expectedFlag=1u<<bit;
|
||||
Require(AddVectoredExceptionHandler(1,Trap)!=nullptr,"install trap handler");
|
||||
#else
|
||||
Require(std::signal(SIGFPE,Trap)!=SIG_ERR,"install trap handler");
|
||||
#endif
|
||||
auto state=Read();
|
||||
state.x87[0]=(state.x87[0]|0x3f)&~(1u<<bit);
|
||||
state.x87[1]&=~0xffu;
|
||||
state.sse=(state.sse&~0x3fu)|(0x3f<<7);
|
||||
Write(state);
|
||||
raiseFlags(static_cast<int>(1u<<bit));
|
||||
Require(false,"enabled x87 exception did not trap");
|
||||
}
|
||||
|
||||
int main(int argc,char** argv) {
|
||||
Require(argc==2 || argc==3,"expected library path and optional trap bit");
|
||||
#ifdef _WIN32
|
||||
const auto library=LoadLibraryA(argv[1]);
|
||||
Require(library!=nullptr,"load libc");
|
||||
raiseFlags=reinterpret_cast<Raise>(GetProcAddress(library,"feraiseexcept_nid_postfix"));
|
||||
#else
|
||||
const auto library=dlopen(argv[1],RTLD_NOW);
|
||||
Require(library!=nullptr,"load libc");
|
||||
raiseFlags=reinterpret_cast<Raise>(dlsym(library,"feraiseexcept_nid_postfix"));
|
||||
#endif
|
||||
Require(raiseFlags!=nullptr,"feraiseexcept export is missing");
|
||||
if (argc==3) CheckTrap(static_cast<unsigned>(std::atoi(argv[2])));
|
||||
Flags(); DifferentMasks(); Threads();
|
||||
#ifdef _WIN32
|
||||
Require(FreeLibrary(library)!=0,"release lookup handle");
|
||||
#else
|
||||
Require(dlclose(library)==0,"release lookup handle");
|
||||
#endif
|
||||
std::puts("feraiseexcept checks passed");
|
||||
}
|
||||
@@ -43,6 +43,7 @@
|
||||
- [__atomic_load, __atomic_store, __atomic_exchange and __atomic_compare_exchange](../../../core/libs/prx/libc/src/AtomicBuiltins.cpp) (libc) - the generic, size-parameterised libatomic entry points are a single `ud2` in the `libc.prx` bundled with PPSA08427 (disassembly), so the console traps on any call; these throw instead
|
||||
- [Sized __atomic calls](../../../core/libs/prx/libc/src/DinkumwareAtomics.cpp) (libc) - the 36 `__atomic_load_N`, `__atomic_store_N`, `__atomic_exchange_N`, `__atomic_compare_exchange_N` and `__atomic_fetch_{add,sub,and,or,xor}_N` calls (N = 1, 2, 4, 8) are sequentially consistent whatever order they are given. The `libc.prx` bundled with PPSA08427, run side by side on random values and on every order from -1 to 7, switches on the order but always uses a locked instruction or CAS loop, except that a store with an order other than `memory_order_seq_cst` is a plain store, so these are never weaker than the console. Unlike `_Atomic_load_8` and the 8-byte `_Atomic_*` exchange and fetch calls above, the console accepts every order here, including release, acq_rel and invalid ones, and these do the same
|
||||
- [Dinkumware mutexes and conditions](../../../core/libs/prx/libc/src/Threads.cpp) (libc) use pointer-to-handle arguments, matching the console aerolib signatures reported in [#3079](https://github.com/boykopovar/AnyPS5/pull/3079). The title's `_Mtx_lock` maps `0` to `_Thrd_success` (0), `0x8002000b` to `_Thrd_busy` (3), and other failures to `_Thrd_error` (4); its internal mutex init recognizes recursive flag `0x100`, while every other type uses default attributes. The observed relock failure for a plain mutex suggests those defaults are error-checking, but that has not been confirmed on hardware. The title's `_Mtx_unlock` and `_Cnd_wait` return success regardless of their internal result. The host implementation treats invalid ownership preconditions as no-ops, and throws for invalid handles, host wait errors and destroying a locked mutex as debug guards; those cases are not confirmed against the console.
|
||||
- [feraiseexcept](../../../core/libs/prx/libc/src/FloatingPoint.cpp) (libSceLibcInternal, implemented in libc, NID `NIfFNcyeCTo`) - console behavior and module/library versions have not been measured. The signature follows the [public PS5 SDK](https://github.com/ps5-payload-dev/sdk/blob/a7685ce8e97ce1f8a4f38253a41d54241c9124f1/include/freebsd/fenv.h); flag updates and x87 trap delivery follow [FreeBSD amd64 fenv.c](https://github.com/freebsd/freebsd-src/blob/d106002967eff712894ab8b5337bbbff106ce5e0/lib/msun/amd64/fenv.c). All six defined exception bits are supported. Unlike the FreeBSD reference, which does not mask or validate the requested bits, this implementation rejects other bits with `std::invalid_argument` before modifying state; console behavior for those bits is unknown. This is distinct from `_Feraise`, which also modifies errno and adds inexact for range exceptions.
|
||||
- [_Mtx_timedlock](../../../core/libs/prx/libc/src/Threads.cpp) (libc, NID `hPzYSd5Nasc`) follows the console's `libc.prx` at `0x5d50` (PPSA02530): it takes an absolute xtime and returns `_Thrd_timedout` (2) on expiry (`0x8002003c`) or 5 on other failures. On non-Windows hosts the native mutex and condition are `std::timed_mutex` and `std::condition_variable_any`; Windows keeps the `CRITICAL_SECTION`/`CONDITION_VARIABLE` pair because `std::condition_variable_any` failed `guest_libc_cnd_timed_wait` on the Windows runner, and its `_Mtx_timedlock` polls `TryEnterCriticalSection` until the deadline. Windows timing is not measured on the console.
|
||||
- [Floating-point compiler-rt builtins](../../../core/libs/prx/libc/src/FloatBuiltins.cpp) (libc) follow the `libc.prx` bundled with PPSA08427 (disassembly, and that binary run side by side on random inputs) where it differs from current compiler-rt: the soft-float `__add`, `__sub`, `__mul` and `__div` (`sf3`, `df3`) round to nearest, raise no exception flags and ignore MXCSR, here done with SSE under a default MXCSR that is restored afterwards; division flushes a quotient below the smallest normal to a signed zero; `__extendsfdf2` and `__truncdfsf2` copy NaN payloads unshifted; float-to-integer conversions do not saturate but shift the significand by a count masked to the shift width. `__mulsc3`, `__muldc3`, `__divsc3` and `__divdc3` fuse the same products as the console with `fma` and scale through the x87 like its `scalbn`, so their values match, but NaN payloads and exception flags can differ, as the host `fma` and `logb` are not the console's VEX FMA code and libm
|
||||
- [random, srandom, initstate, setstate](../../../core/libs/prx/libc/src/Random.cpp) (libc) - not measured: the state `setstate` returns and its null return for a rear position past the degree, `errno` after `initstate` below 8 bytes, a null state (throws here), an `initstate` seed other than 1, concurrent callers (one lock serialises every call here) and a title's own libc. Measured in a payload process on a PS5 (firmware 12.70): a fresh process draws the `srandom(1)` sequence; `srandom` seeds an additive feedback generator through a `good_rand` that reads its argument as an int32, turns 0 into 123459876 and applies `x = 16807 * (x % 127773) - 2836 * (x / 127773)`, adding 0x7fffffff to a negative result, which reproduces seeds 0, 1, 2, 12345, 0xffffffff and 0x80000000; `initstate` returns null at 0 and 7 bytes and selects types 0 to 4 by size as measured at 8, 15, 31, 32, 63, 64, 127, 128, 129, 255, 256, 257 and 1000 bytes.
|
||||
|
||||
Reference in new issue
Block a user