Files
AnyPS5/core/libs/tests/GuestExceptionFault.cpp
T
Jevro1337andClaude Opus 5.5 d6628a4d1e test(libkernel): pass inheritable handles to the fault test's child and read its output while waiting
STARTF_USESTDHANDLES needs inheritable handles, but stdin and stderr were the
parent's own, which ctest need not make inheritable; the child now gets NUL
for both. The parent read the child's stdout only after it ended, so a child
that filled the pipe would have blocked until the timeout; a thread now reads
it while the parent waits.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-03 21:01:19 +01:00

605 lines
22 KiB
C++

#include "SceTypes.hpp"
#include "prx/libc/include/Api.hpp"
#include "prx/libc/include/GuestWriteWatch.hpp"
#include "prx/libkernel/include/Api.hpp"
#include <atomic>
#include <climits>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <thread>
#ifdef _WIN32
#include <windows.h>
#else
#include <chrono>
#include <csignal>
#include <spawn.h>
#include <sys/mman.h>
#include <sys/resource.h>
#include <sys/wait.h>
#include <unistd.h>
extern char** environ;
#endif
struct RegisterState {
std::uint8_t vectorIn[32];
std::uint8_t vectorOut[32];
std::uint32_t avx;
std::uint32_t padding;
std::uint64_t r12;
std::uint64_t r13;
};
static_assert(offsetof(RegisterState, vectorOut) == 32 && offsetof(RegisterState, avx) == 64 && offsetof(RegisterState, r12) == 72 && offsetof(RegisterState, r13) == 80);
extern "C" {
std::uint64_t APS5_VABI FaultTestRead(std::uintptr_t address);
void FaultTestReadSite();
void FaultTestReadResume();
std::uint64_t APS5_VABI FaultTestWrite(std::uintptr_t address, std::uint64_t value);
void FaultTestWriteSite();
void FaultTestWriteResume();
std::uint64_t APS5_VABI FaultTestIllegal();
void FaultTestIllegalSite();
void FaultTestIllegalResume();
std::uint64_t APS5_VABI FaultTestDivide(std::int32_t dividend, std::int32_t divisor);
void FaultTestDivideSite();
void FaultTestDivideResume();
void APS5_VABI FaultTestRegisters(std::uintptr_t address, RegisterState* state);
void FaultTestRegistersSite();
void FaultTestRegistersResume();
std::uint64_t APS5_VABI FaultTestJump(std::uintptr_t address, void* buffer);
void FaultTestJumpSite();
std::uint64_t APS5_VABI FaultTestSimdDivide();
void FaultTestSimdDivideSite();
void FaultTestSimdDivideResume();
}
asm(".text\n"
".globl FaultTestRead\n"
"FaultTestRead:\n"
" xorl %eax, %eax\n"
".globl FaultTestReadSite\n"
"FaultTestReadSite:\n"
" movq (%rdi), %rax\n"
".globl FaultTestReadResume\n"
"FaultTestReadResume:\n"
" ret\n"
".globl FaultTestWrite\n"
"FaultTestWrite:\n"
" xorl %eax, %eax\n"
".globl FaultTestWriteSite\n"
"FaultTestWriteSite:\n"
" movq %rsi, (%rdi)\n"
".globl FaultTestWriteResume\n"
"FaultTestWriteResume:\n"
" ret\n"
".globl FaultTestIllegal\n"
"FaultTestIllegal:\n"
" xorl %eax, %eax\n"
".globl FaultTestIllegalSite\n"
"FaultTestIllegalSite:\n"
" ud2\n"
".globl FaultTestIllegalResume\n"
"FaultTestIllegalResume:\n"
" ret\n"
".globl FaultTestDivide\n"
"FaultTestDivide:\n"
" movl %edi, %eax\n"
" cltd\n"
".globl FaultTestDivideSite\n"
"FaultTestDivideSite:\n"
" idivl %esi\n"
".globl FaultTestDivideResume\n"
"FaultTestDivideResume:\n"
" ret\n"
".globl FaultTestRegisters\n"
"FaultTestRegisters:\n"
" pushq %r12\n"
" pushq %r13\n"
" pushq %rbx\n"
" movq %rsi, %rbx\n"
" movabsq $0x1111222233334444, %r12\n"
" movabsq $0x5555666677778888, %r13\n"
" cmpl $0, 64(%rbx)\n"
" je 1f\n"
" vmovdqu 0(%rbx), %ymm8\n"
" jmp 2f\n"
"1:\n"
" movdqu 0(%rbx), %xmm8\n"
"2:\n"
".globl FaultTestRegistersSite\n"
"FaultTestRegistersSite:\n"
" movq (%rdi), %rax\n"
".globl FaultTestRegistersResume\n"
"FaultTestRegistersResume:\n"
" movq %r12, 72(%rbx)\n"
" movq %r13, 80(%rbx)\n"
" cmpl $0, 64(%rbx)\n"
" je 3f\n"
" vmovdqu %ymm8, 32(%rbx)\n"
" vzeroupper\n"
" jmp 4f\n"
"3:\n"
" movdqu %xmm8, 32(%rbx)\n"
"4:\n"
" popq %rbx\n"
" popq %r13\n"
" popq %r12\n"
" ret\n"
".globl FaultTestJump\n"
"FaultTestJump:\n"
" pushq %rbx\n"
" pushq %rbp\n"
" pushq %r12\n"
" pushq %r13\n"
" pushq %r14\n"
" pushq %r15\n"
" subq $8, %rsp\n"
" leaq 5f(%rip), %rax\n"
" movq %rax, 0(%rsi)\n"
" movq %rbx, 8(%rsi)\n"
" movq %rsp, 16(%rsi)\n"
" movq %rbp, 24(%rsi)\n"
" movq %r12, 32(%rsi)\n"
" movq %r13, 40(%rsi)\n"
" movq %r14, 48(%rsi)\n"
" movq %r15, 56(%rsi)\n"
" stmxcsr 64(%rsi)\n"
" fnstcw 68(%rsi)\n"
".globl FaultTestJumpSite\n"
"FaultTestJumpSite:\n"
" movq (%rdi), %rax\n"
" xorl %eax, %eax\n"
"5:\n"
" addq $8, %rsp\n"
" popq %r15\n"
" popq %r14\n"
" popq %r13\n"
" popq %r12\n"
" popq %rbp\n"
" popq %rbx\n"
" ret\n"
".globl FaultTestSimdDivide\n"
"FaultTestSimdDivide:\n"
" subq $8, %rsp\n"
" stmxcsr (%rsp)\n"
" movl $0x1d80, 4(%rsp)\n"
" ldmxcsr 4(%rsp)\n"
" movl $1, %eax\n"
" cvtsi2ss %eax, %xmm0\n"
" xorps %xmm1, %xmm1\n"
" xorl %eax, %eax\n"
".globl FaultTestSimdDivideSite\n"
"FaultTestSimdDivideSite:\n"
" divss %xmm1, %xmm0\n"
".globl FaultTestSimdDivideResume\n"
"FaultTestSimdDivideResume:\n"
" ldmxcsr (%rsp)\n"
" addq $8, %rsp\n"
" ret\n");
static constexpr int GUEST_SIGILL = 4;
static constexpr int GUEST_SIGFPE = 8;
static constexpr int GUEST_SIGBUS = 10;
static constexpr int GUEST_SIGSEGV = 11;
static constexpr std::uint32_t T_PRIVINFLT = 1;
static constexpr std::uint32_t T_PROTFLT = 9;
static constexpr std::uint32_t T_PAGEFLT = 12;
static constexpr std::uint32_t T_DIVIDE = 18;
static constexpr std::uint32_t T_XMMFLT = 29;
static constexpr std::uint64_t PGEX_P = 0x1;
static constexpr std::uint64_t PGEX_W = 0x2;
static constexpr std::uint64_t PGEX_U = 0x4;
static constexpr std::size_t RbxOffset = 0x80;
static constexpr std::size_t RaxOffset = 0x78;
static constexpr std::size_t R12Offset = 0xa0;
static constexpr std::size_t R13Offset = 0xa8;
static constexpr std::size_t TrapnoOffset = 0xc0;
static constexpr std::size_t AddrOffset = 0xc8;
static constexpr std::size_t ErrOffset = 0xd8;
static constexpr std::size_t RipOffset = 0xe0;
static constexpr std::size_t Xmm8Offset = 0x140 + 160 + 8 * 16;
static constexpr std::uintptr_t NullAddress = 0x10;
static constexpr std::uintptr_t NonCanonicalAddress = 0x8000000000000000ULL;
static constexpr std::uint64_t Resumed = 0x600dcafe0000beefULL;
static constexpr std::uint64_t PatchedR12 = 0x5a5a1234abcd0001ULL;
static constexpr int JumpValue = 7;
static constexpr std::size_t PageBytes = 4096;
static constexpr std::size_t WatchedBytes = 65536;
static constexpr int RetryCalls = 3;
static constexpr int ChildTimeoutSeconds = 10;
static constexpr const char* RemoveAndReturnMode = "remove-and-return";
static constexpr const char* HandledMarker = "[guest_exception_fault] handler removed itself\n";
enum class Mode { Resume, Registers, Nested, Jump, Retry, Unprotect, Remove };
static void Require(bool value) { if (!value) std::abort(); }
template <typename TValue>
static TValue Field(void* context, std::size_t offset) {
TValue value{};
std::memcpy(&value, static_cast<unsigned char*>(context) + offset, sizeof(value));
return value;
}
template <typename TValue>
static void SetField(void* context, std::size_t offset, TValue value) {
std::memcpy(static_cast<unsigned char*>(context) + offset, &value, sizeof(value));
}
static std::uintptr_t Address(void (*label)()) {
return reinterpret_cast<std::uintptr_t>(label);
}
struct Expected {
int signum;
std::uint32_t trapno;
std::uint64_t addr;
std::uintptr_t site;
std::uintptr_t resume;
};
static Mode mode = Mode::Resume;
static Expected expected{};
static std::atomic<int> calls{0};
static std::atomic<int> nestedCalls{0};
static std::uint64_t observedErr = 0;
static std::atomic<std::thread::id> handlerThread;
static RegisterState* registerState = nullptr;
static void* unprotectPage = nullptr;
alignas(16) static std::uint64_t jumpBuffer[16];
static void MakeWritable(void* page) {
#ifdef _WIN32
DWORD previous = 0;
Require(VirtualProtect(page, PageBytes, PAGE_READWRITE, &previous) != 0);
#else
Require(mprotect(page, PageBytes, PROT_READ | PROT_WRITE) == 0);
#endif
}
static void MarkHandled() {
#ifdef _WIN32
DWORD written = 0;
WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), HandledMarker, static_cast<DWORD>(std::strlen(HandledMarker)), &written, nullptr);
#else
Require(write(STDOUT_FILENO, HandledMarker, std::strlen(HandledMarker)) == static_cast<ssize_t>(std::strlen(HandledMarker)));
#endif
}
static void Check(int signum, void* context, const Expected& want) {
Require(signum == want.signum);
Require(Field<std::uint32_t>(context, TrapnoOffset) == want.trapno);
Require(Field<std::uint64_t>(context, AddrOffset) == want.addr);
Require(Field<std::uint64_t>(context, RipOffset) == want.site);
}
static void Resume(void* context, std::uintptr_t resume) {
SetField<std::uint64_t>(context, RipOffset, resume);
SetField<std::uint64_t>(context, RaxOffset, Resumed);
}
static void APS5_VABI IllegalHandler(int signum, void* context) {
if (mode == Mode::Nested && calls.load() == 1) {
Check(signum, context, {GUEST_SIGILL, T_PRIVINFLT, 0, Address(FaultTestIllegalSite), 0});
nestedCalls.fetch_add(1);
Resume(context, Address(FaultTestIllegalResume));
return;
}
Check(signum, context, expected);
calls.fetch_add(1);
Resume(context, expected.resume);
}
static void APS5_VABI Handler(int signum, void* context) {
Check(signum, context, expected);
observedErr = Field<std::uint64_t>(context, ErrOffset);
handlerThread.store(std::this_thread::get_id());
const int call = calls.fetch_add(1) + 1;
if (mode == Mode::Retry && call < RetryCalls) return;
if (mode == Mode::Unprotect) {
MakeWritable(unprotectPage);
return;
}
if (mode == Mode::Remove) {
MarkHandled();
Require(sceKernelRemoveExceptionHandler(signum) == 0);
return;
}
if (mode == Mode::Jump) longjmp_nid_postfix(jumpBuffer, JumpValue);
if (mode == Mode::Nested) Require(FaultTestIllegal() == Resumed);
if (mode == Mode::Registers) {
Require(Field<std::uint64_t>(context, R13Offset) == 0x5555666677778888ULL);
Require(Field<std::uint64_t>(context, RbxOffset) == reinterpret_cast<std::uint64_t>(registerState));
Require(std::memcmp(static_cast<unsigned char*>(context) + Xmm8Offset, registerState->vectorIn, 16) == 0);
SetField<std::uint64_t>(context, R12Offset, PatchedR12);
SetField<std::uint64_t>(context, RipOffset, expected.resume);
if (registerState->avx != 0) asm volatile("vzeroall" ::: "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7", "xmm8", "xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15");
return;
}
Resume(context, expected.resume);
}
static void Arm(Mode next, const Expected& want) {
mode = next;
expected = want;
calls.store(0);
nestedCalls.store(0);
observedErr = 0;
}
static void* Pages(std::size_t bytes, bool writable) {
#ifdef _WIN32
void* pages = VirtualAlloc(nullptr, bytes, MEM_RESERVE | MEM_COMMIT, writable ? PAGE_READWRITE : PAGE_READONLY);
Require(pages != nullptr);
#else
void* pages = mmap(nullptr, bytes, writable ? PROT_READ | PROT_WRITE : PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
Require(pages != MAP_FAILED);
#endif
return pages;
}
static void FreePages(void* pages, std::size_t bytes) {
#ifdef _WIN32
(void)bytes;
Require(VirtualFree(pages, 0, MEM_RELEASE) != 0);
#else
Require(munmap(pages, bytes) == 0);
#endif
}
static void TestRead() {
Arm(Mode::Resume, {GUEST_SIGSEGV, T_PAGEFLT, NullAddress, Address(FaultTestReadSite), Address(FaultTestReadResume)});
Require(FaultTestRead(NullAddress) == Resumed);
Require(calls.load() == 1);
Require((observedErr & PGEX_U) != 0);
Require((observedErr & (PGEX_W | PGEX_P)) == 0);
Require(handlerThread.load() == std::this_thread::get_id());
}
static void TestWriteToReadOnly() {
void* page = Pages(PageBytes, false);
const auto address = reinterpret_cast<std::uintptr_t>(page) + 8;
Require(*reinterpret_cast<volatile std::uint64_t*>(address) == 0);
Arm(Mode::Resume, {GUEST_SIGSEGV, T_PAGEFLT, address, Address(FaultTestWriteSite), Address(FaultTestWriteResume)});
Require(FaultTestWrite(address, 1) == Resumed);
Require(calls.load() == 1);
Require((observedErr & (PGEX_U | PGEX_W | PGEX_P)) == (PGEX_U | PGEX_W | PGEX_P));
Require(*reinterpret_cast<volatile std::uint64_t*>(address) == 0);
FreePages(page, PageBytes);
}
static void TestIllegal() {
Arm(Mode::Resume, {GUEST_SIGILL, T_PRIVINFLT, 0, Address(FaultTestIllegalSite), Address(FaultTestIllegalResume)});
Require(FaultTestIllegal() == Resumed);
Require(calls.load() == 1);
}
static void TestDivide() {
Arm(Mode::Resume, {GUEST_SIGFPE, T_DIVIDE, 0, Address(FaultTestDivideSite), Address(FaultTestDivideResume)});
Require(FaultTestDivide(1, 0) == Resumed);
Require(calls.load() == 1);
Arm(Mode::Resume, {GUEST_SIGFPE, T_DIVIDE, 0, Address(FaultTestDivideSite), Address(FaultTestDivideResume)});
Require(FaultTestDivide(INT_MIN, -1) == Resumed);
Require(calls.load() == 1);
}
static void TestSimdDivide() {
Arm(Mode::Resume, {GUEST_SIGFPE, T_XMMFLT, 0, Address(FaultTestSimdDivideSite), Address(FaultTestSimdDivideResume)});
Require(FaultTestSimdDivide() == Resumed);
Require(calls.load() == 1);
}
static void TestNonCanonical() {
Arm(Mode::Resume, {GUEST_SIGBUS, T_PROTFLT, 0, Address(FaultTestReadSite), Address(FaultTestReadResume)});
Require(FaultTestRead(NonCanonicalAddress) == Resumed);
Require(calls.load() == 1);
}
static void TestRegisters() {
RegisterState state{};
for (std::size_t i = 0; i < sizeof(state.vectorIn); ++i) state.vectorIn[i] = static_cast<std::uint8_t>(0xa0 + i);
state.avx = __builtin_cpu_supports("avx") ? 1 : 0;
registerState = &state;
Arm(Mode::Registers, {GUEST_SIGSEGV, T_PAGEFLT, NullAddress, Address(FaultTestRegistersSite), Address(FaultTestRegistersResume)});
FaultTestRegisters(NullAddress, &state);
Require(calls.load() == 1);
Require(state.r12 == PatchedR12);
Require(state.r13 == 0x5555666677778888ULL);
Require(std::memcmp(state.vectorOut, state.vectorIn, state.avx != 0 ? 32 : 16) == 0);
registerState = nullptr;
}
static void TestNested() {
Arm(Mode::Nested, {GUEST_SIGSEGV, T_PAGEFLT, NullAddress, Address(FaultTestReadSite), Address(FaultTestReadResume)});
Require(FaultTestRead(NullAddress) == Resumed);
Require(calls.load() == 1);
Require(nestedCalls.load() == 1);
}
static void TestJumpOut() {
for (int round = 0; round < 3; ++round) {
Arm(Mode::Jump, {GUEST_SIGSEGV, T_PAGEFLT, NullAddress, Address(FaultTestJumpSite), 0});
Require(FaultTestJump(NullAddress, jumpBuffer) == JumpValue);
Require(calls.load() == 1);
}
TestRead();
}
static void TestWatchedStore() {
using namespace GuestWriteWatch;
if (!GuestWriteWatchAvailable_nid_postfix()) {
std::puts("write watch unavailable: watched store not tested");
return;
}
void* watched = Pages(WatchedBytes, true);
const auto begin = reinterpret_cast<std::uintptr_t>(watched);
GuestWriteWatchRegister_nid_postfix(watched, WatchedBytes);
const auto collect = [&] {
std::uintptr_t written = 0;
Require(GuestWriteWatchCollect_nid_postfix(begin, WatchedBytes, [](void* context, std::uintptr_t first, std::uintptr_t end) {
auto& address = *static_cast<std::uintptr_t*>(context);
if (address == 0) address = first;
(void)end;
}, &written));
return written;
};
collect();
Require(collect() == 0);
const auto address = begin + WatchedBytes / 2 + 8;
Arm(Mode::Resume, {GUEST_SIGSEGV, T_PAGEFLT, address, Address(FaultTestWriteSite), Address(FaultTestWriteResume)});
Require(FaultTestWrite(address, 5) == 0);
Require(calls.load() == 0);
Require(*reinterpret_cast<volatile std::uint64_t*>(address) == 5);
const auto written = collect();
Require(written != 0 && written <= address && address < begin + WatchedBytes);
GuestWriteWatchUnregister_nid_postfix(watched, WatchedBytes);
FreePages(watched, WatchedBytes);
}
static void TestReturnUnchanged() {
Arm(Mode::Retry, {GUEST_SIGSEGV, T_PAGEFLT, NullAddress, Address(FaultTestReadSite), Address(FaultTestReadResume)});
Require(FaultTestRead(NullAddress) == Resumed);
Require(calls.load() == RetryCalls);
}
static void TestFixAndReturn() {
void* page = Pages(PageBytes, false);
const auto address = reinterpret_cast<std::uintptr_t>(page) + 8;
unprotectPage = page;
Arm(Mode::Unprotect, {GUEST_SIGSEGV, T_PAGEFLT, address, Address(FaultTestWriteSite), 0});
Require(FaultTestWrite(address, 9) == 0);
Require(calls.load() == 1);
Require(*reinterpret_cast<volatile std::uint64_t*>(address) == 9);
unprotectPage = nullptr;
FreePages(page, PageBytes);
}
static int RemoveAndReturnChild() {
#ifdef _WIN32
SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
#else
const rlimit noCore{0, 0};
setrlimit(RLIMIT_CORE, &noCore);
#endif
Require(sceKernelInstallExceptionHandler(GUEST_SIGSEGV, reinterpret_cast<void*>(&Handler)) == 0);
Arm(Mode::Remove, {GUEST_SIGSEGV, T_PAGEFLT, NullAddress, Address(FaultTestReadSite), 0});
FaultTestRead(NullAddress);
return EXIT_SUCCESS;
}
static void TestRemoveAndReturn(const char* self) {
std::string output;
#ifdef _WIN32
(void)self;
SECURITY_ATTRIBUTES attributes{sizeof(attributes), nullptr, TRUE};
HANDLE readEnd = nullptr;
HANDLE writeEnd = nullptr;
Require(CreatePipe(&readEnd, &writeEnd, &attributes, 0) != 0);
Require(SetHandleInformation(readEnd, HANDLE_FLAG_INHERIT, 0) != 0);
wchar_t path[MAX_PATH]{};
Require(GetModuleFileNameW(nullptr, path, MAX_PATH) != 0);
std::wstring command = L"\"" + std::wstring(path) + L"\" remove-and-return";
HANDLE nul = CreateFileW(L"NUL", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, &attributes, OPEN_EXISTING, 0, nullptr);
Require(nul != INVALID_HANDLE_VALUE);
STARTUPINFOW startup{};
startup.cb = sizeof(startup);
startup.dwFlags = STARTF_USESTDHANDLES;
startup.hStdInput = nul;
startup.hStdOutput = writeEnd;
startup.hStdError = nul;
PROCESS_INFORMATION child{};
Require(CreateProcessW(path, command.data(), nullptr, nullptr, TRUE, 0, nullptr, nullptr, &startup, &child) != 0);
CloseHandle(writeEnd);
CloseHandle(nul);
std::thread reader([&] {
char buffer[64];
DWORD bytes = 0;
while (ReadFile(readEnd, buffer, sizeof(buffer), &bytes, nullptr) != 0 && bytes != 0) output.append(buffer, bytes);
});
const DWORD waited = WaitForSingleObject(child.hProcess, ChildTimeoutSeconds * 1000);
if (waited != WAIT_OBJECT_0) TerminateProcess(child.hProcess, 1);
reader.join();
Require(waited == WAIT_OBJECT_0);
DWORD code = 0;
Require(GetExitCodeProcess(child.hProcess, &code) != 0);
Require(code == static_cast<DWORD>(EXCEPTION_ACCESS_VIOLATION));
CloseHandle(readEnd);
CloseHandle(child.hThread);
CloseHandle(child.hProcess);
#else
int pipeEnds[2];
Require(pipe(pipeEnds) == 0);
posix_spawn_file_actions_t actions;
Require(posix_spawn_file_actions_init(&actions) == 0);
Require(posix_spawn_file_actions_adddup2(&actions, pipeEnds[1], STDOUT_FILENO) == 0);
Require(posix_spawn_file_actions_addclose(&actions, pipeEnds[0]) == 0);
std::string program = self;
std::string argument = RemoveAndReturnMode;
char* arguments[] = {program.data(), argument.data(), nullptr};
pid_t child = 0;
Require(posix_spawn(&child, "/proc/self/exe", &actions, nullptr, arguments, environ) == 0);
posix_spawn_file_actions_destroy(&actions);
close(pipeEnds[1]);
int status = 0;
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(ChildTimeoutSeconds);
pid_t waited = 0;
while ((waited = waitpid(child, &status, WNOHANG)) == 0 && std::chrono::steady_clock::now() < deadline) std::this_thread::sleep_for(std::chrono::milliseconds(10));
if (waited == 0) {
kill(child, SIGKILL);
waitpid(child, &status, 0);
}
Require(waited == child);
Require(WIFSIGNALED(status) && WTERMSIG(status) == SIGSEGV);
char buffer[64];
ssize_t bytes = 0;
while ((bytes = read(pipeEnds[0], buffer, sizeof(buffer))) > 0) output.append(buffer, static_cast<std::size_t>(bytes));
close(pipeEnds[0]);
#endif
const auto first = output.find(HandledMarker);
Require(first != std::string::npos && output.find(HandledMarker, first + 1) == std::string::npos);
}
static void TestOtherThread() {
Arm(Mode::Resume, {GUEST_SIGSEGV, T_PAGEFLT, NullAddress, Address(FaultTestReadSite), Address(FaultTestReadResume)});
std::thread::id worker;
std::uint64_t result = 0;
std::thread thread([&] {
worker = std::this_thread::get_id();
result = FaultTestRead(NullAddress);
});
thread.join();
Require(result == Resumed);
Require(calls.load() == 1);
Require(handlerThread.load() == worker);
}
int main(int argc, char** argv) {
if (argc > 1 && std::strcmp(argv[1], RemoveAndReturnMode) == 0) return RemoveAndReturnChild();
__builtin_cpu_init();
Require(sceKernelInstallExceptionHandler(GUEST_SIGSEGV, reinterpret_cast<void*>(&Handler)) == 0);
Require(sceKernelInstallExceptionHandler(GUEST_SIGBUS, reinterpret_cast<void*>(&Handler)) == 0);
Require(sceKernelInstallExceptionHandler(GUEST_SIGFPE, reinterpret_cast<void*>(&Handler)) == 0);
Require(sceKernelInstallExceptionHandler(GUEST_SIGILL, reinterpret_cast<void*>(&IllegalHandler)) == 0);
TestRead();
TestWriteToReadOnly();
TestIllegal();
TestDivide();
TestSimdDivide();
TestNonCanonical();
TestRegisters();
TestNested();
TestJumpOut();
TestOtherThread();
TestWatchedStore();
TestReturnUnchanged();
TestFixAndReturn();
TestRemoveAndReturn(argv[0]);
for (const int signum : {GUEST_SIGSEGV, GUEST_SIGBUS, GUEST_SIGFPE, GUEST_SIGILL}) Require(sceKernelRemoveExceptionHandler(signum) == 0);
}