mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-22 08:33:12 -05:00
fix(libc): let a title's own libunwind read _Unwind_Backtrace frames
Homebrew built with the ps5-payload-dev SDK links LLVM libunwind statically but imports _Unwind_Backtrace from libSceLibcInternal. Its trace callback then reads each frame with its own _Unwind_GetIP, which treats the context as a libunwind cursor and calls through the virtual table in its first word. AnyPS5's context started with the saved RAX, so the call jumped to a register value and the title crashed on its error path (vkQuake, PPSA99010, without its game data). The context now starts with a table in AbstractUnwindCursor's slot order that answers register reads and writes, validFloatReg and isSignalFrame; other slots throw. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EvDbpwUPTa78oCAJqe7m9K
This commit is contained in:
1 parent
7e181fcc38
commit
db3ec02c54
5 files changed
+93
-1
No files matched your search
@@ -9,7 +9,12 @@
|
||||
#include "prx/libc/include/General.hpp"
|
||||
#include "prx/libc/include/specifics/itanium/UnwindAbi.hpp"
|
||||
|
||||
namespace LibcUnwind {
|
||||
extern const std::uintptr_t CursorSlots[];
|
||||
}
|
||||
|
||||
struct _Unwind_Context {
|
||||
const std::uintptr_t* cursor = LibcUnwind::CursorSlots;
|
||||
std::uintptr_t registers[17] {};
|
||||
#ifdef _WIN32
|
||||
std::uintptr_t vectorRegisters[16][2] {};
|
||||
|
||||
@@ -21,6 +21,71 @@
|
||||
#include <mach-o/loader.h>
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
constexpr int CursorInstructionRegister = -1;
|
||||
constexpr int CursorStackRegister = -2;
|
||||
constexpr int CursorRegisterCount = 17;
|
||||
|
||||
std::uintptr_t* CursorRegister(_Unwind_Context* context, int number) {
|
||||
if (number == CursorInstructionRegister) return &context->registers[16];
|
||||
if (number == CursorStackRegister) return &context->registers[7];
|
||||
if (number >= 0 && number < CursorRegisterCount) return &context->registers[number];
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool APS5_VABI CursorValidRegister(_Unwind_Context* context, int number) {
|
||||
return CursorRegister(context, number) != nullptr;
|
||||
}
|
||||
|
||||
std::uintptr_t APS5_VABI CursorGetRegister(_Unwind_Context* context, int number) {
|
||||
auto* value = CursorRegister(context, number);
|
||||
if (!value) NotImplemented_nid_no_patch("libunwind cursor: read of an unknown register");
|
||||
return value ? *value : 0;
|
||||
}
|
||||
|
||||
void APS5_VABI CursorSetRegister(_Unwind_Context* context, int number, std::uintptr_t value) {
|
||||
auto* slot = CursorRegister(context, number);
|
||||
if (!slot) NotImplemented_nid_no_patch("libunwind cursor: write of an unknown register");
|
||||
else *slot = value;
|
||||
}
|
||||
|
||||
bool APS5_VABI CursorValidFloatRegister(_Unwind_Context*, int) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool APS5_VABI CursorIsSignalFrame(_Unwind_Context* context) {
|
||||
return context->signalFrame;
|
||||
}
|
||||
|
||||
void APS5_VABI CursorUnsupported(_Unwind_Context*) {
|
||||
NotImplemented_nid_no_patch("libunwind cursor: operation other than register access or isSignalFrame");
|
||||
}
|
||||
|
||||
template<class Function> std::uintptr_t Slot(Function function) {
|
||||
return reinterpret_cast<std::uintptr_t>(function);
|
||||
}
|
||||
}
|
||||
|
||||
namespace LibcUnwind {
|
||||
const std::uintptr_t CursorSlots[] = {
|
||||
Slot(&CursorUnsupported),
|
||||
Slot(&CursorUnsupported),
|
||||
Slot(&CursorValidRegister),
|
||||
Slot(&CursorGetRegister),
|
||||
Slot(&CursorSetRegister),
|
||||
Slot(&CursorValidFloatRegister),
|
||||
Slot(&CursorUnsupported),
|
||||
Slot(&CursorUnsupported),
|
||||
Slot(&CursorUnsupported),
|
||||
Slot(&CursorUnsupported),
|
||||
Slot(&CursorUnsupported),
|
||||
Slot(&CursorIsSignalFrame),
|
||||
Slot(&CursorUnsupported),
|
||||
Slot(&CursorUnsupported),
|
||||
Slot(&CursorUnsupported),
|
||||
};
|
||||
}
|
||||
|
||||
#if defined(__linux__) || defined(_WIN32) || defined(__APPLE__)
|
||||
|
||||
extern "C" _Unwind_Reason_Code __gxx_personality_v0(int, _Unwind_Action, std::uint64_t, _Unwind_Exception*, _Unwind_Context*);
|
||||
|
||||
@@ -32,6 +32,7 @@ extern "C" void __cxa_vec_delete3_nid_postfix(void*, std::size_t, std::size_t, v
|
||||
extern "C" _Unwind_Reason_Code _Unwind_Backtrace_nid_postfix(_Unwind_Trace_Fn, void*);
|
||||
extern "C" _Unwind_Reason_Code _Unwind_ForcedUnwind_nid_postfix(_Unwind_Exception*, _Unwind_Stop_Fn, void*);
|
||||
extern "C" std::uintptr_t _Unwind_GetIP_nid_postfix(_Unwind_Context*);
|
||||
extern "C" std::uintptr_t _Unwind_GetGR_nid_postfix(_Unwind_Context*, int);
|
||||
extern "C" void (*_ZSt13set_terminatePFvvE_nid_postfix(void(*)()))();
|
||||
|
||||
thread_local int destroyed = 0;
|
||||
@@ -263,5 +264,25 @@ int main() {
|
||||
return _URC_NO_REASON;
|
||||
}, &frames);
|
||||
assert(backtrace == _URC_END_OF_STACK && frames >= 2);
|
||||
frames = 0;
|
||||
backtrace = _Unwind_Backtrace_nid_postfix([](_Unwind_Context* context, void* argument) {
|
||||
const auto* slots = *reinterpret_cast<const std::uintptr_t* const*>(context);
|
||||
const auto validRegister = reinterpret_cast<bool (*)(_Unwind_Context*, int)>(slots[2]);
|
||||
const auto getRegister = reinterpret_cast<std::uintptr_t (*)(_Unwind_Context*, int)>(slots[3]);
|
||||
const auto setRegister = reinterpret_cast<void (*)(_Unwind_Context*, int, std::uintptr_t)>(slots[4]);
|
||||
const auto validFloatRegister = reinterpret_cast<bool (*)(_Unwind_Context*, int)>(slots[5]);
|
||||
const auto isSignalFrame = reinterpret_cast<bool (*)(_Unwind_Context*)>(slots[11]);
|
||||
assert(validRegister(context, -1) && validRegister(context, -2) && validRegister(context, 16) && !validRegister(context, 17));
|
||||
assert(getRegister(context, -1) == _Unwind_GetIP_nid_postfix(context) && getRegister(context, -1) != 0);
|
||||
assert(getRegister(context, -2) == _Unwind_GetGR_nid_postfix(context, 7));
|
||||
const auto rbx = getRegister(context, 3);
|
||||
setRegister(context, 3, rbx + 1);
|
||||
assert(_Unwind_GetGR_nid_postfix(context, 3) == rbx + 1);
|
||||
setRegister(context, 3, rbx);
|
||||
assert(!validFloatRegister(context, 17) && !isSignalFrame(context));
|
||||
++*static_cast<int*>(argument);
|
||||
return _URC_NO_REASON;
|
||||
}, &frames);
|
||||
assert(backtrace == _URC_END_OF_STACK && frames >= 2);
|
||||
std::puts("exception runtime tests passed");
|
||||
}
|
||||
@@ -12,7 +12,7 @@
|
||||
#endif
|
||||
|
||||
static_assert(sizeof(_Unwind_Context::registers) == 17 * sizeof(std::uintptr_t));
|
||||
static_assert(offsetof(_Unwind_Context, cfa) == 17 * sizeof(std::uintptr_t));
|
||||
static_assert(offsetof(_Unwind_Context, cfa) == offsetof(_Unwind_Context, registers) + 17 * sizeof(std::uintptr_t));
|
||||
|
||||
extern "C" void CaptureProbe(std::uintptr_t*);
|
||||
extern "C" void RestoreProbe(std::uintptr_t*, std::uintptr_t*);
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
- [sceKernelSync](../../../core/libs/prx/libc/src/General.cpp) (libkernel) flushes only the host files and directories recorded through `RecordWrittenPath_nid_no_patch` (the libc, libkernel, save data and app content file functions) and their changed parent directories. A host write in a library that does not record its path is not flushed by `sceKernelSync`
|
||||
- [sceKernelSync](../../../core/libs/prx/libc/src/General.cpp) (libkernel) on Windows skips a recorded file it cannot open for writing (read-only, or held open by a handle that denies write sharing), since `FlushFileBuffers` needs a writable handle. On Linux, it skips a recorded file it cannot open for reading. Such a file's data may not reach the disk before a host crash; the guest reads the same data either way
|
||||
- [`fopen` streams](../../../core/libs/prx/libc/src/FileAndHeap.cpp) (libc) on Windows share delete access, like the descriptors [`sceKernelOpen`](../../../core/libs/prx/libkernel/File/src/Open.cpp) opens, so a file the guest holds open can be removed or renamed. A stream reopened with `freopen` still goes through `_wfreopen`, which does not share delete access
|
||||
- The [`_Unwind_Context`](../../../core/libs/prx/libc/include/exceptions/Unwind.hpp) that `_Unwind_Backtrace` passes to its callback starts with a table laid out like LLVM libunwind's `AbstractUnwindCursor` virtual table, because homebrew built with the ps5-payload-dev SDK links libunwind statically and reads the frames with its own `_Unwind_GetIP` (`__unw_get_reg`), which calls through that table. Only register reads and writes (`validReg`, `getReg`, `setReg`, with `UNW_REG_IP` and `UNW_REG_SP`), `validFloatReg` (always false) and `isSignalFrame` are answered; the other slots throw. The layout follows libunwind's `UnwindCursor.hpp`, not the console's own `libSceLibcInternal`, which is not checked
|
||||
- The Windows libc [exception unwinder](../../../core/libs/prx/libc/src/exception/Unwind.cpp) steps through frames of modules without the project's `.ehmeta` and `.ehfram` tables (system DLLs, and WinLibs GCC 15.2's `libstdc++-6.dll` and `libwinpthread-1.dll`, whose `.eh_frame` sections are empty) with their PE unwind data through `RtlVirtualUnwind`, but does not run Windows exception or termination handlers: it stops at a frame whose unwind data has one, such as a C++ cleanup through `__gxx_personality_seh0` in `libstdc++-6.dll` or a system DLL function like `qsort` in `ucrtbase.dll` (Windows 10 22H2), and the exception is then reported as unhandled
|
||||
- The libc [SSE4a trap emulation](../../../core/libs/prx/libc/src/specifics/windows/Sse4aEmulation.hpp) on Windows is superseded by `--to-intel` and remains only until the relinked title has been verified without it.
|
||||
- [getifaddrs](../../../core/libs/prx/libc/src/Interfaces.cpp) (libc) lists the host's IPv4 and IPv6 addresses in FreeBSD's `ifaddrs` and sockaddr layouts. FreeBSD's per-interface AF_LINK entries (MAC, index, MTU) and their `if_data` are not produced, so interfaces without an address are omitted. Names are the host's (Linux `lo`, `eth0`; Windows NDIS names such as `ethernet_32769`, or `if<index>` when one does not fit 15 bytes), not the console's. A Linux point-to-point address without a peer loses `IFF_POINTOPOINT`, and an IPv4 broadcast address the host omits is computed from the netmask. On Windows the flags are only `IFF_UP`, `IFF_RUNNING`, `IFF_LOOPBACK`, `IFF_BROADCAST` and `IFF_MULTICAST`, derived from the adapter: PPP and tunnel adapters get neither `IFF_BROADCAST` nor `IFF_POINTOPOINT` because their peer address is not reported, and an address whose prefix length the host reports as unspecified gets a host mask.
|
||||
|
||||
Reference in new issue
Block a user