summaryrefslogtreecommitdiffstats
path: root/src/core/hle/service/ldr
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/hle/service/ldr')
-rw-r--r--src/core/hle/service/ldr/ldr.cpp365
1 files changed, 215 insertions, 150 deletions
diff --git a/src/core/hle/service/ldr/ldr.cpp b/src/core/hle/service/ldr/ldr.cpp
index 647943020..73a2bc770 100644
--- a/src/core/hle/service/ldr/ldr.cpp
+++ b/src/core/hle/service/ldr/ldr.cpp
@@ -8,14 +8,21 @@
#include "common/alignment.h"
#include "common/hex_util.h"
+#include "common/scope_exit.h"
#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/errors.h"
+#include "core/hle/kernel/memory/page_table.h"
+#include "core/hle/kernel/memory/system_control.h"
#include "core/hle/kernel/process.h"
#include "core/hle/service/ldr/ldr.h"
#include "core/hle/service/service.h"
#include "core/loader/nro.h"
+#include "core/memory.h"
namespace Service::LDR {
+constexpr ResultCode ERROR_INSUFFICIENT_ADDRESS_SPACE{ErrorModule::RO, 2};
+
constexpr ResultCode ERROR_INVALID_MEMORY_STATE{ErrorModule::Loader, 51};
constexpr ResultCode ERROR_INVALID_NRO{ErrorModule::Loader, 52};
constexpr ResultCode ERROR_INVALID_NRR{ErrorModule::Loader, 53};
@@ -29,7 +36,61 @@ constexpr ResultCode ERROR_INVALID_NRO_ADDRESS{ErrorModule::Loader, 84};
constexpr ResultCode ERROR_INVALID_NRR_ADDRESS{ErrorModule::Loader, 85};
constexpr ResultCode ERROR_NOT_INITIALIZED{ErrorModule::Loader, 87};
-constexpr u64 MAXIMUM_LOADED_RO = 0x40;
+constexpr std::size_t MAXIMUM_LOADED_RO{0x40};
+constexpr std::size_t MAXIMUM_MAP_RETRIES{0x200};
+
+struct NRRHeader {
+ u32_le magic;
+ INSERT_PADDING_BYTES(12);
+ u64_le title_id_mask;
+ u64_le title_id_pattern;
+ INSERT_PADDING_BYTES(16);
+ std::array<u8, 0x100> modulus;
+ std::array<u8, 0x100> signature_1;
+ std::array<u8, 0x100> signature_2;
+ u64_le title_id;
+ u32_le size;
+ INSERT_PADDING_BYTES(4);
+ u32_le hash_offset;
+ u32_le hash_count;
+ INSERT_PADDING_BYTES(8);
+};
+static_assert(sizeof(NRRHeader) == 0x350, "NRRHeader has incorrect size.");
+
+struct NROHeader {
+ INSERT_PADDING_WORDS(1);
+ u32_le mod_offset;
+ INSERT_PADDING_WORDS(2);
+ u32_le magic;
+ u32_le version;
+ u32_le nro_size;
+ u32_le flags;
+ u32_le text_offset;
+ u32_le text_size;
+ u32_le ro_offset;
+ u32_le ro_size;
+ u32_le rw_offset;
+ u32_le rw_size;
+ u32_le bss_size;
+ INSERT_PADDING_WORDS(1);
+ std::array<u8, 0x20> build_id;
+ INSERT_PADDING_BYTES(0x20);
+};
+static_assert(sizeof(NROHeader) == 0x80, "NROHeader has invalid size.");
+
+using SHA256Hash = std::array<u8, 0x20>;
+
+struct NROInfo {
+ SHA256Hash hash{};
+ VAddr nro_address{};
+ std::size_t nro_size{};
+ VAddr bss_address{};
+ std::size_t bss_size{};
+ std::size_t text_size{};
+ std::size_t ro_size{};
+ std::size_t data_size{};
+ VAddr src_addr{};
+};
class DebugMonitor final : public ServiceFramework<DebugMonitor> {
public:
@@ -84,7 +145,7 @@ public:
{0, &RelocatableObject::LoadNro, "LoadNro"},
{1, &RelocatableObject::UnloadNro, "UnloadNro"},
{2, &RelocatableObject::LoadNrr, "LoadNrr"},
- {3, &RelocatableObject::UnloadNrr, "UnloadNrr"},
+ {3, nullptr, "UnloadNrr"},
{4, &RelocatableObject::Initialize, "Initialize"},
{10, nullptr, "LoadNrrEx"},
};
@@ -190,46 +251,127 @@ public:
rb.Push(RESULT_SUCCESS);
}
- void UnloadNrr(Kernel::HLERequestContext& ctx) {
- if (!initialized) {
- LOG_ERROR(Service_LDR, "LDR:RO not initialized before use!");
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERROR_NOT_INITIALIZED);
- return;
+ bool ValidateRegionForMap(Kernel::Memory::PageTable& page_table, VAddr start,
+ std::size_t size) const {
+ constexpr std::size_t padding_size{4 * Kernel::Memory::PageSize};
+ const auto start_info{page_table.QueryInfo(start - 1)};
+
+ if (start_info.state != Kernel::Memory::MemoryState::Free) {
+ return {};
}
- struct Parameters {
- u64_le process_id;
- u64_le nrr_address;
- };
+ if (start_info.GetAddress() > (start - padding_size)) {
+ return {};
+ }
- IPC::RequestParser rp{ctx};
- const auto [process_id, nrr_address] = rp.PopRaw<Parameters>();
+ const auto end_info{page_table.QueryInfo(start + size)};
- LOG_DEBUG(Service_LDR, "called with process_id={:016X}, nrr_addr={:016X}", process_id,
- nrr_address);
+ if (end_info.state != Kernel::Memory::MemoryState::Free) {
+ return {};
+ }
- if (!Common::Is4KBAligned(nrr_address)) {
- LOG_ERROR(Service_LDR, "NRR Address has invalid alignment (actual {:016X})!",
- nrr_address);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERROR_INVALID_ALIGNMENT);
- return;
+ return (start + size + padding_size) <= (end_info.GetAddress() + end_info.GetSize());
+ }
+
+ VAddr GetRandomMapRegion(const Kernel::Memory::PageTable& page_table, std::size_t size) const {
+ VAddr addr{};
+ const std::size_t end_pages{(page_table.GetAliasCodeRegionSize() - size) >>
+ Kernel::Memory::PageBits};
+ do {
+ addr = page_table.GetAliasCodeRegionStart() +
+ (Kernel::Memory::SystemControl::GenerateRandomRange(0, end_pages)
+ << Kernel::Memory::PageBits);
+ } while (!page_table.IsInsideAddressSpace(addr, size) ||
+ page_table.IsInsideHeapRegion(addr, size) ||
+ page_table.IsInsideAliasRegion(addr, size));
+ return addr;
+ }
+
+ ResultVal<VAddr> MapProcessCodeMemory(Kernel::Process* process, VAddr baseAddress,
+ u64 size) const {
+ for (int retry{}; retry < MAXIMUM_MAP_RETRIES; retry++) {
+ auto& page_table{process->PageTable()};
+ const VAddr addr{GetRandomMapRegion(page_table, size)};
+ const ResultCode result{page_table.MapProcessCodeMemory(addr, baseAddress, size)};
+
+ if (result == Kernel::ERR_INVALID_ADDRESS_STATE) {
+ continue;
+ }
+
+ if (result.IsError()) {
+ return result;
+ }
+
+ if (ValidateRegionForMap(page_table, addr, size)) {
+ return MakeResult<VAddr>(addr);
+ }
}
- const auto iter = nrr.find(nrr_address);
- if (iter == nrr.end()) {
- LOG_ERROR(Service_LDR,
- "Attempting to unload NRR which has not been loaded! (addr={:016X})",
- nrr_address);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERROR_INVALID_NRR_ADDRESS);
- return;
+ return ERROR_INSUFFICIENT_ADDRESS_SPACE;
+ }
+
+ ResultVal<VAddr> MapNro(Kernel::Process* process, VAddr nro_addr, std::size_t nro_size,
+ VAddr bss_addr, std::size_t bss_size, std::size_t size) const {
+
+ for (int retry{}; retry < MAXIMUM_MAP_RETRIES; retry++) {
+ auto& page_table{process->PageTable()};
+ VAddr addr{};
+
+ CASCADE_RESULT(addr, MapProcessCodeMemory(process, nro_addr, nro_size));
+
+ if (bss_size) {
+ auto block_guard = detail::ScopeExit([&] {
+ page_table.UnmapProcessCodeMemory(addr + nro_size, bss_addr, bss_size);
+ page_table.UnmapProcessCodeMemory(addr, nro_addr, nro_size);
+ });
+
+ const ResultCode result{
+ page_table.MapProcessCodeMemory(addr + nro_size, bss_addr, bss_size)};
+
+ if (result == Kernel::ERR_INVALID_ADDRESS_STATE) {
+ continue;
+ }
+
+ if (result.IsError()) {
+ return result;
+ }
+
+ block_guard.Cancel();
+ }
+
+ if (ValidateRegionForMap(page_table, addr, size)) {
+ return MakeResult<VAddr>(addr);
+ }
}
- nrr.erase(iter);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
+ return ERROR_INSUFFICIENT_ADDRESS_SPACE;
+ }
+
+ ResultCode LoadNro(Kernel::Process* process, const NROHeader& nro_header, VAddr nro_addr,
+ VAddr start) const {
+ const VAddr text_start{start + nro_header.text_offset};
+ const VAddr ro_start{start + nro_header.ro_offset};
+ const VAddr data_start{start + nro_header.rw_offset};
+ const VAddr bss_start{data_start + nro_header.rw_size};
+ const VAddr bss_end_addr{
+ Common::AlignUp(bss_start + nro_header.bss_size, Kernel::Memory::PageSize)};
+
+ auto CopyCode{[&](VAddr src_addr, VAddr dst_addr, u64 size) {
+ std::vector<u8> source_data(size);
+ system.Memory().ReadBlock(src_addr, source_data.data(), source_data.size());
+ system.Memory().WriteBlock(dst_addr, source_data.data(), source_data.size());
+ }};
+ CopyCode(nro_addr + nro_header.text_offset, text_start, nro_header.text_size);
+ CopyCode(nro_addr + nro_header.ro_offset, ro_start, nro_header.ro_size);
+ CopyCode(nro_addr + nro_header.rw_offset, data_start, nro_header.rw_size);
+
+ CASCADE_CODE(process->PageTable().SetCodeMemoryPermission(
+ text_start, ro_start - text_start, Kernel::Memory::MemoryPermission::ReadAndExecute));
+ CASCADE_CODE(process->PageTable().SetCodeMemoryPermission(
+ ro_start, data_start - ro_start, Kernel::Memory::MemoryPermission::Read));
+
+ return process->PageTable().SetCodeMemoryPermission(
+ data_start, bss_end_addr - data_start, Kernel::Memory::MemoryPermission::ReadAndWrite);
}
void LoadNro(Kernel::HLERequestContext& ctx) {
@@ -317,9 +459,9 @@ public:
return;
}
- NROHeader header;
+ // Load and validate the NRO header
+ NROHeader header{};
std::memcpy(&header, nro_data.data(), sizeof(NROHeader));
-
if (!IsValidNRO(header, nro_size, bss_size)) {
LOG_ERROR(Service_LDR, "NRO was invalid!");
IPC::ResponseBuilder rb{ctx, 2};
@@ -327,62 +469,49 @@ public:
return;
}
- // Load NRO as new executable module
- auto* process = system.CurrentProcess();
- auto& vm_manager = process->VMManager();
- auto map_address = vm_manager.FindFreeRegion(nro_size + bss_size);
-
- if (!map_address.Succeeded() ||
- *map_address + nro_size + bss_size > vm_manager.GetAddressSpaceEndAddress()) {
-
- LOG_ERROR(Service_LDR,
- "General error while allocation memory or no available memory to allocate!");
+ // Map memory for the NRO
+ const ResultVal<VAddr> map_result{MapNro(system.CurrentProcess(), nro_address, nro_size,
+ bss_address, bss_size, nro_size + bss_size)};
+ if (map_result.Failed()) {
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERROR_INVALID_MEMORY_STATE);
- return;
+ rb.Push(map_result.Code());
}
- // Mark text and read-only region as ModuleCode
- ASSERT(vm_manager
- .MirrorMemory(*map_address, nro_address, header.text_size + header.ro_size,
- Kernel::MemoryState::ModuleCode)
- .IsSuccess());
- // Mark read/write region as ModuleCodeData, which is necessary if this region is used for
- // TransferMemory (e.g. Final Fantasy VIII Remastered does this)
- ASSERT(vm_manager
- .MirrorMemory(*map_address + header.rw_offset, nro_address + header.rw_offset,
- header.rw_size, Kernel::MemoryState::ModuleCodeData)
- .IsSuccess());
- // Revoke permissions from the old memory region
- ASSERT(vm_manager.ReprotectRange(nro_address, nro_size, Kernel::VMAPermission::None)
- .IsSuccess());
-
- if (bss_size > 0) {
- // Mark BSS region as ModuleCodeData, which is necessary if this region is used for
- // TransferMemory (e.g. Final Fantasy VIII Remastered does this)
- ASSERT(vm_manager
- .MirrorMemory(*map_address + nro_size, bss_address, bss_size,
- Kernel::MemoryState::ModuleCodeData)
- .IsSuccess());
- ASSERT(vm_manager.ReprotectRange(bss_address, bss_size, Kernel::VMAPermission::None)
- .IsSuccess());
+ // Load the NRO into the mapped memory
+ if (const ResultCode result{
+ LoadNro(system.CurrentProcess(), header, nro_address, *map_result)};
+ result.IsError()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(map_result.Code());
}
- vm_manager.ReprotectRange(*map_address, header.text_size,
- Kernel::VMAPermission::ReadExecute);
- vm_manager.ReprotectRange(*map_address + header.ro_offset, header.ro_size,
- Kernel::VMAPermission::Read);
- vm_manager.ReprotectRange(*map_address + header.rw_offset, header.rw_size,
- Kernel::VMAPermission::ReadWrite);
+ // Track the loaded NRO
+ nro.insert_or_assign(*map_result, NROInfo{hash, *map_result, nro_size, bss_address,
+ bss_size, header.text_size, header.ro_size,
+ header.rw_size, nro_address});
+ // Invalidate JIT caches for the newly mapped process code
system.InvalidateCpuInstructionCaches();
- nro.insert_or_assign(*map_address,
- NROInfo{hash, nro_address, nro_size, bss_address, bss_size});
-
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
- rb.Push(*map_address);
+ rb.Push(*map_result);
+ }
+
+ ResultCode UnmapNro(const NROInfo& info) {
+ // Each region must be unmapped separately to validate memory state
+ auto& page_table{system.CurrentProcess()->PageTable()};
+ CASCADE_CODE(page_table.UnmapProcessCodeMemory(info.nro_address + info.text_size +
+ info.ro_size + info.data_size,
+ info.bss_address, info.bss_size));
+ CASCADE_CODE(page_table.UnmapProcessCodeMemory(
+ info.nro_address + info.text_size + info.ro_size,
+ info.src_addr + info.text_size + info.ro_size, info.data_size));
+ CASCADE_CODE(page_table.UnmapProcessCodeMemory(
+ info.nro_address + info.text_size, info.src_addr + info.text_size, info.ro_size));
+ CASCADE_CODE(
+ page_table.UnmapProcessCodeMemory(info.nro_address, info.src_addr, info.text_size));
+ return RESULT_SUCCESS;
}
void UnloadNro(Kernel::HLERequestContext& ctx) {
@@ -422,30 +551,15 @@ public:
return;
}
- auto& vm_manager = system.CurrentProcess()->VMManager();
- const auto& nro_info = iter->second;
-
- // Unmap the mirrored memory
- ASSERT(
- vm_manager.UnmapRange(nro_address, nro_info.nro_size + nro_info.bss_size).IsSuccess());
-
- // Reprotect the source memory
- ASSERT(vm_manager
- .ReprotectRange(nro_info.nro_address, nro_info.nro_size,
- Kernel::VMAPermission::ReadWrite)
- .IsSuccess());
- if (nro_info.bss_size > 0) {
- ASSERT(vm_manager
- .ReprotectRange(nro_info.bss_address, nro_info.bss_size,
- Kernel::VMAPermission::ReadWrite)
- .IsSuccess());
- }
+ const ResultCode result{UnmapNro(iter->second)};
system.InvalidateCpuInstructionCaches();
nro.erase(iter);
+
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
+
+ rb.Push(result);
}
void Initialize(Kernel::HLERequestContext& ctx) {
@@ -458,56 +572,7 @@ public:
}
private:
- using SHA256Hash = std::array<u8, 0x20>;
-
- struct NROHeader {
- INSERT_PADDING_WORDS(1);
- u32_le mod_offset;
- INSERT_PADDING_WORDS(2);
- u32_le magic;
- u32_le version;
- u32_le nro_size;
- u32_le flags;
- u32_le text_offset;
- u32_le text_size;
- u32_le ro_offset;
- u32_le ro_size;
- u32_le rw_offset;
- u32_le rw_size;
- u32_le bss_size;
- INSERT_PADDING_WORDS(1);
- std::array<u8, 0x20> build_id;
- INSERT_PADDING_BYTES(0x20);
- };
- static_assert(sizeof(NROHeader) == 0x80, "NROHeader has invalid size.");
-
- struct NRRHeader {
- u32_le magic;
- INSERT_PADDING_BYTES(12);
- u64_le title_id_mask;
- u64_le title_id_pattern;
- INSERT_PADDING_BYTES(16);
- std::array<u8, 0x100> modulus;
- std::array<u8, 0x100> signature_1;
- std::array<u8, 0x100> signature_2;
- u64_le title_id;
- u32_le size;
- INSERT_PADDING_BYTES(4);
- u32_le hash_offset;
- u32_le hash_count;
- INSERT_PADDING_BYTES(8);
- };
- static_assert(sizeof(NRRHeader) == 0x350, "NRRHeader has incorrect size.");
-
- struct NROInfo {
- SHA256Hash hash;
- VAddr nro_address;
- u64 nro_size;
- VAddr bss_address;
- u64 bss_size;
- };
-
- bool initialized = false;
+ bool initialized{};
std::map<VAddr, NROInfo> nro;
std::map<VAddr, std::vector<SHA256Hash>> nrr;