mirror of
https://github.com/skyline-emu/skyline.git
synced 2024-11-22 10:29:16 +01:00
Milestone 1 - Processes & Threads
Reworks the API a fair bit and adds documentation to almost everything.
This commit is contained in:
parent
62cb561888
commit
9e1e06c64b
9
.gitignore
vendored
9
.gitignore
vendored
@ -88,4 +88,11 @@ fabric.properties
|
||||
.idea/caches/build_file_checksums.ser
|
||||
|
||||
# Android Studio captures folder
|
||||
captures/
|
||||
captures/
|
||||
|
||||
# VSCode
|
||||
.vscode/
|
||||
|
||||
# Android Studio
|
||||
.cxx/
|
||||
|
||||
|
@ -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>
|
@ -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>
|
@ -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" />
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -1,46 +1,43 @@
|
||||
#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);
|
||||
env->ReleaseStringUTFChars(pref_path_, pref_path);
|
||||
env->ReleaseStringUTFChars(log_path_, log_path);
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,69 +1,284 @@
|
||||
#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;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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 };
|
||||
}
|
@ -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);
|
||||
}
|
||||
};
|
||||
};
|
@ -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 = {®s, 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 = {®s, 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 *>(®s.regs))[wreg_lut[reg_id]];
|
||||
}
|
||||
|
||||
void Cpu::SetRegister(wreg reg_id, uint32_t value) {
|
||||
(reinterpret_cast<uint32_t *>(®s.regs))[wreg_lut[reg_id]] = value;
|
||||
}
|
||||
}
|
@ -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);
|
||||
};
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
};
|
||||
|
||||
}
|
102
app/src/main/cpp/switch/kernel/ipc.cpp
Normal file
102
app/src/main/cpp/switch/kernel/ipc.cpp
Normal 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); }
|
||||
}
|
69
app/src/main/cpp/switch/kernel/ipc.h
Normal file
69
app/src/main/cpp/switch/kernel/ipc.h
Normal 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);
|
||||
};
|
||||
}
|
7
app/src/main/cpp/switch/kernel/service.cpp
Normal file
7
app/src/main/cpp/switch/kernel/service.cpp
Normal file
@ -0,0 +1,7 @@
|
||||
#include "service.h"
|
||||
|
||||
namespace lightSwitch::kernel::service {
|
||||
Service::Service() {
|
||||
|
||||
}
|
||||
}
|
9
app/src/main/cpp/switch/kernel/service.h
Normal file
9
app/src/main/cpp/switch/kernel/service.h
Normal file
@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "../nce.h"
|
||||
|
||||
namespace lightSwitch::kernel::service {
|
||||
class Service {
|
||||
Service();
|
||||
};
|
||||
}
|
8
app/src/main/cpp/switch/kernel/services/BaseService.h
Normal file
8
app/src/main/cpp/switch/kernel/services/BaseService.h
Normal file
@ -0,0 +1,8 @@
|
||||
#include "../../common.h"
|
||||
|
||||
namespace lightSwitch::kernel::service {
|
||||
class BaseService {
|
||||
virtual const char* name() = 0;
|
||||
BaseService() {}
|
||||
};
|
||||
}
|
136
app/src/main/cpp/switch/kernel/svc.cpp
Normal file
136
app/src/main/cpp/switch/kernel/svc.cpp
Normal 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);
|
||||
}
|
||||
}
|
@ -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
|
||||
@ -181,4 +236,4 @@ namespace lightSwitch::os::svc {
|
||||
nullptr, // 0x7e
|
||||
nullptr // 0x7f
|
||||
};
|
||||
}
|
||||
}
|
15
app/src/main/cpp/switch/kernel/types/KObject.h
Normal file
15
app/src/main/cpp/switch/kernel/types/KObject.h
Normal 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) {}
|
||||
};
|
||||
}
|
193
app/src/main/cpp/switch/kernel/types/KProcess.cpp
Normal file
193
app/src/main/cpp/switch/kernel/types/KProcess.cpp
Normal 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
|
||||
}
|
||||
}
|
191
app/src/main/cpp/switch/kernel/types/KProcess.h
Normal file
191
app/src/main/cpp/switch/kernel/types/KProcess.h
Normal 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);
|
||||
};
|
||||
}
|
26
app/src/main/cpp/switch/kernel/types/KThread.cpp
Normal file
26
app/src/main/cpp/switch/kernel/types/KThread.cpp
Normal 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));
|
||||
}
|
||||
}
|
53
app/src/main/cpp/switch/kernel/types/KThread.h
Normal file
53
app/src/main/cpp/switch/kernel/types/KThread.h
Normal 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);
|
||||
};
|
||||
}
|
@ -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) {}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
294
app/src/main/cpp/switch/nce.cpp
Normal file
294
app/src/main/cpp/switch/nce.cpp
Normal 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 ®isters, pid_t pid) const {
|
||||
iovec iov = {®isters, 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 ®isters, pid_t pid) const {
|
||||
iovec iov = {®isters, 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®ion : 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 *>(®ister_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 *>(®ister_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);
|
||||
}
|
||||
}
|
202
app/src/main/cpp/switch/nce.h
Normal file
202
app/src/main/cpp/switch/nce.h
Normal 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 ®isters, 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 ®isters, 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);
|
||||
};
|
||||
}
|
91
app/src/main/cpp/switch/os.cpp
Normal file
91
app/src/main/cpp/switch/os.cpp
Normal 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;
|
||||
}
|
||||
}
|
61
app/src/main/cpp/switch/os.h
Normal file
61
app/src/main/cpp/switch/os.h
Normal 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);
|
||||
};
|
||||
}
|
@ -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)]);
|
||||
}
|
||||
}
|
@ -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();
|
||||
};
|
||||
}
|
@ -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++;
|
||||
}
|
||||
}
|
@ -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);
|
||||
};
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
@ -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
|
||||
|
3
gradle/wrapper/gradle-wrapper.properties
vendored
3
gradle/wrapper/gradle-wrapper.properties
vendored
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user