Files
AnyPS5/core/libs/tests/GuestUnwindModuleInfoMacos.cpp
T
Emanueleandmugurc bfa5368657 fix(libkernel): report unwind information for guest and host addresses on macOS
Two unwind queries a title makes went wrong on macOS, so a C++ exception the title would catch ended the process instead: God of War Sons of Sparta (PPSA28997) stops on an IL2CPP exception it normally handles.

- sceKernelGetModuleInfoForUnwind found a relinked image and its PT_GNU_EH_FRAME header but left eh_frame_addr and eh_frame_size at zero, so the title's unwinder found no FDE for its own frame. The tables are now read with EhFrame::ReadTables over the image's load segments, as ModuleInfo does on Linux, and eh_frame_size includes the zero terminator, as on Linux.
- sceSysmoduleGetModuleInfoForUnwind read /proc/self/maps outside Windows and threw on every call on macOS. It now answers from mach_vm_region, with the image path from dladdr, as the Linux branch answers from the mapping.

guest_unwind_module_info now runs on macOS: the test executable carries the __APS5DATA,__meta program headers that the macOS dl_iterate_phdr reads from a relinked image. guest_sysmodule checks the macOS answers against mach_vm_region.

Co-authored-by: mugurc <[email protected]>
2026-10-21 18:39:48 +02:00

88 lines
3.3 KiB
C++

#include "SceTypes.hpp"
#include "prx/libc/include/specifics/linux/ElfTypes.hpp"
#include "tests/GuestUnwindModuleInfoFixture.hpp"
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <stdexcept>
extern "C" int APS5_VABI sceKernelGetModuleInfoForUnwind(uint64_t addr, int flags, ModuleInfoForUnwind* info);
struct GuestImageMeta {
std::uint64_t version = 2;
std::uint64_t loadBias = 0;
std::uint8_t unused[0x48] = {};
std::uint64_t count = 0;
Elf64_Phdr headers[2] = {};
};
static_assert(offsetof(GuestImageMeta, count) == 0x58 && offsetof(GuestImageMeta, headers) == 0x60);
[[gnu::section("__APS5DATA,__meta"), gnu::used]] static GuestImageMeta meta;
static UnwindFixture fixture;
static void Require(bool value, const char* message) {
if (!value) throw std::runtime_error(message);
}
static std::uint64_t Address(const void* pointer) {
return reinterpret_cast<std::uint64_t>(pointer);
}
static void Describe(bool withHeader, std::uint64_t loadSize) {
meta.count = withHeader ? 2 : 1;
meta.headers[0] = {};
meta.headers[0].p_type = PT_LOAD;
meta.headers[0].p_vaddr = Address(&fixture);
meta.headers[0].p_memsz = loadSize;
meta.headers[1] = {};
meta.headers[1].p_type = PT_GNU_EH_FRAME;
meta.headers[1].p_vaddr = Address(&fixture.header);
meta.headers[1].p_memsz = sizeof(fixture.header);
}
static ModuleInfoForUnwind Query() {
ModuleInfoForUnwind info{};
Require(sceKernelGetModuleInfoForUnwind(Address(&fixture.frames), 0, &info) == 0, "unwind query failed");
Require(info.st_size == sizeof(ModuleInfoForUnwind), "unwind info has the wrong size");
Require(info.seg0_addr == Address(&fixture) && info.seg0_size == sizeof(fixture), "unwind segment is not the fixture's load segment");
return info;
}
static void RequireTables(std::uint64_t size) {
const auto info = Query();
Require(info.eh_frame_hdr_addr == Address(&fixture.header), "wrong EH header address");
Require(info.eh_frame_addr == Address(&fixture.frames), "wrong EH frame address");
Require(info.eh_frame_size == size, "EH frame size must include the four-byte terminator");
}
static bool QueryThrows() {
ModuleInfoForUnwind info{};
try {
sceKernelGetModuleInfoForUnwind(Address(&fixture.frames), 0, &info);
} catch (const std::runtime_error&) {
return true;
}
return false;
}
int main() {
try {
fixture.header.frames = static_cast<std::int32_t>(Address(&fixture.frames) - (Address(&fixture.header) + 4));
Describe(true, sizeof(fixture));
RequireTables(4 + 12 + 4);
fixture.frames.terminator = 12;
fixture.frames.spare[0] = 1;
RequireTables(4 + 12 + 4 + 12 + 4);
fixture.frames.terminator = 0;
Describe(false, sizeof(fixture));
const auto bare = Query();
Require(bare.eh_frame_hdr_addr == 0 && bare.eh_frame_addr == 0 && bare.eh_frame_size == 0, "an image without PT_GNU_EH_FRAME reported tables");
fixture.frames.terminator = 12;
Describe(true, offsetof(UnwindFixture, frames) + offsetof(UnwindFixtureFrames, spareTerminator));
Require(QueryThrows(), "a table whose terminator lies outside the image was accepted");
} catch (const std::exception& error) {
std::fprintf(stderr, "%s\n", error.what());
return 1;
}
}