Rework loader abstractions and the NRO loader to use the vfs APIs

This will make it easier for us to implement more executable formats in
the future.
This commit is contained in:
Billy Laws 2020-06-19 21:14:40 +01:00 committed by ◱ PixelyIon
parent 4950dd5638
commit dca06f2b49
7 changed files with 135 additions and 71 deletions

View File

@ -57,16 +57,6 @@ namespace skyline {
}
};
/**
* @brief This enumerates the types of the ROM
* @note This needs to be synchronized with emu.skyline.loader.BaseLoader.TitleFormat
*/
enum class TitleFormat {
NRO, //!< The NRO format: https://switchbrew.org/wiki/NRO
XCI, //!< The XCI format: https://switchbrew.org/wiki/XCI
NSP, //!< The NSP format from "nspwn" exploit: https://switchbrew.org/wiki/Switch_System_Flaws
};
namespace util {
/**
* @brief Returns the current time in nanoseconds

View File

@ -3,35 +3,46 @@
#pragma once
#include <unistd.h>
#include <os.h>
#include <kernel/types/KProcess.h>
#include <vfs/backing.h>
#include <vfs/nacp.h>
namespace skyline::loader {
/**
* @brief This enumerates the types of ROM files
* @note This needs to be synchronized with emu.skyline.loader.BaseLoader.RomFormat
*/
enum class RomFormat {
NRO, //!< The NRO format: https://switchbrew.org/wiki/NRO
XCI, //!< The XCI format: https://switchbrew.org/wiki/XCI
NSP, //!< The NSP format from "nspwn" exploit: https://switchbrew.org/wiki/Switch_System_Flaws
};
/**
* @brief The Loader class provides an abstract interface for ROM loaders
*/
class Loader {
protected:
int fd; //!< An FD to the ROM file
/**
* @brief Read the file at a particular offset
* @tparam T The type of object to write to
* @param output The object to write to
* @param offset The offset to read the file at
* @param size The amount to read in bytes
*/
template<typename T>
inline void ReadOffset(T *output, u64 offset, size_t size) {
pread64(fd, output, size, offset);
}
std::shared_ptr<vfs::Backing> backing; //!< The backing of the loader
public:
/**
* @param filePath The path to the ROM file
*/
Loader(int fd) : fd(fd) {}
std::shared_ptr<vfs::NACP> nacp; //!< The NACP of the current application
/**
* This loads in the data of the main process
* @param backing The backing for the NRO
*/
Loader(const std::shared_ptr<vfs::Backing> &backing) : backing(backing) {}
virtual ~Loader() = default;
/**
* @return The icon of the loaded application
*/
virtual std::vector<u8> GetIcon() {
return std::vector<u8>();
}
/**
* @brief This loads in the data of the main process
* @param process The process to load in the data
* @param state The state of the device
*/

View File

@ -1,38 +1,65 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <vector>
#include <nce.h>
#include <os.h>
#include <kernel/memory.h>
#include <vfs/nacp.h>
#include <vfs/region_backing.h>
#include "nro.h"
namespace skyline::loader {
NroLoader::NroLoader(int fd) : Loader(fd) {
ReadOffset((u32 *) &header, 0x0, sizeof(NroHeader));
NroLoader::NroLoader(const std::shared_ptr<vfs::Backing> &backing) : Loader(backing) {
backing->Read(&header);
if (header.magic != util::MakeMagic<u32>("NRO0"))
throw exception("Invalid NRO magic! 0x{0:X}", header.magic);
// The homebrew asset section is appended to the end of an NRO file
if (backing->size > header.size) {
backing->Read(&assetHeader, header.size);
if (assetHeader.magic != util::MakeMagic<u32>("ASET"))
throw exception("Invalid ASET magic! 0x{0:X}", assetHeader.magic);
NroAssetSection &nacpHeader = assetHeader.nacp;
nacp = std::make_shared<vfs::NACP>(std::make_shared<vfs::RegionBacking>(backing, header.size + nacpHeader.offset, nacpHeader.size));
}
}
std::vector<u8> NroLoader::GetIcon() {
NroAssetSection &segmentHeader = assetHeader.icon;
std::vector<u8> buffer(segmentHeader.size);
backing->Read(buffer.data(), header.size + segmentHeader.offset, segmentHeader.size);
return buffer;
}
std::vector<u8> NroLoader::GetSegment(NroSegmentType segment) {
NroSegmentHeader &segmentHeader = header.segments[static_cast<int>(segment)];
std::vector<u8> buffer(segmentHeader.size);
backing->Read(buffer.data(), segmentHeader.offset, segmentHeader.size);
return buffer;
}
void NroLoader::LoadProcessData(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state) {
std::vector<u8> text(header.text.size);
std::vector<u8> rodata(header.ro.size);
std::vector<u8> data(header.data.size);
ReadOffset(text.data(), header.text.offset, header.text.size);
ReadOffset(rodata.data(), header.ro.offset, header.ro.size);
ReadOffset(data.data(), header.data.offset, header.data.size);
std::vector<u32> patch = state.nce->PatchCode(text, constant::BaseAddress, header.text.size + header.ro.size + header.data.size + header.bssSize);
std::vector<u8> text = GetSegment(loader::NroLoader::NroSegmentType::Text);
std::vector<u8> rodata = GetSegment(loader::NroLoader::NroSegmentType::RO);
std::vector<u8> data = GetSegment(loader::NroLoader::NroSegmentType::Data);
u64 textSize = text.size();
u64 rodataSize = rodata.size();
u64 dataSize = data.size();
u64 bssSize = header.bssSize;
std::vector<u32> patch = state.nce->PatchCode(text, constant::BaseAddress, textSize + rodataSize + dataSize + bssSize);
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);
u64 patchSize = patch.size() * sizeof(u32);
u64 padding = util::AlignUp(textSize + rodataSize + dataSize + header.bssSize + patchSize, PAGE_SIZE) - (textSize + rodataSize + dataSize + header.bssSize + patchSize);
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
state.logger->Debug("Successfully mapped section .text @ 0x{0:X}, Size = 0x{1:X}", constant::BaseAddress, textSize);
@ -43,17 +70,17 @@ namespace skyline::loader {
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, header.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, header.bssSize);
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 + header.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 + header.bssSize, patchSize);
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 + header.bssSize, patchSize);
process->WriteMemory(patch.data(), constant::BaseAddress + textSize + rodataSize + dataSize + bssSize, patchSize);
state.os->memory.InitializeRegions(constant::BaseAddress, textSize + rodataSize + dataSize + header.bssSize + patchSize + padding, memory::AddressSpaceType::AddressSpace39Bit);
state.os->memory.InitializeRegions(constant::BaseAddress, textSize + rodataSize + dataSize + bssSize + patchSize + padding, memory::AddressSpaceType::AddressSpace39Bit);
}
}

View File

@ -3,10 +3,13 @@
#pragma once
#include <cstdint>
#include <common.h>
#include "loader.h"
namespace skyline::loader {
/**
* @brief The NroLoader class abstracts access to an NRO file through the Loader interface (https://switchbrew.org/wiki/NRO)
*/
class NroLoader : public Loader {
private:
/**
@ -21,40 +24,67 @@ namespace skyline::loader {
* @brief This holds the header of an NRO file
*/
struct NroHeader {
u32 : 32;
u32 _pad0_;
u32 modOffset; //!< The offset of the MOD metadata
u64 : 64;
u64 _pad1_;
u32 magic; //!< The NRO magic "NRO0"
u32 version; //!< The version of the application
u32 size; //!< The size of the NRO
u32 flags; //!< The flags used with the NRO
NroSegmentHeader text; //!< The .text segment header
NroSegmentHeader ro; //!< The .ro segment header
NroSegmentHeader data; //!< The .data segment header
NroSegmentHeader segments[3]; //!< The .text segment header
u32 bssSize; //!< The size of the bss segment
u32 : 32;
u32 _pad2_;
u64 buildId[4]; //!< The build ID of the NRO
u64 : 64;
u64 _pad3_;
NroSegmentHeader apiInfo; //!< The .apiInfo segment header
NroSegmentHeader dynstr; //!< The .dynstr segment header
NroSegmentHeader dynsym; //!< The .dynsym segment header
} header{};
public:
/**
* @param fd A file descriptor to the ROM
* @brief This holds a single asset sections's offset and size
*/
NroLoader(int fd);
struct NroAssetSection {
u64 offset; //!< The offset of the region
u64 size; //!< The size of the region
};
/**
* @brief This loads in the data of the main process
* @param process The process to load in the data
* @param state The state of the device
* @brief This holds various metadata about an NRO, it is only used by homebrew
*/
struct NroAssetHeader {
u32 magic; //!< The asset section magic "ASET"
u32 version; //!< The format version
NroAssetSection icon; //!< The header describing the location of the icon
NroAssetSection nacp; //!< The header describing the location of the nacp
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
* @return A buffer containing the data of the requested segment
*/
std::vector<u8> GetSegment(NroSegmentType segment);
public:
NroLoader(const std::shared_ptr<vfs::Backing> &backing);
std::vector<u8> GetIcon();
void LoadProcessData(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state);
};
}

View File

@ -1,18 +1,20 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include "os.h"
#include "vfs/os_backing.h"
#include "loader/nro.h"
#include "nce/guest.h"
#include "os.h"
namespace skyline::kernel {
OS::OS(std::shared_ptr<JvmManager> &jvmManager, std::shared_ptr<Logger> &logger, std::shared_ptr<Settings> &settings) : state(this, process, jvmManager, settings, logger), memory(state), serviceManager(state) {}
void OS::Execute(int romFd, TitleFormat romType) {
void OS::Execute(int romFd, loader::RomFormat romType) {
std::shared_ptr<loader::Loader> loader;
auto romFile = std::make_shared<vfs::OsBacking>(romFd);
if (romType == TitleFormat::NRO) {
loader = std::make_shared<loader::NroLoader>(romFd);
if (romType == loader::RomFormat::NRO) {
loader = std::make_shared<loader::NroLoader>(romFile);
} else
throw exception("Unsupported ROM extension.");

View File

@ -6,6 +6,7 @@
#include <thread>
#include <sys/mman.h>
#include "common.h"
#include "loader/loader.h"
#include "kernel/ipc.h"
#include "kernel/types/KProcess.h"
#include "kernel/types/KThread.h"
@ -37,7 +38,7 @@ namespace skyline::kernel {
* @param romFd A FD to the ROM file to execute
* @param romType The type of the ROM file
*/
void Execute(int romFd, TitleFormat romType);
void Execute(int romFd, loader::RomFormat romType);
/**
* @brief Creates a new process

View File

@ -32,6 +32,9 @@ namespace skyline::vfs {
static_assert(sizeof(NacpData) == 0x4000);
public:
/**
* @param backing The backing for the NACP
*/
NACP(const std::shared_ptr<vfs::Backing> &backing);
std::string applicationName; //!< The name of the application in the currently selected language