Files
AnyPS5/core/relinker/elfpatcher/tests/EntryStubTests.cpp
T
john.maiaandClaude Opus 5.5 e4dcf3e6c0 fix(relinker): end the guest frame-pointer chain at the entry stub
The entry stubs called the title's entry with rbp left as the process
started it (0 on Linux) or as a loop counter left it (0 on Windows). The
title's start code pushes rbp, so the frame-pointer chain of its main
thread ended in a null frame pointer. Unreal Engine walks that chain to
capture call stacks (for an ensure, for example) and reads the return
address at [rbp + 8], so the walk read address 8 and crashed.

Both stubs now push a zero frame record (saved rbp 0, return address 0)
and point rbp at it before the call, so frame-pointer walks stop at the
entry. The Linux stub pushes it below the aligned stack pointer, keeping
the 16-byte alignment at the call. The Windows stub writes it into free
slots of its own frame at rsp+0x40.

PPSA15657 crashed in its stack walk at the title screen in about half of
its runs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0134RWCyiyydJBbfKz8g9Bwe
2026-10-09 23:42:31 +01:00

50 lines
2.0 KiB
C++

#include <elfpatcher/general/EntryStubBuilder.hpp>
#include <cstdint>
#include <cstring>
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>
namespace {
void require(const bool condition, const std::string& message) {
if (!condition) throw std::runtime_error(message);
}
bool bytesAt(const std::vector<std::uint8_t>& stub, std::size_t& offset, const std::vector<std::uint8_t>& expected) {
if (offset + expected.size() > stub.size() || std::memcmp(stub.data() + offset, expected.data(), expected.size()) != 0) return false;
offset += expected.size();
return true;
}
void stubTerminatesTheFrameChain() {
constexpr std::uint64_t stubVaddr = 0xb32db98;
constexpr std::uint64_t entryVaddr = 0x80;
const auto stub = Elfpatcher::EntryStubBuilder().BuildEntryStub(stubVaddr, entryVaddr);
std::size_t offset = 0;
require(bytesAt(stub, offset, {0x48, 0x89, 0xe7}), "the stub does not pass the initial stack pointer in rdi");
require(bytesAt(stub, offset, {0x48, 0x83, 0xe4, 0xf0}), "the stub does not align the stack to 16 bytes");
require(bytesAt(stub, offset, {0x6a, 0x00, 0x6a, 0x00}), "the stub does not push a zero frame record");
require(bytesAt(stub, offset, {0x48, 0x89, 0xe5}), "the stub does not point rbp at the zero frame record");
require(bytesAt(stub, offset, {0x48, 0x31, 0xf6}), "the stub does not clear rsi");
require(offset < stub.size() && stub[offset] == 0xe8, "the stub does not call the entry point");
std::int32_t rel32 = 0;
std::memcpy(&rel32, stub.data() + offset + 1, sizeof(rel32));
offset += 5;
require(stubVaddr + offset + static_cast<std::int64_t>(rel32) == entryVaddr, "the stub calls the wrong address");
require(bytesAt(stub, offset, {0x0f, 0x0b}) && offset == stub.size(), "the stub does not end with ud2 after the call");
}
}
int main() {
try {
stubTerminatesTheFrameChain();
} catch (const std::exception& error) {
std::cerr << error.what() << '\n';
return 1;
}
return 0;
}