fix(ajm, apr): keep AJM and APR state alive through static teardown

AJM's instance and batch tables and APR's file, directory and AMM
state were namespace-scope statics, destroyed by std::exit while the
title's threads keep calling the libraries. A decoder thread then read
a freed instance (an unknown codec), sceAjmBatchWait no longer found
its batch, and APR resolved file ids through a freed vector. The state
is now allocated once and never destroyed.
This commit is contained in:
Joey Fisher 2026-10-08 17:05:33 -06:00
parent c963432db0
commit f2d201fbbf
4 changed files with 99 additions and 11 deletions

View File

@ -36,6 +36,13 @@ if(BUILD_TESTING)
target_link_libraries(guest_ajm_tests PRIVATE libSceAjm.native libc)
configure_windows_unwind(guest_ajm_tests)
add_test(NAME guest_ajm COMMAND guest_ajm_tests)
if(UNIX)
add_test_executable(guest_ajm_teardown_tests ${LIBS_INCLUDE_DIR}/tests/GuestAjmTeardown.cpp)
target_include_directories(guest_ajm_teardown_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_ajm_teardown_tests PRIVATE libc ${CMAKE_DL_LIBS})
add_dependencies(guest_ajm_teardown_tests libSceAjm.native)
add_test(NAME guest_ajm_teardown COMMAND guest_ajm_teardown_tests $<TARGET_FILE:libSceAjm.native>)
endif()
if(WIN32)
set_tests_properties(guest_ajm PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceAjm.native>")

View File

@ -144,13 +144,13 @@ struct Instance {
}
};
std::mutex g_lock;
std::map<std::uint32_t, std::unique_ptr<Instance>> g_instances;
std::mutex& g_lock = *new std::mutex();
std::map<std::uint32_t, std::unique_ptr<Instance>>& g_instances = *new std::map<std::uint32_t, std::unique_ptr<Instance>>();
std::atomic<std::uint32_t> g_nextContext{1};
std::atomic<std::uint32_t> g_nextInstance{1};
std::atomic<std::uint32_t> g_nextBatch{1};
std::mutex g_batchLock;
std::set<std::uint32_t> g_batches;
std::mutex& g_batchLock = *new std::mutex();
std::set<std::uint32_t>& g_batches = *new std::set<std::uint32_t>();
enum class JobKind : std::uint32_t {
Initialize = 1,

View File

@ -39,15 +39,15 @@ struct AprFile {
std::uint64_t size;
};
std::mutex g_filesLock;
std::vector<AprFile> g_files;
std::unordered_map<std::string, std::uint32_t> g_idsByPath;
std::mutex& g_filesLock = *new std::mutex();
std::vector<AprFile>& g_files = *new std::vector<AprFile>();
std::unordered_map<std::string, std::uint32_t>& g_idsByPath = *new std::unordered_map<std::string, std::uint32_t>();
// File sizes by directory, listed on first use. Titles resolve thousands of package paths at startup,
// and one size query per path took over 20 s; a listing costs one directory read. Names are compared
// case-insensitively, like the host file system.
std::mutex g_directoriesLock;
std::unordered_map<std::string, std::unordered_map<std::string, std::uint64_t>> g_directories;
std::mutex& g_directoriesLock = *new std::mutex();
std::unordered_map<std::string, std::unordered_map<std::string, std::uint64_t>>& g_directories = *new std::unordered_map<std::string, std::unordered_map<std::string, std::uint64_t>>();
std::string _foldCase(std::string text) {
for (auto& character : text) {
@ -164,7 +164,7 @@ void _writeAddress(const Apr::WriteAddressCommand& command) {
std::array<std::atomic<std::uint32_t>, 256> g_counters{};
std::mutex g_counterWrites;
std::mutex& g_counterWrites = *new std::mutex();
std::uint32_t _counter(std::uint32_t index) {
return g_counters[index % g_counters.size()].load(std::memory_order_acquire);
@ -263,7 +263,7 @@ struct AmmState {
};
AmmState& _amm() {
static AmmState state;
static auto& state = *new AmmState();
return state;
}

View File

@ -0,0 +1,81 @@
#include "SceTypes.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <dlfcn.h>
namespace {
using Initialize = int (APS5_VABI*)(std::int64_t, std::uint32_t*);
using InstanceCreate = int (APS5_VABI*)(std::uint32_t, std::uint32_t, std::uint64_t, std::uint32_t*);
using InstanceDestroy = int (APS5_VABI*)(std::uint32_t, std::uint32_t);
using BatchInitialize = int (APS5_VABI*)(void*, std::size_t, AjmBatchInfo*);
using BatchJobClearContext = int (APS5_VABI*)(AjmBatchInfo*, std::uint32_t, void*);
using BatchStart = int (APS5_VABI*)(std::uint32_t, const AjmBatchInfo*, int, AjmBatchError*, std::uint32_t*);
using BatchWait = int (APS5_VABI*)(std::uint32_t, std::uint32_t, std::uint32_t, AjmBatchError*);
struct Functions {
Initialize initialize;
InstanceCreate instanceCreate;
InstanceDestroy instanceDestroy;
BatchInitialize batchInitialize;
BatchJobClearContext batchJobClearContext;
BatchStart batchStart;
BatchWait batchWait;
};
struct SidebandResult {
std::int32_t result;
std::int32_t internalResult;
};
Functions ajm{};
std::uint32_t context = 0;
std::uint32_t instance = 0;
[[noreturn]] void Fail(const char* message) {
std::fprintf(stderr, "%s\n", message);
std::fflush(stderr);
std::_Exit(1);
}
template <typename T>
T Resolve(void* library, const char* name) {
auto* symbol = dlsym(library, name);
if (symbol == nullptr) Fail(name);
return reinterpret_cast<T>(symbol);
}
void UseAfterStaticTeardown() {
alignas(16) std::uint8_t buffer[0x100]{};
AjmBatchInfo info{};
SidebandResult sideband{-1, -1};
if (ajm.batchInitialize(buffer, sizeof(buffer), &info) != 0 || ajm.batchJobClearContext(&info, instance, &sideband) != 0) Fail("a batch cannot be built after static teardown");
std::uint32_t batch = 0;
AjmBatchError error{};
if (ajm.batchStart(context, &info, 0, &error, &batch) != 0) Fail("sceAjmBatchStart failed after static teardown");
if (sideband.result != 0) Fail("an instance created before exit is unknown after static teardown");
if (ajm.batchWait(context, batch, 0, &error) != 0) Fail("sceAjmBatchWait does not find a batch started after static teardown");
if (ajm.instanceDestroy(context, instance) != 0) Fail("sceAjmInstanceDestroy does not find the instance after static teardown");
}
}
int main(int argc, char** argv) {
if (argc != 2) Fail("usage: guest_ajm_teardown_tests <libSceAjm.native>");
if (std::atexit(UseAfterStaticTeardown) != 0) Fail("atexit failed");
auto* library = dlopen(argv[1], RTLD_NOW);
if (library == nullptr) Fail(dlerror());
ajm = {
Resolve<Initialize>(library, "sceAjmInitialize"),
Resolve<InstanceCreate>(library, "sceAjmInstanceCreate"),
Resolve<InstanceDestroy>(library, "sceAjmInstanceDestroy"),
Resolve<BatchInitialize>(library, "sceAjmBatchInitialize"),
Resolve<BatchJobClearContext>(library, "sceAjmBatchJobClearContext"),
Resolve<BatchStart>(library, "sceAjmBatchStart"),
Resolve<BatchWait>(library, "sceAjmBatchWait"),
};
if (ajm.initialize(0, &context) != 0 || ajm.instanceCreate(context, 0, 0, &instance) != 0) Fail("AJM setup failed");
return 0;
}