test(libSceSysmodule): compare sceSysmoduleGetModuleInfoForUnwind with /proc/self/maps

Covers code, stack and an anonymous page, and an unmapped address. On Linux the test runs again with APS5_NO_PROCMAP_QUERY so the line scan stays covered.
This commit is contained in:
Gustavo Ariel Gamarra Rojas 2026-10-10 21:23:19 -03:00
parent ad4add2b1a
commit 869d73e4d8
2 changed files with 71 additions and 0 deletions

View File

@ -7,6 +7,10 @@ if(BUILD_TESTING)
target_include_directories(guest_sysmodule_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_sysmodule_tests PRIVATE libSceSysmodule)
add_test(NAME guest_sysmodule COMMAND guest_sysmodule_tests)
if(UNIX AND NOT APPLE)
add_test(NAME guest_sysmodule_maps_scan COMMAND guest_sysmodule_tests)
set_tests_properties(guest_sysmodule_maps_scan PROPERTIES ENVIRONMENT "APS5_NO_PROCMAP_QUERY=1")
endif()
if(WIN32)
set_tests_properties(guest_sysmodule PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceSysmodule>")

View File

@ -1,6 +1,15 @@
#include "SceTypes.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include <cstdint>
#include <cstdlib>
#ifdef __linux__
#include <cstdio>
#include <fstream>
#include <stdexcept>
#include <string>
#include <sys/mman.h>
#include <unistd.h>
#endif
extern "C" {
int APS5_VABI sceSysmoduleIsLoaded(std::uint16_t id);
@ -8,6 +17,7 @@ int APS5_VABI sceSysmoduleLoadModule(std::uint16_t id);
int APS5_VABI sceSysmoduleUnloadModule(std::uint16_t id);
int APS5_VABI sceSysmoduleLoadModuleInternal(std::uint32_t id);
int APS5_VABI sceSysmoduleUnloadModuleInternal(std::uint32_t id);
int APS5_VABI sceSysmoduleGetModuleInfoForUnwind(std::uint64_t addr, int flags, ModuleInfoForUnwind* info);
}
namespace {
@ -18,6 +28,60 @@ constexpr std::uint16_t kFiberModuleId = 0x0006;
constexpr std::uint16_t kUltModuleId = 0x0007;
constexpr int kModuleUnloaded = static_cast<int>(0x805A1001);
#ifdef __linux__
struct Mapping {
std::uint64_t start = 0;
std::uint64_t end = 0;
std::string path;
};
Mapping MapsEntry(std::uint64_t address) {
std::ifstream maps("/proc/self/maps");
std::string line;
while (std::getline(maps, line)) {
unsigned long long start = 0;
unsigned long long end = 0;
char path[4096] = {};
const int parsed = std::sscanf(line.c_str(), "%llx-%llx %*s %*x %*x:%*x %*u %4095s", &start, &end, path);
if (parsed >= 2 && address >= start && address < end) return Mapping{start, end, parsed == 3 ? path : ""};
}
std::abort();
}
void RequireUnwindMatchesMaps(const void* pointer) {
const auto address = reinterpret_cast<std::uint64_t>(pointer);
const auto expected = MapsEntry(address);
ModuleInfoForUnwind info{};
Require(sceSysmoduleGetModuleInfoForUnwind(address, 0, &info) == 0);
Require(info.st_size == sizeof(ModuleInfoForUnwind));
Require(info.seg0_addr == expected.start && info.seg0_size == expected.end - expected.start);
Require(std::string(info.name) == expected.path.substr(0, sizeof(info.name) - 1));
Require(info.eh_frame_hdr_addr == 0 && info.eh_frame_addr == 0 && info.eh_frame_size == 0);
}
void CheckUnwindInfo() {
RequireUnwindMatchesMaps(reinterpret_cast<const void*>(&RequireUnwindMatchesMaps));
RequireUnwindMatchesMaps(reinterpret_cast<const void*>(&sceSysmoduleGetModuleInfoForUnwind));
int local = 0;
RequireUnwindMatchesMaps(&local);
const auto page = static_cast<std::size_t>(sysconf(_SC_PAGESIZE));
void* anonymous = mmap(nullptr, page, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
Require(anonymous != MAP_FAILED);
RequireUnwindMatchesMaps(anonymous);
Require(munmap(anonymous, page) == 0);
void* gap = mmap(nullptr, page, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
Require(gap != MAP_FAILED && munmap(gap, page) == 0);
ModuleInfoForUnwind info{};
bool threw = false;
try {
sceSysmoduleGetModuleInfoForUnwind(reinterpret_cast<std::uint64_t>(gap), 0, &info);
} catch (const std::runtime_error&) {
threw = true;
}
Require(threw);
}
#endif
}
int main() {
@ -37,4 +101,7 @@ int main() {
Require(sceSysmoduleIsLoaded(kUltModuleId) == 0);
Require(sceSysmoduleUnloadModule(kUltModuleId) == 0);
Require(sceSysmoduleIsLoaded(kUltModuleId) == kModuleUnloaded);
#ifdef __linux__
CheckUnwindInfo();
#endif
}