mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-18 23:07:07 -05:00
Unreal Engine walks the frame-pointer chain until it reads a return
address of 0 and never checks the frame pointer itself. Guest threads
started with whatever rbp the host code held, so the walk ran into host
start-up frames (glibc's saved rbp 0x21 in a LEGO Batman core dump,
address 8 in Returnal on Windows) and faulted. Fibers ended their chain
with a null frame, and makecontext started its function with rbp 0, so
the same walker read address 8 there too.
Guest threads now start through a thunk that points rbp at a
{saved rbp 0, return address 0} record, as the relinker's entry stub
does for the main thread; the fiber trampolines push the same record,
and the makecontext start function is built with frame pointers so its
frame, over the zero return address makecontext stores, ends the chain.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
71 lines
2.5 KiB
C++
71 lines
2.5 KiB
C++
#include "SceTypes.hpp"
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
|
|
extern "C" {
|
|
int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name);
|
|
int APS5_VABI scePthreadJoin(Pthread thread, void** retval);
|
|
}
|
|
|
|
static constexpr int SCE_OK = 0;
|
|
static constexpr std::uintptr_t ChainReach = std::uintptr_t{64} << 10;
|
|
|
|
static void Require(bool value, const char* what) {
|
|
if (value) return;
|
|
std::fprintf(stderr, "guest pthread frame chain: %s\n", what);
|
|
std::abort();
|
|
}
|
|
|
|
struct Walk {
|
|
int frames = 0;
|
|
bool leftStack = false;
|
|
bool ended = false;
|
|
bool endedOnZeroRecord = false;
|
|
};
|
|
|
|
static Walk g_walk;
|
|
|
|
[[gnu::noinline]] static void WalkFrameChain() {
|
|
auto* frame = static_cast<std::uintptr_t*>(__builtin_frame_address(0));
|
|
const auto low = reinterpret_cast<std::uintptr_t>(frame);
|
|
for (int depth = 0; depth < 100; ++depth) {
|
|
const auto address = reinterpret_cast<std::uintptr_t>(frame);
|
|
if (address < low || address - low > ChainReach) {
|
|
g_walk.leftStack = true;
|
|
return;
|
|
}
|
|
++g_walk.frames;
|
|
if (frame[1] == 0) {
|
|
g_walk.ended = true;
|
|
g_walk.endedOnZeroRecord = frame[0] == 0;
|
|
return;
|
|
}
|
|
auto* next = reinterpret_cast<std::uintptr_t*>(frame[0]);
|
|
if (reinterpret_cast<std::uintptr_t>(next) <= address) {
|
|
g_walk.leftStack = true;
|
|
return;
|
|
}
|
|
frame = next;
|
|
}
|
|
}
|
|
|
|
static void* APS5_VABI WalkingEntry(void* arg) {
|
|
WalkFrameChain();
|
|
return arg;
|
|
}
|
|
|
|
int main() {
|
|
Pthread thread = nullptr;
|
|
Require(scePthreadCreate(&thread, nullptr, WalkingEntry, &g_walk, "frame chain") == SCE_OK, "create a guest thread");
|
|
void* result = nullptr;
|
|
Require(scePthreadJoin(thread, &result) == SCE_OK, "join the guest thread");
|
|
Require(result == &g_walk, "the guest entry gets its argument and its return value reaches scePthreadJoin");
|
|
Require(!g_walk.leftStack, "the frame chain of a guest thread stays on its stack and climbs it");
|
|
Require(g_walk.ended, "the frame chain of a guest thread ends at a frame whose return address is 0");
|
|
Require(g_walk.endedOnZeroRecord, "the last frame of a guest thread's chain also has a saved frame pointer of 0");
|
|
Require(g_walk.frames == 3, "the chain is the walker's frame, the guest entry's frame and the zero record");
|
|
std::puts("guest pthread frame chain tests passed");
|
|
return 0;
|
|
}
|