mirror of
https://github.com/Lime3DS/Lime3DS.git
synced 2026-03-29 07:00:01 -06:00
plgldr: Fix plugin memory management (#1554)
This commit is contained in:
parent
be259fc9b8
commit
40d512cafd
@ -1,7 +1,9 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
// Originally MIT-licensed code from The Pixellizer Group
|
||||
|
||||
// Copyright 2022 The Pixellizer Group
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
@ -22,6 +24,7 @@
|
||||
#include "core/file_sys/file_backend.h"
|
||||
#include "core/file_sys/plugin_3gx.h"
|
||||
#include "core/file_sys/plugin_3gx_bootloader.h"
|
||||
#include "core/hle/kernel/config_mem.h"
|
||||
#include "core/hle/kernel/vm_manager.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
@ -173,10 +176,10 @@ Loader::ResultStatus FileSys::Plugin3GXLoader::Map(
|
||||
}
|
||||
|
||||
const std::array<u32, 4> mem_region_sizes = {
|
||||
5 * 1024 * 1024, // 5 MiB
|
||||
2 * 1024 * 1024, // 2 MiB
|
||||
3 * 1024 * 1024, // 3 MiB
|
||||
4 * 1024 * 1024 // 4 MiB
|
||||
5 * 1024 * 1024, // 5 MiB
|
||||
2 * 1024 * 1024, // 2 MiB
|
||||
10 * 1024 * 1024, // 10 MiB
|
||||
5 * 1024 * 1024 // 5 MiB (reserved)
|
||||
};
|
||||
|
||||
const bool is_mem_private = header.infos.flags.use_private_memory != 0;
|
||||
@ -184,65 +187,64 @@ Loader::ResultStatus FileSys::Plugin3GXLoader::Map(
|
||||
// Map memory block. This behaviour mimics how plugins are loaded on 3DS as much as possible.
|
||||
// Calculate the sizes of the different memory regions
|
||||
const u32 block_size = mem_region_sizes[header.infos.flags.memory_region_size.Value()];
|
||||
const u32 exe_offset = 0;
|
||||
const u32 exe_size = (sizeof(PluginHeader) + text_section.size() + rodata_section.size() +
|
||||
data_section.size() + header.executable.bss_size + 0x1000) &
|
||||
~0xFFFu;
|
||||
const u32 bootloader_offset = exe_offset + exe_size;
|
||||
const u32 bootloader_size = bootloader_memory_size;
|
||||
const u32 heap_offset = bootloader_offset + bootloader_size;
|
||||
const u32 heap_size = block_size - heap_offset;
|
||||
|
||||
// Allocate the framebuffer block so that is in the highest FCRAM position possible
|
||||
auto offset_fb =
|
||||
kernel.GetMemoryRegion(Kernel::MemoryRegion::SYSTEM)->RLinearAllocate(_3GX_fb_size);
|
||||
if (!offset_fb) {
|
||||
LOG_ERROR(Service_PLGLDR, "Failed to load 3GX plugin. Not enough memory: {}",
|
||||
plg_context.plugin_path);
|
||||
return Loader::ResultStatus::ErrorMemoryAllocationFailed;
|
||||
// Allocate a block of memory for the plugin
|
||||
std::optional<u32> offset;
|
||||
if (kernel.GetMemoryMode() == Kernel::MemoryMode::NewProd ||
|
||||
(plg_context.use_user_load_parameters &&
|
||||
plg_context.user_load_parameters.plugin_memory_strategy ==
|
||||
Service::PLGLDR::PLG_LDR::PluginMemoryStrategy::PLG_STRATEGY_MODE3)) {
|
||||
// Allocate memory block from the end of the APPLICATION region
|
||||
offset =
|
||||
kernel.GetMemoryRegion(Kernel::MemoryRegion::APPLICATION)->RLinearAllocate(block_size);
|
||||
|
||||
// If the reported available APP mem equals the actual size, remove the plugin block size.
|
||||
if (offset) {
|
||||
auto& config_mem = kernel.GetConfigMemHandler();
|
||||
if (config_mem.GetConfigMem().app_mem_alloc ==
|
||||
kernel.GetMemoryRegion(Kernel::MemoryRegion::APPLICATION)->size) {
|
||||
config_mem.GetConfigMem().app_mem_alloc -= block_size;
|
||||
}
|
||||
}
|
||||
plg_context.memory_region = Kernel::MemoryRegion::APPLICATION;
|
||||
} else {
|
||||
// Allocate memory block from the start of the SYSTEM region
|
||||
offset = kernel.GetMemoryRegion(Kernel::MemoryRegion::SYSTEM)->LinearAllocate(block_size);
|
||||
plg_context.memory_region = Kernel::MemoryRegion::SYSTEM;
|
||||
}
|
||||
auto backing_memory_fb = kernel.memory.GetFCRAMRef(*offset_fb);
|
||||
plg_ldr.SetPluginFBAddr(Memory::FCRAM_PADDR + *offset_fb);
|
||||
std::fill(backing_memory_fb.GetPtr(), backing_memory_fb.GetPtr() + _3GX_fb_size, 0);
|
||||
|
||||
auto vma_heap_fb = process.vm_manager.MapBackingMemory(
|
||||
_3GX_heap_load_addr, backing_memory_fb, _3GX_fb_size,
|
||||
is_mem_private ? Kernel::MemoryState::Private : Kernel::MemoryState::Shared);
|
||||
ASSERT(vma_heap_fb.Succeeded());
|
||||
process.vm_manager.Reprotect(vma_heap_fb.Unwrap(), Kernel::VMAPermission::ReadWrite);
|
||||
|
||||
// Allocate a block from the end of FCRAM and clear it
|
||||
auto offset = kernel.GetMemoryRegion(Kernel::MemoryRegion::SYSTEM)
|
||||
->RLinearAllocate(block_size - _3GX_fb_size);
|
||||
if (!offset) {
|
||||
kernel.GetMemoryRegion(Kernel::MemoryRegion::SYSTEM)->Free(*offset_fb, _3GX_fb_size);
|
||||
LOG_ERROR(Service_PLGLDR, "Failed to load 3GX plugin. Not enough memory: {}",
|
||||
plg_context.plugin_path);
|
||||
return Loader::ResultStatus::ErrorMemoryAllocationFailed;
|
||||
}
|
||||
auto backing_memory = kernel.memory.GetFCRAMRef(*offset);
|
||||
std::fill(backing_memory.GetPtr(), backing_memory.GetPtr() + block_size - _3GX_fb_size, 0);
|
||||
|
||||
// Then we map part of the memory, which contains the executable
|
||||
auto vma = process.vm_manager.MapBackingMemory(_3GX_exe_load_addr, backing_memory, exe_size,
|
||||
is_mem_private ? Kernel::MemoryState::Private
|
||||
: Kernel::MemoryState::Shared);
|
||||
ASSERT(vma.Succeeded());
|
||||
process.vm_manager.Reprotect(vma.Unwrap(), Kernel::VMAPermission::ReadWriteExecute);
|
||||
u32 fcram_offset = offset.value();
|
||||
|
||||
auto backing_memory_exe = kernel.memory.GetFCRAMRef(fcram_offset + exe_offset);
|
||||
std::fill(backing_memory_exe.GetPtr(), backing_memory_exe.GetPtr() + exe_size, 0);
|
||||
|
||||
// Map the executable
|
||||
auto vma_exe = process.vm_manager.MapBackingMemory(
|
||||
_3GX_exe_load_addr, backing_memory_exe, exe_size,
|
||||
is_mem_private ? Kernel::MemoryState::Private : Kernel::MemoryState::Shared);
|
||||
ASSERT(vma_exe.Succeeded());
|
||||
process.vm_manager.Reprotect(vma_exe.Unwrap(), Kernel::VMAPermission::ReadWriteExecute);
|
||||
|
||||
// Write text section
|
||||
kernel.memory.WriteBlock(process, _3GX_exe_load_addr + sizeof(PluginHeader),
|
||||
text_section.data(), header.executable.code_size);
|
||||
// Write rodata section
|
||||
kernel.memory.WriteBlock(
|
||||
process, _3GX_exe_load_addr + sizeof(PluginHeader) + header.executable.code_size,
|
||||
rodata_section.data(), header.executable.rodata_size);
|
||||
// Write data section
|
||||
kernel.memory.WriteBlock(process,
|
||||
_3GX_exe_load_addr + sizeof(PluginHeader) +
|
||||
header.executable.code_size + header.executable.rodata_size,
|
||||
data_section.data(), header.executable.data_size);
|
||||
// Prepare plugin header and write it
|
||||
PluginHeader plugin_header = {0};
|
||||
plugin_header.version = header.version;
|
||||
plugin_header.exe_size = exe_size;
|
||||
plugin_header.heap_VA = _3GX_heap_load_addr;
|
||||
plugin_header.heap_size = block_size - exe_size;
|
||||
plugin_header.heap_size = heap_size;
|
||||
plg_context.plg_event = _3GX_exe_load_addr - 0x4;
|
||||
plg_context.plg_reply = _3GX_exe_load_addr - 0x8;
|
||||
plugin_header.plgldr_event = plg_context.plg_event;
|
||||
@ -254,39 +256,46 @@ Loader::ResultStatus FileSys::Plugin3GXLoader::Map(
|
||||
}
|
||||
kernel.memory.WriteBlock(process, _3GX_exe_load_addr, &plugin_header, sizeof(PluginHeader));
|
||||
|
||||
// Map plugin heap
|
||||
auto backing_memory_heap = kernel.memory.GetFCRAMRef(*offset + exe_size);
|
||||
// Write text section
|
||||
kernel.memory.WriteBlock(process, _3GX_exe_load_addr + sizeof(PluginHeader),
|
||||
text_section.data(), header.executable.code_size);
|
||||
// Write rodata section
|
||||
kernel.memory.WriteBlock(
|
||||
process, _3GX_exe_load_addr + sizeof(PluginHeader) + header.executable.code_size,
|
||||
rodata_section.data(), header.executable.rodata_size);
|
||||
|
||||
// Map the rest of the memory at the heap location
|
||||
auto vma_heap = process.vm_manager.MapBackingMemory(
|
||||
_3GX_heap_load_addr + _3GX_fb_size, backing_memory_heap,
|
||||
block_size - exe_size - _3GX_fb_size,
|
||||
is_mem_private ? Kernel::MemoryState::Private : Kernel::MemoryState::Shared);
|
||||
ASSERT(vma_heap.Succeeded());
|
||||
process.vm_manager.Reprotect(vma_heap.Unwrap(), Kernel::VMAPermission::ReadWriteExecute);
|
||||
// Write data section
|
||||
kernel.memory.WriteBlock(process,
|
||||
_3GX_exe_load_addr + sizeof(PluginHeader) +
|
||||
header.executable.code_size + header.executable.rodata_size,
|
||||
data_section.data(), header.executable.data_size);
|
||||
|
||||
// Allocate a block from the end of FCRAM and clear it
|
||||
auto bootloader_offset = kernel.GetMemoryRegion(Kernel::MemoryRegion::SYSTEM)
|
||||
->RLinearAllocate(bootloader_memory_size);
|
||||
if (!bootloader_offset) {
|
||||
kernel.GetMemoryRegion(Kernel::MemoryRegion::SYSTEM)->Free(*offset_fb, _3GX_fb_size);
|
||||
kernel.GetMemoryRegion(Kernel::MemoryRegion::SYSTEM)
|
||||
->Free(*offset, block_size - _3GX_fb_size);
|
||||
LOG_ERROR(Service_PLGLDR, "Failed to load 3GX plugin. Not enough memory: {}",
|
||||
plg_context.plugin_path);
|
||||
return Loader::ResultStatus::ErrorMemoryAllocationFailed;
|
||||
}
|
||||
// Map bootloader
|
||||
const bool use_internal = plg_context.load_exe_func.empty();
|
||||
MapBootloader(
|
||||
process, kernel, *bootloader_offset,
|
||||
process, kernel, fcram_offset + bootloader_offset,
|
||||
(use_internal) ? exe_load_func : plg_context.load_exe_func,
|
||||
(use_internal) ? exe_load_args : plg_context.load_exe_args,
|
||||
header.executable.code_size + header.executable.rodata_size + header.executable.data_size,
|
||||
header.infos.exe_load_checksum,
|
||||
plg_context.use_user_load_parameters ? plg_context.user_load_parameters.no_flash : 0);
|
||||
|
||||
// Map plugin heap
|
||||
auto backing_memory_heap = kernel.memory.GetFCRAMRef(fcram_offset + heap_offset);
|
||||
std::fill(backing_memory_heap.GetPtr(), backing_memory_heap.GetPtr() + heap_size, 0);
|
||||
|
||||
auto vma_heap = process.vm_manager.MapBackingMemory(
|
||||
_3GX_heap_load_addr, backing_memory_heap, heap_size,
|
||||
is_mem_private ? Kernel::MemoryState::Private : Kernel::MemoryState::Shared);
|
||||
ASSERT(vma_heap.Succeeded());
|
||||
process.vm_manager.Reprotect(vma_heap.Unwrap(), Kernel::VMAPermission::ReadWriteExecute);
|
||||
|
||||
plg_ldr.SetPluginFBAddr(Memory::FCRAM_PADDR + fcram_offset + heap_offset);
|
||||
plg_context.plugin_loaded = true;
|
||||
plg_context.plugin_process_id = process.process_id;
|
||||
plg_context.use_user_load_parameters = false;
|
||||
plg_context.memory_block = {fcram_offset, block_size};
|
||||
|
||||
return Loader::ResultStatus::Success;
|
||||
}
|
||||
|
||||
@ -355,7 +364,7 @@ void FileSys::Plugin3GXLoader::MapBootloader(Kernel::Process& process, Kernel::K
|
||||
std::fill(backing_memory.GetPtr(), backing_memory.GetPtr() + bootloader_memory_size, 0);
|
||||
auto vma = process.vm_manager.MapBackingMemory(_3GX_exe_load_addr - bootloader_memory_size,
|
||||
backing_memory, bootloader_memory_size,
|
||||
Kernel::MemoryState::Continuous);
|
||||
Kernel::MemoryState::Private);
|
||||
ASSERT(vma.Succeeded());
|
||||
process.vm_manager.Reprotect(vma.Unwrap(), Kernel::VMAPermission::ReadWriteExecute);
|
||||
|
||||
|
||||
@ -119,6 +119,11 @@ enum class SystemInfoType {
|
||||
* For the ARM11 NATIVE_FIRM kernel, this is 5, for processes sm, fs, pm, loader, and pxi."
|
||||
*/
|
||||
KERNEL_SPAWNED_PIDS = 26,
|
||||
/**
|
||||
* Check various Luma3DS config values. This parameter is not available on real systems,
|
||||
* but can be used by homebrew applications.
|
||||
*/
|
||||
LUMA_CFW_INFO = 0x10000,
|
||||
/**
|
||||
* Check if the current system is a new 3DS. This parameter is not available on real systems,
|
||||
* but can be used by homebrew applications.
|
||||
@ -270,6 +275,13 @@ enum class SystemInfoMemUsageRegion {
|
||||
BASE = 3,
|
||||
};
|
||||
|
||||
enum class SystemInfoLumaCFWInformation {
|
||||
REAL_APP_REGION_SIZE = 0x80, // Gets the real APPLICATION region size,
|
||||
// instead of the one reported by the kernel shared page
|
||||
// which depends on the memory mode.
|
||||
IS_N3DS = 0x201, // Checks if the system is a N3DS or not.
|
||||
};
|
||||
|
||||
/**
|
||||
* Accepted by svcGetSystemInfo param with CITRA_INFORMATION type. Selects which information
|
||||
* to fetch from Citra. Some string params don't fit in 7 bytes, so they are split.
|
||||
@ -1827,6 +1839,20 @@ Result SVC::GetSystemInfo(s64* out, u32 type, s32 param) {
|
||||
case SystemInfoType::KERNEL_SPAWNED_PIDS:
|
||||
*out = 5;
|
||||
break;
|
||||
case SystemInfoType::LUMA_CFW_INFO:
|
||||
switch ((SystemInfoLumaCFWInformation)param) {
|
||||
case SystemInfoLumaCFWInformation::REAL_APP_REGION_SIZE:
|
||||
*out = kernel.GetMemoryRegion(MemoryRegion::APPLICATION)->size;
|
||||
break;
|
||||
case SystemInfoLumaCFWInformation::IS_N3DS:
|
||||
*out = Settings::values.is_new_3ds ? 1 : 0;
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR(Kernel_SVC, "unknown GetSystemInfo type=0x10000 region: param={}", param);
|
||||
*out = 0;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case SystemInfoType::NEW_3DS_INFO:
|
||||
// The actual subtypes are not implemented, homebrew just check
|
||||
// this doesn't return an error in n3ds to know the system type
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
// Originally MIT-licensed code from The Pixellizer Group
|
||||
|
||||
// Copyright 2022 The Pixellizer Group
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
@ -70,6 +72,9 @@ void PLG_LDR::PluginLoaderContext::serialize(Archive& ar, const unsigned int) {
|
||||
ar & plugin_loaded;
|
||||
ar & is_default_path;
|
||||
ar & plugin_path;
|
||||
ar & memory_region;
|
||||
ar & memory_block;
|
||||
ar & plugin_process_id;
|
||||
ar & use_user_load_parameters;
|
||||
ar & user_load_parameters;
|
||||
ar & plg_event;
|
||||
@ -148,12 +153,21 @@ void PLG_LDR::OnProcessRun(Kernel::Process& process, Kernel::KernelSystem& kerne
|
||||
}
|
||||
|
||||
void PLG_LDR::OnProcessExit(Kernel::Process& process, Kernel::KernelSystem& kernel) {
|
||||
if (plgldr_context.plugin_loaded) {
|
||||
if (plgldr_context.plugin_loaded && process.process_id == plgldr_context.plugin_process_id) {
|
||||
u32 status = kernel.memory.Read32(FileSys::Plugin3GXLoader::_3GX_exe_load_addr - 0xC);
|
||||
if (status == 0) {
|
||||
LOG_CRITICAL(Service_PLGLDR, "Failed to launch {}: Checksum failed",
|
||||
plgldr_context.plugin_path);
|
||||
}
|
||||
if (plgldr_context.memory_region != static_cast<Kernel::MemoryRegion>(0)) {
|
||||
kernel.GetMemoryRegion(plgldr_context.memory_region)
|
||||
->Free(plgldr_context.memory_block.first, plgldr_context.memory_block.second);
|
||||
plgldr_context.memory_region = static_cast<Kernel::MemoryRegion>(0);
|
||||
}
|
||||
plgldr_context.plugin_loaded = false;
|
||||
plgldr_context.plugin_process_id = UINT32_MAX;
|
||||
plgldr_context.memory_changed_handle = 0;
|
||||
LOG_INFO(Service_PLGLDR, "Plugin unloaded successfully.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
// Originally MIT-licensed code from The Pixellizer Group
|
||||
|
||||
// Copyright 2022 The Pixellizer Group
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
@ -62,6 +64,9 @@ public:
|
||||
bool plugin_loaded = false;
|
||||
bool is_default_path = false;
|
||||
std::string plugin_path = "";
|
||||
u32 plugin_process_id = UINT32_MAX;
|
||||
Kernel::MemoryRegion memory_region{};
|
||||
std::pair<u32, u32> memory_block{};
|
||||
|
||||
bool use_user_load_parameters = false;
|
||||
PluginLoadParameters user_load_parameters;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user