#include #include #include #include #include #include #include #include #include #include #include "elf.h" #pragma pack(push, 1) struct RplLibsDef { be_val name; be_val stubStart; be_val stubEnd; }; #pragma pack(pop) static const uint32_t LoadAddress = 0x01000000u; static const uint32_t CodeAddress = 0x02000000u; static const uint32_t DataAddress = 0x10000000u; static const uint32_t WiiuLoadAddress = 0xC0000000u; int verbose = 0; constexpr int verbose_loaded_sections = 1; constexpr int verbose_loaded_symbols = 3; constexpr int verbose_loaded_imports = 2; constexpr int verbose_loaded_relocations = 4; constexpr int verbose_loaded_dyn_reloc = 4; constexpr int verbose_pruned_symbols = 2; constexpr int verbose_convert_relocations = 4; constexpr int verbose_dup_symbols = 3; constexpr int verbose_output_sections = 1; constexpr int verbose_output_symbols = 2; struct OutputSection; using OutputSectionPtr = std::shared_ptr; struct ElfFile { struct DataSection { std::string name; uint32_t address = 0; elf::SectionType type = elf::SHT_NULL; elf::SectionFlags flags = elf::SectionFlags(0); OutputSectionPtr outSection; int inIndex = -1; /* Data used if type == SHT_PROGBITS */ std::vector data; /* Size used if type == SHT_NOBITS */ uint32_t size = 0; DataSection(uint32_t _address, std::string _name, elf::SectionType _type, elf::SectionFlags _flags) : name(_name), address(_address), type(_type), flags(_flags) {} static std::shared_ptr New(uint32_t _address, std::string _name, elf::SectionType _type, elf::SectionFlags _flags) { return std::make_shared(_address, _name, _type, _flags); } bool contains(uint32_t _address, uint32_t _size = 0) { if (!(flags & elf::SHF_ALLOC) || size == 0 || size == uint32_t(-1)) return false; auto start = address; auto end = start + size; if (_address >= start && _address + _size <= end) return true; return false; } }; using DataSectionPtr = std::shared_ptr; using DataSectionList = std::vector; struct Symbol { std::string name; uint32_t address = 0; uint32_t size = 0; elf::SymbolType type = elf::STT_NOTYPE; elf::SymbolBinding binding = elf::STB_LOCAL; uint32_t outNamePos = 0; uint32_t refCount = 0; int inIndex = -1; int outIndex = -1; DataSectionPtr inSection; OutputSectionPtr outSection; Symbol(uint32_t _address, const std::string& _name, uint32_t _size, elf::SymbolType _type, elf::SymbolBinding _binding) : name(_name), address(_address), size(_size), type(_type), binding(_binding) {} static std::shared_ptr New(uint32_t _address, const std::string& _name, uint32_t _size, elf::SymbolType _type, elf::SymbolBinding _binding) { return std::make_shared(_address, _name, _size, _type, _binding); } }; using SymbolPtr = std::shared_ptr; using SymbolList = std::vector; using SymbolByAddrMap = std::map; struct Relocation { uint32_t target = 0; elf::RelocationType type = elf::R_PPC_NONE; SymbolPtr symbol; DataSectionPtr symbolSection; uint32_t addend = 0; OutputSectionPtr targetSection; }; using RelocationPtr = std::shared_ptr; using RelocationList = std::vector; struct RplImport { SymbolPtr trampSymbol; SymbolPtr stubSymbol; uint32_t stubAddr = 0; uint32_t trampAddr = 0; RplImport(uint32_t _stubAddr, uint32_t _trampAddr, SymbolPtr _stubSymbol, SymbolPtr _trampSymbol) : trampSymbol(_trampSymbol), stubSymbol(_stubSymbol), stubAddr(_stubAddr), trampAddr(_trampAddr) {} static std::shared_ptr New(uint32_t _stubAddr, uint32_t _trampAddr, SymbolPtr _stubSymbol, SymbolPtr _trampSymbol) { return std::make_shared(_stubAddr, _trampAddr, _stubSymbol, _trampSymbol); } }; using RplImportPtr = std::shared_ptr; using RplImportList = std::vector; using RplImportByAddrMap = std::map; struct RplImportLibrary { std::string name; RplImportList imports; RplImportLibrary(const std::string& _name) : name(_name) {} static std::shared_ptr New(const std::string& _name) { return std::make_shared(_name); } void addImport(const RplImportPtr& import) { imports.emplace_back(import); } }; using RplImportLibraryPtr = std::shared_ptr; using RplImportLibraryList = std::vector; uint32_t entryPoint = 0; private: DataSectionList dataSections; SymbolList symbols; RelocationList relocations; RplImportLibraryList rplImports; SymbolByAddrMap symbolsByAddr; RplImportByAddrMap importsByAddr; public: void addDataSection(const DataSectionPtr& section) { dataSections.emplace_back(section); } void addSymbolToIndex(const SymbolPtr& symbol) { auto iter = symbolsByAddr.find(symbol->address); if (iter != symbolsByAddr.end()) { if ((iter->second->type == elf::STT_NOTYPE && symbol->type != elf::STT_NOTYPE) || (iter->second->type != elf::STT_SECTION && symbol->type == elf::STT_SECTION && symbol->address != 0)) { if (verbose >= verbose_dup_symbols) { std::cout << "Duplicate symbol - changed type " << std::hex << "0x" << symbol->address << " " << iter->second->name << " / " << symbol->name << std::endl; } iter->second = symbol; } else if ((iter->second->type != elf::STT_SECTION || symbol->type == elf::STT_SECTION) && symbol->type != elf::STT_NOTYPE && iter->second->size == 0 && symbol->size != 0 && symbol->size != uint32_t(-1) && symbol->address != 0) { if (verbose >= verbose_dup_symbols) { std::cout << "Duplicate symbol - changed size " << std::hex << "0x" << symbol->address << " " << iter->second->size << " / " << symbol->size << " " << iter->second->type << " / " << symbol->type << " " << iter->second->name << " / " << symbol->name << std::endl; } iter->second = symbol; } else { if (symbol->name != iter->second->name && symbol->address != 0) { if (verbose >= verbose_dup_symbols) { std::cout << "Duplicate symbol " << std::hex << "0x" << symbol->address << " " << iter->second->size << " / " << symbol->size << " " << iter->second->type << " / " << symbol->type << " " << iter->second->name << " / " << symbol->name << std::endl; } } } } else { symbolsByAddr.emplace(symbol->address, symbol); } } void addSymbol(const SymbolPtr& symbol) { symbols.emplace_back(symbol); addSymbolToIndex(symbol); } SymbolList::iterator insertSymbol(SymbolList::iterator iter, const SymbolPtr& symbol) { addSymbolToIndex(symbol); return symbols.insert(iter, symbol); } void addImport(const RplImportLibraryPtr& lib, const RplImportPtr& import) { lib->addImport(import); auto result = importsByAddr.emplace(import->stubAddr, import); if (!result.second) { std::cout << "Duplicate import stub address " << import->stubSymbol->name << std::endl; } result = importsByAddr.emplace(import->trampAddr, import); if (!result.second) { std::cout << "Duplicate import tramp address " << import->trampSymbol->name << std::endl; } } void addImportLibrary(const RplImportLibraryPtr& lib) { rplImports.emplace_back(lib); } void addRelocation(const RelocationPtr& relocation) { relocations.emplace_back(relocation); } SymbolPtr findSymbol(uint32_t address) { auto iter = symbolsByAddr.find(address); if (iter != symbolsByAddr.end()) return iter->second; return nullptr; } RplImportPtr findImport(uint32_t address) { auto iter = importsByAddr.find(address); if (iter != importsByAddr.end()) return iter->second; return nullptr; } DataSectionPtr findSection(uint32_t address, const DataSectionPtr& hint = nullptr) { if (address == 0) return dataSections[0]; if (hint && hint->contains(address)) return hint; for (auto §ion : dataSections) { if (section->contains(address)) return section; } return nullptr; } DataSectionList& getDataSections() { return dataSections; } SymbolList& getSymbols() { return symbols; } RelocationList& getRelocations() { return relocations; } RplImportLibraryList& getImportLibs() { return rplImports; } void pruneSymbols() { SymbolList new_symbols; new_symbols.resize(symbols.size()); unsigned d = 0; /* Prune out unneeded symbols - but keep those used for relocations! */ for (unsigned s = 0u; s < symbols.size(); ++s) { if (!symbols[s]->name.empty() && symbols[s]->type == elf::STT_NOTYPE && symbols[s]->size == 0 && symbols[s]->refCount == 0) { if (verbose >= verbose_pruned_symbols) { std::cout << "Pruning symbol " << symbols[s]->name << std::endl; } } else { std::swap(new_symbols[d], symbols[s]); ++d; } } new_symbols.resize(d); std::swap(new_symbols, symbols); } void indexSymbols() { for (unsigned i = 0; i < symbols.size(); ++i) { symbols[i]->outIndex = i; } } }; struct InputSection { elf::SectionHeader header; std::vector data; }; template static Type * getLoaderDataPtr(std::vector &inSections, uint32_t address) { for (auto §ion : inSections) { auto start = section.header.addr; auto end = start + section.data.size(); if (start <= address && end > address) { auto offset = address - start; return reinterpret_cast(section.data.data() + offset); } } return nullptr; } static elf::Symbol * getSectionSymbol(InputSection §ion, size_t index) { auto symbols = reinterpret_cast(section.data.data()); return &symbols[index]; }; static bool read(ElfFile &file, const std::string &filename) { std::ifstream in { filename, std::ifstream::binary }; std::vector inSections; ElfFile::DataSectionList dataSectionIndex; if (!in.is_open()) { std::cout << "Could not open " << filename << " for reading" << std::endl; return false; } /* Read header */ elf::Header header; in.read(reinterpret_cast(&header), sizeof(elf::Header)); if (header.magic != elf::HeaderMagic) { std::cout << "Invalid ELF magic header" << std::endl; return false; } if (header.fileClass != elf::ELFCLASS32) { std::cout << "Unexpected ELF file class" << std::endl; return false; } if (header.encoding != elf::ELFDATA2MSB) { std::cout << "Unexpected ELF encoding" << std::endl; return false; } if (header.machine != elf::EM_PPC) { std::cout << "Unexpected ELF machine type" << std::endl; return false; } if (header.elfVersion != elf::EV_CURRENT) { std::cout << "Unexpected ELF version" << std::endl; return false; } file.entryPoint = header.entry; /* Read section headers and data */ in.seekg(static_cast(header.shoff)); inSections.resize(header.shnum); dataSectionIndex.resize(header.shnum); for (auto §ion : inSections) { in.read(reinterpret_cast(§ion.header), sizeof(elf::SectionHeader)); if (!section.header.size || section.header.type == elf::SHT_NOBITS) { continue; } auto pos = in.tellg(); in.seekg(static_cast(section.header.offset)); section.data.resize(section.header.size); in.read(section.data.data(), section.data.size()); in.seekg(pos); } auto shStrTab = inSections[header.shstrndx].data.data(); /* Process any loader relocations */ for (auto §ion : inSections) { if (section.header.type != elf::SHT_RELA) continue; auto name = std::string { shStrTab + section.header.name }; if (name.compare(".rela.dyn") != 0) continue; auto symSection = inSections[section.header.link]; auto relas = reinterpret_cast(section.data.data()); auto count = section.data.size() / sizeof(elf::Rela); for (unsigned i = 0u; i < count; ++i) { auto &rela = relas[i]; auto index = rela.info >> 8; auto symbol = getSectionSymbol(symSection, index); auto addr = symbol->value + rela.addend; auto type = rela.info & 0xff; auto ptr = getLoaderDataPtr(inSections, rela.offset); if (!ptr) { std::cout << "Unexpected relocation offset in .rela.dyn section" << std::endl; return false; } switch (type) { case elf::R_PPC_RELATIVE: if (verbose >= verbose_loaded_dyn_reloc) { std::cout << "Dyn relocation 0x" << std::hex << rela.offset << " 0x" << byte_swap(*ptr) << " -> 0x" << addr << std::endl; } *ptr = byte_swap(addr); break; case elf::R_PPC_NONE: /* ignore padding */ break; default: std::cout << "Unexpected relocation type in .rela.dyn section" << std::endl; return false; } } } int index = -1; /* Read text/data sections */ for (auto §ion : inSections) { ++index; if (section.header.addr >= LoadAddress && section.header.addr < CodeAddress) { /* Skip any load sections */ continue; } auto name = std::string { shStrTab + section.header.name }; auto address = section.header.addr.value(); auto type = elf::SectionType(section.header.type.value()); auto flags = elf::SectionFlags(section.header.flags.value()); auto size = section.header.size.value(); if (type == elf::SHT_PROGBITS) { auto data = ElfFile::DataSection::New(address, name, type, flags); data->data = section.data; data->size = size; data->inIndex = index; dataSectionIndex[index] = data; file.addDataSection(data); if (verbose >= verbose_loaded_sections) { std::cout << "Data section #" << index << ' ' << name << std::hex << ": 0x" << address << " - 0x" << address + size << std::endl; } } else if (type == elf::SHT_NOBITS) { auto bss = ElfFile::DataSection::New(address, name, type, flags); bss->size = size; bss->inIndex = index; dataSectionIndex[index] = bss; file.addDataSection(bss); if (verbose >= verbose_loaded_sections) { std::cout << "BSS section #" << index << ' ' << name << std::hex << ": 0x" << address << " - 0x" << address + size << std::endl; } } else { if (verbose >= verbose_loaded_sections) { std::cout << "Section #" << index << " type 0x" << type << ' ' << name << std::hex << ": 0x" << address << " - 0x" << address + size << std::endl; } } } /* Default symbols */ auto symNull = ElfFile::Symbol::New(0, "", 0, elf::STT_NOTYPE, elf::STB_LOCAL); file.addSymbol(symNull); auto symText = ElfFile::Symbol::New(CodeAddress, "$TEXT", 0, elf::STT_SECTION, elf::STB_LOCAL); file.addSymbol(symText); auto symData = ElfFile::Symbol::New(DataAddress, "$DATA", 0, elf::STT_SECTION, elf::STB_LOCAL); file.addSymbol(symData); auto symUndef = ElfFile::Symbol::New(0, "$UNDEF", 0, elf::STT_OBJECT, elf::STB_GLOBAL); file.addSymbol(symUndef); /* Read symbols */ for (auto §ion : inSections) { if (section.header.type != elf::SHT_SYMTAB) continue; auto name = std::string { shStrTab + section.header.name }; if (name.compare(".symtab") != 0) { std::cout << "Unexpected symbol section " << name << std::endl; return false; } auto strTab = inSections[section.header.link].data.data(); auto symTab = reinterpret_cast(section.data.data()); auto count = section.data.size() / sizeof(elf::Symbol); for (unsigned i = 0u; i < count; ++i) { auto &sym = symTab[i]; /* Skip any load symbols */ if (sym.value >= LoadAddress && sym.value < CodeAddress) continue; auto type = elf::SymbolType(sym.info & 0xF); auto binding = elf::SymbolBinding((sym.info >> 4) & 0xF); if (type == elf::STT_NOTYPE && sym.value == 0) { /* Skip null symbol */ continue; } if (type == elf::STT_FILE || type == elf::STT_SECTION) { /* Skip file, section symbols */ continue; } auto name = strTab + sym.name; auto symbol = ElfFile::Symbol::New(sym.value, name, sym.size, type, binding); auto inSection = dataSectionIndex[sym.shndx]; if (inSection && inSection->size != 0) symbol->inSection = dataSectionIndex[sym.shndx]; if (verbose >= verbose_loaded_symbols) { std::cout << "Symbol " << symbol->name << " 0x" << std::hex << symbol->address << " 0x" << symbol->size; if (symbol->inSection) { std::cout << " (" << symbol->inSection->name << ')'; } std::cout << '\n'; } file.addSymbol(symbol); } } /* Read RPL imports */ for (auto §ion : inSections) { auto name = std::string { shStrTab + section.header.name }; if (name.compare(".lib.rplLibs") != 0) continue; auto rplTab = reinterpret_cast(section.data.data()); auto count = section.data.size() / sizeof(RplLibsDef); for (unsigned i = 0u; i < count; ++i) { auto &rpl = rplTab[i]; auto libName = getLoaderDataPtr(inSections, rpl.name); auto lib = ElfFile::RplImportLibrary::New(libName); if (verbose >= verbose_loaded_sections) { std::cout << "Import library " << libName << std::endl; } for (auto stubAddr = rpl.stubStart; stubAddr < rpl.stubEnd; stubAddr += 4) { auto trampAddr = byte_swap(*getLoaderDataPtr(inSections, stubAddr)); /* Get the tramp symbol */ auto trampSymbol = file.findSymbol(trampAddr); if (!trampSymbol) { std::cout << "No symbol found for import @ 0x" << std::hex << trampAddr << " in " << libName << std::endl; } if (verbose >= verbose_loaded_imports) { std::cout << "Import symbol " << trampSymbol->name << " 0x" << std::hex << stubAddr << " -> 0x" << trampAddr << std::endl; } /* Create a new symbol to use for the import */ auto stubSymbol = ElfFile::Symbol::New(0, trampSymbol->name, 0, elf::STT_FUNC, elf::STB_GLOBAL); auto import = ElfFile::RplImport::New(stubAddr, trampAddr, stubSymbol, trampSymbol); /* Rename tramp symbol */ /* These are not needed anymore, right */ /* trampSymbol->refCount++; */ trampSymbol->name += "_tramp"; stubSymbol->refCount++; file.addSymbol(stubSymbol); file.addImport(lib, import); } file.addImportLibrary(lib); } } ElfFile::DataSectionPtr prevSection; /* Read relocations */ for (auto §ion : inSections) { if (section.header.type != elf::SHT_RELA) continue; auto name = std::string { shStrTab + section.header.name }; if (name.compare(".rela.dyn") == 0) { /* Skip dyn relocations */ continue; } auto symTab = reinterpret_cast(inSections[section.header.link].data.data()); auto relTab = reinterpret_cast(section.data.data()); auto count = section.data.size() / sizeof(elf::Rela); for (unsigned i = 0u; i < count; ++i) { auto relocation = std::make_shared(); auto &rela = relTab[i]; auto type = rela.info & 0xff; auto index = rela.info >> 8; auto &sym = symTab[index]; auto symType = sym.info & 0xf; if (symType == elf::STT_SECTION && sym.value == CodeAddress) { if (rela.offset < CodeAddress || rela.offset >= DataAddress) { std::cout << "Unexpected symbol referenced in relocation section " << name << std::endl; return false; } } auto addend = static_cast(rela.addend); if (verbose >= verbose_loaded_relocations) { std::cout << "Relocation #" << std::dec << file.getRelocations().size() << " sym index " << index << ' ' << " offset 0x" << std::hex << rela.offset << " addend 0x" << addend; } if (auto import = file.findImport(addend)) { relocation->symbol = import->stubSymbol; relocation->addend = 0; if (verbose >= verbose_loaded_relocations) { std::cout << " -> import "; } } else if (auto symbol = file.findSymbol(addend)) { relocation->symbol = symbol; relocation->addend = 0; if (verbose >= verbose_loaded_relocations) { std::cout << " -> symbol "; } #if 0 } else if (addend >= DataAddress && addend < WiiuLoadAddress) { relocation->symbol = symData; relocation->addend = addend - DataAddress; if (verbose >= verbose_loaded_relocations) { std::cout << " -> data symbol "; } } else if (addend >= CodeAddress && addend < DataAddress) { relocation->symbol = symText; relocation->addend = addend - CodeAddress; if (verbose >= verbose_loaded_relocations) { std::cout << " -> code symbol "; } #else } else if ((prevSection = file.findSection(addend)) != nullptr) { relocation->symbolSection = prevSection; relocation->addend = addend - prevSection->address; if (verbose >= verbose_loaded_relocations) { std::cout << " -> section "; } #endif } else { if (verbose >= verbose_loaded_relocations) { std::cout << " -> not found" << std::endl; } /* If we can't find a proper symbol, write the addend in and hope for the best */ auto ptr = getLoaderDataPtr(inSections, rela.offset); *ptr = addend; std::cout << "Unexpected addend " << std::hex << addend << " referenced in relocation section " << name << ", continuing." << std::endl; continue; } if (verbose >= verbose_loaded_relocations) { if (relocation->symbol) std::cout << relocation->symbol->name; else if (relocation->symbolSection) std::cout << relocation->symbolSection->name; if (relocation->addend != 0) std::cout << " + " << relocation->addend; std::cout << std::endl; } if (relocation->symbol) relocation->symbol->refCount++; relocation->target = rela.offset; relocation->type = static_cast(type); file.addRelocation(relocation); } } /* Read dyn relocations */ for (auto §ion : inSections) { if (section.header.type != elf::SHT_RELA) continue; auto name = std::string { shStrTab + section.header.name }; if (name.compare(".rela.dyn") != 0) continue; auto symSection = inSections[section.header.link]; auto relas = reinterpret_cast(section.data.data()); auto count = section.data.size() / sizeof(elf::Rela); for (unsigned i = 0u; i < count; ++i) { auto relocation = std::make_shared(); auto &rela = relas[i]; auto type = rela.info & 0xff; auto index = rela.info >> 8; auto symbol = getSectionSymbol(symSection, index); auto addr = symbol->value + rela.addend; if (type == elf::R_PPC_NONE) { /* ignore padding */ continue; } if (verbose >= verbose_loaded_dyn_reloc) { std::cout << "Dyn relocation #" << std::dec << file.getRelocations().size() << " offset 0x" << std::hex << rela.offset << " addend 0x" << rela.addend << " addr 0x" << addr; } // Why index must be == 0? Cause it's required for R_PPC_RELATIVE if (index == 0) { auto addend = static_cast(rela.addend); if (auto import = file.findImport(addend)) { relocation->symbol = import->stubSymbol; relocation->addend = 0; if (verbose >= verbose_loaded_dyn_reloc) { std::cout << " -> import "; } } else if (auto symbol = file.findSymbol(addend)) { relocation->symbol = symbol; relocation->addend = addend - symbol->address; if (verbose >= verbose_loaded_dyn_reloc) { std::cout << " -> symbol "; } #if 0 } else if (addr >= CodeAddress && addr < DataAddress) { index = 1; relocation->symbol = symText; relocation->addend = rela.addend - CodeAddress; if (verbose >= verbose_loaded_dyn_reloc) { std::cout << " -> code symbol "; } } else if (addr >= DataAddress && addr < WiiuLoadAddress) { index = 2; relocation->symbol = symData; relocation->addend = rela.addend - DataAddress; if (verbose >= verbose_loaded_dyn_reloc) { std::cout << " -> data symbol "; } #else } else if ((prevSection = file.findSection(addend)) != nullptr) { relocation->symbolSection = prevSection; relocation->addend = addend - prevSection->address; if (verbose >= verbose_loaded_relocations) { std::cout << " -> section "; } #endif } else { if (verbose >= verbose_loaded_dyn_reloc) { std::cout << " -> bad" << std::endl; } std::cout << "Unexpected relocation symbol address 0x" << std::hex << addr << " in .rela.dyn section" << std::endl; return false; } } else { if (verbose >= verbose_loaded_dyn_reloc) { std::cout << " -> index " << std::dec << index << std::endl; } std::cout << "Unexpected relocation symbol index " << std::dec << index << " in .rela.dyn section" << std::endl; return false; } if (verbose >= verbose_loaded_dyn_reloc) { if (relocation->symbol) std::cout << relocation->symbol->name; else if (relocation->symbolSection) std::cout << relocation->symbolSection->name; if (relocation->addend != 0) std::cout << " + " << relocation->addend; std::cout << std::endl; } switch (type) { case elf::R_PPC_RELATIVE: type = elf::R_PPC_ADDR32; break; default: std::cout << "Unexpected relocation type in .rela.dyn section" << std::endl; return false; } relocation->target = rela.offset; relocation->type = static_cast(type); /* Scrap any compiler/linker garbage */ if(relocation->target >= CodeAddress && relocation->target < WiiuLoadAddress) { if (relocation->symbol) relocation->symbol->refCount++; file.addRelocation(relocation); } } } return true; } struct OutputSection { std::string name; elf::SectionHeader header; std::vector data; std::shared_ptr relocationSection; std::shared_ptr sectionSymbol; uint32_t index = uint32_t(-1); bool contains(uint32_t address, uint32_t size = 0) { if (!(header.flags & elf::SHF_ALLOC) || header.size == 0 || header.size == uint32_t(-1)) return false; auto start = header.addr; auto end = start + header.size; if (address >= start && address + size <= end) return true; return false; } }; struct OutputSections { std::vector> sections; std::map> sections_by_name; void addSection(std::shared_ptr section) { section->index = sections.size(); sections.push_back(section); if (verbose >= verbose_output_sections) { auto& header = section->header; std::cout << "Output section #" << std::dec << section->index << std::hex << ' ' << section->header.type << ' ' << section->header.flags << ' ' << section->name << " (0x" << header.addr << " - 0x" << header.addr + header.size << ')' << std::endl; } if (!section->name.empty()) { if (sections_by_name.count(section->name)) { std::cout << "Duplicate output section name " << section->name << std::endl; } else { sections_by_name[section->name] = section; } } }; template SymbolIterator addSection(ElfFile &file, SymbolIterator symbolIterator, std::shared_ptr section) { ElfFile::SymbolPtr sectionSymbol; if (section->header.size != 0) { sectionSymbol = ElfFile::Symbol::New(section->header.addr, section->name, -1, elf::STT_SECTION, elf::STB_LOCAL); sectionSymbol->outSection = section; section->sectionSymbol = sectionSymbol; } section->index = sections.size(); sections.push_back(section); if (verbose >= verbose_output_sections) { auto& header = section->header; std::cout << "Output section #" << std::dec << section->index << std::hex << ' ' << section->header.type << ' ' << section->header.flags << ' ' << section->name << " (0x" << header.addr << " - 0x" << header.addr + header.size << ')' << std::endl; } if (sections_by_name.count(section->name)) { std::cout << "Duplicate output section name " << section->name << std::endl; } else { sections_by_name[section->name] = section; } if (!sectionSymbol) return symbolIterator; if (verbose >= verbose_loaded_symbols) { std::cout << "Section symbol " << sectionSymbol->name << " 0x" << std::hex << sectionSymbol->address << " 0x" << sectionSymbol->size << '\n'; } return file.insertSymbol(symbolIterator, sectionSymbol) + 1; }; uint32_t getSectionIndex(uint32_t address) { if (address == 0) return 0; for (unsigned i = 0u; i < sections.size(); ++i) { if (sections[i]->contains(address)) return i; } return -1; } uint32_t getSectionIndex(const std::string &name) { auto iter = sections_by_name.find(name); if (iter == sections_by_name.end()) return -1; return iter->second->index; } std::shared_ptr getSection(uint32_t address, const std::shared_ptr& hint = nullptr) { if (address == 0) return sections[0]; if (hint && hint->contains(address)) return hint; for (auto §ion : sections) { if (section->contains(address)) return section; } return nullptr; } std::shared_ptr getSection(const std::string &name) { auto iter = sections_by_name.find(name); if (iter == sections_by_name.end()) return nullptr; return iter->second; } const std::vector>& getSections() { return sections; } }; static bool write(ElfFile &file, const std::string &filename) { OutputSections outSections; auto sectionSymbolItr = file.getSymbols().begin() + 4; /* Create NULL section */ auto nullSection = std::make_shared(); memset(&nullSection->header, 0, sizeof(elf::SectionHeader)); outSections.addSection(nullSection); /* Create text/data sections */ for (auto §ion : file.getDataSections()) { auto out = std::make_shared(); out->header.name = -1; out->header.type = section->type; out->header.flags = section->flags; out->header.addr = section->address; out->header.offset = -1; if (section->type == elf::SHT_NOBITS) out->header.size = section->size; else out->header.size = section->data.size(); out->header.link = 0; out->header.info = 0; if (section->address == DataAddress) { out->header.addralign = 4096; out->header.flags |= elf::SHF_WRITE; /* .rodata needs to be writable? */ } else out->header.addralign = 256; out->header.entsize = 0; /* Add section */ out->name = section->name; out->data = section->data; section->outSection = out; sectionSymbolItr = outSections.addSection(file, sectionSymbolItr, out); } std::shared_ptr targetSection; /* Create relocation sections */ for (auto &relocation : file.getRelocations()) { targetSection = outSections.getSection(relocation->target, targetSection); if (!targetSection) { std::cout << "Error could not find section for relocation" << std::endl; return false; } relocation->targetSection = targetSection; if (!targetSection->relocationSection) { /* Create new relocation section */ auto out = std::make_shared(); out->header.name = -1; out->header.type = elf::SHT_RELA; out->header.flags = 0; out->header.addr = 0; out->header.offset = -1; out->header.size = -1; out->header.link = -1; out->header.info = targetSection->index; out->header.addralign = 4; out->header.entsize = sizeof(elf::Rela); /* Add section */ out->name = ".rela" + targetSection->name; sectionSymbolItr = outSections.addSection(file, sectionSymbolItr, out); targetSection->relocationSection = out; } } /* Calculate sizes of symbol/string tables so RPL imports are placed after them */ auto loadAddress = 0xC0000000; auto predictStrTabSize = 1; auto predictSymTabSize = 1; auto predictShstrTabSize = 1; for (auto &symbol : file.getSymbols()) { predictStrTabSize += symbol->name.size() + 1; predictSymTabSize += sizeof(elf::Symbol); } for (auto §ion : outSections.getSections()) predictShstrTabSize += section->name.size() + 1; predictStrTabSize = align_up(predictStrTabSize, 0x10); predictSymTabSize = align_up(predictSymTabSize, 0x10); predictShstrTabSize = align_up(predictShstrTabSize, 0x10); loadAddress += predictStrTabSize + predictSymTabSize + predictShstrTabSize; /* Create RPL import sections, .fimport_*, .dimport_* */ for (auto &lib : file.getImportLibs()) { auto out = std::make_shared(); out->header.name = -1; out->header.type = elf::SHT_RPL_IMPORTS; out->header.flags = elf::SHF_ALLOC | elf::SHF_EXECINSTR; out->header.addr = loadAddress; out->header.offset = -1; out->header.link = 0; out->header.info = 0; out->header.addralign = 4; out->header.entsize = 0; out->name = ".fimport_" + lib->name; /* Calculate size */ auto nameSize = align_up(8 + lib->name.size(), 8); auto stubSize = 8 + 8 * lib->imports.size(); out->header.size = std::max(nameSize, stubSize); out->data.resize(out->header.size); /* Setup data */ auto imports = reinterpret_cast(out->data.data()); imports->count = lib->imports.size(); imports->signature = crc32(0, Z_NULL, 0); memcpy(imports->name, lib->name.data(), lib->name.size()); imports->name[lib->name.size()] = 0; /* Update address of import symbols */ for (unsigned i = 0u; i < lib->imports.size(); ++i) lib->imports[i]->stubSymbol->address = loadAddress + 8 + i * 8; loadAddress = align_up(loadAddress + out->header.size, 4); /* Add section */ sectionSymbolItr = outSections.addSection(file, sectionSymbolItr, out); } /* Prune out unneeded symbols - but keep those used for relocations! */ file.pruneSymbols(); file.indexSymbols(); /* NOTICE: FROM NOW ON DO NOT MODIFY mSymbols */ auto symText = file.findSymbol(CodeAddress); auto symData = file.findSymbol(DataAddress); int number = -1; /* Convert relocations */ for (auto &relocation : file.getRelocations()) { ++number; auto targetSection = relocation->targetSection; if (!targetSection || !targetSection->relocationSection) { std::cout << "Error could not find section for relocation" << std::endl; return false; } /* Get address of relocation->target */ auto relocationSection = targetSection->relocationSection; /* Find symbol this relocation points to */ if (!relocation->symbol && relocation->symbolSection && relocation->symbolSection->outSection) relocation->symbol = relocation->symbolSection->outSection->sectionSymbol; auto symbol = relocation->symbol; auto idx = symbol->outIndex; bool symbol_found = idx != -1; if ((verbose >= verbose_convert_relocations) || !symbol_found) { std::cout << "Converting relocation #" << std::dec << number << std::hex << " @ 0x" << relocation->target; } if ((verbose >= verbose_convert_relocations) && symbol_found) { std::cout << "-> symbol " << std::dec << idx << ' ' << symbol->name << std::endl; } /* If the symbol doesn't exist but it is within DATA or TEXT, use those symbols + an addend */ if (!symbol_found) { if (relocation->symbol->address >= CodeAddress && relocation->symbol->address < DataAddress) { idx = 1; relocation->addend = relocation->symbol->address - CodeAddress; relocation->symbol = symText; std::cout << "-> no symbol, using TEXT" << std::endl; } else if (relocation->symbol->address >= DataAddress && relocation->symbol->address < WiiuLoadAddress) { idx = 2; relocation->addend = relocation->symbol->address - DataAddress; relocation->symbol = symData; std::cout << "-> no symbol, using DATA" << std::endl; } else { std::cout << "-> invalid" << std::endl; std::cout << "Could not find matching symbol for relocation" << std::endl; return false; } } /* Create relocation */ elf::Rela rela; rela.info = relocation->type | idx << 8; if(relocation->type == elf::R_PPC_RELATIVE) rela.info = elf::R_PPC_ADDR32 | idx << 8; rela.addend = relocation->addend; rela.offset = relocation->target; /* Append to relocation section data */ char *relaData = reinterpret_cast(&rela); relocationSection->data.insert(relocationSection->data.end(), relaData, relaData + sizeof(elf::Rela)); } /* String + Symbol sections */ auto symTabSection = std::make_shared(); auto strTabSection = std::make_shared(); auto shStrTabSection = std::make_shared(); symTabSection->name = ".symtab"; strTabSection->name = ".strtab"; shStrTabSection->name = ".shstrtab"; outSections.addSection(symTabSection); auto symTabIndex = symTabSection->index; outSections.addSection(strTabSection); auto strTabIndex = strTabSection->index; outSections.addSection(shStrTabSection); auto shStrTabIndex = shStrTabSection->index; /* Update relocation sections to link to symtab */ for (auto §ion : outSections.getSections()) { if (section->header.type == elf::SHT_RELA) section->header.link = symTabIndex; if (section->header.type != elf::SHT_NOBITS) section->header.size = section->data.size(); if (section->sectionSymbol) { section->sectionSymbol->address = section->header.addr; section->sectionSymbol->size = section->header.size; } } /* Create .strtab */ strTabSection->header.name = 0; strTabSection->header.type = elf::SHT_STRTAB; strTabSection->header.flags = elf::SHF_ALLOC; strTabSection->header.addr = 0; strTabSection->header.offset = -1; strTabSection->header.size = -1; strTabSection->header.link = 0; strTabSection->header.info = 0; strTabSection->header.addralign = 1; strTabSection->header.entsize = 0; /* Add all symbol names to data, update symbol->outNamePos */ strTabSection->data.push_back(0); for (auto &symbol : file.getSymbols()) { if (symbol->name.empty()) symbol->outNamePos = 0; else { symbol->outNamePos = static_cast(strTabSection->data.size()); std::copy(symbol->name.begin(), symbol->name.end(), std::back_inserter(strTabSection->data)); strTabSection->data.push_back(0); } } /* Create .symtab */ symTabSection->header.name = 0; symTabSection->header.type = elf::SHT_SYMTAB; symTabSection->header.flags = elf::SHF_ALLOC; symTabSection->header.addr = 0; symTabSection->header.offset = -1; symTabSection->header.size = -1; symTabSection->header.link = strTabIndex; symTabSection->header.info = 0; symTabSection->header.addralign = 4; symTabSection->header.entsize = sizeof(elf::Symbol); std::shared_ptr symSection; for (auto &symbol : file.getSymbols()) { elf::Symbol sym; if (symbol->outSection) symSection = symbol->outSection; else if (symbol->inSection && symbol->inSection->outSection) symSection = symbol->inSection->outSection; else if (symbol->type == elf::STT_SECTION && symbol->address == 0) symSection = outSections.getSection(symbol->name); else symSection = outSections.getSection(symbol->address, symSection); if (symSection == nullptr) { std::cout << "Could not find section for symbol " << symbol->name << " 0x" << std::hex << symbol->address << std::endl; return false; } sym.name = symbol->outNamePos; sym.value = symbol->address; sym.size = symbol->size; sym.info = symbol->type | (symbol->binding << 4); sym.other = 0; sym.shndx = symSection->index; if (verbose >= verbose_output_symbols) { std::cout << std::right << std::hex << std::setfill('0') << std::setw(8) << symbol->address << ' ' << std::setw(8) << symbol->size << std::dec << ' ' << std::setfill(' ') << std::setw(3) << sym.shndx << ' ' << std::setw(12) << std::left << symSection->name << ' ' << symbol->name << std::endl; } /* Compound symbol crc into section crc */ if (symSection->header.type == elf::SHT_RPL_IMPORTS && symbol->type != elf::STT_SECTION) { auto rplImport = reinterpret_cast(symSection->data.data()); rplImport->signature = crc32(rplImport->signature, reinterpret_cast(strTabSection->data.data() + sym.name),strlen(strTabSection->data.data() + sym.name)+1); } /* Append to symtab data */ char *symData = reinterpret_cast(&sym); symTabSection->data.insert(symTabSection->data.end(), symData, symData + sizeof(elf::Symbol)); } /* Finish SHT_RPL_IMPORTS signatures */ Bytef *zero_buffer = reinterpret_cast(calloc(0x10, 1)); for (auto §ion : outSections.getSections()) { if(section->header.type == elf::SHT_RPL_IMPORTS) { auto rplImport = reinterpret_cast(section->data.data()); rplImport->signature = crc32(rplImport->signature, zero_buffer, 0xE); } } free(zero_buffer); /* Create .shstrtab */ shStrTabSection->header.name = 0; shStrTabSection->header.type = elf::SHT_STRTAB; shStrTabSection->header.flags = elf::SHF_ALLOC; shStrTabSection->header.addr = 0; shStrTabSection->header.offset = -1; shStrTabSection->header.size = -1; shStrTabSection->header.link = 0; shStrTabSection->header.info = 0; shStrTabSection->header.addralign = 1; shStrTabSection->header.entsize = 0; /* Add all section header names to data, update section->header.name */ shStrTabSection->data.push_back(0); for (auto §ion : outSections.getSections()) { if (section->name.empty()) section->header.name = 0; else { section->header.name = shStrTabSection->data.size(); std::copy(section->name.begin(), section->name.end(), std::back_inserter(shStrTabSection->data)); shStrTabSection->data.push_back(0); } } loadAddress = 0xC0000000; /* Update symtab, strtab, shstrtab section addresses */ symTabSection->header.addr = loadAddress; symTabSection->header.size = symTabSection->data.size(); loadAddress = align_up(symTabSection->header.addr + predictSymTabSize, 16); strTabSection->header.addr = loadAddress; strTabSection->header.size = strTabSection->data.size(); loadAddress = align_up(strTabSection->header.addr + predictStrTabSize, 16); shStrTabSection->header.addr = loadAddress; shStrTabSection->header.size = shStrTabSection->data.size(); /* Create SHT_RPL_FILEINFO section */ auto fileInfoSection = std::make_shared(); fileInfoSection->header.name = 0; fileInfoSection->header.type = elf::SHT_RPL_FILEINFO; fileInfoSection->header.flags = 0; fileInfoSection->header.addr = 0; fileInfoSection->header.offset = -1; fileInfoSection->header.size = -1; fileInfoSection->header.link = 0; fileInfoSection->header.info = 0; fileInfoSection->header.addralign = 4; fileInfoSection->header.entsize = 0; elf::RplFileInfo fileInfo; fileInfo.version = 0xCAFE0402; fileInfo.textSize = 0; fileInfo.textAlign = 32; fileInfo.dataSize = 0; fileInfo.dataAlign = 4096; fileInfo.loadSize = 0; fileInfo.loadAlign = 4; fileInfo.tempSize = 0; fileInfo.trampAdjust = 0; fileInfo.trampAddition = 0; fileInfo.sdaBase = 0; fileInfo.sda2Base = 0; fileInfo.stackSize = 0x10000; fileInfo.heapSize = 0x8000; fileInfo.filename = 0; fileInfo.flags = elf::RPL_IS_RPX; fileInfo.minVersion = 0x5078; fileInfo.compressionLevel = -1; fileInfo.fileInfoPad = 0; fileInfo.cafeSdkVersion = 0x51BA; fileInfo.cafeSdkRevision = 0xCCD1; fileInfo.tlsAlignShift = 0; fileInfo.tlsModuleIndex = 0; fileInfo.runtimeFileInfoSize = 0; fileInfo.tagOffset = 0; /* Count file info textSize, dataSize, loadSize */ for (auto §ion : outSections.getSections()) { auto size = section->data.size(); if (section->header.type == elf::SHT_NOBITS) size = section->header.size; if (section->header.addr >= CodeAddress && section->header.addr < DataAddress) { auto val = section->header.addr.value() + section->header.size.value() - CodeAddress; if(val > fileInfo.textSize) fileInfo.textSize = val; } else if (section->header.addr >= DataAddress && section->header.addr < WiiuLoadAddress) { auto val = section->header.addr.value() + section->header.size.value() - DataAddress; if(val > fileInfo.dataSize) fileInfo.dataSize = val; } else if (section->header.addr >= WiiuLoadAddress) { auto val = section->header.addr.value() + section->header.size.value() - WiiuLoadAddress; if(val > fileInfo.loadSize) fileInfo.loadSize = val; } else if (section->header.addr == 0 && section->header.type != elf::SHT_RPL_CRCS && section->header.type != elf::SHT_RPL_FILEINFO) fileInfo.tempSize += (size + 128); } /* TODO: These were calculated based on observation, however some games differ. */ fileInfo.sdaBase = align_up(DataAddress + fileInfo.dataSize + fileInfo.heapSize, 64); fileInfo.sda2Base = align_up(DataAddress + fileInfo.heapSize, 64); char *fileInfoData = reinterpret_cast(&fileInfo); fileInfoSection->data.insert(fileInfoSection->data.end(), fileInfoData, fileInfoData + sizeof(elf::RplFileInfo)); /* Create SHT_RPL_CRCS section */ auto crcSection = std::make_shared(); crcSection->header.name = 0; crcSection->header.type = elf::SHT_RPL_CRCS; crcSection->header.flags = 0; crcSection->header.addr = 0; crcSection->header.offset = -1; crcSection->header.size = -1; crcSection->header.link = 0; crcSection->header.info = 0; crcSection->header.addralign = 4; crcSection->header.entsize = 4; outSections.addSection(crcSection); outSections.addSection(fileInfoSection); std::vector sectionCRCs; for (auto §ion : outSections.getSections()) { auto crc = 0u; if (!section->data.empty()) { crc = crc32(0, Z_NULL, 0); crc = crc32(crc, reinterpret_cast(section->data.data()), section->data.size()); } sectionCRCs.push_back(byte_swap(crc)); } char *crcData = reinterpret_cast(sectionCRCs.data()); crcSection->data.insert(crcSection->data.end(), crcData, crcData + sizeof(uint32_t) * sectionCRCs.size()); /* Update section sizes and offsets */ auto shoff = align_up(sizeof(elf::Header), 64); auto dataOffset = align_up(shoff + outSections.getSections().size() * sizeof(elf::SectionHeader), 64); /* Add CRC and FileInfo sections first */ for (auto §ion : outSections.getSections()) { if (section->header.type != elf::SHT_RPL_CRCS && section->header.type != elf::SHT_RPL_FILEINFO) continue; if (section->header.type != elf::SHT_NOBITS) section->header.size = section->data.size(); if (!section->data.empty()) { section->header.offset = dataOffset; dataOffset = align_up(section->header.offset + section->data.size(), 64); } else section->header.offset = 0; } /* Add data sections next */ for (auto §ion : outSections.getSections()) { if(section->header.offset != -1) continue; if (section->header.addr < DataAddress || section->header.addr >= WiiuLoadAddress) continue; if (section->header.type != elf::SHT_NOBITS) section->header.size = section->data.size(); section->header.offset = dataOffset; dataOffset = align_up(section->header.offset + section->data.size(), 64); } /* Add load sections next */ for (auto §ion : outSections.getSections()) { if(section->header.offset != -1) continue; if (section->header.addr < WiiuLoadAddress) continue; if (section->header.type != elf::SHT_NOBITS) section->header.size = section->data.size(); if (!section->data.empty()) { section->header.offset = dataOffset; dataOffset = align_up(section->header.offset + section->data.size(), 64); } else { section->header.offset = 0; } } /* Everything else */ for (auto §ion : outSections.getSections()) { if(section->header.offset != -1) continue; if (section->header.type != elf::SHT_NOBITS) section->header.size = section->data.size(); if (!section->data.empty()) { section->header.offset = dataOffset; dataOffset = align_up(section->header.offset + section->data.size(), 64); } else section->header.offset = 0; } /* Write to file */ std::ofstream out { filename, std::ofstream::binary }; std::vector padding; if (!out.is_open()) { std::cout << "Could not open " << filename << " for writing" << std::endl; return false; } elf::Header header; header.magic = elf::HeaderMagic; header.fileClass = 1; header.encoding = elf::ELFDATA2MSB; header.elfVersion = elf::EV_CURRENT; header.abi = elf::EABI_CAFE; memset(&header.pad, 0, 7); header.type = 0xFE01; header.machine = elf::EM_PPC; header.version = 1; header.entry = file.entryPoint; header.phoff = 0; header.phentsize = 0; header.phnum = 0; header.shoff = shoff; header.shnum = outSections.getSections().size(); header.shentsize = sizeof(elf::SectionHeader); header.flags = 0; header.ehsize = sizeof(elf::Header); header.shstrndx = shStrTabIndex; out.write(reinterpret_cast(&header), sizeof(elf::Header)); /* Write section headers */ out.seekp(header.shoff.value()); for (auto §ion : outSections.getSections()) out.write(reinterpret_cast(§ion->header), sizeof(elf::SectionHeader)); /* Write section data */ for (auto §ion : outSections.getSections()) { if (!section->data.empty()) { out.seekp(section->header.offset.value()); out.write(section->data.data(), section->data.size()); } } return true; } int main(int argc, char **argv) { int arg_start = 1; while (argc >= arg_start + 1 && argv[arg_start][0] == '-') { if (argv[arg_start] == std::string("--")) { ++arg_start; break; } else if (argv[arg_start] == std::string("-v")) { ++verbose; ++arg_start; } } if (argc < arg_start + 1) { std::cout << "Usage: " << argv[0] << " " << std::endl; return -1; } ElfFile elf; auto src = argv[arg_start]; auto dst = argv[arg_start + 1]; if (!read(elf, src)) return -1; if (!write(elf, dst)) return -1; return 0; }