citra-emu
/
citra
Archived
1
0
Fork 0

LDR: Implement CRO

This commit is contained in:
wwylele 2016-07-15 09:17:01 +03:00
parent 2161f52661
commit 1c9612b791
9 changed files with 3013 additions and 99 deletions

View File

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

View File

@ -1,96 +0,0 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/common_types.h"
#include "common/logging/log.h"
#include "core/hle/service/ldr_ro.h"
////////////////////////////////////////////////////////////////////////////////////////////////////
// Namespace LDR_RO
namespace LDR_RO {
/**
* LDR_RO::Initialize service function
* Inputs:
* 1 : CRS buffer pointer
* 2 : CRS Size
* 3 : Process memory address where the CRS will be mapped
* 4 : Value, must be zero
* 5 : KProcess handle
* Outputs:
* 0 : Return header
* 1 : Result of function, 0 on success, otherwise error code
*/
static void Initialize(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
u32 crs_buffer_ptr = cmd_buff[1];
u32 crs_size = cmd_buff[2];
u32 address = cmd_buff[3];
u32 value = cmd_buff[4];
u32 process = cmd_buff[5];
if (value != 0) {
LOG_ERROR(Service_LDR, "This value should be zero, but is actually %u!", value);
}
// TODO(purpasmart96): Verify return header on HW
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
LOG_WARNING(Service_LDR, "(STUBBED) called. crs_buffer_ptr=0x%08X, crs_size=0x%08X, address=0x%08X, value=0x%08X, process=0x%08X",
crs_buffer_ptr, crs_size, address, value, process);
}
/**
* LDR_RO::LoadCRR service function
* Inputs:
* 1 : CRS buffer pointer
* 2 : CRS Size
* 3 : Value, must be zero
* 4 : KProcess handle
* Outputs:
* 0 : Return header
* 1 : Result of function, 0 on success, otherwise error code
*/
static void LoadCRR(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
u32 crs_buffer_ptr = cmd_buff[1];
u32 crs_size = cmd_buff[2];
u32 value = cmd_buff[3];
u32 process = cmd_buff[4];
if (value != 0) {
LOG_ERROR(Service_LDR, "This value should be zero, but is actually %u!", value);
}
// TODO(purpasmart96): Verify return header on HW
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
LOG_WARNING(Service_LDR, "(STUBBED) called. crs_buffer_ptr=0x%08X, crs_size=0x%08X, value=0x%08X, process=0x%08X",
crs_buffer_ptr, crs_size, value, process);
}
const Interface::FunctionInfo FunctionTable[] = {
{0x000100C2, Initialize, "Initialize"},
{0x00020082, LoadCRR, "LoadCRR"},
{0x00030042, nullptr, "UnloadCCR"},
{0x000402C2, nullptr, "LoadExeCRO"},
{0x000500C2, nullptr, "LoadCROSymbols"},
{0x00060042, nullptr, "CRO_Load?"},
{0x00070042, nullptr, "LoadCROSymbols"},
{0x00080042, nullptr, "Shutdown"},
{0x000902C2, nullptr, "LoadExeCRO_New?"},
};
////////////////////////////////////////////////////////////////////////////////////////////////////
// Interface class
Interface::Interface() {
Register(FunctionTable);
}
} // namespace

File diff suppressed because it is too large Load Diff

View File

@ -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 <array>
#include <tuple>
#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<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;
/// 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<VAddr, u32> 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<int, 17> ENTRY_SIZE;
/// The offset field of the table where to crop for each fix level
static const std::array<HeaderField, 4> 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 <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;
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<bool>. 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 <typename FunctionObject>
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<VAddr> with the virtual address of .data segment in CRO.
*/
ResultVal<VAddr> 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

View File

@ -0,0 +1,748 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/alignment.h"
#include "common/common_types.h"
#include "common/logging/log.h"
#include "core/arm/arm_interface.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/vm_manager.h"
#include "core/hle/service/ldr_ro/cro_helper.h"
#include "core/hle/service/ldr_ro/ldr_ro.h"
#include "core/hle/service/ldr_ro/memory_synchronizer.h"
////////////////////////////////////////////////////////////////////////////////////////////////////
// Namespace LDR_RO
namespace LDR_RO {
static const ResultCode ERROR_ALREADY_INITIALIZED = // 0xD9612FF9
ResultCode(ErrorDescription::AlreadyInitialized, ErrorModule::RO, ErrorSummary::Internal, ErrorLevel::Permanent);
static const ResultCode ERROR_NOT_INITIALIZED = // 0xD9612FF8
ResultCode(ErrorDescription::NotInitialized, ErrorModule::RO, ErrorSummary::Internal, ErrorLevel::Permanent);
static const ResultCode ERROR_BUFFER_TOO_SMALL = // 0xE0E12C1F
ResultCode(static_cast<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 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<std::vector<u8>> crs_mem = std::make_shared<std::vector<u8>>(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<ErrorDescription>(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<std::vector<u8>> cro_mem = std::make_shared<std::vector<u8>>(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 <bool link_on_load_bug_fix>
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<false>, "LoadCRO"},
{0x000500C2, UnloadCRO, "UnloadCRO"},
{0x00060042, LinkCRO, "LinkCRO"},
{0x00070042, UnlinkCRO, "UnlinkCRO"},
{0x00080042, Shutdown, "Shutdown"},
{0x000902C2, LoadCRO<true>, "LoadCRO_New"},
};
////////////////////////////////////////////////////////////////////////////////////////////////////
// Interface class
Interface::Interface() {
Register(FunctionTable);
loaded_crs = 0;
memory_synchronizer.Clear();
}
} // namespace

View File

@ -0,0 +1,46 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#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

View File

@ -0,0 +1,44 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <vector>
#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<MemoryBlock> memory_blocks;
auto FindMemoryBlock(VAddr mapping, VAddr original);
};
} // namespace

View File

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