mirror of
https://github.com/skyline-emu/skyline.git
synced 2024-12-23 19:21:55 +01:00
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:
parent
e7f880e782
commit
bf46293fc7
@ -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
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
27
app/src/main/cpp/skyline/loader/executable.h
Normal file
27
app/src/main/cpp/skyline/loader/executable.h
Normal 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
|
||||||
|
};
|
||||||
|
}
|
49
app/src/main/cpp/skyline/loader/loader.cpp
Normal file
49
app/src/main/cpp/skyline/loader/loader.cpp
Normal 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};
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
Loading…
Reference in New Issue
Block a user