From 6fab9fe2f64f96107eed5d0381fe07ac804d603d Mon Sep 17 00:00:00 2001 From: wwylele Date: Sat, 20 Aug 2016 13:28:59 +0800 Subject: [PATCH] LDR split --- src/core/CMakeLists.txt | 8 +- src/core/hle/service/ldr_ro.cpp | 2817 ----------------- src/core/hle/service/ldr_ro/cro_helper.cpp | 1468 +++++++++ src/core/hle/service/ldr_ro/cro_helper.h | 702 ++++ src/core/hle/service/ldr_ro/ldr_ro.cpp | 750 +++++ src/core/hle/service/{ => ldr_ro}/ldr_ro.h | 0 .../service/ldr_ro/memory_synchronizer.cpp | 46 + .../hle/service/ldr_ro/memory_synchronizer.h | 46 + src/core/hle/service/service.cpp | 2 +- 9 files changed, 3019 insertions(+), 2820 deletions(-) delete mode 100644 src/core/hle/service/ldr_ro.cpp create mode 100644 src/core/hle/service/ldr_ro/cro_helper.cpp create mode 100644 src/core/hle/service/ldr_ro/cro_helper.h create mode 100644 src/core/hle/service/ldr_ro/ldr_ro.cpp rename src/core/hle/service/{ => ldr_ro}/ldr_ro.h (100%) create mode 100644 src/core/hle/service/ldr_ro/memory_synchronizer.cpp create mode 100644 src/core/hle/service/ldr_ro/memory_synchronizer.h 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/hle/service/ldr_ro.cpp b/src/core/hle/service/ldr_ro.cpp deleted file mode 100644 index d359d83e2..000000000 --- a/src/core/hle/service/ldr_ro.cpp +++ /dev/null @@ -1,2817 +0,0 @@ -// 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 "common/scope_exit.h" -#include "common/swap.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.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; - -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 ResultCode CROFormatError(u32 description) { - return ResultCode(static_cast(description), ErrorModule::RO, ErrorSummary::WrongArgument, ErrorLevel::Permanent); -} - -/// Represents a loaded module (CRO) with interfaces manipulating it. -class CROHelper final { - 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 { - 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; - } - - 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) { - 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; - } - - /** - * 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) { - 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; - } - - /** - * 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) { - 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; - } - - /** - * Finds an exported named symbol in this module. - * @param name the name of the symbol to find - * @return VAddr the virtual address of the symbol; 0 if not found. - */ - VAddr FindExportNamedSymbol(const std::string& name) const { - if (!GetField(ExportTreeNum)) - return 0; - - std::size_t len = name.size(); - ExportTreeEntry entry; - GetEntry(0, entry); - ExportTreeEntry::Child next; - next.raw = entry.left.raw; - u32 found_id; - - while (true) { - GetEntry(next.next_index, entry); - - if (next.is_end) { - found_id = entry.export_table_index; - break; - } - - u16 test_byte = entry.test_bit >> 3; - u16 test_bit_in_byte = entry.test_bit & 7; - - if (test_byte >= len) { - next.raw = entry.left.raw; - } else if((name[test_byte] >> test_bit_in_byte) & 1) { - next.raw = entry.right.raw; - } else { - next.raw = entry.left.raw; - } - } - - u32 export_named_symbol_num = GetField(ExportNamedSymbolNum); - - if (found_id >= export_named_symbol_num) - return 0; - - u32 export_strings_size = GetField(ExportStringsSize); - ExportNamedSymbolEntry symbol_entry; - GetEntry(found_id, symbol_entry); - - if (Memory::ReadCString(symbol_entry.name_offset, export_strings_size) != name) - return 0; - - return SegmentTagToAddress(symbol_entry.symbol_position); - } - - /** - * Rebases offsets in module header according to module address. - * @param cro_size the size of the CRO file - * @returns ResultCode RESULT_SUCCESS if all offsets are verified as valid, otherwise error code. - */ - ResultCode RebaseHeader(u32 cro_size) { - ResultCode error = CROFormatError(0x11); - - // verifies magic - if (GetField(Magic) != MAGIC_CRO0) - return error; - - // verifies not registered - if (GetField(NextCRO) != 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; - } - - /** - * 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; - } - - /** - * 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) { - - 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); - } - - /** - * Rebases offsets in exported named symbol table according to module address. - * @returns ResultCode RESULT_SUCCESS if all offsets are verified as valid, otherwise error code. - */ - ResultCode RebaseExportNamedSymbolTable() { - VAddr export_strings_offset = GetField(ExportStringsOffset); - VAddr export_strings_end = export_strings_offset + GetField(ExportStringsSize); - - u32 export_named_symbol_num = GetField(ExportNamedSymbolNum); - for (u32 i = 0; i < export_named_symbol_num; ++i) { - ExportNamedSymbolEntry entry; - GetEntry(i, entry); - - if (entry.name_offset != 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; - } - - /** - * Verifies indices in export tree table. - * @returns ResultCode RESULT_SUCCESS if all indices are verified as valid, otherwise error code. - */ - ResultCode VerifyExportTreeTable() const { - u32 tree_num = GetField(ExportTreeNum); - for (u32 i = 0; i < tree_num; ++i) { - ExportTreeEntry entry; - GetEntry(i, entry); - - if (entry.left.next_index >= tree_num || entry.right.next_index >= tree_num) { - return CROFormatError(0x11); - } - } - return RESULT_SUCCESS; - } - - /** - * Rebases offsets in exported module table according to module address. - * @returns ResultCode RESULT_SUCCESS if all offsets are verified as valid, otherwise error code. - */ - ResultCode RebaseImportModuleTable() { - VAddr import_strings_offset = GetField(ImportStringsOffset); - VAddr import_strings_end = import_strings_offset + GetField(ImportStringsSize); - VAddr import_indexed_symbol_table_offset = GetField(ImportIndexedSymbolTableOffset); - VAddr index_import_table_end = import_indexed_symbol_table_offset + GetField(ImportIndexedSymbolNum) * sizeof(ImportIndexedSymbolEntry); - VAddr import_anonymous_symbol_table_offset = GetField(ImportAnonymousSymbolTableOffset); - VAddr offset_import_table_end = import_anonymous_symbol_table_offset + GetField(ImportAnonymousSymbolNum) * sizeof(ImportAnonymousSymbolEntry); - - u32 module_num = GetField(ImportModuleNum); - for (u32 i = 0; i < module_num; ++i) { - ImportModuleEntry entry; - GetEntry(i, entry); - - if (entry.name_offset != 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; - } - - /** - * Rebases offsets in imported named symbol table according to module address. - * @returns ResultCode RESULT_SUCCESS if all offsets are verified as valid, otherwise error code. - */ - ResultCode RebaseImportNamedSymbolTable() { - VAddr import_strings_offset = GetField(ImportStringsOffset); - VAddr import_strings_end = import_strings_offset + GetField(ImportStringsSize); - VAddr external_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; - } - - /** - * Rebases offsets in imported indexed symbol table according to module address. - * @returns ResultCode RESULT_SUCCESS if all offsets are verified as valid, otherwise error code. - */ - ResultCode RebaseImportIndexedSymbolTable() { - VAddr external_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; - } - - /** - * Rebases offsets in imported anonymous symbol table according to module address. - * @returns ResultCode RESULT_SUCCESS if all offsets are verified as valid, otherwise error code. - */ - ResultCode RebaseImportAnonymousSymbolTable() { - VAddr external_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; - } - - /** - * 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() { - return SegmentTagToAddress(SegmentTag(GetField(OnUnresolvedSegmentTag))); - } - - /** - * Resets all external relocations to unresolved state. - * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. - */ - ResultCode 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; - } - - /** - * Clears all external relocations to zero. - * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. - */ - ResultCode 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; - } - - /** - * Applies all static anonymous symbol to the static module. - * @param crs_address the virtual address of the static module - * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. - */ - ResultCode ApplyStaticAnonymousSymbolToCRS(VAddr crs_address) { - VAddr static_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; - } - - /** - * 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) { - 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; - } - - /** - * Clears all internal relocations to zero. - * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. - */ - ResultCode 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; - } - - /// Unrebases offsets in imported anonymous symbol table - void 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); - } - } - - /// Unrebases offsets in imported indexed symbol table - void 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); - } - } - - /// Unrebases offsets in imported named symbol table - void 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); - } - } - - /// Unrebases offsets in imported module table - void 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); - } - } - - /// Unrebases offsets in exported named symbol table - void 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); - } - } - - /// Unrebases offsets in segment table - void 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); - } - } - - /// Unrebases offsets in module header - void 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); - } - } - - /** - * 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) { - 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; - } - - /** - * Resets all imported named symbols of this module to unresolved state. - * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. - */ - ResultCode 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; - } - - /** - * Resets all imported indexed symbols of this module to unresolved state. - * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. - */ - ResultCode 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; - } - - /** - * Resets all imported anonymous symbols of this module to unresolved state. - * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. - */ - ResultCode 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; - } - - /** - * 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) { - 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; - } - - /** - * 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) { - 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; - } - - /** - * 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) { - 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; - } - - /** - * 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) { - 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; - } - - /** - * 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) { - 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; - } - - /** - * 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) { - 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; - } - -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) { - 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; - } - - /** - * Unrebases the module. - * @param is_crs true if the module itself is the static module - */ - void Unrebase(bool is_crs) { - UnrebaseImportAnonymousSymbolTable(); - UnrebaseImportIndexedSymbolTable(); - UnrebaseImportNamedSymbolTable(); - UnrebaseImportModuleTable(); - UnrebaseExportNamedSymbolTable(); - - if (!is_crs) - UnrebaseSegmentTable(); - - SetNextModule(0); - SetPreviousModule(0); - - SetField(FixedSize, 0); - - UnrebaseHeader(); - } - - /** - * 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 { - // TODO(wwylele): actually verify the hash - return RESULT_SUCCESS; - } - - /** - * 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) { - 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; - } - - /** - * 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) { - - // 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; - } - - /** - * Clears all relocations to zero. - * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. - */ - ResultCode 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 InitCRS() { - SetNextModule(0); - SetPreviousModule(0); - } - - /** - * 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) { - 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); - } - - /** - * Unregisters this module and removes from the module list. - * @param crs_address the virtual address of the static module - */ - void 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); - } - - /** - * 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 { - 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; - } - } - - /** - * 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) { - 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 IsFixed() const { - return GetField(Magic) == MAGIC_FIXD; - } - - u32 GetFixedSize() const { - return GetField(FixedSize); - } - - bool 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; - } - - /** - * 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 { - 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); - } -}; - -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 -}}; - -/** - * 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 { - struct MemoryBlock { - VAddr mapping; - VAddr original; - u32 size; - }; - - std::vector memory_blocks; - - auto 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; - } - -public: - void Clear() { - memory_blocks.clear(); - } - - void AddMemoryBlock(VAddr mapping, VAddr original, u32 size) { - memory_blocks.push_back(MemoryBlock{mapping, original, size}); - } - - void ResizeMemoryBlock(VAddr mapping, VAddr original, u32 size) { - FindMemoryBlock(mapping, original)->size = size; - } - - void RemoveMemoryBlock(VAddr mapping, VAddr original) { - memory_blocks.erase(FindMemoryBlock(mapping, original)); - } - - void SynchronizeOriginalMemory() { - for (auto& block : memory_blocks) { - Memory::CopyBlock(block.original, block.mapping, block.size); - } - } -}; - -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/cro_helper.cpp b/src/core/hle/service/ldr_ro/cro_helper.cpp new file mode 100644 index 000000000..280b23598 --- /dev/null +++ b/src/core/hle/service/ldr_ro/cro_helper.cpp @@ -0,0 +1,1468 @@ +// 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; +} + +ResultCode CROHelper::VerifyStringTableLength(VAddr address, u32 size) { + if (size != 0) { + if (Memory::Read8(address + size - 1) != 0) + return CROFormatError(0x0B); + } + 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; +} + +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..eab6ec6ac --- /dev/null +++ b/src/core/hle/service/ldr_ro/cro_helper.h @@ -0,0 +1,702 @@ +// 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); + + /** + * 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); + + /** + * 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..a9b2ed26b --- /dev/null +++ b/src/core/hle/service/ldr_ro/ldr_ro.cpp @@ -0,0 +1,750 @@ +// 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..abe4072c1 --- /dev/null +++ b/src/core/hle/service/ldr_ro/memory_synchronizer.h @@ -0,0 +1,46 @@ +// 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"