diff --git a/Core/Debugger/LineInfo.cpp b/Core/Debugger/LineInfo.cpp index 16526b4b2a..412ff99bec 100644 --- a/Core/Debugger/LineInfo.cpp +++ b/Core/Debugger/LineInfo.cpp @@ -148,7 +148,7 @@ enum { // One .debug_line unit: header, then a bytecode program that walks a virtual machine whose output // rows are (address, file, line). See the DWARF spec, "Line Number Information". static bool ParseUnit(Reader &r, size_t unitEnd, std::vector *entries, - std::vector *files, std::map *fileIndex, u32 moduleStart, u32 moduleSize) { + std::vector *files, std::map *fileIndex, u32 moduleStart, u32 moduleSize, u32 addressDelta) { const uint16_t version = r.U16(); if (version < 2 || version > 4) { // v5 rewrote the file table to use form-coded entries, which is a different parser. Nothing @@ -208,12 +208,13 @@ static bool ParseUnit(Reader &r, size_t unitEnd, std::vector *entries bool sawAddress = false; auto emit = [&](u32 lineNo) { - // Rows before a DW_LNE_set_address belong to no real code; and anything outside the module - // we're relocating into would only produce bogus lookups. - if (!sawAddress || address > moduleSize) + // Rows before a DW_LNE_set_address belong to no real code, and anything that doesn't land + // inside the module after relocation would only produce bogus lookups. + const u32 finalAddress = addressDelta + address; + if (!sawAddress || finalAddress < moduleStart || finalAddress - moduleStart >= moduleSize) return; LineEntry e; - e.address = moduleStart + address; + e.address = finalAddress; e.line = lineNo; e.fileIndex = file < unitFiles.size() ? unitFiles[file] : 0; entries->push_back(e); @@ -282,7 +283,7 @@ static bool ParseUnit(Reader &r, size_t unitEnd, std::vector *entries return r.ok(); } -int LineInfoMap::AddModule(std::string_view elfData, u32 moduleStart, u32 moduleSize) { +int LineInfoMap::AddModule(std::string_view elfData, u32 moduleStart, u32 moduleSize, u32 addressDelta) { RemoveModule(moduleStart, moduleSize); const uint8_t *data = (const uint8_t *)elfData.data(); @@ -345,7 +346,7 @@ int LineInfoMap::AddModule(std::string_view elfData, u32 moduleStart, u32 module if (unitEnd > debugLine->sh_size) break; - if (!ParseUnit(r, unitEnd, &mod.entries, &mod.files, &fileIndex, moduleStart, moduleSize)) + if (!ParseUnit(r, unitEnd, &mod.entries, &mod.files, &fileIndex, moduleStart, moduleSize, addressDelta)) skippedUnits++; r.Seek(unitEnd); } diff --git a/Core/Debugger/LineInfo.h b/Core/Debugger/LineInfo.h index 61f013fe18..e42bebad4f 100644 --- a/Core/Debugger/LineInfo.h +++ b/Core/Debugger/LineInfo.h @@ -45,10 +45,14 @@ struct LineEntry { class LineInfoMap { public: - // Parses .debug_line out of an unstripped ELF image and relocates it to moduleStart. - // Returns the number of rows kept, or 0 if the ELF has nothing usable. Replaces whatever was - // held for the same module. - int AddModule(std::string_view elfData, u32 moduleStart, u32 moduleSize); + // Parses .debug_line out of an unstripped ELF image. Returns the number of rows kept, or 0 if + // the ELF has nothing usable. Replaces whatever was held for the same module. + // + // addressDelta is added to every address in the table, and rows that don't then land inside + // the module are dropped. That covers both shapes this arrives in: a companion ELF links at + // zero, so the delta is the module's base; an ELF launched directly and loaded where it asked + // 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 diff --git a/Core/ELF/ElfReader.cpp b/Core/ELF/ElfReader.cpp index 961a83b9bd..068e06c3cb 100644 --- a/Core/ELF/ElfReader.cpp +++ b/Core/ELF/ElfReader.cpp @@ -830,47 +830,62 @@ bool ElfReader::LoadSymbols() // Adds the STT_FUNC/STT_OBJECT symbols from one candidate ELF, if it looks like it belongs to a // module of this size. Returns the number added, 0 if it doesn't match or has nothing to offer. -static int LoadSymbolsFromCompanion(const std::string &data, u32 moduleBase, u32 moduleSize, const char **why) { +// Does this ELF describe the module we just loaded? Split out from the symbol loader so line info +// can reuse it: the two want the same identity check but different sections, and an ELF built with +// -g but stripped of its symbol table still has usable line numbers. +static bool CompanionElfMatchesModule(const std::string &data, u32 moduleSize, const char **why) { *why = "too small"; if (data.size() < sizeof(Elf32_Ehdr)) - return 0; + return false; const Elf32_Ehdr *header = (const Elf32_Ehdr *)data.data(); *why = "not an ELF"; if (header->e_ident[EI_MAG0] != ELFMAG0 || header->e_ident[EI_MAG1] != ELFMAG1 || header->e_ident[EI_MAG2] != ELFMAG2 || header->e_ident[EI_MAG3] != ELFMAG3) - return 0; + return false; if (header->e_ident[EI_CLASS] != ELFCLASS32) - return 0; + return false; *why = "no section headers"; if (!header->e_shoff || header->e_shentsize < sizeof(Elf32_Shdr)) - return 0; + return false; if ((size_t)header->e_shoff + (size_t)header->e_shnum * header->e_shentsize > data.size()) - return 0; - - auto section = [&](int i) { - return (const Elf32_Shdr *)(data.data() + header->e_shoff + (size_t)i * header->e_shentsize); - }; + return false; // Identity check. The companion links at base 0 and covers the same image the module was // built into, so the top of its highest section should land within a page of the module's // size. Without this an unrelated ELF sitting in the same folder would happily contribute // nonsense names at real addresses, which is worse than having none. u32 top = 0; - int symtabIndex = -1; for (int i = 0; i < header->e_shnum; i++) { - const Elf32_Shdr *s = section(i); + const Elf32_Shdr *s = (const Elf32_Shdr *)(data.data() + header->e_shoff + (size_t)i * header->e_shentsize); if (s->sh_addr) top = std::max(top, s->sh_addr + s->sh_size); - if (s->sh_type == SHT_SYMTAB) + } + *why = "image size doesn't match the loaded module"; + if (top > moduleSize || top + 0x1000 < moduleSize) + return false; + + *why = "ok"; + return true; +} + +static int LoadSymbolsFromCompanion(const std::string &data, u32 moduleBase, u32 moduleSize, const char **why) { + if (!CompanionElfMatchesModule(data, moduleSize, why)) + return 0; + + const Elf32_Ehdr *header = (const Elf32_Ehdr *)data.data(); + auto section = [&](int i) { + return (const Elf32_Shdr *)(data.data() + header->e_shoff + (size_t)i * header->e_shentsize); + }; + + int symtabIndex = -1; + for (int i = 0; i < header->e_shnum; i++) { + if (section(i)->sh_type == SHT_SYMTAB) symtabIndex = i; } *why = "no symbol table"; if (symtabIndex < 0) return 0; - *why = "image size doesn't match the loaded module"; - if (top > moduleSize || top + 0x1000 < moduleSize) - return 0; const Elf32_Shdr *symtab = section(symtabIndex); if (symtab->sh_link >= header->e_shnum || symtab->sh_entsize < sizeof(Elf32_Sym)) @@ -915,7 +930,7 @@ static int LoadSymbolsFromCompanion(const std::string &data, u32 moduleBase, u32 return added; } -int LoadCompanionElfSymbols(const Path &gameFile, u32 moduleBase, u32 moduleSize) { +int LoadCompanionElfDebugInfo(const Path &gameFile, u32 moduleBase, u32 moduleSize) { if (gameFile.empty() || gameFile.Type() != PathType::NATIVE) return 0; @@ -931,16 +946,23 @@ int LoadCompanionElfSymbols(const Path &gameFile, u32 moduleBase, u32 moduleSize if (!File::ReadBinaryFileToString(file.fullName, &data)) continue; const char *why = ""; + if (!CompanionElfMatchesModule(data, moduleSize, &why)) { + DEBUG_LOG(Log::Loader, "Companion ELF '%s' skipped: %s", file.name.c_str(), why); + continue; + } + + // A companion links at base 0, so its line table needs the module's base added. + const int lines = g_lineInfo.AddModule(data, moduleBase, moduleSize, moduleBase); + const int added = LoadSymbolsFromCompanion(data, moduleBase, moduleSize, &why); - if (added > 0) { - INFO_LOG(Log::Loader, "Loaded %d symbols from companion ELF '%s'", added, file.name.c_str()); + if (added > 0) g_symbolMap->SortSymbols(); - // Same file, already validated as belonging to this module, so its DWARF line table - // relocates the same way. Absent from anything built without -g, which is fine. - g_lineInfo.AddModule(data, moduleBase, moduleSize); + + if (lines > 0 || added > 0) { + INFO_LOG(Log::Loader, "Companion ELF '%s': %d symbols, %d line rows", file.name.c_str(), added, lines); return added; } - DEBUG_LOG(Log::Loader, "Companion ELF '%s' skipped: %s", file.name.c_str(), why); + DEBUG_LOG(Log::Loader, "Companion ELF '%s' matched but had nothing usable: %s", file.name.c_str(), why); } return 0; } diff --git a/Core/ELF/ElfReader.h b/Core/ELF/ElfReader.h index 2ded92d77f..132a7f8669 100644 --- a/Core/ELF/ElfReader.h +++ b/Core/ELF/ElfReader.h @@ -174,5 +174,9 @@ private: // This looks for such a companion in the game's own directory and, if one plausibly belongs to // this module, adds its function and data symbols at the module's load address. // -// Returns the number of symbols added, 0 if no matching ELF was found. -int LoadCompanionElfSymbols(const Path &gameFile, u32 moduleBase, u32 moduleSize); +// Symbols and DWARF line info both come out of that file, and both load unconditionally - +// bAutoSaveLoadSymbols governs writing .ppsym files back out, not reading debug info that's +// already sitting next to the game. Same reason the main ELF's own symbols aren't gated either. +// +// Returns the number of symbols added, 0 if none were or no matching ELF was found. +int LoadCompanionElfDebugInfo(const Path &gameFile, u32 moduleBase, u32 moduleSize); diff --git a/Core/HLE/sceKernelModule.cpp b/Core/HLE/sceKernelModule.cpp index 1483864e16..9adbc9f92f 100644 --- a/Core/HLE/sceKernelModule.cpp +++ b/Core/HLE/sceKernelModule.cpp @@ -1371,15 +1371,28 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load if (module->memoryBlockAddr != 0) { g_symbolMap->AddModule(moduleName, module->memoryBlockAddr, module->memoryBlockSize, module->crc); + + // Line info out of the module we just loaded. That covers an ELF launched directly - + // pspautotests' .elf builds, or homebrew you built yourself - where the debug sections are + // right here in the file. A PRX has none (prxgen strips every .debug section), so it's a + // cheap no-op for the usual EBOOT case, which the companion below handles instead. + // A relocated module's ELF addresses are relative to where it ended up; one loaded at the + // addresses it asked for already has final ones. + const u32 lineDelta = reader.DidRelocate() ? reader.GetVaddr() : 0; + g_lineInfo.AddModule(std::string_view((const char *)ptr, elfSize), module->memoryBlockAddr, module->memoryBlockSize, lineDelta); + + // Homebrew commonly ships the unstripped ELF next to the EBOOT; prxgen strips the symbols + // out of the PRX we actually load, so without this every function in it is just + // z_un_
. See LoadCompanionElfDebugInfo. + LoadCompanionElfDebugInfo(PSP_CoreParameter().fileToStart, module->memoryBlockAddr, module->memoryBlockSize); + + // Only the .ppsym files follow the setting - it's about writing symbols back out, not about + // reading debug info that's already sitting next to the game. if (g_Config.bAutoSaveLoadSymbols) { int idx = g_symbolMap->GetModuleIndexByName(moduleName); if (idx > 0) { g_symbolMap->LoadModuleSymbols(idx, SymbolMap::GetModuleSymbolsPath(moduleName, module->crc)); } - // Homebrew commonly ships the unstripped ELF next to the EBOOT; prxgen strips the - // symbols out of the PRX we actually load, so without this every function in it is - // just z_un_
. See LoadCompanionElfSymbols. - LoadCompanionElfSymbols(PSP_CoreParameter().fileToStart, module->memoryBlockAddr, module->memoryBlockSize); } }