Commonise executable loading infrastructure

Mapping and writing segments into memory is now handled by a common
function that can be shared between all loaders. All they need to do now
is to pack each segment into a common struct.
This commit is contained in:
Billy Laws 2020-06-25 16:29:35 +01:00 committed by ◱ PixelyIon
parent e7f880e782
commit bf46293fc7
7 changed files with 117 additions and 50 deletions

View File

@ -38,6 +38,7 @@ add_library(skyline SHARED
${source_DIR}/skyline/gpu.cpp ${source_DIR}/skyline/gpu.cpp
${source_DIR}/skyline/gpu/texture.cpp ${source_DIR}/skyline/gpu/texture.cpp
${source_DIR}/skyline/os.cpp ${source_DIR}/skyline/os.cpp
${source_DIR}/skyline/loader/loader.cpp
${source_DIR}/skyline/loader/nro.cpp ${source_DIR}/skyline/loader/nro.cpp
${source_DIR}/skyline/kernel/memory.cpp ${source_DIR}/skyline/kernel/memory.cpp
${source_DIR}/skyline/kernel/ipc.cpp ${source_DIR}/skyline/kernel/ipc.cpp

View File

@ -3,6 +3,7 @@
#include "skyline/vfs/os_backing.h" #include "skyline/vfs/os_backing.h"
#include "skyline/loader/nro.h" #include "skyline/loader/nro.h"
#include "skyline/loader/nso.h"
#include "skyline/jvm.h" #include "skyline/jvm.h"
extern "C" JNIEXPORT jlong JNICALL Java_emu_skyline_loader_RomFile_initialize(JNIEnv *env, jobject thiz, jint jformat, jint fd) { extern "C" JNIEXPORT jlong JNICALL Java_emu_skyline_loader_RomFile_initialize(JNIEnv *env, jobject thiz, jint jformat, jint fd) {
@ -14,6 +15,8 @@ extern "C" JNIEXPORT jlong JNICALL Java_emu_skyline_loader_RomFile_initialize(JN
switch (format) { switch (format) {
case skyline::loader::RomFormat::NRO: case skyline::loader::RomFormat::NRO:
return reinterpret_cast<jlong>(new skyline::loader::NroLoader(backing)); return reinterpret_cast<jlong>(new skyline::loader::NroLoader(backing));
case skyline::loader::RomFormat::NSO:
return reinterpret_cast<jlong>(new skyline::loader::NsoLoader(backing));
default: default:
return 0; return 0;
} }

View File

@ -0,0 +1,27 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#pragma once
#include <common.h>
namespace skyline::loader {
/**
* @brief The Executable struct encapsulates the segments of an executable
*/
struct Executable {
/**
* @brief This holds the contents and offset of an executable segment
*/
struct Segment {
std::vector<u8> contents; //!< The raw contents of the segment
size_t offset; //!< The offset from the base address to load the segment at
};
Segment text; //!< The .text segment container
Segment ro; //!< The .rodata segment container
Segment data; //!< The .data segment container
size_t bssSize; //!< The size of the .bss segment
};
}

View File

@ -0,0 +1,49 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <nce.h>
#include <os.h>
#include <kernel/memory.h>
#include "loader.h"
namespace skyline::loader {
Loader::ExecutableLoadInfo Loader::LoadExecutable(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state, Executable &executable, size_t offset) {
u64 base = constant::BaseAddress + offset;
u64 textSize = executable.text.contents.size();
u64 roSize = executable.ro.contents.size();
u64 dataSize = executable.data.contents.size() + executable.bssSize;
if (!util::IsAligned(textSize, PAGE_SIZE) || !util::IsAligned(roSize, PAGE_SIZE) || !util::IsAligned(dataSize, PAGE_SIZE))
throw exception("LoadProcessData: Sections are not aligned with page size: 0x{:X}, 0x{:X}, 0x{:X}", textSize, roSize, dataSize);
if (!util::IsAligned(executable.text.offset, PAGE_SIZE) || !util::IsAligned(executable.ro.offset, PAGE_SIZE) || !util::IsAligned(executable.data.offset, PAGE_SIZE))
throw exception("LoadProcessData: Section offsets are not aligned with page size: 0x{:X}, 0x{:X}, 0x{:X}", executable.text.offset, executable.ro.offset, executable.data.offset);
// The data section will always be the last section in memory, so put the patch section after it
u64 patchOffset = executable.data.offset + dataSize;
std::vector<u32> patch = state.nce->PatchCode(executable.text.contents, base, patchOffset);
u64 patchSize = patch.size() * sizeof(u32);
u64 padding = util::AlignUp(patchSize, PAGE_SIZE) - patchSize;
process->NewHandle<kernel::type::KPrivateMemory>(base + executable.text.offset, textSize, memory::Permission{true, false, true}, memory::states::CodeStatic); // R-X
state.logger->Debug("Successfully mapped section .text @ 0x{0:X}, Size = 0x{1:X}", base + executable.text.offset, textSize);
process->NewHandle<kernel::type::KPrivateMemory>(base + executable.ro.offset, roSize, memory::Permission{true, false, false}, memory::states::CodeReadOnly); // R--
state.logger->Debug("Successfully mapped section .rodata @ 0x{0:X}, Size = 0x{1:X}", base + executable.ro.offset, roSize);
process->NewHandle<kernel::type::KPrivateMemory>(base + executable.data.offset, dataSize, memory::Permission{true, true, false}, memory::states::CodeStatic); // RW-
state.logger->Debug("Successfully mapped section .data @ 0x{0:X}, Size = 0x{1:X}", base + executable.data.offset, dataSize);
process->NewHandle<kernel::type::KPrivateMemory>(base + patchOffset, patchSize + padding, memory::Permission{true, true, true}, memory::states::CodeStatic); // RWX
state.logger->Debug("Successfully mapped section .patch @ 0x{0:X}, Size = 0x{1:X}", base + patchOffset, patchSize + padding);
process->WriteMemory(executable.text.contents.data(), base + executable.text.offset, textSize);
process->WriteMemory(executable.ro.contents.data(), base + executable.ro.offset, roSize);
process->WriteMemory(executable.data.contents.data(), base + executable.data.offset, dataSize - executable.bssSize);
process->WriteMemory(patch.data(), base + patchOffset, patchSize);
return {base, patchOffset + patchSize + padding};
}
}

View File

@ -5,6 +5,7 @@
#include <vfs/backing.h> #include <vfs/backing.h>
#include <vfs/nacp.h> #include <vfs/nacp.h>
#include "executable.h"
namespace skyline::loader { namespace skyline::loader {
/** /**
@ -22,8 +23,25 @@ namespace skyline::loader {
*/ */
class Loader { class Loader {
protected: protected:
/**
* @brief This contains information about the placement of an executable in memory
*/
struct ExecutableLoadInfo {
size_t base; //!< The base of the loaded executable
size_t size; //!< The total size of the loaded executable
};
std::shared_ptr<vfs::Backing> backing; //!< The backing of the loader std::shared_ptr<vfs::Backing> backing; //!< The backing of the loader
/**
* @brief This loads an executable into memory
* @param process The process to load the executable into
* @param executable The executable itself
* @param offset The offset from the base address that the executable should be placed at
* @return An ExecutableLoadInfo struct containing the load base and size
*/
static ExecutableLoadInfo LoadExecutable(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state, Executable &executable, size_t offset = 0);
public: public:
std::shared_ptr<vfs::NACP> nacp; //!< The NACP of the current application std::shared_ptr<vfs::NACP> nacp; //!< The NACP of the current application
std::shared_ptr<vfs::Backing> romFs; //!< The RomFS of the current application std::shared_ptr<vfs::Backing> romFs; //!< The RomFS of the current application

View File

@ -38,52 +38,28 @@ namespace skyline::loader {
return buffer; return buffer;
} }
std::vector<u8> NroLoader::GetSegment(NroSegmentType segment) { std::vector<u8> NroLoader::GetSegment(const NroSegmentHeader &segment) {
NroSegmentHeader &segmentHeader = header.segments[static_cast<int>(segment)]; std::vector<u8> buffer(segment.size);
std::vector<u8> buffer(segmentHeader.size);
backing->Read(buffer.data(), segmentHeader.offset, segmentHeader.size); backing->Read(buffer.data(), segment.offset, segment.size);
return buffer; return buffer;
} }
void NroLoader::LoadProcessData(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state) { void NroLoader::LoadProcessData(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state) {
std::vector<u8> text = GetSegment(loader::NroLoader::NroSegmentType::Text); Executable nroExecutable{};
std::vector<u8> rodata = GetSegment(loader::NroLoader::NroSegmentType::RO);
std::vector<u8> data = GetSegment(loader::NroLoader::NroSegmentType::Data);
u64 textSize = text.size(); nroExecutable.text.contents = GetSegment(header.text);
u64 rodataSize = rodata.size(); nroExecutable.text.offset = 0;
u64 dataSize = data.size();
u64 bssSize = header.bssSize;
std::vector<u32> patch = state.nce->PatchCode(text, constant::BaseAddress, textSize + rodataSize + dataSize + bssSize); nroExecutable.ro.contents = GetSegment(header.ro);
nroExecutable.ro.offset = header.text.size;
if (!util::IsAligned(textSize, PAGE_SIZE) || !util::IsAligned(rodataSize, PAGE_SIZE) || !util::IsAligned(dataSize, PAGE_SIZE)) nroExecutable.data.contents = GetSegment(header.data);
throw exception("LoadProcessData: Sections are not aligned with page size: 0x{:X}, 0x{:X}, 0x{:X}", textSize, rodataSize, dataSize); nroExecutable.data.offset = header.text.size + header.ro.size;
u64 patchSize = patch.size() * sizeof(u32); nroExecutable.bssSize = header.bssSize;
u64 padding = util::AlignUp(textSize + rodataSize + dataSize + bssSize + patchSize, PAGE_SIZE) - (textSize + rodataSize + dataSize + bssSize + patchSize);
process->NewHandle<kernel::type::KPrivateMemory>(constant::BaseAddress, textSize, memory::Permission{true, true, true}, memory::states::CodeStatic); // R-X auto loadInfo = LoadExecutable(process, state, nroExecutable);
state.logger->Debug("Successfully mapped section .text @ 0x{0:X}, Size = 0x{1:X}", constant::BaseAddress, textSize); state.os->memory.InitializeRegions(loadInfo.base, loadInfo.size, memory::AddressSpaceType::AddressSpace39Bit);
process->NewHandle<kernel::type::KPrivateMemory>(constant::BaseAddress + textSize, rodataSize, memory::Permission{true, false, false}, memory::states::CodeReadOnly); // R--
state.logger->Debug("Successfully mapped section .ro @ 0x{0:X}, Size = 0x{1:X}", constant::BaseAddress + textSize, rodataSize);
process->NewHandle<kernel::type::KPrivateMemory>(constant::BaseAddress + textSize + rodataSize, dataSize, memory::Permission{true, true, false}, memory::states::CodeStatic); // RW-
state.logger->Debug("Successfully mapped section .data @ 0x{0:X}, Size = 0x{1:X}", constant::BaseAddress + textSize + rodataSize, dataSize);
process->NewHandle<kernel::type::KPrivateMemory>(constant::BaseAddress + textSize + rodataSize + dataSize, bssSize, memory::Permission{true, true, true}, memory::states::CodeMutable); // RWX
state.logger->Debug("Successfully mapped section .bss @ 0x{0:X}, Size = 0x{1:X}", constant::BaseAddress + textSize + rodataSize + dataSize, bssSize);
process->NewHandle<kernel::type::KPrivateMemory>(constant::BaseAddress + textSize + rodataSize + dataSize + bssSize, patchSize + padding, memory::Permission{true, true, true}, memory::states::CodeStatic); // RWX
state.logger->Debug("Successfully mapped section .patch @ 0x{0:X}, Size = 0x{1:X}", constant::BaseAddress + textSize + rodataSize + dataSize + bssSize, patchSize);
process->WriteMemory(text.data(), constant::BaseAddress, textSize);
process->WriteMemory(rodata.data(), constant::BaseAddress + textSize, rodataSize);
process->WriteMemory(data.data(), constant::BaseAddress + textSize + rodataSize, dataSize);
process->WriteMemory(patch.data(), constant::BaseAddress + textSize + rodataSize + dataSize + bssSize, patchSize);
state.os->memory.InitializeRegions(constant::BaseAddress, textSize + rodataSize + dataSize + bssSize + patchSize + padding, memory::AddressSpaceType::AddressSpace39Bit);
} }
} }

View File

@ -33,7 +33,9 @@ namespace skyline::loader {
u32 size; //!< The size of the NRO u32 size; //!< The size of the NRO
u32 flags; //!< The flags used with the NRO u32 flags; //!< The flags used with the NRO
NroSegmentHeader segments[3]; //!< The .text segment header NroSegmentHeader text; //!< The .text segment header
NroSegmentHeader ro; //!< The .rodata segment header
NroSegmentHeader data; //!< The .data segment header
u32 bssSize; //!< The size of the bss segment u32 bssSize; //!< The size of the bss segment
u32 _pad2_; u32 _pad2_;
@ -46,7 +48,7 @@ namespace skyline::loader {
} header{}; } header{};
/** /**
* @brief This holds a single asset sections's offset and size * @brief This holds a single asset section's offset and size
*/ */
struct NroAssetSection { struct NroAssetSection {
u64 offset; //!< The offset of the region u64 offset; //!< The offset of the region
@ -64,21 +66,12 @@ namespace skyline::loader {
NroAssetSection romFs; //!< The header describing the location of the RomFS NroAssetSection romFs; //!< The header describing the location of the RomFS
} assetHeader{}; } assetHeader{};
/**
* @brief This enumerates the segments withing an NRO file
*/
enum class NroSegmentType : int {
Text = 0, //!< The .text section
RO = 1, //!< The .rodata section
Data = 2 //!< The .data section
};
/** /**
* @brief This reads the data of the specified segment * @brief This reads the data of the specified segment
* @param segment The type of segment to read * @param segment The header of the segment to read
* @return A buffer containing the data of the requested segment * @return A buffer containing the data of the requested segment
*/ */
std::vector<u8> GetSegment(NroSegmentType segment); std::vector<u8> GetSegment(const NroSegmentHeader &segment);
public: public:
NroLoader(const std::shared_ptr<vfs::Backing> &backing); NroLoader(const std::shared_ptr<vfs::Backing> &backing);