Files
ppsspp/Core/ELF/ElfReader.cpp
T
Henrik RydgårdandClaude Opus 5 a6dd949df4 Load ELF debug info regardless of the symbol auto-save setting
bAutoSaveLoadSymbols is about writing .ppsym files back out and reading them in
again. It had also come to gate reading debug info that's simply sitting next to
the game, which is a different thing and shouldn't need asking for: the main
ELF's own symbols were already loaded unconditionally, but the companion ELF's
symbols and all line info were not.

Now the ELF is always the baseline - main or companion, symbols and line info -
and the setting only adds the .ppsym half on top of it.

Line info also loads from the module being loaded, not just from a companion,
so an ELF launched directly brings its own. A PRX has no .debug section for it
to find (prxgen strips them), so that's a cheap no-op for the usual EBOOT case,
which the companion path still covers.

That second source needs the two shapes distinguished, so AddModule takes an
explicit address delta rather than assuming a base: a companion links at zero
and wants the module's base added, while an ELF loaded at the addresses it asked
for already has final ones (bRelocate is just e_type != ET_EXEC). Rows that
don't land inside the module after that are dropped either way, which is a
better check than the old "offset smaller than the module" one.

Splitting the companion's identity check out of the symbol loader lets line info
reuse it, and drops an accidental requirement along the way: it used to reject
any companion without a symbol table, so an ELF built with -g but stripped of
its symbols would have contributed no line numbers either.

Verified with --auto-save-load-symbols off: CrossCraft's companion app.elf loads
3734 symbols and 98383 line rows where it previously loaded neither.

The direct-ELF path is not verified at runtime - it needs a bootable ELF that
carries DWARF, and there isn't one to hand. Both candidates here (pspautotests'
.elf builds and CrossCraft's own app.elf) are linked at address 0 and fail to
boot on that alone, which is pre-existing loader behaviour and nothing to do
with this.

pspautotests 314/314, UnitTest 55/55.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
2026-08-18 13:58:03 +02:00

969 lines
30 KiB
C++

// Copyright (c) 2012- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include <atomic>
#include "Common/StringUtils.h"
#include "Common/Thread/ParallelLoop.h"
#include "Common/File/DirListing.h"
#include "Common/File/FileUtil.h"
#include "Core/MemMap.h"
#include "Core/Reporting.h"
#include "Core/MIPS/MIPSTables.h"
#include "Core/ELF/ElfReader.h"
#include "Core/Debugger/MemBlockInfo.h"
#include "Core/Debugger/LineInfo.h"
#include "Core/Debugger/SymbolMap.h"
#include "Core/HLE/ErrorCodes.h"
#include "Core/HLE/sceKernelMemory.h"
#include "Core/HLE/sceKernelModule.h"
const char *ElfReader::GetSectionName(int section) const {
if (sections[section].sh_type == SHT_NULL)
return nullptr;
int stringsOffset = GetSectionDataOffset(header->e_shstrndx);
int nameOffset = sections[section].sh_name;
if (nameOffset < 0 || (size_t)nameOffset + stringsOffset >= size_) {
ERROR_LOG(Log::Loader, "ELF: Bad name offset %d + %d in section %d (max = %d)", nameOffset, stringsOffset, section, (int)size_);
return nullptr;
}
const char *ptr = (const char *)GetSectionDataPtr(header->e_shstrndx);
if (ptr)
return ptr + nameOffset;
else
return nullptr;
}
void addrToHiLo(u32 addr, u16 &hi, s16 &lo)
{
lo = (addr & 0xFFFF);
u32 naddr = addr - lo;
hi = naddr>>16;
u32 test = (hi<<16) + lo;
if (test != addr)
{
WARN_LOG_REPORT(Log::Loader, "HI16/LO16 relocation failure?");
}
}
bool ElfReader::LoadRelocations(const Elf32_Rel *rels, int numRelocs) {
std::vector<u32> relocOps;
relocOps.resize(numRelocs);
DEBUG_LOG(Log::Loader, "Loading %i relocations...", numRelocs);
std::atomic<int> numErrors;
numErrors.store(0);
{
for (int r = 0; r < numRelocs; r++) {
u32 info = rels[r].r_info;
u32 addr = rels[r].r_offset;
int type = info & 0xf;
// Often: 0 = code, 1 = data.
int readwrite = (info >> 8) & 0xff;
if (readwrite >= (int)ARRAY_SIZE(segmentVAddr)) {
if (numErrors < 10) {
ERROR_LOG_REPORT(Log::Loader, "Bad segment number %i", readwrite);
}
numErrors++;
continue;
}
addr += segmentVAddr[readwrite];
// It appears that misaligned relocations are allowed.
if (((addr & 3) && type != R_MIPS_32) || !Memory::IsValidAddress(addr)) {
if (numErrors < 10) {
WARN_LOG_REPORT(Log::Loader, "Suspicious address %08x, skipping reloc, type = %d", addr, type);
} else if (numErrors == 10) {
WARN_LOG(Log::Loader, "Too many bad relocations, skipping logging");
}
numErrors++;
continue;
}
// NOTE: During loading, we use plain reads instead of Memory::ReadUnchecked_Insruction.
// No blocks are created yet, so that's fine.
relocOps[r] = Memory::ReadUnchecked_U32(addr);
}
for (int r = 0; r < numRelocs; r++) {
VERBOSE_LOG(Log::Loader, "Loading reloc %i (%p)...", r, rels + r);
u32 info = rels[r].r_info;
u32 addr = rels[r].r_offset;
int type = info & 0xf;
int readwrite = (info >> 8) & 0xff;
int relative = (info >> 16) & 0xff;
if (readwrite >= (int)ARRAY_SIZE(segmentVAddr)) {
continue;
}
addr += segmentVAddr[readwrite];
if (((addr & 3) && type != R_MIPS_32) || !Memory::IsValidAddress(addr)) {
continue;
}
u32 op = relocOps[r];
const bool log = false;
//log=true;
if (log) {
DEBUG_LOG(Log::Loader, "rel at: %08x info: %08x type: %i", addr, info, type);
}
u32 relocateTo = relative >= (int)ARRAY_SIZE(segmentVAddr) ? 0 : segmentVAddr[relative];
switch (type) {
case R_MIPS_32:
if (log)
DEBUG_LOG(Log::Loader, "Full address reloc %08x", addr);
//full address, no problemo
op += relocateTo;
break;
case R_MIPS_26: //j, jal
//add on to put in correct address space
if (log)
DEBUG_LOG(Log::Loader, "j/jal reloc %08x", addr);
op = (op & 0xFC000000) | (((op & 0x03FFFFFF) + (relocateTo >> 2)) & 0x03FFFFFF);
break;
case R_MIPS_HI16: //lui part of lui-addiu pairs
{
if (log)
DEBUG_LOG(Log::Loader, "HI reloc %08x", addr);
u32 cur = (op & 0xFFFF) << 16;
u16 hi = 0;
bool found = false;
for (int t = r + 1; t < numRelocs; t++) {
int t_type = rels[t].r_info & 0xF;
if (t_type == R_MIPS_HI16)
continue;
u32 corrLoAddr = rels[t].r_offset + segmentVAddr[readwrite];
// In MotorStorm: Arctic Edge (US), these are sometimes R_MIPS_16 (instead of LO16.)
// It appears the PSP takes any relocation that is not a HI16.
if (t_type != R_MIPS_LO16) {
if (t_type != R_MIPS_16) {
// Let's play it safe for now and skip. We've only seen this type.
// These exists in some popular games like Assassin's Creed: Bloodlines and GTA: VCS: (https://report.ppsspp.org/logs/kind/1187)
ERROR_LOG_REPORT(Log::Loader, "ELF relocation HI16/%d pair (instead of LO16) at %08x / %08x", t_type, addr, corrLoAddr);
continue;
} else {
WARN_LOG_REPORT(Log::Loader, "ELF relocation HI16/%d(16) pair (instead of LO16) at %08x / %08x", t_type, addr, corrLoAddr);
}
}
// Should have matching index and segment info, according to llvm, which makes sense.
if ((rels[t].r_info >> 8) != (rels[r].r_info >> 8)) {
WARN_LOG_REPORT(Log::Loader, "ELF relocation HI16/LO16 with mismatching r_info lo=%08x, hi=%08x", rels[t].r_info, rels[r].r_info);
}
if (log) {
DEBUG_LOG(Log::Loader, "Corresponding lo found at %08x", corrLoAddr);
}
if (Memory::IsValidAddress(corrLoAddr)) {
s16 lo = (s16)relocOps[t];
cur += lo;
cur += relocateTo;
addrToHiLo(cur, hi, lo);
found = true;
break;
} else {
ERROR_LOG(Log::Loader, "Bad corrLoAddr %08x", corrLoAddr);
}
}
if (!found) {
ERROR_LOG_REPORT(Log::Loader, "R_MIPS_HI16: could not find R_MIPS_LO16 (r=%d of %d, addr=%08x)", r, numRelocs, addr);
}
op = (op & 0xFFFF0000) | hi;
}
break;
case R_MIPS_LO16: //addiu part of lui-addiu pairs
{
if (log)
DEBUG_LOG(Log::Loader, "LO reloc %08x", addr);
u32 cur = op & 0xFFFF;
cur += relocateTo;
cur &= 0xFFFF;
op = (op & 0xFFFF0000) | cur;
}
break;
case R_MIPS_GPREL16: //gp
// It seems safe to ignore this, almost a notification of a gp-relative operation?
break;
case R_MIPS_16:
op = (op & 0xFFFF0000) | (((int)(op & 0xFFFF) + (int)relocateTo) & 0xFFFF);
break;
case R_MIPS_NONE:
// This shouldn't matter, not sure the purpose of it.
break;
default:
{
char temp[256];
MIPSDisAsm(MIPSOpcode(op), 0, temp, sizeof(temp));
ERROR_LOG_REPORT(Log::Loader, "ARGH IT'S AN UNKNOWN RELOCATION!!!!!!!! %08x, type=%d : %s", addr, type, temp);
}
break;
}
Memory::WriteUnchecked_U32(op, addr);
NotifyMemInfo(MemBlockFlags::WRITE, addr, 4, "Relocation");
}
}
if (numErrors) {
WARN_LOG(Log::Loader, "%i bad relocations found!!!", numErrors.load());
}
return numErrors == 0;
}
void ElfReader::LoadRelocations2(int rel_seg)
{
u8 *buf, *end, *flag_table, *type_table;
int flag_table_size, type_table_size;
int flag_bits, seg_bits, type_bits;
int cmd, flag, seg, type;
int off_seg = 0, addr_seg, rel_base, rel_offset;
int relocate_to, last_type, lo16 = 0;
u32 op, addr;
int rcount = 0;
const Elf32_Phdr *ph = segments + rel_seg;
buf = (u8*)GetSegmentPtr(rel_seg);
if (!buf) {
ERROR_LOG_REPORT(Log::Loader, "Rel2 segment invalid");
return;
}
end = buf+ph->p_filesz;
flag_bits = buf[2];
type_bits = buf[3];
seg_bits = 1;
while((1<<seg_bits)<rel_seg)
seg_bits += 1;
buf += 4;
flag_table = buf;
flag_table_size = flag_table[0];
buf += flag_table_size;
type_table = buf;
type_table_size = type_table[0];
buf += type_table_size;
rel_base = 0;
last_type = -1;
while(buf<end){
cmd = *(u16*)(buf);
buf += 2;
flag = ( cmd<<(16-flag_bits))&0xffff;
flag = (flag>>(16-flag_bits))&0xffff;
flag = flag_table[flag];
seg = (cmd<<(16-seg_bits-flag_bits))&0xffff;
seg = (seg>>(16-seg_bits))&0xffff;
type = ( cmd<<(16-type_bits-seg_bits-flag_bits))&0xffff;
type = (type>>(16-type_bits))&0xffff;
type = type_table[type];
if((flag&0x01)==0){
off_seg = seg;
if((flag&0x06)==0){
rel_base = cmd>>(seg_bits+flag_bits);
}else if((flag&0x06)==4){
rel_base = buf[0] | (buf[1]<<8) | (buf[2]<<16) | (buf[3]<<24);
buf += 4;
}else{
ERROR_LOG_REPORT(Log::Loader, "Rel2: invalid size flag! %x", flag);
rel_base = 0;
}
}else{
addr_seg = seg;
relocate_to = addr_seg >= (int)ARRAY_SIZE(segmentVAddr) ? 0 : segmentVAddr[addr_seg];
if (!Memory::IsValidAddress(relocate_to)) {
ERROR_LOG_REPORT(Log::Loader, "ELF: Bad address to relocate to: %08x (segment %d)", relocate_to, addr_seg);
continue;
}
if((flag&0x06)==0x00){
rel_offset = cmd;
if(cmd&0x8000){
rel_offset |= 0xffff0000;
rel_offset >>= type_bits+seg_bits+flag_bits;
rel_offset |= 0xffff0000;
}else{
rel_offset >>= type_bits+seg_bits+flag_bits;
}
rel_base += rel_offset;
}else if((flag&0x06)==0x02){
rel_offset = cmd;
if(cmd&0x8000)
rel_offset |= 0xffff0000;
rel_offset >>= type_bits+seg_bits+flag_bits;
rel_offset = (rel_offset<<16) | (buf[0]) | (buf[1]<<8);
buf += 2;
rel_base += rel_offset;
}else if((flag&0x06)==0x04){
rel_base = buf[0] | (buf[1]<<8) | (buf[2]<<16) | (buf[3]<<24);
buf += 4;
}else{
ERROR_LOG_REPORT(Log::Loader, "Rel2: invalid relocat size flag! %x", flag);
}
rel_offset = rel_base+segmentVAddr[off_seg];
if (!Memory::IsValidAddress(rel_offset)) {
ERROR_LOG_REPORT(Log::Loader, "ELF: Bad rel_offset: %08x", rel_offset);
continue;
}
if((flag&0x38)==0x00){
lo16 = 0;
}else if((flag&0x38)==0x08){
if(last_type!=0x04)
lo16 = 0;
}else if((flag&0x38)==0x10){
lo16 = (buf[0]) | (buf[1]<<8);
if(lo16&0x8000)
lo16 |= 0xffff0000;
buf += 2;
}else{
ERROR_LOG_REPORT(Log::Loader, "Rel2: invalid lo16 type! %x", flag);
}
op = Memory::Read_Instruction(rel_offset, true).encoding;
VERBOSE_LOG(Log::Loader, "Rel2: %5d: CMD=0x%04X flag=%x type=%d off_seg=%d offset=%08x addr_seg=%d op=%08x", rcount, cmd, flag, type, off_seg, rel_base, addr_seg, op);
switch(type){
case 0:
continue;
case 2: // R_MIPS_32
op += relocate_to;
break;
case 3: // R_MIPS_26
case 6: // R_MIPS_J26
case 7: // R_MIPS_JAL26
op = (op&0xFC000000) | (((op&0x03FFFFFF)+(relocate_to>>2))&0x03FFFFFF);
// To be safe, let's force it to the specified jump.
if (type == 6)
op = (op & ~0xFC000000) | 0x08000000;
else if (type == 7)
op = (op & ~0xFC000000) | 0x0C000000;
break;
case 4: // R_MIPS_HI16
addr = ((op<<16)+lo16)+relocate_to;
if(addr&0x8000)
addr += 0x00010000;
op = (op&0xffff0000) | (addr>>16 );
break;
case 1:
case 5: // R_MIPS_LO16
op = (op&0xffff0000) | (((op&0xffff)+relocate_to)&0xffff);
break;
default:
ERROR_LOG_REPORT(Log::Loader, "Rel2: unexpected relocation type! %x", type);
break;
}
Memory::WriteUnchecked_U32(op, rel_offset);
NotifyMemInfo(MemBlockFlags::WRITE, rel_offset, 4, "Relocation2");
rcount += 1;
}
}
}
int ElfReader::LoadInto(u32 loadAddress, bool fromTop) {
DEBUG_LOG(Log::Loader,"String section: %i", header->e_shstrndx);
if (size_ < sizeof(Elf32_Ehdr)) {
ERROR_LOG(Log::Loader, "Truncated ELF header, %d bytes", (int)size_);
// Probably not the right error code.
return SCE_KERNEL_ERROR_MEMBLOCK_ALLOC_FAILED;
}
if (header->e_ident[0] != ELFMAG0 || header->e_ident[1] != ELFMAG1
|| header->e_ident[2] != ELFMAG2 || header->e_ident[3] != ELFMAG3)
return SCE_KERNEL_ERROR_UNSUPPORTED_PRX_TYPE;
// technically ELFCLASSNONE would freeze the system, but that's not really desireable
if (header->e_ident[EI_CLASS] != ELFCLASS32) {
if (header->e_ident[EI_CLASS] != 0) {
return SCE_KERNEL_ERROR_MEMBLOCK_ALLOC_FAILED;
}
ERROR_LOG(Log::Loader, "Bad ELF, EI_CLASS (fifth byte) is 0x00, should be 0x01 - would lock up a PSP.");
}
if (header->e_ident[EI_DATA] != ELFDATA2LSB)
return SCE_KERNEL_ERROR_MEMBLOCK_ALLOC_FAILED;
if (size_ < header->e_phoff + sizeof(Elf32_Phdr) * GetNumSegments() || size_ < header->e_shoff + sizeof(Elf32_Shdr) * GetNumSections()) {
ERROR_LOG(Log::Loader, "Truncated ELF, %d bytes with %d sections and %d segments", (int)size_, GetNumSections(), GetNumSegments());
// Probably not the right error code.
return SCE_KERNEL_ERROR_MEMBLOCK_ALLOC_FAILED;
}
// e_ident[EI_VERSION] is ignored
// Should we relocate?
bRelocate = (header->e_type != ET_EXEC);
// Look for the module info - we need to know whether this is kernel or user.
const PspModuleInfo *modInfo = 0;
for (int i = 0; i < GetNumSections(); i++) {
const Elf32_Shdr *s = &sections[i];
const char *name = GetSectionName(i);
if (name && !strcmp(name, ".rodata.sceModuleInfo") && s->sh_offset + sizeof(PspModuleInfo) <= size_) {
modInfo = (const PspModuleInfo *)GetPtr(s->sh_offset);
}
}
if (!modInfo && GetNumSegments() >= 1 && (segments[0].p_paddr & 0x7FFFFFFF) + sizeof(PspModuleInfo) <= size_) {
modInfo = (const PspModuleInfo *)GetPtr(segments[0].p_paddr & 0x7FFFFFFF);
}
bool kernelModule = modInfo ? (modInfo->moduleAttrs & 0x1000) != 0 : false;
std::string modName = "ELF";
if (modInfo) {
size_t n = strnlen(modInfo->name, 28);
modName = "ELF/" + std::string(modInfo->name, n);
}
entryPoint = header->e_entry;
u32 totalStart = 0xFFFFFFFF;
u32 totalEnd = 0;
for (int i = 0; i < header->e_phnum; i++) {
const Elf32_Phdr *p = &segments[i];
if (p->p_type == PT_LOAD) {
if (p->p_vaddr < totalStart) {
totalStart = p->p_vaddr;
firstSegAlign = p->p_align;
}
if (p->p_vaddr + p->p_memsz > totalEnd)
totalEnd = p->p_vaddr + p->p_memsz;
}
}
totalSize = totalEnd - totalStart;
// If a load address is specified that's in regular RAM, override kernel module status
bool inUser = totalStart >= PSP_GetUserMemoryBase();
BlockAllocator &memblock = (kernelModule && !inUser) ? kernelMemory : userMemory;
if (!bRelocate)
{
// Binary is prerelocated, load it where the first segment starts
vaddr = memblock.AllocAt(totalStart, totalSize, modName.c_str());
}
else if (loadAddress)
{
// Binary needs to be relocated: add loadAddress to the binary start address.
// The caller picked the address, so we can't move it - but say so if it doesn't meet
// what the module asked for, since that's how relocations end up off by 64KB.
if (firstSegAlign > 1 && ((loadAddress + totalStart) & (firstSegAlign - 1)) != 0) {
WARN_LOG_REPORT(Log::Loader, "Module %s loaded at %08x, which doesn't meet its segment alignment %08x",
modName.c_str(), loadAddress + totalStart, firstSegAlign);
}
vaddr = memblock.AllocAt(loadAddress + totalStart, totalSize, modName.c_str());
}
else
{
// Just put it where there is room, but honor the alignment the first loadable segment
// asks for. Most PRXs want no more than the allocator's default grain, so this usually
// changes nothing - but a module's relocations are only guaranteed to resolve correctly
// at a base meeting its declared alignment, and ignoring that produces addresses that
// are wrong by a multiple of 64KB rather than an outright failure.
//
// CrossCraft Classic (Zig) is the case in point: it declares p_align 0x10000 precisely
// because a stage of Zig's PSP pipeline emits mispaired HI16/LO16 relocations, and a
// 64KB-aligned base makes that harmless (no carry is ever needed, so which of a symbol's
// LO16 entries a HI16 got paired with stops mattering). Loaded at 0x08804000 instead, 46
// of its addresses came out 64KB low and it jumped through a bogus vtable almost at once.
u32 align = firstSegAlign;
if (align > 1 && (align & (align - 1)) == 0) {
if (align > 0x1000) {
INFO_LOG(Log::Loader, "Module %s requests an unusually large segment alignment (%08x)", modName.c_str(), align);
}
vaddr = memblock.AllocAligned(totalSize, 1, align, fromTop, modName.c_str());
} else {
vaddr = memblock.Alloc(totalSize, fromTop, modName.c_str());
}
}
if (vaddr == (u32)-1) {
ERROR_LOG(Log::Loader, "Failed to allocate memory for ELF!");
return SCE_KERNEL_ERROR_MEMBLOCK_ALLOC_FAILED;
}
if (bRelocate) {
DEBUG_LOG(Log::Loader,"Relocatable module");
if (entryPoint != (u32)-1)
entryPoint += vaddr;
} else {
DEBUG_LOG(Log::Loader,"Prerelocated executable");
}
DEBUG_LOG(Log::Loader,"%i segments:", header->e_phnum);
// First pass: Get the bits into RAM
u32 baseAddress = bRelocate ? vaddr : 0;
for (int i = 0; i < header->e_phnum; i++)
{
const Elf32_Phdr *p = segments + i;
DEBUG_LOG(Log::Loader, "Type: %08x Vaddr: %08x Filesz: %08x Memsz: %08x ", (int)p->p_type, (u32)p->p_vaddr, (int)p->p_filesz, (int)p->p_memsz);
if (p->p_type == PT_LOAD)
{
segmentVAddr[i] = baseAddress + p->p_vaddr;
const u32 writeAddr = segmentVAddr[i];
const u8 *src = GetSegmentPtr(i);
if (!src) {
ERROR_LOG(Log::Loader, "Segment %d pointer invalid?", i);
continue;
}
if (p->p_filesz > size_) {
ERROR_LOG(Log::Loader, "Segment %d size invalid", i);
continue;
}
if ((s64)p->p_filesz + (s64)p->p_offset > (s64)size_) {
ERROR_LOG(Log::Loader, "Segment %d size+offset invalid, reading outside the input", i);
continue;
}
if (p->p_filesz > p->p_memsz) {
ERROR_LOG(Log::Loader, "Segment %d filesz invalid - bigger than memsz", i);
continue;
}
const u32 srcSize = p->p_filesz;
const u32 dstSize = p->p_memsz; // can be bigger than size-in-file (p_filesz), we'll zero the rest below. But cannot be smaller!
u8 *dst = Memory::GetPointerWriteRangeOrException(writeAddr, dstSize);
if (dst) {
if (srcSize < dstSize) {
memset(dst + srcSize, 0, dstSize - srcSize); // zero out the rest of the segment, this also applies to bss (which is all-zero)
NotifyMemInfo(MemBlockFlags::WRITE, writeAddr + srcSize, dstSize - srcSize, "ELFZero");
}
memcpy(dst, src, srcSize);
std::string tag = StringFromFormat("ELFLoad/%08x", writeAddr);
NotifyMemInfo(MemBlockFlags::WRITE, writeAddr, srcSize, tag.c_str(), tag.size());
DEBUG_LOG(Log::Loader, "Loadable Segment Copied to %08x, size %08x", writeAddr, (u32)p->p_memsz);
} else {
ERROR_LOG(Log::Loader, "Bad ELF segment. Trying to write %d bytes to %08x", dstSize, writeAddr);
}
}
}
memblock.ListBlocks(LogLevel::LDEBUG);
DEBUG_LOG(Log::Loader, "%d sections:", header->e_shnum);
sectionOffsets = new u32[GetNumSections()];
sectionAddrs = new u32[GetNumSections()];
for (int i = 0; i < GetNumSections(); i++)
{
const Elf32_Shdr *s = &sections[i];
const char *name = GetSectionName(i);
u32 writeAddr = s->sh_addr + baseAddress;
sectionOffsets[i] = writeAddr - vaddr;
sectionAddrs[i] = writeAddr;
if (s->sh_flags & SHF_ALLOC)
{
std::string tag = name && name[0] ? StringFromFormat("%s/%s", modName.c_str(), name) : StringFromFormat("%s/%08x", modName.c_str(), writeAddr);
NotifyMemInfo(MemBlockFlags::SUB_ALLOC, writeAddr, s->sh_size, tag.c_str(), tag.size());
DEBUG_LOG(Log::Loader,"Data Section found: %s Sitting at %08x, size %08x", name, writeAddr, (u32)s->sh_size);
}
else
{
DEBUG_LOG(Log::Loader,"NonData Section found: %s Ignoring (size=%08x) (flags=%08x)", name, (u32)s->sh_size, (u32)s->sh_flags);
}
}
DEBUG_LOG(Log::Loader, "Relocations:");
// Second pass: Do necessary relocations
for (int i = 0; i < GetNumSections(); i++)
{
const Elf32_Shdr *s = &sections[i];
const char *name = GetSectionName(i);
if (s->sh_type == SHT_PSPREL)
{
//We have a relocation table!
int sectionToModify = s->sh_info;
if (sectionToModify >= 0)
{
if (!(sections[sectionToModify].sh_flags & SHF_ALLOC))
{
ERROR_LOG_REPORT(Log::Loader, "Trying to relocate non-loaded section %s", GetSectionName(sectionToModify));
continue;
}
int numRelocs = s->sh_size / sizeof(Elf32_Rel);
Elf32_Rel *rels = (Elf32_Rel *)GetSectionDataPtr(i);
if (GetSectionDataOffset(i) + sizeof(Elf32_Rel) * numRelocs > size_)
rels = nullptr;
DEBUG_LOG(Log::Loader,"%s: Performing %i relocations on %s : offset = %08x", name, numRelocs, GetSectionName(sectionToModify), sections[i].sh_offset);
if (!rels || !LoadRelocations(rels, numRelocs)) {
WARN_LOG(Log::Loader, "LoadInto: Relocs failed, trying anyway");
}
}
else
{
WARN_LOG_REPORT(Log::Loader, "sectionToModify = %i - ignoring PSP relocation sector %i", sectionToModify, i);
}
}
else if (s->sh_type == SHT_REL)
{
DEBUG_LOG(Log::Loader, "Traditional relocation section found.");
if (!bRelocate)
{
DEBUG_LOG(Log::Loader, "Binary is prerelocated. Skipping relocations.");
}
else
{
//We have a relocation table!
int sectionToModify = s->sh_info;
if (sectionToModify >= 0)
{
if (!(sections[sectionToModify].sh_flags & SHF_ALLOC))
{
// Generally stuff like debug info. We don't need it.
INFO_LOG(Log::Loader, "Skipping relocation of non-loaded section %s", GetSectionName(sectionToModify));
continue;
}
}
else
{
WARN_LOG_REPORT(Log::Loader, "sectionToModify = %i - ignoring relocation sector %i", sectionToModify, i);
}
ERROR_LOG_REPORT(Log::Loader, "Traditional relocations unsupported.");
}
}
}
// Segment relocations (a few games use them)
if (GetNumSections() == 0) {
for (int i = 0; i < header->e_phnum; i++)
{
const Elf32_Phdr *p = &segments[i];
if (p->p_type == PT_PSPREL1) {
INFO_LOG(Log::Loader,"Loading segment relocations");
int numRelocs = p->p_filesz / sizeof(Elf32_Rel);
Elf32_Rel *rels = (Elf32_Rel *)GetSegmentPtr(i);
if (p->p_offset + p->p_filesz > size_)
rels = nullptr;
if (!rels || !LoadRelocations(rels, numRelocs)) {
ERROR_LOG(Log::Loader, "LoadInto: Relocs failed, trying anyway (2)");
}
} else if (p->p_type == PT_PSPREL2) {
INFO_LOG(Log::Loader,"Loading segment relocations2");
LoadRelocations2(i);
}
}
}
return SCE_KERNEL_ERROR_OK;
}
SectionID ElfReader::GetSectionByName(const char *name, int firstSection) const
{
if (!name)
return -1;
for (int i = firstSection; i < header->e_shnum; i++) {
const char *secname = GetSectionName(i);
if (secname && strcmp(name, secname) == 0) {
return i;
}
}
return -1;
}
u32 ElfReader::GetTotalTextSize() const {
u32 total = 0;
for (int i = 0; i < GetNumSections(); ++i) {
if (!(sections[i].sh_flags & SHF_WRITE) && (sections[i].sh_flags & SHF_ALLOC) && !(sections[i].sh_flags & SHF_STRINGS)) {
total += sections[i].sh_size;
}
}
return total;
}
u32 ElfReader::GetTotalTextSizeFromSeg() const {
u32 total = 0;
for (int i = 0; i < GetNumSegments(); ++i) {
if ((segments[i].p_flags & PF_X) != 0) {
total += segments[i].p_filesz;
}
}
return total;
}
u32 ElfReader::GetTotalDataSize() const {
u32 total = 0;
for (int i = 0; i < GetNumSections(); ++i) {
if ((sections[i].sh_flags & SHF_WRITE) && (sections[i].sh_flags & SHF_ALLOC) && !(sections[i].sh_flags & SHF_MASKPROC)) {
total += sections[i].sh_size;
}
}
return total;
}
u32 ElfReader::GetTotalSectionSizeByPrefix(const std::string &prefix) const {
u32 total = 0;
for (int i = 0; i < GetNumSections(); ++i) {
const char *secname = GetSectionName(i);
if (secname && !strncmp(secname, prefix.c_str(), prefix.length())) {
total += sections[i].sh_size;
}
}
return total;
}
std::vector<SectionID> ElfReader::GetCodeSections() const {
std::vector<SectionID> ids;
for (int i = 0; i < GetNumSections(); ++i) {
u32 flags = sections[i].sh_flags;
if ((flags & (SHF_ALLOC | SHF_EXECINSTR)) == (SHF_ALLOC | SHF_EXECINSTR)) {
ids.push_back(i);
}
}
return ids;
}
bool ElfReader::LoadSymbols()
{
bool hasSymbols = false;
SectionID sec = GetSectionByName(".symtab");
if (sec != -1)
{
int stringSection = sections[sec].sh_link;
const char *stringBase = (const char*)GetSectionDataPtr(stringSection);
u32 stringOffset = GetSectionDataOffset(stringSection);
//We have a symbol table!
Elf32_Sym *symtab = (Elf32_Sym *)(GetSectionDataPtr(sec));
u32 symtabOffset = GetSectionDataOffset(sec);
int numSymbols = sections[sec].sh_size / sizeof(Elf32_Sym);
if (!stringBase || !symtab || symtabOffset + sections[sec].sh_size > size_) {
ERROR_LOG(Log::Loader, "Symbols truncated - ignoring");
return false;
}
for (int sym = 0; sym<numSymbols; sym++)
{
int size = symtab[sym].st_size;
if (size == 0)
continue;
int bind = symtab[sym].st_info >> 4;
int type = symtab[sym].st_info & 0xF;
int sectionIndex = symtab[sym].st_shndx;
int value = symtab[sym].st_value;
const char *name = stringBase + symtab[sym].st_name;
if (stringOffset + symtab[sym].st_name >= size_)
continue;
if (bRelocate)
value += sectionAddrs[sectionIndex];
switch (type)
{
case STT_OBJECT:
g_symbolMap->AddData(value,size,DATATYPE_BYTE);
break;
case STT_FUNC:
g_symbolMap->AddFunction(name,value,size);
break;
default:
continue;
}
hasSymbols = true;
//...
}
}
return hasSymbols;
}
// 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.
// 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 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 false;
if (header->e_ident[EI_CLASS] != ELFCLASS32)
return false;
*why = "no section headers";
if (!header->e_shoff || header->e_shentsize < sizeof(Elf32_Shdr))
return false;
if ((size_t)header->e_shoff + (size_t)header->e_shnum * header->e_shentsize > data.size())
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;
for (int i = 0; i < header->e_shnum; 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);
}
*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;
const Elf32_Shdr *symtab = section(symtabIndex);
if (symtab->sh_link >= header->e_shnum || symtab->sh_entsize < sizeof(Elf32_Sym))
return 0;
const Elf32_Shdr *strtab = section(symtab->sh_link);
if ((size_t)symtab->sh_offset + symtab->sh_size > data.size())
return 0;
if ((size_t)strtab->sh_offset + strtab->sh_size > data.size())
return 0;
const char *strings = data.data() + strtab->sh_offset;
const int numSymbols = symtab->sh_size / symtab->sh_entsize;
int added = 0;
for (int i = 0; i < numSymbols; i++) {
const Elf32_Sym *sym = (const Elf32_Sym *)(data.data() + symtab->sh_offset + (size_t)i * symtab->sh_entsize);
if (!sym->st_size || sym->st_name >= strtab->sh_size)
continue;
if (sym->st_value > moduleSize)
continue;
const char *name = strings + sym->st_name;
if (!name[0])
continue;
const u32 addr = moduleBase + sym->st_value;
switch (sym->st_info & 0xF) {
case STT_FUNC:
// updateName: these are the names a human wrote, so they beat the analyzer's
// z_un_<address> placeholders rather than losing to whichever got there first.
g_symbolMap->AddFunction(name, addr, sym->st_size, -1, true);
added++;
break;
case STT_OBJECT:
g_symbolMap->AddData(addr, sym->st_size, DATATYPE_BYTE);
g_symbolMap->AddLabel(name, addr, -1, true);
added++;
break;
default:
break;
}
}
*why = added ? "ok" : "symbol table had nothing usable";
return added;
}
int LoadCompanionElfDebugInfo(const Path &gameFile, u32 moduleBase, u32 moduleSize) {
if (gameFile.empty() || gameFile.Type() != PathType::NATIVE)
return 0;
const Path dir = gameFile.NavigateUp();
std::vector<File::FileInfo> files;
if (!File::GetFilesInDir(dir, &files, "elf:"))
return 0;
for (const File::FileInfo &file : files) {
if (file.isDirectory || file.size < sizeof(Elf32_Ehdr) || file.size > 256 * 1024 * 1024)
continue;
std::string data;
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)
g_symbolMap->SortSymbols();
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' matched but had nothing usable: %s", file.name.c_str(), why);
}
return 0;
}