Milestone 1 - Processes & Threads

Reworks the API a fair bit and adds documentation to almost everything.
This commit is contained in:
◱ PixelyIon 2019-09-05 18:12:19 +05:30
parent 62cb561888
commit 9e1e06c64b
43 changed files with 2029 additions and 688 deletions

7
.gitignore vendored
View File

@ -89,3 +89,10 @@ fabric.properties
# Android Studio captures folder
captures/
# VSCode
.vscode/
# Android Studio
.cxx/

View File

@ -14,6 +14,7 @@
<extensions>
<pair source="cpp" header="h" fileNamingConvention="SNAKE_CASE" />
<pair source="c" header="h" fileNamingConvention="SNAKE_CASE" />
<pair source="cpp" header="h" fileNamingConvention="PASCAL_CASE" />
</extensions>
</Objective-C-extensions>
<XML>
@ -30,5 +31,114 @@
<option name="KEEP_MULTIPLE_EXPRESSIONS_IN_ONE_LINE" value="true" />
<option name="WRAP_LONG_LINES" value="true" />
</codeStyleSettings>
<codeStyleSettings language="XML">
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:id</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>style</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>ANDROID_ATTRIBUTE_ORDER</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
</code_scheme>
</component>

6
.idea/discord.xml generated
View File

@ -1,4 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DiscordIntegrationProjectSettings" description="Lightswitch is an experimental Nintendo Switch emulator for Android phones." />
<component name="DiscordProjectSettings">
<option name="show" value="true" />
</component>
<component name="ProjectNotificationSettings">
<option name="askShowProject" value="false" />
</component>
</project>

4
.idea/misc.xml generated
View File

@ -1,5 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavadocGenerationManager">
<option name="OUTPUT_DIRECTORY" value="$PROJECT_DIR$/../LightSwitchEXTRA/JDoc" />
<option name="OPTION_SCOPE" value="private" />
</component>
<component name="NullableNotNullManager">
<option name="myDefaultNullable" value="org.jetbrains.annotations.Nullable" />
<option name="myDefaultNotNull" value="androidx.annotation.NonNull" />

View File

@ -1,7 +1,7 @@
LightSwitch, a Nintendo Switch emulator for Android
Skyline, a Nintendo Switch emulator for Android
=============
LightSwitch is an experimental Nintendo Switch emulator for Android phones, licensed under GPLv3. Please refer to the [license file](https://github.com/lightswitch-emu/lightswitch/blob/master/LICENSE) for more information. It currently does not run any games, nor Homebrew. It has no graphical output as of now.
Skyline is an experimental Nintendo Switch emulator for Android phones, licensed under GPLv3. Please refer to the [license file](https://github.com/lightswitch-emu/lightswitch/blob/master/LICENSE) for more information. It currently does not run any games, nor Homebrew. It has no graphical output as of now.
### Contact
You can contact the core developers of LightSwitch at our [Discord](https://discord.gg/XnbXNQM). If you have any questions, feel free to ask.
You can contact the core developers of Skyline at our [Discord](https://discord.gg/XnbXNQM). If you have any questions, feel free to ask.

View File

@ -2,9 +2,11 @@ cmake_minimum_required(VERSION 3.8)
project(Lightswitch VERSION 1 LANGUAGES CXX)
set_property(GLOBAL PROPERTY CMAKE_CXX_STANDARD 17 PROPERTY CMAKE_CXX_STANDARD_REQUIRED TRUE)
set(BUILD_TESTS FALSE)
set(BUILD_TESTING OFF)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -Ofast -flto=full")
add_subdirectory("libraries/tinyxml2")
add_subdirectory("libraries/fmt")
@ -14,13 +16,15 @@ include_directories(${source_DIR})
add_library(lightswitch SHARED
${source_DIR}/lightswitch.cpp
${source_DIR}/switch/os/os.cpp
${source_DIR}/switch/os/ipc.cpp
${source_DIR}/switch/os/svc.cpp
${source_DIR}/switch/hw/cpu.cpp
${source_DIR}/switch/hw/memory.cpp
${source_DIR}/switch/common.cpp
${source_DIR}/switch/nce.cpp
${source_DIR}/switch/os.cpp
${source_DIR}/switch/loader/nro.cpp
${source_DIR}/switch/kernel/ipc.cpp
${source_DIR}/switch/kernel/svc.cpp
${source_DIR}/switch/kernel/service.cpp
${source_DIR}/switch/kernel/types/KProcess.cpp
${source_DIR}/switch/kernel/types/KThread.cpp
)
target_link_libraries(lightswitch fmt tinyxml2)
target_link_libraries(lightswitch fmt tinyxml2 android)
target_compile_options(lightswitch PRIVATE -Wno-c++17-extensions)

View File

@ -5,7 +5,7 @@ android {
buildToolsVersion "29.0.0"
defaultConfig {
applicationId "lightswitch.emu"
minSdkVersion 24
minSdkVersion 26
targetSdkVersion 29
versionCode 1
versionName "1.0"
@ -16,11 +16,9 @@ android {
buildTypes {
release {
minifyEnabled true
useProguard false
}
debug {
minifyEnabled false
useProguard false
}
}
externalNativeBuild {

View File

@ -1,43 +1,40 @@
#include <jni.h>
#include <string>
#include <csignal>
#include <thread>
#include <pthread.h>
#include "switch/device.h"
#include <csignal>
#include <string>
#include <thread>
#include "switch/common.h"
#include "switch/os.h"
std::thread *emu_thread;
bool halt = false;
void thread_main(std::string rom_path, std::string pref_path, std::string log_path) {
auto log = std::make_shared<lightSwitch::Logger>(log_path);
log->write(lightSwitch::Logger::INFO, "Launching ROM {0}", rom_path);
auto settings = std::make_shared<lightSwitch::Settings>(pref_path);
try {
lightSwitch::device device(log, settings);
device.run(rom_path);
log->write(lightSwitch::Logger::INFO, "Emulation has ended.");
lightSwitch::kernel::OS os(log, settings);
log->Write(lightSwitch::Logger::INFO, "Launching ROM {}", rom_path);
os.Execute(rom_path);
log->Write(lightSwitch::Logger::INFO, "Emulation has ended");
} catch (std::exception &e) {
log->write(lightSwitch::Logger::ERROR, e.what());
log->Write(lightSwitch::Logger::ERROR, e.what());
} catch (...) {
log->write(lightSwitch::Logger::ERROR, "An unknown exception has occurred.");
log->Write(lightSwitch::Logger::ERROR, "An unknown exception has occurred");
}
}
extern "C"
JNIEXPORT void JNICALL
Java_emu_lightswitch_MainActivity_loadFile(JNIEnv *env, jobject instance, jstring rom_path_, jstring pref_path_, jstring log_path_) {
const char *rom_path = env->GetStringUTFChars(rom_path_, 0);
const char *pref_path = env->GetStringUTFChars(pref_path_, 0);
const char *log_path = env->GetStringUTFChars(log_path_, 0);
extern "C" JNIEXPORT void JNICALL Java_emu_lightswitch_MainActivity_loadFile(JNIEnv *env, jobject instance, jstring rom_path_, jstring pref_path_, jstring log_path_) {
const char *rom_path = env->GetStringUTFChars(rom_path_, nullptr);
const char *pref_path = env->GetStringUTFChars(pref_path_, nullptr);
const char *log_path = env->GetStringUTFChars(log_path_, nullptr);
if (emu_thread) {
halt=true; // This'll cause execution to stop after the next breakpoint
halt = true; // This'll cause execution to stop after the next breakpoint
emu_thread->join();
}
// Running on UI thread is not a good idea, any crashes and such will be propagated
// Running on UI thread is not a good idea as the UI will remain unresponsive
emu_thread = new std::thread(thread_main, std::string(rom_path, strlen(rom_path)), std::string(pref_path, strlen(pref_path)), std::string(log_path, strlen(log_path)));
env->ReleaseStringUTFChars(rom_path_, rom_path);

View File

@ -3,13 +3,36 @@
#include <syslog.h>
namespace lightSwitch {
// Settings
namespace memory {
Permission::Permission() {
r = 0;
w = 0;
x = 0;
}
Permission::Permission(bool read, bool write, bool execute) {
r = read;
w = write;
x = execute;
}
bool Permission::operator==(const Permission &rhs) const { return (this->r == rhs.r && this->w == rhs.w && this->x == rhs.x); }
bool Permission::operator!=(const Permission &rhs) const { return !operator==(rhs); }
int Permission::get() const {
int perm = 0;
if (r) perm |= PROT_READ;
if (w) perm |= PROT_WRITE;
if (x) perm |= PROT_EXEC;
return perm;
}
}
Settings::Settings(std::string pref_xml) {
tinyxml2::XMLDocument pref;
if (pref.LoadFile(pref_xml.c_str())) {
syslog(LOG_ERR, "TinyXML2 Error: %s", pref.ErrorStr());
throw pref.ErrorID();
}
if (pref.LoadFile(pref_xml.c_str()))
throw exception("TinyXML2 Error: " + std::string(pref.ErrorStr()));
tinyxml2::XMLElement *elem = pref.LastChild()->FirstChild()->ToElement();
while (elem) {
switch (elem->Value()[0]) {
@ -55,26 +78,27 @@ namespace lightSwitch {
}
}
// Logger
Logger::Logger(std::string log_path) {
Logger::Logger(const std::string & log_path) {
log_file.open(log_path, std::ios::app);
write_header("Logging started");
WriteHeader("Logging started");
}
Logger::~Logger() {
write_header("Logging ended");
WriteHeader("Logging ended");
}
void Logger::write(Logger::LogLevel level, std::string str) {
if (level == DEBUG && debug_build)
return;
void Logger::WriteHeader(const std::string& str) {
syslog(LOG_ALERT, "%s", str.c_str());
log_file << "0|" << str << "\n";
log_file.flush();
}
void Logger::Write(const LogLevel level, const std::string& str) {
#ifdef NDEBUG
if (level == DEBUG) return;
#endif
syslog(level_syslog[level], "%s", str.c_str());
log_file << "1|" << level_str[level] << "|" << str << "\n";
log_file.flush();
}
void Logger::write_header(std::string str) {
log_file << "0|" << str << "\n";
log_file.flush();
}
}

View File

@ -1,68 +1,283 @@
#pragma once
#include <map>
#include <unordered_map>
#include <vector>
#include <fstream>
#include <syslog.h>
#include <string>
#include <sstream>
#include <memory>
#include <fmt/format.h>
#include "hw/cpu.h"
#include "hw/memory.h"
#include <sys/mman.h>
#include <sys/ptrace.h>
#include <cstdint>
#include <stdexcept>
#include <string>
namespace lightSwitch {
// Global typedefs
typedef std::runtime_error exception; //!< This is used as the default exception
typedef uint32_t handle_t; //!< The type of an handle
namespace constant {
// Memory
constexpr uint64_t base_addr = 0x8000000; //!< The address space base
constexpr uint64_t base_size = 0x7FF8000000; //!< The size of the address space
constexpr uint64_t total_phy_mem = 0xF8000000; // ~4 GB of RAM
constexpr size_t def_stack_size = 0x1E8480; //!< The default amount of stack: 2 MB
constexpr size_t tls_slot_size = 0x200; //!< The size of a single TLS slot
constexpr uint8_t tls_slots = PAGE_SIZE / constant::tls_slot_size; //!< The amount of TLS slots in a single page
// Loader
constexpr uint32_t nro_magic = 0x304F524E; //!< "NRO0" in reverse, this is written at the start of every NRO file
// NCE
constexpr uint8_t num_regs = 31; //!< The amount of registers that ARMv8 has
constexpr uint16_t svc_last = 0x7F; //!< The index of the last SVC
constexpr uint16_t brk_rdy = 0xFF; //!< This is reserved for our kernel's to know when a process/thread is ready
constexpr uint32_t tpidrro_el0 = 0x5E83; //!< ID of tpidrro_el0 in MRS
// IPC
constexpr size_t tls_ipc_size = 0x100; //!< The size of the IPC command buffer in a TLS slot
constexpr uint64_t sm_handle = 0xd000; //!< sm:'s handle
constexpr uint8_t port_size = 0x8; //!< The size of a port name string
constexpr uint32_t ipc_sfco = 0x4F434653; //!< SFCO in reverse
// Process
constexpr uint32_t base_handle_index = 0xD001; // The index of the base handle
constexpr uint8_t default_priority = 31; //!< The default priority of a process
constexpr std::pair<int8_t, int8_t> priority_an = {19, -8}; //!< The range of priority for Android, taken from https://medium.com/mindorks/exploring-android-thread-priority-5d0542eebbd1
constexpr std::pair<uint8_t, uint8_t> priority_nin = {0, 63}; //!< The range of priority for the Nintendo Switch
};
namespace instr {
/**
* A bitfield struct that encapsulates a BRK instruction. It can be used to generate as well as parse the instruction's opcode. See https://developer.arm.com/docs/ddi0596/latest/base-instructions-alphabetic-order/brk-breakpoint-instruction.
*/
struct brk {
/**
* Creates a BRK instruction with a specific immediate value, used for generating BRK opcodes
* @param val The immediate value of the instruction
*/
brk(uint16_t val) {
start = 0x0; // First 5 bits of an BRK instruction are 0
value = val;
end = 0x6A1; // Last 11 bits of an BRK instruction stored as uint16_t
}
/**
* @return If the opcode represents a valid BRK instruction
*/
bool verify() {
return (start == 0x0 && end == 0x6A1);
}
uint8_t start : 5;
uint32_t value : 16;
uint16_t end : 11;
};
/**
* A bitfield struct that encapsulates a SVC instruction. See https://developer.arm.com/docs/ddi0596/latest/base-instructions-alphabetic-order/svc-supervisor-call.
*/
struct svc {
/**
* @return If the opcode represents a valid SVC instruction
*/
bool verify() {
return (start == 0x1 && end == 0x6A0);
}
uint8_t start : 5;
uint32_t value : 16;
uint16_t end : 11;
};
/**
* A bitfield struct that encapsulates a MRS instruction. See https://developer.arm.com/docs/ddi0596/latest/base-instructions-alphabetic-order/mrs-move-system-register.
*/
struct mrs {
/**
* @return If the opcode represents a valid MRS instruction
*/
bool verify() {
return (end == 0xD53);
}
uint8_t dst_reg : 5;
uint32_t src_reg : 15;
uint16_t end : 12;
};
};
/**
* Read about ARMv8 registers here: https://developer.arm.com/docs/100878/latest/registers
*/
namespace regs {
enum xreg { x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21, x22, x23, x24, x25, x26, x27, x28, x29, x30 };
enum wreg { w0, w1, w2, w3, w4, w5, w6, w7, w8, w9, w10, w11, w12, w13, w14, w15, w16, w17, w18, w19, w20, w21, w22, w23, w24, w25, w26, w27, w28, w29, w30 };
enum sreg { sp, pc, pstate };
}
namespace memory {
/**
* The Permission struct holds the permission of a particular chunk of memory
*/
struct Permission {
/**
* Initializes all values to false
*/
Permission();
/**
* @param read If memory has read permission
* @param write If memory has write permission
* @param execute If memory has execute permission
*/
Permission(bool read, bool write, bool execute);
/**
* Equality operator between two Permission objects
*/
bool operator==(const Permission &rhs) const;
/**
* Inequality operator between two Permission objects
*/
bool operator!=(const Permission &rhs) const;
/**
* @return The value of the permission struct in mmap(2) format
*/
int get() const;
bool r, w, x;
};
/**
* Memory Regions that are mapped by the kernel
*/
enum class Region {
heap, tls, text, rodata, data, bss
};
/**
* The RegionData struct holds information about a corresponding Region of memory such as address and size
*/
struct RegionData {
uint64_t address;
size_t size;
Permission perms;
int fd;
};
}
/**
* The Settings class is used to access the parameters set in the Java component of the application
*/
class Settings {
private:
struct KeyCompare {
bool operator()(char const *a, char const *b) const {
return std::strcmp(a, b) < 0;
}
};
}; //!< This is a comparision operator between strings, implemented to store strings in a std::map
std::map<char *, char *, KeyCompare> string_map;
std::map<char *, bool, KeyCompare> bool_map;
std::map<char *, char *, KeyCompare> string_map; //!< A mapping from all keys to their corresponding string value
std::map<char *, bool, KeyCompare> bool_map; //!< A mapping from all keys to their corresponding boolean value
public:
/**
* @param pref_xml The path to the preference XML file
*/
Settings(std::string pref_xml);
/**
* @param key The key of the setting
* @return The string value of the setting
*/
char *GetString(char *key);
/**
* @param key The key of the setting
* @return The boolean value of the setting
*/
bool GetBool(char *key);
/**
* Writes all settings keys and values to syslog. This function is for development purposes.
*/
void List();
};
/**
* The Logger class is to generate a log of the program
*/
class Logger {
private:
std::ofstream log_file;
const char *level_str[4] = {"0", "1", "2", "3"};
int level_syslog[4] = {LOG_ERR, LOG_WARNING, LOG_INFO, LOG_DEBUG};
std::ofstream log_file; //!< An output stream to the log file
const char *level_str[4] = {"0", "1", "2", "3"}; //!< This is used to denote the LogLevel when written out to a file
static constexpr int level_syslog[4] = {LOG_ERR, LOG_WARNING, LOG_INFO, LOG_DEBUG}; //!< This corresponds to LogLevel and provides it's equivalent for syslog
public:
enum LogLevel {
ERROR,
WARN,
INFO,
DEBUG
};
enum LogLevel {ERROR, WARN, INFO, DEBUG}; //!< The level of a particular log
Logger(std::string log_path);
/**
* @param log_path The path to the log file
*/
Logger(const std::string &log_path);
/**
* Writes "Logging ended" to as a header
*/
~Logger();
void write_header(std::string str);
/**
* Writes a header, should only be used for emulation starting and ending
* @param str The value to be written
*/
void WriteHeader(const std::string &str);
void write(LogLevel level, std::string str);
/**
* Write a log to the log file
* @param level The level of the log
* @param str The value to be written
*/
void Write(const LogLevel level, const std::string &str);
/**
* Write a log to the log file with libfmt formatting
* @param level The level of the log
* @param format_str The value to be written, with libfmt formatting
* @param args The arguments based on format_str
*/
template<typename S, typename... Args>
void write(Logger::LogLevel level, const S &format_str, Args &&... args) {
write(level, fmt::format(format_str, args...));
void Write(Logger::LogLevel level, const S &format_str, Args &&... args) {
#ifdef NDEBUG
if (level == DEBUG) return;
#endif
Write(level, fmt::format(format_str, args...));
}
};
// Predeclare some classes here as we use them in device_state
class NCE;
namespace kernel {
namespace type {
class KProcess;
class KThread;
}
class OS;
}
/**
* This struct is used to hold the state of a device
*/
struct device_state {
std::shared_ptr<hw::Cpu> cpu;
std::shared_ptr<hw::Memory> memory;
device_state(kernel::OS *os, std::shared_ptr<kernel::type::KProcess> &this_process, std::shared_ptr<kernel::type::KThread> &this_thread, std::shared_ptr<NCE> nce, std::shared_ptr<Settings> settings, std::shared_ptr<Logger> logger) : os(os), nce(nce), settings(settings), logger(logger), this_process(this_process), this_thread(this_thread) {}
kernel::OS *os; // Because OS holds the device_state struct, it's destruction will accompany that of device_state
std::shared_ptr<kernel::type::KProcess>& this_process;
std::shared_ptr<kernel::type::KThread>& this_thread;
std::shared_ptr<NCE> nce;
std::shared_ptr<Settings> settings;
std::shared_ptr<Logger> logger;
};

View File

@ -1,73 +0,0 @@
#pragma once
#include <cstdint>
#include <stdexcept>
#include <string>
namespace lightSwitch {
typedef std::runtime_error exception;
#ifdef NDEBUG
constexpr bool debug_build = 0;
#else
constexpr bool debug_build = 1;
#endif
namespace constant {
constexpr uint64_t base_addr = 0x80000000;
constexpr uint64_t stack_addr = 0x3000000;
constexpr size_t stack_size = 280; //0x1000000
constexpr uint64_t tls_addr = 0x2000000;
constexpr size_t tls_size = 0x1000;
constexpr uint32_t nro_magic = 0x304F524E; // NRO0 in reverse
constexpr uint_t svc_unimpl = 0x177202; // "Unimplemented behaviour"
constexpr uint32_t base_handle_index = 0xD001;
constexpr uint16_t svc_last = 0x7F;
constexpr uint8_t num_regs = 31;
constexpr uint32_t tpidrro_el0 = 0x5E83; // ID of tpidrro_el0 in MRS
};
namespace instr {
// https://developer.arm.com/docs/ddi0596/latest/base-instructions-alphabetic-order/brk-breakpoint-instruction
struct brk {
brk(uint16_t val) {
start = 0x0; // First 5 bits of an BRK instruction are 0
value = val;
end = 0x6A1; // Last 11 bits of an BRK instruction stored as uint16_t
}
bool verify() {
return (start == 0x0 && end == 0x6A1);
}
uint8_t start : 5;
uint32_t value : 16;
uint16_t end : 11;
};
// https://developer.arm.com/docs/ddi0596/latest/base-instructions-alphabetic-order/svc-supervisor-call
struct svc {
bool verify() {
return (start == 0x1 && end == 0x6A0);
}
uint8_t start : 5;
uint32_t value : 16;
uint16_t end : 11;
};
// https://developer.arm.com/docs/ddi0596/latest/base-instructions-alphabetic-order/mrs-move-system-register
struct mrs {
bool verify() {
return (end == 0xD53);
}
uint8_t dst_reg : 5;
uint32_t src_reg : 15;
uint16_t end : 12;
};
};
enum xreg { x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21, x22, x23, x24, x25, x26, x27, x28, x29, x30 };
enum wreg { w0, w1, w2, w3, w4, w5, w6, w7, w8, w9, w10, w11, w12, w13, w14, w15, w16, w17, w18, w19, w20, w21, w22, w23, w24, w25, w26, w27, w28, w29, w30 };
}

View File

@ -1,27 +0,0 @@
#pragma once
#include "os/os.h"
#include "loader/nro.h"
namespace lightSwitch {
class device {
private:
std::shared_ptr<hw::Cpu> cpu;
std::shared_ptr<hw::Memory> memory;
os::OS os;
device_state state;
public:
device(std::shared_ptr<Logger> &logger, std::shared_ptr<Settings> &settings) : cpu(new hw::Cpu()), memory(new hw::Memory()), state{cpu, memory, settings, logger}, os({cpu, memory, settings, logger}) {};
void run(std::string rom_file) {
std::string rom_ext = rom_file.substr(rom_file.find_last_of('.') + 1);
std::transform(rom_ext.begin(), rom_ext.end(), rom_ext.begin(),
[](unsigned char c){ return std::tolower(c); });
if (rom_ext == "nro") loader::NroLoader loader(rom_file, state);
else throw exception("Unsupported ROM extension.");
cpu->Execute(hw::Memory::text, memory, std::bind(&os::OS::SvcHandler, std::ref(os), std::placeholders::_1, std::placeholders::_2), &state);
}
};
};

View File

@ -1,120 +0,0 @@
#include "cpu.h"
extern bool halt;
namespace lightSwitch::hw {
Cpu::~Cpu() {
if (child) kill(child, SIGKILL);
}
long *Cpu::ReadMemory(uint64_t address) { // Return a single word (32-bit)
status = ptrace(PTRACE_PEEKDATA, child, address, NULL);
if (status == -1) throw std::runtime_error("Cannot read memory");
return &status;
}
void Cpu::WriteMemory(uint64_t address) { // Write a single word (32-bit)
status = ptrace(PTRACE_GETREGSET, child, NT_PRSTATUS, &iov);
if (status == -1) throw std::runtime_error("Cannot write memory");
}
void Cpu::ReadRegisters() { // Read all registers into 'regs'
iov = {&regs, sizeof(regs)};
status = ptrace(PTRACE_GETREGSET, child, NT_PRSTATUS, &iov);
if (status == -1) throw std::runtime_error("Cannot read registers");
}
void Cpu::WriteRegisters() { // Write all registers from 'regs'
iov = {&regs, sizeof(regs)};
status = ptrace(PTRACE_SETREGSET, child, NT_PRSTATUS, &iov);
if (status == -1) throw std::runtime_error("Cannot write registers");
}
void Cpu::ResumeProcess() { // Resumes a process stopped due to a signal
status = ptrace(PTRACE_CONT, child, NULL, NULL);
if (status == -1) throw std::runtime_error("Cannot resume process");
}
void Cpu::WriteBreakpoint(uint64_t address_, uint64_t size) {
auto address = (uint32_t *) address_;
for (uint64_t iter = 0; iter < size; iter++) {
auto instr_svc = reinterpret_cast<instr::svc *>(address + iter);
auto instr_mrs = reinterpret_cast<instr::mrs *>(address + iter);
if (instr_svc->verify()) {
if(debug_build)
syslog(LOG_WARNING, "Found SVC call: 0x%X, At location 0x%X", instr_svc->value, address+iter);
instr::brk brk(static_cast<uint16_t>(instr_svc->value));
address[iter] = *reinterpret_cast<uint32_t *>(&brk);
} else if (instr_mrs->verify() && instr_mrs->src_reg == constant::tpidrro_el0) {
if(debug_build)
syslog(LOG_WARNING, "Found MRS call: 0x%X, At location 0x%X", instr_mrs->dst_reg, address+iter);
instr::brk brk(static_cast<uint16_t>(constant::svc_last + 1 + instr_mrs->dst_reg));
address[iter] = *reinterpret_cast<uint32_t *>(&brk);
}
}
}
void Cpu::Execute(Memory::Region region, std::shared_ptr<Memory> memory, std::function<void(uint16_t, void *)> svc_handler, void *device) {
hw::Memory::RegionData exec = memory->region_map.at(region);
WriteBreakpoint(exec.address, exec.size); // We write the BRK instructions to replace SVC & MRS so we receive a breakpoint
child = ExecuteChild(exec.address);
while (waitpid(child, &pid_status, 0)) {
if (WIFSTOPPED(pid_status)) {
ReadRegisters();
if(debug_build)
syslog(LOG_INFO, "PC is at 0x%X", regs.pc);
if (!regs.pc || regs.pc == 0xBADC0DE) break;
// We store the instruction value as the immediate value. 0x0 to 0x7F are SVC, 0x80 to 0x9E is MRS for TPIDRRO_EL0.
auto instr = reinterpret_cast<instr::brk *>(ReadMemory(regs.pc));
if (instr->verify()) {
if (instr->value <= constant::svc_last) {
svc_handler(static_cast<uint16_t>(instr->value), device);
if (debug_build)
syslog(LOG_ERR, "SVC has been called 0x%X", instr->value);
if (halt) break;
} else if (instr->value > constant::svc_last && instr->value <= constant::svc_last + constant::num_regs) {
// Catch MRS that reads the value of TPIDRRO_EL0 (TLS)
// https://switchbrew.org/wiki/Thread_Local_Storage
SetRegister(xreg(instr->value - (constant::svc_last + 1)), tls);
if (debug_build)
syslog(LOG_ERR, "MRS has been called 0x%X", instr->value - (constant::svc_last + 1));
} else syslog(LOG_ERR, "Received unhandled BRK 0x%X", instr->value);
}
regs.pc += 4; // Increment program counter by a single instruction (32 bits)
WriteRegisters();
} else if (WIFEXITED(pid_status)) break;
ResumeProcess();
}
kill(child, SIGABRT);
child = 0;
pid_status = 0;
halt = false;
}
pid_t Cpu::ExecuteChild(uint64_t address) {
pid_t pid = fork();
if (!pid) {
ptrace(PTRACE_TRACEME, 0, NULL, NULL);
asm volatile("br %0" :: "r"(address));
}
return pid;
}
void Cpu::StopExecution() { halt = true; }
uint64_t Cpu::GetRegister(xreg reg_id) {
return regs.regs[reg_id];
}
void Cpu::SetRegister(xreg reg_id, uint64_t value) {
regs.regs[reg_id] = value;
}
uint64_t Cpu::GetRegister(wreg reg_id) {
return (reinterpret_cast<uint32_t *>(&regs.regs))[wreg_lut[reg_id]];
}
void Cpu::SetRegister(wreg reg_id, uint32_t value) {
(reinterpret_cast<uint32_t *>(&regs.regs))[wreg_lut[reg_id]] = value;
}
}

View File

@ -1,54 +0,0 @@
#pragma once
#include <syslog.h>
#include <unistd.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <linux/uio.h>
#include <linux/elf.h>
#include "../constant.h"
#include "memory.h"
namespace lightSwitch::hw {
class Cpu {
private:
long status = 0;
int pid_status = 0;
pid_t child;
iovec iov;
user_pt_regs regs;
uint64_t tls = constant::tls_addr;
static pid_t ExecuteChild(uint64_t address);
void ReadRegisters();
void WriteRegisters();
long *ReadMemory(uint64_t address);
void WriteMemory(uint64_t address);
void ResumeProcess();
void WriteBreakpoint(uint64_t address, uint64_t size);
uint8_t wreg_lut[31] = {0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60};
public:
~Cpu();
void Execute(Memory::Region region, std::shared_ptr<Memory> memory, std::function<void(uint16_t, void *)> svc_handler, void *device);
void StopExecution();
uint64_t GetRegister(xreg reg_id);
void SetRegister(xreg reg_id, uint64_t value);
uint64_t GetRegister(wreg reg_id);
void SetRegister(wreg reg_id, uint32_t value);
};
}

View File

@ -1,56 +0,0 @@
#include <sys/mman.h>
#include <cerrno>
#include "memory.h"
namespace lightSwitch::hw {
Memory::Memory() {
// Map TLS memory
Memory::Map(constant::tls_addr, constant::tls_size, tls);
}
void Memory::Map(uint64_t address, size_t size, Region region) {
void *ptr = mmap((void *) address, size, PROT_EXEC | PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON | MAP_FIXED, 0, 0);
if (ptr == MAP_FAILED)
throw exception("An occurred while mapping region: " + std::string(strerror(errno)));
region_map.insert(std::pair<Region, RegionData>(region, {address, size}));
}
void Memory::Remap(Region region, size_t size) {
RegionData region_data = region_map.at(region);
void *ptr = mremap(reinterpret_cast<void *>(region_data.address), region_data.size, size, 0);
if (ptr == MAP_FAILED)
throw exception("An occurred while remapping region: " + std::string(strerror(errno)));
region_map[region].size = size;
}
void Memory::Unmap(Region region) {
RegionData region_data = region_map.at(region);
int err = munmap(reinterpret_cast<void *>(region_data.address), region_data.size);
if (err == -1)
throw exception("An occurred while unmapping region: " + std::string(strerror(errno)));
}
void Memory::Write(void *data, uint64_t offset, size_t size) {
std::memcpy(reinterpret_cast<void *>(offset), data, size);
}
template<typename T>
void Memory::Write(T value, uint64_t offset) {
Write(reinterpret_cast<void *>(&value), offset, sizeof(T));
}
void Memory::Read(void *destination, uint64_t offset, size_t size) {
std::memcpy(destination, reinterpret_cast<void *>(offset), size);
}
template<typename T>
T Memory::Read(uint64_t offset) {
T value;
Read(reinterpret_cast<void *>(&value), offset, sizeof(T));
return value;
}
}

View File

@ -1,38 +0,0 @@
#pragma once
#include "../constant.h"
#include <string>
#include <map>
namespace lightSwitch::hw {
class Memory {
public:
enum Region {
heap, tls, text, rodata, data, bss
};
struct RegionData {
uint64_t address;
size_t size;
};
std::map<Region, RegionData> region_map;
Memory();
void Map(uint64_t address, size_t size, Region region);
void Remap(Region region, size_t size);
void Unmap(Region region);
void Write(void *data, uint64_t offset, size_t size);
template<typename T>
void Write(T value, uint64_t offset);
void Read(void *destination, uint64_t offset, size_t size);
template<typename T>
T Read(uint64_t offset);
};
}

View File

@ -0,0 +1,102 @@
#include <syslog.h>
#include <cstdlib>
#include "ipc.h"
#include "types/KProcess.h"
namespace lightSwitch::kernel::ipc {
IpcRequest::IpcRequest(uint8_t *tls_ptr, device_state &state) : req_info((CommandStruct *) tls_ptr) {
state.logger->Write(Logger::DEBUG, "Enable handle descriptor: {0}", (bool) req_info->handle_desc);
data_offset = 8;
if (req_info->handle_desc) {
HandleDescriptor handledesc = *(HandleDescriptor *) (&tls_ptr[8]);
state.logger->Write(Logger::DEBUG, "Moving {} handles and copying {} handles (0x{:X})", uint8_t(handledesc.move_count), uint8_t(handledesc.copy_count), tls_ptr[2]);
data_offset += 4 + handledesc.copy_count * 4 + handledesc.move_count * 4;
}
if (req_info->x_no || req_info->a_no || req_info->b_no || req_info->w_no)
state.logger->Write(Logger::ERROR, "IPC - Descriptors");
// Align to 16 bytes
data_offset = ((data_offset - 1) & ~(15U)) + 16; // ceil data_offset with multiples 16
data_ptr = &tls_ptr[data_offset + sizeof(req_info)];
state.logger->Write(Logger::DEBUG, "Type: 0x{:X}", (uint8_t) req_info->type);
state.logger->Write(Logger::DEBUG, "X descriptors: {}", (uint8_t) req_info->x_no);
state.logger->Write(Logger::DEBUG, "A descriptors: {}", (uint8_t) req_info->a_no);
state.logger->Write(Logger::DEBUG, "B descriptors: {}", (uint8_t) req_info->b_no);
state.logger->Write(Logger::DEBUG, "W descriptors: {}", (uint8_t) req_info->w_no);
state.logger->Write(Logger::DEBUG, "Raw data offset: 0x{:X}", data_offset);
state.logger->Write(Logger::DEBUG, "Raw data size: {}", (uint16_t) req_info->data_sz);
state.logger->Write(Logger::DEBUG, "Payload Command ID: {0} (0x{0:X})", *((uint32_t *) &tls_ptr[data_offset + 8]));
}
template<typename T>
T IpcRequest::GetValue() {
data_offset += sizeof(T);
return *reinterpret_cast<T *>(&data_ptr[data_offset - sizeof(T)]);
}
IpcResponse::IpcResponse() : resp_info{0} {
tls_ptr = reinterpret_cast<uint32_t *>(new uint8_t[constant::tls_ipc_size]);
}
IpcResponse::~IpcResponse() {
delete[] tls_ptr;
}
void IpcResponse::Generate(device_state &state) {
state.logger->Write(Logger::DEBUG, "Moving {} handles and copying {} handles", moved_handles.size(), copied_handles.size());
resp_info.type = 0;
data_offset = 8;
if (!moved_handles.empty() || !copied_handles.empty()) {
resp_info.handle_desc = true;
HandleDescriptor handledesc{};
handledesc.copy_count = static_cast<uint8_t>(copied_handles.size());
handledesc.move_count = static_cast<uint8_t>(moved_handles.size());
*(HandleDescriptor *) &tls_ptr[2] = handledesc;
uint64_t handle_index = 0;
for (auto& copied_handle : copied_handles) {
state.logger->Write(Logger::DEBUG, "Copying handle to 0x{:X}", 12 + handle_index * 4);
tls_ptr[3 + handle_index] = copied_handle;
handle_index += 1;
}
for (auto& moved_handle : moved_handles) {
state.logger->Write(Logger::DEBUG, "Moving handle to 0x{:X}", 12 + handle_index * 4);
tls_ptr[3 + handle_index] = moved_handle;
handle_index += 1;
}
data_offset += 4 + copied_handles.size() * 4 + moved_handles.size() * 4;
}
state.logger->Write(Logger::DEBUG, "Data offset: 0x{:X}", data_offset);
// Align to 16 bytes
data_offset = ((data_offset - 1) & ~(15U)) + 16; // ceil data_offset with multiples 16
if (is_domain) {
tls_ptr[data_offset >> 2] = static_cast<uint32_t>(moved_handles.size());
data_offset += 0x10;
}
data_offset >>= 2;
// TODO: Calculate data size
resp_info.data_sz = static_cast<uint16_t>(16 + (is_domain ? 4 : 0)); // + data_words;
tls_ptr[data_offset] = constant::ipc_sfco;
tls_ptr[data_offset + 2] = error_code;
}
void IpcResponse::SetError(uint32_t _error_code) { error_code = _error_code; }
template<typename T>
void IpcResponse::WriteValue() {
}
void IpcResponse::CopyHandle(uint32_t handle) { copied_handles.push_back(handle); }
void IpcResponse::MoveHandle(uint32_t handle) { moved_handles.push_back(handle); }
}

View File

@ -0,0 +1,69 @@
#pragma once
#include <cstdint>
#include <vector>
#include "switch/common.h"
namespace lightSwitch::kernel::ipc {
struct CommandStruct {
// https://switchbrew.org/wiki/IPC_Marshalling#IPC_Command_Structure
uint16_t type : 16;
uint8_t x_no : 4;
uint8_t a_no : 4;
uint8_t b_no : 4;
uint8_t w_no : 4;
uint16_t data_sz : 10;
uint8_t c_flags : 4;
uint32_t : 17;
bool handle_desc : 1;
};
struct HandleDescriptor {
bool send_pid : 1;
uint8_t copy_count : 4;
uint8_t move_count : 4;
};
class IpcRequest {
private:
uint8_t *data_ptr;
uint32_t data_offset;
public:
CommandStruct *req_info;
IpcRequest(uint8_t *tlsPtr, device_state &state);
template<typename T>
T GetValue();
};
class IpcResponse {
private:
uint32_t *tls_ptr{};
uint32_t data_offset{}; // Offset to raw data relative to tls_ptr
bool is_domain{}; // TODO
uint32_t error_code{};
CommandStruct resp_info;
std::vector<uint32_t> copied_handles;
std::vector<uint32_t> moved_handles;
std::vector<uint8_t> data;
uint16_t data_pos{}; // Position in raw data relative to data_offset
public:
IpcResponse();
~IpcResponse();
void Generate(device_state &state);
void SetError(uint32_t _error_code);
template<typename T>
void WriteValue(); // TODO
void CopyHandle(uint32_t handle);
void MoveHandle(uint32_t handle);
};
}

View File

@ -0,0 +1,7 @@
#include "service.h"
namespace lightSwitch::kernel::service {
Service::Service() {
}
}

View File

@ -0,0 +1,9 @@
#pragma once
#include "../nce.h"
namespace lightSwitch::kernel::service {
class Service {
Service();
};
}

View File

@ -0,0 +1,8 @@
#include "../../common.h"
namespace lightSwitch::kernel::service {
class BaseService {
virtual const char* name() = 0;
BaseService() {}
};
}

View File

@ -0,0 +1,136 @@
#include <cstdint>
#include <string>
#include <syslog.h>
#include <utility>
#include "svc.h"
namespace lightSwitch::kernel::svc {
void SetHeapSize(device_state &state) {
uint64_t addr = state.this_process->MapPrivate(0, state.nce->GetRegister(regs::w1), {true, true, false}, memory::Region::heap);
state.nce->SetRegister(regs::w0, constant::status::success);
state.nce->SetRegister(regs::x1, addr);
}
void CreateThread(device_state &state) {
// TODO: Check if the values supplied by the process are actually valid & Support Core Mask potentially ?
auto thread = state.this_process->CreateThread(state.nce->GetRegister(regs::x1), state.nce->GetRegister(regs::x2), state.nce->GetRegister(regs::x3), static_cast<uint8_t>(state.nce->GetRegister(regs::w4)));
state.nce->SetRegister(regs::w0, constant::status::success);
state.nce->SetRegister(regs::w1, thread->handle);
}
void StartThread(device_state &state) {
auto& object = state.this_process->handle_table.at(static_cast<const unsigned int &>(state.nce->GetRegister(regs::w0)));
if(object->type==type::KObjectType::KThread) {
std::static_pointer_cast<type::KThread>(object)->Start();
} else
throw exception("StartThread was called on a non-KThread object");
}
void ExitThread(device_state &state) {
state.os->KillThread(state.this_thread->pid);
}
void GetThreadPriority(device_state &state) {
auto& object = state.this_process->handle_table.at(static_cast<const unsigned int &>(state.nce->GetRegister(regs::w0)));
if(object->type==type::KObjectType::KThread) {
state.nce->SetRegister(regs::w0, constant::status::success);
state.nce->SetRegister(regs::w1, std::static_pointer_cast<type::KThread>(object)->priority);
} else
throw exception("GetThreadPriority was called on a non-KThread object");
}
void SetThreadPriority(device_state &state) {
auto& object = state.this_process->handle_table.at(static_cast<const unsigned int &>(state.nce->GetRegister(regs::w0)));
if(object->type==type::KObjectType::KThread) {
std::static_pointer_cast<type::KThread>(object)->Start();
} else
throw exception("SetThreadPriority was called on a non-KThread object");
}
void CloseHandle(device_state &state) {
auto& object = state.this_process->handle_table.at(static_cast<const unsigned int &>(state.nce->GetRegister(regs::w0)));
switch (object->type) {
case(type::KObjectType::KThread):
state.os->KillThread(std::static_pointer_cast<type::KThread>(object)->pid);
break;
case(type::KObjectType::KProcess):
state.os->KillThread(std::static_pointer_cast<type::KProcess>(object)->main_thread);
break;
}
state.nce->SetRegister(regs::w0, constant::status::success);
}
void ConnectToNamedPort(device_state &state) {
char port[constant::port_size]{0};
state.os->this_process->ReadMemory(port, state.nce->GetRegister(regs::x1), constant::port_size);
if (std::strcmp(port, "sm:") == 0)
state.nce->SetRegister(regs::w1, constant::sm_handle);
else
throw exception(fmt::format("svcConnectToNamedPort tried connecting to invalid port: \"{}\"", port));
state.nce->SetRegister(regs::w0, constant::status::success);
}
void SendSyncRequest(device_state &state) {
state.logger->Write(Logger::DEBUG, "svcSendSyncRequest called for handle 0x{:X}.", state.nce->GetRegister(regs::x0));
uint8_t tls[constant::tls_ipc_size];
state.os->this_process->ReadMemory(&tls, state.this_thread->tls, constant::tls_ipc_size);
ipc::IpcRequest request(tls, state);
state.os->IpcHandler(request);
}
void OutputDebugString(device_state &state) {
std::string debug(state.nce->GetRegister(regs::x1), '\0');
state.os->this_process->ReadMemory((void *) debug.data(), state.nce->GetRegister(regs::x0), state.nce->GetRegister(regs::x1));
state.logger->Write(Logger::INFO, "svcOutputDebugString: {}", debug.c_str());
state.nce->SetRegister(regs::w0, 0);
}
void GetInfo(device_state &state) {
state.logger->Write(Logger::DEBUG, "svcGetInfo called with ID0: {}, ID1: {}", state.nce->GetRegister(regs::w1), state.nce->GetRegister(regs::x3));
switch (state.nce->GetRegister(regs::w1)) {
case constant::infoState::AllowedCpuIdBitmask:
case constant::infoState::AllowedThreadPriorityMask:
case constant::infoState::IsCurrentProcessBeingDebugged:
case constant::infoState::TitleId:
case constant::infoState::PrivilegedProcessId:
state.nce->SetRegister(regs::x1, 0);
break;
case constant::infoState::HeapRegionBaseAddr:
state.nce->SetRegister(regs::x1, state.os->this_process->memory_map.at(memory::Region::heap).address);
break;
case constant::infoState::HeapRegionSize:
state.nce->SetRegister(regs::x1, state.os->this_process->memory_map.at(memory::Region::heap).size);
break;
case constant::infoState::AddressSpaceBaseAddr:
state.nce->SetRegister(regs::x1, constant::base_addr);
break;
case constant::infoState::AddressSpaceSize:
state.nce->SetRegister(regs::x1, constant::base_size);
break;
case constant::infoState::PersonalMmHeapSize:
state.nce->SetRegister(regs::x1, constant::total_phy_mem);
break;
case constant::infoState::PersonalMmHeapUsage:
state.nce->SetRegister(regs::x1, state.os->this_process->memory_map.at(memory::Region::heap).address + state.this_process->main_thread_stack_sz);
break;
case constant::infoState::TotalMemoryAvailableWithoutMmHeap:
state.nce->SetRegister(regs::x1, constant::total_phy_mem); // TODO: NPDM specifies SystemResourceSize, subtract that from this
break;
case constant::infoState::TotalMemoryUsedWithoutMmHeap:
state.nce->SetRegister(regs::x1, state.os->this_process->memory_map.at(memory::Region::heap).address + state.this_process->main_thread_stack_sz); // TODO: Same as above
break;
case constant::infoState::UserExceptionContextAddr:
state.nce->SetRegister(regs::x1, state.this_process->tls_pages[0]->Get(0));
break;
default:
state.logger->Write(Logger::WARN, "Unimplemented svcGetInfo with ID0: {}, ID1: {}", state.nce->GetRegister(regs::w1), state.nce->GetRegister(regs::x3));
state.nce->SetRegister(regs::w0, constant::status::unimpl);
return;
}
state.nce->SetRegister(regs::w0, constant::status::success);
}
void ExitProcess(device_state &state) {
state.os->KillThread(state.this_process->main_thread);
}
}

View File

@ -2,13 +2,9 @@
#include "ipc.h"
#include "../common.h"
// https://switchbrew.org/wiki/SVC
#include "switch/os.h"
namespace lightSwitch::constant {
constexpr uint64_t sm_handle = 0xd000; // sm: is hardcoded for now
constexpr uint32_t tls_ipc_size = 0x100;
constexpr uint8_t port_size = 0x8;
namespace infoState {
// 1.0.0+
constexpr uint8_t AllowedCpuIdBitmask = 0x0;
@ -32,40 +28,99 @@ namespace lightSwitch::constant {
constexpr uint8_t PersonalMmHeapSize = 0x10;
constexpr uint8_t PersonalMmHeapUsage = 0x11;
constexpr uint8_t TitleId = 0x12;
// 4.0.0+
constexpr uint8_t PrivilegedProcessId = 0x13;
// 5.0.0+
constexpr uint8_t UserExceptionContextAddr = 0x14;
// 6.0.0+
constexpr uint8_t TotalMemoryAvailableWithoutMmHeap = 0x15;
constexpr uint8_t TotalMemoryUsedWithoutMmHeap = 0x16;
};
namespace status {
constexpr uint32_t success = 0x0; //!< "Success"
constexpr uint32_t unimpl = 0x177202; //!< "Unimplemented behaviour"
}
};
namespace lightSwitch::os::svc {
void ConnectToNamedPort(device_state &state);
void SendSyncRequest(device_state &state);
void OutputDebugString(device_state &state);
void GetInfo(device_state &state);
namespace lightSwitch::kernel::svc {
/**
* Set the process heap to a given size (https://switchbrew.org/wiki/SVC#svcSetHeapSize)
*/
void SetHeapSize(device_state &state);
/**
* Exits the current process (https://switchbrew.org/wiki/SVC#svcExitProcess)
*/
void ExitProcess(device_state &state);
/**
* Create a thread in the current process (https://switchbrew.org/wiki/SVC#svcCreateThread)
*/
void CreateThread(device_state &state);
/**
* Starts the thread for the provided handle (https://switchbrew.org/wiki/SVC#svcStartThread)
*/
void StartThread(device_state &state);
/**
* Exits the current thread (https://switchbrew.org/wiki/SVC#svcExitThread)
*/
void ExitThread(device_state &state);
/**
* Get priority of provided thread handle (https://switchbrew.org/wiki/SVC#svcGetThreadPriority)
*/
void GetThreadPriority(device_state &state);
/**
* Set priority of provided thread handle (https://switchbrew.org/wiki/SVC#svcSetThreadPriority)
*/
void SetThreadPriority(device_state &state);
/**
* Closes the specified handle
*/
void CloseHandle(device_state &state);
/**
* Connects to a named IPC port
*/
void ConnectToNamedPort(device_state &state);
/**
* Send a synchronous IPC request to a service
*/
void SendSyncRequest(device_state &state);
/**
* Outputs a debug string
*/
void OutputDebugString(device_state &state);
/**
* Retrieves a piece of information (https://switchbrew.org/wiki/SVC#svcGetInfo)
*/
void GetInfo(device_state &state);
/**
* The SVC Table maps all SVCs to their corresponding functions
*/
void static (*svcTable[0x80])(device_state &) = {
nullptr, // 0x00
nullptr, // 0x01
nullptr, // 0x00 (Does not exist)
SetHeapSize, // 0x01
nullptr, // 0x02
nullptr, // 0x03
nullptr, // 0x04
nullptr, // 0x05
nullptr, // 0x06
ExitProcess, // 0x07
nullptr, // 0x08
nullptr, // 0x09
nullptr, // 0x0a
CreateThread, // 0x08
StartThread, // 0x09
ExitThread, // 0x0a
nullptr, // 0x0b
nullptr, // 0x0c
nullptr, // 0x0d
GetThreadPriority, // 0x0c
SetThreadPriority, // 0x0d
nullptr, // 0x0e
nullptr, // 0x0f
nullptr, // 0x10
@ -74,7 +129,7 @@ namespace lightSwitch::os::svc {
nullptr, // 0x13
nullptr, // 0x14
nullptr, // 0x15
nullptr, // 0x16
CloseHandle, // 0x16
nullptr, // 0x17
nullptr, // 0x18
nullptr, // 0x19

View File

@ -0,0 +1,15 @@
#pragma once
#include "../../common.h"
namespace lightSwitch::kernel::type {
enum class KObjectType {
KThread, KProcess
};
class KObject {
public:
uint32_t handle;
KObjectType type;
KObject(handle_t handle, KObjectType type) : handle(handle), type(type) {}
};
}

View File

@ -0,0 +1,193 @@
#include "KProcess.h"
#include "../../nce.h"
#include <fcntl.h>
#include <unistd.h>
#include <utility>
namespace lightSwitch::kernel::type {
KProcess::tls_page_t::tls_page_t(uint64_t address) : address(address) {}
uint64_t KProcess::tls_page_t::ReserveSlot() {
if (Full())
throw exception("Trying to get TLS slot from full page");
slot[index] = true;
return Get(index++); // ++ on right will cause increment after evaluation of expression
}
uint64_t KProcess::tls_page_t::Get(uint8_t slot_no) {
if(slot_no>=constant::tls_slots)
throw exception("TLS slot is out of range");
return address + (constant::tls_slot_size * slot_no);
}
bool KProcess::tls_page_t::Full() {
return slot[constant::tls_slots - 1];
}
uint64_t KProcess::GetTLSSlot(bool init) {
if (!init)
for (auto &tls_page: tls_pages) {
if (!tls_page->Full())
return tls_page->ReserveSlot();
}
uint64_t address = MapPrivate(0, PAGE_SIZE, {true, true, false}, memory::Region::tls);
tls_pages.push_back(std::make_shared<tls_page_t>(address));
auto &tls_page = tls_pages.back();
if (init)
tls_page->ReserveSlot(); // User-mode exception handling
return tls_page->ReserveSlot();
}
KProcess::KProcess(pid_t pid, uint64_t entry_point, uint64_t stack_base, uint64_t stack_size, const device_state &state, handle_t handle) : state(state), handle(handle), main_thread_stack_sz(stack_size), KObject(handle, KObjectType::KProcess) {
process_state = process_state_t::Created;
main_thread = pid;
state.nce->WaitRdy(pid);
thread_map[main_thread] = std::make_shared<KThread>(handle_index, pid, entry_point, 0, stack_base + stack_size, GetTLSSlot(true), constant::default_priority, this, state);
NewHandle(std::static_pointer_cast<KObject>(thread_map[main_thread]));
mem_fd = open(fmt::format("/proc/{}/mem", pid).c_str(), O_RDWR | O_CLOEXEC); // NOLINT(hicpp-signed-bitwise)
if (mem_fd == -1) throw exception(fmt::format("Cannot open file descriptor to /proc/{}/mem", pid));
}
KProcess::~KProcess() {
close(mem_fd);
}
/**
* Function executed by all child threads after cloning
*/
int ExecuteChild(void *) {
ptrace(PTRACE_TRACEME);
asm volatile("brk #0xFF"); // BRK #constant::brk_rdy (So we know when the thread/process is ready)
return 0;
}
uint64_t CreateThreadFunc(uint64_t stack_top) {
pid_t pid = clone(&ExecuteChild, reinterpret_cast<void *>(stack_top), CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD | CLONE_SYSVSEM, nullptr); // NOLINT(hicpp-signed-bitwise)
return static_cast<uint64_t>(pid);
}
std::shared_ptr<KThread> KProcess::CreateThread(uint64_t entry_point, uint64_t entry_arg, uint64_t stack_top, uint8_t priority) {
user_pt_regs fregs = {0};
fregs.regs[0] = entry_point;
fregs.regs[1] = stack_top;
state.nce->ExecuteFunction((void *) CreateThreadFunc, fregs, main_thread);
if (fregs.regs[0]==-1)
throw exception(fmt::format("Cannot create thread: Address: {}, Stack Top: {}", entry_point, stack_top));
auto thread = std::make_shared<kernel::type::KThread>(handle_index, static_cast<pid_t>(fregs.regs[0]), entry_point, entry_arg, stack_top, GetTLSSlot(false), priority, this, state);
NewHandle(std::static_pointer_cast<KObject>(thread));
return thread;
}
template<typename T>
T KProcess::ReadMemory(uint64_t address) const {
T item{};
ReadMemory(&item, address, sizeof(T));
return item;
}
template<typename T>
void KProcess::WriteMemory(T &item, uint64_t address) const {
WriteMemory(&item, address, sizeof(T));
}
void KProcess::ReadMemory(void *destination, uint64_t offset, size_t size) const {
state.logger->Write(Logger::DEBUG, "ReadMemory for DE: 0x{:X}, OF: 0x{:X}, SZ: 0x{:X}", (uint64_t) destination, offset, size);
pread64(mem_fd, destination, size, offset);
}
void KProcess::WriteMemory(void *source, uint64_t offset, size_t size) const {
state.logger->Write(Logger::DEBUG, "WriteMemory for SRC: 0x{:X}, OF: 0x{:X}, SZ: 0x{:X}", (uint64_t) source, offset, size);
pwrite64(mem_fd, source, size, offset);
}
uint64_t MapPrivateFunc(uint64_t address, size_t size, uint64_t perms) {
int flags = MAP_PRIVATE | MAP_ANONYMOUS; // NOLINT(hicpp-signed-bitwise)
if (address) flags |= MAP_FIXED; // NOLINT(hicpp-signed-bitwise)
return reinterpret_cast<uint64_t>(mmap(reinterpret_cast<void *>(address), size, static_cast<int>(perms), flags, -1, 0));
}
uint64_t KProcess::MapPrivate(uint64_t address, size_t size, const memory::Permission perms) {
user_pt_regs fregs = {0};
fregs.regs[0] = address;
fregs.regs[1] = size;
fregs.regs[2] = static_cast<uint64_t>(perms.get());
state.nce->ExecuteFunction(reinterpret_cast<void *>(MapPrivateFunc), fregs, main_thread);
if (reinterpret_cast<void *>(fregs.regs[0]) == MAP_FAILED)
throw exception("An error occurred while mapping private region");
state.logger->Write(Logger::DEBUG, "MapPrivate for ADR: 0x{:X}, SZ: 0x{:X}", fregs.regs[0], size);
return fregs.regs[0];
}
uint64_t KProcess::MapPrivate(uint64_t address, size_t size, const memory::Permission perms, const memory::Region region) {
uint64_t addr = MapPrivate(address,size, perms);
memory_map.insert(std::pair<memory::Region, memory::RegionData>(region, {addr, size, perms}));
return addr;
}
uint64_t RemapPrivateFunc(uint64_t address, size_t old_size, size_t size) {
return (uint64_t) mremap(reinterpret_cast<void *>(address), old_size, size, 0);
}
void KProcess::RemapPrivate(uint64_t address, size_t old_size, size_t size) {
user_pt_regs fregs = {0};
fregs.regs[0] = address;
fregs.regs[1] = old_size;
fregs.regs[2] = size;
state.nce->ExecuteFunction(reinterpret_cast<void *>(RemapPrivateFunc), fregs, main_thread);
if (reinterpret_cast<void *>(fregs.regs[0]) == MAP_FAILED)
throw exception("An error occurred while remapping private region");
}
void KProcess::RemapPrivate(const memory::Region region, size_t size) {
memory::RegionData region_data = memory_map.at(region);
RemapPrivate(region_data.address, region_data.size, size);
region_data.size = size;
}
int UpdatePermissionPrivateFunc(uint64_t address, size_t size, uint64_t perms) {
return mprotect(reinterpret_cast<void *>(address), size, static_cast<int>(perms));
}
void KProcess::UpdatePermissionPrivate(uint64_t address, size_t size, const memory::Permission perms) {
user_pt_regs fregs = {0};
fregs.regs[0] = address;
fregs.regs[1] = size;
fregs.regs[2] = static_cast<uint64_t>(perms.get());
state.nce->ExecuteFunction(reinterpret_cast<void *>(UpdatePermissionPrivateFunc), fregs, main_thread);
if (static_cast<int>(fregs.regs[0]) == -1)
throw exception("An error occurred while updating private region's permissions");
}
void KProcess::UpdatePermissionPrivate(const memory::Region region, const memory::Permission perms) {
memory::RegionData region_data = memory_map.at(region);
if (region_data.perms != perms) {
UpdatePermissionPrivate(region_data.address, region_data.size, perms);
region_data.perms = perms;
}
}
uint64_t UnmapPrivateFunc(uint64_t address, size_t size) {
return static_cast<uint64_t>(munmap(reinterpret_cast<void *>(address), size));
}
void KProcess::UnmapPrivate(uint64_t address, size_t size) {
user_pt_regs fregs = {0};
fregs.regs[0] = address;
fregs.regs[1] = size;
state.nce->ExecuteFunction(reinterpret_cast<void *>(UnmapPrivateFunc), fregs, main_thread);
if (static_cast<int>(fregs.regs[0]) == -1)
throw exception("An error occurred while unmapping private region");
}
void KProcess::UnmapPrivate(const memory::Region region) {
memory::RegionData region_data = memory_map.at(region);
UnmapPrivate(region_data.address, region_data.size);
memory_map.erase(region);
}
handle_t KProcess::NewHandle(std::shared_ptr<KObject> obj) {
handle_table[handle_index] = std::move(obj);
state.logger->Write(Logger::DEBUG, "Creating handle index 0x{0:X}", handle_index);
return handle_index++; // Increment value after return
}
}

View File

@ -0,0 +1,191 @@
#pragma once
#include "KThread.h"
namespace lightSwitch::kernel::type {
/**
* The KProcess class is responsible for holding the state of a process
*/
class KProcess : public KObject {
private:
/**
* tls_page_t holds the status of a single TLS page (A page is 4096 bytes on ARMv8).
* Each TLS page has 8 slots, each 0x200 (512) bytes in size.
* The first slot of the first page is reserved for user-mode exception handling
* Read more about TLS here: https://switchbrew.org/wiki/Thread_Local_Storage
*/
struct tls_page_t {
uint64_t address; //!< The address of the page allocated for TLS
uint8_t index = 0; //!< The slots are assigned sequentially, this holds the index of the last TLS slot reserved
bool slot[constant::tls_slots]{0}; //!< An array of booleans denoting which TLS slots are reserved
/**
* @param address The address of the allocated page
*/
tls_page_t(uint64_t address);
/**
* Reserves a single 0x200 byte TLS slot
* @return The address of the reserved slot
*/
uint64_t ReserveSlot();
/**
* Returns the address of a particular slot
* @param slot_no The number of the slot to be returned
* @return The address of the specified slot
*/
uint64_t Get(uint8_t slot_no);
/**
* @return If the whole page is full or not
*/
bool Full();
};
/**
* @param init If this initializes the first page (As the first TLS slot is reserved)
* @return The address of a free TLS slot
*/
uint64_t GetTLSSlot(bool init);
int mem_fd; //!< The file descriptor to the memory of the process
const device_state& state; //!< The state of the device
public:
enum class process_state_t { Created, CreatedAttached, Started, Crashed, StartedAttached, Exiting, Exited, DebugSuspended } process_state; //!< The state of the process
handle_t handle; //!< The handle of the current process in it's parent process's handle table (Will be 0 if this is the main process)
handle_t handle_index = constant::base_handle_index; //!< This is used to keep track of what to map as an handle
pid_t main_thread; //!< The PID of the main thread
size_t main_thread_stack_sz; //!< The size of the main thread's stack (All other threads map stack themselves so we don't know the size per-se)
std::map<memory::Region, memory::RegionData> memory_map; //!< A mapping from every memory::Region to it's corresponding memory::RegionData which holds it's address and size
std::map<handle_t, std::shared_ptr<KObject>> handle_table; //!< A mapping from a handle_t to it's corresponding KObject which is the actual underlying object
std::map<pid_t, std::shared_ptr<KThread>> thread_map; //!< A mapping from a PID to it's corresponding KThread object
std::vector<std::shared_ptr<tls_page_t>> tls_pages; //!< A vector of all allocated TLS pages
/**
* Creates a KThread object for the main thread and opens the process's memory file
* @param pid The PID of the main thread
* @param entry_point The address to start execution at
* @param stack_base The base of the stack
* @param stack_size The size of the stack
* @param state The state of the device
* @param handle A handle to the process, this isn't used if the kernel creates the process
*/
KProcess(pid_t pid, uint64_t entry_point, uint64_t stack_base, uint64_t stack_size, const device_state& state, handle_t handle=0);
/**
* Close the file descriptor to the process's memory
*/
~KProcess();
/**
* Create a thread in this process
* @param entry_point The address of the initial function
* @param entry_arg An argument to the function
* @param stack_top The top of the stack
* @param priority The priority of the thread
* @return An instance of KThread class for the corresponding thread
*/
std::shared_ptr<KThread> CreateThread(uint64_t entry_point, uint64_t entry_arg, uint64_t stack_top, uint8_t priority);
/**
* Returns an object of type T from process memory
* @tparam T The type of the object to be read
* @param address The address of the object
* @return An object of type T with read data
*/
template <typename T>
T ReadMemory(uint64_t address) const;
/**
* Writes an object of type T to process memory
* @tparam T The type of the object to be written
* @param item The object to write
* @param address The address of the object
*/
template<typename T>
void WriteMemory(T &item, uint64_t address) const;
/**
* Read a piece of process memory
* @param destination The address to the location where the process memory is written
* @param offset The address to read from in process memory
* @param size The amount of memory to be read
*/
void ReadMemory(void *destination, uint64_t offset, size_t size) const;
/**
* Write a piece of process memory
* @param source The address of where the data to be written is present
* @param offset The address to write to in process memory
* @param size The amount of memory to be written
*/
void WriteMemory(void *source, uint64_t offset, size_t size) const;
/**
* Map a chunk of process local memory (private memory)
* @param address The address to map to (Can be 0 if address doesn't matter)
* @param size The size of the chunk of memory
* @param perms The permissions of the memory
* @return The address of the mapped chunk (Use when address is 0)
*/
uint64_t MapPrivate(uint64_t address, size_t size, const memory::Permission perms);
/**
* Map a chunk of process local memory (private memory)
* @param address The address to map to (Can be 0 if address doesn't matter)
* @param size The size of the chunk of memory
* @param perms The permissions of the memory
* @param region The specific region this memory is mapped for
* @return The address of the mapped chunk (Use when address is 0)
*/
uint64_t MapPrivate(uint64_t address, size_t size, const memory::Permission perms, const memory::Region region);
/**
* Remap a chunk of memory as to change the size occupied by it
* @param address The address of the mapped memory
* @param old_size The current size of the memory
* @param size The new size of the memory
*/
void RemapPrivate(uint64_t address, size_t old_size, size_t size);
/**
* Remap a chunk of memory as to change the size occupied by it
* @param region The region of memory that was mapped
* @param size The new size of the memory
*/
void RemapPrivate(const memory::Region region, size_t size);
/**
* Updates the permissions of a chunk of mapped memory
* @param address The address of the mapped memory
* @param size The size of the mapped memory
* @param perms The new permissions to be set for the memory
*/
void UpdatePermissionPrivate(uint64_t address, size_t size, const memory::Permission perms);
/**
* Updates the permissions of a chunk of mapped memory
* @param region The region of memory that was mapped
* @param perms The new permissions to be set for the memory
*/
void UpdatePermissionPrivate(const memory::Region region, const memory::Permission perms);
/**
* Unmap a particular chunk of mapped memory
* @param address The address of the mapped memory
* @param size The size of the mapped memory
*/
void UnmapPrivate(uint64_t address, size_t size);
/**
* Unmap a particular chunk of mapped memory
* @param region The region of mapped memory
*/
void UnmapPrivate(const memory::Region region);
/**
* Creates a new handle to a KObject and adds it to the process handle_table
* @param obj A shared pointer to the KObject to be added to the table
* @return The handle of the corresponding object
*/
handle_t NewHandle(std::shared_ptr<KObject> obj);
};
}

View File

@ -0,0 +1,26 @@
#include <sys/resource.h>
#include "KThread.h"
#include "KProcess.h"
#include "../../nce.h"
namespace lightSwitch::kernel::type {
KThread::KThread(handle_t handle, pid_t pid, uint64_t entry_point, uint64_t entry_arg, uint64_t stack_top, uint64_t tls, uint8_t priority, KProcess* parent, const device_state &state) : handle(handle), pid(pid), entry_point(entry_point), entry_arg(entry_arg), stack_top(stack_top), tls(tls), priority(priority), parent(parent), state(state), KObject(handle, KObjectType::KThread) {
UpdatePriority(priority);
}
KThread::~KThread() {
kill(pid, SIGKILL);
}
void KThread::Start() {
if(pid==parent->main_thread) parent->process_state = KProcess::process_state_t::Started;
state.nce->StartProcess(entry_point, entry_arg, stack_top, handle, pid);
}
void KThread::UpdatePriority(uint8_t priority) {
this->priority = priority;
auto li_priority = static_cast<int8_t>(constant::priority_an.first + ((static_cast<float>(constant::priority_an.second - constant::priority_an.first) / static_cast<float>(constant::priority_nin.second - constant::priority_nin.first)) * (static_cast<float>(priority) - constant::priority_nin.first))); // Remap range priority_nin (Nintendo Priority) to priority_an (Android Priority)
if(setpriority(PRIO_PROCESS, static_cast<id_t>(pid), li_priority) == -1)
throw exception(fmt::format("Couldn't set process priority to {} for PID: {}", li_priority, pid));
}
}

View File

@ -0,0 +1,53 @@
#pragma once
#include "KObject.h"
namespace lightSwitch::kernel::type {
/**
* KThread class is responsible for holding the state of a thread
*/
class KThread : public KObject {
private:
KProcess *parent; //!< The parent process of this thread
const device_state& state; //!< The state of the device
uint64_t entry_point; //!< The address to start execution at
uint64_t entry_arg; //!< An argument to pass to the process on entry
public:
handle_t handle; //!< The handle of the current thread in it's parent process's handle table
pid_t pid; //!< The PID of the current thread (As in kernel PID and not PGID [In short, Linux implements threads as processes that share a lot of stuff at the kernel level])
uint64_t stack_top; //!< The top of the stack (Where it starts growing downwards from)
uint64_t tls; //!< The address of TLS (Thread Local Storage) slot assigned to the current thread
uint8_t priority; //!< Hold the priority of a thread in Nintendo format
/**
* @param handle The handle of the current thread
* @param pid The PID of the current thread
* @param entry_point The address to start execution at
* @param entry_arg An argument to pass to the process on entry
* @param stack_top The top of the stack
* @param tls The address of the TLS slot assigned
* @param priority The priority of the thread in Nintendo format
* @param parent The parent process of this thread
* @param arg An optional argument to pass to the process
*/
KThread(handle_t handle, pid_t pid, uint64_t entry_point, uint64_t entry_arg, uint64_t stack_top, uint64_t tls, uint8_t priority, KProcess *parent, const device_state &state);
/**
* Kills the thread and deallocates the memory allocated for stack.
*/
~KThread();
/**
* Starts the current thread
*/
void Start();
/**
* Set the priority of the current thread to `priority` using setpriority [https://linux.die.net/man/3/setpriority].
* We rescale the priority from Nintendo scale to that of Android.
* @param priority The priority of the thread in Nintendo format
*/
void UpdatePriority(uint8_t priority);
};
}

View File

@ -1,24 +1,32 @@
#pragma once
#include <string>
#include "../common.h"
#include "../os.h"
namespace lightSwitch::loader {
class Loader {
protected:
std::string file_path;
std::ifstream file;
device_state state;
std::string file_path; //!< The path to the ROM file
std::ifstream file; //!< An input stream from the file
/**
* 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>
void ReadOffset(T *output, uint32_t offset, size_t size) {
file.seekg(offset, std::ios_base::beg);
file.read(reinterpret_cast<char *>(output), size);
}
virtual void Load(device_state state) = 0;
public:
Loader(std::string &file_path_, device_state &state_) : file_path(file_path_), state(state_), file(file_path, std::ios::binary | std::ios::beg) {}
/**
* @param file_path_ The path to the ROM file
*/
Loader(std::string &file_path) : file_path(file_path), file(file_path, std::ios::binary | std::ios::beg) {}
};
}

View File

@ -2,42 +2,45 @@
#include "nro.h"
namespace lightSwitch::loader {
void NroLoader::Load(device_state state) {
NroLoader::NroLoader(std::string file_path, const device_state &state) : Loader(file_path) {
NroHeader header{};
ReadOffset((uint32_t *) &header, 0x0, sizeof(NroHeader));
if (header.magic != constant::nro_magic)
throw exception(fmt::format("Invalid NRO magic! 0x{0:X}", header.magic));
auto text = new uint32_t[header.text.size]();
auto ro = new uint32_t[header.ro.size]();
auto data = new uint32_t[header.data.size]();
state.nce->MapShared(constant::base_addr, header.text.size, {true, true, true}, memory::Region::text); // RWX
state.logger->Write(Logger::DEBUG, "Successfully mapped region .text @ 0x{0:X}, Size = 0x{1:X}", constant::base_addr, header.text.size);
ReadOffset(text, header.text.offset, header.text.size);
ReadOffset(ro, header.ro.offset, header.ro.size);
ReadOffset(data, header.data.offset, header.data.size);
state.nce->MapShared(constant::base_addr + header.text.size, header.ro.size, {true, true, false}, memory::Region::rodata); // RW- but should be R--
state.logger->Write(Logger::DEBUG, "Successfully mapped region .ro @ 0x{0:X}, Size = 0x{1:X}", constant::base_addr + header.text.size, header.ro.size);
state.memory->Map(constant::base_addr, header.text.size, hw::Memory::text);
state.logger->write(Logger::DEBUG, "Successfully mapped region .text @ 0x{0:X}, Size = 0x{1:X}",
constant::base_addr, header.text.size);
state.nce->MapShared(constant::base_addr + header.text.size + header.ro.size, header.data.size, {true, true, false}, memory::Region::data); // RW-
state.logger->Write(Logger::DEBUG, "Successfully mapped region .data @ 0x{0:X}, Size = 0x{1:X}", constant::base_addr + header.text.size + header.ro.size, header.data.size);
state.memory->Map(constant::base_addr + header.text.size, header.ro.size, hw::Memory::rodata);
state.logger->write(Logger::DEBUG, "Successfully mapped region .ro @ 0x{0:X}, Size = 0x{1:X}",
constant::base_addr + header.text.size, header.ro.size);
state.nce->MapShared(constant::base_addr + header.text.size + header.ro.size + header.data.size, header.bssSize, {true, true, false}, memory::Region::bss); // RW-
state.logger->Write(Logger::DEBUG, "Successfully mapped region .bss @ 0x{0:X}, Size = 0x{1:X}", constant::base_addr + header.text.size + header.ro.size + header.data.size, header.bssSize);
state.memory->Map(constant::base_addr + header.text.size + header.ro.size, header.data.size, hw::Memory::data);
state.logger->write(Logger::DEBUG, "Successfully mapped region .data @ 0x{0:X}, Size = 0x{1:X}",
constant::base_addr + header.text.size + header.ro.size, header.data.size);
ReadOffset(reinterpret_cast<uint8_t *>(constant::base_addr), header.text.offset, header.text.size);
ReadOffset(reinterpret_cast<uint8_t *>(constant::base_addr + header.text.size), header.ro.offset, header.ro.size);
ReadOffset(reinterpret_cast<uint8_t *>(constant::base_addr + header.text.size + header.ro.size), header.data.offset, header.data.size);
state.memory->Map(constant::base_addr + header.text.size + header.ro.size + header.data.size, header.bssSize, hw::Memory::bss);
state.logger->write(Logger::DEBUG, "Successfully mapped region .bss @ 0x{0:X}, Size = 0x{1:X}",
constant::base_addr + header.text.size + header.ro.size + header.data.size, header.bssSize);
// Make .ro read-only after writing to it
state.nce->UpdatePermissionShared(memory::Region::rodata, {true, false, false});
state.memory->Write(text, constant::base_addr, header.text.size);
state.memory->Write(ro, constant::base_addr + header.text.size, header.ro.size);
state.memory->Write(data, constant::base_addr + header.text.size + header.ro.size, header.data.size);
// Replace SVC & MRS with BRK
auto address = (uint32_t *) constant::base_addr + header.text.offset;
size_t text_size = header.text.size / sizeof(uint32_t);
for (size_t iter = 0; iter < text_size; iter++) {
auto instr_svc = reinterpret_cast<instr::svc *>(address + iter);
auto instr_mrs = reinterpret_cast<instr::mrs *>(address + iter);
delete[] text;
delete[] ro;
delete[] data;
if (instr_svc->verify()) {
instr::brk brk(static_cast<uint16_t>(instr_svc->value));
address[iter] = *reinterpret_cast<uint32_t *>(&brk);
} else if (instr_mrs->verify() && instr_mrs->src_reg == constant::tpidrro_el0) {
instr::brk brk(static_cast<uint16_t>(constant::svc_last + 1 + instr_mrs->dst_reg));
address[iter] = *reinterpret_cast<uint32_t *>(&brk);
}
}
}
}

View File

@ -9,7 +9,7 @@ namespace lightSwitch::loader {
struct NroSegmentHeader {
uint32_t offset;
uint32_t size;
};
}; //!< The structure of a single Segment descriptor in the NRO's header
struct NroHeader {
uint32_t : 32;
@ -33,11 +33,13 @@ namespace lightSwitch::loader {
NroSegmentHeader api_info;
NroSegmentHeader dynstr;
NroSegmentHeader dynsym;
};
void Load(device_state state);
}; //!< A bit-field struct to read the header of an NRO directly
public:
NroLoader(std::string file_path, device_state state) : Loader(file_path, state) { Load(state); };
/**
* @param file_path The path to the ROM file
* @param state The state of the device
*/
NroLoader(std::string file_path, const device_state &state);
};
}

View File

@ -0,0 +1,294 @@
#include <sched.h>
#include <linux/uio.h>
#include <linux/elf.h>
#include <fcntl.h>
#include <unistd.h>
#include <android/sharedmem.h>
#include "os.h"
#include "nce.h"
extern bool halt;
namespace lightSwitch {
void NCE::ReadRegisters(user_pt_regs &registers, pid_t pid) const {
iovec iov = {&registers, sizeof(registers)};
long status = ptrace(PTRACE_GETREGSET, pid ? pid : curr_pid, NT_PRSTATUS, &iov);
if (status == -1) throw exception(fmt::format("Cannot read registers, PID: {}, Error: {}", pid, strerror(errno)));
}
void NCE::WriteRegisters(user_pt_regs &registers, pid_t pid) const {
iovec iov = {&registers, sizeof(registers)};
long status = ptrace(PTRACE_SETREGSET, pid ? pid : curr_pid, NT_PRSTATUS, &iov);
if (status == -1) throw exception(fmt::format("Cannot write registers, PID: {}, Error: {}", pid, strerror(errno)));
}
instr::brk NCE::ReadBrk(uint64_t address, pid_t pid) const {
long status = ptrace(PTRACE_PEEKDATA, pid ? pid : curr_pid, address, NULL);
if (status == -1) throw exception(fmt::format("Cannot read instruction from memory, Address: {}, PID: {}, Error: {}", address, pid, strerror(errno)));
return *(reinterpret_cast<instr::brk *>(&status));
}
NCE::~NCE() {
for (auto&region : region_memory_map) {
munmap(reinterpret_cast<void *>(region.second.address), region.second.size);
};
}
void NCE::Execute(const device_state &state) {
this->state = const_cast<device_state *>(&state);
int status;
while (!halt && !state.os->process_map.empty() && ((curr_pid = wait(&status)) != -1)) {
if (WIFSTOPPED(status) && (WSTOPSIG(status) == SIGTRAP || WSTOPSIG(status) == SIGSTOP)) { // NOLINT(hicpp-signed-bitwise)
auto& curr_regs = register_map[curr_pid];
ReadRegisters(curr_regs);
auto instr = ReadBrk(curr_regs.pc);
if (instr.verify()) {
// We store the instruction value as the immediate value in BRK. 0x0 to 0x7F are SVC, 0x80 to 0x9E is MRS for TPIDRRO_EL0.
if (instr.value <= constant::svc_last) {
state.os->SvcHandler(static_cast<uint16_t>(instr.value), curr_pid);
} else if (instr.value > constant::svc_last && instr.value <= constant::svc_last + constant::num_regs) {
// Catch MRS that reads the value of TPIDRRO_EL0 (TLS)
SetRegister(regs::xreg(instr.value - (constant::svc_last + 1)), state.os->process_map.at(curr_pid)->thread_map.at(curr_pid)->tls);
state.logger->Write(Logger::DEBUG, "\"MRS X{}, TPIDRRO_EL0\" has been called", instr.value - (constant::svc_last + 1));
} else if (instr.value == constant::brk_rdy)
continue;
else
throw exception(fmt::format("Received unhandled BRK: 0x{:X}", static_cast<uint64_t>(instr.value)));
}
curr_regs.pc += 4; // Increment program counter by a single instruction (32 bits)
WriteRegisters(curr_regs);
} else {
auto& curr_regs = register_map[curr_pid];
ReadRegisters(curr_regs);
state.logger->Write(Logger::DEBUG, "Thread threw unknown signal, PID: {}, Status: 0x{:X}, INSTR: 0x{:X}", curr_pid, status, *(uint32_t*)curr_regs.pc);
state.os->KillThread(curr_pid);
}
ResumeProcess();
}
}
void brk_lr() {
asm("BRK #0xFF"); // BRK #constant::brk_rdy
}
void NCE::ExecuteFunction(void *func, user_pt_regs &func_regs, pid_t pid) {
pid = pid ? pid : curr_pid;
bool was_running = PauseProcess(pid);
user_pt_regs backup_regs{};
ReadRegisters(backup_regs, pid);
func_regs.pc = reinterpret_cast<uint64_t>(func);
func_regs.sp = backup_regs.sp;
func_regs.regs[regs::x30] = reinterpret_cast<uint64_t>(brk_lr); // Set LR to 'brk_lr' so the application will hit a breakpoint after the function returns [LR is where the program goes after it returns from a function]
WriteRegisters(func_regs, pid);
ResumeProcess(pid);
func_regs = WaitRdy(pid);
WriteRegisters(backup_regs, pid);
if (was_running)
ResumeProcess(pid);
}
user_pt_regs NCE::WaitRdy(pid_t pid) {
int status;
user_pt_regs regs{};
waitpid(pid, &status, 0);
if (WIFSTOPPED(status) && (WSTOPSIG(status) == SIGTRAP)) { // NOLINT(hicpp-signed-bitwise)
ReadRegisters(regs, pid);
auto instr = ReadBrk(regs.pc, pid);
if (instr.verify() && instr.value == constant::brk_rdy) {
regs.pc += 4; // Increment program counter by a single instruction (32 bits)
WriteRegisters(regs, pid);
return regs;
} else
throw exception(fmt::format("An unknown BRK was hit during WaitRdy, PID: {}, BRK value: {}", pid, static_cast<uint64_t>(instr.value)));
} else
throw exception(fmt::format("An unknown signal was caused during WaitRdy, PID: {}, Status: 0x{:X}, Signal: {}", pid, status, strsignal(WSTOPSIG(status)))); // NOLINT(hicpp-signed-bitwise)
}
bool NCE::PauseProcess(pid_t pid) const {
pid = pid ? pid : curr_pid;
int status = 0;
waitpid(pid, &status, WNOHANG);
bool was_stopped = WIFSTOPPED(status); // NOLINT(hicpp-signed-bitwise)
if (was_stopped) {
if ((kill(pid, SIGSTOP) != -1) && (waitpid(pid, nullptr, 0) != -1)) return true;
else throw exception(fmt::format("Cannot pause process: {}, Error: {}", pid, strerror(errno)));
} else return false;
}
void NCE::ResumeProcess(pid_t pid) const {
long status = ptrace(PTRACE_CONT, pid ? pid : curr_pid, NULL, NULL);
if (status == -1) throw exception(fmt::format("Cannot resume process: {}, Error: {}", pid, strerror(errno)));
}
void NCE::StartProcess(uint64_t entry_point, uint64_t entry_arg, uint64_t stack_top, uint32_t handle, pid_t pid) const {
user_pt_regs regs{0};
regs.sp = stack_top;
regs.pc = entry_point;
regs.regs[0] = entry_arg;
regs.regs[1] = handle;
WriteRegisters(regs, pid);
ResumeProcess(pid);
}
uint64_t NCE::GetRegister(regs::xreg reg_id, pid_t pid) {
return register_map.at(pid ? pid : curr_pid).regs[reg_id];
}
void NCE::SetRegister(regs::xreg reg_id, uint64_t value, pid_t pid) {
register_map.at(pid ? pid : curr_pid).regs[reg_id] = value;
}
uint64_t NCE::GetRegister(regs::wreg reg_id, pid_t pid) {
return (reinterpret_cast<uint32_t *>(&register_map.at(pid ? pid : curr_pid).regs))[reg_id * 2];
}
void NCE::SetRegister(regs::wreg reg_id, uint32_t value, pid_t pid) {
(reinterpret_cast<uint32_t *>(&register_map.at(pid ? pid : curr_pid).regs))[reg_id * 2] = value;
}
uint64_t NCE::GetRegister(regs::sreg reg_id, pid_t pid) {
pid = pid ? pid : curr_pid;
switch (reg_id) {
case regs::pc:
return register_map.at(pid).pc;
case regs::sp:
return register_map.at(pid).sp;
case regs::pstate:
return register_map.at(pid).pstate;
default:
return 0;
}
}
void NCE::SetRegister(regs::sreg reg_id, uint32_t value, pid_t pid) {
pid = pid ? pid : curr_pid;
switch (reg_id) {
case regs::pc:
register_map.at(pid).pc = value;
case regs::sp:
register_map.at(pid).sp = value;
case regs::pstate:
register_map.at(pid).pstate = value;
}
}
uint64_t MapSharedFunc(uint64_t address, size_t size, uint64_t perms, uint64_t fd) {
return reinterpret_cast<uint64_t>(mmap(reinterpret_cast<void *>(address), size, static_cast<int>(perms), MAP_PRIVATE | MAP_ANONYMOUS | ((address) ? MAP_FIXED : 0), static_cast<int>(fd), 0)); // NOLINT(hicpp-signed-bitwise)
}
uint64_t NCE::MapShared(uint64_t address, size_t size, const memory::Permission perms) {
int fd = -1;
if(state && !state->os->process_map.empty()) {
fd = ASharedMemory_create("", size);
for(auto& process : state->os->process_map) {
user_pt_regs fregs = {0};
fregs.regs[0] = address;
fregs.regs[1] = size;
fregs.regs[2] = static_cast<uint64_t>(perms.get());
fregs.regs[3] = static_cast<uint64_t>(fd);
ExecuteFunction(reinterpret_cast<void *>(MapSharedFunc), fregs, process.second->main_thread);
if (reinterpret_cast<void *>(fregs.regs[0]) == MAP_FAILED)
throw exception("An error occurred while mapping shared region in child process");
address = fregs.regs[0]; // In case address was 0, this ensures all processes allocate the same address
}
}
void *ptr = mmap((void *) address, size, perms.get(), MAP_PRIVATE | MAP_ANONYMOUS | ((address) ? MAP_FIXED : 0), fd, 0); // NOLINT(hicpp-signed-bitwise)
if (ptr == MAP_FAILED)
throw exception(fmt::format("An occurred while mapping shared region: {}", strerror(errno)));
addr_memory_map[address] = {address, size, perms, fd};
return reinterpret_cast<uint64_t>(ptr);
}
uint64_t NCE::MapShared(uint64_t address, size_t size, const memory::Permission perms, const memory::Region region) {
uint64_t addr = MapShared(address,size, perms); // TODO: Change MapShared return type to RegionData
region_memory_map[region] = {addr, size, perms};
return addr;
}
uint64_t RemapSharedFunc(uint64_t address, size_t old_size, size_t size) {
return reinterpret_cast<uint64_t>(mremap(reinterpret_cast<void *>(address), old_size, size, 0));
}
void NCE::RemapShared(uint64_t address, size_t old_size, size_t size) {
if(state && !state->os->process_map.empty()) {
for(auto& process : state->os->process_map) {
user_pt_regs fregs = {0};
fregs.regs[0] = address;
fregs.regs[1] = old_size;
fregs.regs[2] = size;
ExecuteFunction(reinterpret_cast<void *>(RemapSharedFunc), fregs, process.second->main_thread);
if (reinterpret_cast<void *>(fregs.regs[0]) == MAP_FAILED)
throw exception("An error occurred while remapping shared region in child process");
}
}
void *ptr = mremap(reinterpret_cast<void *>(address), old_size, size, 0);
if (ptr == MAP_FAILED)
throw exception(fmt::format("An occurred while remapping shared region: {}", strerror(errno)));
addr_memory_map.at(address).size = size;
}
void NCE::RemapShared(const memory::Region region, size_t size) {
memory::RegionData& region_data = region_memory_map.at(region);
RemapShared(region_data.address, region_data.size, size);
region_data.size = size;
}
uint64_t UpdatePermissionSharedFunc(uint64_t address, size_t size, uint64_t perms) {
return static_cast<uint64_t>(mprotect(reinterpret_cast<void *>(address), size, static_cast<int>(perms)));
}
void NCE::UpdatePermissionShared(uint64_t address, size_t size, const memory::Permission perms) {
if(state && !state->os->process_map.empty()) {
for(auto& process : state->os->process_map) {
user_pt_regs fregs = {0};
fregs.regs[0] = address;
fregs.regs[1] = size;
fregs.regs[2] = static_cast<uint64_t>(perms.get());
ExecuteFunction(reinterpret_cast<void *>(UpdatePermissionSharedFunc), fregs, process.second->main_thread);
if (static_cast<int>(fregs.regs[0]) == -1)
throw exception("An error occurred while updating shared region's permissions in child process");
}
}
if (mprotect(reinterpret_cast<void *>(address), size, perms.get()) == -1)
throw exception(fmt::format("An occurred while updating shared region's permissions: {}", strerror(errno)));
addr_memory_map.at(address).perms = perms;
}
void NCE::UpdatePermissionShared(const memory::Region region, const memory::Permission perms) {
memory::RegionData& region_data = region_memory_map.at(region);
if (region_data.perms != perms) {
UpdatePermissionShared(region_data.address, region_data.size, perms);
region_data.perms = perms;
}
}
uint64_t UnmapSharedFunc(uint64_t address, size_t size) {
return static_cast<uint64_t>(munmap(reinterpret_cast<void *>(address), size));
}
void NCE::UnmapShared(uint64_t address, size_t size) {
if(state && !state->os->process_map.empty()) {
for(auto& process : state->os->process_map) {
user_pt_regs fregs = {0};
fregs.regs[0] = address;
fregs.regs[1] = size;
ExecuteFunction(reinterpret_cast<void *>(UnmapSharedFunc), fregs, process.second->main_thread);
if (static_cast<int>(fregs.regs[0]) == -1)
throw exception("An error occurred while unmapping shared region in child process");
}
}
int err = munmap(reinterpret_cast<void *>(address), size);
if (err == -1)
throw exception(fmt::format("An occurred while unmapping shared region: {}", strerror(errno)));
if (addr_memory_map.at(address).fd!=-1) {
if (close(addr_memory_map.at(address).fd)==-1)
throw exception(fmt::format("An occurred while closing shared file descriptor: {}", strerror(errno)));
}
}
void NCE::UnmapShared(const memory::Region region) {
memory::RegionData& region_data = region_memory_map.at(region);
UnmapShared(region_data.address, region_data.size);
region_memory_map.erase(region);
}
}

View File

@ -0,0 +1,202 @@
#pragma once
#include <syslog.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <vector>
#include <unordered_map>
#include "common.h"
namespace lightSwitch {
class NCE {
private:
pid_t curr_pid = 0; //!< The PID of the process currently being handled, this is so the PID won't have to be passed into functions like ReadRegister redundantly
std::unordered_map<pid_t, user_pt_regs> register_map; //!< A map of all PIDs and their corresponding registers (Whenever they were last updated)
device_state *state;
/**
* Reads process registers into the `registers` variable
* @param registers A set of registers to fill with values from the process
* @param pid The PID of the process
*/
void ReadRegisters(user_pt_regs &registers, pid_t pid=0) const;
/**
* Writes process registers from the `registers` variable
* @param registers The registers to be written by the process
* @param pid The PID of the process
*/
void WriteRegisters(user_pt_regs &registers, pid_t pid=0) const;
/**
* @param address The address of the BRK instruction
* @param pid The PID of the process
* @return An instance of BRK with the corresponding values
*/
instr::brk ReadBrk(uint64_t address, pid_t pid=0) const;
public:
std::map<memory::Region, memory::RegionData> region_memory_map; //!< A mapping from every memory::Region to it's corresponding memory::RegionData which holds it's address, size and fd
std::map<uint64_t, memory::RegionData> addr_memory_map; //!< A mapping from every address to it's corresponding memory::RegionData
/**
* Iterates over shared_memory_vec unmapping all memory
*/
~NCE();
/**
* Start managing child processes
* @param state The state of the device
*/
void Execute(const device_state& state);
/**
* Execute any arbitrary function on a particular child process
* @param func The entry point of the function
* @param func_regs A set of registers to run the function with (PC, SP and X29 are replaced)
* @param pid The PID of the process
*/
void ExecuteFunction(void *func, user_pt_regs &func_regs, pid_t pid);
/**
* Waits till a process calls "BRK #constant::brk_rdy"
* @param pid The PID of the process
* @return The registers after the BRK
*/
user_pt_regs WaitRdy(pid_t pid);
/**
* Pauses a particular process if was not already paused
* @param pid The PID of the process
* @return If the application was paused beforehand
*/
bool PauseProcess(pid_t pid = 0) const;
/**
* Resumes a particular process, does nothing if it was already running
* @param pid The PID of the process
*/
void ResumeProcess(pid_t pid=0) const;
/**
* Starts a particular process, sets the registers to their expected values and jumps to address
* @param address The address to jump to
* @param entry_arg The argument to pass in for the entry function
* @param stack_top The top of the stack
* @param handle The handle of the main thread (Set to value of 1st register)
* @param pid The PID of the process
*/
void StartProcess(uint64_t address, uint64_t entry_arg, uint64_t stack_top, uint32_t handle, pid_t pid) const;
/**
* Get the value of a Xn register
* @param reg_id The ID of the register
* @param pid The PID of the process
* @return The value of the register
*/
uint64_t GetRegister(regs::xreg reg_id, pid_t pid=0);
/**
* Set the value of a Xn register
* @param reg_id The ID of the register
* @param value The value to set
* @param pid The PID of the process
*/
void SetRegister(regs::xreg reg_id, uint64_t value, pid_t pid=0);
/**
* Get the value of a Wn register
* @param reg_id The ID of the register
* @param pid The PID of the process
* @return The value in the register
*/
uint64_t GetRegister(regs::wreg reg_id, pid_t pid=0);
/**
* Set the value of a Wn register
* @param reg_id The ID of the register
* @param value The value to set
* @param pid The PID of the process
*/
void SetRegister(regs::wreg reg_id, uint32_t value, pid_t pid=0);
/**
* Get the value of a special register
* @param reg_id The ID of the register
* @param pid The PID of the process
* @return The value in the register
*/
uint64_t GetRegister(regs::sreg reg_id, pid_t pid=0);
/**
* Set the value of a special register
* @param reg_id The ID of the register
* @param value The value to set
* @param pid The PID of the process
*/
void SetRegister(regs::sreg reg_id, uint32_t value, pid_t pid=0);
// TODO: Shared Memory mappings don't update after child process has been created
/**
* Map a chunk of shared memory
* @param address The address to map to (Can be 0 if address doesn't matter)
* @param size The size of the chunk of memory
* @param perms The permissions of the memory
* @return The address of the mapped chunk (Use when address is 0)
*/
uint64_t MapShared(uint64_t address, size_t size, const memory::Permission perms);
/**
* Map a chunk of shared memory
* @param address The address to map to (Can be 0 if address doesn't matter)
* @param size The size of the chunk of memory
* @param perms The permissions of the memory
* @param region The specific region this memory is mapped for
* @return The address of the mapped chunk (Use when address is 0)
*/
uint64_t MapShared(uint64_t address, size_t size, const memory::Permission perms, const memory::Region region);
/**
* Remap a chunk of memory as to change the size occupied by it
* @param address The address of the mapped memory
* @param old_size The current size of the memory
* @param size The new size of the memory
*/
void RemapShared(uint64_t address, size_t old_size, size_t size);
/**
* Remap a chunk of memory as to change the size occupied by it
* @param region The region of memory that was mapped
* @param size The new size of the memory
*/
void RemapShared(memory::Region region, size_t size);
/**
* Updates the permissions of a chunk of mapped memory
* @param address The address of the mapped memory
* @param size The size of the mapped memory
* @param perms The new permissions to be set for the memory
*/
void UpdatePermissionShared(uint64_t address, size_t size, const memory::Permission perms);
/**
* Updates the permissions of a chunk of mapped memory
* @param region The region of memory that was mapped
* @param perms The new permissions to be set for the memory
*/
void UpdatePermissionShared(memory::Region region, memory::Permission perms);
/**
* Unmap a particular chunk of mapped memory
* @param address The address of the mapped memory
* @param size The size of the mapped memory
*/
void UnmapShared(uint64_t address, size_t size);
/**
* Unmap a particular chunk of mapped memory
* @param region The region of mapped memory
*/
void UnmapShared(const memory::Region region);
};
}

View File

@ -0,0 +1,91 @@
#include "os.h"
#include "kernel/svc.h"
#include "loader/nro.h"
#include "nce.h"
extern bool halt;
namespace lightSwitch::kernel {
OS::OS(std::shared_ptr<Logger> &logger, std::shared_ptr<Settings> &settings) : state(this, this_process, this_thread, std::make_shared<NCE>(), settings, logger) {}
void OS::Execute(std::string rom_file) {
std::string rom_ext = rom_file.substr(rom_file.find_last_of('.') + 1);
std::transform(rom_ext.begin(), rom_ext.end(), rom_ext.begin(), [](unsigned char c) { return std::tolower(c); });
if (rom_ext == "nro") loader::NroLoader loader(rom_file, state);
else throw exception("Unsupported ROM extension.");
auto main_process = CreateProcess(state.nce->region_memory_map.at(memory::Region::text).address, constant::def_stack_size);
main_process->thread_map[main_process->main_thread]->Start(); // The kernel itself is responsible for starting the main thread
state.nce->Execute(state);
}
/**
* Function executed by all child processes after cloning
*/
int ExecuteChild(void *) {
ptrace(PTRACE_TRACEME);
asm volatile("brk #0xFF"); // BRK #constant::brk_rdy (So we know when the thread/process is ready)
return 0;
}
std::shared_ptr<type::KProcess> OS::CreateProcess(uint64_t address, size_t stack_size) {
auto *stack = static_cast<uint8_t *>(mmap(nullptr, stack_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_NORESERVE | MAP_ANONYMOUS | MAP_STACK, -1, 0)); // NOLINT(hicpp-signed-bitwise)
if (stack == MAP_FAILED)
throw exception("Failed to allocate stack memory");
if (mprotect(stack, PAGE_SIZE, PROT_NONE)) {
munmap(stack, stack_size);
throw exception("Failed to create guard pages");
}
pid_t pid = clone(&ExecuteChild, stack + stack_size, CLONE_FS | SIGCHLD, nullptr); // NOLINT(hicpp-signed-bitwise)
if(pid==-1) throw exception(fmt::format("Call to clone() has failed: {}", strerror(errno)));
std::shared_ptr<type::KProcess> process = std::make_shared<kernel::type::KProcess>(pid, address, reinterpret_cast<uint64_t>(stack), stack_size, state);
process_map[pid] = process;
state.logger->Write(Logger::DEBUG, "Successfully created process with PID: {}", pid);
return process;
}
void OS::KillThread(pid_t pid) {
auto process = process_map.at(pid);
if(process->main_thread==pid) {
state.logger->Write(Logger::DEBUG, "Exiting process with PID: {}", pid);
// Erasing all shared_ptr instances to the process will call the destructor
// However, in the case these are not all instances of it we wouldn't want to call the destructor
for (auto& [key, value]: process->thread_map) {
process_map.erase(key);
};
} else {
state.logger->Write(Logger::DEBUG, "Exiting thread with TID: {}", pid);
process->handle_table.erase(process->thread_map[pid]->handle);
process->thread_map.erase(pid);
process_map.erase(pid);
}
}
void OS::SvcHandler(uint16_t svc, pid_t pid) {
this_process = process_map.at(pid);
this_thread = this_process->thread_map.at(pid);
if (svc::svcTable[svc]) {
state.logger->Write(Logger::DEBUG, "SVC called 0x{:X}", svc);
(*svc::svcTable[svc])(state);
} else {
throw exception(fmt::format("Unimplemented SVC 0x{:X}", svc));
}
}
ipc::IpcResponse OS::IpcHandler(ipc::IpcRequest &request) {
ipc::IpcResponse response;
switch (request.req_info->type) {
case 4: // Request
response.SetError(0xDEADBEE5);
response.MoveHandle(0xBAADBEEF);
response.MoveHandle(0xFACCF00D);
response.Generate(state);
break;
default:
throw exception(fmt::format("Unimplemented IPC message type {0}", uint16_t(request.req_info->type)));
}
return response;
}
}

View File

@ -0,0 +1,61 @@
#pragma once
#include <sys/mman.h>
#include "common.h"
#include "kernel/ipc.h"
#include "kernel/types/KProcess.h"
#include "kernel/types/KThread.h"
#include "nce.h"
namespace lightSwitch::kernel {
/**
* The OS class manages the interaction between OS components and the underlying hardware in NCE
*/
class OS {
private:
device_state state; //!< The state of the device
public:
std::unordered_map<pid_t, std::shared_ptr<type::KProcess>> process_map; //!< The state of the device
std::shared_ptr<type::KProcess> this_process; //!< The corresponding KProcess object of the process that's called an SVC
std::shared_ptr<type::KThread> this_thread; //!< The corresponding KThread object of the thread that's called an SVC
/**
* @param logger An instance of the Logger class
* @param settings An instance of the Settings class
*/
OS(std::shared_ptr<Logger> &logger, std::shared_ptr<Settings> &settings);
/**
* Execute a particular ROM file. This launches a the main processes and calls the NCE class to handle execution.
* @param rom_file The path to the ROM file to execute
*/
void Execute(std::string rom_file);
/**
* Creates a new process
* @param address The address of the initial function
* @param stack_size The size of the main stack
* @return An instance of the KProcess of the created process
*/
std::shared_ptr<type::KProcess> CreateProcess(uint64_t address, size_t stack_size);
/**
* Kill a particular thread
* @param pid The PID of the thread
*/
void KillThread(pid_t pid);
/**
* @param svc The ID of the SVC to be called
* @param pid The PID of the process/thread calling the SVC
*/
void SvcHandler(uint16_t svc, pid_t pid);
/**
* @param req The IPC request to be handled
* @return The corresponding response returned by a service
*/
ipc::IpcResponse IpcHandler(ipc::IpcRequest &req);
};
}

View File

@ -1,31 +0,0 @@
#include <syslog.h>
#include <cstdlib>
#include "ipc.h"
namespace lightSwitch::os::ipc {
IpcRequest::IpcRequest(uint8_t *tls_ptr, device_state &state) : req_info((command_struct *) tls_ptr) {
state.logger->write(Logger::DEBUG, "Enable handle descriptor: {0}", (bool) req_info->handle_desc);
if (req_info->handle_desc)
throw exception("IPC - Handle descriptor");
// Align to 16 bytes
data_pos = 8;
data_pos = ((data_pos - 1) & ~(15U)) + 16; // ceil data_pos with multiples 16
data_ptr = &tls_ptr[data_pos + sizeof(command_struct)];
state.logger->write(Logger::DEBUG, "Type: 0x{:X}", (uint8_t) req_info->type);
state.logger->write(Logger::DEBUG, "X descriptors: {}", (uint8_t) req_info->x_no);
state.logger->write(Logger::DEBUG, "A descriptors: {}", (uint8_t) req_info->a_no);
state.logger->write(Logger::DEBUG, "B descriptors: {}", (uint8_t) req_info->b_no);
state.logger->write(Logger::DEBUG, "W descriptors: {}", (uint8_t) req_info->w_no);
state.logger->write(Logger::DEBUG, "Raw data offset: 0x{:X}", data_pos);
state.logger->write(Logger::DEBUG, "Raw data size: {}", (uint16_t) req_info->data_sz);
state.logger->write(Logger::DEBUG, "Payload Command ID: {}", *((uint32_t *) &tls_ptr[data_pos + 8]));
}
template<typename T>
T IpcRequest::GetValue() {
data_pos += sizeof(T);
return *reinterpret_cast<T *>(&data_ptr[data_pos - sizeof(T)]);
}
}

View File

@ -1,29 +0,0 @@
#pragma once
#include "switch/common.h"
namespace lightSwitch::os::ipc {
class IpcRequest {
private:
uint8_t *data_ptr;
uint32_t data_pos;
public:
struct command_struct {
// https://switchbrew.org/wiki/IPC_Marshalling#IPC_Command_Structure
uint16_t type : 16;
uint8_t x_no : 4;
uint8_t a_no : 4;
uint8_t b_no : 4;
uint8_t w_no : 4;
uint16_t data_sz : 10;
uint8_t c_flags : 4;
uint32_t : 17;
bool handle_desc : 1;
} *req_info;
IpcRequest(uint8_t *tlsPtr, device_state &state);
template<typename T>
T GetValue();
};
}

View File

@ -1,22 +0,0 @@
#include "os.h"
namespace lightSwitch::os {
OS::OS(device_state state_) : state(std::move(state_)) {}
void OS::SvcHandler(uint16_t svc, void *vstate) {
device_state state = *((device_state *) vstate);
if (svc::svcTable[svc])
(*svc::svcTable[svc])(state);
else {
state.logger->write(Logger::ERROR, "Unimplemented SVC 0x{0:X}", svc);
state.cpu->StopExecution();
}
}
uint32_t OS::NewHandle(KObjectPtr obj) {
handles.insert({handle_index, obj});
state.logger->write(Logger::DEBUG, "Creating new handle 0x{0:x}", handle_index);
return handle_index++;
}
}

View File

@ -1,32 +0,0 @@
#pragma once
#include <unordered_map>
#include "../common.h"
#include "ipc.h"
#include "svc.h"
namespace lightSwitch::os {
class KObject {
private:
uint32_t handle;
public:
KObject(uint32_t handle) : handle(handle) {}
uint32_t Handle() { return handle; }
};
typedef std::shared_ptr<KObject> KObjectPtr;
class OS {
private:
device_state state;
uint32_t handle_index = constant::base_handle_index;
std::unordered_map<uint32_t, KObjectPtr> handles;
public:
OS(device_state state_);
void SvcHandler(uint16_t svc, void *vstate);
uint32_t NewHandle(KObjectPtr obj);
};
}

View File

@ -1,66 +0,0 @@
#include <cstdint>
#include <string>
#include <syslog.h>
#include <utility>
#include "svc.h"
namespace lightSwitch::os::svc {
void ConnectToNamedPort(device_state &state) {
char port[constant::port_size]{0};
state.memory->Read(port, state.cpu->GetRegister(xreg::x1), constant::port_size);
if (std::strcmp(port, "sm:") == 0)
state.cpu->SetRegister(wreg::w1, constant::sm_handle);
else {
state.logger->write(Logger::ERROR, "svcConnectToNamedPort tried connecting to invalid port \"{0}\"", port);
state.cpu->StopExecution();
}
state.cpu->SetRegister(wreg::w0, 0);
}
void SendSyncRequest(device_state &state) {
state.logger->write(Logger::DEBUG, "svcSendSyncRequest called for handle 0x{0:X}.", state.cpu->GetRegister(xreg::x0));
uint8_t tls[constant::tls_ipc_size];
state.memory->Read(&tls, constant::tls_addr, constant::tls_ipc_size);
ipc::IpcRequest request(tls, state);
state.cpu->SetRegister(wreg::w0, 0);
}
void OutputDebugString(device_state &state) {
std::string debug(state.cpu->GetRegister(xreg::x1), '\0');
state.memory->Read((void *) debug.data(), state.cpu->GetRegister(xreg::x0), state.cpu->GetRegister(xreg::x1));
state.logger->write(Logger::INFO, "svcOutputDebugString: {0}", debug.c_str());
state.cpu->SetRegister(wreg::w0, 0);
}
void GetInfo(device_state &state) {
switch (state.cpu->GetRegister(xreg::x1)) {
case constant::infoState::AllowedCpuIdBitmask:
case constant::infoState::AllowedThreadPriorityMask:
case constant::infoState::IsCurrentProcessBeingDebugged:
state.cpu->SetRegister(xreg::x1, 0);
break;
case constant::infoState::AddressSpaceBaseAddr:
state.cpu->SetRegister(xreg::x1, constant::base_addr);
break;
case constant::infoState::TitleId:
state.cpu->SetRegister(xreg::x1, 0); // TODO: Complete this
break;
default:
state.logger->write(Logger::WARN, "Unimplemented GetInfo call. ID1: {0}, ID2: {1}", state.cpu->GetRegister(xreg::x1), state.cpu->GetRegister(xreg::x3));
state.cpu->SetRegister(xreg::x1, constant::svc_unimpl);
return;
}
state.cpu->SetRegister(wreg::w0, 0);
}
void ExitProcess(device_state &state) {
state.cpu->StopExecution();
}
}

View File

@ -143,7 +143,7 @@ public class LogActivity extends AppCompatActivity {
bufferedWriter.flush();
bufferedWriter.close();
outputStream.close();
//urlConnection.connect();
urlConnection.connect();
InputStream inputStream = new BufferedInputStream(urlConnection.getInputStream());
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
String key = new JSONObject(bufferedReader.lines().collect(Collectors.joining())).getString("key");
@ -160,7 +160,7 @@ public class LogActivity extends AppCompatActivity {
}});
share_thread.start();
try {
share_thread.join();
share_thread.join(1000);
} catch (InterruptedException e) {
Toast.makeText(getApplicationContext(), getString(R.string.share_error), Toast.LENGTH_LONG).show();
e.printStackTrace();

View File

@ -7,7 +7,7 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:3.4.2'
classpath 'com.android.tools.build:gradle:3.5.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files

View File

@ -1,6 +1,5 @@
#Fri Jun 28 17:28:16 EDT 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.1-all.zip