feat(libSceAmpr): implement std::_Throw_Cpp_error

Throw a guest std::system_error for the Dinkumware _Cpp_error codes,
as in VC12 and microsoft/STL thread0.cpp: the error_code holds the
FreeBSD errno and a guest generic error category modelled in libc, and
what() is the table message followed by the category message. Codes
outside the table throw std::out_of_range. The system_error copy
constructor and assignment now copy the error_code too.

Imported by Deathloop (PPSA01670) and Minecraft Legends (PPSA05510).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit bed1deafdee4b50bea9683a1cead0d0320150eb7)
This commit is contained in:
David 2026-10-11 09:52:46 +02:00 committed by MarcZX100
parent 1acfa7f788
commit 2dc4d9d65c
6 changed files with 297 additions and 6 deletions

View File

@ -33,4 +33,17 @@ if(BUILD_TESTING)
set_tests_properties(guest_ampr_threads PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceAmpr>")
endif()
if(MINGW)
enable_language(ASM)
add_test_executable(guest_ampr_cpp_error_tests
${LIBS_INCLUDE_DIR}/tests/GuestAmprCppError.cpp
${LIBS_INCLUDE_DIR}/tests/GuestAmprCppError.S)
target_include_directories(guest_ampr_cpp_error_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_ampr_cpp_error_tests PRIVATE libSceAmpr libkernel libc)
configure_windows_unwind(guest_ampr_cpp_error_tests)
add_test(NAME guest_ampr_cpp_error COMMAND guest_ampr_cpp_error_tests)
set_tests_properties(guest_ampr_cpp_error PROPERTIES TIMEOUT 10 ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceAmpr>")
endif()
endif()

View File

@ -228,6 +228,7 @@ int APS5_VABI pthread_join_nid_postfix(Pthread thread, void** value);
int APS5_VABI wcsrtombs_s_nid_postfix(std::size_t* result, char* destination, std::size_t capacity, const std::uint16_t** source, std::size_t limit, void* state);
int APS5_VABI _Cnd_init_nid_postfix(void** handle);
int SignalCondition_nid_no_patch(void** handle);
[[noreturn]] void ThrowCppError_nid_no_patch(int code);
}
extern "C" {
@ -836,9 +837,8 @@ int APS5_VABI _ZNSt8ios_base7_AddstdEPS__nid_postfix(void) {
return 0;
}
int APS5_VABI _ZSt16_Throw_Cpp_errori_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
[[noreturn]] void APS5_VABI _ZSt16_Throw_Cpp_errori_nid_postfix(int code) {
ThrowCppError_nid_no_patch(code);
}
int APS5_VABI _Thrd_join_nid_postfix(Pthread thread, int* code) {

View File

@ -1,7 +1,10 @@
#include "prx/libc/include/exceptions/Runtime.hpp"
#include <cstdio>
#include <iterator>
#include <limits>
#include <regex>
#include <stdexcept>
#include <string>
namespace LibcException {
struct TypeRecord { const void* const* vtable; const char* name; const TypeRecord* base; };
@ -119,6 +122,40 @@ ExceptionObject* Assign(ExceptionObject* object, const ExceptionObject* source)
object->vtable = &table.destroy;
__cxa_throw_nid_postfix(object, reinterpret_cast<std::type_info*>(const_cast<TypeRecord*>(table.type)), [](void* p) { DestroyPlain(static_cast<ExceptionObject*>(p)); });
}
struct CategoryObject { const void* vtable; };
struct ErrorCode { int value; const CategoryObject* category; };
struct SystemErrorObject { ExceptionObject base; ErrorCode code; };
static_assert(sizeof(ErrorCode) == 16 && sizeof(SystemErrorObject) == 32 && offsetof(SystemErrorObject, code) == 16);
struct CppError { int value; const char* message; };
const TypeRecord ErrorCategoryType {ClassTypeVtable + 2, "St14error_category", nullptr};
const TypeRecord GenericCategoryType {SingleTypeVtable + 2, "St23_Generic_error_category", &ErrorCategoryType};
static void APS5_VABI GenericCategoryDestroy_nid_no_patch(CategoryObject*) {}
static void APS5_VABI GenericCategoryDelete_nid_no_patch(CategoryObject*) {
throw std::invalid_argument("std::_Generic_error_category: the generic category object is not heap allocated");
}
static const char* APS5_VABI GenericCategoryName_nid_no_patch(const CategoryObject*) { return "generic"; }
static void* APS5_VABI GenericCategoryMessage_nid_no_patch(void* result, const CategoryObject*, int) {
NotImplemented_nid_no_patch("std::_Generic_error_category::message");
return result;
}
static ErrorCode APS5_VABI GenericCategoryCondition_nid_no_patch(const CategoryObject* self, int value) { return {value, self}; }
static bool APS5_VABI GenericCategoryEquivalentCondition_nid_no_patch(const CategoryObject* self, int value, const ErrorCode* condition) {
return condition->category == self && condition->value == value;
}
static bool APS5_VABI GenericCategoryEquivalentCode_nid_no_patch(const CategoryObject* self, const ErrorCode* code, int value) {
return code->category == self && code->value == value;
}
const void* const GenericCategoryVtable[] {
nullptr, &GenericCategoryType,
reinterpret_cast<const void*>(GenericCategoryDestroy_nid_no_patch), reinterpret_cast<const void*>(GenericCategoryDelete_nid_no_patch),
reinterpret_cast<const void*>(GenericCategoryName_nid_no_patch), reinterpret_cast<const void*>(GenericCategoryMessage_nid_no_patch),
reinterpret_cast<const void*>(GenericCategoryCondition_nid_no_patch),
reinterpret_cast<const void*>(GenericCategoryEquivalentCondition_nid_no_patch),
reinterpret_cast<const void*>(GenericCategoryEquivalentCode_nid_no_patch)
};
const CategoryObject GenericCategory {GenericCategoryVtable + 2};
}
extern "C" {
@ -271,9 +308,19 @@ void APS5_VABI _ZNSt12system_errorD1Ev_nid_postfix(LibcException::ExceptionObjec
void APS5_VABI _ZNSt12system_errorD2Ev_nid_postfix(LibcException::ExceptionObject* self) { LibcException::DestroyMessage(self); }
void APS5_VABI _ZNSt12system_errorD0Ev_nid_postfix(LibcException::ExceptionObject* self) { LibcException::DeleteMessage(self); }
const char* APS5_VABI _ZNKSt12system_error4whatEv_nid_postfix(const LibcException::ExceptionObject* self) { return LibcException::MessageWhat(self); }
void APS5_VABI _ZNSt12system_errorC1ERKS__nid_postfix(LibcException::ExceptionObject* self, const LibcException::ExceptionObject* source) { LibcException::Copy(self, source, _ZTVSt12system_error_nid_postfix); }
void APS5_VABI _ZNSt12system_errorC2ERKS__nid_postfix(LibcException::ExceptionObject* self, const LibcException::ExceptionObject* source) { LibcException::Copy(self, source, _ZTVSt12system_error_nid_postfix); }
LibcException::ExceptionObject* APS5_VABI _ZNSt12system_erroraSERKS__nid_postfix(LibcException::ExceptionObject* self, const LibcException::ExceptionObject* source) { return LibcException::Assign(self, source); }
void APS5_VABI _ZNSt12system_errorC1ERKS__nid_postfix(LibcException::SystemErrorObject* self, const LibcException::SystemErrorObject* source) {
LibcException::Copy(&self->base, &source->base, _ZTVSt12system_error_nid_postfix);
self->code = source->code;
}
void APS5_VABI _ZNSt12system_errorC2ERKS__nid_postfix(LibcException::SystemErrorObject* self, const LibcException::SystemErrorObject* source) {
LibcException::Copy(&self->base, &source->base, _ZTVSt12system_error_nid_postfix);
self->code = source->code;
}
LibcException::SystemErrorObject* APS5_VABI _ZNSt12system_erroraSERKS__nid_postfix(LibcException::SystemErrorObject* self, const LibcException::SystemErrorObject* source) {
LibcException::Assign(&self->base, &source->base);
self->code = source->code;
return self;
}
LibcException::TypeRecord _ZTISt11regex_error_nid_postfix {LibcException::SingleTypeVtable + 2, "St11regex_error", &_ZTISt13runtime_error_nid_postfix};
LibcException::ExceptionVtable _ZTVSt11regex_error_nid_postfix {0, &_ZTISt11regex_error_nid_postfix, LibcException::GuestDestroyMessage_nid_no_patch, LibcException::GuestDeleteMessage_nid_no_patch, LibcException::GuestMessageWhat_nid_no_patch};
@ -321,4 +368,25 @@ const char* APS5_VABI _ZNKSt8ios_base7failure4whatEv_nid_postfix(const LibcExcep
__cxa_throw_nid_postfix(object, reinterpret_cast<std::type_info*>(&_ZTISt11regex_error_nid_postfix),
reinterpret_cast<decltype(LibcException::Header::destructor)>(LibcException::DestroyGuestMessage_nid_no_patch));
}
[[noreturn]] void ThrowCppError_nid_no_patch(int code) {
static constexpr LibcException::CppError errors[] {
{16, "device or resource busy"},
{22, "invalid argument"},
{3, "no such process"},
{12, "not enough memory"},
{1, "operation not permitted"},
{11, "resource deadlock would occur"},
{35, "resource unavailable try again"}
};
if (code < 0 || code >= static_cast<int>(std::size(errors)))
throw std::out_of_range("_Throw_Cpp_error: " + std::to_string(code) + " is not a _Cpp_error code");
const auto& error = errors[code];
char message[128];
std::snprintf(message, sizeof(message), "%s: %s", error.message, error.message);
auto* object = static_cast<LibcException::SystemErrorObject*>(__cxa_allocate_exception_nid_postfix(sizeof(LibcException::SystemErrorObject)));
LibcException::Construct(&object->base, _ZTVSt12system_error_nid_postfix, message);
object->code = {error.value, &LibcException::GenericCategory};
__cxa_throw_nid_postfix(object, reinterpret_cast<std::type_info*>(&_ZTISt12system_error_nid_postfix),
reinterpret_cast<decltype(LibcException::Header::destructor)>(LibcException::DestroyGuestMessage_nid_no_patch));
}
}

View File

@ -0,0 +1,50 @@
.cfi_sections .eh_frame
.text
.globl GuestCatch
GuestCatch:
.cfi_startproc
.cfi_personality 0x00, __gxx_personality_v0_nid_postfix
.cfi_lsda 0x00, .Lcatch_lsda
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset %rbp, -16
movq %rsp, %rbp
.cfi_def_cfa_register %rbp
subq $16, %rsp
.Lcatch_call:
call _ZSt16_Throw_Cpp_errori_nid_postfix
.Lcatch_call_end:
ud2
.Lcatch_landing:
cmpq $2, %rdx
jne .Lcatch_bad
movq %rax, %rdi
call __cxa_begin_catch_nid_postfix
movq %rax, %rdi
call FixtureInspect
call __cxa_end_catch_nid_postfix
leave
.cfi_def_cfa %rsp, 8
ret
.Lcatch_bad:
movl $1, %edi
call FixtureFail
ud2
.cfi_endproc
.section .rdata,"dr"
.Lcatch_lsda:
.byte 0xff, 0x80
.uleb128 .Lcatch_types_end-.Lcatch_types_offset
.Lcatch_types_offset:
.byte 0x01
.uleb128 .Lcatch_calls_end-.Lcatch_calls
.Lcatch_calls:
.uleb128 .Lcatch_call-GuestCatch
.uleb128 .Lcatch_call_end-.Lcatch_call
.uleb128 .Lcatch_landing-GuestCatch
.uleb128 1
.Lcatch_calls_end:
.byte 1, 1, 2, 0
.quad FixtureCatchType
.quad FixtureOtherType
.Lcatch_types_end:

View File

@ -0,0 +1,159 @@
#include "prx/libc/include/general/VabiMacros.hpp"
#include <cstddef>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iterator>
#include <stdexcept>
#include <string>
struct TypeRecord { const void* vtable; const char* name; const TypeRecord* base; };
struct Category { const void* const* vtable; };
struct ErrorCode { int value; const Category* category; };
struct SystemError { const void* const* vtable; const char* message; ErrorCode code; };
static_assert(sizeof(SystemError) == 32 && offsetof(SystemError, code) == 16);
using GuestWhat = const char* (APS5_VABI *)(const SystemError*);
using CategoryDelete = void (APS5_VABI *)(const Category*);
using CategoryName = const char* (APS5_VABI *)(const Category*);
using CategoryMessage = void* (APS5_VABI *)(void*, const Category*, int);
using CategoryCondition = ErrorCode (APS5_VABI *)(const Category*, int);
using CategoryEquivalentCondition = bool (APS5_VABI *)(const Category*, int, const ErrorCode*);
using CategoryEquivalentCode = bool (APS5_VABI *)(const Category*, const ErrorCode*, int);
struct Expected { int value; const char* what; };
static constexpr Expected Errors[] {
{16, "device or resource busy: device or resource busy"},
{22, "invalid argument: invalid argument"},
{3, "no such process: no such process"},
{12, "not enough memory: not enough memory"},
{1, "operation not permitted: operation not permitted"},
{11, "resource deadlock would occur: resource deadlock would occur"},
{35, "resource unavailable try again: resource unavailable try again"}
};
extern "C" {
extern const unsigned char _ZTISt9exception_nid_postfix[];
extern const unsigned char _ZTISt11logic_error_nid_postfix[];
extern const unsigned char _ZTISt13runtime_error_nid_postfix[];
extern const unsigned char _ZTISt12system_error_nid_postfix[];
[[noreturn]] void APS5_VABI _ZSt16_Throw_Cpp_errori_nid_postfix(int code);
void APS5_VABI _ZNSt12system_errorC1ERKS__nid_postfix(SystemError* self, const SystemError* source);
SystemError* APS5_VABI _ZNSt12system_erroraSERKS__nid_postfix(SystemError* self, const SystemError* source);
void APS5_VABI _ZNSt12system_errorD1Ev_nid_postfix(SystemError* self);
void APS5_VABI GuestCatch(int code);
const void* FixtureCatchType = nullptr;
const void* FixtureOtherType = nullptr;
const Expected* FixtureExpected = nullptr;
unsigned FixtureInspected = 0;
[[noreturn]] void APS5_VABI FixtureFail(unsigned code) {
std::fprintf(stderr, "guest _Throw_Cpp_error failure %u\n", code);
std::exit(static_cast<int>(code));
}
}
static void Require(bool value, unsigned code) {
if (!value) FixtureFail(code);
}
template<class TFunction>
static TFunction Slot(const void* const* vtable, int index) {
return reinterpret_cast<TFunction>(const_cast<void*>(vtable[index]));
}
static const TypeRecord* TypeOf(const void* const* vtable, int destroySlot) {
return static_cast<const TypeRecord*>(vtable[destroySlot - 1]);
}
static void CheckCategory(const SystemError& error) {
const Category* category = error.code.category;
Require(category != nullptr, 20);
const TypeRecord* type = TypeOf(category->vtable, 0);
Require(std::strcmp(type->name, "St23_Generic_error_category") == 0, 21);
Require(type->base && std::strcmp(type->base->name, "St14error_category") == 0, 22);
Require(std::strcmp(Slot<CategoryName>(category->vtable, 2)(category), "generic") == 0, 23);
const ErrorCode condition = Slot<CategoryCondition>(category->vtable, 4)(category, error.code.value);
Require(condition.value == error.code.value && condition.category == category, 24);
const ErrorCode other {error.code.value + 1, category};
const ErrorCode foreign {error.code.value, nullptr};
auto equivalentCondition = Slot<CategoryEquivalentCondition>(category->vtable, 5);
Require(equivalentCondition(category, error.code.value, &condition), 25);
Require(!equivalentCondition(category, error.code.value, &other), 26);
Require(!equivalentCondition(category, error.code.value, &foreign), 27);
auto equivalentCode = Slot<CategoryEquivalentCode>(category->vtable, 6);
Require(equivalentCode(category, &error.code, error.code.value), 28);
Require(!equivalentCode(category, &error.code, error.code.value + 1), 29);
Require(!equivalentCode(category, &foreign, error.code.value), 30);
bool messageThrew = false;
try {
unsigned char result[32] {};
Slot<CategoryMessage>(category->vtable, 3)(result, category, error.code.value);
} catch (const std::runtime_error&) {
messageThrew = true;
}
Require(messageThrew, 31);
bool deleteThrew = false;
try {
Slot<CategoryDelete>(category->vtable, 1)(category);
} catch (const std::invalid_argument&) {
deleteThrew = true;
}
Require(deleteThrew, 32);
}
static void CheckCopies(const SystemError& error) {
SystemError copy {};
_ZNSt12system_errorC1ERKS__nid_postfix(&copy, &error);
Require(copy.vtable == error.vtable && copy.message == error.message, 40);
Require(copy.code.value == error.code.value && copy.code.category == error.code.category, 41);
SystemError assigned {};
_ZNSt12system_errorC1ERKS__nid_postfix(&assigned, &error);
assigned.code = {0, nullptr};
Require(_ZNSt12system_erroraSERKS__nid_postfix(&assigned, &error) == &assigned, 42);
Require(assigned.code.value == error.code.value && assigned.code.category == error.code.category, 43);
_ZNSt12system_errorD1Ev_nid_postfix(&assigned);
_ZNSt12system_errorD1Ev_nid_postfix(&copy);
Require(copy.message == nullptr && assigned.message == nullptr, 44);
}
extern "C" void APS5_VABI FixtureInspect(void* object) {
const auto& error = *static_cast<const SystemError*>(object);
Require(std::strcmp(TypeOf(error.vtable, 0)->name, "St12system_error") == 0, 10);
const char* what = Slot<GuestWhat>(error.vtable, 2)(&error);
Require(what && std::strcmp(what, FixtureExpected->what) == 0, 11);
Require(error.code.value == FixtureExpected->value, 12);
CheckCategory(error);
CheckCopies(error);
++FixtureInspected;
}
static void Catch(const void* type, int code) {
FixtureCatchType = type;
FixtureExpected = &Errors[code];
const auto before = FixtureInspected;
GuestCatch(code);
Require(FixtureInspected == before + 1, 2);
}
static void CheckOutOfRange(int code) {
bool threw = false;
try {
_ZSt16_Throw_Cpp_errori_nid_postfix(code);
} catch (const std::out_of_range& error) {
threw = std::string(error.what()).find(std::to_string(code)) != std::string::npos;
}
Require(threw, 3);
}
int main() {
FixtureOtherType = _ZTISt11logic_error_nid_postfix;
for (int code = 0; code < static_cast<int>(std::size(Errors)); ++code) Catch(_ZTISt12system_error_nid_postfix, code);
Catch(_ZTISt13runtime_error_nid_postfix, 5);
Catch(_ZTISt9exception_nid_postfix, 6);
CheckOutOfRange(-1);
CheckOutOfRange(static_cast<int>(std::size(Errors)));
std::puts("guest _Throw_Cpp_error system_error, generic category and range: PASS");
return 0;
}

View File

@ -56,3 +56,4 @@
- [daemon](../../../core/libs/prx/libc/src/Process.cpp) (libc) throws: forking into the background is not supported. PPSA85300 imports it and fails to load without it. VLC 3.0 calls it only when its `daemon` option is set ([libvlc.c](https://github.com/videolan/vlc/blob/6de05adcbaf2e8b85fe86aad4169393098628119/src/libvlc.c#L190-L197)), and the title starts libVLC with `--ignore-config` and without `--daemon` ([player.cc](https://github.com/ZiZc3/VLC/blob/f3253020a64368153233ede29183014c0b6c8979/src/player.cc#L960-L981))
- [_WStoul](../../../core/libs/prx/libc/src/Strings.cpp), [_Iswctype](../../../core/libs/prx/libc/src/RuneLocale.cpp) and [swscanf_s](../../../core/libs/prx/libc/src/ScanningWide.cpp) (libc) - `_WStoul` is `wcstoul` and throws for a base other than 0 or 2 to 36. `_Iswctype` takes the class numbers of Dinkumware's [xwcc.h](https://github.com/superna9999/ti-cgt6x/blob/cgt6x_7_3_4/include/xwcc.h) (1 `alnum` to 11 `xdigit`, 12 `blank`) and throws for any other value, including the FreeBSD masks `wctype` returns; the numbering is not confirmed on the console's `libc.prx`. `swscanf_s` follows C11 7.29.2.2 and K.3.9.1.5, but reads the size argument as `unsigned int` like `sscanf_s` where K.3.9.1.5 specifies `rsize_t`; the console's width is unverified. A runtime-constraint violation (null buffer, format or receiving pointer) only returns `EOF`, and a too small array gets a null first element as in `sscanf_s`. A literal mismatch is a matching failure and a field that is only a prefix, such as `0x` or `1e`, does not convert, as C11 requires; FreeBSD's `vfwscanf` reports an input failure for the first and converts the second. Integers out of range are truncated from `strtoll`/`strtoull`. It throws for `%p`, `%S`, `%C`, unknown length modifiers, scanset ranges, `%n` with suppression or width, and narrow `%s`, `%c` or `%[` input that the C-locale `wcrtomb` cannot convert
- [flock](../../../core/libs/prx/libc/src/FileLock.cpp) (libkernel) on Windows shares one lock state between duplicated descriptors, following FreeBSD 11.4's [flock(2)](https://github.com/freebsd/freebsd-src/blob/releng/11.4/lib/libc/sys/flock.2). Independent opens have separate states; closing a descriptor or reopening a stream drops only its reference. Lock transitions on one open file description are serialized. The host's [LockFileEx](https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-lockfileex) locks also restrict ordinary reads and writes through other handles, unlike FreeBSD's advisory locks, and do not restrict mapped views. Console behavior, signal interruption and concurrent closure of a descriptor while its lock call is pending are unverified.
- [std::_Throw_Cpp_error](../../../core/libs/prx/libc/src/exception/StandardExceptions.cpp) (libSceAmpr) follows Dinkumware's `thread0.cpp` as shipped with [Visual C++ 2013](https://github.com/ganboing/crt.vc12/blob/f1d47126bf13d1c9dccaa6c53b72217fd0474968/src/thread0.cpp) and in [microsoft/STL](https://github.com/microsoft/STL/blob/da52dc0fcadbd83690cfd1ed32408d2847439493/stl/src/thread0.cpp), without console verification. `_Cpp_error` codes 0 to 6 throw a guest `std::system_error` whose `error_code` holds the FreeBSD errno of the `errc` value (EBUSY 16, EINVAL 22, ESRCH 3, ENOMEM 12, EPERM 1, EDEADLK 11, EAGAIN 35) and the generic category, and whose `what()` is the table message, `": "` and the generic category's message for that errno, taken to be the same text as in VC12's [`syserror.cpp`](https://github.com/ganboing/crt.vc12/blob/f1d47126bf13d1c9dccaa6c53b72217fd0474968/src/syserror.cpp); another code throws `std::out_of_range` on the host instead of reading past the table. The guest `system_error` is taken to be [VC12's](https://github.com/ganboing/crt.vc12/blob/f1d47126bf13d1c9dccaa6c53b72217fd0474968/src/system_error): the 16-byte `runtime_error` followed by `error_code {int, const error_category*}`, 32 bytes, so its copy constructor and assignment copy the code too. The generic category is a static libc object with VC12's `error_category` virtual order (destructors, `name`, `message`, `default_error_condition`, the two `equivalent`), not the console's `_Error_objects<int>::_Generic_object`, and no `generic_category()` export returns it, so it compares unequal to a title's own `generic_category()`. Its `message` throws, as the guest `std::string` layout is not modelled, and so does its deleting destructor