Просмотр исходного кода

service: ldr: Updates for new VMM.

- Includes removing some service impls. that are untested.
bunnei 6 лет назад
Родитель
Сommit
37b79ebe85
1 измененных файлов с 215 добавлено и 150 удалено
  1. 215 150
      src/core/hle/service/ldr/ldr.cpp

+ 215 - 150
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;