// Copyright 2016 Citra Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #include "common/alignment.h" #include "common/logging/log.h" #include "common/scope_exit.h" #include "core/hle/service/ldr_ro/cro_helper.h" namespace Service { namespace LDR { static const ResultCode ERROR_BUFFER_TOO_SMALL = // 0xE0E12C1F ResultCode(static_cast(31), ErrorModule::RO, ErrorSummary::InvalidArgument, ErrorLevel::Usage); static ResultCode CROFormatError(u32 description) { return ResultCode(static_cast(description), ErrorModule::RO, ErrorSummary::WrongArgument, ErrorLevel::Permanent); } const std::array CROHelper::ENTRY_SIZE{{ 1, // code 1, // data 1, // module name sizeof(SegmentEntry), sizeof(ExportNamedSymbolEntry), sizeof(ExportIndexedSymbolEntry), 1, // export strings sizeof(ExportTreeEntry), sizeof(ImportModuleEntry), sizeof(ExternalRelocationEntry), sizeof(ImportNamedSymbolEntry), sizeof(ImportIndexedSymbolEntry), sizeof(ImportAnonymousSymbolEntry), 1, // import strings sizeof(StaticAnonymousSymbolEntry), sizeof(InternalRelocationEntry), sizeof(StaticRelocationEntry), }}; const std::array CROHelper::FIX_BARRIERS{{ Fix0Barrier, Fix1Barrier, Fix2Barrier, Fix3Barrier, }}; VAddr CROHelper::SegmentTagToAddress(SegmentTag segment_tag) const { u32 segment_num = GetField(SegmentNum); if (segment_tag.segment_index >= segment_num) return 0; SegmentEntry entry; GetEntry(segment_tag.segment_index, entry); if (segment_tag.offset_into_segment >= entry.size) return 0; return entry.offset + segment_tag.offset_into_segment; } ResultCode CROHelper::ApplyRelocation(VAddr target_address, RelocationType relocation_type, u32 addend, u32 symbol_address, u32 target_future_address) { switch (relocation_type) { case RelocationType::Nothing: break; case RelocationType::AbsoluteAddress: case RelocationType::AbsoluteAddress2: Memory::Write32(target_address, symbol_address + addend); break; case RelocationType::RelativeAddress: Memory::Write32(target_address, symbol_address + addend - target_future_address); break; case RelocationType::ThumbBranch: case RelocationType::ArmBranch: case RelocationType::ModifyArmBranch: case RelocationType::AlignedRelativeAddress: // TODO(wwylele): implement other types UNIMPLEMENTED(); break; default: return CROFormatError(0x22); } return RESULT_SUCCESS; } ResultCode CROHelper::ClearRelocation(VAddr target_address, RelocationType relocation_type) { switch (relocation_type) { case RelocationType::Nothing: break; case RelocationType::AbsoluteAddress: case RelocationType::AbsoluteAddress2: case RelocationType::RelativeAddress: Memory::Write32(target_address, 0); break; case RelocationType::ThumbBranch: case RelocationType::ArmBranch: case RelocationType::ModifyArmBranch: case RelocationType::AlignedRelativeAddress: // TODO(wwylele): implement other types UNIMPLEMENTED(); break; default: return CROFormatError(0x22); } return RESULT_SUCCESS; } ResultCode CROHelper::ApplyRelocationBatch(VAddr batch, u32 symbol_address, bool reset) { if (symbol_address == 0 && !reset) return CROFormatError(0x10); VAddr relocation_address = batch; while (true) { RelocationEntry relocation; Memory::ReadBlock(relocation_address, &relocation, sizeof(RelocationEntry)); VAddr relocation_target = SegmentTagToAddress(relocation.target_position); if (relocation_target == 0) { return CROFormatError(0x12); } ResultCode result = ApplyRelocation(relocation_target, relocation.type, relocation.addend, symbol_address, relocation_target); if (result.IsError()) { LOG_ERROR(Service_LDR, "Error applying relocation %08X", result.raw); return result; } if (relocation.is_batch_end) break; relocation_address += sizeof(RelocationEntry); } RelocationEntry relocation; Memory::ReadBlock(batch, &relocation, sizeof(RelocationEntry)); relocation.is_batch_resolved = reset ? 0 : 1; Memory::WriteBlock(batch, &relocation, sizeof(RelocationEntry)); return RESULT_SUCCESS; } VAddr CROHelper::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); } ResultCode CROHelper::RebaseHeader(u32 cro_size) { ResultCode error = CROFormatError(0x11); // verifies magic if (GetField(Magic) != MAGIC_CRO0) return error; // verifies not registered if (GetField(NextCRO) != 0 || GetField(PreviousCRO) != 0) 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) != 0) 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, ExternalRelocationTableOffset, ImportNamedSymbolTableOffset, ImportIndexedSymbolTableOffset, ImportAnonymousSymbolTableOffset, ImportStringsOffset, StaticAnonymousSymbolTableOffset, InternalRelocationTableOffset, StaticRelocationTableOffset, 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 != 0) SetField(NameOffset, offset + module_address); for (int field = CodeOffset; field < Fix0Barrier; field += 2) { HeaderField header_field = static_cast(field); offset = GetField(header_field); if (offset != 0) SetField(header_field, offset + module_address); } // verifies everything is not beyond the buffer u32 file_end = module_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; } ResultVal CROHelper::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 != 0) { 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 != 0) { if (segment.size > bss_segment_size) return ERROR_BUFFER_TOO_SMALL; segment.offset = bss_segment_address; } } else if (segment.offset != 0) { segment.offset += module_address; if (segment.offset > module_address + cro_size) return CROFormatError(0x19); } SetEntry(i, segment); } return MakeResult(prev_data_segment + module_address); } ResultCode CROHelper::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 != 0) { entry.name_offset += module_address; if (entry.name_offset < export_strings_offset || entry.name_offset >= export_strings_end) { return CROFormatError(0x11); } } SetEntry(i, entry); } return RESULT_SUCCESS; } ResultCode CROHelper::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; } ResultCode CROHelper::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 != 0) { entry.name_offset += module_address; if (entry.name_offset < import_strings_offset || entry.name_offset >= import_strings_end) { return CROFormatError(0x18); } } if (entry.import_indexed_symbol_table_offset != 0) { entry.import_indexed_symbol_table_offset += module_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 != 0) { entry.import_anonymous_symbol_table_offset += module_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; } ResultCode CROHelper::RebaseImportNamedSymbolTable() { VAddr import_strings_offset = GetField(ImportStringsOffset); VAddr import_strings_end = import_strings_offset + GetField(ImportStringsSize); VAddr external_relocation_table_offset = GetField(ExternalRelocationTableOffset); VAddr external_relocation_table_end = external_relocation_table_offset + GetField(ExternalRelocationNum) * sizeof(ExternalRelocationEntry); u32 num = GetField(ImportNamedSymbolNum); for (u32 i = 0; i < num; ++i) { ImportNamedSymbolEntry entry; GetEntry(i, entry); if (entry.name_offset != 0) { entry.name_offset += module_address; if (entry.name_offset < import_strings_offset || entry.name_offset >= import_strings_end) { return CROFormatError(0x1B); } } if (entry.relocation_batch_offset != 0) { entry.relocation_batch_offset += module_address; if (entry.relocation_batch_offset < external_relocation_table_offset || entry.relocation_batch_offset > external_relocation_table_end) { return CROFormatError(0x1B); } } SetEntry(i, entry); } return RESULT_SUCCESS; } ResultCode CROHelper::RebaseImportIndexedSymbolTable() { VAddr external_relocation_table_offset = GetField(ExternalRelocationTableOffset); VAddr external_relocation_table_end = external_relocation_table_offset + GetField(ExternalRelocationNum) * sizeof(ExternalRelocationEntry); u32 num = GetField(ImportIndexedSymbolNum); for (u32 i = 0; i < num; ++i) { ImportIndexedSymbolEntry entry; GetEntry(i, entry); if (entry.relocation_batch_offset != 0) { entry.relocation_batch_offset += module_address; if (entry.relocation_batch_offset < external_relocation_table_offset || entry.relocation_batch_offset > external_relocation_table_end) { return CROFormatError(0x14); } } SetEntry(i, entry); } return RESULT_SUCCESS; } ResultCode CROHelper::RebaseImportAnonymousSymbolTable() { VAddr external_relocation_table_offset = GetField(ExternalRelocationTableOffset); VAddr external_relocation_table_end = external_relocation_table_offset + GetField(ExternalRelocationNum) * sizeof(ExternalRelocationEntry); u32 num = GetField(ImportAnonymousSymbolNum); for (u32 i = 0; i < num; ++i) { ImportAnonymousSymbolEntry entry; GetEntry(i, entry); if (entry.relocation_batch_offset != 0) { entry.relocation_batch_offset += module_address; if (entry.relocation_batch_offset < external_relocation_table_offset || entry.relocation_batch_offset > external_relocation_table_end) { return CROFormatError(0x17); } } SetEntry(i, entry); } return RESULT_SUCCESS; } VAddr CROHelper::GetOnUnresolvedAddress() { return SegmentTagToAddress(SegmentTag(GetField(OnUnresolvedSegmentTag))); } ResultCode CROHelper::ResetExternalRelocations() { u32 unresolved_symbol = GetOnUnresolvedAddress(); u32 external_relocation_num = GetField(ExternalRelocationNum); ExternalRelocationEntry relocation; // Verifies that the last relocation is the end of a batch GetEntry(external_relocation_num - 1, relocation); if (!relocation.is_batch_end) { return CROFormatError(0x12); } bool batch_begin = true; for (u32 i = 0; i < external_relocation_num; ++i) { GetEntry(i, relocation); VAddr relocation_target = SegmentTagToAddress(relocation.target_position); if (relocation_target == 0) { return CROFormatError(0x12); } ResultCode result = ApplyRelocation(relocation_target, relocation.type, relocation.addend, unresolved_symbol, relocation_target); if (result.IsError()) { LOG_ERROR(Service_LDR, "Error applying relocation %08X", result.raw); return result; } if (batch_begin) { // resets to unresolved state relocation.is_batch_resolved = 0; SetEntry(i, relocation); } // if current is an end, then the next is a beginning batch_begin = relocation.is_batch_end != 0; } return RESULT_SUCCESS; } ResultCode CROHelper::ClearExternalRelocations() { u32 external_relocation_num = GetField(ExternalRelocationNum); ExternalRelocationEntry relocation; bool batch_begin = true; for (u32 i = 0; i < external_relocation_num; ++i) { GetEntry(i, relocation); VAddr relocation_target = SegmentTagToAddress(relocation.target_position); if (relocation_target == 0) { return CROFormatError(0x12); } ResultCode result = ClearRelocation(relocation_target, relocation.type); if (result.IsError()) { LOG_ERROR(Service_LDR, "Error clearing relocation %08X", result.raw); return result; } if (batch_begin) { // resets to unresolved state relocation.is_batch_resolved = 0; SetEntry(i, relocation); } // if current is an end, then the next is a beginning batch_begin = relocation.is_batch_end != 0; } return RESULT_SUCCESS; } ResultCode CROHelper::ApplyStaticAnonymousSymbolToCRS(VAddr crs_address) { VAddr static_relocation_table_offset = GetField(StaticRelocationTableOffset); VAddr static_relocation_table_end = static_relocation_table_offset + GetField(StaticRelocationNum) * sizeof(StaticRelocationEntry); 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.relocation_batch_offset + module_address; if (batch_address < static_relocation_table_offset || batch_address > static_relocation_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.ApplyRelocationBatch(batch_address, symbol_address); if (result.IsError()) { LOG_ERROR(Service_LDR, "Error applying relocation batch %08X", result.raw); return result; } } return RESULT_SUCCESS; } ResultCode CROHelper::ApplyInternalRelocations(u32 old_data_segment_address) { u32 segment_num = GetField(SegmentNum); u32 internal_relocation_num = GetField(InternalRelocationNum); for (u32 i = 0; i < internal_relocation_num; ++i) { InternalRelocationEntry relocation; GetEntry(i, relocation); VAddr target_addressB = SegmentTagToAddress(relocation.target_position); if (target_addressB == 0) { return CROFormatError(0x15); } VAddr target_address; SegmentEntry target_segment; GetEntry(relocation.target_position.segment_index, target_segment); if (target_segment.type == SegmentType::Data) { // If the relocation is to the .data segment, we need to relocate it in the old buffer target_address = old_data_segment_address + relocation.target_position.offset_into_segment; } else { target_address = target_addressB; } if (relocation.symbol_segment >= segment_num) { return CROFormatError(0x15); } SegmentEntry symbol_segment; GetEntry(relocation.symbol_segment, symbol_segment); LOG_TRACE(Service_LDR, "Internally relocates 0x%08X with 0x%08X", target_address, symbol_segment.offset); ResultCode result = ApplyRelocation(target_address, relocation.type, relocation.addend, symbol_segment.offset, target_addressB); if (result.IsError()) { LOG_ERROR(Service_LDR, "Error applying relocation %08X", result.raw); return result; } } return RESULT_SUCCESS; } ResultCode CROHelper::ClearInternalRelocations() { u32 internal_relocation_num = GetField(InternalRelocationNum); for (u32 i = 0; i < internal_relocation_num; ++i) { InternalRelocationEntry relocation; GetEntry(i, relocation); VAddr target_address = SegmentTagToAddress(relocation.target_position); if (target_address == 0) { return CROFormatError(0x15); } ResultCode result = ClearRelocation(target_address, relocation.type); if (result.IsError()) { LOG_ERROR(Service_LDR, "Error clearing relocation %08X", result.raw); return result; } } return RESULT_SUCCESS; } void CROHelper::UnrebaseImportAnonymousSymbolTable() { u32 num = GetField(ImportAnonymousSymbolNum); for (u32 i = 0; i < num; ++i) { ImportAnonymousSymbolEntry entry; GetEntry(i, entry); if (entry.relocation_batch_offset != 0) { entry.relocation_batch_offset -= module_address; } SetEntry(i, entry); } } void CROHelper::UnrebaseImportIndexedSymbolTable() { u32 num = GetField(ImportIndexedSymbolNum); for (u32 i = 0; i < num; ++i) { ImportIndexedSymbolEntry entry; GetEntry(i, entry); if (entry.relocation_batch_offset != 0) { entry.relocation_batch_offset -= module_address; } SetEntry(i, entry); } } void CROHelper::UnrebaseImportNamedSymbolTable() { u32 num = GetField(ImportNamedSymbolNum); for (u32 i = 0; i < num; ++i) { ImportNamedSymbolEntry entry; GetEntry(i, entry); if (entry.name_offset != 0) { entry.name_offset -= module_address; } if (entry.relocation_batch_offset) { entry.relocation_batch_offset -= module_address; } SetEntry(i, entry); } } void CROHelper::UnrebaseImportModuleTable() { u32 module_num = GetField(ImportModuleNum); for (u32 i = 0; i < module_num; ++i) { ImportModuleEntry entry; GetEntry(i, entry); if (entry.name_offset != 0) { entry.name_offset -= module_address; } if (entry.import_indexed_symbol_table_offset) { entry.import_indexed_symbol_table_offset -= module_address; } if (entry.import_anonymous_symbol_table_offset) { entry.import_anonymous_symbol_table_offset -= module_address; } SetEntry(i, entry); } } void CROHelper::UnrebaseExportNamedSymbolTable() { 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 != 0) { entry.name_offset -= module_address; } SetEntry(i, entry); } } void CROHelper::UnrebaseSegmentTable() { u32 segment_num = GetField(SegmentNum); for (u32 i = 0; i < segment_num; ++i) { SegmentEntry segment; GetEntry(i, segment); if (segment.type == SegmentType::BSS) { segment.offset = 0; } else if (segment.offset != 0) { segment.offset -= module_address; } SetEntry(i, segment); } } void CROHelper::UnrebaseHeader() { u32 offset = GetField(NameOffset); if (offset != 0) SetField(NameOffset, offset - module_address); for (int field = CodeOffset; field < Fix0Barrier; field += 2) { HeaderField header_field = static_cast(field); offset = GetField(header_field); if (offset != 0) SetField(header_field, offset - module_address); } } ResultCode CROHelper::ApplyImportNamedSymbol(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 relocation_addr = entry.relocation_batch_offset; ExternalRelocationEntry relocation_entry; Memory::ReadBlock(relocation_addr, &relocation_entry, sizeof(ExternalRelocationEntry)); if (!relocation_entry.is_batch_resolved) { ResultCode result = ForEachAutoLinkCRO(crs_address, [&](CROHelper source) -> ResultVal { std::string symbol_name = Memory::ReadCString(entry.name_offset, import_strings_size); u32 symbol_address = source.FindExportNamedSymbol(symbol_name); if (symbol_address != 0) { LOG_TRACE(Service_LDR, "CRO \"%s\" imports \"%s\" from \"%s\"", ModuleName().data(), symbol_name.data(), source.ModuleName().data()); ResultCode result = ApplyRelocationBatch(relocation_addr, symbol_address); if (result.IsError()) { LOG_ERROR(Service_LDR, "Error applying relocation batch %08X", result.raw); return result; } return MakeResult(false); } return MakeResult(true); }); if (result.IsError()) { return result; } } } return RESULT_SUCCESS; } ResultCode CROHelper::ResetImportNamedSymbol() { u32 unresolved_symbol = GetOnUnresolvedAddress(); u32 symbol_import_num = GetField(ImportNamedSymbolNum); for (u32 i = 0; i < symbol_import_num; ++i) { ImportNamedSymbolEntry entry; GetEntry(i, entry); VAddr relocation_addr = entry.relocation_batch_offset; ExternalRelocationEntry relocation_entry; Memory::ReadBlock(relocation_addr, &relocation_entry, sizeof(ExternalRelocationEntry)); ResultCode result = ApplyRelocationBatch(relocation_addr, unresolved_symbol, true); if (result.IsError()) { LOG_ERROR(Service_LDR, "Error reseting relocation batch %08X", result.raw); return result; } } return RESULT_SUCCESS; } ResultCode CROHelper::ResetImportIndexedSymbol() { u32 unresolved_symbol = GetOnUnresolvedAddress(); u32 import_num = GetField(ImportIndexedSymbolNum); for (u32 i = 0; i < import_num; ++i) { ImportIndexedSymbolEntry entry; GetEntry(i, entry); VAddr relocation_addr = entry.relocation_batch_offset; ExternalRelocationEntry relocation_entry; Memory::ReadBlock(relocation_addr, &relocation_entry, sizeof(ExternalRelocationEntry)); ResultCode result = ApplyRelocationBatch(relocation_addr, unresolved_symbol, true); if (result.IsError()) { LOG_ERROR(Service_LDR, "Error reseting relocation batch %08X", result.raw); return result; } } return RESULT_SUCCESS; } ResultCode CROHelper::ResetImportAnonymousSymbol() { u32 unresolved_symbol = GetOnUnresolvedAddress(); u32 import_num = GetField(ImportAnonymousSymbolNum); for (u32 i = 0; i < import_num; ++i) { ImportAnonymousSymbolEntry entry; GetEntry(i, entry); VAddr relocation_addr = entry.relocation_batch_offset; ExternalRelocationEntry relocation_entry; Memory::ReadBlock(relocation_addr, &relocation_entry, sizeof(ExternalRelocationEntry)); ResultCode result = ApplyRelocationBatch(relocation_addr, unresolved_symbol, true); if (result.IsError()) { LOG_ERROR(Service_LDR, "Error reseting relocation batch %08X", result.raw); return result; } } return RESULT_SUCCESS; } ResultCode CROHelper::ApplyModuleImport(VAddr crs_address) { u32 import_strings_size = GetField(ImportStringsSize); u32 import_module_num = GetField(ImportModuleNum); for (u32 i = 0; i < import_module_num; ++i) { ImportModuleEntry entry; GetEntry(i, entry); std::string want_cro_name = Memory::ReadCString(entry.name_offset, import_strings_size); ResultCode result = ForEachAutoLinkCRO(crs_address, [&](CROHelper source) -> ResultVal { if (want_cro_name == source.ModuleName()) { LOG_INFO(Service_LDR, "CRO \"%s\" imports %d indexed symbols from \"%s\"", ModuleName().data(), entry.import_indexed_symbol_num, source.ModuleName().data()); for (u32 j = 0; j < entry.import_indexed_symbol_num; ++j) { ImportIndexedSymbolEntry im; entry.GetImportIndexedSymbolEntry(j, im); ExportIndexedSymbolEntry ex; source.GetEntry(im.index, ex); u32 symbol_address = source.SegmentTagToAddress(ex.symbol_position); LOG_TRACE(Service_LDR, " Imports 0x%08X", symbol_address); ResultCode result = ApplyRelocationBatch(im.relocation_batch_offset, symbol_address); if (result.IsError()) { LOG_ERROR(Service_LDR, "Error applying relocation batch %08X", result.raw); return result; } } LOG_INFO(Service_LDR, "CRO \"%s\" imports %d anonymous symbols from \"%s\"", ModuleName().data(), entry.import_anonymous_symbol_num, source.ModuleName().data()); for (u32 j = 0; j < entry.import_anonymous_symbol_num; ++j) { ImportAnonymousSymbolEntry im; entry.GetImportAnonymousSymbolEntry(j, im); u32 symbol_address = source.SegmentTagToAddress(im.symbol_position); LOG_TRACE(Service_LDR, " Imports 0x%08X", symbol_address); ResultCode result = ApplyRelocationBatch(im.relocation_batch_offset, symbol_address); if (result.IsError()) { LOG_ERROR(Service_LDR, "Error applying relocation batch %08X", result.raw); return result; } } return MakeResult(false); } return MakeResult(true); }); if (result.IsError()) { return result; } } return RESULT_SUCCESS; } ResultCode CROHelper::ApplyExportNamedSymbol(CROHelper target) { LOG_DEBUG(Service_LDR, "CRO \"%s\" exports named symbols to \"%s\"", ModuleName().data(), target.ModuleName().data()); u32 target_import_strings_size = target.GetField(ImportStringsSize); u32 target_symbol_import_num = target.GetField(ImportNamedSymbolNum); for (u32 i = 0; i < target_symbol_import_num; ++i) { ImportNamedSymbolEntry entry; target.GetEntry(i, entry); VAddr relocation_addr = entry.relocation_batch_offset; ExternalRelocationEntry relocation_entry; Memory::ReadBlock(relocation_addr, &relocation_entry, sizeof(ExternalRelocationEntry)); if (!relocation_entry.is_batch_resolved) { std::string symbol_name = Memory::ReadCString(entry.name_offset, target_import_strings_size); u32 symbol_address = FindExportNamedSymbol(symbol_name); if (symbol_address != 0) { LOG_TRACE(Service_LDR, " exports symbol \"%s\"", symbol_name.data()); ResultCode result = target.ApplyRelocationBatch(relocation_addr, symbol_address); if (result.IsError()) { LOG_ERROR(Service_LDR, "Error applying relocation batch %08X", result.raw); return result; } } } } return RESULT_SUCCESS; } ResultCode CROHelper::ResetExportNamedSymbol(CROHelper target) { LOG_DEBUG(Service_LDR, "CRO \"%s\" unexports named symbols to \"%s\"", ModuleName().data(), target.ModuleName().data()); u32 unresolved_symbol = target.GetOnUnresolvedAddress(); u32 target_import_strings_size = target.GetField(ImportStringsSize); u32 target_symbol_import_num = target.GetField(ImportNamedSymbolNum); for (u32 i = 0; i < target_symbol_import_num; ++i) { ImportNamedSymbolEntry entry; target.GetEntry(i, entry); VAddr relocation_addr = entry.relocation_batch_offset; ExternalRelocationEntry relocation_entry; Memory::ReadBlock(relocation_addr, &relocation_entry, sizeof(ExternalRelocationEntry)); if (relocation_entry.is_batch_resolved) { std::string symbol_name = Memory::ReadCString(entry.name_offset, target_import_strings_size); u32 symbol_address = FindExportNamedSymbol(symbol_name); if (symbol_address != 0) { LOG_TRACE(Service_LDR, " unexports symbol \"%s\"", symbol_name.data()); ResultCode result = target.ApplyRelocationBatch(relocation_addr, unresolved_symbol, true); if (result.IsError()) { LOG_ERROR(Service_LDR, "Error applying relocation batch %08X", result.raw); return result; } } } } return RESULT_SUCCESS; } ResultCode CROHelper::ApplyModuleExport(CROHelper target) { std::string module_name = ModuleName(); u32 target_import_string_size = target.GetField(ImportStringsSize); u32 target_import_module_num = target.GetField(ImportModuleNum); for (u32 i = 0; i < target_import_module_num; ++i) { ImportModuleEntry entry; target.GetEntry(i, entry); if (Memory::ReadCString(entry.name_offset, target_import_string_size) != module_name) continue; LOG_INFO(Service_LDR, "CRO \"%s\" exports %d indexed symbols to \"%s\"", module_name.data(), entry.import_indexed_symbol_num, target.ModuleName().data()); for (u32 j = 0; j < entry.import_indexed_symbol_num; ++j) { ImportIndexedSymbolEntry im; entry.GetImportIndexedSymbolEntry(j, im); ExportIndexedSymbolEntry ex; GetEntry(im.index, ex); u32 symbol_address = SegmentTagToAddress(ex.symbol_position); LOG_TRACE(Service_LDR, " exports symbol 0x%08X", symbol_address); ResultCode result = target.ApplyRelocationBatch(im.relocation_batch_offset, symbol_address); if (result.IsError()) { LOG_ERROR(Service_LDR, "Error applying relocation batch %08X", result.raw); return result; } } LOG_INFO(Service_LDR, "CRO \"%s\" exports %d anonymous symbols to \"%s\"", module_name.data(), entry.import_anonymous_symbol_num, target.ModuleName().data()); for (u32 j = 0; j < entry.import_anonymous_symbol_num; ++j) { ImportAnonymousSymbolEntry im; entry.GetImportAnonymousSymbolEntry(j, im); u32 symbol_address = SegmentTagToAddress(im.symbol_position); LOG_TRACE(Service_LDR, " exports symbol 0x%08X", symbol_address); ResultCode result = target.ApplyRelocationBatch(im.relocation_batch_offset, symbol_address); if (result.IsError()) { LOG_ERROR(Service_LDR, "Error applying relocation batch %08X", result.raw); return result; } } } return RESULT_SUCCESS; } ResultCode CROHelper::ResetModuleExport(CROHelper target) { u32 unresolved_symbol = target.GetOnUnresolvedAddress(); std::string module_name = ModuleName(); u32 target_import_string_size = target.GetField(ImportStringsSize); u32 target_import_module_num = target.GetField(ImportModuleNum); for (u32 i = 0; i < target_import_module_num; ++i) { ImportModuleEntry entry; target.GetEntry(i, entry); if (Memory::ReadCString(entry.name_offset, target_import_string_size) != module_name) continue; LOG_DEBUG(Service_LDR, "CRO \"%s\" unexports indexed symbols to \"%s\"", module_name.data(), target.ModuleName().data()); for (u32 j = 0; j < entry.import_indexed_symbol_num; ++j) { ImportIndexedSymbolEntry im; entry.GetImportIndexedSymbolEntry(j, im); ResultCode result = target.ApplyRelocationBatch(im.relocation_batch_offset, unresolved_symbol, true); if (result.IsError()) { LOG_ERROR(Service_LDR, "Error applying relocation batch %08X", result.raw); return result; } } LOG_DEBUG(Service_LDR, "CRO \"%s\" unexports anonymous symbols to \"%s\"", module_name.data(), target.ModuleName().data()); for (u32 j = 0; j < entry.import_anonymous_symbol_num; ++j) { ImportAnonymousSymbolEntry im; entry.GetImportAnonymousSymbolEntry(j, im); ResultCode result = target.ApplyRelocationBatch(im.relocation_batch_offset, unresolved_symbol, true); if (result.IsError()) { LOG_ERROR(Service_LDR, "Error applying relocation batch %08X", result.raw); return result; } } } return RESULT_SUCCESS; } ResultCode CROHelper::ApplyExitRelocations(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 relocation_addr = entry.relocation_batch_offset; ExternalRelocationEntry relocation_entry; Memory::ReadBlock(relocation_addr, &relocation_entry, sizeof(ExternalRelocationEntry)); 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 != 0) { LOG_DEBUG(Service_LDR, "CRO \"%s\" import exit function from \"%s\"", ModuleName().data(), source.ModuleName().data()); ResultCode result = ApplyRelocationBatch(relocation_addr, symbol_address); if (result.IsError()) { LOG_ERROR(Service_LDR, "Error applying relocation batch %08X", result.raw); return result; } return MakeResult(false); } return MakeResult(true); }); if (result.IsError()) { LOG_ERROR(Service_LDR, "Error applying exit relocation %08X", result.raw); return result; } } } return RESULT_SUCCESS; } /** * Verifies a string or a string table matching a predicted size (i.e. terminated by 0) * if it is not empty. There can be many other nulls in the string table because * they are composed by many sub strings. This function is to check whether the * whole string (table) is terminated properly, despite that it is not actually one string. * @param address the virtual address of the string (table) * @param size the size of the string (table), including the terminating 0 * @returns ResultCode RESULT_SUCCESS if the size matches, otherwise error code. */ static ResultCode VerifyStringTableLength(VAddr address, u32 size) { if (size != 0) { if (Memory::Read8(address + size - 1) != 0) return CROFormatError(0x0B); } return RESULT_SUCCESS; } ResultCode CROHelper::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 = VerifyStringTableLength(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; } 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 = VerifyStringTableLength(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 = ResetExternalRelocations(); if (result.IsError()) { LOG_ERROR(Service_LDR, "Error resetting all external relocations %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 = VerifyStringTableLength(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 = ApplyInternalRelocations(prev_data_segment_address); if (result.IsError()) { LOG_ERROR(Service_LDR, "Error applying internal relocations %08X", result.raw); return result; } if (!is_crs) { result = ApplyExitRelocations(crs_address); if (result.IsError()) { LOG_ERROR(Service_LDR, "Error applying exit relocations %08X", result.raw); return result; } } return RESULT_SUCCESS; } void CROHelper::Unrebase(bool is_crs) { UnrebaseImportAnonymousSymbolTable(); UnrebaseImportIndexedSymbolTable(); UnrebaseImportNamedSymbolTable(); UnrebaseImportModuleTable(); UnrebaseExportNamedSymbolTable(); if (!is_crs) UnrebaseSegmentTable(); SetNextModule(0); SetPreviousModule(0); SetField(FixedSize, 0); UnrebaseHeader(); } ResultCode CROHelper::VerifyHash(u32 cro_size, VAddr crr) const { // TODO(wwylele): actually verify the hash return RESULT_SUCCESS; } ResultCode CROHelper::Link(VAddr crs_address, bool link_on_load_bug_fix) { ResultCode result = RESULT_SUCCESS; { VAddr data_segment_address; if (link_on_load_bug_fix) { // this is a bug fix introduced by 7.2.0-17's LoadCRO_New // The bug itself is: // If a relocation target is in .data segment, it will relocate to the // user-specified buffer. But if this is linking during loading, // the .data segment hasn't been transfer from CRO to the buffer, // thus the relocation will be overwritten by data transfer. // To fix this bug, we need temporarily restore the old .data segment // offset and apply imported symbols. // RO service seems assuming segment_index == segment_type, // so we do the same if (GetField(SegmentNum) >= 2) { // means we have .data segment SegmentEntry entry; GetEntry(2, entry); ASSERT(entry.type == SegmentType::Data); data_segment_address = entry.offset; entry.offset = GetField(DataOffset); SetEntry(2, entry); } } SCOPE_EXIT({ // Restore the new .data segment address after importing if (link_on_load_bug_fix) { if (GetField(SegmentNum) >= 2) { SegmentEntry entry; GetEntry(2, entry); entry.offset = data_segment_address; SetEntry(2, entry); } } }); // Imports named symbols from other modules result = ApplyImportNamedSymbol(crs_address); if (result.IsError()) { LOG_ERROR(Service_LDR, "Error applying symbol import %08X", result.raw); return result; } // Imports indexed and anonymous symbols from other modules result = ApplyModuleImport(crs_address); if (result.IsError()) { LOG_ERROR(Service_LDR, "Error applying module import %08X", result.raw); return result; } } // Exports symbols to other modules result = ForEachAutoLinkCRO(crs_address, [this](CROHelper target) -> ResultVal { ResultCode result = ApplyExportNamedSymbol(target); if (result.IsError()) return result; result = ApplyModuleExport(target); if (result.IsError()) return result; return MakeResult(true); }); if (result.IsError()) { LOG_ERROR(Service_LDR, "Error applying export %08X", result.raw); return result; } return RESULT_SUCCESS; } ResultCode CROHelper::Unlink(VAddr crs_address) { // Resets all imported named symbols ResultCode result = ResetImportNamedSymbol(); if (result.IsError()) { LOG_ERROR(Service_LDR, "Error resetting symbol import %08X", result.raw); return result; } // Resets all imported indexed symbols result = ResetImportIndexedSymbol(); if (result.IsError()) { LOG_ERROR(Service_LDR, "Error resetting indexed import %08X", result.raw); return result; } // Resets all imported anonymous symbols result = ResetImportAnonymousSymbol(); if (result.IsError()) { LOG_ERROR(Service_LDR, "Error resetting anonymous import %08X", result.raw); return result; } // Resets all symbols in other modules imported from this module // Note: the RO service seems only searching in auto-link modules result = ForEachAutoLinkCRO(crs_address, [this](CROHelper target) -> ResultVal { ResultCode result = ResetExportNamedSymbol(target); if (result.IsError()) return result; result = ResetModuleExport(target); if (result.IsError()) return result; return MakeResult(true); }); if (result.IsError()) { LOG_ERROR(Service_LDR, "Error resetting export %08X", result.raw); return result; } return RESULT_SUCCESS; } ResultCode CROHelper::ClearRelocations() { ResultCode result = ClearExternalRelocations(); if (result.IsError()) { LOG_ERROR(Service_LDR, "Error clearing external relocations %08X", result.raw); return result; } result = ClearInternalRelocations(); if (result.IsError()) { LOG_ERROR(Service_LDR, "Error clearing internal relocations %08X", result.raw); return result; } return RESULT_SUCCESS; } void CROHelper::InitCRS() { SetNextModule(0); SetPreviousModule(0); } void CROHelper::Register(VAddr crs_address, bool auto_link) { CROHelper crs(crs_address); CROHelper head(auto_link ? crs.NextModule() : crs.PreviousModule()); if (head.module_address) { // there are already CROs registered // register as the new tail CROHelper tail(head.PreviousModule()); // link with the old tail ASSERT(tail.NextModule() == 0); SetPreviousModule(tail.module_address); tail.SetNextModule(module_address); // set previous of the head pointing to the new tail head.SetPreviousModule(module_address); } else { // register as the first CRO // set previous to self as tail SetPreviousModule(module_address); // set self as head if (auto_link) crs.SetNextModule(module_address); else crs.SetPreviousModule(module_address); } // the new one is the tail SetNextModule(0); } void CROHelper::Unregister(VAddr crs_address) { CROHelper crs(crs_address); CROHelper next_head(crs.NextModule()), previous_head(crs.PreviousModule()); CROHelper next(NextModule()), previous(PreviousModule()); if (module_address == next_head.module_address || module_address == previous_head.module_address) { // removing head if (next.module_address) { // the next is new head // let its previous point to the tail next.SetPreviousModule(previous.module_address); } // set new head if (module_address == previous_head.module_address) { crs.SetPreviousModule(next.module_address); } else { crs.SetNextModule(next.module_address); } } else if (next.module_address) { // link previous and next previous.SetNextModule(next.module_address); next.SetPreviousModule(previous.module_address); } else { // removing tail // set previous as new tail previous.SetNextModule(0); // let head's previous point to the new tail if (next_head.module_address && next_head.PreviousModule() == module_address) { next_head.SetPreviousModule(previous.module_address); } else if (previous_head.module_address && previous_head.PreviousModule() == module_address) { previous_head.SetPreviousModule(previous.module_address); } else { UNREACHABLE(); } } // unlink self SetNextModule(0); SetPreviousModule(0); } u32 CROHelper::GetFixEnd(u32 fix_level) const { u32 end = CRO_HEADER_SIZE; end = std::max(end, GetField(CodeOffset) + GetField(CodeSize)); u32 entry_size_i = 2; int field = ModuleNameOffset; while (true) { end = std::max(end, GetField(static_cast(field)) + GetField(static_cast(field + 1)) * ENTRY_SIZE[entry_size_i]); ++entry_size_i; field += 2; if (field == FIX_BARRIERS[fix_level]) return end; } } u32 CROHelper::Fix(u32 fix_level) { u32 fix_end = GetFixEnd(fix_level); if (fix_level != 0) { SetField(Magic, MAGIC_FIXD); for (int field = FIX_BARRIERS[fix_level]; field < Fix0Barrier; field += 2) { SetField(static_cast(field), fix_end); SetField(static_cast(field + 1), 0); } } fix_end = Common::AlignUp(fix_end, Memory::PAGE_SIZE); u32 fixed_size = fix_end - module_address; SetField(FixedSize, fixed_size); return fixed_size; } bool CROHelper::IsLoaded() const { u32 magic = GetField(Magic); if (magic != MAGIC_CRO0 && magic != MAGIC_FIXD) return false; // TODO(wwylele): verify memory state here after memory aliasing is implemented return true; } std::tuple CROHelper::GetExecutablePages() const { u32 segment_num = GetField(SegmentNum); for (u32 i = 0; i < segment_num; ++i) { SegmentEntry entry; GetEntry(i, entry); if (entry.type == SegmentType::Code && entry.size != 0) { VAddr begin = Common::AlignDown(entry.offset, Memory::PAGE_SIZE); VAddr end = Common::AlignUp(entry.offset + entry.size, Memory::PAGE_SIZE); return std::make_tuple(begin, end - begin); } } return std::make_tuple(0, 0); } } // namespace LDR } // namespace Service