From bf46293fc794d4648e5543e964458fd94969b1b7 Mon Sep 17 00:00:00 2001 From: Billy Laws Date: Thu, 25 Jun 2020 16:29:35 +0100 Subject: [PATCH] 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. --- app/CMakeLists.txt | 1 + app/src/main/cpp/loader_jni.cpp | 3 ++ app/src/main/cpp/skyline/loader/executable.h | 27 +++++++++++ app/src/main/cpp/skyline/loader/loader.cpp | 49 +++++++++++++++++++ app/src/main/cpp/skyline/loader/loader.h | 18 +++++++ app/src/main/cpp/skyline/loader/nro.cpp | 50 +++++--------------- app/src/main/cpp/skyline/loader/nro.h | 19 +++----- 7 files changed, 117 insertions(+), 50 deletions(-) create mode 100644 app/src/main/cpp/skyline/loader/executable.h create mode 100644 app/src/main/cpp/skyline/loader/loader.cpp diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index d4488751..2a8c4bc6 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -38,6 +38,7 @@ add_library(skyline SHARED ${source_DIR}/skyline/gpu.cpp ${source_DIR}/skyline/gpu/texture.cpp ${source_DIR}/skyline/os.cpp + ${source_DIR}/skyline/loader/loader.cpp ${source_DIR}/skyline/loader/nro.cpp ${source_DIR}/skyline/kernel/memory.cpp ${source_DIR}/skyline/kernel/ipc.cpp diff --git a/app/src/main/cpp/loader_jni.cpp b/app/src/main/cpp/loader_jni.cpp index 76d55f8a..aa83cff0 100644 --- a/app/src/main/cpp/loader_jni.cpp +++ b/app/src/main/cpp/loader_jni.cpp @@ -3,6 +3,7 @@ #include "skyline/vfs/os_backing.h" #include "skyline/loader/nro.h" +#include "skyline/loader/nso.h" #include "skyline/jvm.h" 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) { case skyline::loader::RomFormat::NRO: return reinterpret_cast(new skyline::loader::NroLoader(backing)); + case skyline::loader::RomFormat::NSO: + return reinterpret_cast(new skyline::loader::NsoLoader(backing)); default: return 0; } diff --git a/app/src/main/cpp/skyline/loader/executable.h b/app/src/main/cpp/skyline/loader/executable.h new file mode 100644 index 00000000..3725d365 --- /dev/null +++ b/app/src/main/cpp/skyline/loader/executable.h @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#pragma once + +#include + +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 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 + }; +} diff --git a/app/src/main/cpp/skyline/loader/loader.cpp b/app/src/main/cpp/skyline/loader/loader.cpp new file mode 100644 index 00000000..31f623c6 --- /dev/null +++ b/app/src/main/cpp/skyline/loader/loader.cpp @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#include +#include +#include +#include "loader.h" + +namespace skyline::loader { + Loader::ExecutableLoadInfo Loader::LoadExecutable(const std::shared_ptr 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 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(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(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(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(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}; + } +} diff --git a/app/src/main/cpp/skyline/loader/loader.h b/app/src/main/cpp/skyline/loader/loader.h index ea1d6ec8..27b5bb81 100644 --- a/app/src/main/cpp/skyline/loader/loader.h +++ b/app/src/main/cpp/skyline/loader/loader.h @@ -5,6 +5,7 @@ #include #include +#include "executable.h" namespace skyline::loader { /** @@ -22,8 +23,25 @@ namespace skyline::loader { */ class Loader { 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 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 process, const DeviceState &state, Executable &executable, size_t offset = 0); + public: std::shared_ptr nacp; //!< The NACP of the current application std::shared_ptr romFs; //!< The RomFS of the current application diff --git a/app/src/main/cpp/skyline/loader/nro.cpp b/app/src/main/cpp/skyline/loader/nro.cpp index f119eabd..e7c1bf8f 100644 --- a/app/src/main/cpp/skyline/loader/nro.cpp +++ b/app/src/main/cpp/skyline/loader/nro.cpp @@ -38,52 +38,28 @@ namespace skyline::loader { return buffer; } - std::vector NroLoader::GetSegment(NroSegmentType segment) { - NroSegmentHeader &segmentHeader = header.segments[static_cast(segment)]; - std::vector buffer(segmentHeader.size); + std::vector NroLoader::GetSegment(const NroSegmentHeader &segment) { + std::vector buffer(segment.size); - backing->Read(buffer.data(), segmentHeader.offset, segmentHeader.size); + backing->Read(buffer.data(), segment.offset, segment.size); return buffer; } void NroLoader::LoadProcessData(const std::shared_ptr process, const DeviceState &state) { - std::vector text = GetSegment(loader::NroLoader::NroSegmentType::Text); - std::vector rodata = GetSegment(loader::NroLoader::NroSegmentType::RO); - std::vector data = GetSegment(loader::NroLoader::NroSegmentType::Data); + Executable nroExecutable{}; - u64 textSize = text.size(); - u64 rodataSize = rodata.size(); - u64 dataSize = data.size(); - u64 bssSize = header.bssSize; + nroExecutable.text.contents = GetSegment(header.text); + nroExecutable.text.offset = 0; - std::vector 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)) - throw exception("LoadProcessData: Sections are not aligned with page size: 0x{:X}, 0x{:X}, 0x{:X}", textSize, rodataSize, dataSize); + nroExecutable.data.contents = GetSegment(header.data); + nroExecutable.data.offset = header.text.size + header.ro.size; - u64 patchSize = patch.size() * sizeof(u32); - u64 padding = util::AlignUp(textSize + rodataSize + dataSize + bssSize + patchSize, PAGE_SIZE) - (textSize + rodataSize + dataSize + bssSize + patchSize); + nroExecutable.bssSize = header.bssSize; - process->NewHandle(constant::BaseAddress, textSize, memory::Permission{true, true, true}, memory::states::CodeStatic); // R-X - state.logger->Debug("Successfully mapped section .text @ 0x{0:X}, Size = 0x{1:X}", constant::BaseAddress, textSize); - - process->NewHandle(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(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(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(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); + auto loadInfo = LoadExecutable(process, state, nroExecutable); + state.os->memory.InitializeRegions(loadInfo.base, loadInfo.size, memory::AddressSpaceType::AddressSpace39Bit); } } diff --git a/app/src/main/cpp/skyline/loader/nro.h b/app/src/main/cpp/skyline/loader/nro.h index 63c34454..91f044cd 100644 --- a/app/src/main/cpp/skyline/loader/nro.h +++ b/app/src/main/cpp/skyline/loader/nro.h @@ -33,7 +33,9 @@ namespace skyline::loader { u32 size; //!< The size of 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 _pad2_; @@ -46,7 +48,7 @@ namespace skyline::loader { } 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 { u64 offset; //!< The offset of the region @@ -64,21 +66,12 @@ namespace skyline::loader { NroAssetSection romFs; //!< The header describing the location of the RomFS } 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 - * @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 */ - std::vector GetSegment(NroSegmentType segment); + std::vector GetSegment(const NroSegmentHeader &segment); public: NroLoader(const std::shared_ptr &backing);