fix(ampr): raise std::_Pad failures as the guest std::system_error
A failing _Pad primitive threw a host std::runtime_error, which no guest catch can see. threadcall.cpp calls the checked wrappers of xthread (_Mtx_lockX, _Cnd_waitX, _Thrd_startX), which pass a non-success code to _Throw_C_error: _Thrd_nomem and _Thrd_timedout become resource_unavailable_try_again, _Thrd_busy device_or_resource_busy and _Thrd_error invalid_argument, raised through _Throw_Cpp_error, which main now implements. A failing thread start is _Thrd_startX's resource_unavailable_try_again. _Release now wakes the launching thread with _Cnd_signal, as threadcall.cpp does; libc has it since the pad was written. guest_ampr_threads checks, in a forked child whose guest terminate handler reports the thrown object, that _Release on the thread still holding the pad's mutex throws std::system_error with EBUSY. It fails with the previous host exception and with busy mapped to invalid_argument.
This commit is contained in:
parent
8e3b2a06f2
commit
dd1deb6965
|
|
@ -225,8 +225,15 @@ static int RecordWriteAddressFromCounterPair(Apr::CommandBufferObject* buffer, v
|
|||
}
|
||||
|
||||
static constexpr int ThrdSuccess = 0;
|
||||
static constexpr int ThrdNomem = 1;
|
||||
static constexpr int ThrdTimedout = 2;
|
||||
static constexpr int ThrdBusy = 3;
|
||||
static constexpr int ThrdError = 4;
|
||||
|
||||
static constexpr int CppDeviceOrResourceBusy = 0;
|
||||
static constexpr int CppInvalidArgument = 1;
|
||||
static constexpr int CppResourceUnavailableTryAgain = 6;
|
||||
|
||||
static constexpr int MtxPlain = 0x1;
|
||||
|
||||
static std::atomic_flag sharedPtrSpinLock;
|
||||
|
|
@ -262,7 +269,7 @@ int APS5_VABI _Mtx_unlock_nid_postfix(void** handle);
|
|||
int APS5_VABI _Cnd_init_nid_postfix(void** handle);
|
||||
void APS5_VABI _Cnd_destroy_nid_postfix(void** handle);
|
||||
int APS5_VABI _Cnd_wait_nid_postfix(void** condition, void** mutex);
|
||||
int APS5_VABI _Cnd_broadcast_nid_postfix(void** handle);
|
||||
int APS5_VABI _Cnd_signal_nid_postfix(void** handle);
|
||||
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);
|
||||
|
|
@ -270,7 +277,18 @@ int SignalCondition_nid_no_patch(void** handle);
|
|||
}
|
||||
|
||||
static void RequireThrd(int result, const char* what) {
|
||||
if (result != ThrdSuccess) throw std::runtime_error(std::string(what) + " failed with " + std::to_string(result));
|
||||
switch (result) {
|
||||
case ThrdSuccess:
|
||||
return;
|
||||
case ThrdNomem:
|
||||
case ThrdTimedout:
|
||||
ThrowCppError_nid_no_patch(CppResourceUnavailableTryAgain);
|
||||
case ThrdBusy:
|
||||
ThrowCppError_nid_no_patch(CppDeviceOrResourceBusy);
|
||||
case ThrdError:
|
||||
ThrowCppError_nid_no_patch(CppInvalidArgument);
|
||||
}
|
||||
throw std::runtime_error(std::string(what) + " failed with " + std::to_string(result));
|
||||
}
|
||||
|
||||
static void* APS5_VABI PadEntry_nid_no_patch(void* argument) {
|
||||
|
|
@ -900,7 +918,7 @@ void APS5_VABI _ZNSt4_Pad8_ReleaseEv_nid_postfix(GuestPad* pad) {
|
|||
if (!pad) APS5_INVALID_ARG_EX;
|
||||
RequireThrd(_Mtx_lock_nid_postfix(&pad->mutex), "_Pad::_Release: _Mtx_lock");
|
||||
pad->started = true;
|
||||
RequireThrd(_Cnd_broadcast_nid_postfix(&pad->condition), "_Pad::_Release: _Cnd_broadcast");
|
||||
RequireThrd(_Cnd_signal_nid_postfix(&pad->condition), "_Pad::_Release: _Cnd_signal");
|
||||
RequireThrd(_Mtx_unlock_nid_postfix(&pad->mutex), "_Pad::_Release: _Mtx_unlock");
|
||||
}
|
||||
|
||||
|
|
@ -936,7 +954,7 @@ int APS5_VABI _Thrd_id_nid_postfix(void) {
|
|||
void APS5_VABI _ZNSt4_Pad7_LaunchEPP7pthread_nid_postfix(GuestPad* pad, Pthread* thread) {
|
||||
if (!pad || !thread) APS5_INVALID_ARG_EX;
|
||||
const int created = pthread_create_nid_postfix(thread, nullptr, PadEntry_nid_no_patch, pad);
|
||||
if (created != 0) throw std::runtime_error("_Pad::_Launch: pthread_create failed with " + std::to_string(created));
|
||||
if (created != 0) ThrowCppError_nid_no_patch(CppResourceUnavailableTryAgain);
|
||||
while (!pad->started) RequireThrd(_Cnd_wait_nid_postfix(&pad->condition, &pad->mutex), "_Pad::_Launch: _Cnd_wait");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,10 @@
|
|||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
#ifndef _WIN32
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
struct GuestXtime {
|
||||
std::int64_t sec;
|
||||
|
|
@ -24,6 +28,20 @@ struct PadTarget {
|
|||
unsigned exitCode;
|
||||
};
|
||||
|
||||
struct GuestTypeInfo {
|
||||
const void* vtable;
|
||||
const char* name;
|
||||
};
|
||||
|
||||
struct GuestSystemError {
|
||||
const void* vtable;
|
||||
const char* message;
|
||||
int code;
|
||||
const void* category;
|
||||
};
|
||||
|
||||
using GuestTerminate = void (APS5_VABI*)();
|
||||
|
||||
struct TestPad {
|
||||
unsigned (APS5_VABI* const* vtable)(TestPad*);
|
||||
void* condition;
|
||||
|
|
@ -55,10 +73,14 @@ int APS5_VABI _Mtx_init_nid_postfix(void** handle, int type);
|
|||
void APS5_VABI _Mtx_destroy_nid_postfix(void** handle);
|
||||
int APS5_VABI _Mtx_lock_nid_postfix(void** handle);
|
||||
int APS5_VABI _Mtx_unlock_nid_postfix(void** handle);
|
||||
GuestTerminate APS5_VABI _ZSt13set_terminatePFvvE_nid_postfix(GuestTerminate handler);
|
||||
const GuestTypeInfo* APS5_VABI __cxa_current_exception_type_nid_postfix();
|
||||
void* APS5_VABI __cxa_current_primary_exception_nid_postfix();
|
||||
}
|
||||
|
||||
static constexpr int ThrdSuccess = 0;
|
||||
static constexpr int ThrdError = 4;
|
||||
static constexpr int GuestEbusy = 16;
|
||||
static constexpr int GuestEinval = 22;
|
||||
static constexpr int GuestErange = 34;
|
||||
static constexpr int GuestEilseq = 86;
|
||||
|
|
@ -457,6 +479,28 @@ static void* APS5_VABI DestroyPad(void* arg) {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
static void APS5_VABI ExitWithSystemErrorCode() {
|
||||
const auto* type = __cxa_current_exception_type_nid_postfix();
|
||||
const auto* error = static_cast<const GuestSystemError*>(__cxa_current_primary_exception_nid_postfix());
|
||||
_exit(type && error && std::strcmp(type->name, "St12system_error") == 0 ? error->code : 1);
|
||||
}
|
||||
|
||||
template <typename TCall>
|
||||
static int GuestSystemErrorCode(TCall call) {
|
||||
const pid_t child = fork();
|
||||
Require(child >= 0);
|
||||
if (child == 0) {
|
||||
_ZSt13set_terminatePFvvE_nid_postfix(ExitWithSystemErrorCode);
|
||||
call();
|
||||
_exit(2);
|
||||
}
|
||||
int status = 0;
|
||||
Require(waitpid(child, &status, 0) == child);
|
||||
return WIFEXITED(status) ? WEXITSTATUS(status) : -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void TestPadErrors() {
|
||||
Require(Throws<std::invalid_argument>([] { _ZNSt4_PadC2Ev_nid_postfix(nullptr); }));
|
||||
Require(Throws<std::invalid_argument>([] { _ZNSt4_PadD2Ev_nid_postfix(nullptr); }));
|
||||
|
|
@ -470,7 +514,9 @@ static void TestPadErrors() {
|
|||
ConstructPad(pad, order, destroyed, 0, 0);
|
||||
Require(Throws<std::invalid_argument>([&] { _ZNSt4_Pad7_LaunchEPP7pthread_nid_postfix(&pad, nullptr); }));
|
||||
Require(!pad.started && order.load() == 0);
|
||||
Require(Throws<std::runtime_error>([&] { _ZNSt4_Pad8_ReleaseEv_nid_postfix(&pad); }));
|
||||
#ifndef _WIN32
|
||||
Require(GuestSystemErrorCode([&] { _ZNSt4_Pad8_ReleaseEv_nid_postfix(&pad); }) == GuestEbusy);
|
||||
#endif
|
||||
Require(!pad.started);
|
||||
|
||||
ForeignDestroy context{&pad, false};
|
||||
|
|
|
|||
|
|
@ -13,4 +13,4 @@
|
|||
- [sceAmprAmmGetUsageStatsData](../../../core/libs/prx/libSceAmpr/Export.cpp) (libSceAmpr) fills the caller-sized 24-byte statistics structure with 512 page-table pool entries, no allocated entries, a zero high watermark and ring idle flags 0x7, the constants of [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/main/src/libs/libAmpr.cpp); they are not measured on a console, and the real call is a pass-through to `sceKernelMapperGetUsageStatsData` per [drakmor/ampr_emu](https://github.com/drakmor/ampr_emu/blob/main/src/ampr_emu_amm.cpp), which this port has no kernel-side counterpart for.
|
||||
- [Dinkumware thread exports](../../../core/libs/prx/libSceAmpr/Export.cpp) (libSceAmpr) follow the Dinkumware threads library shipped with [Visual C++ 2013](https://github.com/adityaramesh/concurrency_runtime/tree/master/VS2013_SP2/crt/src/thr) (`cthread.c`) and its `atomic.c`, [microsoft/STL `cthread.cpp`](https://github.com/microsoft/STL/blob/main/stl/src/cthread.cpp) and C11 Annex K, without console verification. `_Thrd_t` is taken to be the guest `pthread_t`, as the `std::_Pad::_Launch(pthread**)` export suggests: `_Thrd_join` returns `_Thrd_error` (4) when `pthread_join` fails and stores the thread's return value truncated to `int`. `_Thrd_join` (32-bit store of the result, 0 or 4) and the spin lock (byte exchange loop, no yield) match that disassembly. The shared_ptr spin lock is one process-wide flag that yields while it spins. `wcstombs_s` is `wcsrtombs_s` with a fresh conversion state, so `*retval` excludes the terminating null as in Annex K, not as in the Microsoft CRT, and `_TRUNCATE` has no meaning.
|
||||
- [_Cnd_signal, _Cnd_init_with_name](../../../core/libs/prx/libSceAmpr/Export.cpp) (libSceAmpr) - take a pointer to the handle slot like the libc `_Cnd_*` exports from [#3079](https://github.com/boykopovar/AnyPS5/pull/3079) and act on the same conditions. `_Cnd_signal` wakes one waiter and returns `_Thrd_success` (0), as `_Cnd_broadcast` does; whether the console maps a failing internal signal to `_Thrd_error` is not known. The signature `_Cnd_init_with_name(_Cnd_t*, const char* name)` is inferred from [fpPS4 `ps4_mtx_internal.pas`](https://github.com/red-prig/fpPS4/blob/trunk/src/libcinternal/ps4_mtx_internal.pas), where the PS4 `_Mtx_init` is `_Mtx_init_with_name` with the name `SceLibcI_Mtx` and the name only reaches the pthread object; here it is `_Cnd_init` and the name, which may be null, is ignored. Neither is confirmed on a PS5 `libc.prx`. `_Thrd_id` still throws: microsoft/STL returns an `unsigned int` thread id, but whether the PS5 returns the `pthread_t` or a kernel thread id, and at what width, is not known
|
||||
- [std::_Pad](../../../core/libs/prx/libSceAmpr/Export.cpp) (libSceAmpr) follows Dinkumware V6.00 `thr/xthread` and `thr/threadcall.cpp` of Visual C++ 2013 ([crt.vc12 `f1d47126`](https://github.com/ganboing/crt.vc12/tree/f1d47126/src/thr)), without console verification. The pad is taken to be that header's class with the console's pointer handles: a vtable pointer whose first slot is the pure virtual `_Go()`, then `_Cnd_t` at 0x8, `_Mtx_t` at 0x10 and `bool _Started` at 0x18, 32 bytes, the derived `_LaunchPad` target following it; the console's `_Pad(const char*)` and `_Launch(const char*, pthread**)`/`_Launch(pthread_attr**, pthread**)` overloads suggest named primitives and could mean another layout or vtable. `_Launch` starts the thread with `pthread_create` and default attributes (the console's `_Thrd_start` name, stack size and priority are unknown), its entry calls `_Go()` and returns the result as the exit code, and it does not run `_Cnd_do_broadcast_at_thread_exit`, which is absent here. `_Release` wakes the launching thread with `_Cnd_broadcast`, the only waiter on the pad's condition, since libc has no `_Cnd_signal`. A failing thread start or primitive throws a host exception instead of the guest `std::system_error` of `_Throw_Cpp_error`, and a null pad or thread slot throws
|
||||
- [std::_Pad](../../../core/libs/prx/libSceAmpr/Export.cpp) (libSceAmpr) follows Dinkumware V6.00 `thr/xthread` and `thr/threadcall.cpp` of Visual C++ 2013 ([crt.vc12 `f1d47126`](https://github.com/ganboing/crt.vc12/tree/f1d47126/src/thr)), without console verification. The pad is taken to be that header's class with the console's pointer handles: a vtable pointer whose first slot is the pure virtual `_Go()`, then `_Cnd_t` at 0x8, `_Mtx_t` at 0x10 and `bool _Started` at 0x18, 32 bytes, the derived `_LaunchPad` target following it; the console's `_Pad(const char*)` and `_Launch(const char*, pthread**)`/`_Launch(pthread_attr**, pthread**)` overloads suggest named primitives and could mean another layout or vtable. `_Launch` starts the thread with `pthread_create` and default attributes (the console's `_Thrd_start` name, stack size and priority are unknown), its entry calls `_Go()` and returns the result as the exit code, and it does not run `_Cnd_do_broadcast_at_thread_exit`, which is absent here. A failing primitive throws the guest `std::system_error` that `_Throw_C_error` picks for its code, and a failing thread start the `resource_unavailable_try_again` one of `_Thrd_startX`; any `pthread_create` error counts, since the console's `_Thrd_start` result is unknown. A null pad or thread slot throws
|
||||
|
|
|
|||
Loading…
Reference in New Issue