diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 0773339a9..174e9dc79 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -95,7 +95,9 @@ set(SRCS hle/service/ir/ir_rst.cpp hle/service/ir/ir_u.cpp hle/service/ir/ir_user.cpp - hle/service/ldr_ro.cpp + hle/service/ldr_ro/cro_helper.cpp + hle/service/ldr_ro/ldr_ro.cpp + hle/service/ldr_ro/memory_synchronizer.cpp hle/service/mic_u.cpp hle/service/ndm/ndm.cpp hle/service/ndm/ndm_u.cpp @@ -238,7 +240,9 @@ set(HEADERS hle/service/ir/ir_rst.h hle/service/ir/ir_u.h hle/service/ir/ir_user.h - hle/service/ldr_ro.h + hle/service/ldr_ro/cro_helper.h + hle/service/ldr_ro/ldr_ro.h + hle/service/ldr_ro/memory_synchronizer.h hle/service/mic_u.h hle/service/ndm/ndm.h hle/service/ndm/ndm_u.h diff --git a/src/core/arm/arm_interface.h b/src/core/arm/arm_interface.h index d8abe5aeb..de5e9c8fa 100644 --- a/src/core/arm/arm_interface.h +++ b/src/core/arm/arm_interface.h @@ -32,6 +32,9 @@ public: Run(1); } + /// Clear all instruction cache + virtual void ClearInstructionCache() = 0; + /** * Set the Program Counter to an address * @param addr Address to set PC to diff --git a/src/core/arm/dyncom/arm_dyncom.cpp b/src/core/arm/dyncom/arm_dyncom.cpp index 13492a08b..ab77da965 100644 --- a/src/core/arm/dyncom/arm_dyncom.cpp +++ b/src/core/arm/dyncom/arm_dyncom.cpp @@ -12,6 +12,7 @@ #include "core/arm/dyncom/arm_dyncom.h" #include "core/arm/dyncom/arm_dyncom_interpreter.h" #include "core/arm/dyncom/arm_dyncom_run.h" +#include "core/arm/dyncom/arm_dyncom_trans.h" #include "core/core.h" #include "core/core_timing.h" @@ -23,6 +24,11 @@ ARM_DynCom::ARM_DynCom(PrivilegeMode initial_mode) { ARM_DynCom::~ARM_DynCom() { } +void ARM_DynCom::ClearInstructionCache() { + state->instruction_cache.clear(); + trans_cache_buf_top = 0; +} + void ARM_DynCom::SetPC(u32 pc) { state->Reg[15] = pc; } diff --git a/src/core/arm/dyncom/arm_dyncom.h b/src/core/arm/dyncom/arm_dyncom.h index 3664fd728..e763abc24 100644 --- a/src/core/arm/dyncom/arm_dyncom.h +++ b/src/core/arm/dyncom/arm_dyncom.h @@ -21,6 +21,8 @@ public: ARM_DynCom(PrivilegeMode initial_mode); ~ARM_DynCom(); + void ClearInstructionCache() override; + void SetPC(u32 pc) override; u32 GetPC() const override; u32 GetReg(int index) const override; diff --git a/src/core/hle/service/ldr_ro.cpp b/src/core/hle/service/ldr_ro.cpp deleted file mode 100644 index ecec2ce32..000000000 --- a/src/core/hle/service/ldr_ro.cpp +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include "common/common_types.h" -#include "common/logging/log.h" - -#include "core/hle/service/ldr_ro.h" - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Namespace LDR_RO - -namespace LDR_RO { - -/** - * LDR_RO::Initialize service function - * Inputs: - * 1 : CRS buffer pointer - * 2 : CRS Size - * 3 : Process memory address where the CRS will be mapped - * 4 : Value, must be zero - * 5 : KProcess handle - * Outputs: - * 0 : Return header - * 1 : Result of function, 0 on success, otherwise error code - */ -static void Initialize(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 crs_buffer_ptr = cmd_buff[1]; - u32 crs_size = cmd_buff[2]; - u32 address = cmd_buff[3]; - u32 value = cmd_buff[4]; - u32 process = cmd_buff[5]; - - if (value != 0) { - LOG_ERROR(Service_LDR, "This value should be zero, but is actually %u!", value); - } - - // TODO(purpasmart96): Verify return header on HW - - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - - LOG_WARNING(Service_LDR, "(STUBBED) called. crs_buffer_ptr=0x%08X, crs_size=0x%08X, address=0x%08X, value=0x%08X, process=0x%08X", - crs_buffer_ptr, crs_size, address, value, process); -} - -/** - * LDR_RO::LoadCRR service function - * Inputs: - * 1 : CRS buffer pointer - * 2 : CRS Size - * 3 : Value, must be zero - * 4 : KProcess handle - * Outputs: - * 0 : Return header - * 1 : Result of function, 0 on success, otherwise error code - */ -static void LoadCRR(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 crs_buffer_ptr = cmd_buff[1]; - u32 crs_size = cmd_buff[2]; - u32 value = cmd_buff[3]; - u32 process = cmd_buff[4]; - - if (value != 0) { - LOG_ERROR(Service_LDR, "This value should be zero, but is actually %u!", value); - } - - // TODO(purpasmart96): Verify return header on HW - - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - - LOG_WARNING(Service_LDR, "(STUBBED) called. crs_buffer_ptr=0x%08X, crs_size=0x%08X, value=0x%08X, process=0x%08X", - crs_buffer_ptr, crs_size, value, process); -} - -const Interface::FunctionInfo FunctionTable[] = { - {0x000100C2, Initialize, "Initialize"}, - {0x00020082, LoadCRR, "LoadCRR"}, - {0x00030042, nullptr, "UnloadCCR"}, - {0x000402C2, nullptr, "LoadExeCRO"}, - {0x000500C2, nullptr, "LoadCROSymbols"}, - {0x00060042, nullptr, "CRO_Load?"}, - {0x00070042, nullptr, "LoadCROSymbols"}, - {0x00080042, nullptr, "Shutdown"}, - {0x000902C2, nullptr, "LoadExeCRO_New?"}, -}; - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Interface class - -Interface::Interface() { - Register(FunctionTable); -} - -} // namespace diff --git a/src/core/hle/service/ldr_ro/cro_helper.cpp b/src/core/hle/service/ldr_ro/cro_helper.cpp new file mode 100644 index 000000000..3d2a613ee --- /dev/null +++ b/src/core/hle/service/ldr_ro/cro_helper.cpp @@ -0,0 +1,1477 @@ +// 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 LDR_RO + +namespace LDR_RO { + +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 tranfer 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 diff --git a/src/core/hle/service/ldr_ro/cro_helper.h b/src/core/hle/service/ldr_ro/cro_helper.h new file mode 100644 index 000000000..34e357afd --- /dev/null +++ b/src/core/hle/service/ldr_ro/cro_helper.h @@ -0,0 +1,691 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include + +#include "common/common_types.h" +#include "common/swap.h" + +#include "core/memory.h" +#include "core/hle/result.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Namespace LDR_RO + +namespace LDR_RO { + +// GCC versions < 5.0 do not implement std::is_trivially_copyable. +// Excluding MSVC because it has weird behaviour for std::is_trivially_copyable. +#if (__GNUC__ >= 5) || defined(__clang__) + #define ASSERT_CRO_STRUCT(name, size) \ + static_assert(std::is_standard_layout::value, "CRO structure " #name " doesn't use standard layout"); \ + static_assert(std::is_trivially_copyable::value, "CRO structure " #name " isn't trivially copyable"); \ + static_assert(sizeof(name) == (size), "Unexpected struct size for CRO structure " #name) +#else + #define ASSERT_CRO_STRUCT(name, size) \ + static_assert(std::is_standard_layout::value, "CRO structure " #name " doesn't use standard layout"); \ + static_assert(sizeof(name) == (size), "Unexpected struct size for CRO structure " #name) +#endif + +static constexpr u32 CRO_HEADER_SIZE = 0x138; +static constexpr u32 CRO_HASH_SIZE = 0x80; + +/// Represents a loaded module (CRO) with interfaces manipulating it. +class CROHelper final { +public: + explicit CROHelper(VAddr cro_address) : module_address(cro_address) { + } + + std::string ModuleName() const { + return Memory::ReadCString(GetField(ModuleNameOffset), GetField(ModuleNameSize)); + } + + u32 GetFileSize() const { + 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); + + /** + * Unrebases the module. + * @param is_crs true if the module itself is the static module + */ + void Unrebase(bool is_crs); + + /** + * Verifies module hash by CRR. + * @param cro_size the size of the CRO + * @param crr the virtual address of the CRR + * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. + */ + ResultCode VerifyHash(u32 cro_size, VAddr crr) const; + + /** + * Links this module with all registered auto-link module. + * @param crs_address the virtual address of the static module + * @param link_on_load_bug_fix true if links when loading and fixes the bug + * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. + */ + ResultCode Link(VAddr crs_address, bool link_on_load_bug_fix); + + /** + * Unlinks this module with other modules. + * @param crs_address the virtual address of the static module + * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. + */ + ResultCode Unlink(VAddr crs_address); + + /** + * Clears all relocations to zero. + * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. + */ + ResultCode ClearRelocations(); + + /// Initialize this module as the static module (CRS) + void InitCRS(); + + /** + * Registers this module and adds it to the module list. + * @param crs_address the virtual address of the static module + * @auto_link whether to register as an auto link module + */ + void Register(VAddr crs_address, bool auto_link); + + /** + * Unregisters this module and removes from the module list. + * @param crs_address the virtual address of the static module + */ + void Unregister(VAddr crs_address); + + /** + * Gets the end of reserved data according to the fix level. + * @param fix_level fix level from 0 to 3 + * @returns the end of reserved data. + */ + u32 GetFixEnd(u32 fix_level) const; + + /** + * Zeros offsets to cropped data according to the fix level and marks as fixed. + * @param fix_level fix level from 0 to 3 + * @returns page-aligned size of the module after fixing. + */ + u32 Fix(u32 fix_level); + + bool IsFixed() const { + return GetField(Magic) == MAGIC_FIXD; + } + + u32 GetFixedSize() const { + return GetField(FixedSize); + } + + bool IsLoaded() const; + + /** + * Gets the page address and size of the code segment. + * @returns a tuple of (address, size); (0, 0) if the code segment doesn't exist. + */ + std::tuple GetExecutablePages() const; + +private: + const VAddr module_address; ///< the virtual address of this module + + /** + * Each item in this enum represents a u32 field in the header begin from address+0x80, successively. + * We don't directly use a struct here, to avoid GetPointer, reinterpret_cast, or Read/WriteBlock repeatedly. + */ + enum HeaderField { + Magic = 0, + NameOffset, + NextCRO, + PreviousCRO, + FileSize, + BssSize, + FixedSize, + UnknownZero, + UnkSegmentTag, + OnLoadSegmentTag, + OnExitSegmentTag, + OnUnresolvedSegmentTag, + + CodeOffset, + CodeSize, + DataOffset, + DataSize, + ModuleNameOffset, + ModuleNameSize, + SegmentTableOffset, + SegmentNum, + + ExportNamedSymbolTableOffset, + ExportNamedSymbolNum, + ExportIndexedSymbolTableOffset, + ExportIndexedSymbolNum, + ExportStringsOffset, + ExportStringsSize, + ExportTreeTableOffset, + ExportTreeNum, + + ImportModuleTableOffset, + ImportModuleNum, + ExternalRelocationTableOffset, + ExternalRelocationNum, + ImportNamedSymbolTableOffset, + ImportNamedSymbolNum, + ImportIndexedSymbolTableOffset, + ImportIndexedSymbolNum, + ImportAnonymousSymbolTableOffset, + ImportAnonymousSymbolNum, + ImportStringsOffset, + ImportStringsSize, + + StaticAnonymousSymbolTableOffset, + StaticAnonymousSymbolNum, + InternalRelocationTableOffset, + InternalRelocationNum, + StaticRelocationTableOffset, + StaticRelocationNum, + Fix0Barrier, + + Fix3Barrier = ExportNamedSymbolTableOffset, + Fix2Barrier = ImportModuleTableOffset, + Fix1Barrier = StaticAnonymousSymbolTableOffset, + }; + static_assert(Fix0Barrier == (CRO_HEADER_SIZE - CRO_HASH_SIZE) / 4, "CRO Header fields are wrong!"); + + enum class SegmentType : u32 { + Code = 0, + ROData = 1, + Data = 2, + BSS = 3, + }; + + /** + * Identifies a program location inside of a segment. + * Required to refer to program locations because individual segments may be relocated independently of each other. + */ + union SegmentTag { + u32_le raw; + BitField<0, 4, u32_le> segment_index; + BitField<4, 28, u32_le> offset_into_segment; + + SegmentTag() = default; + explicit SegmentTag(u32 raw_) : raw(raw_) {} + }; + + /// Information of a segment in this module. + struct SegmentEntry { + u32_le offset; + u32_le size; + SegmentType type; + + static constexpr HeaderField TABLE_OFFSET_FIELD = SegmentTableOffset; + }; + ASSERT_CRO_STRUCT(SegmentEntry, 12); + + /// Identifies a named symbol exported from this module. + struct ExportNamedSymbolEntry { + u32_le name_offset; // pointing to a substring in ExportStrings + SegmentTag symbol_position; // to self's segment + + static constexpr HeaderField TABLE_OFFSET_FIELD = ExportNamedSymbolTableOffset; + }; + ASSERT_CRO_STRUCT(ExportNamedSymbolEntry, 8); + + /// Identifies an indexed symbol exported from this module. + struct ExportIndexedSymbolEntry { + SegmentTag symbol_position; // to self's segment + + static constexpr HeaderField TABLE_OFFSET_FIELD = ExportIndexedSymbolTableOffset; + }; + ASSERT_CRO_STRUCT(ExportIndexedSymbolEntry, 4); + + /// A tree node in the symbol lookup tree. + struct ExportTreeEntry { + u16_le test_bit; // bit address into the name to test + union Child { + u16_le raw; + BitField<0, 15, u16_le> next_index; + BitField<15, 1, u16_le> is_end; + } left, right; + u16_le export_table_index; // index of an ExportNamedSymbolEntry + + static constexpr HeaderField TABLE_OFFSET_FIELD = ExportTreeTableOffset; + }; + ASSERT_CRO_STRUCT(ExportTreeEntry, 8); + + /// Identifies a named symbol imported from another module. + struct ImportNamedSymbolEntry { + u32_le name_offset; // pointing to a substring in ImportStrings + u32_le relocation_batch_offset; // pointing to a relocation batch in ExternalRelocationTable + + static constexpr HeaderField TABLE_OFFSET_FIELD = ImportNamedSymbolTableOffset; + }; + ASSERT_CRO_STRUCT(ImportNamedSymbolEntry, 8); + + /// Identifies an indexed symbol imported from another module. + struct ImportIndexedSymbolEntry { + u32_le index; // index of an ExportIndexedSymbolEntry in the exporting module + u32_le relocation_batch_offset; // pointing to a relocation batch in ExternalRelocationTable + + static constexpr HeaderField TABLE_OFFSET_FIELD = ImportIndexedSymbolTableOffset; + }; + ASSERT_CRO_STRUCT(ImportIndexedSymbolEntry, 8); + + /// Identifies an anonymous symbol imported from another module. + struct ImportAnonymousSymbolEntry { + SegmentTag symbol_position; // in the exporting segment + u32_le relocation_batch_offset; // pointing to a relocation batch in ExternalRelocationTable + + static constexpr HeaderField TABLE_OFFSET_FIELD = ImportAnonymousSymbolTableOffset; + }; + ASSERT_CRO_STRUCT(ImportAnonymousSymbolEntry, 8); + + /// Information of a imported module and symbols imported from it. + struct ImportModuleEntry { + u32_le name_offset; // pointing to a substring in ImportStrings + u32_le import_indexed_symbol_table_offset; // pointing to a subtable in ImportIndexedSymbolTable + u32_le import_indexed_symbol_num; + u32_le import_anonymous_symbol_table_offset; // pointing to a subtable in ImportAnonymousSymbolTable + u32_le import_anonymous_symbol_num; + + static constexpr HeaderField TABLE_OFFSET_FIELD = ImportModuleTableOffset; + + void GetImportIndexedSymbolEntry(u32 index, ImportIndexedSymbolEntry& entry) { + Memory::ReadBlock(import_indexed_symbol_table_offset + index * sizeof(ImportIndexedSymbolEntry), + &entry, sizeof(ImportIndexedSymbolEntry)); + } + + void GetImportAnonymousSymbolEntry(u32 index, ImportAnonymousSymbolEntry& entry) { + Memory::ReadBlock(import_anonymous_symbol_table_offset + index * sizeof(ImportAnonymousSymbolEntry), + &entry, sizeof(ImportAnonymousSymbolEntry)); + } + }; + ASSERT_CRO_STRUCT(ImportModuleEntry, 20); + + enum class RelocationType : u8 { + Nothing = 0, + AbsoluteAddress = 2, + RelativeAddress = 3, + ThumbBranch = 10, + ArmBranch = 28, + ModifyArmBranch = 29, + AbsoluteAddress2 = 38, + AlignedRelativeAddress = 42, + }; + + struct RelocationEntry { + SegmentTag target_position; // to self's segment as an ExternalRelocationEntry; to static module segment as a StaticRelocationEntry + RelocationType type; + u8 is_batch_end; + u8 is_batch_resolved; // set at a batch beginning if the batch is resolved + INSERT_PADDING_BYTES(1); + u32_le addend; + }; + + /// Identifies a normal cross-module relocation. + struct ExternalRelocationEntry : RelocationEntry { + static constexpr HeaderField TABLE_OFFSET_FIELD = ExternalRelocationTableOffset; + }; + ASSERT_CRO_STRUCT(ExternalRelocationEntry, 12); + + /// Identifies a special static relocation (no game is known using this). + struct StaticRelocationEntry : RelocationEntry { + static constexpr HeaderField TABLE_OFFSET_FIELD = StaticRelocationTableOffset; + }; + ASSERT_CRO_STRUCT(StaticRelocationEntry, 12); + + /// Identifies a in-module relocation. + struct InternalRelocationEntry { + SegmentTag target_position; // to self's segment + RelocationType type; + u8 symbol_segment; + INSERT_PADDING_BYTES(2); + u32_le addend; + + static constexpr HeaderField TABLE_OFFSET_FIELD = InternalRelocationTableOffset; + }; + ASSERT_CRO_STRUCT(InternalRelocationEntry, 12); + + /// Identifies a special static anonymous symbol (no game is known using this). + struct StaticAnonymousSymbolEntry { + SegmentTag symbol_position; // to self's segment + u32_le relocation_batch_offset; // pointing to a relocation batch in StaticRelocationTable + + static constexpr HeaderField TABLE_OFFSET_FIELD = StaticAnonymousSymbolTableOffset; + }; + ASSERT_CRO_STRUCT(StaticAnonymousSymbolEntry, 8); + + /** + * Entry size of each table, from Code to StaticRelocationTable. + * Byte string contents (such as Code) are treated with entries of size 1. + * This is used for verifying the size of each table and calculating the fix end. + */ + static const std::array ENTRY_SIZE; + + /// The offset field of the table where to crop for each fix level + static const std::array FIX_BARRIERS; + + static constexpr u32 MAGIC_CRO0 = 0x304F5243; + static constexpr u32 MAGIC_FIXD = 0x44584946; + + VAddr Field(HeaderField field) const { + return module_address + CRO_HASH_SIZE + field * 4; + } + + u32 GetField(HeaderField field) const { + return Memory::Read32(Field(field)); + } + + void SetField(HeaderField field, u32 value) { + Memory::Write32(Field(field), value); + } + + /** + * Reads an entry in one of module tables. + * @param index index of the entry + * @param data where to put the read entry + * @note the entry type must have the static member TABLE_OFFSET_FIELD + * indicating which table the entry is in. + */ + template + void GetEntry(std::size_t index, T& data) const { + Memory::ReadBlock(GetField(T::TABLE_OFFSET_FIELD) + index * sizeof(T), &data, sizeof(T)); + } + + /** + * Writes an entry to one of module tables. + * @param index index of the entry + * @param data the entry data to write + * @note the entry type must have the static member TABLE_OFFSET_FIELD + * indicating which table the entry is in. + */ + template + void SetEntry(std::size_t index, const T& data) { + Memory::WriteBlock(GetField(T::TABLE_OFFSET_FIELD) + index * sizeof(T), &data, sizeof(T)); + } + + /** + * Converts a segment tag to virtual address in this module. + * @param segment_tag the segment tag to convert + * @returns VAddr the virtual address the segment tag points to; 0 if invalid. + */ + VAddr SegmentTagToAddress(SegmentTag segment_tag) const; + + VAddr NextModule() const { + return GetField(NextCRO); + } + + VAddr PreviousModule() const { + return GetField(PreviousCRO); + } + + void SetNextModule(VAddr next) { + SetField(NextCRO, next); + } + + void SetPreviousModule(VAddr previous) { + SetField(PreviousCRO, previous); + } + + /** + * A helper function iterating over all registered auto-link modules, including the static module. + * @param crs_address the virtual address of the static module + * @param func a function object to operate on a module. It accepts one parameter + * CROHelper and returns ResultVal. It should return true to continue the iteration, + * false to stop the iteration, or an error code (which will also stop the iteration). + * @returns ResultCode indicating the result of the operation, RESULT_SUCCESS if all iteration success, + * otherwise error code of the last iteration. + */ + template + static ResultCode ForEachAutoLinkCRO(VAddr crs_address, FunctionObject func) { + VAddr current = crs_address; + while (current != 0) { + CROHelper cro(current); + CASCADE_RESULT(bool next, func(cro)); + if (!next) + break; + current = cro.NextModule(); + } + return RESULT_SUCCESS; + } + + /** + * Applies a relocation + * @param target_address where to apply the relocation + * @param relocation_type the type of the relocation + * @param addend address addend applied to the relocated symbol + * @param symbol_address the symbol address to be relocated with + * @param target_future_address the future address of the target. + * Usually equals to target_address, but will be different for a target in .data segment + * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. + */ + ResultCode ApplyRelocation(VAddr target_address, RelocationType relocation_type, + u32 addend, u32 symbol_address, u32 target_future_address); + + /** + * Clears a relocation to zero + * @param target_address where to apply the relocation + * @param relocation_type the type of the relocation + * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. + */ + ResultCode ClearRelocation(VAddr target_address, RelocationType relocation_type); + + /** + * Applies or resets a batch of relocations + * @param batch the virtual address of the first relocation in the batch + * @param symbol_address the symbol address to be relocated with + * @param reset false to set the batch to resolved state, true to reset the batch to unresolved state + * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. + */ + ResultCode ApplyRelocationBatch(VAddr batch, u32 symbol_address, bool reset = false); + + /** + * 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; + + /** + * 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); + + /** + * 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 virtual address of .data segment in CRO. + */ + ResultVal RebaseSegmentTable(u32 cro_size, + VAddr data_segment_address, u32 data_segment_size, + VAddr bss_segment_address, u32 bss_segment_size); + + /** + * 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(); + + /** + * Verifies indices in export tree table. + * @returns ResultCode RESULT_SUCCESS if all indices are verified as valid, otherwise error code. + */ + ResultCode VerifyExportTreeTable() const; + + /** + * 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(); + + /** + * 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(); + + /** + * 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(); + + /** + * 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(); + + /** + * Gets the address of OnUnresolved function in this module. + * Used as the applied symbol for reset relocation. + * @returns the virtual address of OnUnresolved. 0 if not provided. + */ + VAddr GetOnUnresolvedAddress(); + + /** + * Resets all external relocations to unresolved state. + * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. + */ + ResultCode ResetExternalRelocations(); + + /** + * Clears all external relocations to zero. + * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. + */ + ResultCode ClearExternalRelocations(); + + /** + * 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); + + /** + * Applies all internal relocations 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 ApplyInternalRelocations(u32 old_data_segment_address); + + /** + * Clears all internal relocations to zero. + * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. + */ + ResultCode ClearInternalRelocations(); + + /// Unrebases offsets in imported anonymous symbol table + void UnrebaseImportAnonymousSymbolTable(); + + /// Unrebases offsets in imported indexed symbol table + void UnrebaseImportIndexedSymbolTable(); + + /// Unrebases offsets in imported named symbol table + void UnrebaseImportNamedSymbolTable(); + + /// Unrebases offsets in imported module table + void UnrebaseImportModuleTable(); + + /// Unrebases offsets in exported named symbol table + void UnrebaseExportNamedSymbolTable(); + + /// Unrebases offsets in segment table + void UnrebaseSegmentTable(); + + /// Unrebases offsets in module header + void UnrebaseHeader(); + + /** + * Looks up all imported named symbols of this module in all registered auto-link modules, and resolves them if found. + * @param crs_address the virtual address of the static module + * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. + */ + ResultCode ApplyImportNamedSymbol(VAddr crs_address); + + /** + * Resets all imported named symbols of this module to unresolved state. + * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. + */ + ResultCode ResetImportNamedSymbol(); + + /** + * Resets all imported indexed symbols of this module to unresolved state. + * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. + */ + ResultCode ResetImportIndexedSymbol(); + + /** + * Resets all imported anonymous symbols of this module to unresolved state. + * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. + */ + ResultCode ResetImportAnonymousSymbol(); + + /** + * Finds registered auto-link modules that this module imports, and resolves indexed and anonymous symbols exported by them. + * @param crs_address the virtual address of the static module + * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. + */ + ResultCode ApplyModuleImport(VAddr crs_address); + + /** + * Resolves target module's imported named symbols that exported by this module. + * @param target the module to resolve. + * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. + */ + ResultCode ApplyExportNamedSymbol(CROHelper target); + + /** + * Resets target's named symbols imported from this module to unresolved state. + * @param target the module to reset. + * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. + */ + ResultCode ResetExportNamedSymbol(CROHelper target); + + /** + * Resolves imported indexed and anonymous symbols in the target module which imports this module. + * @param target the module to resolve. + * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. + */ + ResultCode ApplyModuleExport(CROHelper target); + + /** + * Resets target's indexed and anonymous symbol imported from this module to unresolved state. + * @param target the module to reset. + * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. + */ + ResultCode ResetModuleExport(CROHelper target); + + /** + * 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 ApplyExitRelocations(VAddr crs_address); +}; + +} // namespace diff --git a/src/core/hle/service/ldr_ro/ldr_ro.cpp b/src/core/hle/service/ldr_ro/ldr_ro.cpp new file mode 100644 index 000000000..8ba73ea8d --- /dev/null +++ b/src/core/hle/service/ldr_ro/ldr_ro.cpp @@ -0,0 +1,748 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/alignment.h" +#include "common/common_types.h" +#include "common/logging/log.h" + +#include "core/arm/arm_interface.h" +#include "core/hle/kernel/process.h" +#include "core/hle/kernel/vm_manager.h" +#include "core/hle/service/ldr_ro/cro_helper.h" +#include "core/hle/service/ldr_ro/ldr_ro.h" +#include "core/hle/service/ldr_ro/memory_synchronizer.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Namespace LDR_RO + +namespace LDR_RO { + +static const ResultCode ERROR_ALREADY_INITIALIZED = // 0xD9612FF9 + ResultCode(ErrorDescription::AlreadyInitialized, ErrorModule::RO, ErrorSummary::Internal, ErrorLevel::Permanent); +static const ResultCode ERROR_NOT_INITIALIZED = // 0xD9612FF8 + ResultCode(ErrorDescription::NotInitialized, ErrorModule::RO, ErrorSummary::Internal, ErrorLevel::Permanent); +static const ResultCode ERROR_BUFFER_TOO_SMALL = // 0xE0E12C1F + ResultCode(static_cast(31), ErrorModule::RO, ErrorSummary::InvalidArgument, ErrorLevel::Usage); +static const ResultCode ERROR_MISALIGNED_ADDRESS = // 0xD9012FF1 + ResultCode(ErrorDescription::MisalignedAddress, ErrorModule::RO, ErrorSummary::WrongArgument, ErrorLevel::Permanent); +static const ResultCode ERROR_MISALIGNED_SIZE = // 0xD9012FF2 + ResultCode(ErrorDescription::MisalignedSize, ErrorModule::RO, ErrorSummary::WrongArgument, ErrorLevel::Permanent); +static const ResultCode ERROR_ILLEGAL_ADDRESS = // 0xE1612C0F + ResultCode(static_cast(15), ErrorModule::RO, ErrorSummary::Internal, ErrorLevel::Usage); +static const ResultCode ERROR_INVALID_MEMORY_STATE = // 0xD8A12C08 + ResultCode(static_cast(8), ErrorModule::RO, ErrorSummary::InvalidState, ErrorLevel::Permanent); +static const ResultCode ERROR_NOT_LOADED = // 0xD8A12C0D + ResultCode(static_cast(13), ErrorModule::RO, ErrorSummary::InvalidState, ErrorLevel::Permanent); +static const ResultCode ERROR_INVALID_DESCRIPTOR = // 0xD9001830 + ResultCode(ErrorDescription::OS_InvalidBufferDescriptor, ErrorModule::OS, ErrorSummary::WrongArgument, ErrorLevel::Permanent); + +static MemorySynchronizer memory_synchronizer; + +// TODO(wwylele): this should be in the per-client storage when we implement multi-process +static VAddr loaded_crs; ///< the virtual address of the static module + +static bool VerifyBufferState(VAddr buffer_ptr, u32 size) { + auto vma = Kernel::g_current_process->vm_manager.FindVMA(buffer_ptr); + return vma != Kernel::g_current_process->vm_manager.vma_map.end() + && vma->second.base + vma->second.size >= buffer_ptr + size + && vma->second.permissions == Kernel::VMAPermission::ReadWrite + && vma->second.meminfo_state == Kernel::MemoryState::Private; +} + +/** + * LDR_RO::Initialize service function + * Inputs: + * 0 : 0x000100C2 + * 1 : CRS buffer pointer + * 2 : CRS Size + * 3 : Process memory address where the CRS will be mapped + * 4 : handle translation descriptor (zero) + * 5 : KProcess handle + * Outputs: + * 0 : Return header + * 1 : Result of function, 0 on success, otherwise error code + */ +static void Initialize(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + VAddr crs_buffer_ptr = cmd_buff[1]; + u32 crs_size = cmd_buff[2]; + VAddr crs_address = cmd_buff[3]; + u32 descriptor = cmd_buff[4]; + u32 process = cmd_buff[5]; + + LOG_DEBUG(Service_LDR, "called, crs_buffer_ptr=0x%08X, crs_address=0x%08X, crs_size=0x%X, descriptor=0x%08X, process=0x%08X", + crs_buffer_ptr, crs_address, crs_size, descriptor, process); + + if (descriptor != 0) { + LOG_ERROR(Service_LDR, "IPC handle descriptor failed validation (0x%X)", descriptor); + cmd_buff[0] = IPC::MakeHeader(0, 1, 0); + cmd_buff[1] = ERROR_INVALID_DESCRIPTOR.raw; + return; + } + + cmd_buff[0] = IPC::MakeHeader(1, 1, 0); + + if (loaded_crs != 0) { + LOG_ERROR(Service_LDR, "Already initialized"); + cmd_buff[1] = ERROR_ALREADY_INITIALIZED.raw; + return; + } + + if (crs_size < CRO_HEADER_SIZE) { + LOG_ERROR(Service_LDR, "CRS is too small"); + cmd_buff[1] = ERROR_BUFFER_TOO_SMALL.raw; + return; + } + + if (crs_buffer_ptr & Memory::PAGE_MASK) { + LOG_ERROR(Service_LDR, "CRS original address is not aligned"); + cmd_buff[1] = ERROR_MISALIGNED_ADDRESS.raw; + return; + } + + if (crs_address & Memory::PAGE_MASK) { + LOG_ERROR(Service_LDR, "CRS mapping address is not aligned"); + cmd_buff[1] = ERROR_MISALIGNED_ADDRESS.raw; + return; + } + + if (crs_size & Memory::PAGE_MASK) { + LOG_ERROR(Service_LDR, "CRS size is not aligned"); + cmd_buff[1] = ERROR_MISALIGNED_SIZE.raw; + return; + } + + if (!VerifyBufferState(crs_buffer_ptr, crs_size)) { + LOG_ERROR(Service_LDR, "CRS original buffer is in invalid state"); + cmd_buff[1] = ERROR_INVALID_MEMORY_STATE.raw; + return; + } + + if (crs_address < Memory::PROCESS_IMAGE_VADDR || crs_address + crs_size > Memory::PROCESS_IMAGE_VADDR_END) { + LOG_ERROR(Service_LDR, "CRS mapping address is not in the process image region"); + cmd_buff[1] = ERROR_ILLEGAL_ADDRESS.raw; + return; + } + + ResultCode result = RESULT_SUCCESS; + + if (crs_buffer_ptr != crs_address) { + // TODO(wwylele): should be memory aliasing + std::shared_ptr> crs_mem = std::make_shared>(crs_size); + Memory::ReadBlock(crs_buffer_ptr, crs_mem->data(), crs_size); + result = Kernel::g_current_process->vm_manager.MapMemoryBlock(crs_address, crs_mem, 0, crs_size, Kernel::MemoryState::Code).Code(); + if (result.IsError()) { + LOG_ERROR(Service_LDR, "Error mapping memory block %08X", result.raw); + cmd_buff[1] = result.raw; + return; + } + + result = Kernel::g_current_process->vm_manager.ReprotectRange(crs_address, crs_size, Kernel::VMAPermission::Read); + if (result.IsError()) { + LOG_ERROR(Service_LDR, "Error reprotecting memory block %08X", result.raw); + cmd_buff[1] = result.raw; + return; + } + + memory_synchronizer.AddMemoryBlock(crs_address, crs_buffer_ptr, crs_size); + } else { + // Do nothing if buffer_ptr == address + // TODO(wwylele): verify this behaviour. This is only seen in the web browser app, + // and the actual behaviour is unclear. "Do nothing" is probably an incorrect implement. + // There is also a chance that another issue causes the app passing wrong arguments. + LOG_WARNING(Service_LDR, "crs_buffer_ptr == crs_address (0x%08X)", crs_address); + } + + CROHelper crs(crs_address); + crs.InitCRS(); + + result = crs.Rebase(0, crs_size, 0, 0, 0, 0, true); + if (result.IsError()) { + LOG_ERROR(Service_LDR, "Error rebasing CRS 0x%08X", result.raw); + cmd_buff[1] = result.raw; + return; + } + + memory_synchronizer.SynchronizeOriginalMemory(); + + loaded_crs = crs_address; + + cmd_buff[1] = RESULT_SUCCESS.raw; +} + +/** + * LDR_RO::LoadCRR service function + * Inputs: + * 0 : 0x00020082 + * 1 : CRR buffer pointer + * 2 : CRR Size + * 3 : handle translation descriptor (zero) + * 4 : KProcess handle + * Outputs: + * 0 : Return header + * 1 : Result of function, 0 on success, otherwise error code + */ +static void LoadCRR(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + u32 crr_buffer_ptr = cmd_buff[1]; + u32 crr_size = cmd_buff[2]; + u32 descriptor = cmd_buff[3]; + u32 process = cmd_buff[4]; + + if (descriptor != 0) { + LOG_ERROR(Service_LDR, "IPC handle descriptor failed validation (0x%X)", descriptor); + cmd_buff[0] = IPC::MakeHeader(0, 1, 0); + cmd_buff[1] = ERROR_INVALID_DESCRIPTOR.raw; + return; + } + + cmd_buff[0] = IPC::MakeHeader(2, 1, 0); + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + + LOG_WARNING(Service_LDR, "(STUBBED) called, crr_buffer_ptr=0x%08X, crr_size=0x%08X, descriptor=0x%08X, process=0x%08X", + crr_buffer_ptr, crr_size, descriptor, process); +} + +/** + * LDR_RO::UnloadCRR service function + * Inputs: + * 0 : 0x00030042 + * 1 : CRR buffer pointer + * 2 : handle translation descriptor (zero) + * 3 : KProcess handle + * Outputs: + * 0 : Return header + * 1 : Result of function, 0 on success, otherwise error code + */ +static void UnloadCRR(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + u32 crr_buffer_ptr = cmd_buff[1]; + u32 descriptor = cmd_buff[2]; + u32 process = cmd_buff[3]; + + if (descriptor != 0) { + LOG_ERROR(Service_LDR, "IPC handle descriptor failed validation (0x%X)", descriptor); + cmd_buff[0] = IPC::MakeHeader(0, 1, 0); + cmd_buff[1] = ERROR_INVALID_DESCRIPTOR.raw; + return; + } + + cmd_buff[0] = IPC::MakeHeader(3, 1, 0); + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + + LOG_WARNING(Service_LDR, "(STUBBED) called, crr_buffer_ptr=0x%08X, descriptor=0x%08X, process=0x%08X", + crr_buffer_ptr, descriptor, process); +} + +/** + * LDR_RO::LoadCRO service function + * Inputs: + * 0 : 0x000402C2 (old) / 0x000902C2 (new) + * 1 : CRO buffer pointer + * 2 : memory address where the CRO will be mapped + * 3 : CRO Size + * 4 : .data segment buffer pointer + * 5 : must be zero + * 6 : .data segment buffer size + * 7 : .bss segment buffer pointer + * 8 : .bss segment buffer size + * 9 : (bool) register CRO as auto-link module + * 10 : fix level + * 11 : CRR address (zero if use loaded CRR) + * 12 : handle translation descriptor (zero) + * 13 : KProcess handle + * Outputs: + * 0 : Return header + * 1 : Result of function, 0 on success, otherwise error code + * 2 : CRO fixed size + * Note: + * This service function has two versions. The function defined here is a + * unified one of two, with an additional parameter link_on_load_bug_fix. + * There is a dispatcher template below. + */ +static void LoadCRO(Service::Interface* self, bool link_on_load_bug_fix) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + VAddr cro_buffer_ptr = cmd_buff[1]; + VAddr cro_address = cmd_buff[2]; + u32 cro_size = cmd_buff[3]; + VAddr data_segment_address = cmd_buff[4]; + u32 zero = cmd_buff[5]; + u32 data_segment_size = cmd_buff[6]; + u32 bss_segment_address = cmd_buff[7]; + u32 bss_segment_size = cmd_buff[8]; + bool auto_link = (cmd_buff[9] & 0xFF) != 0; + u32 fix_level = cmd_buff[10]; + VAddr crr_address = cmd_buff[11]; + u32 descriptor = cmd_buff[12]; + u32 process = cmd_buff[13]; + + LOG_DEBUG(Service_LDR, "called (%s), cro_buffer_ptr=0x%08X, cro_address=0x%08X, cro_size=0x%X, " + "data_segment_address=0x%08X, zero=%d, data_segment_size=0x%X, bss_segment_address=0x%08X, bss_segment_size=0x%X, " + "auto_link=%s, fix_level=%d, crr_address=0x%08X, descriptor=0x%08X, process=0x%08X", + link_on_load_bug_fix ? "new" : "old", cro_buffer_ptr, cro_address, cro_size, + data_segment_address, zero, data_segment_size, bss_segment_address, bss_segment_size, + auto_link ? "true" : "false", fix_level, crr_address, descriptor, process + ); + + if (descriptor != 0) { + LOG_ERROR(Service_LDR, "IPC handle descriptor failed validation (0x%X)", descriptor); + cmd_buff[0] = IPC::MakeHeader(0, 1, 0); + cmd_buff[1] = ERROR_INVALID_DESCRIPTOR.raw; + return; + } + + cmd_buff[0] = IPC::MakeHeader(link_on_load_bug_fix ? 9 : 4, 2, 0); + + if (loaded_crs == 0) { + LOG_ERROR(Service_LDR, "Not initialized"); + cmd_buff[1] = ERROR_NOT_INITIALIZED.raw; + return; + } + + if (cro_size < CRO_HEADER_SIZE) { + LOG_ERROR(Service_LDR, "CRO too small"); + cmd_buff[1] = ERROR_BUFFER_TOO_SMALL.raw; + return; + } + + if (cro_buffer_ptr & Memory::PAGE_MASK) { + LOG_ERROR(Service_LDR, "CRO original address is not aligned"); + cmd_buff[1] = ERROR_MISALIGNED_ADDRESS.raw; + return; + } + + if (cro_address & Memory::PAGE_MASK) { + LOG_ERROR(Service_LDR, "CRO mapping address is not aligned"); + cmd_buff[1] = ERROR_MISALIGNED_ADDRESS.raw; + return; + } + + if (cro_size & Memory::PAGE_MASK) { + LOG_ERROR(Service_LDR, "CRO size is not aligned"); + cmd_buff[1] = ERROR_MISALIGNED_SIZE.raw; + return; + } + + if (!VerifyBufferState(cro_buffer_ptr, cro_size)) { + LOG_ERROR(Service_LDR, "CRO original buffer is in invalid state"); + cmd_buff[1] = ERROR_INVALID_MEMORY_STATE.raw; + return; + } + + if (cro_address < Memory::PROCESS_IMAGE_VADDR + || cro_address + cro_size > Memory::PROCESS_IMAGE_VADDR_END) { + LOG_ERROR(Service_LDR, "CRO mapping address is not in the process image region"); + cmd_buff[1] = ERROR_ILLEGAL_ADDRESS.raw; + return; + } + + if (zero) { + LOG_ERROR(Service_LDR, "Zero is not zero %d", zero); + cmd_buff[1] = ResultCode(static_cast(29), ErrorModule::RO, ErrorSummary::Internal, ErrorLevel::Usage).raw; + return; + } + + ResultCode result = RESULT_SUCCESS; + + if (cro_buffer_ptr != cro_address) { + // TODO(wwylele): should be memory aliasing + std::shared_ptr> cro_mem = std::make_shared>(cro_size); + Memory::ReadBlock(cro_buffer_ptr, cro_mem->data(), cro_size); + result = Kernel::g_current_process->vm_manager.MapMemoryBlock(cro_address, cro_mem, 0, cro_size, Kernel::MemoryState::Code).Code(); + if (result.IsError()) { + LOG_ERROR(Service_LDR, "Error mapping memory block %08X", result.raw); + cmd_buff[1] = result.raw; + return; + } + + result = Kernel::g_current_process->vm_manager.ReprotectRange(cro_address, cro_size, Kernel::VMAPermission::Read); + if (result.IsError()) { + LOG_ERROR(Service_LDR, "Error reprotecting memory block %08X", result.raw); + Kernel::g_current_process->vm_manager.UnmapRange(cro_address, cro_size); + cmd_buff[1] = result.raw; + return; + } + + memory_synchronizer.AddMemoryBlock(cro_address, cro_buffer_ptr, cro_size); + } else { + // Do nothing if buffer_ptr == address + // TODO(wwylele): verify this behaviour. + // This is derived from the case of LoadCRS with buffer_ptr==address, + // and is never seen in any game. "Do nothing" is probably an incorrect implement. + // There is also a chance that this case is just prohibited. + LOG_WARNING(Service_LDR, "cro_buffer_ptr == cro_address (0x%08X)", cro_address); + } + + CROHelper cro(cro_address); + + result = cro.VerifyHash(cro_size, crr_address); + if (result.IsError()) { + LOG_ERROR(Service_LDR, "Error verifying CRO in CRR %08X", result.raw); + Kernel::g_current_process->vm_manager.UnmapRange(cro_address, cro_size); + cmd_buff[1] = result.raw; + return; + } + + result = cro.Rebase(loaded_crs, cro_size, data_segment_address, data_segment_size, bss_segment_address, bss_segment_size, false); + if (result.IsError()) { + LOG_ERROR(Service_LDR, "Error rebasing CRO %08X", result.raw); + Kernel::g_current_process->vm_manager.UnmapRange(cro_address, cro_size); + cmd_buff[1] = result.raw; + return; + } + + result = cro.Link(loaded_crs, link_on_load_bug_fix); + if (result.IsError()) { + LOG_ERROR(Service_LDR, "Error linking CRO %08X", result.raw); + Kernel::g_current_process->vm_manager.UnmapRange(cro_address, cro_size); + cmd_buff[1] = result.raw; + return; + } + + cro.Register(loaded_crs, auto_link); + + u32 fix_size = cro.Fix(fix_level); + + memory_synchronizer.SynchronizeOriginalMemory(); + + // TODO(wwylele): verify the behaviour when buffer_ptr == address + if (cro_buffer_ptr != cro_address) { + if (fix_size != cro_size) { + result = Kernel::g_current_process->vm_manager.UnmapRange(cro_address + fix_size, cro_size - fix_size); + if (result.IsError()) { + LOG_ERROR(Service_LDR, "Error unmapping memory block %08X", result.raw); + Kernel::g_current_process->vm_manager.UnmapRange(cro_address, cro_size); + cmd_buff[1] = result.raw; + return; + } + } + + // Changes the block size + memory_synchronizer.ResizeMemoryBlock(cro_address, cro_buffer_ptr, fix_size); + } + + VAddr exe_begin; + u32 exe_size; + std::tie(exe_begin, exe_size) = cro.GetExecutablePages(); + if (exe_begin) { + result = Kernel::g_current_process->vm_manager.ReprotectRange(exe_begin, exe_size, Kernel::VMAPermission::ReadExecute); + if (result.IsError()) { + LOG_ERROR(Service_LDR, "Error reprotecting memory block %08X", result.raw); + Kernel::g_current_process->vm_manager.UnmapRange(cro_address, fix_size); + cmd_buff[1] = result.raw; + return; + } + } + + Core::g_app_core->ClearInstructionCache(); + + LOG_INFO(Service_LDR, "CRO \"%s\" loaded at 0x%08X, fixed_end=0x%08X", + cro.ModuleName().data(), cro_address, cro_address+fix_size); + + cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[2] = fix_size; +} + +template +static void LoadCRO(Service::Interface* self) { + LoadCRO(self, link_on_load_bug_fix); +} + +/** + * LDR_RO::UnloadCRO service function + * Inputs: + * 0 : 0x000500C2 + * 1 : mapped CRO pointer + * 2 : zero? (RO service doesn't care) + * 3 : original CRO pointer + * 4 : handle translation descriptor (zero) + * 5 : KProcess handle + * Outputs: + * 0 : Return header + * 1 : Result of function, 0 on success, otherwise error code + */ +static void UnloadCRO(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + VAddr cro_address = cmd_buff[1]; + u32 zero = cmd_buff[2]; + VAddr cro_buffer_ptr = cmd_buff[3]; + u32 descriptor = cmd_buff[4]; + u32 process = cmd_buff[5]; + + LOG_DEBUG(Service_LDR, "called, cro_address=0x%08X, zero=%d, cro_buffer_ptr=0x%08X, descriptor=0x%08X, process=0x%08X", + cro_address, zero, cro_buffer_ptr, descriptor, process); + + if (descriptor != 0) { + LOG_ERROR(Service_LDR, "IPC handle descriptor failed validation (0x%X)", descriptor); + cmd_buff[0] = IPC::MakeHeader(0, 1, 0); + cmd_buff[1] = ERROR_INVALID_DESCRIPTOR.raw; + return; + } + + CROHelper cro(cro_address); + + cmd_buff[0] = IPC::MakeHeader(5, 1, 0); + + if (loaded_crs == 0) { + LOG_ERROR(Service_LDR, "Not initialized"); + cmd_buff[1] = ERROR_NOT_INITIALIZED.raw; + return; + } + + if (cro_address & Memory::PAGE_MASK) { + LOG_ERROR(Service_LDR, "CRO address is not aligned"); + cmd_buff[1] = ERROR_MISALIGNED_ADDRESS.raw; + return; + } + + if (!cro.IsLoaded()) { + LOG_ERROR(Service_LDR, "Invalid or not loaded CRO"); + cmd_buff[1] = ERROR_NOT_LOADED.raw; + return; + } + + LOG_INFO(Service_LDR, "Unloading CRO \"%s\"", cro.ModuleName().data()); + + u32 fixed_size = cro.GetFixedSize(); + + cro.Unregister(loaded_crs); + + ResultCode result = cro.Unlink(loaded_crs); + if (result.IsError()) { + LOG_ERROR(Service_LDR, "Error unlinking CRO %08X", result.raw); + cmd_buff[1] = result.raw; + return; + } + + // If the module is not fixed, clears all external/internal relocations + // to restore the state before loading, so that it can be loaded again(?) + if (!cro.IsFixed()) { + result = cro.ClearRelocations(); + if (result.IsError()) { + LOG_ERROR(Service_LDR, "Error clearing relocations %08X", result.raw); + cmd_buff[1] = result.raw; + return; + } + } + + cro.Unrebase(false); + + memory_synchronizer.SynchronizeOriginalMemory(); + + // TODO(wwylele): verify the behaviour when buffer_ptr == address + if (cro_address != cro_buffer_ptr) { + result = Kernel::g_current_process->vm_manager.UnmapRange(cro_address, fixed_size); + if (result.IsError()) { + LOG_ERROR(Service_LDR, "Error unmapping CRO %08X", result.raw); + } + memory_synchronizer.RemoveMemoryBlock(cro_address, cro_buffer_ptr); + } + + Core::g_app_core->ClearInstructionCache(); + + cmd_buff[1] = result.raw; +} + +/** + * LDR_RO::LinkCRO service function + * Inputs: + * 0 : 0x00060042 + * 1 : mapped CRO pointer + * 2 : handle translation descriptor (zero) + * 3 : KProcess handle + * Outputs: + * 0 : Return header + * 1 : Result of function, 0 on success, otherwise error code + */ +static void LinkCRO(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + VAddr cro_address = cmd_buff[1]; + u32 descriptor = cmd_buff[2]; + u32 process = cmd_buff[3]; + + LOG_DEBUG(Service_LDR, "called, cro_address=0x%08X, descriptor=0x%08X, process=0x%08X", + cro_address, descriptor, process); + + if (descriptor != 0) { + LOG_ERROR(Service_LDR, "IPC handle descriptor failed validation (0x%X)", descriptor); + cmd_buff[0] = IPC::MakeHeader(0, 1, 0); + cmd_buff[1] = ERROR_INVALID_DESCRIPTOR.raw; + return; + } + + CROHelper cro(cro_address); + + cmd_buff[0] = IPC::MakeHeader(6, 1, 0); + + if (loaded_crs == 0) { + LOG_ERROR(Service_LDR, "Not initialized"); + cmd_buff[1] = ERROR_NOT_INITIALIZED.raw; + return; + } + + if (cro_address & Memory::PAGE_MASK) { + LOG_ERROR(Service_LDR, "CRO address is not aligned"); + cmd_buff[1] = ERROR_MISALIGNED_ADDRESS.raw; + return; + } + + if (!cro.IsLoaded()) { + LOG_ERROR(Service_LDR, "Invalid or not loaded CRO"); + cmd_buff[1] = ERROR_NOT_LOADED.raw; + return; + } + + LOG_INFO(Service_LDR, "Linking CRO \"%s\"", cro.ModuleName().data()); + + ResultCode result = cro.Link(loaded_crs, false); + if (result.IsError()) { + LOG_ERROR(Service_LDR, "Error linking CRO %08X", result.raw); + } + + memory_synchronizer.SynchronizeOriginalMemory(); + Core::g_app_core->ClearInstructionCache(); + + cmd_buff[1] = result.raw; +} + +/** + * LDR_RO::UnlinkCRO service function + * Inputs: + * 0 : 0x00070042 + * 1 : mapped CRO pointer + * 2 : handle translation descriptor (zero) + * 3 : KProcess handle + * Outputs: + * 0 : Return header + * 1 : Result of function, 0 on success, otherwise error code + */ +static void UnlinkCRO(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + VAddr cro_address = cmd_buff[1]; + u32 descriptor = cmd_buff[2]; + u32 process = cmd_buff[3]; + + LOG_DEBUG(Service_LDR, "called, cro_address=0x%08X, descriptor=0x%08X, process=0x%08X", + cro_address, descriptor, process); + + if (descriptor != 0) { + LOG_ERROR(Service_LDR, "IPC handle descriptor failed validation (0x%X)", descriptor); + cmd_buff[0] = IPC::MakeHeader(0, 1, 0); + cmd_buff[1] = ERROR_INVALID_DESCRIPTOR.raw; + return; + } + + CROHelper cro(cro_address); + + cmd_buff[0] = IPC::MakeHeader(7, 1, 0); + + if (loaded_crs == 0) { + LOG_ERROR(Service_LDR, "Not initialized"); + cmd_buff[1] = ERROR_NOT_INITIALIZED.raw; + return; + } + + if (cro_address & Memory::PAGE_MASK) { + LOG_ERROR(Service_LDR, "CRO address is not aligned"); + cmd_buff[1] = ERROR_MISALIGNED_ADDRESS.raw; + return; + } + + if (!cro.IsLoaded()) { + LOG_ERROR(Service_LDR, "Invalid or not loaded CRO"); + cmd_buff[1] = ERROR_NOT_LOADED.raw; + return; + } + + LOG_INFO(Service_LDR, "Unlinking CRO \"%s\"", cro.ModuleName().data()); + + ResultCode result = cro.Unlink(loaded_crs); + if (result.IsError()) { + LOG_ERROR(Service_LDR, "Error unlinking CRO %08X", result.raw); + } + + memory_synchronizer.SynchronizeOriginalMemory(); + Core::g_app_core->ClearInstructionCache(); + + cmd_buff[1] = result.raw; +} + +/** + * LDR_RO::Shutdown service function + * Inputs: + * 0 : 0x00080042 + * 1 : original CRS buffer pointer + * 2 : handle translation descriptor (zero) + * 3 : KProcess handle + * Outputs: + * 0 : Return header + * 1 : Result of function, 0 on success, otherwise error code + */ +static void Shutdown(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + VAddr crs_buffer_ptr = cmd_buff[1]; + u32 descriptor = cmd_buff[2]; + u32 process = cmd_buff[3]; + + LOG_DEBUG(Service_LDR, "called, crs_buffer_ptr=0x%08X, descriptor=0x%08X, process=0x%08X", + crs_buffer_ptr, descriptor, process); + + if (descriptor != 0) { + LOG_ERROR(Service_LDR, "IPC handle descriptor failed validation (0x%X)", descriptor); + cmd_buff[0] = IPC::MakeHeader(0, 1, 0); + cmd_buff[1] = ERROR_INVALID_DESCRIPTOR.raw; + return; + } + + if (loaded_crs == 0) { + LOG_ERROR(Service_LDR, "Not initialized"); + cmd_buff[1] = ERROR_NOT_INITIALIZED.raw; + return; + } + + cmd_buff[0] = IPC::MakeHeader(8, 1, 0); + + CROHelper crs(loaded_crs); + crs.Unrebase(true); + + memory_synchronizer.SynchronizeOriginalMemory(); + + ResultCode result = RESULT_SUCCESS; + + // TODO(wwylele): verify the behaviour when buffer_ptr == address + if (loaded_crs != crs_buffer_ptr) { + result = Kernel::g_current_process->vm_manager.UnmapRange(loaded_crs, crs.GetFileSize()); + if (result.IsError()) { + LOG_ERROR(Service_LDR, "Error unmapping CRS %08X", result.raw); + } + memory_synchronizer.RemoveMemoryBlock(loaded_crs, crs_buffer_ptr); + } + + loaded_crs = 0; + cmd_buff[1] = result.raw; +} + +const Interface::FunctionInfo FunctionTable[] = { + {0x000100C2, Initialize, "Initialize"}, + {0x00020082, LoadCRR, "LoadCRR"}, + {0x00030042, UnloadCRR, "UnloadCRR"}, + {0x000402C2, LoadCRO, "LoadCRO"}, + {0x000500C2, UnloadCRO, "UnloadCRO"}, + {0x00060042, LinkCRO, "LinkCRO"}, + {0x00070042, UnlinkCRO, "UnlinkCRO"}, + {0x00080042, Shutdown, "Shutdown"}, + {0x000902C2, LoadCRO, "LoadCRO_New"}, +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Interface class + +Interface::Interface() { + Register(FunctionTable); + + loaded_crs = 0; + memory_synchronizer.Clear(); +} + +} // namespace diff --git a/src/core/hle/service/ldr_ro.h b/src/core/hle/service/ldr_ro/ldr_ro.h similarity index 100% rename from src/core/hle/service/ldr_ro.h rename to src/core/hle/service/ldr_ro/ldr_ro.h diff --git a/src/core/hle/service/ldr_ro/memory_synchronizer.cpp b/src/core/hle/service/ldr_ro/memory_synchronizer.cpp new file mode 100644 index 000000000..4402876e6 --- /dev/null +++ b/src/core/hle/service/ldr_ro/memory_synchronizer.cpp @@ -0,0 +1,46 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include + +#include "common/assert.h" + +#include "core/hle/service/ldr_ro/memory_synchronizer.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Namespace LDR_RO + +namespace LDR_RO { + +auto MemorySynchronizer::FindMemoryBlock(VAddr mapping, VAddr original) { + auto block = std::find_if(memory_blocks.begin(), memory_blocks.end(), [=](MemoryBlock& b){ + return b.original == original; + }); + ASSERT(block->mapping == mapping); + return block; +} + +void MemorySynchronizer::Clear() { + memory_blocks.clear(); +} + +void MemorySynchronizer::AddMemoryBlock(VAddr mapping, VAddr original, u32 size) { + memory_blocks.push_back(MemoryBlock{mapping, original, size}); +} + +void MemorySynchronizer::ResizeMemoryBlock(VAddr mapping, VAddr original, u32 size) { + FindMemoryBlock(mapping, original)->size = size; +} + +void MemorySynchronizer::RemoveMemoryBlock(VAddr mapping, VAddr original) { + memory_blocks.erase(FindMemoryBlock(mapping, original)); +} + +void MemorySynchronizer::SynchronizeOriginalMemory() { + for (auto& block : memory_blocks) { + Memory::CopyBlock(block.original, block.mapping, block.size); + } +} + +} // namespace diff --git a/src/core/hle/service/ldr_ro/memory_synchronizer.h b/src/core/hle/service/ldr_ro/memory_synchronizer.h new file mode 100644 index 000000000..92f267912 --- /dev/null +++ b/src/core/hle/service/ldr_ro/memory_synchronizer.h @@ -0,0 +1,44 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include + +#include "core/memory.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Namespace LDR_RO + +namespace LDR_RO { + +/** + * This is a work-around before we implement memory aliasing. + * CRS and CRO are mapped (aliased) to another memory when loading. Games can read + * from both the original buffer and the mapping memory. So we use this to synchronize + * all original buffers with mapping memory after modifying the content. + */ +class MemorySynchronizer { +public: + void Clear(); + + void AddMemoryBlock(VAddr mapping, VAddr original, u32 size); + void ResizeMemoryBlock(VAddr mapping, VAddr original, u32 size); + void RemoveMemoryBlock(VAddr mapping, VAddr original); + + void SynchronizeOriginalMemory(); + +private: + struct MemoryBlock { + VAddr mapping; + VAddr original; + u32 size; + }; + + std::vector memory_blocks; + + auto FindMemoryBlock(VAddr mapping, VAddr original); +}; + +} // namespace diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index 395880843..5b8440b77 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -15,7 +15,6 @@ #include "core/hle/service/gsp_gpu.h" #include "core/hle/service/gsp_lcd.h" #include "core/hle/service/http_c.h" -#include "core/hle/service/ldr_ro.h" #include "core/hle/service/mic_u.h" #include "core/hle/service/ns_s.h" #include "core/hle/service/nwm_uds.h" @@ -36,6 +35,7 @@ #include "core/hle/service/cfg/cfg.h" #include "core/hle/service/hid/hid.h" #include "core/hle/service/ir/ir.h" +#include "core/hle/service/ldr_ro/ldr_ro.h" #include "core/hle/service/ndm/ndm.h" #include "core/hle/service/news/news.h" #include "core/hle/service/nim/nim.h" diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 8c9e5d46d..9aa8c4e5a 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -280,6 +280,20 @@ u8* GetPointer(const VAddr vaddr) { return nullptr; } +std::string ReadCString(VAddr vaddr, std::size_t max_length) { + std::string string; + string.reserve(max_length); + for (std::size_t i = 0; i < max_length; ++i) { + char c = Read8(vaddr); + if (c == '\0') + break; + string.push_back(c); + ++vaddr; + } + string.shrink_to_fit(); + return string; +} + u8* GetPhysicalPointer(PAddr address) { // TODO(Subv): This call should not go through the application's memory mapping. return GetPointer(PhysicalToVirtualAddress(address)); diff --git a/src/core/memory.h b/src/core/memory.h index ae5588dee..cad845385 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -5,6 +5,7 @@ #pragma once #include +#include #include "common/common_types.h" @@ -130,6 +131,8 @@ void CopyBlock(VAddr dest_addr, VAddr src_addr, size_t size); u8* GetPointer(VAddr virtual_address); +std::string ReadCString(VAddr virtual_address, std::size_t max_length); + /** * Converts a virtual address inside a region with 1:1 mapping to physical memory to a physical * address. This should be used by services to translate addresses for use by the hardware.