From 2dc4d9d65c6a74244387d04f0b8ea5b1acc173fc Mon Sep 17 00:00:00 2001 From: David Date: Sun, 11 Oct 2026 09:52:46 +0200 Subject: [PATCH] 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 (cherry picked from commit bed1deafdee4b50bea9683a1cead0d0320150eb7) --- core/libs/prx/libSceAmpr/CMakeLists.txt | 13 ++ core/libs/prx/libSceAmpr/Export.cpp | 6 +- .../libc/src/exception/StandardExceptions.cpp | 74 +++++++- core/libs/tests/GuestAmprCppError.S | 50 ++++++ core/libs/tests/GuestAmprCppError.cpp | 159 ++++++++++++++++++ docs/dev/debt/libc.md | 1 + 6 files changed, 297 insertions(+), 6 deletions(-) create mode 100644 core/libs/tests/GuestAmprCppError.S create mode 100644 core/libs/tests/GuestAmprCppError.cpp diff --git a/core/libs/prx/libSceAmpr/CMakeLists.txt b/core/libs/prx/libSceAmpr/CMakeLists.txt index 896ffaf20..35c734c3e 100644 --- a/core/libs/prx/libSceAmpr/CMakeLists.txt +++ b/core/libs/prx/libSceAmpr/CMakeLists.txt @@ -33,4 +33,17 @@ if(BUILD_TESTING) set_tests_properties(guest_ampr_threads PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") 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:$") + endif() endif() diff --git a/core/libs/prx/libSceAmpr/Export.cpp b/core/libs/prx/libSceAmpr/Export.cpp index db2eb580c..f717aabdb 100644 --- a/core/libs/prx/libSceAmpr/Export.cpp +++ b/core/libs/prx/libSceAmpr/Export.cpp @@ -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) { diff --git a/core/libs/prx/libc/src/exception/StandardExceptions.cpp b/core/libs/prx/libc/src/exception/StandardExceptions.cpp index 9e425979f..2f290cfd4 100644 --- a/core/libs/prx/libc/src/exception/StandardExceptions.cpp +++ b/core/libs/prx/libc/src/exception/StandardExceptions.cpp @@ -1,7 +1,10 @@ #include "prx/libc/include/exceptions/Runtime.hpp" +#include +#include #include #include #include +#include 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(const_cast(table.type)), [](void* p) { DestroyPlain(static_cast(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(GenericCategoryDestroy_nid_no_patch), reinterpret_cast(GenericCategoryDelete_nid_no_patch), + reinterpret_cast(GenericCategoryName_nid_no_patch), reinterpret_cast(GenericCategoryMessage_nid_no_patch), + reinterpret_cast(GenericCategoryCondition_nid_no_patch), + reinterpret_cast(GenericCategoryEquivalentCondition_nid_no_patch), + reinterpret_cast(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(&_ZTISt11regex_error_nid_postfix), reinterpret_cast(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(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(__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(&_ZTISt12system_error_nid_postfix), + reinterpret_cast(LibcException::DestroyGuestMessage_nid_no_patch)); +} } diff --git a/core/libs/tests/GuestAmprCppError.S b/core/libs/tests/GuestAmprCppError.S new file mode 100644 index 000000000..c9988cf44 --- /dev/null +++ b/core/libs/tests/GuestAmprCppError.S @@ -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: diff --git a/core/libs/tests/GuestAmprCppError.cpp b/core/libs/tests/GuestAmprCppError.cpp new file mode 100644 index 000000000..f16fe3bff --- /dev/null +++ b/core/libs/tests/GuestAmprCppError.cpp @@ -0,0 +1,159 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include + +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(code)); +} +} + +static void Require(bool value, unsigned code) { + if (!value) FixtureFail(code); +} + +template +static TFunction Slot(const void* const* vtable, int index) { + return reinterpret_cast(const_cast(vtable[index])); +} + +static const TypeRecord* TypeOf(const void* const* vtable, int destroySlot) { + return static_cast(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(category->vtable, 2)(category), "generic") == 0, 23); + const ErrorCode condition = Slot(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(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(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(category->vtable, 3)(result, category, error.code.value); + } catch (const std::runtime_error&) { + messageThrew = true; + } + Require(messageThrew, 31); + bool deleteThrew = false; + try { + Slot(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(©, &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(©); + Require(copy.message == nullptr && assigned.message == nullptr, 44); +} + +extern "C" void APS5_VABI FixtureInspect(void* object) { + const auto& error = *static_cast(object); + Require(std::strcmp(TypeOf(error.vtable, 0)->name, "St12system_error") == 0, 10); + const char* what = Slot(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(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(std::size(Errors))); + std::puts("guest _Throw_Cpp_error system_error, generic category and range: PASS"); + return 0; +} diff --git a/docs/dev/debt/libc.md b/docs/dev/debt/libc.md index dfc540a6d..644922138 100644 --- a/docs/dev/debt/libc.md +++ b/docs/dev/debt/libc.md @@ -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::_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