feat(libc): operator new/delete, bounds-checked functions, char locale facets and runtime helpers

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ButHENCbXXt8h4EZuWUabJ
This commit is contained in:
Dean Galvin 2026-09-28 14:22:48 +13:00
parent 7656458121
commit a599eca727
12 changed files with 348 additions and 11 deletions

View File

@ -1,7 +1,14 @@
#include "prx/libc/include/General.hpp"
#include "prx/libc/include/HeapDiagnostics.hpp"
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);
}

View File

@ -9,6 +9,7 @@
#include "general/ExportMacros.hpp"
extern "C" void NotImplemented_nid_no_patch(const char* funcName);
extern "C" void CxaFinalize_nid_no_patch(void* dsoHandle);
extern "C" std::filesystem::path ResolvePath_nid_no_patch(const char* path);
// Guest path aliases: a guest prefix such as "/_sm/0" (a save-data mount point) resolves to a host

View File

@ -9,4 +9,12 @@ inline unsigned int GccAtomicFetchSub(volatile unsigned int* target, unsigned in
return __atomic_fetch_sub(target, value, __ATOMIC_SEQ_CST);
}
inline bool GccAtomicCompareExchangeWeak(volatile unsigned int* target, unsigned int* expected, unsigned int desired) {
return __atomic_compare_exchange_n(target, expected, desired, true, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST);
}
inline unsigned int GccAtomicLoad(volatile unsigned int* target) {
return __atomic_load_n(target, __ATOMIC_SEQ_CST);
}
#endif

View File

@ -63,6 +63,14 @@ FileStream* APS5_VABI fopen_nid_postfix(const char* filename, const char* mode)
return stream.release();
}
int APS5_VABI fopen_s_nid_postfix(FileStream** result, const char* filename, const char* mode) {
constexpr int GuestEinval = 22;
constexpr int GuestEnoent = 2;
if (!result || !filename || !mode) return GuestEinval;
*result = fopen_nid_postfix(filename, mode);
return *result ? 0 : GuestEnoent;
}
int APS5_VABI fclose_nid_postfix(FileStream* stream) {
GetNativeStream(stream);
std::unique_ptr<FileStream> owner(stream->IsDynamic() ? stream : nullptr);
@ -121,6 +129,17 @@ int APS5_VABI fseek_nid_postfix(FileStream* stream, std::int64_t offset, int ori
std::int64_t APS5_VABI ftell_nid_postfix(FileStream* stream) { return ftello_nid_postfix(stream); }
int APS5_VABI fgetpos_nid_postfix(FileStream* stream, std::int64_t* position) {
if (!position) throw std::invalid_argument("fgetpos: null position");
*position = ftello_nid_postfix(stream);
return 0;
}
int APS5_VABI fsetpos_nid_postfix(FileStream* stream, const std::int64_t* position) {
if (!position) throw std::invalid_argument("fsetpos: null position");
return fseeko_nid_postfix(stream, *position, SEEK_SET);
}
int APS5_VABI fputs_nid_postfix(const char* str, FileStream* stream) {
if (!str) throw std::runtime_error("fputs: null string");
const int result = std::fputs(str, GetNativeStream(stream));
@ -149,6 +168,15 @@ void* APS5_VABI memalign_nid_postfix(size_t alignment, size_t size) {
return ApplicationHeapAlign_nid_no_patch(alignment, size);
}
void* APS5_VABI aligned_alloc_nid_postfix(size_t alignment, size_t size) {
return ApplicationHeapAlign_nid_no_patch(alignment, size);
}
void* APS5_VABI reallocalign_nid_postfix(void* ptr, size_t size, size_t alignment) {
if (ptr != nullptr) NotImplemented_nid_no_patch("reallocalign of an existing block");
return ApplicationHeapAlign_nid_no_patch(alignment, size);
}
void* APS5_VABI calloc_nid_postfix(size_t count, size_t size) {
return ApplicationHeapCalloc_nid_no_patch(count, size);
}

View File

@ -169,6 +169,56 @@ int APS5_VABI vsnprintf_nid_postfix(char* str, size_t size, const char* format,
#endif
}
#ifdef _WIN32
int APS5_VABI snprintf_s_nid_postfix(char* buffer, size_t size, const char* format, ...) {
__builtin_sysv_va_list args;
__builtin_sysv_va_start(args, format);
const int result = LibcDetail::FormatWindows(buffer, size, format, args);
__builtin_sysv_va_end(args);
return result;
}
int APS5_VABI printf_s_nid_postfix(const char* format, ...) {
__builtin_sysv_va_list args;
__builtin_sysv_va_start(args, format);
const int result = LibcDetail::PrintWindows(format, args);
__builtin_sysv_va_end(args);
return result;
}
#else
int APS5_VABI vsprintf_s_nid_postfix(char* buffer, size_t size, const char* format, VaList* args) {
return std::vsnprintf(buffer, size, format, *reinterpret_cast<std::va_list*>(args));
}
int APS5_VABI sprintf_s_nid_postfix(char* buffer, size_t size, const char* format, ...) {
std::va_list args;
va_start(args, format);
const int result = std::vsnprintf(buffer, size, format, args);
va_end(args);
return result;
}
int APS5_VABI snprintf_s_nid_postfix(char* buffer, size_t size, const char* format, ...) {
std::va_list args;
va_start(args, format);
const int result = std::vsnprintf(buffer, size, format, args);
va_end(args);
return result;
}
int APS5_VABI printf_s_nid_postfix(const char* format, ...) {
std::va_list args;
va_start(args, format);
const int result = std::vprintf(format, args);
va_end(args);
return result;
}
#endif
int APS5_VABI puts_nid_postfix(const char* s) {
return std::puts(s);
}

View File

@ -143,6 +143,9 @@ int APS5_VABI tolower_nid_postfix(int c) { return ConvertCharacter(c, false); }
std::uint64_t _ZNSt5ctypeIcE2idE_nid_postfix = 0;
std::uint64_t _ZNSt5ctypeIwE2idE_nid_postfix = 0;
std::uint64_t _ZNSt7collateIwE2idE_nid_postfix = 0;
std::uint64_t _ZNSt7collateIcE2idE_nid_postfix = 0;
std::uint64_t _ZNSt7codecvtIcc9_MbstatetE2idE_nid_postfix = 0;
std::uintptr_t _ZTVSt7codecvtIcc9_MbstatetE_nid_postfix[16] {};
std::uint64_t _ZNSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix = 0;
std::uintptr_t _ZTVSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE_nid_postfix[12] {};
@ -185,6 +188,10 @@ void APS5_VABI _ZNSt7collateIwE7_GetcatEPPKNSt6locale5facetEPKS1__nid_postfix(Gu
NotImplemented_nid_no_patch(__func__);
}
void APS5_VABI _ZNSt7collateIcE7_GetcatEPPKNSt6locale5facetEPKS1__nid_postfix(GuestLocale::Facet**, const GuestLocale::Implementation*) {
NotImplemented_nid_no_patch(__func__);
}
void APS5_VABI _ZNSt8_LocinfoC1EPKc_nid_postfix(GuestLocale::LocinfoStorage* self, const char* localeName) {
if (self == nullptr || localeName == nullptr || std::strcmp(localeName, "C") != 0) throw std::invalid_argument("_Locinfo: only the C locale is supported");
new (self) GuestLocale::LocinfoStorage{};

View File

@ -1,4 +1,6 @@
#include <algorithm>
#include <cstdio>
#include <iterator>
#include <cstdlib>
#include <stdexcept>
#include <functional>
@ -6,6 +8,9 @@
#include <mutex>
#include <vector>
#include <utility>
#include <random>
#include <string>
#include <cinttypes>
#include "prx/libc/include/General.hpp"
#include "prx/libc/include/specifics/gcc/AtomicOps.hpp"
@ -15,6 +20,16 @@ namespace {
std::recursive_mutex g_sysLock;
struct ExitDestructor {
void (*func)(void*);
void* arg;
void* dsoHandle;
};
std::mutex g_exitMutex;
std::vector<ExitDestructor> g_exitDestructors;
bool g_exitRunnerRegistered = false;
}
extern "C" {
@ -24,21 +39,31 @@ FileStream _Stdout_nid_postfix{stdout};
FileStream _Stdin_nid_postfix{stdin};
int APS5_VABI __cxa_atexit_nid_postfix(void (*func)(void*), void* arg, void* dsoHandle) {
(void)dsoHandle;
static std::vector<std::pair<void (*)(void*), void*>> destructors;
static bool runnerRegistered = false;
destructors.emplace_back(func, arg);
if (!runnerRegistered) {
runnerRegistered = true;
std::atexit([] {
for (auto it = destructors.rbegin(); it != destructors.rend(); ++it) {
it->first(it->second);
}
});
std::lock_guard lock(g_exitMutex);
g_exitDestructors.push_back({func, arg, dsoHandle});
if (!g_exitRunnerRegistered) {
g_exitRunnerRegistered = true;
std::atexit([] { CxaFinalize_nid_no_patch(nullptr); });
}
return 0;
}
void CxaFinalize_nid_no_patch(void* dsoHandle) {
for (;;) {
ExitDestructor destructor{};
{
std::lock_guard lock(g_exitMutex);
const auto found = std::find_if(g_exitDestructors.rbegin(), g_exitDestructors.rend(), [&](const ExitDestructor& entry) {
return dsoHandle == nullptr || entry.dsoHandle == dsoHandle;
});
if (found == g_exitDestructors.rend()) return;
destructor = *found;
g_exitDestructors.erase(std::next(found).base());
}
destructor.func(destructor.arg);
}
}
unsigned int APS5_VABI _Atomic_fetch_add_4_nid_postfix(volatile unsigned int* target, unsigned int value, int memoryOrder) {
(void)memoryOrder;
return GccAtomicFetchAdd(target, value);
@ -49,6 +74,50 @@ unsigned int APS5_VABI _Atomic_fetch_sub_4_nid_postfix(volatile unsigned int* ta
return GccAtomicFetchSub(target, value);
}
int APS5_VABI _Atomic_compare_exchange_weak_4_nid_postfix(volatile unsigned int* target, unsigned int* expected, unsigned int desired, int successOrder, int failureOrder) {
(void)successOrder;
(void)failureOrder;
return GccAtomicCompareExchangeWeak(target, expected, desired) ? 1 : 0;
}
unsigned int APS5_VABI _Atomic_load_4_nid_postfix(volatile unsigned int* target, int memoryOrder) {
(void)memoryOrder;
return GccAtomicLoad(target);
}
std::uintmax_t APS5_VABI strtoumax_nid_postfix(const char* str, char** endptr, int base) {
return std::strtoumax(str, endptr, base);
}
unsigned int APS5_VABI _ZSt14_Random_devicev_nid_postfix() {
static std::random_device device;
static std::mutex mutex;
std::lock_guard lock(mutex);
return device();
}
[[noreturn]] void APS5_VABI _ZSt14_Throw_C_errori_nid_postfix(int code) {
throw std::runtime_error("_Throw_C_error: C11 thread error " + std::to_string(code));
}
void APS5_VABI _Lockfilelock_nid_postfix(FileStream* stream) {
if (stream == nullptr) throw std::invalid_argument("_Lockfilelock: null stream");
#ifdef _WIN32
_lock_file(GetNativeStream(stream));
#else
flockfile(GetNativeStream(stream));
#endif
}
void APS5_VABI _Unlockfilelock_nid_postfix(FileStream* stream) {
if (stream == nullptr) throw std::invalid_argument("_Unlockfilelock: null stream");
#ifdef _WIN32
_unlock_file(GetNativeStream(stream));
#else
funlockfile(GetNativeStream(stream));
#endif
}
unsigned long APS5_VABI _Stoul_nid_postfix(const char* str, char** endptr, int base) {
return std::strtoul(str, endptr, base);
}

View File

@ -6,6 +6,7 @@
#include "prx/libc/include/FileStream.hpp"
#include "prx/libc/include/GuestHeap.hpp"
#include "prx/libc/include/ApplicationHeap.hpp"
#include "prx/libc/include/General.hpp"
#include "SceTypes.hpp"
@ -19,6 +20,10 @@ using GuestNewHandler = void (APS5_VABI*)();
GuestNewHandler g_newHandler = nullptr;
void* Allocate(std::size_t size) {
return ApplicationHeapAllocate_nid_no_patch(size == 0 ? 1 : size);
}
}
extern "C" {
@ -58,6 +63,44 @@ GuestNewHandler APS5_VABI _ZSt15set_new_handlerPFvvE_nid_postfix(GuestNewHandler
return previous;
}
GuestNewHandler APS5_VABI _ZSt15get_new_handlerv_nid_postfix() {
return g_newHandler;
}
unsigned char _ZSt7nothrow_nid_postfix = 0;
void* APS5_VABI _Znwm_nid_postfix(std::size_t size) {
return Allocate(size);
}
void* APS5_VABI _Znam_nid_postfix(std::size_t size) {
return Allocate(size);
}
void* APS5_VABI _ZnwmRKSt9nothrow_t_nid_postfix(std::size_t size, const void*) {
return Allocate(size);
}
void* APS5_VABI _ZnamRKSt9nothrow_t_nid_postfix(std::size_t size, const void*) {
return Allocate(size);
}
void APS5_VABI _ZdlPv_nid_postfix(void* pointer) {
if (pointer != nullptr) ApplicationHeapFree_nid_no_patch(pointer);
}
void APS5_VABI _ZdaPv_nid_postfix(void* pointer) {
if (pointer != nullptr) ApplicationHeapFree_nid_no_patch(pointer);
}
void APS5_VABI _ZdlPvm_nid_postfix(void* pointer, std::size_t) {
if (pointer != nullptr) ApplicationHeapFree_nid_no_patch(pointer);
}
void APS5_VABI _ZdaPvm_nid_postfix(void* pointer, std::size_t) {
if (pointer != nullptr) ApplicationHeapFree_nid_no_patch(pointer);
}
void APS5_VABI _ZdlPvSt11align_val_t_nid_postfix(void* pointer, std::size_t alignment) {
(void)alignment;
GuestHeap::GuestHeapFree_nid_postfix(pointer);

View File

@ -188,6 +188,79 @@ int APS5_VABI strncpy_s_nid_postfix(char* dest, size_t destsz, const char* src,
return 0;
}
int APS5_VABI strcpy_s_nid_postfix(char* dest, size_t destsz, const char* src) {
return strncpy_s_nid_postfix(dest, destsz, src, static_cast<size_t>(-1));
}
int APS5_VABI strncat_s_nid_postfix(char* dest, size_t destsz, const char* src, size_t count) {
constexpr int GuestEinval = 22;
constexpr int GuestErange = 34;
if (!dest || destsz == 0) return GuestEinval;
size_t used = 0;
while (used < destsz && dest[used] != '\0') ++used;
if (used == destsz || !src) {
dest[0] = '\0';
return used == destsz ? GuestErange : GuestEinval;
}
size_t length = 0;
while (length < count && src[length] != '\0') ++length;
if (length >= destsz - used) {
dest[0] = '\0';
return GuestErange;
}
std::memcpy(dest + used, src, length);
dest[used + length] = '\0';
return 0;
}
int APS5_VABI strcat_s_nid_postfix(char* dest, size_t destsz, const char* src) {
return strncat_s_nid_postfix(dest, destsz, src, static_cast<size_t>(-1));
}
int APS5_VABI memcpy_s_nid_postfix(void* dest, size_t destsz, const void* src, size_t count) {
constexpr int GuestEinval = 22;
constexpr int GuestErange = 34;
if (!dest) return GuestEinval;
if (!src || count > destsz) {
std::memset(dest, 0, destsz);
return src ? GuestErange : GuestEinval;
}
std::memcpy(dest, src, count);
return 0;
}
int APS5_VABI memmove_s_nid_postfix(void* dest, size_t destsz, const void* src, size_t count) {
constexpr int GuestEinval = 22;
constexpr int GuestErange = 34;
if (!dest) return GuestEinval;
if (!src || count > destsz) {
std::memset(dest, 0, destsz);
return src ? GuestErange : GuestEinval;
}
std::memmove(dest, src, count);
return 0;
}
int APS5_VABI memset_s_nid_postfix(void* dest, size_t destsz, int value, size_t count) {
constexpr int GuestEinval = 22;
constexpr int GuestErange = 34;
if (!dest) return GuestEinval;
const auto length = count > destsz ? destsz : count;
auto* bytes = static_cast<volatile unsigned char*>(dest);
for (size_t index = 0; index < length; ++index) bytes[index] = static_cast<unsigned char>(value);
return count > destsz ? GuestErange : 0;
}
char* APS5_VABI strnstr_nid_postfix(const char* haystack, const char* needle, size_t length) {
const size_t needleLength = std::strlen(needle);
if (needleLength == 0) return const_cast<char*>(haystack);
for (size_t index = 0; index < length && haystack[index] != '\0'; ++index) {
if (needleLength > length - index) break;
if (std::strncmp(haystack + index, needle, needleLength) == 0) return const_cast<char*>(haystack + index);
}
return nullptr;
}
unsigned long long APS5_VABI _Stoull_nid_postfix(const char* str, char** endptr, int base) {
return std::strtoull(str, endptr, base);
}

View File

@ -89,6 +89,10 @@ size_t APS5_VABI libc_strftime_nid_postfix(char* str, size_t count, const char*
return std::strftime(str, count, format, timeptr);
}
char* APS5_VABI asctime_nid_postfix(const std::tm* timeptr) {
return std::asctime(timeptr);
}
size_t APS5_VABI strftime_nid_postfix(char* str, size_t count, const char* format, const std::tm* timeptr) {
return std::strftime(str, count, format, timeptr);
}

View File

@ -7,6 +7,13 @@
#include <limits>
#include <stdexcept>
extern "C" {
void* APS5_VABI _Znwm_nid_postfix(std::size_t);
void* APS5_VABI _ZnamRKSt9nothrow_t_nid_postfix(std::size_t, const void*);
void APS5_VABI _ZdlPv_nid_postfix(void*);
void APS5_VABI _ZdaPv_nid_postfix(void*);
}
namespace {
alignas(64) std::array<std::byte, 256> storage{};
@ -117,7 +124,15 @@ int main(int argc, char**) {
require(ApplicationHeapPosixAlign_nid_no_patch(&pointer, 64, 128) == 0 && pointer == storage.data() && lastAlignment == 64);
reject([] { ApplicationHeapAlign_nid_no_patch(3, 64); });
reject([] { ApplicationHeapCalloc_nid_no_patch(2, std::numeric_limits<std::size_t>::max()); });
require(_Znwm_nid_postfix(0) == storage.data() && lastSize == 1);
_ZdlPv_nid_postfix(storage.data());
_ZdlPv_nid_postfix(nullptr);
require(frees == 3);
require(_ZnamRKSt9nothrow_t_nid_postfix(24, nullptr) == storage.data() && lastSize == 24);
_ZdaPv_nid_postfix(storage.data());
require(frees == 4);
fail = true;
reject([] { _Znwm_nid_postfix(8); });
reject([] { ApplicationHeapAlign_nid_no_patch(4, 64); });
reject([] { ApplicationHeapAllocate_nid_no_patch(64); });
void* unchanged = storage.data();

View File

@ -15,6 +15,14 @@ std::size_t APS5_VABI strlcat_nid_postfix(char*, const char*, std::size_t);
char* APS5_VABI strtok_r_nid_postfix(char*, const char*, char**);
char* APS5_VABI strtok_nid_postfix(char*, const char*);
char* APS5_VABI strcasestr_nid_postfix(const char*, const char*);
int APS5_VABI strcpy_s_nid_postfix(char*, std::size_t, const char*);
int APS5_VABI strcat_s_nid_postfix(char*, std::size_t, const char*);
int APS5_VABI strncat_s_nid_postfix(char*, std::size_t, const char*, std::size_t);
int APS5_VABI memcpy_s_nid_postfix(void*, std::size_t, const void*, std::size_t);
int APS5_VABI memmove_s_nid_postfix(void*, std::size_t, const void*, std::size_t);
int APS5_VABI memset_s_nid_postfix(void*, std::size_t, int, std::size_t);
char* APS5_VABI strnstr_nid_postfix(const char*, const char*, std::size_t);
int APS5_VABI snprintf_s_nid_postfix(char*, std::size_t, const char*, ...);
}
static void Require(bool condition) {
@ -24,7 +32,31 @@ static void Require(bool condition) {
}
}
static void CheckBoundsCheckedFunctions() {
char small[4] = "zz";
Require(strcpy_s_nid_postfix(small, sizeof(small), "abc") == 0 && std::strcmp(small, "abc") == 0);
Require(strcpy_s_nid_postfix(small, sizeof(small), "abcd") == 34 && small[0] == '\0');
Require(strcpy_s_nid_postfix(nullptr, 4, "a") == 22);
char joined[8] = "ab";
Require(strcat_s_nid_postfix(joined, sizeof(joined), "cd") == 0 && std::strcmp(joined, "abcd") == 0);
Require(strncat_s_nid_postfix(joined, sizeof(joined), "efgh", 2) == 0 && std::strcmp(joined, "abcdef") == 0);
Require(strcat_s_nid_postfix(joined, sizeof(joined), "gh") == 34 && joined[0] == '\0');
char bytes[4] = {1, 2, 3, 4};
const char source[4] = {5, 6, 7, 8};
Require(memcpy_s_nid_postfix(bytes, sizeof(bytes), source, 2) == 0 && bytes[0] == 5 && bytes[2] == 3);
Require(memcpy_s_nid_postfix(bytes, 2, source, 4) == 34 && bytes[0] == 0 && bytes[1] == 0 && bytes[2] == 3);
char overlap[6] = "abcde";
Require(memmove_s_nid_postfix(overlap + 1, 5, overlap, 3) == 0 && std::strcmp(overlap, "aabce") == 0);
Require(memset_s_nid_postfix(bytes, sizeof(bytes), 9, 8) == 34 && bytes[3] == 9);
Require(strnstr_nid_postfix("haystack", "st", 4) == nullptr);
const char haystack[] = "haystack";
Require(strnstr_nid_postfix(haystack, "st", 6) == haystack + 3);
char formatted[8];
Require(snprintf_s_nid_postfix(formatted, sizeof(formatted), "%d-%s", 42, "x") == 4 && std::strcmp(formatted, "42-x") == 0);
}
int main() {
CheckBoundsCheckedFunctions();
Require(std::strcmp(basename_nid_postfix(nullptr), ".") == 0);
Require(std::strcmp(basename_nid_postfix(""), ".") == 0);
Require(std::strcmp(basename_nid_postfix("////"), "/") == 0);