Files
AnyPS5/core/libs/prx/libSceLibcInternal/Export.cpp
T
Scott SchusterandClaude Opus 5.5 383bffd8c7 feat(libc): implement alarm
RetroArch (PPSA99169) stops at alarm on main: `FAIL: unresolved ELF import DQXJraCc1rA from libSceLibcInternal.prx`.

alarm(seconds) arms one process-wide real-time alarm and returns the seconds left on the previous one, rounded up like FreeBSD's alarm.c (any partial second counts); alarm(0) cancels it. When it expires, SIGALRM's disposition as recorded by sigaction or signal applies: ignored, nothing happens; default, the process terminates as on FreeBSD (exit status 128 + 14); a guest handler throws, because asynchronous delivery to a guest handler is not implemented.

The timer lives in libkernel next to the signal dispositions (GuestAlarm_nid_no_patch); libSceLibcInternal, which the titles import alarm from, forwards to it.

AI-assisted (Claude Code).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 17:59:58 -05:00

140 lines
4.1 KiB
C++

#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
#include "prx/libc/include/HeapDiagnostics.hpp"
#include "prx/libc/include/HostThreadLocal.hpp"
#include <cstddef>
#include <cstdint>
#include <sstream>
#include <stdexcept>
#include <vector>
#ifdef _WIN32
#include <windows.h>
#else
#include <dlfcn.h>
#endif
extern "C" void APS5_VABI sceKernelSetThreadDtors(thread_dtors_func_t dtors);
extern "C" int APS5_VABI sceKernelGetModuleInfoFromAddr(std::uint64_t address, int flags, ModuleInfoEx* info);
namespace {
using ThreadDestructorFunction = void (APS5_VABI*)(void*);
struct ThreadDestructor {
ThreadDestructorFunction function;
void* object;
void* dsoSymbol;
};
struct ThreadDestructorsTag {};
std::vector<ThreadDestructor>& ThreadDestructors() {
return HostThreadLocal<std::vector<ThreadDestructor>, ThreadDestructorsTag>();
}
bool IsInLoadedImage(const void* address) {
if (address == nullptr)
return false;
#ifdef _WIN32
HMODULE module = nullptr;
return GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, static_cast<LPCSTR>(address), &module) != 0;
#else
Dl_info info{};
return dladdr(address, &info) != 0;
#endif
}
void CallThreadDestructor(const ThreadDestructor& destructor) {
const auto* function = reinterpret_cast<const void*>(destructor.function);
if (!IsInLoadedImage(function)) {
std::ostringstream message;
message << "thread_local destructor " << function << " of dso " << destructor.dsoSymbol << " is not in a loaded image";
throw std::runtime_error(message.str());
}
destructor.function(destructor.object);
}
void APS5_VABI RunThreadDestructors_nid_no_patch() {
auto& destructors = ThreadDestructors();
while (!destructors.empty()) {
const ThreadDestructor destructor = destructors.back();
destructors.pop_back();
CallThreadDestructor(destructor);
}
}
bool FindDsoModule(const void* dsoSymbol, KernelModule& handle) {
ModuleInfoEx info{};
info.st_size = sizeof(ModuleInfoEx);
if (sceKernelGetModuleInfoFromAddr(reinterpret_cast<std::uintptr_t>(dsoSymbol), 2, &info) != 0) {
handle = 0;
return false;
}
handle = info.id;
return true;
}
bool ForceThreadDestructorPass(KernelModule handle) {
auto& destructors = ThreadDestructors();
bool found = false;
for (std::size_t index = destructors.size(); index-- > 0;) {
const ThreadDestructor destructor = destructors[index];
KernelModule module = 0;
const bool loaded = FindDsoModule(destructor.dsoSymbol, module);
if (module != handle)
continue;
found = true;
destructors.erase(destructors.begin() + static_cast<std::ptrdiff_t>(index));
if (loaded && *static_cast<void* const*>(destructor.dsoSymbol) == destructor.dsoSymbol)
CallThreadDestructor(destructor);
}
return found;
}
void RegisterThreadExitHook() {
[[maybe_unused]] static const bool registered = [] {
sceKernelSetThreadDtors(RunThreadDestructors_nid_no_patch);
return true;
}();
}
}
extern "C" {
int Need_sceLibcInternal_nid_postfix = 1;
void APS5_VABI __cxa_finalize_nid_postfix(void* dsoHandle) {
CxaFinalize_nid_no_patch(dsoHandle);
}
void APS5_VABI sceLibcHeapGetTraceInfo_nid_postfix(Info* info) {
LibcHeapTraceInfo_nid_no_patch(info);
}
int APS5_VABI _sceLibcInternalThreadAtexit_nid_postfix(ThreadDestructorFunction destructor, void* object, void* dsoSymbol) {
RegisterThreadExitHook();
ThreadDestructors().push_back({destructor, object, dsoSymbol});
return 0;
}
void APS5_VABI _sceLibcInternalThreadDtors_nid_postfix() {
RunThreadDestructors_nid_no_patch();
}
int APS5_VABI _sceLibcInternalForceTlsDestructor_nid_postfix(KernelModule handle) {
for (int pass = 0; pass < 4; ++pass) {
if (!ForceThreadDestructorPass(handle))
break;
}
return 0;
}
unsigned int APS5_VABI GuestAlarm_nid_no_patch(unsigned int seconds);
unsigned int APS5_VABI alarm_nid_postfix(unsigned int seconds) {
return GuestAlarm_nid_no_patch(seconds);
}
}