summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-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 @@
8 8
9#include "common/alignment.h" 9#include "common/alignment.h"
10#include "common/hex_util.h" 10#include "common/hex_util.h"
11#include "common/scope_exit.h"
11#include "core/hle/ipc_helpers.h" 12#include "core/hle/ipc_helpers.h"
13#include "core/hle/kernel/errors.h"
14#include "core/hle/kernel/memory/page_table.h"
15#include "core/hle/kernel/memory/system_control.h"
12#include "core/hle/kernel/process.h" 16#include "core/hle/kernel/process.h"
13#include "core/hle/service/ldr/ldr.h" 17#include "core/hle/service/ldr/ldr.h"
14#include "core/hle/service/service.h" 18#include "core/hle/service/service.h"
15#include "core/loader/nro.h" 19#include "core/loader/nro.h"
20#include "core/memory.h"
16 21
17namespace Service::LDR { 22namespace Service::LDR {
18 23
24constexpr ResultCode ERROR_INSUFFICIENT_ADDRESS_SPACE{ErrorModule::RO, 2};
25
19constexpr ResultCode ERROR_INVALID_MEMORY_STATE{ErrorModule::Loader, 51}; 26constexpr ResultCode ERROR_INVALID_MEMORY_STATE{ErrorModule::Loader, 51};
20constexpr ResultCode ERROR_INVALID_NRO{ErrorModule::Loader, 52}; 27constexpr ResultCode ERROR_INVALID_NRO{ErrorModule::Loader, 52};
21constexpr ResultCode ERROR_INVALID_NRR{ErrorModule::Loader, 53}; 28constexpr ResultCode ERROR_INVALID_NRR{ErrorModule::Loader, 53};
@@ -29,7 +36,61 @@ constexpr ResultCode ERROR_INVALID_NRO_ADDRESS{ErrorModule::Loader, 84};
29constexpr ResultCode ERROR_INVALID_NRR_ADDRESS{ErrorModule::Loader, 85}; 36constexpr ResultCode ERROR_INVALID_NRR_ADDRESS{ErrorModule::Loader, 85};
30constexpr ResultCode ERROR_NOT_INITIALIZED{ErrorModule::Loader, 87}; 37constexpr ResultCode ERROR_NOT_INITIALIZED{ErrorModule::Loader, 87};
31 38
32constexpr u64 MAXIMUM_LOADED_RO = 0x40; 39constexpr std::size_t MAXIMUM_LOADED_RO{0x40};
40constexpr std::size_t MAXIMUM_MAP_RETRIES{0x200};
41
42struct NRRHeader {
43 u32_le magic;
44 INSERT_PADDING_BYTES(12);
45 u64_le title_id_mask;
46 u64_le title_id_pattern;
47 INSERT_PADDING_BYTES(16);
48 std::array<u8, 0x100> modulus;
49 std::array<u8, 0x100> signature_1;
50 std::array<u8, 0x100> signature_2;
51 u64_le title_id;
52 u32_le size;
53 INSERT_PADDING_BYTES(4);
54 u32_le hash_offset;
55 u32_le hash_count;
56 INSERT_PADDING_BYTES(8);
57};
58static_assert(sizeof(NRRHeader) == 0x350, "NRRHeader has incorrect size.");
59
60struct NROHeader {
61 INSERT_PADDING_WORDS(1);
62 u32_le mod_offset;
63 INSERT_PADDING_WORDS(2);
64 u32_le magic;
65 u32_le version;
66 u32_le nro_size;
67 u32_le flags;
68 u32_le text_offset;
69 u32_le text_size;
70 u32_le ro_offset;
71 u32_le ro_size;
72 u32_le rw_offset;
73 u32_le rw_size;
74 u32_le bss_size;
75 INSERT_PADDING_WORDS(1);
76 std::array<u8, 0x20> build_id;
77 INSERT_PADDING_BYTES(0x20);
78};
79static_assert(sizeof(NROHeader) == 0x80, "NROHeader has invalid size.");
80
81using SHA256Hash = std::array<u8, 0x20>;
82
83struct NROInfo {
84 SHA256Hash hash{};
85 VAddr nro_address{};
86 std::size_t nro_size{};
87 VAddr bss_address{};
88 std::size_t bss_size{};
89 std::size_t text_size{};
90 std::size_t ro_size{};
91 std::size_t data_size{};
92 VAddr src_addr{};
93};
33 94
34class DebugMonitor final : public ServiceFramework<DebugMonitor> { 95class DebugMonitor final : public ServiceFramework<DebugMonitor> {
35public: 96public:
@@ -84,7 +145,7 @@ public:
84 {0, &RelocatableObject::LoadNro, "LoadNro"}, 145 {0, &RelocatableObject::LoadNro, "LoadNro"},
85 {1, &RelocatableObject::UnloadNro, "UnloadNro"}, 146 {1, &RelocatableObject::UnloadNro, "UnloadNro"},
86 {2, &RelocatableObject::LoadNrr, "LoadNrr"}, 147 {2, &RelocatableObject::LoadNrr, "LoadNrr"},
87 {3, &RelocatableObject::UnloadNrr, "UnloadNrr"}, 148 {3, nullptr, "UnloadNrr"},
88 {4, &RelocatableObject::Initialize, "Initialize"}, 149 {4, &RelocatableObject::Initialize, "Initialize"},
89 {10, nullptr, "LoadNrrEx"}, 150 {10, nullptr, "LoadNrrEx"},
90 }; 151 };
@@ -190,46 +251,127 @@ public:
190 rb.Push(RESULT_SUCCESS); 251 rb.Push(RESULT_SUCCESS);
191 } 252 }
192 253
193 void UnloadNrr(Kernel::HLERequestContext& ctx) { 254 bool ValidateRegionForMap(Kernel::Memory::PageTable& page_table, VAddr start,
194 if (!initialized) { 255 std::size_t size) const {
195 LOG_ERROR(Service_LDR, "LDR:RO not initialized before use!"); 256 constexpr std::size_t padding_size{4 * Kernel::Memory::PageSize};
196 IPC::ResponseBuilder rb{ctx, 2}; 257 const auto start_info{page_table.QueryInfo(start - 1)};
197 rb.Push(ERROR_NOT_INITIALIZED); 258
198 return; 259 if (start_info.state != Kernel::Memory::MemoryState::Free) {
260 return {};
199 } 261 }
200 262
201 struct Parameters { 263 if (start_info.GetAddress() > (start - padding_size)) {
202 u64_le process_id; 264 return {};
203 u64_le nrr_address; 265 }
204 };
205 266
206 IPC::RequestParser rp{ctx}; 267 const auto end_info{page_table.QueryInfo(start + size)};
207 const auto [process_id, nrr_address] = rp.PopRaw<Parameters>();
208 268
209 LOG_DEBUG(Service_LDR, "called with process_id={:016X}, nrr_addr={:016X}", process_id, 269 if (end_info.state != Kernel::Memory::MemoryState::Free) {
210 nrr_address); 270 return {};
271 }
211 272
212 if (!Common::Is4KBAligned(nrr_address)) { 273 return (start + size + padding_size) <= (end_info.GetAddress() + end_info.GetSize());
213 LOG_ERROR(Service_LDR, "NRR Address has invalid alignment (actual {:016X})!", 274 }
214 nrr_address); 275
215 IPC::ResponseBuilder rb{ctx, 2}; 276 VAddr GetRandomMapRegion(const Kernel::Memory::PageTable& page_table, std::size_t size) const {
216 rb.Push(ERROR_INVALID_ALIGNMENT); 277 VAddr addr{};
217 return; 278 const std::size_t end_pages{(page_table.GetAliasCodeRegionSize() - size) >>
279 Kernel::Memory::PageBits};
280 do {
281 addr = page_table.GetAliasCodeRegionStart() +
282 (Kernel::Memory::SystemControl::GenerateRandomRange(0, end_pages)
283 << Kernel::Memory::PageBits);
284 } while (!page_table.IsInsideAddressSpace(addr, size) ||
285 page_table.IsInsideHeapRegion(addr, size) ||
286 page_table.IsInsideAliasRegion(addr, size));
287 return addr;
288 }
289
290 ResultVal<VAddr> MapProcessCodeMemory(Kernel::Process* process, VAddr baseAddress,
291 u64 size) const {
292 for (int retry{}; retry < MAXIMUM_MAP_RETRIES; retry++) {
293 auto& page_table{process->PageTable()};
294 const VAddr addr{GetRandomMapRegion(page_table, size)};
295 const ResultCode result{page_table.MapProcessCodeMemory(addr, baseAddress, size)};
296
297 if (result == Kernel::ERR_INVALID_ADDRESS_STATE) {
298 continue;
299 }
300
301 if (result.IsError()) {
302 return result;
303 }
304
305 if (ValidateRegionForMap(page_table, addr, size)) {
306 return MakeResult<VAddr>(addr);
307 }
218 } 308 }
219 309
220 const auto iter = nrr.find(nrr_address); 310 return ERROR_INSUFFICIENT_ADDRESS_SPACE;
221 if (iter == nrr.end()) { 311 }
222 LOG_ERROR(Service_LDR, 312
223 "Attempting to unload NRR which has not been loaded! (addr={:016X})", 313 ResultVal<VAddr> MapNro(Kernel::Process* process, VAddr nro_addr, std::size_t nro_size,
224 nrr_address); 314 VAddr bss_addr, std::size_t bss_size, std::size_t size) const {
225 IPC::ResponseBuilder rb{ctx, 2}; 315
226 rb.Push(ERROR_INVALID_NRR_ADDRESS); 316 for (int retry{}; retry < MAXIMUM_MAP_RETRIES; retry++) {
227 return; 317 auto& page_table{process->PageTable()};
318 VAddr addr{};
319
320 CASCADE_RESULT(addr, MapProcessCodeMemory(process, nro_addr, nro_size));
321
322 if (bss_size) {
323 auto block_guard = detail::ScopeExit([&] {
324 page_table.UnmapProcessCodeMemory(addr + nro_size, bss_addr, bss_size);
325 page_table.UnmapProcessCodeMemory(addr, nro_addr, nro_size);
326 });
327
328 const ResultCode result{
329 page_table.MapProcessCodeMemory(addr + nro_size, bss_addr, bss_size)};
330
331 if (result == Kernel::ERR_INVALID_ADDRESS_STATE) {
332 continue;
333 }
334
335 if (result.IsError()) {
336 return result;
337 }
338
339 block_guard.Cancel();
340 }
341
342 if (ValidateRegionForMap(page_table, addr, size)) {
343 return MakeResult<VAddr>(addr);
344 }
228 } 345 }
229 346
230 nrr.erase(iter); 347 return ERROR_INSUFFICIENT_ADDRESS_SPACE;
231 IPC::ResponseBuilder rb{ctx, 2}; 348 }
232 rb.Push(RESULT_SUCCESS); 349
350 ResultCode LoadNro(Kernel::Process* process, const NROHeader& nro_header, VAddr nro_addr,
351 VAddr start) const {
352 const VAddr text_start{start + nro_header.text_offset};
353 const VAddr ro_start{start + nro_header.ro_offset};
354 const VAddr data_start{start + nro_header.rw_offset};
355 const VAddr bss_start{data_start + nro_header.rw_size};
356 const VAddr bss_end_addr{
357 Common::AlignUp(bss_start + nro_header.bss_size, Kernel::Memory::PageSize)};
358
359 auto CopyCode{[&](VAddr src_addr, VAddr dst_addr, u64 size) {
360 std::vector<u8> source_data(size);
361 system.Memory().ReadBlock(src_addr, source_data.data(), source_data.size());
362 system.Memory().WriteBlock(dst_addr, source_data.data(), source_data.size());
363 }};
364 CopyCode(nro_addr + nro_header.text_offset, text_start, nro_header.text_size);
365 CopyCode(nro_addr + nro_header.ro_offset, ro_start, nro_header.ro_size);
366 CopyCode(nro_addr + nro_header.rw_offset, data_start, nro_header.rw_size);
367
368 CASCADE_CODE(process->PageTable().SetCodeMemoryPermission(
369 text_start, ro_start - text_start, Kernel::Memory::MemoryPermission::ReadAndExecute));
370 CASCADE_CODE(process->PageTable().SetCodeMemoryPermission(
371 ro_start, data_start - ro_start, Kernel::Memory::MemoryPermission::Read));
372
373 return process->PageTable().SetCodeMemoryPermission(
374 data_start, bss_end_addr - data_start, Kernel::Memory::MemoryPermission::ReadAndWrite);
233 } 375 }
234 376
235 void LoadNro(Kernel::HLERequestContext& ctx) { 377 void LoadNro(Kernel::HLERequestContext& ctx) {
@@ -317,9 +459,9 @@ public:
317 return; 459 return;
318 } 460 }
319 461
320 NROHeader header; 462 // Load and validate the NRO header
463 NROHeader header{};
321 std::memcpy(&header, nro_data.data(), sizeof(NROHeader)); 464 std::memcpy(&header, nro_data.data(), sizeof(NROHeader));
322
323 if (!IsValidNRO(header, nro_size, bss_size)) { 465 if (!IsValidNRO(header, nro_size, bss_size)) {
324 LOG_ERROR(Service_LDR, "NRO was invalid!"); 466 LOG_ERROR(Service_LDR, "NRO was invalid!");
325 IPC::ResponseBuilder rb{ctx, 2}; 467 IPC::ResponseBuilder rb{ctx, 2};
@@ -327,62 +469,49 @@ public:
327 return; 469 return;
328 } 470 }
329 471
330 // Load NRO as new executable module 472 // Map memory for the NRO
331 auto* process = system.CurrentProcess(); 473 const ResultVal<VAddr> map_result{MapNro(system.CurrentProcess(), nro_address, nro_size,
332 auto& vm_manager = process->VMManager(); 474 bss_address, bss_size, nro_size + bss_size)};
333 auto map_address = vm_manager.FindFreeRegion(nro_size + bss_size); 475 if (map_result.Failed()) {
334
335 if (!map_address.Succeeded() ||
336 *map_address + nro_size + bss_size > vm_manager.GetAddressSpaceEndAddress()) {
337
338 LOG_ERROR(Service_LDR,
339 "General error while allocation memory or no available memory to allocate!");
340 IPC::ResponseBuilder rb{ctx, 2}; 476 IPC::ResponseBuilder rb{ctx, 2};
341 rb.Push(ERROR_INVALID_MEMORY_STATE); 477 rb.Push(map_result.Code());
342 return;
343 } 478 }
344 479
345 // Mark text and read-only region as ModuleCode 480 // Load the NRO into the mapped memory
346 ASSERT(vm_manager 481 if (const ResultCode result{
347 .MirrorMemory(*map_address, nro_address, header.text_size + header.ro_size, 482 LoadNro(system.CurrentProcess(), header, nro_address, *map_result)};
348 Kernel::MemoryState::ModuleCode) 483 result.IsError()) {
349 .IsSuccess()); 484 IPC::ResponseBuilder rb{ctx, 2};
350 // Mark read/write region as ModuleCodeData, which is necessary if this region is used for 485 rb.Push(map_result.Code());
351 // TransferMemory (e.g. Final Fantasy VIII Remastered does this)
352 ASSERT(vm_manager
353 .MirrorMemory(*map_address + header.rw_offset, nro_address + header.rw_offset,
354 header.rw_size, Kernel::MemoryState::ModuleCodeData)
355 .IsSuccess());
356 // Revoke permissions from the old memory region
357 ASSERT(vm_manager.ReprotectRange(nro_address, nro_size, Kernel::VMAPermission::None)
358 .IsSuccess());
359
360 if (bss_size > 0) {
361 // Mark BSS region as ModuleCodeData, which is necessary if this region is used for
362 // TransferMemory (e.g. Final Fantasy VIII Remastered does this)
363 ASSERT(vm_manager
364 .MirrorMemory(*map_address + nro_size, bss_address, bss_size,
365 Kernel::MemoryState::ModuleCodeData)
366 .IsSuccess());
367 ASSERT(vm_manager.ReprotectRange(bss_address, bss_size, Kernel::VMAPermission::None)
368 .IsSuccess());
369 } 486 }
370 487
371 vm_manager.ReprotectRange(*map_address, header.text_size, 488 // Track the loaded NRO
372 Kernel::VMAPermission::ReadExecute); 489 nro.insert_or_assign(*map_result, NROInfo{hash, *map_result, nro_size, bss_address,
373 vm_manager.ReprotectRange(*map_address + header.ro_offset, header.ro_size, 490 bss_size, header.text_size, header.ro_size,
374 Kernel::VMAPermission::Read); 491 header.rw_size, nro_address});
375 vm_manager.ReprotectRange(*map_address + header.rw_offset, header.rw_size,
376 Kernel::VMAPermission::ReadWrite);
377 492
493 // Invalidate JIT caches for the newly mapped process code
378 system.InvalidateCpuInstructionCaches(); 494 system.InvalidateCpuInstructionCaches();
379 495
380 nro.insert_or_assign(*map_address,
381 NROInfo{hash, nro_address, nro_size, bss_address, bss_size});
382
383 IPC::ResponseBuilder rb{ctx, 4}; 496 IPC::ResponseBuilder rb{ctx, 4};
384 rb.Push(RESULT_SUCCESS); 497 rb.Push(RESULT_SUCCESS);
385 rb.Push(*map_address); 498 rb.Push(*map_result);
499 }
500
501 ResultCode UnmapNro(const NROInfo& info) {
502 // Each region must be unmapped separately to validate memory state
503 auto& page_table{system.CurrentProcess()->PageTable()};
504 CASCADE_CODE(page_table.UnmapProcessCodeMemory(info.nro_address + info.text_size +
505 info.ro_size + info.data_size,
506 info.bss_address, info.bss_size));
507 CASCADE_CODE(page_table.UnmapProcessCodeMemory(
508 info.nro_address + info.text_size + info.ro_size,
509 info.src_addr + info.text_size + info.ro_size, info.data_size));
510 CASCADE_CODE(page_table.UnmapProcessCodeMemory(
511 info.nro_address + info.text_size, info.src_addr + info.text_size, info.ro_size));
512 CASCADE_CODE(
513 page_table.UnmapProcessCodeMemory(info.nro_address, info.src_addr, info.text_size));
514 return RESULT_SUCCESS;
386 } 515 }
387 516
388 void UnloadNro(Kernel::HLERequestContext& ctx) { 517 void UnloadNro(Kernel::HLERequestContext& ctx) {
@@ -422,30 +551,15 @@ public:
422 return; 551 return;
423 } 552 }
424 553
425 auto& vm_manager = system.CurrentProcess()->VMManager(); 554 const ResultCode result{UnmapNro(iter->second)};
426 const auto& nro_info = iter->second;
427
428 // Unmap the mirrored memory
429 ASSERT(
430 vm_manager.UnmapRange(nro_address, nro_info.nro_size + nro_info.bss_size).IsSuccess());
431
432 // Reprotect the source memory
433 ASSERT(vm_manager
434 .ReprotectRange(nro_info.nro_address, nro_info.nro_size,
435 Kernel::VMAPermission::ReadWrite)
436 .IsSuccess());
437 if (nro_info.bss_size > 0) {
438 ASSERT(vm_manager
439 .ReprotectRange(nro_info.bss_address, nro_info.bss_size,
440 Kernel::VMAPermission::ReadWrite)
441 .IsSuccess());
442 }
443 555
444 system.InvalidateCpuInstructionCaches(); 556 system.InvalidateCpuInstructionCaches();
445 557
446 nro.erase(iter); 558 nro.erase(iter);
559
447 IPC::ResponseBuilder rb{ctx, 2}; 560 IPC::ResponseBuilder rb{ctx, 2};
448 rb.Push(RESULT_SUCCESS); 561
562 rb.Push(result);
449 } 563 }
450 564
451 void Initialize(Kernel::HLERequestContext& ctx) { 565 void Initialize(Kernel::HLERequestContext& ctx) {
@@ -458,56 +572,7 @@ public:
458 } 572 }
459 573
460private: 574private:
461 using SHA256Hash = std::array<u8, 0x20>; 575 bool initialized{};
462
463 struct NROHeader {
464 INSERT_PADDING_WORDS(1);
465 u32_le mod_offset;
466 INSERT_PADDING_WORDS(2);
467 u32_le magic;
468 u32_le version;
469 u32_le nro_size;
470 u32_le flags;
471 u32_le text_offset;
472 u32_le text_size;
473 u32_le ro_offset;
474 u32_le ro_size;
475 u32_le rw_offset;
476 u32_le rw_size;
477 u32_le bss_size;
478 INSERT_PADDING_WORDS(1);
479 std::array<u8, 0x20> build_id;
480 INSERT_PADDING_BYTES(0x20);
481 };
482 static_assert(sizeof(NROHeader) == 0x80, "NROHeader has invalid size.");
483
484 struct NRRHeader {
485 u32_le magic;
486 INSERT_PADDING_BYTES(12);
487 u64_le title_id_mask;
488 u64_le title_id_pattern;
489 INSERT_PADDING_BYTES(16);
490 std::array<u8, 0x100> modulus;
491 std::array<u8, 0x100> signature_1;
492 std::array<u8, 0x100> signature_2;
493 u64_le title_id;
494 u32_le size;
495 INSERT_PADDING_BYTES(4);
496 u32_le hash_offset;
497 u32_le hash_count;
498 INSERT_PADDING_BYTES(8);
499 };
500 static_assert(sizeof(NRRHeader) == 0x350, "NRRHeader has incorrect size.");
501
502 struct NROInfo {
503 SHA256Hash hash;
504 VAddr nro_address;
505 u64 nro_size;
506 VAddr bss_address;
507 u64 bss_size;
508 };
509
510 bool initialized = false;
511 576
512 std::map<VAddr, NROInfo> nro; 577 std::map<VAddr, NROInfo> nro;
513 std::map<VAddr, std::vector<SHA256Hash>> nrr; 578 std::map<VAddr, std::vector<SHA256Hash>> nrr;