diff --git a/src/core/hle/service/ldr_ro.cpp b/src/core/hle/service/ldr_ro.cpp index fcdb440ad..b8fcfc43d 100644 --- a/src/core/hle/service/ldr_ro.cpp +++ b/src/core/hle/service/ldr_ro.cpp @@ -489,6 +489,532 @@ class CROHelper final { return RESULT_SUCCESS; } + /** + * Finds an exported named symbol in this module. + * @param name the name of the symbol to find + * @return VAddr the virtual address of the symbol; 0 if not found. + */ + VAddr FindExportNamedSymbol(const std::string& name) const { + if (!GetField(ExportTreeNum)) + return 0; + + std::size_t len = name.size(); + ExportTreeEntry entry; + GetEntry(0, entry); + ExportTreeEntry::Child next; + next.raw = entry.left.raw; + u32 found_id; + + while (true) { + GetEntry(next.next_index, entry); + + if (next.is_end) { + found_id = entry.export_table_index; + break; + } + + u16 test_byte = entry.test_bit >> 3; + u16 test_bit_in_byte = entry.test_bit & 7; + + if (test_byte >= len) { + next.raw = entry.left.raw; + } else if((name[test_byte] >> test_bit_in_byte) & 1) { + next.raw = entry.right.raw; + } else { + next.raw = entry.left.raw; + } + } + + u32 export_named_symbol_num = GetField(ExportNamedSymbolNum); + + if (found_id >= export_named_symbol_num) + return 0; + + u32 export_strings_size = GetField(ExportStringsSize); + ExportNamedSymbolEntry symbol_entry; + GetEntry(found_id, symbol_entry); + + if (Memory::ReadCString(symbol_entry.name_offset, export_strings_size) != name) + return 0; + + return SegmentTagToAddress(symbol_entry.symbol_position); + } + + /** + * Rebases offsets in module header according to module address. + * @param cro_size the size of the CRO file + * @returns ResultCode RESULT_SUCCESS if all offsets are verified as valid, otherwise error code. + */ + ResultCode RebaseHeader(u32 cro_size) { + ResultCode error = CROFormatError(0x11); + + // verifies magic + if (GetField(Magic) != MAGIC_CRO0) + return error; + + // verifies not registered + if (GetField(NextCRO) || GetField(PreviousCRO)) + return error; + + // This seems to be a hard limit set by the RO module + if (GetField(FileSize) > 0x10000000 || GetField(BssSize) > 0x10000000) + return error; + + // verifies not fixed + if (GetField(FixedSize)) + return error; + + if (GetField(CodeOffset) < CRO_HEADER_SIZE) + return error; + + // verifies that all offsets are in the correct order + constexpr std::array OFFSET_ORDER = {{ + CodeOffset, + ModuleNameOffset, + SegmentTableOffset, + ExportNamedSymbolTableOffset, + ExportTreeTableOffset, + ExportIndexedSymbolTableOffset, + ExportStringsOffset, + ImportModuleTableOffset, + ExternalPatchTableOffset, + ImportNamedSymbolTableOffset, + ImportIndexedSymbolTableOffset, + ImportAnonymousSymbolTableOffset, + ImportStringsOffset, + StaticAnonymousSymbolTableOffset, + InternalPatchTableOffset, + StaticPatchTableOffset, + DataOffset, + FileSize + }}; + + u32 prev_offset = GetField(OFFSET_ORDER[0]); + u32 cur_offset; + for (std::size_t i = 1; i < OFFSET_ORDER.size(); ++i) { + cur_offset = GetField(OFFSET_ORDER[i]); + if (cur_offset < prev_offset) + return error; + prev_offset = cur_offset; + } + + // rebases offsets + u32 offset = GetField(NameOffset); + if (offset) + SetField(NameOffset, offset + address); + + for (int field = CodeOffset; field < Fix0Barrier; field += 2) { + HeaderField header_field = static_cast(field); + offset = GetField(header_field); + if (offset) + SetField(header_field, offset + address); + } + + // verifies everything is not beyond the buffer + u32 file_end = address + cro_size; + for (int field = CodeOffset, i = 0; field < Fix0Barrier; field += 2, ++i) { + HeaderField offset_field = static_cast(field); + HeaderField size_field = static_cast(field + 1); + if (GetField(offset_field) + GetField(size_field) * ENTRY_SIZE[i] > file_end) + return error; + } + + return RESULT_SUCCESS; + } + + /** + * Verifies a string matching a predicted size (i.e. terminated by 0) if it is not empty + * @param address the virtual address of the string + * @param size the size of the string, including the terminating 0 + * @returns ResultCode RESULT_SUCCESS if the size matches, otherwise error code. + */ + static ResultCode VerifyString(VAddr address, u32 size) { + if (size) { + if (Memory::Read8(address + size - 1) != 0) + return CROFormatError(0x0B); + } + return RESULT_SUCCESS; + } + + /** + * Rebases offsets in segment table according to module address. + * @param cro_size the size of the CRO file + * @param data_segment_address the buffer address for .data segment + * @param data_segment_size the buffer size for .data segment + * @param bss_segment_address the buffer address for .bss segment + * @param bss_segment_size the buffer size for .bss segment + * @returns ResultVal with the previous data segment offset before rebasing. + */ + ResultVal RebaseSegmentTable(u32 cro_size, + VAddr data_segment_address, u32 data_segment_size, + VAddr bss_segment_address, u32 bss_segment_size) { + u32 prev_data_segment = 0; + u32 segment_num = GetField(SegmentNum); + for (u32 i = 0; i < segment_num; ++i) { + SegmentEntry segment; + GetEntry(i, segment); + if (segment.type == SegmentType::Data) { + if (segment.size) { + if (segment.size > data_segment_size) + return ERROR_BUFFER_TOO_SMALL; + prev_data_segment = segment.offset; + segment.offset = data_segment_address; + } + } else if (segment.type == SegmentType::BSS) { + if (segment.size) { + if (segment.size > bss_segment_size) + return ERROR_BUFFER_TOO_SMALL; + segment.offset = bss_segment_address; + } + } else if (segment.offset) { + segment.offset += address; + if (segment.offset > address + cro_size) + return CROFormatError(0x19); + } + SetEntry(i, segment); + } + return MakeResult(prev_data_segment); + } + + /** + * Rebases offsets in exported named symbol table according to module address. + * @returns ResultCode RESULT_SUCCESS if all offsets are verified as valid, otherwise error code. + */ + ResultCode RebaseExportNamedSymbolTable() { + VAddr export_strings_offset = GetField(ExportStringsOffset); + VAddr export_strings_end = export_strings_offset + GetField(ExportStringsSize); + + u32 export_named_symbol_num = GetField(ExportNamedSymbolNum); + for (u32 i = 0; i < export_named_symbol_num; ++i) { + ExportNamedSymbolEntry entry; + GetEntry(i, entry); + + if (entry.name_offset) { + entry.name_offset += address; + if (entry.name_offset < export_strings_offset + || entry.name_offset >= export_strings_end) { + return CROFormatError(0x11); + } + } + + SetEntry(i, entry); + } + return RESULT_SUCCESS; + } + + /** + * Verifies indeces in export tree table. + * @returns ResultCode RESULT_SUCCESS if all indeces are verified as valid, otherwise error code. + */ + ResultCode VerifyExportTreeTable() const { + u32 tree_num = GetField(ExportTreeNum); + for (u32 i = 0; i < tree_num; ++i) { + ExportTreeEntry entry; + GetEntry(i, entry); + + if (entry.left.next_index >= tree_num || entry.right.next_index >= tree_num) { + return CROFormatError(0x11); + } + } + return RESULT_SUCCESS; + } + + /** + * Rebases offsets in exported module table according to module address. + * @returns ResultCode RESULT_SUCCESS if all offsets are verified as valid, otherwise error code. + */ + ResultCode RebaseImportModuleTable() { + VAddr import_strings_offset = GetField(ImportStringsOffset); + VAddr import_strings_end = import_strings_offset + GetField(ImportStringsSize); + VAddr import_indexed_symbol_table_offset = GetField(ImportIndexedSymbolTableOffset); + VAddr index_import_table_end = import_indexed_symbol_table_offset + GetField(ImportIndexedSymbolNum) * sizeof(ImportIndexedSymbolEntry); + VAddr import_anonymous_symbol_table_offset = GetField(ImportAnonymousSymbolTableOffset); + VAddr offset_import_table_end = import_anonymous_symbol_table_offset + GetField(ImportAnonymousSymbolNum) * sizeof(ImportAnonymousSymbolEntry); + + u32 module_num = GetField(ImportModuleNum); + for (u32 i = 0; i < module_num; ++i) { + ImportModuleEntry entry; + GetEntry(i, entry); + + if (entry.name_offset) { + entry.name_offset += address; + if (entry.name_offset < import_strings_offset + || entry.name_offset >= import_strings_end) { + return CROFormatError(0x18); + } + } + + if (entry.import_indexed_symbol_table_offset) { + entry.import_indexed_symbol_table_offset += address; + if (entry.import_indexed_symbol_table_offset < import_indexed_symbol_table_offset + || entry.import_indexed_symbol_table_offset > index_import_table_end) { + return CROFormatError(0x18); + } + } + + if (entry.import_anonymous_symbol_table_offset) { + entry.import_anonymous_symbol_table_offset += address; + if (entry.import_anonymous_symbol_table_offset < import_anonymous_symbol_table_offset + || entry.import_anonymous_symbol_table_offset > offset_import_table_end) { + return CROFormatError(0x18); + } + } + + SetEntry(i, entry); + } + return RESULT_SUCCESS; + } + + /** + * Rebases offsets in imported named symbol table according to module address. + * @returns ResultCode RESULT_SUCCESS if all offsets are verified as valid, otherwise error code. + */ + ResultCode RebaseImportNamedSymbolTable() { + VAddr import_strings_offset = GetField(ImportStringsOffset); + VAddr import_strings_end = import_strings_offset + GetField(ImportStringsSize); + VAddr external_patch_table_offset = GetField(ExternalPatchTableOffset); + VAddr external_patch_table_end = external_patch_table_offset + GetField(ExternalPatchNum) * sizeof(ExternalPatchEntry); + + u32 num = GetField(ImportNamedSymbolNum); + for (u32 i = 0; i < num ; ++i) { + ImportNamedSymbolEntry entry; + GetEntry(i, entry); + + if (entry.name_offset) { + entry.name_offset += address; + if (entry.name_offset < import_strings_offset + || entry.name_offset >= import_strings_end) { + return CROFormatError(0x1B); + } + } + + if (entry.patch_batch_offset) { + entry.patch_batch_offset += address; + if (entry.patch_batch_offset < external_patch_table_offset + || entry.patch_batch_offset > external_patch_table_end) { + return CROFormatError(0x1B); + } + } + + SetEntry(i, entry); + } + return RESULT_SUCCESS; + } + + /** + * Rebases offsets in imported indexed symbol table according to module address. + * @returns ResultCode RESULT_SUCCESS if all offsets are verified as valid, otherwise error code. + */ + ResultCode RebaseImportIndexedSymbolTable() { + VAddr external_patch_table_offset = GetField(ExternalPatchTableOffset); + VAddr external_patch_table_end = external_patch_table_offset + GetField(ExternalPatchNum) * sizeof(ExternalPatchEntry); + + u32 num = GetField(ImportIndexedSymbolNum); + for (u32 i = 0; i < num ; ++i) { + ImportIndexedSymbolEntry entry; + GetEntry(i, entry); + + if (entry.patch_batch_offset) { + entry.patch_batch_offset += address; + if (entry.patch_batch_offset < external_patch_table_offset + || entry.patch_batch_offset > external_patch_table_end) { + return CROFormatError(0x14); + } + } + + SetEntry(i, entry); + } + return RESULT_SUCCESS; + } + + /** + * Rebases offsets in imported anonymous symbol table according to module address. + * @returns ResultCode RESULT_SUCCESS if all offsets are verified as valid, otherwise error code. + */ + ResultCode RebaseImportAnonymousSymbolTable() { + VAddr external_patch_table_offset = GetField(ExternalPatchTableOffset); + VAddr external_patch_table_end = external_patch_table_offset + GetField(ExternalPatchNum) * sizeof(ExternalPatchEntry); + + u32 num = GetField(ImportAnonymousSymbolNum); + for (u32 i = 0; i < num ; ++i) { + ImportAnonymousSymbolEntry entry; + GetEntry(i, entry); + + if (entry.patch_batch_offset) { + entry.patch_batch_offset += address; + if (entry.patch_batch_offset < external_patch_table_offset + || entry.patch_batch_offset > external_patch_table_end) { + return CROFormatError(0x17); + } + } + + SetEntry(i, entry); + } + return RESULT_SUCCESS; + } + + /** + * Resets all external patches to unresolved state. + * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. + */ + ResultCode ResetExternalPatches() { + u32 unresolved_symbol = SegmentTagToAddress(GetField(OnUnresolvedSegmentTag)); + u32 external_patch_num = GetField(ExternalPatchNum); + ExternalPatchEntry patch; + + // Verifies that the last patch is the end of a batch + GetEntry(external_patch_num - 1, patch); + if (!patch.is_batch_end) { + return CROFormatError(0x12); + } + + bool batch_begin = true; + for (u32 i = 0; i < external_patch_num; ++i) { + GetEntry(i, patch); + VAddr patch_target = SegmentTagToAddress(patch.target_position); + + if (patch_target == 0) { + return CROFormatError(0x12); + } + + ResultCode result = ApplyPatch(patch_target, patch.type, patch.shift, unresolved_symbol, patch_target); + if (result.IsError()) { + LOG_ERROR(Service_LDR, "Error applying patch %08X", result.raw); + return result; + } + + if (batch_begin) { + // resets to unresolved state + patch.is_batch_resolved = 0; + SetEntry(i, patch); + } + + // if current is an end, then the next is a beginning + batch_begin = patch.is_batch_end != 0; + } + + return RESULT_SUCCESS; + } + + /** + * Applies all static anonymous symbol to the static module. + * @param crs_address the virtual address of the static module + * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. + */ + ResultCode ApplyStaticAnonymousSymbolToCRS(VAddr crs_address) { + VAddr static_patch_table_offset = GetField(StaticPatchTableOffset); + VAddr static_patch_table_end = static_patch_table_offset + GetField(StaticPatchNum) * sizeof(StaticPatchEntry); + + CROHelper crs(crs_address); + u32 offset_export_num = GetField(StaticAnonymousSymbolNum); + LOG_INFO(Service_LDR, "CRO \"%s\" exports %d static anonymous symbols", ModuleName().data(), offset_export_num); + for (u32 i = 0; i < offset_export_num; ++i) { + StaticAnonymousSymbolEntry entry; + GetEntry(i, entry); + u32 batch_address = entry.patch_batch_offset + address; + + if (batch_address < static_patch_table_offset + || batch_address > static_patch_table_end) { + return CROFormatError(0x16); + } + + u32 symbol_address = SegmentTagToAddress(entry.symbol_position); + LOG_TRACE(Service_LDR, "CRO \"%s\" exports 0x%08X to the static module", ModuleName().data(), symbol_address); + ResultCode result = crs.ApplyPatchBatch(batch_address, symbol_address); + if (result.IsError()) { + LOG_ERROR(Service_LDR, "Error applying patch batch %08X", result.raw); + return result; + } + } + return RESULT_SUCCESS; + } + + /** + * Applies all internal patches to the module itself. + * @param old_data_segment_address the virtual address of data segment in CRO buffer + * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. + */ + ResultCode ApplyInternalPatches(u32 old_data_segment_address) { + u32 segment_num = GetField(SegmentNum); + u32 internal_patch_num = GetField(InternalPatchNum); + for (u32 i = 0; i < internal_patch_num; ++i) { + InternalPatchEntry patch; + GetEntry(i, patch); + VAddr target_addressB = SegmentTagToAddress(patch.target_position); + if (target_addressB == 0) { + return CROFormatError(0x15); + } + + VAddr target_address; + SegmentEntry target_segment; + GetEntry(patch.target_position.segment_index, target_segment); + + if (target_segment.type == SegmentType::Data) { + // If the patch is to the .data segment, we need to patch it in the old buffer + target_address = old_data_segment_address + patch.target_position.offset_into_segment; + } else { + target_address = target_addressB; + } + + if (patch.symbol_segment >= segment_num) { + return CROFormatError(0x15); + } + + SegmentEntry symbol_segment; + GetEntry(patch.symbol_segment, symbol_segment); + LOG_TRACE(Service_LDR, "Internally patches 0x%08X with 0x%08X", target_address, symbol_segment.offset); + ResultCode result = ApplyPatch(target_address, patch.type, patch.shift, symbol_segment.offset, target_addressB); + if (result.IsError()) { + LOG_ERROR(Service_LDR, "Error applying patch %08X", result.raw); + return result; + } + } + return RESULT_SUCCESS; + } + + /** + * Resolves the exit function in this module + * @param crs_address the virtual address of the static module. + * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. + */ + ResultCode ApplyExitPatches(VAddr crs_address) { + u32 import_strings_size = GetField(ImportStringsSize); + u32 symbol_import_num = GetField(ImportNamedSymbolNum); + for (u32 i = 0; i < symbol_import_num; ++i) { + ImportNamedSymbolEntry entry; + GetEntry(i, entry); + VAddr patch_addr = entry.patch_batch_offset; + ExternalPatchEntry patch_entry; + Memory::ReadBlock(patch_addr, &patch_entry, sizeof(ExternalPatchEntry)); + + if (Memory::ReadCString(entry.name_offset, import_strings_size) == "__aeabi_atexit"){ + ResultCode result = ForEachAutoLinkCRO(crs_address, [&](CROHelper source) -> ResultVal { + u32 symbol_address = source.FindExportNamedSymbol("nnroAeabiAtexit_"); + + if (symbol_address) { + LOG_DEBUG(Service_LDR, "CRO \"%s\" import exit function from \"%s\"", + ModuleName().data(), source.ModuleName().data()); + + ResultCode result = ApplyPatchBatch(patch_addr, symbol_address); + if (result.IsError()) { + LOG_ERROR(Service_LDR, "Error applying patch batch %08X", result.raw); + return result; + } + + return MakeResult(false); + } + + return MakeResult(true); + }); + if (result.IsError()) { + LOG_ERROR(Service_LDR, "Error applying exit patch %08X", result.raw); + return result; + } + } + } + return RESULT_SUCCESS; + } + public: explicit CROHelper(VAddr cro_address) : address(cro_address) { } @@ -501,6 +1027,124 @@ public: return GetField(FileSize); } + /** + * Rebases the module according to its address. + * @param crs_address the virtual address of the static module + * @param cro_size the size of the CRO file + * @param data_segment_address buffer address for .data segment + * @param data_segment_size the buffer size for .data segment + * @param bss_segment_address the buffer address for .bss segment + * @param bss_segment_size the buffer size for .bss segment + * @param is_crs true if the module itself is the static module + * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. + */ + ResultCode Rebase(VAddr crs_address, u32 cro_size, + VAddr data_segment_addresss, u32 data_segment_size, + VAddr bss_segment_address, u32 bss_segment_size, bool is_crs) { + ResultCode result = RebaseHeader(cro_size); + if (result.IsError()) { + LOG_ERROR(Service_LDR, "Error rebasing header %08X", result.raw); + return result; + } + + result = VerifyString(GetField(ModuleNameOffset), GetField(ModuleNameSize)); + if (result.IsError()) { + LOG_ERROR(Service_LDR, "Error verifying module name %08X", result.raw); + return result; + } + + u32 prev_data_segment_address = 0; + if (!is_crs) { + auto result_val = RebaseSegmentTable(cro_size, + data_segment_addresss, data_segment_size, + bss_segment_address, bss_segment_size); + if (result_val.Failed()) { + LOG_ERROR(Service_LDR, "Error rebasing segment table %08X", result_val.Code().raw); + return result_val.Code(); + } + prev_data_segment_address = *result_val; + } + prev_data_segment_address += address; + + result = RebaseExportNamedSymbolTable(); + if (result.IsError()) { + LOG_ERROR(Service_LDR, "Error rebasing symbol export table %08X", result.raw); + return result; + } + + result = VerifyExportTreeTable(); + if (result.IsError()) { + LOG_ERROR(Service_LDR, "Error verifying export tree %08X", result.raw); + return result; + } + + result = VerifyString(GetField(ExportStringsOffset), GetField(ExportStringsSize)); + if (result.IsError()) { + LOG_ERROR(Service_LDR, "Error verifying export strings %08X", result.raw); + return result; + } + + result = RebaseImportModuleTable(); + if (result.IsError()) { + LOG_ERROR(Service_LDR, "Error rebasing object table %08X", result.raw); + return result; + } + + result = ResetExternalPatches(); + if (result.IsError()) { + LOG_ERROR(Service_LDR, "Error resetting all external patches %08X", result.raw); + return result; + } + + result = RebaseImportNamedSymbolTable(); + if (result.IsError()) { + LOG_ERROR(Service_LDR, "Error rebasing symbol import table %08X", result.raw); + return result; + } + + result = RebaseImportIndexedSymbolTable(); + if (result.IsError()) { + LOG_ERROR(Service_LDR, "Error rebasing index import table %08X", result.raw); + return result; + } + + result = RebaseImportAnonymousSymbolTable(); + if (result.IsError()) { + LOG_ERROR(Service_LDR, "Error rebasing offset import table %08X", result.raw); + return result; + } + + result = VerifyString(GetField(ImportStringsOffset), GetField(ImportStringsSize)); + if (result.IsError()) { + LOG_ERROR(Service_LDR, "Error verifying import strings %08X", result.raw); + return result; + } + + if (!is_crs) { + result = ApplyStaticAnonymousSymbolToCRS(crs_address); + if (result.IsError()) { + LOG_ERROR(Service_LDR, "Error applying offset export to CRS %08X", result.raw); + return result; + } + } + + result = ApplyInternalPatches(prev_data_segment_address); + if (result.IsError()) { + LOG_ERROR(Service_LDR, "Error applying internal patches %08X", result.raw); + return result; + } + + if (!is_crs) { + result = ApplyExitPatches(crs_address); + if (result.IsError()) { + LOG_ERROR(Service_LDR, "Error applying exit patches %08X", result.raw); + return result; + } + } + + return RESULT_SUCCESS; + } + void InitCRS() { SetNext(0); SetPrevious(0);