LDR: CROHelper and infrastructure

This commit is contained in:
wwylele 2016-07-15 09:17:01 +03:00
parent d7898e269b
commit 469172c5c0

View File

@ -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: