From 34637895970b09b7a96834dfffd3d3d478417635 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 18 Aug 2026 14:19:58 +0200 Subject: [PATCH] Find the companion ELF when a game is launched by folder, and keep line info Two things kept the companion ELF from doing its job. The first is the one that mattered: fileToStart is the game's own *directory* for folder-launched homebrew (IdentifiedFileType::PSP_PBP_DIRECTORY), which is the normal case when you pick a homebrew in the UI. The search navigated up from it regardless, landing in PSP/GAME and listing sibling games - all directories, all skipped - so app.elf sitting right next to the EBOOT was never found. It only ever worked when the path pointed at the EBOOT itself, which is how headless is invoked, which is why it looked fine from there. Searches the directory itself now when that's what it's given. The second: line info didn't survive loading a savestate. Modules aren't just re-registered there, they're destroyed and rebuilt (KernelObjectPool::Clear), so removing a module's lines in ~PSPModule threw the table away on every state load. The previous commit worked around it by re-reading the companion, which was both wasteful and no help at all to an ELF launched directly - those bytes are long gone by then. SymbolMap already solves this and line info now does it the same way: keep what you have, and let whatever next claims the address range replace it. AddModule replaces by key and is called for every module load, including ones with no line info of their own, so a range gets retired when it's genuinely reused. The whole table goes when the game does, in PSP_Shutdown. That also means the savestate path has nothing to re-read, so state loads no longer pay to re-parse a multi-megabyte ELF. The tradeoff is a window between a module unloading and its range being reclaimed where a lookup can still answer for it. For a debugger that's a stale file:line on an address nothing owns, against certain and total loss on every state load. Verified with --auto-save-load-symbols off, launched both ways: by directory (the case that was broken) and by EBOOT path, both give 3734 symbols and 98383 line rows. pspautotests 314/314, UnitTest 55/55. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9 --- Core/Debugger/LineInfo.h | 8 +++++--- Core/ELF/ElfReader.cpp | 7 ++++++- Core/HLE/sceKernelModule.cpp | 18 ++++++++++++------ Core/System.cpp | 4 ++++ 4 files changed, 27 insertions(+), 10 deletions(-) diff --git a/Core/Debugger/LineInfo.h b/Core/Debugger/LineInfo.h index e42bebad4f..4e3993b29b 100644 --- a/Core/Debugger/LineInfo.h +++ b/Core/Debugger/LineInfo.h @@ -54,9 +54,11 @@ public: // to be already has final addresses, so the delta is zero. int AddModule(std::string_view elfData, u32 moduleStart, u32 moduleSize, u32 addressDelta); - // Keyed the same way SymbolMap::UnloadModule is, so that unloading one module drops only its - // own lines. Each module owns its rows and its file names outright - there's no shared table - // for an unload to have to pick apart. + // Each module owns its rows and its file names outright, so dropping one can never disturb + // another's. Note this isn't called when a module unloads: a savestate load destroys and + // rebuilds every kernel object, and doing it there discarded the table every time - AddModule + // replacing by key is what retires a range instead, when something else claims it. See + // ~PSPModule. void RemoveModule(u32 moduleStart, u32 moduleSize); void Clear(); diff --git a/Core/ELF/ElfReader.cpp b/Core/ELF/ElfReader.cpp index 068e06c3cb..770bc7f155 100644 --- a/Core/ELF/ElfReader.cpp +++ b/Core/ELF/ElfReader.cpp @@ -934,7 +934,12 @@ int LoadCompanionElfDebugInfo(const Path &gameFile, u32 moduleBase, u32 moduleSi if (gameFile.empty() || gameFile.Type() != PathType::NATIVE) return 0; - const Path dir = gameFile.NavigateUp(); + // fileToStart is the game's own directory for folder-launched homebrew + // (IdentifiedFileType::PSP_PBP_DIRECTORY - the normal case when you pick one in the UI), and + // the EBOOT/ISO itself otherwise. Navigating up from a directory lands in PSP/GAME and lists + // sibling *games*, so the companion right there next to the EBOOT was never found - which is + // exactly the interactive case this exists for. + const Path dir = File::IsDirectory(gameFile) ? gameFile : gameFile.NavigateUp(); std::vector files; if (!File::GetFilesInDir(dir, &files, "elf:")) return 0; diff --git a/Core/HLE/sceKernelModule.cpp b/Core/HLE/sceKernelModule.cpp index 063140197f..da425070ea 100644 --- a/Core/HLE/sceKernelModule.cpp +++ b/Core/HLE/sceKernelModule.cpp @@ -211,8 +211,14 @@ PSPModule::~PSPModule() { userMemory.Free(memoryBlockAddr); } g_symbolMap->UnloadModule(memoryBlockAddr, memoryBlockSize); - // Keyed identically, so a module going away takes its own line info and nothing else's. - g_lineInfo.RemoveModule(memoryBlockAddr, memoryBlockSize); + // Deliberately *not* dropping this module's line info here. Loading a savestate deletes + // every kernel object and rebuilds it (KernelObjectPool::Clear), so removing on destruction + // threw the line table away every time a state was loaded - and for an ELF launched + // directly there's no file left to read it back from. SymbolMap has the same problem and + // solves it the same way: keep what you have, and let the module that next claims the + // address range replace it (LineInfoMap::AddModule replaces by key, and it's called for + // every module load whether or not that module has any line info to add). The whole thing + // is dropped when the game does, in PSP_Shutdown. } if (modulePtr.ptr) { @@ -308,11 +314,11 @@ void PSPModule::DoState(PointerWrap &p) { char moduleName[29] = { 0 }; truncate_cpy(moduleName, nm.name); if (memoryBlockAddr != 0) { + // Re-registering is enough to bring both back: SymbolMap keeps every symbol it has ever + // seen and just rebuilds its active view from the loaded modules, and line info is no + // longer dropped when a module is destroyed (see ~PSPModule). So there's nothing to + // re-read here, and a state load doesn't pay for re-parsing the companion ELF. g_symbolMap->AddModule(moduleName, memoryBlockAddr, memoryBlockSize, crc); - // Loading a state tears the old module down and re-registers it here, which takes its - // symbols and line info with it. The companion ELF is still sitting next to the game, - // so read it again rather than coming back from a savestate with none. - LoadCompanionElfDebugInfo(PSP_CoreParameter().fileToStart, memoryBlockAddr, memoryBlockSize); if (g_Config.bAutoSaveLoadSymbols) { int idx = g_symbolMap->GetModuleIndexByName(moduleName); if (idx > 0) { diff --git a/Core/System.cpp b/Core/System.cpp index 84ad473695..721b41e6d0 100644 --- a/Core/System.cpp +++ b/Core/System.cpp @@ -50,6 +50,7 @@ #include "Core/MIPS/MIPS.h" #include "Core/MIPS/MIPSAnalyst.h" #include "Core/MIPS/MIPSVFPUUtils.h" +#include "Core/Debugger/LineInfo.h" #include "Core/Debugger/SymbolMap.h" #include "Core/System.h" #include "Core/HLE/HLE.h" @@ -626,6 +627,9 @@ void CPU_Shutdown(bool success) { g_CoreParameter.mountIsoLoader = nullptr; delete g_symbolMap; g_symbolMap = nullptr; + // Line info outlives individual modules on purpose (see ~PSPModule), so the game going away is + // what ends it. + g_lineInfo.Clear(); g_lua.Shutdown();