From 1c9612b79155de9538d4b71ff71938786adcea11 Mon Sep 17 00:00:00 2001 From: wwylele Date: Fri, 15 Jul 2016 09:17:01 +0300 Subject: LDR: Implement CRO --- src/core/hle/service/ldr_ro/cro_helper.h | 691 +++++++++++++++++++++++++++++++ 1 file changed, 691 insertions(+) create mode 100644 src/core/hle/service/ldr_ro/cro_helper.h (limited to 'src/core/hle/service/ldr_ro/cro_helper.h') diff --git a/src/core/hle/service/ldr_ro/cro_helper.h b/src/core/hle/service/ldr_ro/cro_helper.h new file mode 100644 index 000000000..34e357afd --- /dev/null +++ b/src/core/hle/service/ldr_ro/cro_helper.h @@ -0,0 +1,691 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include + +#include "common/common_types.h" +#include "common/swap.h" + +#include "core/memory.h" +#include "core/hle/result.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Namespace LDR_RO + +namespace LDR_RO { + +// GCC versions < 5.0 do not implement std::is_trivially_copyable. +// Excluding MSVC because it has weird behaviour for std::is_trivially_copyable. +#if (__GNUC__ >= 5) || defined(__clang__) + #define ASSERT_CRO_STRUCT(name, size) \ + static_assert(std::is_standard_layout::value, "CRO structure " #name " doesn't use standard layout"); \ + static_assert(std::is_trivially_copyable::value, "CRO structure " #name " isn't trivially copyable"); \ + static_assert(sizeof(name) == (size), "Unexpected struct size for CRO structure " #name) +#else + #define ASSERT_CRO_STRUCT(name, size) \ + static_assert(std::is_standard_layout::value, "CRO structure " #name " doesn't use standard layout"); \ + static_assert(sizeof(name) == (size), "Unexpected struct size for CRO structure " #name) +#endif + +static constexpr u32 CRO_HEADER_SIZE = 0x138; +static constexpr u32 CRO_HASH_SIZE = 0x80; + +/// Represents a loaded module (CRO) with interfaces manipulating it. +class CROHelper final { +public: + explicit CROHelper(VAddr cro_address) : module_address(cro_address) { + } + + std::string ModuleName() const { + return Memory::ReadCString(GetField(ModuleNameOffset), GetField(ModuleNameSize)); + } + + u32 GetFileSize() const { + return GetField(FileSize); + } + + /** + * Rebases the module according to its address. + * @param crs_address the virtual address of the static module + * @param cro_size the size of the CRO file + * @param data_segment_address buffer address for .data segment + * @param data_segment_size the buffer size for .data segment + * @param bss_segment_address the buffer address for .bss segment + * @param bss_segment_size the buffer size for .bss segment + * @param is_crs true if the module itself is the static module + * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. + */ + ResultCode Rebase(VAddr crs_address, u32 cro_size, + VAddr data_segment_addresss, u32 data_segment_size, + VAddr bss_segment_address, u32 bss_segment_size, bool is_crs); + + /** + * Unrebases the module. + * @param is_crs true if the module itself is the static module + */ + void Unrebase(bool is_crs); + + /** + * Verifies module hash by CRR. + * @param cro_size the size of the CRO + * @param crr the virtual address of the CRR + * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. + */ + ResultCode VerifyHash(u32 cro_size, VAddr crr) const; + + /** + * Links this module with all registered auto-link module. + * @param crs_address the virtual address of the static module + * @param link_on_load_bug_fix true if links when loading and fixes the bug + * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. + */ + ResultCode Link(VAddr crs_address, bool link_on_load_bug_fix); + + /** + * Unlinks this module with other modules. + * @param crs_address the virtual address of the static module + * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. + */ + ResultCode Unlink(VAddr crs_address); + + /** + * Clears all relocations to zero. + * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. + */ + ResultCode ClearRelocations(); + + /// Initialize this module as the static module (CRS) + void InitCRS(); + + /** + * Registers this module and adds it to the module list. + * @param crs_address the virtual address of the static module + * @auto_link whether to register as an auto link module + */ + void Register(VAddr crs_address, bool auto_link); + + /** + * Unregisters this module and removes from the module list. + * @param crs_address the virtual address of the static module + */ + void Unregister(VAddr crs_address); + + /** + * Gets the end of reserved data according to the fix level. + * @param fix_level fix level from 0 to 3 + * @returns the end of reserved data. + */ + u32 GetFixEnd(u32 fix_level) const; + + /** + * Zeros offsets to cropped data according to the fix level and marks as fixed. + * @param fix_level fix level from 0 to 3 + * @returns page-aligned size of the module after fixing. + */ + u32 Fix(u32 fix_level); + + bool IsFixed() const { + return GetField(Magic) == MAGIC_FIXD; + } + + u32 GetFixedSize() const { + return GetField(FixedSize); + } + + bool IsLoaded() const; + + /** + * Gets the page address and size of the code segment. + * @returns a tuple of (address, size); (0, 0) if the code segment doesn't exist. + */ + std::tuple GetExecutablePages() const; + +private: + const VAddr module_address; ///< the virtual address of this module + + /** + * Each item in this enum represents a u32 field in the header begin from address+0x80, successively. + * We don't directly use a struct here, to avoid GetPointer, reinterpret_cast, or Read/WriteBlock repeatedly. + */ + enum HeaderField { + Magic = 0, + NameOffset, + NextCRO, + PreviousCRO, + FileSize, + BssSize, + FixedSize, + UnknownZero, + UnkSegmentTag, + OnLoadSegmentTag, + OnExitSegmentTag, + OnUnresolvedSegmentTag, + + CodeOffset, + CodeSize, + DataOffset, + DataSize, + ModuleNameOffset, + ModuleNameSize, + SegmentTableOffset, + SegmentNum, + + ExportNamedSymbolTableOffset, + ExportNamedSymbolNum, + ExportIndexedSymbolTableOffset, + ExportIndexedSymbolNum, + ExportStringsOffset, + ExportStringsSize, + ExportTreeTableOffset, + ExportTreeNum, + + ImportModuleTableOffset, + ImportModuleNum, + ExternalRelocationTableOffset, + ExternalRelocationNum, + ImportNamedSymbolTableOffset, + ImportNamedSymbolNum, + ImportIndexedSymbolTableOffset, + ImportIndexedSymbolNum, + ImportAnonymousSymbolTableOffset, + ImportAnonymousSymbolNum, + ImportStringsOffset, + ImportStringsSize, + + StaticAnonymousSymbolTableOffset, + StaticAnonymousSymbolNum, + InternalRelocationTableOffset, + InternalRelocationNum, + StaticRelocationTableOffset, + StaticRelocationNum, + Fix0Barrier, + + Fix3Barrier = ExportNamedSymbolTableOffset, + Fix2Barrier = ImportModuleTableOffset, + Fix1Barrier = StaticAnonymousSymbolTableOffset, + }; + static_assert(Fix0Barrier == (CRO_HEADER_SIZE - CRO_HASH_SIZE) / 4, "CRO Header fields are wrong!"); + + enum class SegmentType : u32 { + Code = 0, + ROData = 1, + Data = 2, + BSS = 3, + }; + + /** + * Identifies a program location inside of a segment. + * Required to refer to program locations because individual segments may be relocated independently of each other. + */ + union SegmentTag { + u32_le raw; + BitField<0, 4, u32_le> segment_index; + BitField<4, 28, u32_le> offset_into_segment; + + SegmentTag() = default; + explicit SegmentTag(u32 raw_) : raw(raw_) {} + }; + + /// Information of a segment in this module. + struct SegmentEntry { + u32_le offset; + u32_le size; + SegmentType type; + + static constexpr HeaderField TABLE_OFFSET_FIELD = SegmentTableOffset; + }; + ASSERT_CRO_STRUCT(SegmentEntry, 12); + + /// Identifies a named symbol exported from this module. + struct ExportNamedSymbolEntry { + u32_le name_offset; // pointing to a substring in ExportStrings + SegmentTag symbol_position; // to self's segment + + static constexpr HeaderField TABLE_OFFSET_FIELD = ExportNamedSymbolTableOffset; + }; + ASSERT_CRO_STRUCT(ExportNamedSymbolEntry, 8); + + /// Identifies an indexed symbol exported from this module. + struct ExportIndexedSymbolEntry { + SegmentTag symbol_position; // to self's segment + + static constexpr HeaderField TABLE_OFFSET_FIELD = ExportIndexedSymbolTableOffset; + }; + ASSERT_CRO_STRUCT(ExportIndexedSymbolEntry, 4); + + /// A tree node in the symbol lookup tree. + struct ExportTreeEntry { + u16_le test_bit; // bit address into the name to test + union Child { + u16_le raw; + BitField<0, 15, u16_le> next_index; + BitField<15, 1, u16_le> is_end; + } left, right; + u16_le export_table_index; // index of an ExportNamedSymbolEntry + + static constexpr HeaderField TABLE_OFFSET_FIELD = ExportTreeTableOffset; + }; + ASSERT_CRO_STRUCT(ExportTreeEntry, 8); + + /// Identifies a named symbol imported from another module. + struct ImportNamedSymbolEntry { + u32_le name_offset; // pointing to a substring in ImportStrings + u32_le relocation_batch_offset; // pointing to a relocation batch in ExternalRelocationTable + + static constexpr HeaderField TABLE_OFFSET_FIELD = ImportNamedSymbolTableOffset; + }; + ASSERT_CRO_STRUCT(ImportNamedSymbolEntry, 8); + + /// Identifies an indexed symbol imported from another module. + struct ImportIndexedSymbolEntry { + u32_le index; // index of an ExportIndexedSymbolEntry in the exporting module + u32_le relocation_batch_offset; // pointing to a relocation batch in ExternalRelocationTable + + static constexpr HeaderField TABLE_OFFSET_FIELD = ImportIndexedSymbolTableOffset; + }; + ASSERT_CRO_STRUCT(ImportIndexedSymbolEntry, 8); + + /// Identifies an anonymous symbol imported from another module. + struct ImportAnonymousSymbolEntry { + SegmentTag symbol_position; // in the exporting segment + u32_le relocation_batch_offset; // pointing to a relocation batch in ExternalRelocationTable + + static constexpr HeaderField TABLE_OFFSET_FIELD = ImportAnonymousSymbolTableOffset; + }; + ASSERT_CRO_STRUCT(ImportAnonymousSymbolEntry, 8); + + /// Information of a imported module and symbols imported from it. + struct ImportModuleEntry { + u32_le name_offset; // pointing to a substring in ImportStrings + u32_le import_indexed_symbol_table_offset; // pointing to a subtable in ImportIndexedSymbolTable + u32_le import_indexed_symbol_num; + u32_le import_anonymous_symbol_table_offset; // pointing to a subtable in ImportAnonymousSymbolTable + u32_le import_anonymous_symbol_num; + + static constexpr HeaderField TABLE_OFFSET_FIELD = ImportModuleTableOffset; + + void GetImportIndexedSymbolEntry(u32 index, ImportIndexedSymbolEntry& entry) { + Memory::ReadBlock(import_indexed_symbol_table_offset + index * sizeof(ImportIndexedSymbolEntry), + &entry, sizeof(ImportIndexedSymbolEntry)); + } + + void GetImportAnonymousSymbolEntry(u32 index, ImportAnonymousSymbolEntry& entry) { + Memory::ReadBlock(import_anonymous_symbol_table_offset + index * sizeof(ImportAnonymousSymbolEntry), + &entry, sizeof(ImportAnonymousSymbolEntry)); + } + }; + ASSERT_CRO_STRUCT(ImportModuleEntry, 20); + + enum class RelocationType : u8 { + Nothing = 0, + AbsoluteAddress = 2, + RelativeAddress = 3, + ThumbBranch = 10, + ArmBranch = 28, + ModifyArmBranch = 29, + AbsoluteAddress2 = 38, + AlignedRelativeAddress = 42, + }; + + struct RelocationEntry { + SegmentTag target_position; // to self's segment as an ExternalRelocationEntry; to static module segment as a StaticRelocationEntry + RelocationType type; + u8 is_batch_end; + u8 is_batch_resolved; // set at a batch beginning if the batch is resolved + INSERT_PADDING_BYTES(1); + u32_le addend; + }; + + /// Identifies a normal cross-module relocation. + struct ExternalRelocationEntry : RelocationEntry { + static constexpr HeaderField TABLE_OFFSET_FIELD = ExternalRelocationTableOffset; + }; + ASSERT_CRO_STRUCT(ExternalRelocationEntry, 12); + + /// Identifies a special static relocation (no game is known using this). + struct StaticRelocationEntry : RelocationEntry { + static constexpr HeaderField TABLE_OFFSET_FIELD = StaticRelocationTableOffset; + }; + ASSERT_CRO_STRUCT(StaticRelocationEntry, 12); + + /// Identifies a in-module relocation. + struct InternalRelocationEntry { + SegmentTag target_position; // to self's segment + RelocationType type; + u8 symbol_segment; + INSERT_PADDING_BYTES(2); + u32_le addend; + + static constexpr HeaderField TABLE_OFFSET_FIELD = InternalRelocationTableOffset; + }; + ASSERT_CRO_STRUCT(InternalRelocationEntry, 12); + + /// Identifies a special static anonymous symbol (no game is known using this). + struct StaticAnonymousSymbolEntry { + SegmentTag symbol_position; // to self's segment + u32_le relocation_batch_offset; // pointing to a relocation batch in StaticRelocationTable + + static constexpr HeaderField TABLE_OFFSET_FIELD = StaticAnonymousSymbolTableOffset; + }; + ASSERT_CRO_STRUCT(StaticAnonymousSymbolEntry, 8); + + /** + * Entry size of each table, from Code to StaticRelocationTable. + * Byte string contents (such as Code) are treated with entries of size 1. + * This is used for verifying the size of each table and calculating the fix end. + */ + static const std::array ENTRY_SIZE; + + /// The offset field of the table where to crop for each fix level + static const std::array FIX_BARRIERS; + + static constexpr u32 MAGIC_CRO0 = 0x304F5243; + static constexpr u32 MAGIC_FIXD = 0x44584946; + + VAddr Field(HeaderField field) const { + return module_address + CRO_HASH_SIZE + field * 4; + } + + u32 GetField(HeaderField field) const { + return Memory::Read32(Field(field)); + } + + void SetField(HeaderField field, u32 value) { + Memory::Write32(Field(field), value); + } + + /** + * Reads an entry in one of module tables. + * @param index index of the entry + * @param data where to put the read entry + * @note the entry type must have the static member TABLE_OFFSET_FIELD + * indicating which table the entry is in. + */ + template + void GetEntry(std::size_t index, T& data) const { + Memory::ReadBlock(GetField(T::TABLE_OFFSET_FIELD) + index * sizeof(T), &data, sizeof(T)); + } + + /** + * Writes an entry to one of module tables. + * @param index index of the entry + * @param data the entry data to write + * @note the entry type must have the static member TABLE_OFFSET_FIELD + * indicating which table the entry is in. + */ + template + void SetEntry(std::size_t index, const T& data) { + Memory::WriteBlock(GetField(T::TABLE_OFFSET_FIELD) + index * sizeof(T), &data, sizeof(T)); + } + + /** + * Converts a segment tag to virtual address in this module. + * @param segment_tag the segment tag to convert + * @returns VAddr the virtual address the segment tag points to; 0 if invalid. + */ + VAddr SegmentTagToAddress(SegmentTag segment_tag) const; + + VAddr NextModule() const { + return GetField(NextCRO); + } + + VAddr PreviousModule() const { + return GetField(PreviousCRO); + } + + void SetNextModule(VAddr next) { + SetField(NextCRO, next); + } + + void SetPreviousModule(VAddr previous) { + SetField(PreviousCRO, previous); + } + + /** + * A helper function iterating over all registered auto-link modules, including the static module. + * @param crs_address the virtual address of the static module + * @param func a function object to operate on a module. It accepts one parameter + * CROHelper and returns ResultVal. It should return true to continue the iteration, + * false to stop the iteration, or an error code (which will also stop the iteration). + * @returns ResultCode indicating the result of the operation, RESULT_SUCCESS if all iteration success, + * otherwise error code of the last iteration. + */ + template + static ResultCode ForEachAutoLinkCRO(VAddr crs_address, FunctionObject func) { + VAddr current = crs_address; + while (current != 0) { + CROHelper cro(current); + CASCADE_RESULT(bool next, func(cro)); + if (!next) + break; + current = cro.NextModule(); + } + return RESULT_SUCCESS; + } + + /** + * Applies a relocation + * @param target_address where to apply the relocation + * @param relocation_type the type of the relocation + * @param addend address addend applied to the relocated symbol + * @param symbol_address the symbol address to be relocated with + * @param target_future_address the future address of the target. + * Usually equals to target_address, but will be different for a target in .data segment + * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. + */ + ResultCode ApplyRelocation(VAddr target_address, RelocationType relocation_type, + u32 addend, u32 symbol_address, u32 target_future_address); + + /** + * Clears a relocation to zero + * @param target_address where to apply the relocation + * @param relocation_type the type of the relocation + * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. + */ + ResultCode ClearRelocation(VAddr target_address, RelocationType relocation_type); + + /** + * Applies or resets a batch of relocations + * @param batch the virtual address of the first relocation in the batch + * @param symbol_address the symbol address to be relocated with + * @param reset false to set the batch to resolved state, true to reset the batch to unresolved state + * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. + */ + ResultCode ApplyRelocationBatch(VAddr batch, u32 symbol_address, bool reset = false); + + /** + * Finds an exported named symbol in this module. + * @param name the name of the symbol to find + * @return VAddr the virtual address of the symbol; 0 if not found. + */ + VAddr FindExportNamedSymbol(const std::string& name) const; + + /** + * Rebases offsets in module header according to module address. + * @param cro_size the size of the CRO file + * @returns ResultCode RESULT_SUCCESS if all offsets are verified as valid, otherwise error code. + */ + ResultCode RebaseHeader(u32 cro_size); + + /** + * Rebases offsets in segment table according to module address. + * @param cro_size the size of the CRO file + * @param data_segment_address the buffer address for .data segment + * @param data_segment_size the buffer size for .data segment + * @param bss_segment_address the buffer address for .bss segment + * @param bss_segment_size the buffer size for .bss segment + * @returns ResultVal with the virtual address of .data segment in CRO. + */ + ResultVal RebaseSegmentTable(u32 cro_size, + VAddr data_segment_address, u32 data_segment_size, + VAddr bss_segment_address, u32 bss_segment_size); + + /** + * Rebases offsets in exported named symbol table according to module address. + * @returns ResultCode RESULT_SUCCESS if all offsets are verified as valid, otherwise error code. + */ + ResultCode RebaseExportNamedSymbolTable(); + + /** + * Verifies indices in export tree table. + * @returns ResultCode RESULT_SUCCESS if all indices are verified as valid, otherwise error code. + */ + ResultCode VerifyExportTreeTable() const; + + /** + * Rebases offsets in exported module table according to module address. + * @returns ResultCode RESULT_SUCCESS if all offsets are verified as valid, otherwise error code. + */ + ResultCode RebaseImportModuleTable(); + + /** + * Rebases offsets in imported named symbol table according to module address. + * @returns ResultCode RESULT_SUCCESS if all offsets are verified as valid, otherwise error code. + */ + ResultCode RebaseImportNamedSymbolTable(); + + /** + * Rebases offsets in imported indexed symbol table according to module address. + * @returns ResultCode RESULT_SUCCESS if all offsets are verified as valid, otherwise error code. + */ + ResultCode RebaseImportIndexedSymbolTable(); + + /** + * Rebases offsets in imported anonymous symbol table according to module address. + * @returns ResultCode RESULT_SUCCESS if all offsets are verified as valid, otherwise error code. + */ + ResultCode RebaseImportAnonymousSymbolTable(); + + /** + * Gets the address of OnUnresolved function in this module. + * Used as the applied symbol for reset relocation. + * @returns the virtual address of OnUnresolved. 0 if not provided. + */ + VAddr GetOnUnresolvedAddress(); + + /** + * Resets all external relocations to unresolved state. + * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. + */ + ResultCode ResetExternalRelocations(); + + /** + * Clears all external relocations to zero. + * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. + */ + ResultCode ClearExternalRelocations(); + + /** + * Applies all static anonymous symbol to the static module. + * @param crs_address the virtual address of the static module + * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. + */ + ResultCode ApplyStaticAnonymousSymbolToCRS(VAddr crs_address); + + /** + * Applies all internal relocations to the module itself. + * @param old_data_segment_address the virtual address of data segment in CRO buffer + * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. + */ + ResultCode ApplyInternalRelocations(u32 old_data_segment_address); + + /** + * Clears all internal relocations to zero. + * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. + */ + ResultCode ClearInternalRelocations(); + + /// Unrebases offsets in imported anonymous symbol table + void UnrebaseImportAnonymousSymbolTable(); + + /// Unrebases offsets in imported indexed symbol table + void UnrebaseImportIndexedSymbolTable(); + + /// Unrebases offsets in imported named symbol table + void UnrebaseImportNamedSymbolTable(); + + /// Unrebases offsets in imported module table + void UnrebaseImportModuleTable(); + + /// Unrebases offsets in exported named symbol table + void UnrebaseExportNamedSymbolTable(); + + /// Unrebases offsets in segment table + void UnrebaseSegmentTable(); + + /// Unrebases offsets in module header + void UnrebaseHeader(); + + /** + * Looks up all imported named symbols of this module in all registered auto-link modules, and resolves them if found. + * @param crs_address the virtual address of the static module + * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. + */ + ResultCode ApplyImportNamedSymbol(VAddr crs_address); + + /** + * Resets all imported named symbols of this module to unresolved state. + * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. + */ + ResultCode ResetImportNamedSymbol(); + + /** + * Resets all imported indexed symbols of this module to unresolved state. + * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. + */ + ResultCode ResetImportIndexedSymbol(); + + /** + * Resets all imported anonymous symbols of this module to unresolved state. + * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. + */ + ResultCode ResetImportAnonymousSymbol(); + + /** + * Finds registered auto-link modules that this module imports, and resolves indexed and anonymous symbols exported by them. + * @param crs_address the virtual address of the static module + * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. + */ + ResultCode ApplyModuleImport(VAddr crs_address); + + /** + * Resolves target module's imported named symbols that exported by this module. + * @param target the module to resolve. + * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. + */ + ResultCode ApplyExportNamedSymbol(CROHelper target); + + /** + * Resets target's named symbols imported from this module to unresolved state. + * @param target the module to reset. + * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. + */ + ResultCode ResetExportNamedSymbol(CROHelper target); + + /** + * Resolves imported indexed and anonymous symbols in the target module which imports this module. + * @param target the module to resolve. + * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. + */ + ResultCode ApplyModuleExport(CROHelper target); + + /** + * Resets target's indexed and anonymous symbol imported from this module to unresolved state. + * @param target the module to reset. + * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. + */ + ResultCode ResetModuleExport(CROHelper target); + + /** + * Resolves the exit function in this module + * @param crs_address the virtual address of the static module. + * @returns ResultCode RESULT_SUCCESS on success, otherwise error code. + */ + ResultCode ApplyExitRelocations(VAddr crs_address); +}; + +} // namespace -- cgit v1.2.3