Files
AnyPS5/core/libs/tests/GuestRaiseInHostCode.cpp
T
billythemenace2andClaude Opus 5.5 2e363d375a fix(libkernel): redirect a raise only once the target leaves host code
On Windows, sceKernelRaiseException suspends the target and redirects it to
the guest handler wherever it stopped, except inside NtContinue or
winpthreads. A target stopped inside host code (a C runtime call, a host
library) can hold a host lock there; a handler that then waits, as a
collector's thread-stop handler does, keeps it held, and every host thread
that needs the lock stops with it. SONIC SUPERSTARS stopped at frame 65 this
way: its "UDS" thread was redirected inside the stderr write of a trophy
message and parked in sceKernelWaitEventFlag holding the stream lock, and the
driver's logging threads blocked on it.

A target running code of a module other than the executable or a guest
module is now resumed and suspended again until it is back in guest code,
for up to the existing retry limit; a target blocked in an ntdll wait is
still redirected there, as before.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-18 21:24:44 +01:00

70 lines
2.4 KiB
C++

#include "SceTypes.hpp"
#include <atomic>
#include <chrono>
#include <cstdio>
#include <cstdint>
#include <cstdlib>
#include <string>
#include <thread>
extern "C" {
int APS5_VABI scePthreadCreate(Pthread*, const PthreadAttr*, PthreadEntry, void*, const char*);
int APS5_VABI scePthreadJoin(Pthread, void**);
int APS5_VABI sceKernelInstallExceptionHandler(int, void*);
int APS5_VABI sceKernelRemoveExceptionHandler(int);
int APS5_VABI sceKernelRaiseException(Pthread, int);
}
static constexpr int SigUsr1 = 30;
static constexpr int Raises = 20;
static void Require(bool value) { if (!value) std::abort(); }
static std::FILE* shared = nullptr;
static std::atomic<int> deliveries{0};
static std::atomic<int> released{0};
static std::atomic<bool> stop{false};
static std::atomic<bool> started{false};
static void APS5_VABI Handler(int, void*) {
const auto delivery = deliveries.fetch_add(1) + 1;
while (released.load() < delivery) std::this_thread::yield();
}
static void* APS5_VABI Writer(void*) {
const std::string block(1u << 20u, 'x');
started.store(true);
while (!stop.load()) {
Require(std::fwrite(block.data(), 1, block.size(), shared) == block.size());
Require(std::fseek(shared, 0, SEEK_SET) == 0);
volatile std::uint64_t spin = 0;
for (std::uint64_t i = 0; i < 200000; ++i) spin = spin + i;
}
return nullptr;
}
int main() {
std::atomic<bool> done{false};
std::thread watchdog([&done] {
for (int i = 0; i < 20000 && !done.load(); ++i) std::this_thread::sleep_for(std::chrono::milliseconds(1));
if (!done.load()) std::abort();
});
shared = std::tmpfile();
Require(shared != nullptr);
Require(sceKernelInstallExceptionHandler(SigUsr1, reinterpret_cast<void*>(&Handler)) == 0);
Pthread writer = nullptr;
Require(scePthreadCreate(&writer, nullptr, Writer, nullptr, "host writer") == 0);
while (!started.load()) std::this_thread::yield();
for (int raise = 1; raise <= Raises; ++raise) {
Require(sceKernelRaiseException(writer, SigUsr1) == 0);
while (deliveries.load() < raise) std::this_thread::yield();
Require(std::fputc('y', shared) == 'y');
released.store(raise);
}
stop.store(true);
Require(scePthreadJoin(writer, nullptr) == 0);
Require(sceKernelRemoveExceptionHandler(SigUsr1) == 0);
std::fclose(shared);
done.store(true);
watchdog.join();
}