mirror of
https://github.com/citra-emu/citra.git
synced 2024-11-25 18:30:14 +00:00
LDR: CROHelper and infrastructure
This commit is contained in:
parent
d7898e269b
commit
469172c5c0
@ -2,9 +2,15 @@
|
|||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "common/alignment.h"
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "common/logging/log.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"
|
#include "core/hle/service/ldr_ro.h"
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
@ -12,6 +18,378 @@
|
|||||||
|
|
||||||
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<name>::value, "CRO structure " #name " doesn't use standard layout"); \
|
||||||
|
static_assert(std::is_trivially_copyable<name>::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<name>::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<ErrorDescription>(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<ErrorDescription>(15), ErrorModule::RO, ErrorSummary::Internal, ErrorLevel::Usage);
|
||||||
|
static const ResultCode ERROR_INVALID_MEMORY_STATE = // 0xD8A12C08
|
||||||
|
ResultCode(static_cast<ErrorDescription>(8), ErrorModule::RO, ErrorSummary::InvalidState, ErrorLevel::Permanent);
|
||||||
|
static const ResultCode ERROR_NOT_LOADED = // 0xD8A12C0D
|
||||||
|
ResultCode(static_cast<ErrorDescription>(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<ErrorDescription>(description), ErrorModule::RO, ErrorSummary::WrongArgument, ErrorLevel::Permanent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a loaded module (CRO) with interfaces manipulating it.
|
||||||
|
class CROHelper final {
|
||||||
|
const VAddr 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,
|
||||||
|
ExternalPatchTableOffset,
|
||||||
|
ExternalPatchNum,
|
||||||
|
ImportNamedSymbolTableOffset,
|
||||||
|
ImportNamedSymbolNum,
|
||||||
|
ImportIndexedSymbolTableOffset,
|
||||||
|
ImportIndexedSymbolNum,
|
||||||
|
ImportAnonymousSymbolTableOffset,
|
||||||
|
ImportAnonymousSymbolNum,
|
||||||
|
ImportStringsOffset,
|
||||||
|
ImportStringsSize,
|
||||||
|
|
||||||
|
StaticAnonymousSymbolTableOffset,
|
||||||
|
StaticAnonymousSymbolNum,
|
||||||
|
InternalPatchTableOffset,
|
||||||
|
InternalPatchNum,
|
||||||
|
StaticPatchTableOffset,
|
||||||
|
StaticPatchNum,
|
||||||
|
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;
|
||||||
|
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 other module.
|
||||||
|
struct ImportNamedSymbolEntry {
|
||||||
|
u32_le name_offset; // pointing to a substring in ImportStrings
|
||||||
|
u32_le patch_batch_offset; // pointing to a patch batch in ExternalPatchTable
|
||||||
|
|
||||||
|
static constexpr HeaderField TABLE_OFFSET_FIELD = ImportNamedSymbolTableOffset;
|
||||||
|
};
|
||||||
|
ASSERT_CRO_STRUCT(ImportNamedSymbolEntry, 8);
|
||||||
|
|
||||||
|
/// Identifies an indexed symbol imported from other module.
|
||||||
|
struct ImportIndexedSymbolEntry {
|
||||||
|
u32_le index; // index of an opponent's ExportIndexedSymbolEntry
|
||||||
|
u32_le patch_batch_offset; // pointing to a patch batch in ExternalPatchTable
|
||||||
|
|
||||||
|
static constexpr HeaderField TABLE_OFFSET_FIELD = ImportIndexedSymbolTableOffset;
|
||||||
|
};
|
||||||
|
ASSERT_CRO_STRUCT(ImportIndexedSymbolEntry, 8);
|
||||||
|
|
||||||
|
/// Identifies an anonymous symbol imported from other module.
|
||||||
|
struct ImportAnonymousSymbolEntry {
|
||||||
|
SegmentTag symbol_position; // to the opponent's segment
|
||||||
|
u32_le patch_batch_offset; // pointing to a patch batch in ExternalPatchTable
|
||||||
|
|
||||||
|
static constexpr HeaderField TABLE_OFFSET_FIELD = ImportAnonymousSymbolTableOffset;
|
||||||
|
};
|
||||||
|
ASSERT_CRO_STRUCT(ImportAnonymousSymbolEntry, 8);
|
||||||
|
|
||||||
|
/// Information of a referred module and symbols imported from it.
|
||||||
|
struct ImportModuleEntry {
|
||||||
|
u32_le name_offset; // pointing to a substring in ImporStrings
|
||||||
|
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 PatchType : u8 {
|
||||||
|
Nothing = 0,
|
||||||
|
AbsoluteAddress = 2,
|
||||||
|
RelativeAddress = 3,
|
||||||
|
ThumbBranch = 10,
|
||||||
|
ArmBranch = 28,
|
||||||
|
ModifyArmBranch = 29,
|
||||||
|
AbsoluteAddress2 = 38,
|
||||||
|
AlignedRelativeAddress = 42,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PatchEntry {
|
||||||
|
SegmentTag target_position; // to self's segment as an ExternalPatchEntry; to static module segment as a StaticPatchEntry
|
||||||
|
PatchType 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 shift;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Identifies a normal cross-module patch.
|
||||||
|
struct ExternalPatchEntry : PatchEntry {
|
||||||
|
static constexpr HeaderField TABLE_OFFSET_FIELD = ExternalPatchTableOffset;
|
||||||
|
};
|
||||||
|
ASSERT_CRO_STRUCT(ExternalPatchEntry, 12);
|
||||||
|
|
||||||
|
/// Identifies a special static patch (no game is known using this).
|
||||||
|
struct StaticPatchEntry : PatchEntry {
|
||||||
|
static constexpr HeaderField TABLE_OFFSET_FIELD = StaticPatchTableOffset;
|
||||||
|
};
|
||||||
|
ASSERT_CRO_STRUCT(StaticPatchEntry, 12);
|
||||||
|
|
||||||
|
/// Identifies a in-module patch.
|
||||||
|
struct InternalPatchEntry {
|
||||||
|
SegmentTag target_position; // to self's segment
|
||||||
|
PatchType type;
|
||||||
|
u8 symbol_segment;
|
||||||
|
INSERT_PADDING_BYTES(2);
|
||||||
|
u32_le shift;
|
||||||
|
|
||||||
|
static constexpr HeaderField TABLE_OFFSET_FIELD = InternalPatchTableOffset;
|
||||||
|
};
|
||||||
|
ASSERT_CRO_STRUCT(InternalPatchEntry, 12);
|
||||||
|
|
||||||
|
/// Identifies a special static anonymous symbol (no game is known using this).
|
||||||
|
struct StaticAnonymousSymbolEntry {
|
||||||
|
SegmentTag symbol_position; // to self's segment
|
||||||
|
u32_le patch_batch_offset; // pointing to a patch batch in StaticPatchTable
|
||||||
|
|
||||||
|
static constexpr HeaderField TABLE_OFFSET_FIELD = StaticAnonymousSymbolTableOffset;
|
||||||
|
};
|
||||||
|
ASSERT_CRO_STRUCT(StaticAnonymousSymbolEntry, 8);
|
||||||
|
|
||||||
|
static std::array<int, 17> ENTRY_SIZE;
|
||||||
|
static std::array<HeaderField, 4> FIX_BARRIERS;
|
||||||
|
|
||||||
|
static constexpr u32 MAGIC_CRO0 = 0x304F5243;
|
||||||
|
static constexpr u32 MAGIC_FIXD = 0x44584946;
|
||||||
|
|
||||||
|
VAddr Field(HeaderField field) const {
|
||||||
|
return 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 <typename T>
|
||||||
|
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 <typename T>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit CROHelper(VAddr cro_address) : address(cro_address) {
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ModuleName() const {
|
||||||
|
return Memory::ReadCString(GetField(ModuleNameOffset), GetField(ModuleNameSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 GetFileSize() const {
|
||||||
|
return GetField(FileSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
std::array<int, 17> CROHelper::ENTRY_SIZE {{
|
||||||
|
1, // code
|
||||||
|
1, // data
|
||||||
|
1, // module name
|
||||||
|
sizeof(SegmentEntry),
|
||||||
|
sizeof(ExportNamedSymbolEntry),
|
||||||
|
sizeof(ExportIndexedSymbolEntry),
|
||||||
|
1, // export strings
|
||||||
|
sizeof(ExportTreeEntry),
|
||||||
|
sizeof(ImportModuleEntry),
|
||||||
|
sizeof(ExternalPatchEntry),
|
||||||
|
sizeof(ImportNamedSymbolEntry),
|
||||||
|
sizeof(ImportIndexedSymbolEntry),
|
||||||
|
sizeof(ImportAnonymousSymbolEntry),
|
||||||
|
1, // import strings
|
||||||
|
sizeof(StaticAnonymousSymbolEntry),
|
||||||
|
sizeof(InternalPatchEntry),
|
||||||
|
sizeof(StaticPatchEntry)
|
||||||
|
}};
|
||||||
|
|
||||||
|
std::array<CROHelper::HeaderField, 4> CROHelper::FIX_BARRIERS {{
|
||||||
|
Fix0Barrier,
|
||||||
|
Fix1Barrier,
|
||||||
|
Fix2Barrier,
|
||||||
|
Fix3Barrier
|
||||||
|
}};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* LDR_RO::Initialize service function
|
* LDR_RO::Initialize service function
|
||||||
* Inputs:
|
* Inputs:
|
||||||
|
Loading…
Reference in New Issue
Block a user