From 56fe8c717792bf127dc488a61732fbebad46873f Mon Sep 17 00:00:00 2001 From: Nomi <nomiwen@outlook.de> Date: Thu, 7 Sep 2023 18:54:51 +0200 Subject: [PATCH] Make code prettier --- src/core/services/ldr_ro.cpp | 826 +++++++++++++++++++++++------------ 1 file changed, 553 insertions(+), 273 deletions(-) diff --git a/src/core/services/ldr_ro.cpp b/src/core/services/ldr_ro.cpp index e87dad7b..05414b38 100644 --- a/src/core/services/ldr_ro.cpp +++ b/src/core/services/ldr_ro.cpp @@ -1,6 +1,9 @@ #include "services/ldr_ro.hpp" #include "ipc.hpp" +#include <cstdio> +#include <string> + namespace LDRCommands { enum : u32 { Initialize = 0x000100C2, @@ -9,107 +12,264 @@ namespace LDRCommands { }; } +namespace CROHeader { + enum : u32 { + ID = 0x080, + NameOffset = 0x084, + NextCRO = 0x088, + PrevCRO = 0x08C, + CodeOffset = 0x0B0, + DataOffset = 0x0B8, + ModuleNameOffset = 0x0C0, + SegmentTableOffset = 0x0C8, + SegmentTableSize = 0x0CC, + NamedExportTableOffset = 0x0D0, + NamedExportTableSize = 0x0D4, + IndexedExportTableOffset = 0x0D8, + IndexedExportTableSize = 0x0DC, + ExportStringTableOffset = 0x0E0, + ExportStringSize = 0x0E4, + ExportTreeOffset = 0x0E8, + ImportModuleTableOffset = 0x0F0, + ImportModuleTableSize = 0x0F4, + ImportPatchTableOffset = 0x0F8, + ImportPatchTableSize = 0x0FC, + NamedImportTableOffset = 0x100, + NamedImportTableSize = 0x104, + IndexedImportTableOffset = 0x108, + IndexedImportTableSize = 0x10C, + AnonymousImportTableOffset = 0x110, + AnonymousImportTableSize = 0x114, + ImportStringTableOffset = 0x118, + ImportStringSize = 0x11C, + StaticAnonymousSymbolTableOffset = 0x120, + StaticAnonymousSymbolTableSize = 0x124, + RelocationPatchTableOffset = 0x128, + RelocationPatchTableSize = 0x12C, + StaticAnonymousPatchTableOffset = 0x130, + StaticAnonymousPatchTableSize = 0x134, + }; +} + +namespace SegmentTable { + enum : u32 { + Offset = 0, + Size = 4, + ID = 8, + }; + + namespace SegmentID { + enum : u32 { + TEXT, RODATA, DATA, BSS, + }; + } +} + +namespace NamedExportTable { + enum : u32 { + NameOffset = 0, + SegmentOffset = 4, + }; +}; + +namespace NamedImportTable { + enum : u32 { + NameOffset = 0, + RelocationOffset = 4, + }; +}; + +namespace IndexedImportTable { + enum : u32 { + Index = 0, + RelocationOffset = 4, + }; +}; + +namespace AnonymousImportTable { + enum : u32 { + SegmentOffset = 0, + RelocationOffset = 4, + }; +}; + +namespace ImportModuleTable { + enum : u32 { + NameOffset = 0, + IndexedOffset = 8, + AnonymousOffset = 16, + }; +}; + +namespace RelocationPatch { + enum : u32 { + SegmentOffset = 0, + PatchType = 4, + IsLastEntry = 5, // For import patches + SegmentIndex = 5, // For relocation patches + IsResolved = 6, + Addend = 8, + }; + + namespace RelocationPatchType { + enum : u32 { + AbsoluteAddress = 2, + }; + }; +}; + +struct CROHeaderEntry { + u32 offset, size; +}; + static constexpr u32 CRO_HEADER_SIZE = 0x138; class CRO { - // CRO header offsets - static constexpr u32 HEADER_ID = 0x80; - static constexpr u32 HEADER_NAME_OFFSET = 0x84; - static constexpr u32 HEADER_NEXT_CRO = 0x88; - static constexpr u32 HEADER_PREV_CRO = 0x8C; - static constexpr u32 HEADER_CODE_OFFSET = 0xB0; - static constexpr u32 HEADER_DATA_OFFSET = 0xB8; - static constexpr u32 HEADER_MODULE_NAME_OFFSET = 0xC0; - static constexpr u32 HEADER_SEGMENT_TABLE_OFFSET = 0xC8; - static constexpr u32 HEADER_SEGMENT_TABLE_SIZE = 0xCC; - static constexpr u32 HEADER_NAMED_EXPORT_TABLE_OFFSET = 0xD0; - static constexpr u32 HEADER_NAMED_EXPORT_TABLE_SIZE = 0xD4; - static constexpr u32 HEADER_INDEXED_EXPORT_TABLE_OFFSET = 0xD8; - static constexpr u32 HEADER_EXPORT_STRINGS_OFFSET = 0xE0; - static constexpr u32 HEADER_EXPORT_TREE_OFFSET = 0xE8; - static constexpr u32 HEADER_IMPORT_MODULE_TABLE_OFFSET = 0xF0; - static constexpr u32 HEADER_IMPORT_MODULE_TABLE_SIZE = 0xF4; - static constexpr u32 HEADER_IMPORT_PATCHES_OFFSET = 0xF8; - static constexpr u32 HEADER_NAMED_IMPORT_TABLE_OFFSET = 0x100; - static constexpr u32 HEADER_NAMED_IMPORT_TABLE_SIZE = 0x104; - static constexpr u32 HEADER_INDEXED_IMPORT_TABLE_OFFSET = 0x108; - static constexpr u32 HEADER_INDEXED_IMPORT_TABLE_SIZE = 0x10C; - static constexpr u32 HEADER_ANONYMOUS_IMPORT_TABLE_OFFSET = 0x110; - static constexpr u32 HEADER_ANONYMOUS_IMPORT_TABLE_SIZE = 0x114; - static constexpr u32 HEADER_IMPORT_STRINGS_OFFSET = 0x118; - static constexpr u32 HEADER_STATIC_ANONYMOUS_SYMBOLS_OFFSET = 0x120; - static constexpr u32 HEADER_RELOCATION_PATCHES_OFFSET = 0x128; - static constexpr u32 HEADER_RELOCATION_PATCHES_SIZE = 0x12C; - static constexpr u32 HEADER_STATIC_ANONYMOUS_PATCHES_OFFSET = 0x130; - - // Segment table entry offsets - static constexpr u32 SEGMENT_OFFSET = 0; - static constexpr u32 SEGMENT_ID = 8; - static constexpr u32 SEGMENT_ENTRY_SIZE = 12; - - // Segment table entry IDs - static constexpr u32 SEGMENT_ID_TEXT = 0; - static constexpr u32 SEGMENT_ID_RODATA = 1; - static constexpr u32 SEGMENT_ID_DATA = 2; - static constexpr u32 SEGMENT_ID_BSS = 3; - - // Named export table - static constexpr u32 NAMED_EXPORT_ENTRY_SIZE = 8; - - // Import module table - static constexpr u32 IMPORT_MODULE_TABLE_NAME_OFFSET = 0; - static constexpr u32 IMPORT_MODULE_TABLE_INDEXED_OFFSET = 8; - static constexpr u32 IMPORT_MODULE_TABLE_ANONYMOUS_OFFSET = 16; - static constexpr u32 IMPORT_MODULE_TABLE_ENTRY_SIZE = 20; - - // Named import table - static constexpr u32 NAMED_IMPORT_NAME_OFFSET = 0; - static constexpr u32 NAMED_IMPORT_RELOCATION_OFFSET = 4; - static constexpr u32 NAMED_IMPORT_TABLE_ENTRY_SIZE = 8; - - // Indexed import table - static constexpr u32 INDEXED_IMPORT_RELOCATION_OFFSET = 4; - static constexpr u32 INDEXED_IMPORT_TABLE_ENTRY_SIZE = 8; - - // Anonymous import table - static constexpr u32 ANONYMOUS_IMPORT_RELOCATION_OFFSET = 4; - static constexpr u32 ANONYMOUS_IMPORT_TABLE_ENTRY_SIZE = 8; - Memory &mem; - u32 croPointer; + u32 croPointer; // Origin address of CRO in RAM + + bool isCRO; // False if CRS public: - CRO(Memory &mem, u32 croPointer) : mem(mem), croPointer(croPointer) {} + CRO(Memory &mem, u32 croPointer, bool isCRO) : mem(mem), croPointer(croPointer), isCRO(isCRO) {} ~CRO() = default; - bool load() { - const u8* header = (u8*)mem.getReadPointer(croPointer); + u32 getNextCRO() { + return mem.read32(croPointer + CROHeader::NextCRO); + } + + u32 getPrevCRO() { + return mem.read32(croPointer + CROHeader::PrevCRO); + } + void setNextCRO(u32 nextCRO) { + mem.write32(croPointer + CROHeader::NextCRO, nextCRO); + } + + void setPrevCRO(u32 prevCRO) { + mem.write32(croPointer + CROHeader::PrevCRO, prevCRO); + } + + // Returns CRO header offset-size pair + CROHeaderEntry getHeaderEntry(u32 entry) { + return CROHeaderEntry{.offset = mem.read32(croPointer + entry), .size = mem.read32(croPointer + entry + 4)}; + } + + u32 getSegmentAddr(u32 segmentOffset) { + // "Decoded" segment tag + const u32 segmentIndex = segmentOffset & 0xF; + const u32 offset = segmentOffset >> 4; + + const CROHeaderEntry segmentTable = getHeaderEntry(CROHeader::SegmentTableOffset); + + // Safeguard + if (segmentIndex >= segmentTable.size) { + Helpers::panic("Invalid segment index = %u (table size = %u)", segmentIndex, segmentTable.size); + } + + // Get segment table entry + const u32 entryOffset = mem.read32(segmentTable.offset + 12 * segmentIndex + SegmentTable::Offset); + const u32 entrySize = mem.read32(segmentTable.offset + 12 * segmentIndex + SegmentTable::Size); + + // Another safeguard + if (offset >= entrySize) { + Helpers::panic("Invalid segment entry offset = %u (entry size = %u)", offset, entrySize); + } + + return entryOffset + offset; + } + + u32 getNamedExportSymbolAddr(const std::string& symbolName) { + // Note: The CRO contains a trie for fast symbol lookup. For simplicity, + // we won't use it and instead look up the symbol in the named export symbol table + + const u32 exportStringSize = mem.read32(croPointer + CROHeader::ExportStringSize); + + const CROHeaderEntry namedExportTable = getHeaderEntry(CROHeader::NamedExportTableOffset); + + for (u32 namedExport = 0; namedExport < namedExportTable.size; namedExport++) { + const u32 nameOffset = mem.read32(namedExportTable.offset + 8 * namedExport + NamedExportTable::NameOffset); + + const std::string exportSymbolName = mem.readString(nameOffset, exportStringSize); + + if (symbolName.compare(exportSymbolName) == 0) { + return getSegmentAddr(mem.read32(namedExportTable.offset + 8 * namedExport + NamedExportTable::SegmentOffset)); + } + } + + return 0; + } + + // Patches one symbol + bool patchSymbol(u32 relocationTarget, u8 patchType, u32 addend, u32 symbolOffset) { + switch (patchType) { + case RelocationPatch::RelocationPatchType::AbsoluteAddress: mem.write32(relocationTarget, symbolOffset + addend); break; + default: Helpers::panic("Unhandled relocation type = %X\n", patchType); + } + + return true; + } + + // Patches symbol batches + bool patchBatch(u32 batchAddr, u32 symbolAddr) { + u32 relocationPatch = batchAddr; + + while (true) { + const u32 segmentOffset = mem.read32(relocationPatch + RelocationPatch::SegmentOffset); + const u8 patchType = mem.read8(relocationPatch + RelocationPatch::PatchType); + const u8 isLastBatch = mem.read8(relocationPatch + RelocationPatch::IsLastEntry); + const u32 addend = mem.read32(relocationPatch + RelocationPatch::Addend); + + const u32 relocationTarget = getSegmentAddr(segmentOffset); + + if (relocationTarget == 0) { + Helpers::panic("Relocation target is NULL"); + } + + patchSymbol(relocationTarget, patchType, addend, symbolAddr); + + if (isLastBatch != 0) { + break; + } + + relocationPatch += 12; + } + + mem.write8(relocationPatch + RelocationPatch::IsResolved, 1); + + return true; + } + + bool load() { // TODO: verify SHA hashes? // Verify CRO magic - if (std::memcmp(&header[HEADER_ID], "CRO0", 4) != 0) { + const std::string magic = mem.readString(croPointer + CROHeader::ID, 4); + if (magic.compare(std::string("CRO0")) != 0) { return false; } // These fields are initially 0, the RO service sets them on load. If non-0, // this CRO has already been loaded - if ((*(u32*)&header[HEADER_NEXT_CRO] != 0) || (*(u32*)&header[HEADER_PREV_CRO] != 0)) { + if ((getNextCRO() != 0) || (getPrevCRO() != 0)) { return false; } return true; } - // Modify CRO offsets to point at virtual addresses - bool rebase(u32 mapVaddr, u32 dataVaddr, u32 bssVaddr) { + // Modifies CRO offsets to point at virtual addresses + bool rebase(u32 loadedCRS, u32 mapVaddr, u32 dataVaddr, u32 bssVaddr) { rebaseHeader(mapVaddr); u32 oldDataVaddr = 0; - rebaseSegmentTable(mapVaddr, dataVaddr, bssVaddr, &oldDataVaddr); - std::printf("Old .data vaddr = %X\n", oldDataVaddr); + // Note: Citra rebases the segment table only if the file is not a CRS. + // Presumably because CRS files don't contain segments? + if (isCRO) { + rebaseSegmentTable(mapVaddr, dataVaddr, bssVaddr, &oldDataVaddr); + } rebaseNamedExportTable(mapVaddr); rebaseImportModuleTable(mapVaddr); @@ -117,265 +277,193 @@ public: rebaseIndexedImportTable(mapVaddr); rebaseAnonymousImportTable(mapVaddr); - relocateInternal(oldDataVaddr); + relocateInternalSymbols(oldDataVaddr); + + // Note: Citra relocates static anonymous symbols and exit symbols only if the file is not a CRS + if (isCRO) { + relocateStaticAnonymousSymbols(); + relocateExitSymbols(loadedCRS); + } return true; } bool rebaseHeader(u32 mapVaddr) { - std::puts("Rebasing CRO header"); - - const u8* header = (u8*)mem.getReadPointer(croPointer); - constexpr u32 headerOffsets[] = { - HEADER_NAME_OFFSET, - HEADER_CODE_OFFSET, - HEADER_DATA_OFFSET, - HEADER_MODULE_NAME_OFFSET, - HEADER_SEGMENT_TABLE_OFFSET, - HEADER_NAMED_EXPORT_TABLE_OFFSET, - HEADER_INDEXED_EXPORT_TABLE_OFFSET, - HEADER_EXPORT_STRINGS_OFFSET, - HEADER_EXPORT_TREE_OFFSET, - HEADER_IMPORT_MODULE_TABLE_OFFSET, - HEADER_IMPORT_PATCHES_OFFSET, - HEADER_NAMED_IMPORT_TABLE_OFFSET, - HEADER_INDEXED_IMPORT_TABLE_OFFSET, - HEADER_ANONYMOUS_IMPORT_TABLE_OFFSET, - HEADER_IMPORT_STRINGS_OFFSET, - HEADER_STATIC_ANONYMOUS_SYMBOLS_OFFSET, // ? - HEADER_RELOCATION_PATCHES_OFFSET, - HEADER_STATIC_ANONYMOUS_PATCHES_OFFSET, // ? + CROHeader::NameOffset, + CROHeader::CodeOffset, + CROHeader::DataOffset, + CROHeader::ModuleNameOffset, + CROHeader::SegmentTableOffset, + CROHeader::NamedExportTableOffset, + CROHeader::IndexedExportTableOffset, + CROHeader::ExportStringTableOffset, + CROHeader::ExportTreeOffset, + CROHeader::ImportModuleTableOffset, + CROHeader::ImportPatchTableOffset, + CROHeader::NamedImportTableOffset, + CROHeader::IndexedImportTableOffset, + CROHeader::AnonymousImportTableOffset, + CROHeader::ImportStringTableOffset, + CROHeader::StaticAnonymousSymbolTableOffset, + CROHeader::RelocationPatchTableOffset, + CROHeader::StaticAnonymousPatchTableOffset, }; - for (auto offset : headerOffsets) { - *(u32*)&header[offset] += mapVaddr; + for (u32 offset : headerOffsets) { + mem.write32(croPointer + offset, mem.read32(croPointer + offset) + mapVaddr); } return true; } bool rebaseSegmentTable(u32 mapVaddr, u32 dataVaddr, u32 bssVaddr, u32 *oldDataVaddr) { - std::puts("Rebasing segment table"); + const CROHeaderEntry segmentTable = getHeaderEntry(CROHeader::SegmentTableOffset); - const u8* header = (u8*)mem.getReadPointer(croPointer); - - const u32 segmentTableAddr = *(u32*)&header[HEADER_SEGMENT_TABLE_OFFSET]; - const u32 segmentTableSize = *(u32*)&header[HEADER_SEGMENT_TABLE_SIZE]; + for (u32 segment = 0; segment < segmentTable.size; segment++) { + u32 segmentOffset = mem.read32(segmentTable.offset + 12 * segment + SegmentTable::Offset); - if ((segmentTableAddr & 3) != 0) { - Helpers::panic("Unaligned segment table address"); - } - - if (segmentTableSize == 0) { - Helpers::panic("Segment table empty"); - } - - const u8* segmentTable = (u8*)mem.getReadPointer(segmentTableAddr); - - for (u32 segment = 0; segment < segmentTableSize; segment++) { - u32* segmentOffset = (u32*)&segmentTable[SEGMENT_ENTRY_SIZE * segment + SEGMENT_OFFSET]; - - const u32 segmentID = *(u32*)&segmentTable[SEGMENT_ENTRY_SIZE * segment + SEGMENT_ID]; + const u32 segmentID = mem.read32(segmentTable.offset + 12 * segment + SegmentTable::ID); switch (segmentID) { - case SEGMENT_ID_DATA: - *oldDataVaddr = *segmentOffset + dataVaddr; *segmentOffset = dataVaddr; break; - case SEGMENT_ID_BSS: *segmentOffset = bssVaddr; break; - case SEGMENT_ID_TEXT: - case SEGMENT_ID_RODATA: - *segmentOffset += mapVaddr; break; + case SegmentTable::SegmentID::DATA: + *oldDataVaddr = segmentOffset + dataVaddr; segmentOffset = dataVaddr; break; + case SegmentTable::SegmentID::BSS: segmentOffset = bssVaddr; break; + case SegmentTable::SegmentID::TEXT: + case SegmentTable::SegmentID::RODATA: + segmentOffset += mapVaddr; break; default: - Helpers::panic("Unknown segment ID"); + Helpers::panic("Unknown segment ID = %u", segmentID); } - std::printf("Rebasing segment table entry %u (ID = %u), addr = %X\n", segment, segmentID, *segmentOffset); + mem.write32(segmentTable.offset + 12 * segment + SegmentTable::Offset, segmentOffset); } return true; } bool rebaseNamedExportTable(u32 mapVaddr) { - std::puts("Rebasing named export table"); + const CROHeaderEntry namedExportTable = getHeaderEntry(CROHeader::NamedExportTableOffset); - const u8* header = (u8*)mem.getReadPointer(croPointer); + for (u32 namedExport = 0; namedExport < namedExportTable.size; namedExport++) { + u32 nameOffset = mem.read32(namedExportTable.offset + 8 * namedExport); - const u32 namedExportAddr = *(u32*)&header[HEADER_NAMED_EXPORT_TABLE_OFFSET]; - const u32 namedExportSize = *(u32*)&header[HEADER_NAMED_EXPORT_TABLE_SIZE]; + // Note: I don't know if this can happen, better add this safeguard + if (nameOffset == 0) { + Helpers::panic("Named export name offset is NULL"); + } - if ((namedExportAddr & 3) != 0) { - Helpers::panic("Unaligned named export table address"); - } - - const u8* namedExportTable = (u8*)mem.getReadPointer(namedExportAddr); - - for (u32 namedExport = 0; namedExport < namedExportSize; namedExport++) { - u32* nameOffset = (u32*)&namedExportTable[NAMED_EXPORT_ENTRY_SIZE * namedExport]; - - assert(*nameOffset != 0); - - *nameOffset += mapVaddr; - - std::printf("Rebasing named export %u, addr = %X\n", namedExport, *nameOffset); + mem.write32(namedExportTable.offset + 8 * namedExport, nameOffset + mapVaddr); } return true; } bool rebaseImportModuleTable(u32 mapVaddr) { - std::puts("Rebasing import module table"); + const CROHeaderEntry importModuleTable = getHeaderEntry(CROHeader::ImportModuleTableOffset); - const u8* header = (u8*)mem.getReadPointer(croPointer); + for (u32 importModule = 0; importModule < importModuleTable.size; importModule++) { + u32 nameOffset = mem.read32(importModuleTable.offset + 20 * importModule + ImportModuleTable::NameOffset); - const u32 importModuleTableAddr = *(u32*)&header[HEADER_IMPORT_MODULE_TABLE_OFFSET]; - const u32 importModuleTableSize = *(u32*)&header[HEADER_IMPORT_MODULE_TABLE_SIZE]; + if (nameOffset == 0) { + Helpers::panic("Import module name offset is NULL"); + } - if ((importModuleTableAddr & 3) != 0) { - Helpers::panic("Unaligned import module table address"); - } + mem.write32(importModuleTable.offset + 20 * importModule + ImportModuleTable::NameOffset, nameOffset + mapVaddr); - const u8* importModuleTable = (u8*)mem.getReadPointer(importModuleTableAddr); + u32 indexedOffset = mem.read32(importModuleTable.offset + 20 * importModule + ImportModuleTable::IndexedOffset); - for (u32 importModule = 0; importModule < importModuleTableSize; importModule++) { - u32* nameOffset = (u32*)&importModuleTable[IMPORT_MODULE_TABLE_ENTRY_SIZE * importModule + IMPORT_MODULE_TABLE_NAME_OFFSET]; + if (indexedOffset == 0) { + Helpers::panic("Import module indexed offset is NULL"); + } - assert(*nameOffset != 0); + mem.write32(importModuleTable.offset + 20 * importModule + ImportModuleTable::IndexedOffset, indexedOffset + mapVaddr); - *nameOffset += mapVaddr; + u32 anonymousOffset = mem.read32(importModuleTable.offset + 20 * importModule + ImportModuleTable::AnonymousOffset); - u32 *indexedOffset = (u32*)&importModuleTable[IMPORT_MODULE_TABLE_ENTRY_SIZE * importModule + IMPORT_MODULE_TABLE_INDEXED_OFFSET]; + if (anonymousOffset == 0) { + Helpers::panic("Import module anonymous offset is NULL"); + } - assert(*indexedOffset != 0); - - *indexedOffset += mapVaddr; - - u32 *anonymousOffset = (u32*)&importModuleTable[IMPORT_MODULE_TABLE_ENTRY_SIZE * importModule + IMPORT_MODULE_TABLE_ANONYMOUS_OFFSET]; - - assert(*anonymousOffset != 0); - - *anonymousOffset += mapVaddr; - - std::printf("Rebasing import module %u, name addr = %X, indexed addr = %X, anonymous addr = %X\n", importModule, *nameOffset, *indexedOffset, *anonymousOffset); + mem.write32(importModuleTable.offset + 20 * importModule + ImportModuleTable::AnonymousOffset, anonymousOffset + mapVaddr); } return true; } bool rebaseNamedImportTable(u32 mapVaddr) { - std::puts("Rebasing named import table"); + const CROHeaderEntry namedImportTable = getHeaderEntry(CROHeader::NamedImportTableOffset); - const u8* header = (u8*)mem.getReadPointer(croPointer); + for (u32 namedImport = 0; namedImport < namedImportTable.size; namedImport++) { + u32 nameOffset = mem.read32(namedImportTable.offset + 8 * namedImport + NamedImportTable::NameOffset); - const u32 namedImportTableAddr = *(u32*)&header[HEADER_NAMED_IMPORT_TABLE_OFFSET]; - const u32 namedImportTableSize = *(u32*)&header[HEADER_NAMED_IMPORT_TABLE_SIZE]; + if (nameOffset == 0) { + Helpers::panic("Named import name offset is NULL"); + } - if ((namedImportTableAddr & 3) != 0) { - Helpers::panic("Unaligned named import table address"); - } + mem.write32(namedImportTable.offset + 8 * namedImport + NamedImportTable::NameOffset, nameOffset + mapVaddr); - const u8* namedImportTable = (u8*)mem.getReadPointer(namedImportTableAddr); + u32 relocationOffset = mem.read32(namedImportTable.offset + 8 * namedImport + NamedImportTable::RelocationOffset); - for (u32 namedImport = 0; namedImport < namedImportTableSize; namedImport++) { - u32* nameOffset = (u32*)&namedImportTable[NAMED_IMPORT_TABLE_ENTRY_SIZE * namedImport + NAMED_IMPORT_NAME_OFFSET]; + if (relocationOffset == 0) { + Helpers::panic("Named import relocation offset is NULL"); + } - assert(*namedImport != 0); - - *nameOffset += mapVaddr; - - u32* relocationOffset = (u32*)&namedImportTable[NAMED_IMPORT_TABLE_ENTRY_SIZE * namedImport + NAMED_IMPORT_RELOCATION_OFFSET]; - - assert(*relocationOffset != 0); - - *relocationOffset += mapVaddr; - - std::printf("Rebasing named import %u, name addr = %X, relocation addr = %X\n", namedImport, *nameOffset, *relocationOffset); + mem.write32(namedImportTable.offset + 8 * namedImport + NamedImportTable::RelocationOffset, relocationOffset + mapVaddr); } return true; } bool rebaseIndexedImportTable(u32 mapVaddr) { - std::puts("Rebase indexed import table"); + const CROHeaderEntry indexedImportTable = getHeaderEntry(CROHeader::IndexedImportTableOffset); - const u8* header = (u8*)mem.getReadPointer(croPointer); + for (u32 indexedImport = 0; indexedImport < indexedImportTable.size; indexedImport++) { + u32 relocationOffset = mem.read32(indexedImportTable.offset + 8 * indexedImport + IndexedImportTable::RelocationOffset); - const u32 indexedImportTableAddr = *(u32*)&header[HEADER_INDEXED_IMPORT_TABLE_OFFSET]; - const u32 indexedImportTableSize = *(u32*)&header[HEADER_INDEXED_IMPORT_TABLE_SIZE]; + if (relocationOffset == 0) { + Helpers::panic("Indexed import relocation offset is NULL"); + } - if ((indexedImportTableAddr & 3) != 0) { - Helpers::panic("Unaligned indexed import table address"); - } - - const u8* indexedImportTable = (u8*)mem.getReadPointer(indexedImportTableAddr); - - for (u32 indexedImport = 0; indexedImport < indexedImportTableSize; indexedImport++) { - u32* relocationOffset = (u32*)&indexedImportTable[INDEXED_IMPORT_TABLE_ENTRY_SIZE * indexedImport + INDEXED_IMPORT_RELOCATION_OFFSET]; - - assert(*relocationOffset != 0); - - *relocationOffset += mapVaddr; - - std::printf("Rebasing indexed import %u, relocation addr = %X\n", indexedImport, *relocationOffset); + mem.write32(indexedImportTable.offset + 8 * indexedImport + IndexedImportTable::RelocationOffset, relocationOffset + mapVaddr); } return true; } bool rebaseAnonymousImportTable(u32 mapVaddr) { - std::puts("Rebase anonymous import table"); + const CROHeaderEntry anonymousImportTable = getHeaderEntry(CROHeader::AnonymousImportTableOffset); - const u8* header = (u8*)mem.getReadPointer(croPointer); + for (u32 anonymousImport = 0; anonymousImport < anonymousImportTable.size; anonymousImport++) { + u32 relocationOffset = mem.read32(anonymousImportTable.offset + 8 * anonymousImport + AnonymousImportTable::RelocationOffset); - const u32 anonymousImportTableAddr = *(u32*)&header[HEADER_ANONYMOUS_IMPORT_TABLE_OFFSET]; - const u32 anonymousImportTableSize = *(u32*)&header[HEADER_ANONYMOUS_IMPORT_TABLE_SIZE]; + if (relocationOffset == 0) { + Helpers::panic("Anonymous import relocation offset is NULL"); + } - if ((anonymousImportTableAddr & 3) != 0) { - Helpers::panic("Unaligned anonymous import table address"); - } - - const u8* anonymousImportTable = (u8*)mem.getReadPointer(anonymousImportTableAddr); - - for (u32 anonymousImport = 0; anonymousImport < anonymousImportTableSize; anonymousImport++) { - u32* relocationOffset = (u32*)&anonymousImportTable[ANONYMOUS_IMPORT_TABLE_ENTRY_SIZE * anonymousImport + ANONYMOUS_IMPORT_RELOCATION_OFFSET]; - - assert(*relocationOffset != 0); - - *relocationOffset += mapVaddr; - - std::printf("Rebasing anonymous import %u, relocation addr = %X\n", anonymousImport, *relocationOffset); + mem.write32(anonymousImportTable.offset + 8 * anonymousImport + AnonymousImportTable::RelocationOffset, relocationOffset + mapVaddr); } return true; } - bool relocateInternal(u32 oldDataVaddr) { - std::puts("Relocate internal"); - + bool relocateInternalSymbols(u32 oldDataVaddr) { const u8* header = (u8*)mem.getReadPointer(croPointer); - const u32 relocationTableAddr = *(u32*)&header[HEADER_RELOCATION_PATCHES_OFFSET]; - const u32 relocationTableSize = *(u32*)&header[HEADER_RELOCATION_PATCHES_SIZE]; + const CROHeaderEntry relocationPatchTable = getHeaderEntry(CROHeader::RelocationPatchTableOffset); + const CROHeaderEntry segmentTable = getHeaderEntry(CROHeader::SegmentTableOffset); - const u32 segmentTableAddr = *(u32*)&header[HEADER_SEGMENT_TABLE_OFFSET]; - //const u32 segmentTableSize = *(u32*)&header[HEADER_SEGMENT_TABLE_SIZE]; - - const u8* relocationTable = (u8*)mem.getReadPointer(relocationTableAddr); - const u8* segmentTable = (u8*)mem.getReadPointer(segmentTableAddr); - - for (u32 relocationNum = 0; relocationNum < relocationTableSize; relocationNum++) { - const u32 segmentOffset = *(u32*)&relocationTable[12 * relocationNum]; - const u8 patchType = *(u8*)&relocationTable[12 * relocationNum + 4]; - const u8 index = *(u8*)&relocationTable[12 * relocationNum + 5]; - const u32 addend = *(u32*)&relocationTable[12 * relocationNum + 8]; - - std::printf("Relocation %u, segment offset = %X, patch type = %X, index = %X, addend = %X\n", relocationNum, segmentOffset, patchType, index, addend); + for (u32 relocationPatch = 0; relocationPatch < relocationPatchTable.size; relocationPatch++) { + const u32 segmentOffset = mem.read32(relocationPatchTable.offset + 12 * relocationPatch + RelocationPatch::SegmentOffset); + const u8 patchType = mem.read8(relocationPatchTable.offset + 12 * relocationPatch + RelocationPatch::PatchType); + const u8 segmentIndex = mem.read8(relocationPatchTable.offset + 12 * relocationPatch + RelocationPatch::SegmentIndex); + const u32 addend = mem.read32(relocationPatchTable.offset + 12 * relocationPatch + RelocationPatch::Addend); const u32 segmentAddr = getSegmentAddr(segmentOffset); - // Get relocation target address - const u32 entryID = *(u32*)&segmentTable[SEGMENT_ENTRY_SIZE * (segmentOffset & 0xF) + SEGMENT_ID]; + const u32 entryID = mem.read32(segmentTable.offset + 12 * (segmentOffset & 0xF) + SegmentTable::ID); u32 relocationTarget = segmentAddr; - if (entryID == SEGMENT_ID_DATA) { + if (entryID == SegmentTable::SegmentID::DATA) { // Recompute relocation target for .data relocationTarget = oldDataVaddr + (segmentOffset >> 4); } @@ -384,7 +472,7 @@ public: Helpers::panic("Relocation target is NULL"); } - const u32 symbolOffset = *(u32*)&segmentTable[SEGMENT_ENTRY_SIZE * index + SEGMENT_OFFSET]; + const u32 symbolOffset = mem.read32(segmentTable.offset + 12 * segmentIndex + SegmentTable::Offset); patchSymbol(relocationTarget, patchType, addend, symbolOffset); } @@ -392,44 +480,191 @@ public: return true; } - bool patchSymbol(u32 relocationTarget, u8 patchType, u32 addend, u32 symbolOffset) { - switch (patchType) { - case 2: mem.write32(relocationTarget, symbolOffset + addend); break; - default: Helpers::panic("Unhandled relocation type = %X\n", patchType); + bool relocateStaticAnonymousSymbols() { + const CROHeaderEntry staticAnonymousSymbolTable = getHeaderEntry(CROHeader::StaticAnonymousSymbolTableOffset); + + for (u32 symbol = 0; symbol < staticAnonymousSymbolTable.size; symbol++) { + Helpers::panic("TODO: relocate static anonymous symbols"); } return true; } - u32 getSegmentAddr(u32 segmentOffset) { - const u8* header = (u8*)mem.getReadPointer(croPointer); - - // "Decoded" segment tag - const u32 segmentIndex = segmentOffset & 0xF; - const u32 offset = segmentOffset >> 4; - - const u32 segmentTableAddr = *(u32*)&header[HEADER_SEGMENT_TABLE_OFFSET]; - const u32 segmentTableSize = *(u32*)&header[HEADER_SEGMENT_TABLE_SIZE]; - - if (segmentIndex >= segmentTableSize) { - Helpers::panic("bwaaa (invalid segment index = %u, table size = %u)", segmentIndex, segmentTableSize); + // Patches "__aeabi_atexit" symbol to "nnroAeabiAtexit_" + bool relocateExitSymbols(u32 loadedCRS) { + if (loadedCRS == 0) { + Helpers::panic("CRS not loaded"); } - const u8* segmentTable = (u8*)mem.getReadPointer(segmentTableAddr); + const u32 importStringSize = mem.read32(croPointer + CROHeader::ImportStringSize); - // Get segment table entry - const u32 entryOffset = *(u32*)&segmentTable[SEGMENT_ENTRY_SIZE * segmentIndex]; - const u32 entrySize = *(u32*)&segmentTable[SEGMENT_ENTRY_SIZE * segmentIndex + 4]; + const CROHeaderEntry namedImportTable = getHeaderEntry(CROHeader::NamedImportTableOffset); - if (offset >= entrySize) { - Helpers::panic("bwaaa (invalid offset = %X, entry size = %X)", offset, entrySize); + for (u32 namedImport = 0; namedImport < namedImportTable.size; namedImport++) { + const u32 nameOffset = mem.read32(namedImportTable.offset + 8 * namedImport + NamedImportTable::NameOffset); + const u32 relocationOffset = mem.read32(namedImportTable.offset + 8 * namedImport + NamedImportTable::RelocationOffset); + + const std::string symbolName = mem.readString(nameOffset, importStringSize); + + if (symbolName.compare(std::string("__aeabi_atexit")) == 0) { + // Find exit symbol in other CROs + u32 currentCROPointer = loadedCRS; + while (currentCROPointer != 0) { + CRO cro(mem, currentCROPointer, true); + + const u32 exportSymbolAddr = cro.getNamedExportSymbolAddr(std::string("nnroAeabiAtexit_")); + if (exportSymbolAddr != 0) { + patchBatch(relocationOffset, exportSymbolAddr); + + return true; + } + + currentCROPointer = cro.getNextCRO(); + } + } } - return entryOffset + offset; + Helpers::warn("Failed to relocate exit symbols"); + + return false; + } + + bool importNamedSymbols(u32 loadedCRS) { + if (loadedCRS == 0) { + Helpers::panic("CRS not loaded"); + } + + const u32 importStringSize = mem.read32(croPointer + CROHeader::ImportStringSize); + + const CROHeaderEntry namedImportTable = getHeaderEntry(CROHeader::NamedImportTableOffset); + + for (u32 namedImport = 0; namedImport < namedImportTable.size; namedImport++) { + const u32 relocationOffset = mem.read32(namedImportTable.offset + 8 * namedImport + NamedImportTable::RelocationOffset); + + u8 isResolved = mem.read8(relocationOffset + RelocationPatch::IsResolved); + + if (isResolved == 0) { + const u32 nameOffset = mem.read32(namedImportTable.offset + 8 * namedImport + NamedImportTable::NameOffset); + + const std::string symbolName = mem.readString(nameOffset, importStringSize); + + // Check every loaded CRO for the symbol (the pain) + u32 currentCROPointer = loadedCRS; + while (currentCROPointer != 0) { + CRO cro(mem, currentCROPointer, true); + + const u32 exportSymbolAddr = cro.getNamedExportSymbolAddr(symbolName); + if (exportSymbolAddr != 0) { + patchBatch(relocationOffset, exportSymbolAddr); + + isResolved = 1; + break; + } + + currentCROPointer = cro.getNextCRO(); + } + + if (isResolved == 0) { + Helpers::panic("Failed to resolve symbol %s", symbolName.c_str()); + } + + mem.write8(relocationOffset + RelocationPatch::IsResolved, 1); + } + } + + return true; + } + + bool importModules(u32 loadedCRS) { + if (loadedCRS == 0) { + Helpers::panic("CRS not loaded"); + } + + const u32 importStringSize = mem.read32(croPointer + CROHeader::ImportStringSize); + + const CROHeaderEntry importModuleTable = getHeaderEntry(CROHeader::ImportModuleTableOffset); + + for (u32 importModule = 0; importModule < importModuleTable.size; importModule++) { + Helpers::panic("TODO: import modules"); + } + + return true; + } + + // Links CROs. Heavily based on Citra's CRO linker + bool link(u32 loadedCRS) { + if (loadedCRS == 0) { + Helpers::panic("CRS not loaded"); + } + + const CROHeaderEntry segmentTable = getHeaderEntry(CROHeader::SegmentTableOffset); + + // Fix data segment offset (LoadCRO_New) + // Note: the old LoadCRO does *not* fix .data + u32 dataVaddr; + if (segmentTable.size > 1) { + // Note: ldr:ro assumes that segment index 2 is .data + dataVaddr = mem.read32(segmentTable.offset + 24 + SegmentTable::Offset); + + mem.write32(segmentTable.offset + 24 + SegmentTable::Offset, mem.read32(croPointer + CROHeader::DataOffset)); + } + + importNamedSymbols(loadedCRS); + importModules(loadedCRS); + + // TODO: export symbols to other CROs + + // Restore .data segment offset (LoadCRO_New) + if (segmentTable.size > 1) { + mem.write32(segmentTable.offset + 24 + SegmentTable::Offset, dataVaddr); + } + + return true; + } + + // Adds CRO to the linked list of loaded CROs + void registerCRO(u32 loadedCRS, bool autoLink) { + if (loadedCRS == 0) { + Helpers::panic("CRS not loaded"); + } + + CRO crs(mem, loadedCRS, false); + + u32 headAddr = crs.getPrevCRO(); + if (autoLink) { + headAddr = crs.getNextCRO(); + } + + if (headAddr == 0) { + // Register first CRO + setPrevCRO(croPointer); + + if (autoLink) { + crs.setNextCRO(croPointer); + } else { + crs.setPrevCRO(croPointer); + } + } else { + // Register new CRO + CRO head(mem, headAddr, true); + + if (head.getPrevCRO() == 0) { + Helpers::panic("No tail CRO found"); + } + + CRO tail(mem, head.getPrevCRO(), true); + + setPrevCRO(tail.croPointer); + + tail.setNextCRO(croPointer); + head.setPrevCRO(croPointer); + } } }; -void LDRService::reset() {} +void LDRService::reset() { + loadedCRS = 0; +} void LDRService::handleSyncRequest(u32 messagePointer) { const u32 command = mem.read32(messagePointer); @@ -448,6 +683,43 @@ void LDRService::initialize(u32 messagePointer) { const Handle process = mem.read32(messagePointer + 20); log("LDR_RO::Initialize (buffer = %08X, size = %08X, vaddr = %08X, process = %X)\n", crsPointer, size, mapVaddr, process); + + // Sanity checks + if (loadedCRS != 0) { + Helpers::panic("CRS already loaded\n"); + } + + if (size < CRO_HEADER_SIZE) { + Helpers::panic("CRS too small\n"); + } + + if ((size & mem.pageMask) != 0) { + Helpers::panic("Unaligned CRS size\n"); + } + + if ((crsPointer & mem.pageMask) != 0) { + Helpers::panic("Unaligned CRS pointer\n"); + } + + if ((mapVaddr & mem.pageMask) != 0) { + Helpers::panic("Unaligned CRS output vaddr\n"); + } + + // Map CRO to output address + mem.mirrorMapping(mapVaddr, crsPointer, size); + + CRO crs(mem, crsPointer, false); + + if (!crs.load()) { + Helpers::panic("Failed to load CRS"); + } + + if (!crs.rebase(0, mapVaddr, 0, 0)) { + Helpers::panic("Failed to rebase CRS"); + } + + loadedCRS = crsPointer; + mem.write32(messagePointer, IPC::responseHeader(0x1, 1, 0)); mem.write32(messagePointer + 4, Result::Success); } @@ -474,7 +746,7 @@ void LDRService::loadCRONew(u32 messagePointer) { const u32 fixLevel = mem.read32(messagePointer + 40); const Handle process = mem.read32(messagePointer + 52); - std::printf("LDR_RO::LoadCRONew (buffer = %08X, vaddr = %08X, size = %08X, .data vaddr = %08X, .data size = %08X, .bss vaddr = %08X, .bss size = %08X, auto link = %d, fix level = %X, process = %X)\n", croPointer, mapVaddr, size, dataVaddr, dataSize, bssVaddr, bssSize, autoLink, fixLevel, process); + log("LDR_RO::LoadCRONew (buffer = %08X, vaddr = %08X, size = %08X, .data vaddr = %08X, .data size = %08X, .bss vaddr = %08X, .bss size = %08X, auto link = %d, fix level = %X, process = %X)\n", croPointer, mapVaddr, size, dataVaddr, dataSize, bssVaddr, bssSize, autoLink, fixLevel, process); // Sanity checks if (size < CRO_HEADER_SIZE) { @@ -496,16 +768,24 @@ void LDRService::loadCRONew(u32 messagePointer) { // Map CRO to output address mem.mirrorMapping(mapVaddr, croPointer, size); - CRO cro(mem, croPointer); + CRO cro(mem, croPointer, true); if (!cro.load()) { Helpers::panic("Failed to load CRO"); } - if (!cro.rebase(mapVaddr, dataVaddr, bssVaddr)) { + if (!cro.rebase(loadedCRS, mapVaddr, dataVaddr, bssVaddr)) { Helpers::panic("Failed to rebase CRO"); } + if (!cro.link(loadedCRS)) { + Helpers::panic("Failed to link CRO"); + } + + cro.registerCRO(loadedCRS, autoLink); + + // TODO: add fixing + mem.write32(messagePointer, IPC::responseHeader(0x9, 2, 0)); mem.write32(messagePointer + 4, Result::Success); mem.write32(messagePointer + 8, size);