/*************************************************************************** * Copyright (C) 2020 PCSX-Redux authors * * * * 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; either version 2 of the License, or * * (at your option) any later version. * * * * 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 for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * ***************************************************************************/ #include #include #include #include #include #include "elfio/elfio.hpp" #include "flags.h" #include "fmt/format.h" #include #include "support/djbhash.h" #include "support/file.h" #include "support/list.h" #include "support/tree.h" #include "support/slice.h" #include "support/windowswrapper.h" #define SHF_MIPS_GPREL 0x10000000 #define vprint(...) \ if (verbose) fmt::print(__VA_ARGS__) /* The constants from the psyq link files */ enum class PsyqOpcode : uint8_t { END = 0, BYTES = 2, SWITCH = 6, ZEROES = 8, RELOCATION = 10, EXPORTED_SYMBOL = 12, IMPORTED_SYMBOL = 14, SECTION = 16, LOCAL_SYMBOL = 18, FILENAME = 28, PROGRAMTYPE = 46, UNINITIALIZED = 48, INC_SLD_LINENUM = 50, INC_SLD_LINENUM_BY_BYTE = 52, INC_SLD_LINENUM_BY_WORD = 54, SET_SLD_LINENUM = 56, SET_SLD_LINENUM_FILE = 58, END_SLD = 60, FUNCTION = 74, FUNCTION_END = 76, BLOCK_START = 78, BLOCK_END = 80, SECTION_DEF = 82, SECTION_DEF2 = 84, FUNCTION_START2 = 86, }; enum class PsyqRelocType : uint8_t { REL32_BE = 8, GPREL16_BE = 12, REL32 = 16, GPREL16_LE = 30, REL26 = 74, HI16 = 82, LO16 = 84, REL26_BE = 92, HI16_BE = 96, LO16_BE = 98, GPREL16 = 100, }; enum class PsyqExprOpcode : uint8_t { VALUE = 0, SYMBOL = 2, SECTION_BASE = 4, SECTION_START = 12, SECTION_END = 22, ADD = 44, SUB = 46, DIV = 50, }; /* Constants to indicate which rewrite pass we're at */ enum class ElfRelocationPass { PASS1, PASS2, }; /* ELFIO isn't providing those */ enum class elf_mips_reloc_type : unsigned char { R_MIPS_NONE = 0, R_MIPS_16, R_MIPS_32, R_MIPS_REL32, R_MIPS_26, R_MIPS_HI16, R_MIPS_LO16, R_MIPS_GPREL16, R_MIPS_LITERAL, R_MIPS_GOT16, R_MIPS_PC16, R_MIPS_CALL16, R_MIPS_GPREL32, }; /* The main structure to hold a psyq LNK (.OBJ) file */ struct PsyqLnkFile { struct Section; struct Symbol; struct Relocation; struct Expression; /* The main parser entry point; will return nullptr on error */ static std::unique_ptr parse(PCSX::IO file, bool verbose, bool sorted); static std::string readPsyqString(PCSX::IO file) { return file->readString(file->byte()); } /* Our list of sections and symbols will be keyed by their id from the LNK file */ typedef PCSX::Intrusive::Tree SectionTree; typedef PCSX::Intrusive::List
SectionList; typedef PCSX::Intrusive::Tree SymbolTree; typedef PCSX::Intrusive::List SymbolList; struct Section : public SectionTree::Node, public SectionList::Node { uint16_t group; uint8_t alignment; std::string name; uint32_t zeroes = 0; uint32_t uninitializedOffset = 0; PCSX::Slice data; std::list relocations; uint32_t getFullSize() { return data.size() + zeroes + uninitializedOffset; } uint32_t pointer = 0; uint16_t getKey() { return getLow(); } ELFIO::section* section = nullptr; ELFIO::section* rel_sec = nullptr; void display(PsyqLnkFile* lnk); void displayRelocs(PsyqLnkFile* lnk); bool isBss() { return (name == ".bss") || (name == ".sbss"); } bool isText() { return (name == ".text"); } bool generateElfSection(PsyqLnkFile* psyq, ELFIO::elfio& writer); bool generateElfRelocations(ElfRelocationPass pass, const std::string& prefix, PsyqLnkFile* psyq, ELFIO::elfio& writer, ELFIO::Elf_Word symbolSectionIndex, ELFIO::string_section_accessor& stra, ELFIO::symbol_section_accessor& syma); }; struct Symbol : public SymbolTree::Node, public SymbolList::Node { enum class Type { LOCAL, EXPORTED, IMPORTED, UNINITIALIZED, } symbolType; uint16_t sectionIndex; uint32_t offset = 0; uint32_t size = 0; std::string name; ELFIO::Elf_Word elfSym; uint16_t getKey() { return getLow(); } uint32_t getOffset(PsyqLnkFile* psyq) const { if (symbolType == Type::UNINITIALIZED) { auto section = psyq->sections.find(sectionIndex); assert(section != psyq->sections.end()); return section->data.size() + section->zeroes + offset; } else { return offset; } } void display(PsyqLnkFile* lnk); bool generateElfSymbol(PsyqLnkFile* psyq, ELFIO::string_section_accessor& stra, ELFIO::symbol_section_accessor& syma); }; struct Relocation { PsyqRelocType type; uint32_t offset; int32_t addend; std::unique_ptr expression; void display(PsyqLnkFile* lnk, PsyqLnkFile::Section* sec); bool generateElf(ElfRelocationPass pass, const std::string& prefix, PsyqLnkFile* psyq, PsyqLnkFile::Section* section, ELFIO::elfio& writer, ELFIO::string_section_accessor& stra, ELFIO::symbol_section_accessor& syma, ELFIO::relocation_section_accessor& rela); }; struct Expression { PsyqExprOpcode type; std::unique_ptr left = nullptr; std::unique_ptr right = nullptr; uint32_t value; uint16_t symbolIndex; uint16_t sectionIndex; static std::unique_ptr parse(PCSX::IO file, bool verbose, int level = 0); void display(PsyqLnkFile* lnk, bool top = false); }; SectionTree sections; SectionList sectionsList; SectionList unseenSectionsList; SymbolTree symbols; SymbolList symbolsList; int localIndex = 0; /* There's some state we need to maintain during parsing */ Section* getCurrentSection() { auto section = sections.find(currentSection); if (section == sections.end()) return nullptr; return &*section; } uint16_t currentSection = 0xffff; bool gotProgramSeven = false; /* And there's some state we need to maintain during elf conversion */ std::map localElfSymbols; std::map functionSizes; std::string elfConversionError; void display(); bool writeElf(const std::string& prefix, const std::string& out, bool abiNone, bool bigEndian); template inline void setElfConversionError(std::string_view formatStr, Args&&... args) { elfConversionError = fmt::format(fmt::runtime(formatStr), args...); #ifdef _WIN32 if (IsDebuggerPresent()) __debugbreak(); #endif } ~PsyqLnkFile() { sectionsList.destroyAll(); symbolsList.destroyAll(); } }; /* The psyq LNK parser code */ std::unique_ptr PsyqLnkFile::parse(PCSX::IO file, bool verbose, bool sorted) { std::unique_ptr ret = std::make_unique(); vprint(":: Reading signature.\n"); std::string signature = file->readString(3); if (signature != "LNK") { fmt::print(stderr, "Wrong signature: {}\n", signature); return nullptr; } vprint(" --> Signature ok.\n"); vprint(":: Reading version: "); uint8_t version = file->byte(); vprint("{}\n", version); if (version != 2) { fmt::print(stderr, "Unknown version {}\n", version); return nullptr; } uint32_t curFunctionStart = 0; std::string curFunctionName = ""; vprint(":: Parsing file...\n"); while (!file->eof()) { uint8_t opcode = file->byte(); vprint(" :: Read opcode {} --> ", opcode); switch (opcode) { case (uint8_t)PsyqOpcode::END: { vprint("EOF\n"); while (!ret->unseenSectionsList.empty()) { ret->sectionsList.push_back(&*ret->unseenSectionsList.begin()); } if (sorted) { ret->sectionsList.clear(); ret->symbolsList.clear(); for (auto& section : ret->sections) { ret->sectionsList.push_back(§ion); } for (auto& symbol : ret->symbols) { ret->symbolsList.push_back(&symbol); } } // Determine bss symbol placement // This has to be done after parsing the whole psyq object, as bss may be out of order in the file. // Doing it here ensures that we process symbols in their id order, instead of by psyq object file // order, if the user requested ordering by id - otherwise, it'll indeed be order of appearance. for (auto& symbol : ret->symbolsList) { // Static bss symbols will be represented as a ZEROES opcode instead of UNINITIALIZED. // This will cause them to have a size of zero, so ignore size zero symbols here. // Their relocs will resolve to an offset of the local .bss instead, so this causes no issues. if (symbol.size > 0) { auto section = ret->sections.find(symbol.sectionIndex); if (section != ret->sections.end() && section->isBss()) { auto align = std::min((uint32_t)section->alignment, symbol.size) - 1; section->uninitializedOffset += align; section->uninitializedOffset &= ~align; symbol.offset = section->uninitializedOffset; section->uninitializedOffset += symbol.size; } } } return ret; } case (uint8_t)PsyqOpcode::BYTES: { uint16_t size = file->read(); vprint("Bytes ({:04x})\n", size); PCSX::Slice slice = file->read(size); std::string hex = slice.toHexString(); vprint("{}\n", hex.c_str()); auto section = ret->getCurrentSection(); if (!section) { fmt::print("Section {} not found\n", ret->currentSection); return nullptr; } if (!ret->sectionsList.isLinked(section)) { ret->sectionsList.push_back(section); } section->pointer = section->getFullSize(); if (section->zeroes) { void* ptr = calloc(section->zeroes, 1); PCSX::Slice zeroes; zeroes.acquire(ptr, section->zeroes); section->zeroes = 0; section->data.concatenate(zeroes); } section->data.concatenate(slice); break; } case (uint8_t)PsyqOpcode::SWITCH: { uint16_t sectionIndex = file->read(); vprint("Switch to section {}\n", sectionIndex); ret->currentSection = sectionIndex; break; } case (uint8_t)PsyqOpcode::ZEROES: { uint32_t size = file->read(); vprint("Zeroes ({:04x})\n", size); auto section = ret->getCurrentSection(); if (!section) { fmt::print("Section {} not found\n", ret->currentSection); return nullptr; } section->zeroes += size; break; } case (uint8_t)PsyqOpcode::RELOCATION: { uint8_t relocType = file->read(); vprint("Relocation {} ", relocType); switch (relocType) { case (uint8_t)PsyqRelocType::REL32: { vprint("(REL32), "); break; } case (uint8_t)PsyqRelocType::REL26: { vprint("(REL26), "); break; } case (uint8_t)PsyqRelocType::HI16: { vprint("(HI16), "); break; } case (uint8_t)PsyqRelocType::LO16: { vprint("(LO16), "); break; } case (uint8_t)PsyqRelocType::GPREL16: { vprint("(GPREL16), "); break; } case (uint8_t)PsyqRelocType::GPREL16_LE: { vprint("(GPREL16 LE), "); break; } case (uint8_t)PsyqRelocType::GPREL16_BE: { vprint("(GPREL16 BE), "); break; } case (uint8_t)PsyqRelocType::HI16_BE: { vprint("(HI16 BE), "); break; } case (uint8_t)PsyqRelocType::LO16_BE: { vprint("(LO16 BE), "); break; } case (uint8_t)PsyqRelocType::REL26_BE: { vprint("(REL26 BE), "); break; } case (uint8_t)PsyqRelocType::REL32_BE: { vprint("(REL32 BE), "); break; } default: { fmt::print("Unknown relocation type {}\n", relocType); return nullptr; } } uint16_t offset = file->read(); auto section = ret->getCurrentSection(); if (!section) { fmt::print("Section {} not found\n", ret->currentSection); return nullptr; } vprint("offset {:04x}+{:08x}, expression: \n", offset, section->pointer); std::unique_ptr expression = Expression::parse(file, verbose); if (!expression) return nullptr; // Addend will be populated later during expression evaluation section->relocations.emplace_back( Relocation{PsyqRelocType(relocType), offset + section->pointer, 0, std::move(expression)}); break; } case (uint8_t)PsyqOpcode::EXPORTED_SYMBOL: { uint16_t symbolIndex = file->read(); uint16_t sectionIndex = file->read(); uint32_t offset = file->read(); std::string name = readPsyqString(file); vprint("Export: id {}, section {}, offset {:08x}, name {}\n", symbolIndex, sectionIndex, offset, name); Symbol* symbol = new Symbol(); symbol->symbolType = Symbol::Type::EXPORTED; symbol->sectionIndex = sectionIndex; symbol->offset = offset; symbol->name = name; ret->symbols.insert(symbolIndex, symbol); ret->symbolsList.push_back(symbol); break; } case (uint8_t)PsyqOpcode::IMPORTED_SYMBOL: { uint16_t symbolIndex = file->read(); std::string name = readPsyqString(file); vprint("Import: id {}, name {}\n", symbolIndex, name); Symbol* symbol = new Symbol(); symbol->symbolType = Symbol::Type::IMPORTED; symbol->name = name; ret->symbols.insert(symbolIndex, symbol); ret->symbolsList.push_back(symbol); break; } case (uint8_t)PsyqOpcode::SECTION: { uint16_t sectionIndex = file->read(); uint16_t group = file->read(); uint8_t alignment = file->read(); std::string name = readPsyqString(file); vprint("Section: id {}, group {}, alignment {}, name {}\n", sectionIndex, group, alignment, name); Section* section = new Section(); section->group = group; section->alignment = alignment; section->name = name; ret->sections.insert(sectionIndex, section); ret->unseenSectionsList.push_back(section); if ((alignment - 1) & alignment) { fmt::print(stderr, "Section alignment {} isn't a power of two.\n", alignment); return nullptr; } break; } case (uint8_t)PsyqOpcode::LOCAL_SYMBOL: { uint16_t sectionIndex = file->read(); uint32_t offset = file->read(); std::string name = readPsyqString(file); vprint("Local: section {}, offset {}, name {}\n", sectionIndex, offset, name); Symbol* symbol = new Symbol(); symbol->symbolType = Symbol::Type::LOCAL; symbol->sectionIndex = sectionIndex; symbol->offset = offset; symbol->name = name; ret->symbols.insert(--ret->localIndex, symbol); ret->symbolsList.push_back(symbol); break; } case (uint8_t)PsyqOpcode::FILENAME: { uint16_t index = file->read(); std::string name = readPsyqString(file); vprint("File {}: {}\n", index, name); break; } case (uint8_t)PsyqOpcode::PROGRAMTYPE: { uint8_t type = file->read(); vprint("Program type: {}\n", type); if (type != 7 && type != 9) { fmt::print(stderr, "Unknown program type {}.\n", type); return nullptr; } if (ret->gotProgramSeven) { fmt::print(stderr, "Already got program type.\n"); return nullptr; } ret->gotProgramSeven = true; break; } case (uint8_t)PsyqOpcode::UNINITIALIZED: { uint16_t symbolIndex = file->read(); uint16_t sectionIndex = file->read(); uint32_t size = file->read(); std::string name = readPsyqString(file); Symbol* symbol = new Symbol(); symbol->symbolType = Symbol::Type::UNINITIALIZED; symbol->sectionIndex = sectionIndex; symbol->size = size; symbol->name = name; vprint("Uninitialized: id {}, section {}, size {}, name {}\n", symbolIndex, sectionIndex, size, name); auto section = ret->sections.find(sectionIndex); if (section == ret->sections.end()) { fmt::print(stderr, "Section {} not found for {}.\n", sectionIndex, name); return nullptr; } symbol->offset = 0; // Filled in later ret->symbols.insert(symbolIndex, symbol); ret->symbolsList.push_back(symbol); break; } case (uint8_t)PsyqOpcode::INC_SLD_LINENUM: { uint16_t offset = file->read(); vprint("INC_SLD_LINENUM: offset {}\n", offset); break; } case (uint8_t)PsyqOpcode::INC_SLD_LINENUM_BY_BYTE: { uint16_t offset = file->read(); uint8_t value = file->read(); vprint("INC_SLD_LINENUM_BY_BYTE: offset {}, value {}\n", offset, value); break; } case (uint8_t)PsyqOpcode::INC_SLD_LINENUM_BY_WORD: { uint16_t offset = file->read(); uint16_t value = file->read(); vprint("INC_SLD_LINENUM_BY_WORD: offset {}, value {}\n", offset, value); break; } case (uint8_t)PsyqOpcode::SET_SLD_LINENUM: { uint16_t offset = file->read(); uint32_t lineNum = file->read(); vprint("SET_SLD_LINENUM lineNum: {}, offset {}\n", lineNum, offset); break; } case (uint8_t)PsyqOpcode::SET_SLD_LINENUM_FILE: { uint16_t offset = file->read(); uint32_t lineNum = file->read(); uint16_t fileId = file->read(); vprint("SET_SLD_LINENUM_FILE: lineNum {}, offset {}, fileId {}\n", lineNum, offset, fileId); break; } case (uint8_t)PsyqOpcode::END_SLD: { // 2 bytes of nothing uint16_t zero = file->read(); assert(zero == 0); vprint("END_SLD\n"); break; } case (uint8_t)PsyqOpcode::FUNCTION: { uint16_t section = file->read(); uint32_t offset = file->read(); uint16_t fileId = file->read(); uint32_t startLine = file->read(); uint16_t frameReg = file->read(); uint32_t frameSize = file->read(); uint16_t retnPcReg = file->read(); uint32_t mask = file->read(); uint32_t maskOffset = file->read(); std::string name = readPsyqString(file); curFunctionStart = offset; vprint( "Function: section {}, offset {}, fileId {}, startLine {}, frameReg {}, frameSize {}, retnPcReg {}, " "mask {}, maskOffset {}, name {}\n", section, offset, fileId, startLine, frameReg, frameSize, retnPcReg, mask, maskOffset, name); curFunctionName = std::move(name); break; } case (uint8_t)PsyqOpcode::FUNCTION_END: { uint16_t section = file->read(); uint32_t offset = file->read(); uint32_t endLine = file->read(); ret->functionSizes[curFunctionName] = offset - curFunctionStart; vprint("Function end: section {}, offset {}, endLine {}\n", section, offset, endLine); break; } case (uint8_t)PsyqOpcode::BLOCK_START: { uint16_t section = file->read(); uint32_t offset = file->read(); uint32_t start = file->read(); vprint("Block start at line {} in section {} with offset {:X}\n", start, section, offset); break; } case (uint8_t)PsyqOpcode::BLOCK_END: { uint16_t section = file->read(); uint32_t offset = file->read(); uint32_t end = file->read(); vprint("Block end at line {} in section {} with offset {:X}\n", end, section, offset); break; } case (uint8_t)PsyqOpcode::SECTION_DEF: { uint16_t section = file->read(); uint32_t value = file->read(); uint16_t classId = file->read(); uint16_t type = file->read(); uint32_t size = file->read(); std::string name = readPsyqString(file); vprint("Section Definition: section {}, value {}, _class {}, type {}, size {}\n", section, value, classId, type, size); break; } case (uint8_t)PsyqOpcode::SECTION_DEF2: { uint16_t section = file->read(); uint32_t value = file->read(); uint16_t _class = file->read(); uint16_t type = file->read(); uint32_t size = file->read(); uint16_t dims = file->read(); while (dims-- > 0) { // ignore for now uint16_t dim = file->read(); } std::string tag = readPsyqString(file); std::string name = readPsyqString(file); vprint("SECTION_DEF2: section {}, value {}, _class {}, type {}, size {}, dims {}, tag {}, name {}\n", section, value, _class, type, size, dims, tag, name); break; } case (uint8_t)PsyqOpcode::FUNCTION_START2: { uint16_t section = file->read(); uint32_t offset = file->read(); uint16_t fileId = file->read(); uint32_t startLine = file->read(); uint16_t frameReg = file->read(); uint32_t frameSize = file->read(); uint16_t retnPcReg = file->read(); uint32_t mask = file->read(); uint32_t maskOffset = file->read(); uint32_t unk1 = file->read(); uint32_t unk2 = file->read(); std::string name = readPsyqString(file); curFunctionStart = offset; vprint( "Function (2): section {}, offset {}, fileId {}, startLine {}, frameReg {}, frameSize {}, retnPcReg {}, " "mask {}, maskOffset {}, name {}, unk1 {:08x}, unk2 {:08x}\n", section, offset, fileId, startLine, frameReg, frameSize, retnPcReg, mask, maskOffset, name, unk1, unk2); curFunctionName = std::move(name); break; } default: { fmt::print(stderr, "Unknown opcode {}.\n", opcode); return nullptr; } } } fmt::print(stderr, "Got actual end of file before EOF command.\n"); return nullptr; } std::unique_ptr PsyqLnkFile::Expression::parse(PCSX::IO file, bool verbose, int level) { std::unique_ptr ret = std::make_unique(); uint8_t exprOp = file->read(); ret->type = PsyqExprOpcode(exprOp); vprint(" "); for (int i = 0; i < level; i++) vprint(" "); switch (exprOp) { case (uint8_t)PsyqExprOpcode::VALUE: { uint32_t value = file->read(); vprint("Value: {:08x}\n", value); ret->value = value; break; } case (uint8_t)PsyqExprOpcode::SYMBOL: { uint16_t import = file->read(); vprint("Import: {}\n", import); ret->symbolIndex = import; break; } case (uint8_t)PsyqExprOpcode::SECTION_BASE: { uint16_t sectionIndex = file->read(); vprint("Base of section {}\n", sectionIndex); ret->sectionIndex = sectionIndex; break; } case (uint8_t)PsyqExprOpcode::SECTION_START: { uint16_t sectionIndex = file->read(); vprint("Start of section {}\n", sectionIndex); ret->sectionIndex = sectionIndex; break; } case (uint8_t)PsyqExprOpcode::SECTION_END: { uint16_t sectionIndex = file->read(); vprint("End of section {}\n", sectionIndex); ret->sectionIndex = sectionIndex; break; } case (uint8_t)PsyqExprOpcode::ADD: { vprint("Add:\n"); ret->right = parse(file, verbose, level + 1); ret->left = parse(file, verbose, level + 1); if ((ret->right->type == PsyqExprOpcode::ADD) && (ret->left->type == PsyqExprOpcode::VALUE)) { auto addend = ret->left->value; if (ret->right->right->type == PsyqExprOpcode::VALUE) { ret->right->right->value += addend; return std::move(ret->right); } else if (ret->right->left->type == PsyqExprOpcode::VALUE) { ret->right->left->value += addend; return std::move(ret->right); } } else if ((ret->left->type == PsyqExprOpcode::ADD) && (ret->right->type == PsyqExprOpcode::VALUE)) { auto addend = ret->right->value; if (ret->left->right->type == PsyqExprOpcode::VALUE) { ret->left->right->value += addend; return std::move(ret->left); } else if (ret->left->left->type == PsyqExprOpcode::VALUE) { ret->left->left->value += addend; return std::move(ret->left); } } if (!ret->left || !ret->right) { return nullptr; } break; } case (uint8_t)PsyqExprOpcode::SUB: { vprint("Sub:\n"); ret->right = parse(file, verbose, level + 1); ret->left = parse(file, verbose, level + 1); if (!ret->left || !ret->right) { return nullptr; } break; } case (uint8_t)PsyqExprOpcode::DIV: { vprint("Div:\n"); ret->right = parse(file, verbose, level + 1); ret->left = parse(file, verbose, level + 1); if (!ret->left || !ret->right) { return nullptr; } break; } default: { fmt::print(stderr, "Unknown expression type {}\n", exprOp); return nullptr; } } return ret; } /* The display functions */ void PsyqLnkFile::display() { fmt::print(" :: Symbols\n\n"); fmt::print(" {:^4} {:^6} {:^6} {:^12} {:^8} {:^8} {}\n", "indx", "type", "sectn", "", "offset", "size", "name"); fmt::print(" -----------------------------------------------------------------\n"); for (auto& symbol : symbolsList) { symbol.display(this); } fmt::print("\n\n\n :: Sections\n\n"); fmt::print(" {:4} {:4} {:8} {:8} {:8} {:8} {:8} {}\n", "indx", "grp", "alignmnt", "size", "data", "zeroes", "alloc", "name"); fmt::print(" -------------------------------------------------------------------------\n"); for (auto& section : sectionsList) { section.display(this); } fmt::print("\n\n\n :: Relocations\n\n"); fmt::print(" {:10} {:>10}::{:8} {}\n", "type", "section", "offset", "expression"); fmt::print(" ------------------------------------------\n"); for (auto& section : sectionsList) { section.displayRelocs(this); } } void PsyqLnkFile::Symbol::display(PsyqLnkFile* lnk) { if (symbolType == Type::EXPORTED) { auto section = lnk->sections.find(sectionIndex); if (section == lnk->sections.end()) { fmt::print("** BROKEN SYMBOL AT INDEX {:04x} **\n", getKey()); } else { fmt::print(" {:04x} {:6} ({:04x}) {:12} {:08x} {:8} {}\n", getKey(), "EXPORT", sectionIndex, section->name, getOffset(lnk), "", name); } } else if (symbolType == Type::IMPORTED) { fmt::print(" {:04x} {:6} {:6} {:12} {:8} {:8} {}\n", getKey(), "IMPORT", "", "", "", "", name); } else { auto section = lnk->sections.find(sectionIndex); if (section == lnk->sections.end()) { fmt::print("** BROKEN SYMBOL AT INDEX {:04x} **\n", getKey()); } else { fmt::print(" {:04x} {:6} ({:04x}) {:12} ({:08x}) {:08x} {}\n", getKey(), "UNDEFN", sectionIndex, section->name, getOffset(lnk), size, name); } } } void PsyqLnkFile::Section::display(PsyqLnkFile* lnk) { fmt::print(" {:04x} {:04x} {:8} {:08x} {:08x} {:08x} {:08x} {}\n", getKey(), group, alignment, getFullSize(), data.size(), zeroes, uninitializedOffset, name); } void PsyqLnkFile::Section::displayRelocs(PsyqLnkFile* lnk) { for (auto& reloc : relocations) { reloc.display(lnk, this); fmt::print("\n"); } } void PsyqLnkFile::Relocation::display(PsyqLnkFile* lnk, PsyqLnkFile::Section* sec) { static const std::map typeStr = { {PsyqRelocType::REL32, "REL32"}, {PsyqRelocType::REL26, "REL26"}, {PsyqRelocType::HI16, "HI16"}, {PsyqRelocType::LO16, "LO16"}, {PsyqRelocType::GPREL16, "GPREL16"}, {PsyqRelocType::GPREL16_LE, "GPREL16 LE"}, {PsyqRelocType::GPREL16_BE, "GPREL16 BE"}, {PsyqRelocType::REL32_BE, "REL32 BE"}, {PsyqRelocType::REL26_BE, "REL26 BE"}, {PsyqRelocType::HI16_BE, "HI16 BE"}, {PsyqRelocType::LO16_BE, "LO16 BE"}, }; fmt::print(" {:10} {:>10}::{:08x} ", typeStr.find(type)->second, sec->name, offset); expression->display(lnk, true); } void PsyqLnkFile::Expression::display(PsyqLnkFile* lnk, bool top) { switch (type) { case PsyqExprOpcode::VALUE: { fmt::print("{}", value); break; } case PsyqExprOpcode::SYMBOL: { auto symbol = lnk->symbols.find(symbolIndex); fmt::print("{}", symbol == lnk->symbols.end() ? "**ERR**" : symbol->name); break; } case PsyqExprOpcode::SECTION_BASE: { auto section = lnk->sections.find(sectionIndex); fmt::print("{}__base", section == lnk->sections.end() ? "**ERR**" : section->name); break; } case PsyqExprOpcode::SECTION_START: { auto section = lnk->sections.find(sectionIndex); fmt::print("{}__start", section == lnk->sections.end() ? "**ERR**" : section->name); break; } case PsyqExprOpcode::SECTION_END: { auto section = lnk->sections.find(sectionIndex); fmt::print("{}__end", section == lnk->sections.end() ? "**ERR**" : section->name); break; } case PsyqExprOpcode::ADD: { if (left->type == PsyqExprOpcode::VALUE) { if (left->value == 0) { right->display(lnk); } else { if (!top) fmt::print("("); right->display(lnk); fmt::print(" + "); left->display(lnk); if (!top) fmt::print(")"); } } else { if (!top) fmt::print("("); left->display(lnk); fmt::print(" + "); right->display(lnk); if (!top) fmt::print(")"); } break; } case PsyqExprOpcode::SUB: { if (!top) fmt::print("("); left->display(lnk); fmt::print(" - "); right->display(lnk); if (!top) fmt::print(")"); break; } case PsyqExprOpcode::DIV: { if (!top) fmt::print("("); left->display(lnk); fmt::print(" / "); right->display(lnk); if (!top) fmt::print(")"); break; } } } /* The ELF writer code */ bool PsyqLnkFile::writeElf(const std::string& prefix, const std::string& out, bool abiNone, bool bigEndian) { ELFIO::elfio writer; writer.create(ELFIO::ELFCLASS32, bigEndian ? ELFIO::ELFDATA2MSB : ELFIO::ELFDATA2LSB); writer.set_os_abi(abiNone ? ELFIO::ELFOSABI_NONE : ELFIO::ELFOSABI_LINUX); writer.set_type(ELFIO::ET_REL); writer.set_machine(ELFIO::EM_MIPS); // conflate bigEndian with PSX vs N64 if (bigEndian) { writer.set_flags(0x20001101); } else { writer.set_flags(0x1000); // ?! } fmt::print(" :: Generating sections\n"); for (auto& section : sectionsList) { bool success = section.generateElfSection(this, writer); if (!success) return false; } ELFIO::section* str_sec = writer.sections.add(".strtab"); str_sec->set_type(ELFIO::SHT_STRTAB); ELFIO::string_section_accessor stra(str_sec); ELFIO::section* sym_sec = writer.sections.add(".symtab"); sym_sec->set_type(ELFIO::SHT_SYMTAB); sym_sec->set_addr_align(0x4); sym_sec->set_entry_size(writer.get_default_entry_size(ELFIO::SHT_SYMTAB)); sym_sec->set_link(str_sec->get_index()); ELFIO::symbol_section_accessor syma(writer, sym_sec); syma.add_symbol(stra, out.c_str(), 0, ELFIO::STB_LOCAL, ELFIO::STT_FILE, 0, ELFIO::SHN_ABS); fmt::print(" :: Generating relocations - pass 1, local only\n"); for (auto& section : sectionsList) { bool success = section.generateElfRelocations(ElfRelocationPass::PASS1, prefix, this, writer, sym_sec->get_index(), stra, syma); if (!success) return false; } fmt::print(" :: Generating symbols\n"); // Generate local symbols first for (auto& symbol : symbolsList) { if (symbol.symbolType == Symbol::Type::LOCAL) { bool success = symbol.generateElfSymbol(this, stra, syma); if (!success) return false; } } sym_sec->set_info(syma.get_symbols_num()); // Generate all other symbols afterwards for (auto& symbol : symbolsList) { if (symbol.symbolType != Symbol::Type::LOCAL) { bool success = symbol.generateElfSymbol(this, stra, syma); if (!success) return false; } } fmt::print(" :: Generating relocations - pass 2, globals only\n"); for (auto& section : sectionsList) { bool success = section.generateElfRelocations(ElfRelocationPass::PASS2, prefix, this, writer, sym_sec->get_index(), stra, syma); if (!success) return false; } ELFIO::section* note = writer.sections.add(".note"); note->set_type(ELFIO::SHT_NOTE); ELFIO::note_section_accessor noteWriter(writer, note); noteWriter.add_note(0x01, "psyq-obj-parser", 0, 0); noteWriter.add_note(0x01, "pcsx-redux project", 0, 0); noteWriter.add_note(0x01, "https://github.com/grumpycoders/pcsx-redux", 0, 0); writer.save(out); return true; } bool PsyqLnkFile::Symbol::generateElfSymbol(PsyqLnkFile* psyq, ELFIO::string_section_accessor& stra, ELFIO::symbol_section_accessor& syma) { ELFIO::Elf_Half elfSectionIndex = 0; bool isText = false; bool isWeak = false; fmt::print(" :: Generating symbol {} {} {}\n", name, getOffset(psyq), sectionIndex); if (symbolType != Type::IMPORTED) { auto section = psyq->sections.find(sectionIndex); if (section == psyq->sections.end()) { psyq->setElfConversionError("Couldn't find section index {} for symbol {} ('{}')", sectionIndex, getKey(), name); return false; } elfSectionIndex = section->section->get_index(); isText = section->isText(); isWeak = symbolType != Type::EXPORTED; } uint32_t functionSize = 0; if (isText) { auto functionSizeIter = psyq->functionSizes.find(name); if (functionSizeIter != psyq->functionSizes.end()) { functionSize = functionSizeIter->second; } } elfSym = syma.add_symbol(stra, name.c_str(), getOffset(psyq), isText ? functionSize : size, isWeak ? ELFIO::STB_WEAK : symbolType == Type::LOCAL ? ELFIO::STB_LOCAL : ELFIO::STB_GLOBAL, isText ? ELFIO::STT_FUNC : ELFIO::STT_NOTYPE, 0, elfSectionIndex); return true; } bool PsyqLnkFile::Section::generateElfSection(PsyqLnkFile* psyq, ELFIO::elfio& writer) { if (getFullSize() == 0) return true; fmt::print(" :: Generating section {}\n", name); static const std::map flagsMap = { {".text", ELFIO::SHF_ALLOC | ELFIO::SHF_EXECINSTR}, {".rdata", ELFIO::SHF_ALLOC}, {".data", ELFIO::SHF_ALLOC | ELFIO::SHF_WRITE}, {".sdata", ELFIO::SHF_ALLOC | ELFIO::SHF_WRITE | SHF_MIPS_GPREL}, {".bss", ELFIO::SHF_ALLOC | ELFIO::SHF_WRITE}, {".sbss", ELFIO::SHF_ALLOC | ELFIO::SHF_WRITE | SHF_MIPS_GPREL}, }; auto flags = flagsMap.find(name); if (flags == flagsMap.end()) { psyq->setElfConversionError("Unknown section type '{}'", name); return false; } if (isBss() && data.size()) { psyq->setElfConversionError("Section {} looks like bss, but has data", name); return false; } section = writer.sections.add(name); section->set_type(isBss() ? ELFIO::SHT_NOBITS : ELFIO::SHT_PROGBITS); section->set_flags(flags->second); section->set_addr_align(alignment); if (isBss()) { section->set_size(getFullSize()); } else { section->set_data((const char*)data.data(), ELFIO::Elf_Word(data.size())); ELFIO::Elf_Word z = zeroes + uninitializedOffset; if (z) { void* ptr = calloc(z, 1); section->append_data((char*)ptr, z); free(ptr); } } return true; } static const std::map typeMap = { {PsyqRelocType::REL32, elf_mips_reloc_type::R_MIPS_32}, {PsyqRelocType::REL26, elf_mips_reloc_type::R_MIPS_26}, {PsyqRelocType::HI16, elf_mips_reloc_type::R_MIPS_HI16}, {PsyqRelocType::LO16, elf_mips_reloc_type::R_MIPS_LO16}, {PsyqRelocType::GPREL16, elf_mips_reloc_type::R_MIPS_GPREL16}, {PsyqRelocType::GPREL16_LE, elf_mips_reloc_type::R_MIPS_GPREL16}, {PsyqRelocType::GPREL16_BE, elf_mips_reloc_type::R_MIPS_GPREL16}, {PsyqRelocType::REL26_BE, elf_mips_reloc_type::R_MIPS_26}, {PsyqRelocType::HI16_BE, elf_mips_reloc_type::R_MIPS_HI16}, {PsyqRelocType::LO16_BE, elf_mips_reloc_type::R_MIPS_LO16}, {PsyqRelocType::REL32_BE, elf_mips_reloc_type::R_MIPS_32}, }; bool PsyqLnkFile::Section::generateElfRelocations(ElfRelocationPass pass, const std::string& prefix, PsyqLnkFile* psyq, ELFIO::elfio& writer, ELFIO::Elf_Word symbolSectionIndex, ELFIO::string_section_accessor& stra, ELFIO::symbol_section_accessor& syma) { if (relocations.size() == 0) return true; if (pass == ElfRelocationPass::PASS1) { rel_sec = writer.sections.add(fmt::format(".rel{}", name)); rel_sec->set_type(ELFIO::SHT_REL); rel_sec->set_info(section->get_index()); rel_sec->set_addr_align(0x4); rel_sec->set_entry_size(writer.get_default_entry_size(ELFIO::SHT_REL)); rel_sec->set_link(symbolSectionIndex); } ELFIO::relocation_section_accessor rela(writer, rel_sec); for (auto& relocation : relocations) { bool success = relocation.generateElf(pass, prefix, psyq, this, writer, stra, syma, rela); if (!success) return false; } // Pair any stray HI16 relocs with a previous matching one so that relocation addends are handled correctly if (pass == ElfRelocationPass::PASS2) { ELFIO::Elf_Xword num_relocs = rela.get_entries_num(); // Create a dummy type to represent a reloc, which is 8 bytes using reloc_t = std::array; // Get the pointer to the reloc table's data const reloc_t* reloc_table = (const reloc_t*)rel_sec->get_data(); // Allocate a buffer to hold a copy of the reloc table when modifying it std::vector reloc_table_copy(num_relocs); // Vector to hold the true addend of each reloc table entry std::vector reloc_addends(num_relocs); // Copy the reloc table into the working buffer std::copy_n(reloc_table, num_relocs, reloc_table_copy.begin()); // Lambda that finds a reloc from relocations given a type and offset, writing a reference to it to `out` // Returns the index of the reloc in relocations if a reloc was found, -1 if not auto find_reloc = [&](elf_mips_reloc_type type, uint32_t offset, int32_t& out) { int idx = 0; for (auto& cur_reloc : relocations) { elf_mips_reloc_type corresponding_reloc_type = typeMap.at(cur_reloc.type); if (cur_reloc.offset == offset && corresponding_reloc_type == (elf_mips_reloc_type)type) { out = cur_reloc.addend; return idx; } ++idx; } return -1; }; // Do an initial pass to get the full addend for each reloc for (ELFIO::Elf_Xword reloc_idx = 0; reloc_idx < num_relocs; ++reloc_idx) { ELFIO::Elf64_Addr offset; ELFIO::Elf_Word symbol; ELFIO::Elf_Word type; ELFIO::Elf_Sxword fake_addend; // Addend isn't encoded in the reloc, so this value is useless rela.get_entry(reloc_idx, offset, symbol, type, fake_addend); // We need to correlate Relocation objects to elf reloc table entries in order to get full addends, since // they're not in the same order int found_idx = find_reloc((elf_mips_reloc_type)type, (uint32_t)offset, reloc_addends[reloc_idx]); } // Check every entry to see if it's a stray HI16 for (ELFIO::Elf_Xword reloc_idx = 0; reloc_idx < num_relocs; ++reloc_idx) { ELFIO::Elf64_Addr offset; ELFIO::Elf_Word symbol; ELFIO::Elf_Word type; ELFIO::Elf_Sxword fake_addend; // Addend isn't encoded in the reloc, so this value is useless int32_t addend = reloc_addends[reloc_idx]; // This will instead contain the full 32 bit addend rela.get_entry(reloc_idx, offset, symbol, type, fake_addend); // Check if this is a HI16 if ((elf_mips_reloc_type)type == elf_mips_reloc_type::R_MIPS_HI16) { bool stray_reloc; if (reloc_idx + 1 >= num_relocs) { // Make sure that we don't overrun the reloc table stray_reloc = true; } else { ELFIO::Elf_Xword checked_idx = reloc_idx + 1; ELFIO::Elf64_Addr checked_offset; ELFIO::Elf_Word checked_symbol; ELFIO::Elf_Word checked_type; ELFIO::Elf_Sxword checked_fake_addend; int32_t checked_addend; // Loop through relocs until we hit one that isn't identical to the current one or until we hit the // end of the symbol table do { rela.get_entry(checked_idx, checked_offset, checked_symbol, checked_type, checked_fake_addend); checked_addend = reloc_addends[checked_idx]; ++checked_idx; } while (checked_idx + 1 < num_relocs && (elf_mips_reloc_type)checked_type == elf_mips_reloc_type::R_MIPS_HI16 && symbol == checked_symbol && addend == checked_addend); // Check if we ended up at a LO16 with the same symbol and full addend as the HI16 // If so, then this HI16 (and any that we passed over) is paired correctly if ((elf_mips_reloc_type)checked_type == elf_mips_reloc_type::R_MIPS_LO16 && symbol == checked_symbol && addend == checked_addend) { stray_reloc = false; // We can skip straight to the next reloc, as we've also verified all of the ones up the LO16 we // found reloc_idx = checked_idx + 1; } else { stray_reloc = true; } } if (stray_reloc) { // Find the matching LO16 ELFIO::Elf_Xword checked_idx; ELFIO::Elf64_Addr checked_offset; ELFIO::Elf_Word checked_symbol; ELFIO::Elf_Word checked_type; ELFIO::Elf_Sxword checked_fake_addend; int32_t checked_addend; ELFIO::Elf_Xword matching_idx = -1; // Loop through relocs until we find a LO16 with a matching offset, symbol, and addend for (checked_idx = 0; checked_idx < num_relocs; ++checked_idx) { rela.get_entry(checked_idx, checked_offset, checked_symbol, checked_type, checked_fake_addend); checked_addend = reloc_addends[checked_idx]; // Check if this is a LO16 that matches if (checked_symbol == symbol && checked_addend == addend && (elf_mips_reloc_type)checked_type == elf_mips_reloc_type::R_MIPS_LO16) { matching_idx = checked_idx; break; } } if (matching_idx != -1) { if (matching_idx < reloc_idx) { // Move the HI16 backwards so it's before the LO16 // This is effectively rotating the subset of the reloc table to the right // so that the HI16 ends up at the start of the rotated output std::rotate(std::make_reverse_iterator(reloc_table_copy.begin() + reloc_idx + 1), std::make_reverse_iterator(reloc_table_copy.begin() + reloc_idx), std::make_reverse_iterator(reloc_table_copy.begin() + matching_idx)); std::rotate(std::make_reverse_iterator(reloc_addends.begin() + reloc_idx + 1), std::make_reverse_iterator(reloc_addends.begin() + reloc_idx), std::make_reverse_iterator(reloc_addends.begin() + matching_idx)); } else { // Shift the HI16 forwards so it's before the LO16 // This is effectively rotating the subset of the reloc table to the left // so that the HI16 ends up at the start of the rotated output std::rotate(reloc_table_copy.begin() + reloc_idx, reloc_table_copy.begin() + reloc_idx + 1, reloc_table_copy.begin() + matching_idx); std::rotate(reloc_addends.begin() + reloc_idx, reloc_addends.begin() + reloc_idx + 1, reloc_addends.begin() + matching_idx); } // Update the reloc table so that get_entry is valid for the next iteration rel_sec->set_data((char*)reloc_table_copy.data(), num_relocs * sizeof(reloc_t)); } } } } } return true; } bool PsyqLnkFile::Relocation::generateElf(ElfRelocationPass pass, const std::string& prefix, PsyqLnkFile* psyq, PsyqLnkFile::Section* section, ELFIO::elfio& writer, ELFIO::string_section_accessor& stra, ELFIO::symbol_section_accessor& syma, ELFIO::relocation_section_accessor& rela) { fmt::print(" :: Generating relocation "); display(psyq, section); fmt::print("\n"); struct SkippedDisplay { ~SkippedDisplay() { if (skipped) fmt::print(" :: Skipped for this pass\n"); } bool skipped = false; } skipped; auto simpleSymbolReloc = [&, this](Expression* expr, ELFIO::Elf_Word elfSym = 0, int32_t symbolOffset = 0) { if (expr) { auto symbol = psyq->symbols.find(expr->symbolIndex); if (symbol == psyq->symbols.end()) { psyq->setElfConversionError("Couldn't find symbol {} for relocation.", expr->symbolIndex); return false; } elfSym = symbol->elfSym; } if (type == PsyqRelocType::HI16_BE || type == PsyqRelocType::LO16_BE || type == PsyqRelocType::GPREL16_BE) { offset -= 0x2; } auto elfType = typeMap.find(type); // TODO get_entries_num to get the new entry index and insert the symbolOffset into a vector for referencing // later to be used for reloc pairing rela.add_entry(offset, elfSym, (unsigned char)elfType->second); ELFIO::Elf_Xword size = section->section->get_size(); uint8_t* sectionData = (uint8_t*)malloc(size); memcpy(sectionData, section->section->get_data(), size); switch (type) { case PsyqRelocType::REL32: { sectionData[offset + 0] = (uint8_t)(symbolOffset >> 0x00); sectionData[offset + 1] = (uint8_t)(symbolOffset >> 0x08); sectionData[offset + 2] = (uint8_t)(symbolOffset >> 0x10); sectionData[offset + 3] = (uint8_t)(symbolOffset >> 0x18); break; } case PsyqRelocType::REL26: { sectionData[offset + 0] = (uint8_t)(symbolOffset >> 0x02); sectionData[offset + 1] = (uint8_t)(symbolOffset >> 0x0A); sectionData[offset + 2] = (uint8_t)(symbolOffset >> 0x12); sectionData[offset + 3] &= 0xfc; sectionData[offset + 3] |= (uint8_t)(symbolOffset >> 0x1A); break; } case PsyqRelocType::HI16: { // Calculate the high reloc, accounting for the signedness of the corresponding low reloc uint16_t hi = symbolOffset >> 16; if (symbolOffset & 0x8000) { hi += 1; } sectionData[offset + 0] = (uint8_t)(hi >> 0); sectionData[offset + 1] = (uint8_t)(hi >> 8); break; } case PsyqRelocType::LO16: { // Calculate the low reloc uint16_t lo = symbolOffset & 0xFFFF; sectionData[offset + 0] = (uint8_t)(lo >> 0); sectionData[offset + 1] = (uint8_t)(lo >> 8); break; } case PsyqRelocType::GPREL16: { sectionData[offset + 0] = 0; sectionData[offset + 1] = 0; break; } case PsyqRelocType::GPREL16_LE: { uint16_t lo = symbolOffset & 0xFFFF; sectionData[offset + 0] = (uint8_t)(lo >> 0); sectionData[offset + 1] = (uint8_t)(lo >> 8); break; } case PsyqRelocType::GPREL16_BE: { uint16_t lo = symbolOffset & 0xFFFF; sectionData[offset + 3] = (uint8_t)(lo >> 0); sectionData[offset + 2] = (uint8_t)(lo >> 8); break; } case PsyqRelocType::REL32_BE: { sectionData[offset + 3] = (uint8_t)(symbolOffset >> 0x00); sectionData[offset + 2] = (uint8_t)(symbolOffset >> 0x08); sectionData[offset + 1] = (uint8_t)(symbolOffset >> 0x10); sectionData[offset + 0] = (uint8_t)(symbolOffset >> 0x18); break; } case PsyqRelocType::REL26_BE: { sectionData[offset + 3] = (uint8_t)(symbolOffset >> 0x02); sectionData[offset + 2] = (uint8_t)(symbolOffset >> 0x0A); sectionData[offset + 1] = (uint8_t)(symbolOffset >> 0x12); sectionData[offset + 0] &= 0xfc; sectionData[offset + 0] |= (uint8_t)(symbolOffset >> 0x1A); break; } case PsyqRelocType::HI16_BE: { // Calculate the high reloc, accounting for the signedness of the corresponding low reloc uint16_t hi = symbolOffset >> 16; if (symbolOffset & 0x8000) { hi += 1; } sectionData[offset + 3] = (uint8_t)(hi >> 0); sectionData[offset + 2] = (uint8_t)(hi >> 8); break; } case PsyqRelocType::LO16_BE: { // Calculate the low reloc uint16_t lo = symbolOffset & 0xFFFF; sectionData[offset + 3] = (uint8_t)(lo >> 0); sectionData[offset + 2] = (uint8_t)(lo >> 8); break; } default: psyq->setElfConversionError("Unsupported relocation type {}.", magic_enum::enum_integer(type)); return false; } section->section->set_data((char*)sectionData, size); free(sectionData); return true; }; auto localSymbolReloc = [&, this](uint16_t sectionIndex, int32_t symbolOffset) { if (pass == ElfRelocationPass::PASS2) { skipped.skipped = true; return true; } auto section = psyq->sections.find(sectionIndex); if (section == psyq->sections.end()) { psyq->setElfConversionError("Section {} not found in relocation", sectionIndex); return false; } bool useLocalSymOffsets = true; std::string symbolName = useLocalSymOffsets ? section->name : fmt::format("${}.rel{}@{:08x}", prefix, section->name, symbolOffset); auto existing = psyq->localElfSymbols.find(symbolName); ELFIO::Elf_Word elfSym; if (existing == psyq->localElfSymbols.end()) { elfSym = syma.add_symbol(stra, symbolName.c_str(), useLocalSymOffsets ? 0 : symbolOffset, 0, ELFIO::STB_LOCAL, ELFIO::STT_SECTION, 0, section->section->get_index()); psyq->localElfSymbols.insert(std::make_pair(symbolName, elfSym)); } else { elfSym = existing->second; } return simpleSymbolReloc(nullptr, elfSym, symbolOffset); }; auto checkZero = [&, this](Expression* expr) { this->addend = 0; switch (expr->type) { case PsyqExprOpcode::SECTION_BASE: { return localSymbolReloc(expr->sectionIndex, 0); } case PsyqExprOpcode::SECTION_START: case PsyqExprOpcode::SYMBOL: { if (pass == ElfRelocationPass::PASS1) { skipped.skipped = true; return true; } return simpleSymbolReloc(expr); } default: { psyq->setElfConversionError("Unsupported relocation expression type: {}", magic_enum::enum_integer(expr->type)); return false; } } }; auto check = [&, this](Expression* expr, int32_t addend) { this->addend = addend; switch (expr->type) { case PsyqExprOpcode::SECTION_BASE: { return localSymbolReloc(expr->sectionIndex, addend); } case PsyqExprOpcode::SYMBOL: { auto symbol = psyq->symbols.find(expr->symbolIndex); if (symbol == psyq->symbols.end()) { psyq->setElfConversionError("Couldn't find symbol {} for relocation.", expr->symbolIndex); return false; } if (symbol->symbolType != PsyqLnkFile::Symbol::Type::IMPORTED) { return localSymbolReloc(symbol->sectionIndex, symbol->getOffset(psyq) + addend); } if (pass == ElfRelocationPass::PASS1) { skipped.skipped = true; return true; } ELFIO::Elf_Word elfSym = symbol->elfSym; // this is the most complex case, as the psyq format can do // relocations that have addend from a symbol, but ELF can't, // which means we need to alter the code's byte stream to // compute the proper addend. switch (type) { case PsyqRelocType::HI16: { bool success = simpleSymbolReloc(nullptr, elfSym); if (!success) return false; ELFIO::Elf_Xword size = section->section->get_size(); uint8_t* sectionData = (uint8_t*)malloc(size); memcpy(sectionData, section->section->get_data(), size); fmt::print(" :: Altering bytestream to account for HI symbol+addend relocation\n"); if (addend < 0) { fmt::print(" :: Adjusting for negative addend\n"); addend += 0x10000; } addend >>= 16; sectionData[offset + 0] = addend & 0xff; addend >>= 8; sectionData[offset + 1] = addend & 0xff; addend >>= 8; section->section->set_data((char*)sectionData, size); free(sectionData); return true; } case PsyqRelocType::LO16: { bool success = simpleSymbolReloc(nullptr, elfSym); if (!success) return false; ELFIO::Elf_Xword size = section->section->get_size(); uint8_t* sectionData = (uint8_t*)malloc(size); memcpy(sectionData, section->section->get_data(), size); fmt::print(" :: Altering bytestream to account for LO symbol+addend relocation\n"); sectionData[offset + 0] = addend & 0xff; addend >>= 8; sectionData[offset + 1] = addend & 0xff; addend >>= 8; section->section->set_data((char*)sectionData, size); free(sectionData); return true; } // Handled already case PsyqRelocType::REL32_BE: case PsyqRelocType::HI16_BE: case PsyqRelocType::LO16_BE: { bool success = simpleSymbolReloc(nullptr, elfSym, addend); return true; } default: { psyq->setElfConversionError("Unsupported relocation from a symbol with an addend"); return false; } } } default: { psyq->setElfConversionError("Unsupported relocation expression type: {}", magic_enum::enum_integer(expr->type)); return false; } } }; switch (expression->type) { case PsyqExprOpcode::ADD: { if (expression->right->type == PsyqExprOpcode::VALUE) { if ((expression->left->type == PsyqExprOpcode::SYMBOL) && (expression->right->value == 0)) { return checkZero(expression->left.get()); } else { return check(expression->left.get(), expression->right->value); } } else if (expression->left->type == PsyqExprOpcode::VALUE) { if ((expression->right->type == PsyqExprOpcode::SYMBOL) && (expression->left->value == 0)) { return checkZero(expression->right.get()); } else { return check(expression->right.get(), expression->left->value); } } else { psyq->setElfConversionError("Unsupported ADD operation in relocation"); return false; } break; } case PsyqExprOpcode::SUB: { if (expression->right->type == PsyqExprOpcode::VALUE) { // Why if (expression->left->type == PsyqExprOpcode::ADD) { if (expression->left->left->type == PsyqExprOpcode::VALUE) { return check(expression->left->right.get(), expression->left->left->value - expression->right->value); } } else { return check(expression->left.get(), -((int32_t)expression->right->value)); } } else if (expression->right->type == PsyqExprOpcode::SECTION_START) { // Why if (expression->left->type == PsyqExprOpcode::ADD) { if (expression->left->left->type == PsyqExprOpcode::VALUE) { return check(expression->left->right.get(), expression->left->left->value - expression->right->value); } } else { return checkZero(expression->left.get()); } } else { psyq->setElfConversionError("Unsupported SUB operation in relocation"); return false; } break; } case PsyqExprOpcode::DIV: { psyq->setElfConversionError("Unsupported DIV operation in relocation"); return false; } default: { return checkZero(expression.get()); } } psyq->setElfConversionError("Shouldn't happen"); return false; } int main(int argc, char** argv) { CommandLine::args args(argc, argv); auto output = args.get("o"); auto inputs = args.positional(); const bool asksForHelp = args.get("h") || args.get("help"); const bool noInput = inputs.size() == 0; const bool hasOutput = output.has_value(); const bool oneInput = inputs.size() == 1; if (asksForHelp || noInput || (hasOutput && !oneInput)) { fmt::print(R"( Usage: {} input.obj [input2.obj...] [-h] [-v] [-d] [-n] [-p prefix] [-o output.o] input.obj mandatory: specifies the input psyq LNK object file. -h displays this help information and exit. -v turns on verbose mode for the parser. -d displays the parsed input file. -n uses "none" ABI instead of Linux. -p prefix uses this prefix for local symbols. -s sorts the sections and symbols by id, instead of order of appearance. -o output.o tries to dump the parsed psyq LNK file into an ELF file; can only work with a single input file. -b outputs a big-endian ELF file. )", argv[0]); return -1; } bool verbose = !!args.get("v"); int ret = 0; for (auto& input : inputs) { PCSX::IO file(new PCSX::PosixFile(input)); if (file->failed()) { fmt::print(stderr, "Unable to open file: {}\n", input); ret = -2; } else { auto psyq = PsyqLnkFile::parse(file, verbose, !!args.get("s")); if (!psyq) { ret = -3; } else { if (args.get("d").value_or(false)) { fmt::print(":: Displaying {}\n", input); psyq->display(); fmt::print("\n\n\n"); } if (hasOutput) { fmt::print(":: Converting {} to {}...\n", input, output.value()); std::string prefix = args.get("p").value_or(""); bool success = psyq->writeElf(prefix, output.value(), !!args.get("n"), !!args.get("b")); if (success) { fmt::print(":: Conversion completed.\n"); } else { fmt::print(stderr, ":: Conversion failed: {}\n", psyq->elfConversionError); ret = -4; } } } } file.reset(); } return ret; }