mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-27 00:28:14 +00:00
276 lines
11 KiB
C++
276 lines
11 KiB
C++
#include "prx/libc/include/Api.hpp"
|
|
#include "prx/libc/include/general/VabiMacros.hpp"
|
|
#include <array>
|
|
#include <barrier>
|
|
#include <csignal>
|
|
#include <stdexcept>
|
|
#include <thread>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <xmmintrin.h>
|
|
#ifdef _WIN32
|
|
#include <windows.h>
|
|
#else
|
|
#include <dlfcn.h>
|
|
#include <sys/mman.h>
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
struct Environment {
|
|
std::array<std::uint32_t,7> x87;
|
|
std::uint32_t mxcsr;
|
|
};
|
|
static_assert(sizeof(Environment)==32 && offsetof(Environment,mxcsr)==28);
|
|
using UpdateEnvironment=int (APS5_VABI*)(const Environment*);
|
|
|
|
UpdateEnvironment updateEnvironment;
|
|
|
|
void Require(bool condition,const char* message) {
|
|
if(!condition) { std::fprintf(stderr,"feupdateenv: %s\n",message); std::exit(1); }
|
|
}
|
|
Environment Read() {
|
|
Environment result;
|
|
asm volatile("fnstenv %0\n\tfldcw %0" : "=m"(result.x87));
|
|
result.mxcsr=_mm_getcsr();
|
|
return result;
|
|
}
|
|
void Write(const Environment& value) {
|
|
asm volatile("fldenv %0" : : "m"(value.x87) : "st","st(1)","st(2)","st(3)","st(4)","st(5)","st(6)","st(7)");
|
|
_mm_setcsr(value.mxcsr);
|
|
}
|
|
bool Same(const Environment& a,const Environment& b){return a.x87==b.x87 && a.mxcsr==b.mxcsr;}
|
|
void Matrix() {
|
|
const auto original=Read();
|
|
auto clean=original;
|
|
clean.x87[0]|=0x3f;
|
|
clean.x87[1]&=~0xffu;
|
|
clean.mxcsr=(clean.mxcsr&~0x3fu)|(0x3f<<7);
|
|
unsigned cases=0;
|
|
for(unsigned x87=0;x87<64;++x87)
|
|
for(unsigned sse=0;sse<64;++sse)
|
|
for(unsigned pattern=0;pattern<4;++pattern)
|
|
for(unsigned rounding=0;rounding<16;++rounding) {
|
|
auto saved=clean;
|
|
constexpr unsigned precisions[]{0u,0x200u,0x300u,0u};
|
|
saved.x87[0]=(saved.x87[0]&~0x300u)|precisions[pattern];
|
|
saved.x87[0]=(saved.x87[0]&~0xc00u)|((rounding&3)<<10);
|
|
saved.mxcsr=(saved.mxcsr&~0x6000u)|((rounding>>2)<<13);
|
|
const unsigned savedX87=pattern==0?0:pattern==1?0x15:pattern==2?0x2a:0x3f;
|
|
const unsigned savedSse=pattern==0?0x3f:pattern==1?0x2a:pattern==2?0x15:0;
|
|
saved.x87[1]|=savedX87;
|
|
saved.mxcsr|=savedSse;
|
|
Write(saved);
|
|
saved=Read();
|
|
auto pending=clean;
|
|
pending.x87[1]|=x87;
|
|
pending.mxcsr|=sse;
|
|
Write(pending);
|
|
const auto input=saved;
|
|
*__error_nid_postfix()=123;
|
|
const int result=updateEnvironment(&saved);
|
|
const auto after=Read();
|
|
const int error=*__error_nid_postfix();
|
|
Write(clean);
|
|
auto expected=input;
|
|
expected.x87[1]|=x87|sse;
|
|
expected.mxcsr|=x87|sse;
|
|
Require(result==0 && error==123,"return/errno");
|
|
Require(after.x87==expected.x87 && after.mxcsr==expected.mxcsr,"restore saved controls and raise old union in both units");
|
|
Require(std::memcmp(&input,&saved,sizeof(input))==0,"input unchanged");
|
|
++cases;
|
|
}
|
|
Write(original);
|
|
std::printf("feupdateenv: %u flag/rounding combinations passed\n",cases);
|
|
}
|
|
|
|
void TagsAndPointers() {
|
|
const auto entry=Read();
|
|
auto clean=entry;
|
|
clean.x87[0]|=0x3f; clean.x87[1]&=~0xffu;
|
|
clean.mxcsr=(clean.mxcsr&~0x3fu)|(0x3f<<7);
|
|
Write(clean);
|
|
const auto original=Read();
|
|
asm volatile("fninit\n\tfld1\n\tfldpi" : : : "st","st(1)");
|
|
const auto desired=Read();
|
|
asm volatile("fninit" : : : "st","st(1)","st(2)","st(3)","st(4)","st(5)","st(6)","st(7)");
|
|
const int result=updateEnvironment(&desired);
|
|
const auto after=Read();
|
|
Write(original);
|
|
Require(result==0 && Same(after,desired),"nonempty x87 tag/TOP and instruction pointer restored");
|
|
GuestFloatingPointEnvironment guestSaved;
|
|
Require(fegetenv_nid_postfix(&guestSaved)==0,"save using existing guest fegetenv");
|
|
asm volatile("fninit" : : : "st","st(1)","st(2)","st(3)","st(4)","st(5)","st(6)","st(7)");
|
|
Environment saved;
|
|
std::memcpy(&saved, &guestSaved, sizeof(saved));
|
|
Require(updateEnvironment(&saved)==0,"restore guest fegetenv output");
|
|
const auto restored=Read();
|
|
Write(original);
|
|
Require(Same(saved,restored),"guest fegetenv round trip");
|
|
Write(entry);
|
|
}
|
|
|
|
void BoundsAndErrors() {
|
|
const auto entry=Read();
|
|
auto clean=entry;
|
|
clean.x87[0]|=0x3f; clean.x87[1]&=~0xffu;
|
|
clean.mxcsr=(clean.mxcsr&~0x3fu)|(0x3f<<7);
|
|
Write(clean);
|
|
const auto original=Read();
|
|
#ifdef _WIN32
|
|
SYSTEM_INFO info{};
|
|
GetSystemInfo(&info);
|
|
const auto page=static_cast<std::size_t>(info.dwPageSize);
|
|
auto* memory=static_cast<unsigned char*>(VirtualAlloc(nullptr,2*page,MEM_RESERVE|MEM_COMMIT,PAGE_READWRITE));
|
|
Require(memory!=nullptr,"allocate guard pages");
|
|
DWORD previous;
|
|
Require(VirtualProtect(memory+page,page,PAGE_NOACCESS,&previous)!=0,"protect trailing page");
|
|
#else
|
|
const auto page=static_cast<std::size_t>(sysconf(_SC_PAGESIZE));
|
|
auto* memory=static_cast<unsigned char*>(mmap(nullptr,2*page,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS,-1,0));
|
|
Require(memory!=MAP_FAILED,"allocate guard pages");
|
|
Require(mprotect(memory+page,page,PROT_NONE)==0,"protect trailing page");
|
|
#endif
|
|
auto* end=reinterpret_cast<Environment*>(memory+page-sizeof(Environment));
|
|
std::memcpy(end,&original,sizeof(original));
|
|
#ifdef _WIN32
|
|
Require(VirtualProtect(memory,page,PAGE_READONLY,&previous)!=0,"input read-only");
|
|
#else
|
|
Require(mprotect(memory,page,PROT_READ)==0,"input read-only");
|
|
#endif
|
|
const int result=updateEnvironment(end);
|
|
const auto after=Read();
|
|
Require(result==0 && Same(after,original),"exact32-byte read at guard-page boundary");
|
|
#ifdef _WIN32
|
|
Require(VirtualFree(memory,0,MEM_RELEASE)!=0,"release guard pages");
|
|
#else
|
|
Require(munmap(memory,2*page)==0,"release guard pages");
|
|
#endif
|
|
bool rejected=false;
|
|
*__error_nid_postfix()=123;
|
|
try { updateEnvironment(nullptr); } catch(const std::invalid_argument&) { rejected=true; }
|
|
Require(rejected && Same(Read(),original) && *__error_nid_postfix()==123,"null rejected without state or errno change");
|
|
alignas(16) std::array<std::uint32_t,128> extended;
|
|
asm volatile("fxsave %0" : "=m"(extended));
|
|
const auto supported=extended[7] ? extended[7] : 0xffbfu;
|
|
for(unsigned bit=0;bit<32;++bit) {
|
|
const auto reserved=1u<<bit;
|
|
if((supported&reserved)!=0) continue;
|
|
auto desired=original;
|
|
desired.x87[0]^=0xc00;
|
|
desired.mxcsr|=reserved;
|
|
rejected=false;
|
|
try { updateEnvironment(&desired); } catch(const std::invalid_argument&) { rejected=true; }
|
|
Require(rejected && Same(Read(),original) && *__error_nid_postfix()==123,"host-unsupported MXCSR rejected before x87 changes");
|
|
}
|
|
Write(entry);
|
|
}
|
|
|
|
void MasksAndThreads() {
|
|
const auto original=Read();
|
|
auto clean=original;
|
|
clean.x87[0]|=0x3f; clean.x87[1]&=~0xffu;
|
|
clean.mxcsr=(clean.mxcsr&~0x3fu)|(0x3f<<7);
|
|
for(unsigned a=0;a<64;++a) for(unsigned b=0;b<64;++b) {
|
|
auto desired=clean;
|
|
desired.x87[0]=(desired.x87[0]&~0x3fu)|a;
|
|
desired.mxcsr=(desired.mxcsr&~(0x3fu<<7))|(b<<7);
|
|
Write(clean);
|
|
Require(updateEnvironment(&desired)==0,"independent exception masks");
|
|
const auto actual=Read(); Write(clean);
|
|
Require(Same(actual,desired),"exception masks preserved");
|
|
}
|
|
alignas(16) std::array<std::uint32_t,128> state;
|
|
asm volatile("fxsave %0" : "=m"(state));
|
|
const auto supported=state[7]?state[7]:0xffbfu;
|
|
for(unsigned mode:{0u,0x40u,0x8000u,0x8040u}) {
|
|
if(mode&~supported) continue;
|
|
auto desired=clean; desired.mxcsr=(desired.mxcsr&~0x8040u)|mode;
|
|
Require(updateEnvironment(&desired)==0,"supported DAZ/FTZ return");
|
|
const auto actual=Read(); Write(clean);
|
|
Require(Same(actual,desired),"supported DAZ/FTZ restored");
|
|
}
|
|
std::barrier ready(2);
|
|
auto worker=[&](unsigned rounding,unsigned flags) {
|
|
const auto entry=Read();
|
|
auto desired=clean;
|
|
desired.x87[0]=(desired.x87[0]&~0xc00u)|(rounding<<10);
|
|
desired.mxcsr=(desired.mxcsr&~0x6000u)|(rounding<<13);
|
|
auto pending=clean; pending.mxcsr|=flags;
|
|
ready.arrive_and_wait();
|
|
Write(pending);
|
|
*__error_nid_postfix()=static_cast<int>(flags);
|
|
const int result=updateEnvironment(&desired);
|
|
const int error=*__error_nid_postfix();
|
|
const auto after=Read();
|
|
ready.arrive_and_wait();
|
|
Write(entry);
|
|
desired.x87[1]|=flags; desired.mxcsr|=flags;
|
|
Require(result==0 && error==static_cast<int>(flags),"thread return/errno");
|
|
Require(Same(after,desired),"thread environment and flag union");
|
|
};
|
|
std::thread a(worker,1,4),b(worker,2,8);a.join();b.join();
|
|
Write(original);
|
|
}
|
|
volatile std::sig_atomic_t entered=0;
|
|
#ifdef _WIN32
|
|
unsigned expectedFlag;
|
|
DWORD expectedException;
|
|
LONG CALLBACK Trap(EXCEPTION_POINTERS* exception) {
|
|
const auto& state=exception->ContextRecord->FltSave;
|
|
const auto active=state.StatusWord&~state.ControlWord&0x3f;
|
|
const auto code=exception->ExceptionRecord->ExceptionCode;
|
|
const bool denormal=expectedFlag==2 && code==EXCEPTION_FLT_INVALID_OPERATION;
|
|
if(entered && active==expectedFlag && (code==expectedException || denormal)) std::_Exit(0);
|
|
return EXCEPTION_CONTINUE_SEARCH;
|
|
}
|
|
#else
|
|
void Trap(int){std::_Exit(entered?0:1);}
|
|
#endif
|
|
void Pending(unsigned bit,unsigned source) {
|
|
Require(bit<6 && source<3,"trap arguments");
|
|
#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};
|
|
expectedFlag=1u<<bit;expectedException=codes[bit];
|
|
Require(AddVectoredExceptionHandler(1,Trap)!=nullptr,"install trap handler");
|
|
#else
|
|
Require(std::signal(SIGFPE,Trap)!=SIG_ERR,"install trap handler");
|
|
#endif
|
|
auto desired=Read();
|
|
desired.x87[0]=(desired.x87[0]|0x3f)&~(1u<<bit);
|
|
desired.x87[1]&=~0xffu;
|
|
desired.mxcsr=(desired.mxcsr&~0x3fu)|(0x3f<<7);
|
|
auto pending=desired;pending.x87[0]|=0x3f;
|
|
if(source==0) pending.x87[1]|=1u<<bit;
|
|
else if(source==1) pending.mxcsr|=1u<<bit;
|
|
else desired.x87[1]|=1u<<bit;
|
|
Write(pending);
|
|
entered=1;
|
|
updateEnvironment(&desired);
|
|
entered=0;
|
|
Require(false,"enabled pending exception did not trap during feupdateenv");
|
|
}
|
|
|
|
int main(int argc,char** argv) {
|
|
Require(argc==2 || argc==4,"expected libc path and optional trap bit/source");
|
|
#ifdef _WIN32
|
|
const auto library=LoadLibraryA(argv[1]);
|
|
Require(library!=nullptr,"load libc");
|
|
updateEnvironment=reinterpret_cast<UpdateEnvironment>(GetProcAddress(library,"feupdateenv_nid_postfix"));
|
|
#else
|
|
const auto library=dlopen(argv[1],RTLD_NOW);
|
|
Require(library!=nullptr,"load libc");
|
|
updateEnvironment=reinterpret_cast<UpdateEnvironment>(dlsym(library,"feupdateenv_nid_postfix"));
|
|
#endif
|
|
Require(updateEnvironment!=nullptr,"export is missing");
|
|
if(argc==4) Pending(static_cast<unsigned>(std::atoi(argv[2])),static_cast<unsigned>(std::atoi(argv[3])));
|
|
Matrix(); TagsAndPointers(); BoundsAndErrors(); MasksAndThreads();
|
|
#ifdef _WIN32
|
|
Require(FreeLibrary(library)!=0,"release library lookup handle");
|
|
#else
|
|
Require(dlclose(library)==0,"release library lookup handle");
|
|
#endif
|
|
}
|