Milestone 2 - Memory & IPC Marshalling

This commit introduces a new memory model that supports true shared memory with separate permissions for remote and local processes, implements svcQueryMemory and completes svcGetInfo further, adds IPC support with the IpcRequest and IpcResponse classes.
This commit is contained in:
◱ PixelyIon 2019-09-14 18:11:00 +05:30
parent 9e1e06c64b
commit a54f5ff578
59 changed files with 1752 additions and 1256 deletions

8
.gitignore vendored
View File

@ -61,7 +61,8 @@ build/
local.properties local.properties
# External native build folder generated in Android Studio 2.2 and later # External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild .externalNativeBuild/
.cxx/
# IntelliJ # IntelliJ
out/ out/
@ -93,6 +94,5 @@ captures/
# VSCode # VSCode
.vscode/ .vscode/
# Android Studio # Discord plugin for IntelliJ IDEA
.cxx/ .idea\discord.xml

View File

@ -4,6 +4,8 @@
<option name="WRAP_WHEN_TYPING_REACHES_RIGHT_MARGIN" value="true" /> <option name="WRAP_WHEN_TYPING_REACHES_RIGHT_MARGIN" value="true" />
<option name="SOFT_MARGINS" value="80,140" /> <option name="SOFT_MARGINS" value="80,140" />
<Objective-C> <Objective-C>
<option name="INDENT_VISIBILITY_KEYWORDS" value="2" />
<option name="INDENT_PREPROCESSOR_DIRECTIVE" value="4" />
<option name="INDENT_DIRECTIVE_AS_CODE" value="true" /> <option name="INDENT_DIRECTIVE_AS_CODE" value="true" />
<option name="KEEP_STRUCTURES_IN_ONE_LINE" value="true" /> <option name="KEEP_STRUCTURES_IN_ONE_LINE" value="true" />
<option name="FUNCTION_PARAMETERS_WRAP" value="0" /> <option name="FUNCTION_PARAMETERS_WRAP" value="0" />
@ -31,6 +33,12 @@
<option name="KEEP_MULTIPLE_EXPRESSIONS_IN_ONE_LINE" value="true" /> <option name="KEEP_MULTIPLE_EXPRESSIONS_IN_ONE_LINE" value="true" />
<option name="WRAP_LONG_LINES" value="true" /> <option name="WRAP_LONG_LINES" value="true" />
</codeStyleSettings> </codeStyleSettings>
<codeStyleSettings language="ObjectiveC">
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
<option name="LABEL_INDENT_SIZE" value="-1" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="XML"> <codeStyleSettings language="XML">
<arrangement> <arrangement>
<rules> <rules>

View File

@ -4,7 +4,4 @@
<component name="DiscordProjectSettings"> <component name="DiscordProjectSettings">
<option name="show" value="true" /> <option name="show" value="true" />
</component> </component>
<component name="ProjectNotificationSettings">
<option name="askShowProject" value="false" />
</component>
</project> </project>

View File

@ -1,6 +1,5 @@
cmake_minimum_required(VERSION 3.8) cmake_minimum_required(VERSION 3.8)
project(Lightswitch VERSION 1 LANGUAGES CXX) project(Lightswitch VERSION 1 LANGUAGES CXX)
set_property(GLOBAL PROPERTY CMAKE_CXX_STANDARD 17 PROPERTY CMAKE_CXX_STANDARD_REQUIRED TRUE)
set(BUILD_TESTING OFF) set(BUILD_TESTING OFF)
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 17)
@ -25,6 +24,8 @@ add_library(lightswitch SHARED
${source_DIR}/switch/kernel/service.cpp ${source_DIR}/switch/kernel/service.cpp
${source_DIR}/switch/kernel/types/KProcess.cpp ${source_DIR}/switch/kernel/types/KProcess.cpp
${source_DIR}/switch/kernel/types/KThread.cpp ${source_DIR}/switch/kernel/types/KThread.cpp
${source_DIR}/switch/kernel/types/KSharedMemory.cpp
${source_DIR}/switch/kernel/types/KPrivateMemory.cpp
) )
target_link_libraries(lightswitch fmt tinyxml2 android) target_link_libraries(lightswitch fmt tinyxml2 android)
target_compile_options(lightswitch PRIVATE -Wno-c++17-extensions) target_compile_options(lightswitch PRIVATE -Wno-c++17-extensions)

View File

@ -1,11 +1,11 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
package="emu.lightswitch"> package="emu.lightswitch">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_INTERNAL_STORAGE"/> <uses-permission android:name="android.permission.READ_INTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET" />
<application <application
android:allowBackup="true" android:allowBackup="true"
@ -15,12 +15,13 @@
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme" android:theme="@style/AppTheme"
tools:ignore="GoogleAppIndexingWarning"> tools:ignore="GoogleAppIndexingWarning">
<activity android:name=".LogActivity" <activity
android:label="@string/log" android:name=".LogActivity"
android:parentActivityName=".MainActivity"> android:label="@string/log"
android:parentActivityName=".MainActivity">
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
android:value="emu.lightswitch.MainActivity"/> android:value="emu.lightswitch.MainActivity" />
</activity> </activity>
<activity <activity
android:name=".SettingsActivity" android:name=".SettingsActivity"
@ -28,15 +29,15 @@
android:parentActivityName=".MainActivity"> android:parentActivityName=".MainActivity">
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
android:value="emu.lightswitch.MainActivity"/> android:value="emu.lightswitch.MainActivity" />
</activity> </activity>
<activity android:name=".MainActivity"> <activity android:name=".MainActivity">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
</application> </application>
</manifest> </manifest>

View File

@ -32,6 +32,7 @@ extern "C" JNIEXPORT void JNICALL Java_emu_lightswitch_MainActivity_loadFile(JNI
if (emu_thread) { 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(); emu_thread->join();
halt = false; // Or the current instance will halt immediately
} }
// Running on UI thread is not a good idea as the UI will remain unresponsive // Running on UI thread is not a good idea as the UI will remain unresponsive

View File

@ -3,33 +3,7 @@
#include <syslog.h> #include <syslog.h>
namespace lightSwitch { namespace lightSwitch {
namespace memory { Settings::Settings(std::string &pref_xml) {
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; tinyxml2::XMLDocument pref;
if (pref.LoadFile(pref_xml.c_str())) if (pref.LoadFile(pref_xml.c_str()))
throw exception("TinyXML2 Error: " + std::string(pref.ErrorStr())); throw exception("TinyXML2 Error: " + std::string(pref.ErrorStr()));
@ -38,13 +12,10 @@ namespace lightSwitch {
switch (elem->Value()[0]) { switch (elem->Value()[0]) {
case 's': case 's':
string_map.insert( string_map.insert(
std::pair<char *, char *>((char *) elem->FindAttribute("name")->Value(), std::pair<char *, char *>((char *) elem->FindAttribute("name")->Value(), (char *) elem->GetText()));
(char *) elem->GetText()));
break; break;
case 'b': case 'b':
bool_map.insert( bool_map.insert(std::pair<char *, bool>((char *) elem->FindAttribute("name")->Value(), elem->FindAttribute("value")->BoolValue()));
std::pair<char *, bool>((char *) elem->FindAttribute("name")->Value(),
elem->FindAttribute("value")->BoolValue()));
default: default:
break; break;
}; };
@ -78,7 +49,7 @@ namespace lightSwitch {
} }
} }
Logger::Logger(const std::string & log_path) { Logger::Logger(const std::string &log_path) {
log_file.open(log_path, std::ios::app); log_file.open(log_path, std::ios::app);
WriteHeader("Logging started"); WriteHeader("Logging started");
} }
@ -87,13 +58,13 @@ namespace lightSwitch {
WriteHeader("Logging ended"); WriteHeader("Logging ended");
} }
void Logger::WriteHeader(const std::string& str) { void Logger::WriteHeader(const std::string &str) {
syslog(LOG_ALERT, "%s", str.c_str()); syslog(LOG_ALERT, "%s", str.c_str());
log_file << "0|" << str << "\n"; log_file << "0|" << str << "\n";
log_file.flush(); log_file.flush();
} }
void Logger::Write(const LogLevel level, const std::string& str) { void Logger::Write(const LogLevel level, const std::string &str) {
#ifdef NDEBUG #ifdef NDEBUG
if (level == DEBUG) return; if (level == DEBUG) return;
#endif #endif

View File

@ -18,49 +18,71 @@
namespace lightSwitch { namespace lightSwitch {
// Global typedefs // Global typedefs
typedef __uint128_t u128;
typedef __uint64_t u64;
typedef __uint32_t u32;
typedef __uint16_t u16;
typedef __uint8_t u8;
typedef __int128_t i128;
typedef __int64_t i64;
typedef __int32_t i32;
typedef __int16_t i16;
typedef __int8_t i8;
typedef std::runtime_error exception; //!< This is used as the default exception typedef std::runtime_error exception; //!< This is used as the default exception
typedef uint32_t handle_t; //!< The type of an handle typedef u32 handle_t; //!< The type of an handle
namespace constant { namespace constant {
// Memory // Memory
constexpr uint64_t base_addr = 0x8000000; //!< The address space base constexpr u64 base_addr = 0x8000000; //!< The address space base
constexpr uint64_t base_size = 0x7FF8000000; //!< The size of the address space constexpr u64 map_addr = base_addr + 0x80000000; //!< The address of the map region
constexpr uint64_t total_phy_mem = 0xF8000000; // ~4 GB of RAM constexpr u64 base_size = 0x7FF8000000; //!< The size of the address space
constexpr u64 map_size = 0x1000000000; //!< The size of the map region
constexpr u64 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 def_stack_size = 0x1E8480; //!< The default amount of stack: 2 MB
constexpr size_t def_heap_size = PAGE_SIZE; //!< The default amount of heap
constexpr size_t tls_slot_size = 0x200; //!< The size of a single TLS slot 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 constexpr u8 tls_slots = PAGE_SIZE / tls_slot_size; //!< The amount of TLS slots in a single page
// Loader // Loader
constexpr uint32_t nro_magic = 0x304F524E; //!< "NRO0" in reverse, this is written at the start of every NRO file constexpr u32 nro_magic = 0x304F524E; //!< "NRO0" in reverse, this is written at the start of every NRO file
// NCE // NCE
constexpr uint8_t num_regs = 31; //!< The amount of registers that ARMv8 has constexpr u8 num_regs = 31; //!< The amount of registers that ARMv8 has
constexpr uint16_t svc_last = 0x7F; //!< The index of the last SVC constexpr u16 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 u16 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 constexpr u32 tpidrro_el0 = 0x5E83; //!< ID of tpidrro_el0 in MRS
// IPC // IPC
constexpr size_t tls_ipc_size = 0x100; //!< The size of the IPC command buffer in a TLS slot 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 handle_t sm_handle = 0xD000; //!< sm:'s handle
constexpr uint8_t port_size = 0x8; //!< The size of a port name string constexpr u8 port_size = 0x8; //!< The size of a port name string
constexpr uint32_t ipc_sfco = 0x4F434653; //!< SFCO in reverse constexpr u32 sfco_magic = 0x4F434653; //!< SFCO in reverse, written to IPC messages
constexpr u32 sfci_magic = 0x49434653; //!< SFCI in reverse, present in received IPC messages
constexpr u64 padding_sum = 0x10; //!< The sum of the padding surrounding DataPayload
// Process // Process
constexpr uint32_t base_handle_index = 0xD001; // The index of the base handle constexpr handle_t base_handle_index = sm_handle + 1; // The index of the base handle
constexpr uint8_t default_priority = 31; //!< The default priority of a process constexpr u8 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<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 constexpr std::pair<u8, u8> priority_nin = {0, 63}; //!< The range of priority for the Nintendo Switch
// Status codes
namespace status {
constexpr u32 success = 0x0; //!< "Success"
constexpr u32 inv_address = 0xCC01; //!< "Invalid address"
constexpr u32 inv_handle = 0xE401; //!< "Invalid handle"
constexpr u32 unimpl = 0x177202; //!< "Unimplemented behaviour"
}
}; };
namespace instr { 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. * A bit-field 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 { struct brk {
/** /**
* Creates a BRK instruction with a specific immediate value, used for generating BRK opcodes * Creates a BRK instruction with a specific immediate value, used for generating BRK opcodes
* @param val The immediate value of the instruction * @param val The immediate value of the instruction
*/ */
brk(uint16_t val) { brk(u16 val) {
start = 0x0; // First 5 bits of an BRK instruction are 0 start = 0x0; // First 5 bits of an BRK instruction are 0
value = val; value = val;
end = 0x6A1; // Last 11 bits of an BRK instruction stored as uint16_t end = 0x6A1; // Last 11 bits of an BRK instruction stored as u16
} }
/** /**
@ -70,13 +92,15 @@ namespace lightSwitch {
return (start == 0x0 && end == 0x6A1); return (start == 0x0 && end == 0x6A1);
} }
uint8_t start : 5; u8 start : 5;
uint32_t value : 16; u32 value : 16;
uint16_t end : 11; u16 end : 11;
}; };
static_assert(sizeof(brk) == sizeof(u32));
/** /**
* A bitfield struct that encapsulates a SVC instruction. See https://developer.arm.com/docs/ddi0596/latest/base-instructions-alphabetic-order/svc-supervisor-call. * A bit-field struct that encapsulates a SVC instruction. See https://developer.arm.com/docs/ddi0596/latest/base-instructions-alphabetic-order/svc-supervisor-call.
*/ */
struct svc { struct svc {
/** /**
@ -86,13 +110,15 @@ namespace lightSwitch {
return (start == 0x1 && end == 0x6A0); return (start == 0x1 && end == 0x6A0);
} }
uint8_t start : 5; u8 start : 5;
uint32_t value : 16; u32 value : 16;
uint16_t end : 11; u16 end : 11;
}; };
static_assert(sizeof(svc) == sizeof(u32));
/** /**
* A bitfield struct that encapsulates a MRS instruction. See https://developer.arm.com/docs/ddi0596/latest/base-instructions-alphabetic-order/mrs-move-system-register. * A bit-field struct that encapsulates a MRS instruction. See https://developer.arm.com/docs/ddi0596/latest/base-instructions-alphabetic-order/mrs-move-system-register.
*/ */
struct mrs { struct mrs {
/** /**
@ -102,79 +128,26 @@ namespace lightSwitch {
return (end == 0xD53); return (end == 0xD53);
} }
uint8_t dst_reg : 5; u8 dst_reg : 5;
uint32_t src_reg : 15; u32 src_reg : 15;
uint16_t end : 12; u16 end : 12;
}; };
static_assert(sizeof(mrs) == sizeof(u32));
}; };
/** /**
* Read about ARMv8 registers here: https://developer.arm.com/docs/100878/latest/registers * Read about ARMv8 registers here: https://developer.arm.com/docs/100878/latest/registers
*/ */
namespace regs { enum class 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 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 class 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 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 class sreg { sp, pc, pstate };
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 * The Settings class is used to access the parameters set in the Java component of the application
*/ */
class Settings { class Settings {
private: private:
struct KeyCompare { struct KeyCompare {
bool operator()(char const *a, char const *b) const { bool operator()(char const *a, char const *b) const {
return std::strcmp(a, b) < 0; return std::strcmp(a, b) < 0;
@ -184,11 +157,11 @@ namespace lightSwitch {
std::map<char *, char *, KeyCompare> string_map; //!< A mapping from all keys to their corresponding string value 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 std::map<char *, bool, KeyCompare> bool_map; //!< A mapping from all keys to their corresponding boolean value
public: public:
/** /**
* @param pref_xml The path to the preference XML file * @param pref_xml The path to the preference XML file
*/ */
Settings(std::string pref_xml); Settings(std::string &pref_xml);
/** /**
* @param key The key of the setting * @param key The key of the setting
@ -212,13 +185,13 @@ namespace lightSwitch {
* The Logger class is to generate a log of the program * The Logger class is to generate a log of the program
*/ */
class Logger { class Logger {
private: private:
std::ofstream log_file; //!< An output stream to the log file 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 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 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: public:
enum LogLevel {ERROR, WARN, INFO, DEBUG}; //!< The level of a particular log enum LogLevel { ERROR, WARN, INFO, DEBUG }; //!< The level of a particular log
/** /**
* @param log_path The path to the log file * @param log_path The path to the log file
@ -263,6 +236,7 @@ namespace lightSwitch {
namespace kernel { namespace kernel {
namespace type { namespace type {
class KProcess; class KProcess;
class KThread; class KThread;
} }
class OS; class OS;
@ -275,8 +249,8 @@ namespace lightSwitch {
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) {} 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 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::KProcess> &this_process;
std::shared_ptr<kernel::type::KThread>& this_thread; std::shared_ptr<kernel::type::KThread> &this_thread;
std::shared_ptr<NCE> nce; std::shared_ptr<NCE> nce;
std::shared_ptr<Settings> settings; std::shared_ptr<Settings> settings;
std::shared_ptr<Logger> logger; std::shared_ptr<Logger> logger;

View File

@ -4,99 +4,130 @@
#include "types/KProcess.h" #include "types/KProcess.h"
namespace lightSwitch::kernel::ipc { namespace lightSwitch::kernel::ipc {
IpcRequest::IpcRequest(uint8_t *tls_ptr, device_state &state) : req_info((CommandStruct *) tls_ptr) { IpcRequest::IpcRequest(bool is_domain, device_state &state) : is_domain(is_domain), state(state), tls() {
state.logger->Write(Logger::DEBUG, "Enable handle descriptor: {0}", (bool) req_info->handle_desc); u8 *curr_ptr = tls.data();
state.this_process->ReadMemory(curr_ptr, state.this_thread->tls, constant::tls_ipc_size);
data_offset = 8; header = reinterpret_cast<CommandHeader *>(curr_ptr);
if (req_info->handle_desc) { curr_ptr += sizeof(CommandHeader);
HandleDescriptor handledesc = *(HandleDescriptor *) (&tls_ptr[8]); if (header->handle_desc) {
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]); handle_desc = reinterpret_cast<HandleDescriptor *>(curr_ptr);
data_offset += 4 + handledesc.copy_count * 4 + handledesc.move_count * 4; curr_ptr += sizeof(HandleDescriptor) + (handle_desc->send_pid ? sizeof(u8) : 0);
} for (uint index = 0; handle_desc->copy_count > index; index++) {
copy_handles.push_back(*reinterpret_cast<handle_t *>(curr_ptr));
if (req_info->x_no || req_info->a_no || req_info->b_no || req_info->w_no) curr_ptr += sizeof(handle_t);
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) { for (uint index = 0; handle_desc->move_count > index; index++) {
state.logger->Write(Logger::DEBUG, "Moving handle to 0x{:X}", 12 + handle_index * 4); move_handles.push_back(*reinterpret_cast<handle_t *>(curr_ptr));
tls_ptr[3 + handle_index] = moved_handle; curr_ptr += sizeof(handle_t);
handle_index += 1;
} }
data_offset += 4 + copied_handles.size() * 4 + moved_handles.size() * 4;
} }
for (uint index = 0; header->x_no > index; index++) {
state.logger->Write(Logger::DEBUG, "Data offset: 0x{:X}", data_offset); vec_buf_x.push_back(reinterpret_cast<BufferDescriptorX *>(curr_ptr));
curr_ptr += sizeof(BufferDescriptorX);
// Align to 16 bytes }
data_offset = ((data_offset - 1) & ~(15U)) + 16; // ceil data_offset with multiples 16 for (uint index = 0; header->a_no > index; index++) {
vec_buf_a.push_back(reinterpret_cast<BufferDescriptorABW *>(curr_ptr));
curr_ptr += sizeof(BufferDescriptorABW);
}
for (uint index = 0; header->b_no > index; index++) {
vec_buf_b.push_back(reinterpret_cast<BufferDescriptorABW *>(curr_ptr));
curr_ptr += sizeof(BufferDescriptorABW);
}
for (uint index = 0; header->w_no > index; index++) {
vec_buf_w.push_back(reinterpret_cast<BufferDescriptorABW *>(curr_ptr));
curr_ptr += sizeof(BufferDescriptorABW);
}
auto raw_ptr = reinterpret_cast<u8 *>((((reinterpret_cast<u64>(curr_ptr) - reinterpret_cast<u64>(tls.data())) - 1U) & ~(constant::padding_sum - 1U)) + constant::padding_sum + reinterpret_cast<u64>(tls.data())); // Align to 16 bytes relative to start of TLS
if (is_domain) { if (is_domain) {
tls_ptr[data_offset >> 2] = static_cast<uint32_t>(moved_handles.size()); domain = reinterpret_cast<DomainHeaderRequest *>(raw_ptr);
data_offset += 0x10; payload = reinterpret_cast<PayloadHeader *>(raw_ptr + sizeof(DomainHeaderRequest));
cmd_arg_sz = domain->payload_sz - sizeof(PayloadHeader);
} else {
payload = reinterpret_cast<PayloadHeader *>(raw_ptr);
cmd_arg_sz = (header->raw_sz * sizeof(u32)) - (constant::padding_sum + sizeof(PayloadHeader));
}
if (payload->magic != constant::sfci_magic) throw exception(fmt::format("Unexpected Magic in PayloadHeader: 0x{:X}", reinterpret_cast<u32>(payload->magic)));
cmd_arg = reinterpret_cast<u8 *>(payload) + sizeof(PayloadHeader);
curr_ptr += header->raw_sz * sizeof(u32);
if (header->c_flag == static_cast<u8>(BufferCFlag::SingleDescriptor)) {
vec_buf_c.push_back(reinterpret_cast<BufferDescriptorC *>(curr_ptr));
} else if (header->c_flag > static_cast<u8>(BufferCFlag::SingleDescriptor)) {
for (uint index = 0; (header->c_flag - 2) > index; index++) { // (c_flag - 2) C descriptors are present
vec_buf_c.push_back(reinterpret_cast<BufferDescriptorC *>(curr_ptr));
curr_ptr += sizeof(BufferDescriptorC);
}
} }
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; } IpcResponse::IpcResponse(bool is_domain, device_state &state) : is_domain(is_domain), state(state) {}
template<typename T>
void IpcResponse::WriteValue() {
void IpcResponse::WriteTls() {
std::array<u8, constant::tls_ipc_size> tls{};
u8 *curr_ptr = tls.data();
auto header = reinterpret_cast<CommandHeader *>(curr_ptr);
header->x_no = static_cast<u8>(vec_buf_x.size());
header->a_no = static_cast<u8>(vec_buf_a.size());
header->b_no = static_cast<u8>(vec_buf_b.size());
header->w_no = static_cast<u8>(vec_buf_w.size());
header->raw_sz = static_cast<u32>((sizeof(PayloadHeader) + arg_vec.size() + constant::padding_sum + (is_domain ? sizeof(DomainHeaderRequest) : 0)) / sizeof(u32)); // Size is in 32-bit units because Nintendo
if (!vec_buf_c.empty())
header->c_flag = (vec_buf_c.size() == 1) ? static_cast<u8>(BufferCFlag::SingleDescriptor) : static_cast<u8>(vec_buf_c.size() + static_cast<u8>(BufferCFlag::SingleDescriptor));
header->handle_desc = (!copy_handles.empty() || !move_handles.empty());
curr_ptr += sizeof(CommandHeader);
if (header->handle_desc) {
auto handle_desc = reinterpret_cast<HandleDescriptor *>(curr_ptr);
handle_desc->send_pid = false; // TODO: Figure this out ?
handle_desc->copy_count = static_cast<u8>(copy_handles.size());
handle_desc->move_count = static_cast<u8>(move_handles.size());
curr_ptr += sizeof(HandleDescriptor);
for (uint index = 0; handle_desc->copy_count > index; index++) {
*reinterpret_cast<handle_t *>(curr_ptr) = copy_handles[index];
curr_ptr += sizeof(handle_t);
}
for (uint index = 0; handle_desc->move_count > index; index++) {
*reinterpret_cast<handle_t *>(curr_ptr) = move_handles[index];
curr_ptr += sizeof(handle_t);
}
}
for (uint index = 0; header->x_no > index; index++) {
*reinterpret_cast<BufferDescriptorX *>(curr_ptr) = vec_buf_x[index];
curr_ptr += sizeof(BufferDescriptorX);
}
for (uint index = 0; header->a_no > index; index++) {
*reinterpret_cast<BufferDescriptorABW *>(curr_ptr) = vec_buf_a[index];
curr_ptr += sizeof(BufferDescriptorABW);
}
for (uint index = 0; header->b_no > index; index++) {
*reinterpret_cast<BufferDescriptorABW *>(curr_ptr) = vec_buf_b[index];
curr_ptr += sizeof(BufferDescriptorABW);
}
for (uint index = 0; header->w_no > index; index++) {
*reinterpret_cast<BufferDescriptorABW *>(curr_ptr) = vec_buf_w[index];
curr_ptr += sizeof(BufferDescriptorABW);
}
u64 padding = ((((reinterpret_cast<u64>(curr_ptr) - reinterpret_cast<u64>(tls.data())) - 1U) & ~(constant::padding_sum - 1U)) + constant::padding_sum + (reinterpret_cast<u64>(tls.data()) - reinterpret_cast<u64>(curr_ptr))); // Calculate the amount of padding at the front
curr_ptr += padding;
PayloadHeader *payload;
if (is_domain) {
auto domain = reinterpret_cast<DomainHeaderResponse *>(curr_ptr);
domain->output_count = 0; // TODO: Figure this out
payload = reinterpret_cast<PayloadHeader *>(curr_ptr + sizeof(DomainHeaderResponse));
} else {
payload = reinterpret_cast<PayloadHeader *>(curr_ptr);
}
payload->magic = constant::sfco_magic;
payload->version = 1;
payload->value = error_code;
curr_ptr += sizeof(PayloadHeader);
if (!arg_vec.empty()) memcpy(curr_ptr, arg_vec.data(), arg_vec.size());
curr_ptr += arg_vec.size() + (constant::padding_sum - padding);
if (header->c_flag == static_cast<u8>(BufferCFlag::SingleDescriptor)) {
*reinterpret_cast<BufferDescriptorC *>(curr_ptr) = vec_buf_c[0];
} else if (header->c_flag > static_cast<u8>(BufferCFlag::SingleDescriptor)) {
for (uint index = 0; (header->c_flag - 2) > index; index++) {
*reinterpret_cast<BufferDescriptorC *>(curr_ptr) = vec_buf_c[index];
curr_ptr += sizeof(BufferDescriptorC);
}
}
} }
void IpcResponse::CopyHandle(uint32_t handle) { copied_handles.push_back(handle); }
void IpcResponse::MoveHandle(uint32_t handle) { moved_handles.push_back(handle); }
} }

View File

@ -2,68 +2,224 @@
#include <cstdint> #include <cstdint>
#include <vector> #include <vector>
#include <array>
#include "switch/common.h" #include "switch/common.h"
namespace lightSwitch::kernel::ipc { namespace lightSwitch::kernel::ipc {
struct CommandStruct { /**
// https://switchbrew.org/wiki/IPC_Marshalling#IPC_Command_Structure * This bit-field structure holds the header of an IPC command.
uint16_t type : 16; * https://switchbrew.org/wiki/IPC_Marshalling#IPC_Command_Structure
uint8_t x_no : 4; */
uint8_t a_no : 4; struct CommandHeader {
uint8_t b_no : 4; u16 type : 16;
uint8_t w_no : 4; u8 x_no : 4;
uint16_t data_sz : 10; u8 a_no : 4;
uint8_t c_flags : 4; u8 b_no : 4;
uint32_t : 17; u8 w_no : 4;
u32 raw_sz : 10;
u8 c_flag : 4;
u32 : 17;
bool handle_desc : 1; bool handle_desc : 1;
}; };
static_assert(sizeof(CommandHeader) == 8);
struct HandleDescriptor { /**
bool send_pid : 1; * This reflects the value in CommandStruct::type
uint8_t copy_count : 4; */
uint8_t move_count : 4; enum class CommandType : u16 {
Invalid = 0, LegacyRequest = 1, Close = 2, LegacyControl = 3, Request = 4, Control = 5, RequestWithContext = 6, ControlWithContext = 7
}; };
/**
* This reflects the value in CommandStruct::c_flags
*/
enum class BufferCFlag : u8 {
None = 0, InlineDescriptor = 1, SingleDescriptor = 2
};
/**
* This bit-field structure holds the handle descriptor of a recieved IPC command.
* https://switchbrew.org/wiki/IPC_Marshalling#Handle_descriptor
*/
struct HandleDescriptor {
bool send_pid : 1;
u32 copy_count : 4;
u32 move_count : 4;
u32 : 23;
};
static_assert(sizeof(HandleDescriptor) == 4);
/**
* This bit-field structure holds the domain's header of an IPC request command.
* https://switchbrew.org/wiki/IPC_Marshalling#Domains
*/
struct DomainHeaderRequest {
u8 command : 8;
u8 input_count : 8;
u16 payload_sz : 16;
u32 object_id : 32;
u32 : 32;
u32 token : 32;
};
static_assert(sizeof(DomainHeaderRequest) == 16);
/**
* This bit-field structure holds the domain's header of an IPC response command.
* https://switchbrew.org/wiki/IPC_Marshalling#Domains
*/
struct DomainHeaderResponse {
u64 output_count : 32;
u64 : 32;
u64 : 64;
};
static_assert(sizeof(DomainHeaderResponse) == 16);
/**
* This bit-field structure holds the data payload of an IPC command.
* https://switchbrew.org/wiki/IPC_Marshalling#Data_payload
*/
struct PayloadHeader {
u32 magic : 32;
u32 version : 32;
u32 value : 32;
u32 token : 32;
};
static_assert(sizeof(PayloadHeader) == 16);
/**
* This is a buffer descriptor for X buffers: https://switchbrew.org/wiki/IPC_Marshalling#Buffer_descriptor_X_.22Pointer.22
*/
struct BufferDescriptorX {
u16 counter_0_5 : 6;
u16 address_36_38 : 3;
u16 counter_9_11 : 3;
u16 address_32_35 : 4;
u16 size : 16;
u32 address_0_31 : 32;
BufferDescriptorX(u64 address, u16 counter, u16 size) : size(size) {
// Test: The AND mask might be the other way around
address_0_31 = static_cast<u32>(address & 0x7FFFFFFF80000000);
address_32_35 = static_cast<u16>(address & 0x78000000);
address_36_38 = static_cast<u16>(address & 0x7000000);
counter_0_5 = static_cast<u16>(address & 0x7E00);
counter_9_11 = static_cast<u16>(address & 0x38);
}
inline u64 Address() const {
return static_cast<u64>(address_0_31) | static_cast<u64>(address_32_35) << 32 | static_cast<u64>(address_36_38) << 36;
}
inline u16 Counter() const {
return static_cast<u16>(counter_0_5) | static_cast<u16>(counter_9_11) << 9;
}
};
static_assert(sizeof(BufferDescriptorX) == 8);
/**
* This is a buffer descriptor for A/B/W buffers: https://switchbrew.org/wiki/IPC_Marshalling#Buffer_descriptor_A.2FB.2FW_.22Send.22.2F.22Receive.22.2F.22Exchange.22
*/
struct BufferDescriptorABW {
u32 size_0_31 : 32;
u32 address_0_31 : 32;
u8 flags : 2;
u8 address_36_38 : 3;
u32 : 19;
u8 size_32_35 : 4;
u8 address_32_35 : 4;
BufferDescriptorABW(u64 address, u64 size) {
address_0_31 = static_cast<u32>(address & 0x7FFFFFFF80000000);
address_32_35 = static_cast<u8>(address & 0x78000000);
address_36_38 = static_cast<u8>(address & 0x7000000);
size_0_31 = static_cast<u32>(size & 0x7FFFFFFF80000000);
size_32_35 = static_cast<u8>(size & 0x78000000);
}
inline u64 Address() const {
return static_cast<u64>(address_0_31) | static_cast<u64>(address_32_35) << 32 | static_cast<u64>(address_36_38) << 36;
}
inline u64 Size() const {
return static_cast<u64>(size_0_31) | static_cast<u64>(size_32_35) << 32;
}
};
static_assert(sizeof(BufferDescriptorABW) == 12);
/**
* This is a buffer descriptor for C buffers: https://switchbrew.org/wiki/IPC_Marshalling#Buffer_descriptor_C_.22ReceiveList.22
*/
struct BufferDescriptorC {
u32 address_0_31 : 32;
u16 address_32_48 : 16;
u16 size : 16;
BufferDescriptorC(u64 address, u16 size) : size(size) {
address_0_31 = static_cast<u32>(address & 0x7FFFFFFF80000000);
address_32_48 = static_cast<u16>(address & 0x7FFFC000);
}
inline u64 Address() const {
return static_cast<u64>(address_0_31) | static_cast<u64>(address_32_48) << 32;
}
};
static_assert(sizeof(BufferDescriptorC) == 8);
class IpcRequest { class IpcRequest {
private: private:
uint8_t *data_ptr; device_state &state;
uint32_t data_offset;
public:
CommandStruct *req_info;
IpcRequest(uint8_t *tlsPtr, device_state &state); public:
std::array<u8, constant::tls_ipc_size> tls;
CommandHeader *header{};
HandleDescriptor *handle_desc{};
bool is_domain{};
DomainHeaderRequest *domain{};
PayloadHeader *payload{};
u8 *cmd_arg{};
u64 cmd_arg_sz{};
std::vector<handle_t> copy_handles;
std::vector<handle_t> move_handles;
std::vector<BufferDescriptorX *> vec_buf_x;
std::vector<BufferDescriptorABW *> vec_buf_a;
std::vector<BufferDescriptorABW *> vec_buf_b;
std::vector<BufferDescriptorABW *> vec_buf_w;
std::vector<BufferDescriptorC *> vec_buf_c;
template<typename T> IpcRequest(bool is_domain, device_state &state);
T GetValue();
}; };
class IpcResponse { class IpcResponse {
private: private:
uint32_t *tls_ptr{}; std::vector<u8> arg_vec;
uint32_t data_offset{}; // Offset to raw data relative to tls_ptr device_state &state;
bool is_domain{}; // TODO public:
uint32_t error_code{}; bool is_domain{};
u32 error_code{};
std::vector<handle_t> copy_handles;
std::vector<handle_t> move_handles;
std::vector<BufferDescriptorX> vec_buf_x;
std::vector<BufferDescriptorABW> vec_buf_a;
std::vector<BufferDescriptorABW> vec_buf_b;
std::vector<BufferDescriptorABW> vec_buf_w;
std::vector<BufferDescriptorC> vec_buf_c;
CommandStruct resp_info; IpcResponse(bool is_domain, device_state &state);
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> template<typename T>
void WriteValue(); // TODO void WriteValue(const T &value) {
arg_vec.reserve(arg_vec.size() + sizeof(T));
auto item = reinterpret_cast<const u8 *>(&value);
for (uint index = 0; sizeof(T) > index; index++) {
arg_vec.push_back(*item);
item++;
}
}
void CopyHandle(uint32_t handle); void WriteTls();
void MoveHandle(uint32_t handle);
}; };
} }

View File

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

View File

@ -3,7 +3,7 @@
#include "../nce.h" #include "../nce.h"
namespace lightSwitch::kernel::service { namespace lightSwitch::kernel::service {
class Service { class Service {
Service(); Service();
}; };
} }

View File

@ -1,8 +1,18 @@
#include "../../common.h" #include "../../common.h"
#include "../ipc.h"
namespace lightSwitch::kernel::service { namespace lightSwitch::kernel::service {
class BaseService { class BaseService {
virtual const char* name() = 0; protected:
BaseService() {} device_state &state;
public:
BaseService(device_state &state) : state(state) {}
virtual const char *Name() = 0;
virtual ipc::IpcResponse HandleSyncRequest(ipc::IpcRequest &request) = 0;
}; };
} }

View File

@ -6,21 +6,37 @@
namespace lightSwitch::kernel::svc { namespace lightSwitch::kernel::svc {
void SetHeapSize(device_state &state) { void SetHeapSize(device_state &state) {
uint64_t addr = state.this_process->MapPrivate(0, state.nce->GetRegister(regs::w1), {true, true, false}, memory::Region::heap); auto heap = state.this_process->MapPrivateRegion(0, state.nce->GetRegister(wreg::w1), {true, true, false}, Memory::Type::Heap, Memory::Region::heap);
state.nce->SetRegister(regs::w0, constant::status::success); state.nce->SetRegister(wreg::w0, constant::status::success);
state.nce->SetRegister(regs::x1, addr); state.nce->SetRegister(xreg::x1, heap->address);
state.logger->Write(Logger::DEBUG, "Heap size was set to 0x{:X}", state.nce->GetRegister(wreg::w1));
}
void QueryMemory(device_state &state) {
Memory::MemoryInfo mem_inf;
u64 addr = state.nce->GetRegister(xreg::x2);
if (state.nce->memory_map.count(addr)) {
mem_inf = state.nce->memory_map.at(addr)->GetInfo(state.this_process->main_thread);
} else if (state.this_process->memory_map.count(addr)) {
mem_inf = state.this_process->memory_map.at(addr)->GetInfo();
} else {
state.nce->SetRegister(wreg::w0, constant::status::inv_address);
return;
}
state.this_process->WriteMemory<Memory::MemoryInfo>(mem_inf, state.nce->GetRegister(xreg::x0));
state.nce->SetRegister(wreg::w0, constant::status::success);
} }
void CreateThread(device_state &state) { void CreateThread(device_state &state) {
// TODO: Check if the values supplied by the process are actually valid & Support Core Mask potentially ? // 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))); auto thread = state.this_process->CreateThread(state.nce->GetRegister(xreg::x1), state.nce->GetRegister(xreg::x2), state.nce->GetRegister(xreg::x3), static_cast<u8>(state.nce->GetRegister(wreg::w4)));
state.nce->SetRegister(regs::w0, constant::status::success); state.nce->SetRegister(wreg::w0, constant::status::success);
state.nce->SetRegister(regs::w1, thread->handle); state.nce->SetRegister(wreg::w1, thread->handle);
} }
void StartThread(device_state &state) { void StartThread(device_state &state) {
auto& object = state.this_process->handle_table.at(static_cast<const unsigned int &>(state.nce->GetRegister(regs::w0))); auto &object = state.this_process->handle_table.at(static_cast<const unsigned int &>(state.nce->GetRegister(wreg::w0)));
if(object->type==type::KObjectType::KThread) { if (object->type == type::KObjectType::KThread) {
std::static_pointer_cast<type::KThread>(object)->Start(); std::static_pointer_cast<type::KThread>(object)->Start();
} else } else
throw exception("StartThread was called on a non-KThread object"); throw exception("StartThread was called on a non-KThread object");
@ -31,103 +47,125 @@ namespace lightSwitch::kernel::svc {
} }
void GetThreadPriority(device_state &state) { void GetThreadPriority(device_state &state) {
auto& object = state.this_process->handle_table.at(static_cast<const unsigned int &>(state.nce->GetRegister(regs::w0))); auto &object = state.this_process->handle_table.at(static_cast<const unsigned int &>(state.nce->GetRegister(wreg::w0)));
if(object->type==type::KObjectType::KThread) { if (object->type == type::KObjectType::KThread) {
state.nce->SetRegister(regs::w0, constant::status::success); state.nce->SetRegister(wreg::w0, constant::status::success);
state.nce->SetRegister(regs::w1, std::static_pointer_cast<type::KThread>(object)->priority); state.nce->SetRegister(wreg::w1, std::static_pointer_cast<type::KThread>(object)->priority);
} else } else
throw exception("GetThreadPriority was called on a non-KThread object"); throw exception("GetThreadPriority was called on a non-KThread object");
} }
void SetThreadPriority(device_state &state) { void SetThreadPriority(device_state &state) {
auto& object = state.this_process->handle_table.at(static_cast<const unsigned int &>(state.nce->GetRegister(regs::w0))); auto &object = state.this_process->handle_table.at(static_cast<const unsigned int &>(state.nce->GetRegister(wreg::w0)));
if(object->type==type::KObjectType::KThread) { if (object->type == type::KObjectType::KThread) {
std::static_pointer_cast<type::KThread>(object)->Start(); std::static_pointer_cast<type::KThread>(object)->Start();
} else } else
throw exception("SetThreadPriority was called on a non-KThread object"); throw exception("SetThreadPriority was called on a non-KThread object");
} }
void CloseHandle(device_state &state) { void CloseHandle(device_state &state) {
auto& object = state.this_process->handle_table.at(static_cast<const unsigned int &>(state.nce->GetRegister(regs::w0))); auto &object = state.this_process->handle_table.at(static_cast<const unsigned int &>(state.nce->GetRegister(wreg::w0)));
switch (object->type) { switch (object->type) {
case(type::KObjectType::KThread): case (type::KObjectType::KThread):
state.os->KillThread(std::static_pointer_cast<type::KThread>(object)->pid); state.os->KillThread(std::static_pointer_cast<type::KThread>(object)->pid);
break; break;
case(type::KObjectType::KProcess): case (type::KObjectType::KProcess):
state.os->KillThread(std::static_pointer_cast<type::KProcess>(object)->main_thread); state.os->KillThread(std::static_pointer_cast<type::KProcess>(object)->main_thread);
break; break;
default:
state.nce->SetRegister(wreg::w0, constant::status::inv_handle);
return;
} }
state.nce->SetRegister(regs::w0, constant::status::success); state.nce->SetRegister(wreg::w0, constant::status::success);
} }
void ConnectToNamedPort(device_state &state) { void ConnectToNamedPort(device_state &state) {
char port[constant::port_size]{0}; char port[constant::port_size]{0};
state.os->this_process->ReadMemory(port, state.nce->GetRegister(regs::x1), constant::port_size); state.os->this_process->ReadMemory(port, state.nce->GetRegister(xreg::x1), constant::port_size);
if (std::strcmp(port, "sm:") == 0) if (std::strcmp(port, "sm:") == 0)
state.nce->SetRegister(regs::w1, constant::sm_handle); state.nce->SetRegister(wreg::w1, constant::sm_handle);
else else
throw exception(fmt::format("svcConnectToNamedPort tried connecting to invalid port: \"{}\"", port)); throw exception(fmt::format("svcConnectToNamedPort tried connecting to invalid port: \"{}\"", port));
state.nce->SetRegister(regs::w0, constant::status::success); state.nce->SetRegister(wreg::w0, constant::status::success);
} }
void SendSyncRequest(device_state &state) { void SendSyncRequest(device_state &state) {
state.logger->Write(Logger::DEBUG, "svcSendSyncRequest called for handle 0x{:X}.", state.nce->GetRegister(regs::x0)); state.logger->Write(Logger::DEBUG, "----------------------------svcSendSyncRequest Start-----------------------");
uint8_t tls[constant::tls_ipc_size]; state.logger->Write(Logger::DEBUG, "svcSendSyncRequest called for handle 0x{:X}.", state.nce->GetRegister(xreg::x0));
state.os->this_process->ReadMemory(&tls, state.this_thread->tls, constant::tls_ipc_size); state.os->IpcHandler(static_cast<handle_t>(state.nce->GetRegister(xreg::x0)));
ipc::IpcRequest request(tls, state); state.nce->SetRegister(wreg::w0, constant::status::success);
state.os->IpcHandler(request); state.nce->SetRegister(wreg::w19, constant::status::success);
state.logger->Write(Logger::DEBUG, "----------------------------svcSendSyncRequest End-------------------------");
} }
void OutputDebugString(device_state &state) { void OutputDebugString(device_state &state) {
std::string debug(state.nce->GetRegister(regs::x1), '\0'); std::string debug(state.nce->GetRegister(xreg::x1), '\0');
state.os->this_process->ReadMemory((void *) debug.data(), state.nce->GetRegister(regs::x0), state.nce->GetRegister(regs::x1)); state.os->this_process->ReadMemory((void *) debug.data(), state.nce->GetRegister(xreg::x0), state.nce->GetRegister(xreg::x1));
state.logger->Write(Logger::INFO, "svcOutputDebugString: {}", debug.c_str()); state.logger->Write(Logger::INFO, "svcOutputDebugString: {}", debug.c_str());
state.nce->SetRegister(regs::w0, 0); state.nce->SetRegister(wreg::w0, 0);
} }
void GetInfo(device_state &state) { 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)); state.logger->Write(Logger::DEBUG, "svcGetInfo called with ID0: {}, ID1: {}", state.nce->GetRegister(wreg::w1), state.nce->GetRegister(xreg::x3));
switch (state.nce->GetRegister(regs::w1)) { switch (state.nce->GetRegister(wreg::w1)) {
case constant::infoState::AllowedCpuIdBitmask: case constant::infoState::AllowedCpuIdBitmask:
case constant::infoState::AllowedThreadPriorityMask: case constant::infoState::AllowedThreadPriorityMask:
case constant::infoState::IsCurrentProcessBeingDebugged: case constant::infoState::IsCurrentProcessBeingDebugged:
case constant::infoState::TitleId: case constant::infoState::TitleId:
case constant::infoState::PrivilegedProcessId: case constant::infoState::PrivilegedProcessId:
state.nce->SetRegister(regs::x1, 0); state.nce->SetRegister(xreg::x1, 0);
break;
case constant::infoState::AliasRegionBaseAddr:
state.nce->SetRegister(xreg::x1, constant::map_addr);
break;
case constant::infoState::AliasRegionSize:
state.nce->SetRegister(xreg::x1, constant::map_size);
break; break;
case constant::infoState::HeapRegionBaseAddr: case constant::infoState::HeapRegionBaseAddr:
state.nce->SetRegister(regs::x1, state.os->this_process->memory_map.at(memory::Region::heap).address); state.nce->SetRegister(xreg::x1, state.os->this_process->memory_region_map.at(Memory::Region::heap)->address);
break; break;
case constant::infoState::HeapRegionSize: case constant::infoState::HeapRegionSize:
state.nce->SetRegister(regs::x1, state.os->this_process->memory_map.at(memory::Region::heap).size); state.nce->SetRegister(xreg::x1, state.os->this_process->memory_region_map.at(Memory::Region::heap)->size);
break;
case constant::infoState::TotalMemoryAvailable:
state.nce->SetRegister(xreg::x1, constant::total_phy_mem);
break;
case constant::infoState::TotalMemoryUsage:
state.nce->SetRegister(xreg::x1, state.os->this_process->memory_region_map.at(Memory::Region::heap)->address + state.this_process->main_thread_stack_sz + state.nce->GetSharedSize());
break; break;
case constant::infoState::AddressSpaceBaseAddr: case constant::infoState::AddressSpaceBaseAddr:
state.nce->SetRegister(regs::x1, constant::base_addr); state.nce->SetRegister(xreg::x1, constant::base_addr);
break; break;
case constant::infoState::AddressSpaceSize: case constant::infoState::AddressSpaceSize:
state.nce->SetRegister(regs::x1, constant::base_size); state.nce->SetRegister(xreg::x1, constant::base_size);
break;
case constant::infoState::StackRegionBaseAddr:
state.nce->SetRegister(xreg::x1, state.this_thread->stack_top);
break;
case constant::infoState::StackRegionSize:
state.nce->SetRegister(xreg::x1, state.this_process->main_thread_stack_sz);
break; break;
case constant::infoState::PersonalMmHeapSize: case constant::infoState::PersonalMmHeapSize:
state.nce->SetRegister(regs::x1, constant::total_phy_mem); state.nce->SetRegister(xreg::x1, constant::total_phy_mem);
break; break;
case constant::infoState::PersonalMmHeapUsage: 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); state.nce->SetRegister(xreg::x1, state.os->this_process->memory_region_map.at(Memory::Region::heap)->address + state.this_process->main_thread_stack_sz);
break; break;
case constant::infoState::TotalMemoryAvailableWithoutMmHeap: case constant::infoState::TotalMemoryAvailableWithoutMmHeap:
state.nce->SetRegister(regs::x1, constant::total_phy_mem); // TODO: NPDM specifies SystemResourceSize, subtract that from this state.nce->SetRegister(xreg::x1, constant::total_phy_mem); // TODO: NPDM specifies SystemResourceSize, subtract that from this
break; break;
case constant::infoState::TotalMemoryUsedWithoutMmHeap: 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 state.nce->SetRegister(xreg::x1, state.os->this_process->memory_region_map.at(Memory::Region::heap)->address + state.this_process->main_thread_stack_sz); // TODO: Same as above
break; break;
case constant::infoState::UserExceptionContextAddr: case constant::infoState::UserExceptionContextAddr:
state.nce->SetRegister(regs::x1, state.this_process->tls_pages[0]->Get(0)); state.nce->SetRegister(xreg::x1, state.this_process->tls_pages[0]->Get(0));
break; break;
default: default:
state.logger->Write(Logger::WARN, "Unimplemented svcGetInfo with ID0: {}, ID1: {}", state.nce->GetRegister(regs::w1), state.nce->GetRegister(regs::x3)); state.logger->Write(Logger::WARN, "Unimplemented svcGetInfo with ID0: {}, ID1: {}", state.nce->GetRegister(wreg::w1), state.nce->GetRegister(xreg::x3));
state.nce->SetRegister(regs::w0, constant::status::unimpl); state.nce->SetRegister(wreg::w0, constant::status::unimpl);
return; return;
} }
state.nce->SetRegister(regs::w0, constant::status::success); state.nce->SetRegister(wreg::w0, constant::status::success);
} }
void ExitProcess(device_state &state) { void ExitProcess(device_state &state) {

View File

@ -4,116 +4,119 @@
#include "../common.h" #include "../common.h"
#include "switch/os.h" #include "switch/os.h"
namespace lightSwitch::constant { namespace lightSwitch {
namespace infoState { namespace constant::infoState {
// 1.0.0+ // 1.0.0+
constexpr uint8_t AllowedCpuIdBitmask = 0x0; constexpr u8 AllowedCpuIdBitmask = 0x0;
constexpr uint8_t AllowedThreadPriorityMask = 0x1; constexpr u8 AllowedThreadPriorityMask = 0x1;
constexpr uint8_t AliasRegionBaseAddr = 0x2; constexpr u8 AliasRegionBaseAddr = 0x2;
constexpr uint8_t AliasRegionSize = 0x3; constexpr u8 AliasRegionSize = 0x3;
constexpr uint8_t HeapRegionBaseAddr = 0x4; constexpr u8 HeapRegionBaseAddr = 0x4;
constexpr uint8_t HeapRegionSize = 0x5; constexpr u8 HeapRegionSize = 0x5;
constexpr uint8_t TotalMemoryAvailable = 0x6; constexpr u8 TotalMemoryAvailable = 0x6;
constexpr uint8_t TotalMemoryUsage = 0x7; constexpr u8 TotalMemoryUsage = 0x7;
constexpr uint8_t IsCurrentProcessBeingDebugged = 0x8; constexpr u8 IsCurrentProcessBeingDebugged = 0x8;
constexpr uint8_t ResourceLimit = 0x9; constexpr u8 ResourceLimit = 0x9;
constexpr uint8_t IdleTickCount = 0xA; constexpr u8 IdleTickCount = 0xA;
constexpr uint8_t RandomEntropy = 0xB; constexpr u8 RandomEntropy = 0xB;
// 2.0.0+ // 2.0.0+
constexpr uint8_t AddressSpaceBaseAddr = 0xC; constexpr u8 AddressSpaceBaseAddr = 0xC;
constexpr uint8_t AddressSpaceSize = 0xD; constexpr u8 AddressSpaceSize = 0xD;
constexpr uint8_t StackRegionBaseAddr = 0xE; constexpr u8 StackRegionBaseAddr = 0xE;
constexpr uint8_t StackRegionSize = 0xF; constexpr u8 StackRegionSize = 0xF;
// 3.0.0+ // 3.0.0+
constexpr uint8_t PersonalMmHeapSize = 0x10; constexpr u8 PersonalMmHeapSize = 0x10;
constexpr uint8_t PersonalMmHeapUsage = 0x11; constexpr u8 PersonalMmHeapUsage = 0x11;
constexpr uint8_t TitleId = 0x12; constexpr u8 TitleId = 0x12;
// 4.0.0+ // 4.0.0+
constexpr uint8_t PrivilegedProcessId = 0x13; constexpr u8 PrivilegedProcessId = 0x13;
// 5.0.0+ // 5.0.0+
constexpr uint8_t UserExceptionContextAddr = 0x14; constexpr u8 UserExceptionContextAddr = 0x14;
// 6.0.0+ // 6.0.0+
constexpr uint8_t TotalMemoryAvailableWithoutMmHeap = 0x15; constexpr u8 TotalMemoryAvailableWithoutMmHeap = 0x15;
constexpr uint8_t TotalMemoryUsedWithoutMmHeap = 0x16; constexpr u8 TotalMemoryUsedWithoutMmHeap = 0x16;
}; };
namespace status { namespace kernel::svc {
constexpr uint32_t success = 0x0; //!< "Success" namespace structs {
constexpr uint32_t unimpl = 0x177202; //!< "Unimplemented behaviour"
}
};
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) * Set the process heap to a given size (https://switchbrew.org/wiki/SVC#svcSetHeapSize)
*/ */
void ExitProcess(device_state &state); void SetHeapSize(device_state &state);
/** /**
* Create a thread in the current process (https://switchbrew.org/wiki/SVC#svcCreateThread) * Query information about an address. Will always fetch the lowest page-aligned mapping that contains the provided address (https://switchbrew.org/wiki/SVC#svcQueryMemory)
*/ */
void CreateThread(device_state &state); void QueryMemory(device_state &state);
/** /**
* Starts the thread for the provided handle (https://switchbrew.org/wiki/SVC#svcStartThread) * Exits the current process (https://switchbrew.org/wiki/SVC#svcExitProcess)
*/ */
void StartThread(device_state &state); void ExitProcess(device_state &state);
/** /**
* Exits the current thread (https://switchbrew.org/wiki/SVC#svcExitThread) * Create a thread in the current process (https://switchbrew.org/wiki/SVC#svcCreateThread)
*/ */
void ExitThread(device_state &state); void CreateThread(device_state &state);
/** /**
* Get priority of provided thread handle (https://switchbrew.org/wiki/SVC#svcGetThreadPriority) * Starts the thread for the provided handle (https://switchbrew.org/wiki/SVC#svcStartThread)
*/ */
void GetThreadPriority(device_state &state); void StartThread(device_state &state);
/** /**
* Set priority of provided thread handle (https://switchbrew.org/wiki/SVC#svcSetThreadPriority) * Exits the current thread (https://switchbrew.org/wiki/SVC#svcExitThread)
*/ */
void SetThreadPriority(device_state &state); void ExitThread(device_state &state);
/** /**
* Closes the specified handle * Get priority of provided thread handle (https://switchbrew.org/wiki/SVC#svcGetThreadPriority)
*/ */
void CloseHandle(device_state &state); void GetThreadPriority(device_state &state);
/** /**
* Connects to a named IPC port * Set priority of provided thread handle (https://switchbrew.org/wiki/SVC#svcSetThreadPriority)
*/ */
void ConnectToNamedPort(device_state &state); void SetThreadPriority(device_state &state);
/** /**
* Send a synchronous IPC request to a service * Closes the specified handle
*/ */
void SendSyncRequest(device_state &state); void CloseHandle(device_state &state);
/** /**
* Outputs a debug string * Connects to a named IPC port
*/ */
void OutputDebugString(device_state &state); void ConnectToNamedPort(device_state &state);
/** /**
* Retrieves a piece of information (https://switchbrew.org/wiki/SVC#svcGetInfo) * Send a synchronous IPC request to a service
*/ */
void GetInfo(device_state &state); void SendSyncRequest(device_state &state);
/** /**
* The SVC Table maps all SVCs to their corresponding functions * Outputs a debug string
*/ */
void static (*svcTable[0x80])(device_state &) = { 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 (Does not exist) nullptr, // 0x00 (Does not exist)
SetHeapSize, // 0x01 SetHeapSize, // 0x01
nullptr, // 0x02 nullptr, // 0x02
nullptr, // 0x03 nullptr, // 0x03
nullptr, // 0x04 nullptr, // 0x04
nullptr, // 0x05 nullptr, // 0x05
nullptr, // 0x06 QueryMemory, // 0x06
ExitProcess, // 0x07 ExitProcess, // 0x07
CreateThread, // 0x08 CreateThread, // 0x08
StartThread, // 0x09 StartThread, // 0x09
@ -235,5 +238,6 @@ namespace lightSwitch::kernel::svc {
nullptr, // 0x7d nullptr, // 0x7d
nullptr, // 0x7e nullptr, // 0x7e
nullptr // 0x7f nullptr // 0x7f
}; };
}
} }

View File

@ -4,12 +4,14 @@
namespace lightSwitch::kernel::type { namespace lightSwitch::kernel::type {
enum class KObjectType { enum class KObjectType {
KThread, KProcess KThread, KProcess, KSharedMemory
}; };
class KObject { class KObject {
public: public:
uint32_t handle; u32 handle;
KObjectType type; KObjectType type;
KObject(handle_t handle, KObjectType type) : handle(handle), type(type) {} KObject(handle_t handle, KObjectType type) : handle(handle), type(type) {}
}; };
} }

View File

@ -0,0 +1,76 @@
#include "KPrivateMemory.h"
#include "../../nce.h"
#include "../../os.h"
#include <android/sharedmem.h>
#include <fcntl.h>
#include <unistd.h>
namespace lightSwitch::kernel::type {
u64 MapPrivateFunc(u64 dst_address, u64 src_address, size_t size, u64 perms) {
dst_address = reinterpret_cast<u64>(mmap(reinterpret_cast<void *>(dst_address), size, static_cast<int>(perms), MAP_PRIVATE | MAP_ANONYMOUS | ((dst_address) ? MAP_FIXED : 0), -1, 0)); // NOLINT(hicpp-signed-bitwise)
if (src_address) {
memcpy(reinterpret_cast<void *>(dst_address), reinterpret_cast<const void *>(src_address), size);
mprotect(reinterpret_cast<void *>(src_address), size, PROT_NONE);
}
return dst_address;
}
KPrivateMemory::KPrivateMemory(const device_state &state, u64 dst_address, u64 src_address, size_t size, Memory::Permission permission, const Memory::Type type, pid_t owner_pid) : state(state), address(dst_address), size(size), permission(permission), type(type), owner_pid(owner_pid) {
user_pt_regs fregs = {0};
fregs.regs[0] = dst_address;
fregs.regs[1] = src_address;
fregs.regs[2] = size;
fregs.regs[3] = static_cast<u64>(permission.get());
state.nce->ExecuteFunction(reinterpret_cast<void *>(MapPrivateFunc), fregs, owner_pid);
if (reinterpret_cast<void *>(fregs.regs[0]) == MAP_FAILED)
throw exception("An error occurred while mapping private region in child process");
if (!this->address) this->address = fregs.regs[0];
}
u64 UnmapPrivateFunc(u64 address, size_t size) {
return static_cast<u64>(munmap(reinterpret_cast<void *>(address), size));
}
u64 RemapPrivateFunc(u64 address, size_t old_size, size_t size) {
return reinterpret_cast<u64>(mremap(reinterpret_cast<void *>(address), old_size, size, 0));
}
void KPrivateMemory::Resize(size_t new_size) {
user_pt_regs fregs = {0};
fregs.regs[0] = address;
fregs.regs[1] = size;
fregs.regs[2] = new_size;
state.nce->ExecuteFunction(reinterpret_cast<void *>(RemapPrivateFunc), fregs, owner_pid);
if (reinterpret_cast<void *>(fregs.regs[0]) == MAP_FAILED)
throw exception("An error occurred while remapping private region in child process");
size = new_size;
}
u64 UpdatePermissionPrivateFunc(u64 address, size_t size, u64 perms) {
return static_cast<u64>(mprotect(reinterpret_cast<void *>(address), size, static_cast<int>(perms)));
}
void KPrivateMemory::UpdatePermission(Memory::Permission new_perms) {
user_pt_regs fregs = {0};
fregs.regs[0] = address;
fregs.regs[1] = size;
fregs.regs[2] = static_cast<u64>(new_perms.get());
state.nce->ExecuteFunction(reinterpret_cast<void *>(UpdatePermissionPrivateFunc), fregs, owner_pid);
if (static_cast<int>(fregs.regs[0]) == -1)
throw exception("An error occurred while updating private region's permissions in child process");
permission = new_perms;
}
Memory::MemoryInfo KPrivateMemory::GetInfo() {
Memory::MemoryInfo info{};
info.base_address = address;
info.size = size;
info.type = static_cast<u64>(type);
info.memory_attribute.IsIpcLocked = (info.ipc_ref_count > 0);
info.memory_attribute.IsDeviceShared = (info.device_ref_count > 0);
info.perms = permission;
info.ipc_ref_count = ipc_ref_count;
info.device_ref_count = device_ref_count;
return info;
}
};

View File

@ -0,0 +1,50 @@
#pragma once
#include "../../memory.h"
#include "KObject.h"
namespace lightSwitch::kernel::type {
class KPrivateMemory {
private:
const device_state &state; //!< The state of the device
public:
u64 address; //!< The address of the allocated memory
size_t size; //!< The size of the allocated memory
u16 ipc_ref_count{}; //!< The amount of reference to this memory for IPC
u16 device_ref_count{}; //!< The amount of reference to this memory for IPC
Memory::Permission permission; //!< The amount of reference to this memory for IPC
const Memory::Type type; //!< The type of this memory allocation
const pid_t owner_pid; //!< The PID of the owner process
/**
* Constructor of a private memory object
* @param dst_address The address to map to (If NULL then an arbitrary address is picked)
* @param src_address The address to map from (If NULL then no copy is performed)
* @param size The size of the allocation
* @param permission The permissions for the memory
* @param owner_pid The PID of the owner process
*/
KPrivateMemory(const device_state &state, u64 dst_address, u64 src_address, size_t size, Memory::Permission permission, const Memory::Type type, const pid_t owner_pid);
/**
* 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 Resize(size_t new_size);
/**
* Updates the permissions of a chunk of mapped memory
* @param perms The new permissions to be set for the memory
*/
void UpdatePermission(Memory::Permission new_perms);
/**
* @param pid The PID of the requesting process
* @return A Memory::MemoryInfo struct based on attributes of the memory
*/
Memory::MemoryInfo GetInfo();
};
}

View File

@ -5,17 +5,17 @@
#include <utility> #include <utility>
namespace lightSwitch::kernel::type { namespace lightSwitch::kernel::type {
KProcess::tls_page_t::tls_page_t(uint64_t address) : address(address) {} KProcess::tls_page_t::tls_page_t(u64 address) : address(address) {}
uint64_t KProcess::tls_page_t::ReserveSlot() { u64 KProcess::tls_page_t::ReserveSlot() {
if (Full()) if (Full())
throw exception("Trying to get TLS slot from full page"); throw exception("Trying to get TLS slot from full page");
slot[index] = true; slot[index] = true;
return Get(index++); // ++ on right will cause increment after evaluation of expression return Get(index++); // ++ on right will cause increment after evaluation of expression
} }
uint64_t KProcess::tls_page_t::Get(uint8_t slot_no) { u64 KProcess::tls_page_t::Get(u8 slot_no) {
if(slot_no>=constant::tls_slots) if (slot_no >= constant::tls_slots)
throw exception("TLS slot is out of range"); throw exception("TLS slot is out of range");
return address + (constant::tls_slot_size * slot_no); return address + (constant::tls_slot_size * slot_no);
} }
@ -24,26 +24,31 @@ namespace lightSwitch::kernel::type {
return slot[constant::tls_slots - 1]; return slot[constant::tls_slots - 1];
} }
uint64_t KProcess::GetTLSSlot(bool init) { u64 KProcess::GetTLSSlot(bool init) {
if (!init) if (!init)
for (auto &tls_page: tls_pages) { for (auto &tls_page: tls_pages) {
if (!tls_page->Full()) if (!tls_page->Full())
return tls_page->ReserveSlot(); return tls_page->ReserveSlot();
} }
uint64_t address = MapPrivate(0, PAGE_SIZE, {true, true, false}, memory::Region::tls); auto tls_mem = std::make_shared<KPrivateMemory>(KPrivateMemory(state, 0, 0, PAGE_SIZE, {true, true, false}, Memory::Type::ThreadLocal, main_thread));
tls_pages.push_back(std::make_shared<tls_page_t>(address)); memory_map[tls_mem->address] = tls_mem;
tls_pages.push_back(std::make_shared<tls_page_t>(tls_mem->address));
auto &tls_page = tls_pages.back(); auto &tls_page = tls_pages.back();
if (init) if (init)
tls_page->ReserveSlot(); // User-mode exception handling tls_page->ReserveSlot(); // User-mode exception handling
return tls_page->ReserveSlot(); 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) { KProcess::KProcess(pid_t pid, u64 entry_point, u64 stack_base, u64 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; process_state = process_state_t::Created;
main_thread = pid; main_thread = pid;
state.nce->WaitRdy(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); 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])); NewHandle(std::static_pointer_cast<KObject>(thread_map[main_thread]));
MapPrivateRegion(0, constant::def_heap_size, {true, true, true}, Memory::Type::Heap, Memory::Region::heap);
for (auto &region : state.nce->memory_map) {
region.second->InitiateProcess(pid);
}
mem_fd = open(fmt::format("/proc/{}/mem", pid).c_str(), O_RDWR | O_CLOEXEC); // NOLINT(hicpp-signed-bitwise) 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)); if (mem_fd == -1) throw exception(fmt::format("Cannot open file descriptor to /proc/{}/mem", pid));
} }
@ -61,128 +66,36 @@ namespace lightSwitch::kernel::type {
return 0; return 0;
} }
uint64_t CreateThreadFunc(uint64_t stack_top) { u64 CreateThreadFunc(u64 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) 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); return static_cast<u64>(pid);
} }
std::shared_ptr<KThread> KProcess::CreateThread(uint64_t entry_point, uint64_t entry_arg, uint64_t stack_top, uint8_t priority) { std::shared_ptr<KThread> KProcess::CreateThread(u64 entry_point, u64 entry_arg, u64 stack_top, u8 priority) {
user_pt_regs fregs = {0}; user_pt_regs fregs = {0};
fregs.regs[0] = entry_point; fregs.regs[0] = entry_point;
fregs.regs[1] = stack_top; fregs.regs[1] = stack_top;
state.nce->ExecuteFunction((void *) CreateThreadFunc, fregs, main_thread); state.nce->ExecuteFunction((void *) CreateThreadFunc, fregs, main_thread);
if (fregs.regs[0]==-1) if (fregs.regs[0] == -1)
throw exception(fmt::format("Cannot create thread: Address: {}, Stack Top: {}", entry_point, stack_top)); 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); 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)); NewHandle(std::static_pointer_cast<KObject>(thread));
return thread; return thread;
} }
template<typename T> void KProcess::ReadMemory(void *destination, u64 offset, size_t size) const {
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); pread64(mem_fd, destination, size, offset);
} }
void KProcess::WriteMemory(void *source, uint64_t offset, size_t size) const { void KProcess::WriteMemory(void *source, u64 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); pwrite64(mem_fd, source, size, offset);
} }
uint64_t MapPrivateFunc(uint64_t address, size_t size, uint64_t perms) { std::shared_ptr<KPrivateMemory> KProcess::MapPrivateRegion(u64 address, size_t size, const Memory::Permission perms, const Memory::Type type, const Memory::Region region) {
int flags = MAP_PRIVATE | MAP_ANONYMOUS; // NOLINT(hicpp-signed-bitwise) auto item = std::make_shared<KPrivateMemory>(state, address, 0, size, perms, type, main_thread);
if (address) flags |= MAP_FIXED; // NOLINT(hicpp-signed-bitwise) memory_map[item->address] = item;
return reinterpret_cast<uint64_t>(mmap(reinterpret_cast<void *>(address), size, static_cast<int>(perms), flags, -1, 0)); memory_region_map[region] = item;
} return item;
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_t KProcess::NewHandle(std::shared_ptr<KObject> obj) {

View File

@ -1,13 +1,14 @@
#pragma once #pragma once
#include "KThread.h" #include "KThread.h"
#include "KPrivateMemory.h"
namespace lightSwitch::kernel::type { namespace lightSwitch::kernel::type {
/** /**
* The KProcess class is responsible for holding the state of a process * The KProcess class is responsible for holding the state of a process
*/ */
class KProcess : public KObject { class KProcess : public KObject {
private: private:
/** /**
* tls_page_t holds the status of a single TLS page (A page is 4096 bytes on ARMv8). * 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. * Each TLS page has 8 slots, each 0x200 (512) bytes in size.
@ -15,46 +16,51 @@ namespace lightSwitch::kernel::type {
* Read more about TLS here: https://switchbrew.org/wiki/Thread_Local_Storage * Read more about TLS here: https://switchbrew.org/wiki/Thread_Local_Storage
*/ */
struct tls_page_t { struct tls_page_t {
uint64_t address; //!< The address of the page allocated for TLS u64 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 u8 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 bool slot[constant::tls_slots]{0}; //!< An array of booleans denoting which TLS slots are reserved
/** /**
* @param address The address of the allocated page * @param address The address of the allocated page
*/ */
tls_page_t(uint64_t address); tls_page_t(u64 address);
/** /**
* Reserves a single 0x200 byte TLS slot * Reserves a single 0x200 byte TLS slot
* @return The address of the reserved slot * @return The address of the reserved slot
*/ */
uint64_t ReserveSlot(); u64 ReserveSlot();
/** /**
* Returns the address of a particular slot * Returns the address of a particular slot
* @param slot_no The number of the slot to be returned * @param slot_no The number of the slot to be returned
* @return The address of the specified slot * @return The address of the specified slot
*/ */
uint64_t Get(uint8_t slot_no); u64 Get(u8 slot_no);
/** /**
* @return If the whole page is full or not * @return If the whole page is full or not
*/ */
bool Full(); bool Full();
}; };
/** /**
* @param init If this initializes the first page (As the first TLS slot is reserved) * @param init If this initializes the first page (As the first TLS slot is reserved)
* @return The address of a free TLS slot * @return The address of a free TLS slot
*/ */
uint64_t GetTLSSlot(bool init); u64 GetTLSSlot(bool init);
int mem_fd; //!< The file descriptor to the memory of the process int mem_fd; //!< The file descriptor to the memory of the process
const device_state& state; //!< The state of the device const device_state &state; //!< The state of the device
public: public:
enum class process_state_t { Created, CreatedAttached, Started, Crashed, StartedAttached, Exiting, Exited, DebugSuspended } process_state; //!< The state of the process 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; //!< 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 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 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) 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<u64, std::shared_ptr<KPrivateMemory>> memory_map; //!< A mapping from every address to a shared pointer of it's corresponding KPrivateMemory
std::map<Memory::Region, std::shared_ptr<KPrivateMemory>> memory_region_map; //!< A mapping from every MemoryRegion to a shared pointer of it's corresponding KPrivateMemory
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<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::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 std::vector<std::shared_ptr<tls_page_t>> tls_pages; //!< A vector of all allocated TLS pages
@ -68,7 +74,7 @@ namespace lightSwitch::kernel::type {
* @param state The state of the device * @param state The state of the device
* @param handle A handle to the process, this isn't used if the kernel creates the process * @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); KProcess(pid_t pid, u64 entry_point, u64 stack_base, u64 stack_size, const device_state &state, handle_t handle = 0);
/** /**
* Close the file descriptor to the process's memory * Close the file descriptor to the process's memory
@ -83,7 +89,7 @@ namespace lightSwitch::kernel::type {
* @param priority The priority of the thread * @param priority The priority of the thread
* @return An instance of KThread class for the corresponding 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); std::shared_ptr<KThread> CreateThread(u64 entry_point, u64 entry_arg, u64 stack_top, u8 priority);
/** /**
* Returns an object of type T from process memory * Returns an object of type T from process memory
@ -91,8 +97,12 @@ namespace lightSwitch::kernel::type {
* @param address The address of the object * @param address The address of the object
* @return An object of type T with read data * @return An object of type T with read data
*/ */
template <typename T> template<typename T>
T ReadMemory(uint64_t address) const; T ReadMemory(u64 address) const {
T item{};
ReadMemory(&item, address, sizeof(T));
return item;
}
/** /**
* Writes an object of type T to process memory * Writes an object of type T to process memory
@ -101,7 +111,9 @@ namespace lightSwitch::kernel::type {
* @param address The address of the object * @param address The address of the object
*/ */
template<typename T> template<typename T>
void WriteMemory(T &item, uint64_t address) const; void WriteMemory(T &item, u64 address) const {
WriteMemory(&item, address, sizeof(T));
}
/** /**
* Read a piece of process memory * Read a piece of process memory
@ -109,7 +121,7 @@ namespace lightSwitch::kernel::type {
* @param offset The address to read from in process memory * @param offset The address to read from in process memory
* @param size The amount of memory to be read * @param size The amount of memory to be read
*/ */
void ReadMemory(void *destination, uint64_t offset, size_t size) const; void ReadMemory(void *destination, u64 offset, size_t size) const;
/** /**
* Write a piece of process memory * Write a piece of process memory
@ -117,69 +129,18 @@ namespace lightSwitch::kernel::type {
* @param offset The address to write to in process memory * @param offset The address to write to in process memory
* @param size The amount of memory to be written * @param size The amount of memory to be written
*/ */
void WriteMemory(void *source, uint64_t offset, size_t size) const; void WriteMemory(void *source, u64 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) * 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 address The address to map to (Can be 0 if address doesn't matter)
* @param size The size of the chunk of memory * @param size The size of the chunk of memory
* @param perms The permissions of the memory * @param perms The permissions of the memory
* @param type The type of the memory
* @param region The specific region this memory is mapped for * @param region The specific region this memory is mapped for
* @return The address of the mapped chunk (Use when address is 0) * @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); std::shared_ptr<KPrivateMemory> MapPrivateRegion(u64 address, size_t size, const Memory::Permission perms, const Memory::Type type, 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 * Creates a new handle to a KObject and adds it to the process handle_table

View File

@ -0,0 +1,119 @@
#include "KSharedMemory.h"
#include "../../nce.h"
#include "../../os.h"
#include <android/sharedmem.h>
#include <fcntl.h>
#include <unistd.h>
namespace lightSwitch::kernel::type {
u64 MapFunc(u64 address, size_t size, u64 perms, u64 fd) {
return reinterpret_cast<u64>(mmap(reinterpret_cast<void *>(address), size, static_cast<int>(perms), MAP_SHARED | ((address) ? MAP_FIXED : 0), static_cast<int>(fd), 0)); // NOLINT(hicpp-signed-bitwise)
}
KSharedMemory::KSharedMemory(const device_state &state, size_t size, const Memory::Permission local_permission, const Memory::Permission remote_permission, Memory::Type type, handle_t handle, pid_t owner_pid) : state(state), size(size), local_permission(local_permission), remote_permission(remote_permission), type(type), owner_pid(owner_pid), KObject(handle, KObjectType::KSharedMemory) {
fd = ASharedMemory_create("", size);
}
void KSharedMemory::Map(u64 address) {
this->address = address;
for (auto process : state.os->process_vec) {
user_pt_regs fregs = {0};
fregs.regs[0] = this->address;
fregs.regs[1] = size;
if (process == owner_pid)
fregs.regs[2] = static_cast<u64 >(local_permission.get());
else
fregs.regs[2] = static_cast<u64>(remote_permission.get());
fregs.regs[3] = static_cast<u64>(fd);
state.nce->ExecuteFunction(reinterpret_cast<void *>(MapFunc), fregs, process);
if (reinterpret_cast<void *>(fregs.regs[0]) == MAP_FAILED)
throw exception("An error occurred while mapping shared region in child process");
if (!this->address) this->address = fregs.regs[0];
}
this->address = MapFunc(this->address, size, static_cast<u64>(owner_pid ? remote_permission.get() : local_permission.get()), static_cast<u64>(fd));
if (this->address == reinterpret_cast<u64>(MAP_FAILED)) // NOLINT(hicpp-signed-bitwise)
throw exception(fmt::format("An occurred while mapping shared region: {}", strerror(errno)));
}
u64 UnmapFunc(u64 address, size_t size) {
return static_cast<u64>(munmap(reinterpret_cast<void *>(address), size));
}
KSharedMemory::~KSharedMemory() {
for (auto process : state.os->process_vec) {
user_pt_regs fregs = {0};
fregs.regs[0] = address;
fregs.regs[1] = size;
state.nce->ExecuteFunction(reinterpret_cast<void *>(UnmapFunc), fregs, process);
}
UnmapFunc(address, size);
close(fd);
}
u64 RemapFunc(u64 address, size_t old_size, size_t size) {
return reinterpret_cast<u64>(mremap(reinterpret_cast<void *>(address), old_size, size, 0));
}
void KSharedMemory::Resize(size_t new_size) {
for (auto process : state.os->process_vec) {
user_pt_regs fregs = {0};
fregs.regs[0] = address;
fregs.regs[1] = size;
fregs.regs[2] = new_size;
state.nce->ExecuteFunction(reinterpret_cast<void *>(RemapFunc), fregs, process);
if (reinterpret_cast<void *>(fregs.regs[0]) == MAP_FAILED)
throw exception("An error occurred while remapping shared region in child process");
}
if (RemapFunc(address, size, new_size) == reinterpret_cast<u64>(MAP_FAILED))
throw exception(fmt::format("An occurred while remapping shared region: {}", strerror(errno)));
size = new_size;
}
u64 UpdatePermissionFunc(u64 address, size_t size, u64 perms) {
return static_cast<u64>(mprotect(reinterpret_cast<void *>(address), size, static_cast<int>(perms)));
}
void KSharedMemory::UpdatePermission(bool local, Memory::Permission new_perms) {
for (auto process : state.os->process_vec) {
if ((local && process == owner_pid) || (!local && process != owner_pid)) {
user_pt_regs fregs = {0};
fregs.regs[0] = address;
fregs.regs[1] = size;
fregs.regs[2] = static_cast<u64>(new_perms.get());
state.nce->ExecuteFunction(reinterpret_cast<void *>(UpdatePermissionFunc), fregs, process);
if (static_cast<int>(fregs.regs[0]) == -1)
throw exception("An error occurred while updating shared region's permissions in child process");
}
}
if ((local && owner_pid == 0) || (!local && owner_pid != 0))
if (mprotect(reinterpret_cast<void *>(address), size, new_perms.get()) == -1)
throw exception(fmt::format("An occurred while updating shared region's permissions: {}", strerror(errno)));
if (local)
local_permission = new_perms;
else
remote_permission = new_perms;
}
void KSharedMemory::InitiateProcess(pid_t pid) {
user_pt_regs fregs = {0};
fregs.regs[0] = address;
fregs.regs[1] = size;
fregs.regs[2] = static_cast<u64>(remote_permission.get());
state.nce->ExecuteFunction(reinterpret_cast<void *>(UpdatePermissionFunc), fregs, pid);
if (static_cast<int>(fregs.regs[0]) == -1)
throw exception("An error occurred while setting shared region's permissions in child process");
}
Memory::MemoryInfo KSharedMemory::GetInfo(pid_t pid) {
Memory::MemoryInfo info{};
info.base_address = address;
info.size = size;
info.type = static_cast<u64>(type);
info.memory_attribute.IsIpcLocked = (info.ipc_ref_count > 0);
info.memory_attribute.IsDeviceShared = (info.device_ref_count > 0);
info.perms = (pid == owner_pid) ? local_permission : remote_permission;
info.ipc_ref_count = ipc_ref_count;
info.device_ref_count = device_ref_count;
return info;
}
};

View File

@ -0,0 +1,67 @@
#pragma once
#include "../../memory.h"
#include "KObject.h"
namespace lightSwitch::kernel::type {
class KSharedMemory : public KObject {
private:
const device_state &state; //!< The state of the device
int fd; //!< A file descriptor to the underlying shared memory
public:
u64 address; //!< The address of the allocated memory
size_t size; //!< The size of the allocated memory
u16 ipc_ref_count{}; //!< The amount of reference to this memory for IPC
u16 device_ref_count{}; //!< The amount of reference to this memory for IPC
Memory::Permission local_permission; //!< The amount of reference to this memory for IPC
Memory::Permission remote_permission; //!< The permission of any process except the owner process
Memory::Type type; //!< The type of this memory allocation
pid_t owner_pid; //!< The PID of the owner process, 0 means memory is owned by kernel process
/**
* Constructor of a shared memory object
* @param size The size of the allocation
* @param local_permission The permission of the owner process
* @param remote_permission The permission of any process except the owner process
* @param owner_pid The PID of the owner process, 0 means memory is owned by kernel process
*/
KSharedMemory(const device_state &state, size_t size, const Memory::Permission local_permission, const Memory::Permission remote_permission, Memory::Type type, handle_t handle, pid_t owner_pid = 0);
/**
* Maps the shared memory at an address
* @param address The address to map to (If NULL an arbitrary address is picked)
*/
void Map(u64 address);
/**
* Destructor of shared memory, it deallocates the memory from all processes
*/
~KSharedMemory();
/**
* Resize a chunk of memory as to change the size occupied by it
* @param new_size The new size of the memory
*/
void Resize(size_t new_size);
/**
* Updates the permissions of a chunk of mapped memory
* @param local If true change local permissions else change remote permissions
* @param perms The new permissions to be set for the memory
*/
void UpdatePermission(bool local, Memory::Permission new_perms);
/**
* Initiates the instance of shared memory in a particular process
* @param pid The PID of the process
*/
void InitiateProcess(pid_t pid);
/**
* @param pid The PID of the requesting process
* @return A Memory::MemoryInfo struct based on attributes of the memory
*/
Memory::MemoryInfo GetInfo(pid_t pid);
};
}

View File

@ -4,7 +4,7 @@
#include "../../nce.h" #include "../../nce.h"
namespace lightSwitch::kernel::type { 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) { KThread::KThread(handle_t handle, pid_t pid, u64 entry_point, u64 entry_arg, u64 stack_top, u64 tls, u8 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); UpdatePriority(priority);
} }
@ -13,14 +13,14 @@ namespace lightSwitch::kernel::type {
} }
void KThread::Start() { void KThread::Start() {
if(pid==parent->main_thread) parent->process_state = KProcess::process_state_t::Started; if (pid == parent->main_thread) parent->process_state = KProcess::process_state_t::Started;
state.nce->StartProcess(entry_point, entry_arg, stack_top, handle, pid); state.nce->StartProcess(entry_point, entry_arg, stack_top, handle, pid);
} }
void KThread::UpdatePriority(uint8_t priority) { void KThread::UpdatePriority(u8 priority) {
this->priority = 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) 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))); // Resize range priority_nin (Nintendo Priority) to priority_an (Android Priority)
if(setpriority(PRIO_PROCESS, static_cast<id_t>(pid), li_priority) == -1) 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)); throw exception(fmt::format("Couldn't set process priority to {} for PID: {}", li_priority, pid));
} }
} }

View File

@ -7,18 +7,18 @@ namespace lightSwitch::kernel::type {
* KThread class is responsible for holding the state of a thread * KThread class is responsible for holding the state of a thread
*/ */
class KThread : public KObject { class KThread : public KObject {
private: private:
KProcess *parent; //!< The parent process of this thread KProcess *parent; //!< The parent process of this thread
const device_state& state; //!< The state of the device const device_state &state; //!< The state of the device
uint64_t entry_point; //!< The address to start execution at u64 entry_point; //!< The address to start execution at
uint64_t entry_arg; //!< An argument to pass to the process on entry u64 entry_arg; //!< An argument to pass to the process on entry
public: public:
handle_t handle; //!< The handle of the current thread in it's parent process's handle table 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]) 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) u64 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 u64 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 u8 priority; //!< Hold the priority of a thread in Nintendo format
/** /**
* @param handle The handle of the current thread * @param handle The handle of the current thread
@ -31,7 +31,7 @@ namespace lightSwitch::kernel::type {
* @param parent The parent process of this thread * @param parent The parent process of this thread
* @param arg An optional argument to pass to the process * @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); KThread(handle_t handle, pid_t pid, u64 entry_point, u64 entry_arg, u64 stack_top, u64 tls, u8 priority, KProcess *parent, const device_state &state);
/** /**
* Kills the thread and deallocates the memory allocated for stack. * Kills the thread and deallocates the memory allocated for stack.
@ -48,6 +48,6 @@ namespace lightSwitch::kernel::type {
* We rescale the priority from Nintendo scale to that of Android. * We rescale the priority from Nintendo scale to that of Android.
* @param priority The priority of the thread in Nintendo format * @param priority The priority of the thread in Nintendo format
*/ */
void UpdatePriority(uint8_t priority); void UpdatePriority(u8 priority);
}; };
} }

View File

@ -5,7 +5,7 @@
namespace lightSwitch::loader { namespace lightSwitch::loader {
class Loader { class Loader {
protected: protected:
std::string file_path; //!< The path to the ROM file std::string file_path; //!< The path to the ROM file
std::ifstream file; //!< An input stream from the file std::ifstream file; //!< An input stream from the file
@ -17,13 +17,13 @@ namespace lightSwitch::loader {
* @param size The amount to read in bytes * @param size The amount to read in bytes
*/ */
template<typename T> template<typename T>
void ReadOffset(T *output, uint32_t offset, size_t size) { void ReadOffset(T *output, u32 offset, size_t size) {
file.seekg(offset, std::ios_base::beg); file.seekg(offset, std::ios_base::beg);
file.read(reinterpret_cast<char *>(output), size); file.read(reinterpret_cast<char *>(output), size);
} }
public: public:
/** /**
* @param file_path_ The path to the ROM file * @param file_path_ The path to the ROM file
*/ */

View File

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

View File

@ -5,37 +5,37 @@
namespace lightSwitch::loader { namespace lightSwitch::loader {
class NroLoader : public Loader { class NroLoader : public Loader {
private: private:
struct NroSegmentHeader { struct NroSegmentHeader {
uint32_t offset; u32 offset;
uint32_t size; u32 size;
}; //!< The structure of a single Segment descriptor in the NRO's header }; //!< The structure of a single Segment descriptor in the NRO's header
struct NroHeader { struct NroHeader {
uint32_t : 32; u32 : 32;
uint32_t mod_offset; u32 mod_offset;
uint64_t : 64; u64 : 64;
uint32_t magic; u32 magic;
uint32_t version; u32 version;
uint32_t size; u32 size;
uint32_t flags; u32 flags;
NroSegmentHeader text; NroSegmentHeader text;
NroSegmentHeader ro; NroSegmentHeader ro;
NroSegmentHeader data; NroSegmentHeader data;
uint32_t bssSize; u32 bssSize;
uint32_t : 32; u32 : 32;
uint64_t build_id[4]; u64 build_id[4];
uint64_t : 64; u64 : 64;
NroSegmentHeader api_info; NroSegmentHeader api_info;
NroSegmentHeader dynstr; NroSegmentHeader dynstr;
NroSegmentHeader dynsym; NroSegmentHeader dynsym;
}; //!< A bit-field struct to read the header of an NRO directly }; //!< A bit-field struct to read the header of an NRO directly
public: public:
/** /**
* @param file_path The path to the ROM file * @param file_path The path to the ROM file
* @param state The state of the device * @param state The state of the device

View File

@ -0,0 +1,110 @@
#pragma once
#include "common.h"
namespace lightSwitch::Memory {
/**
* The Permission struct holds the permission of a particular chunk of memory
*/
struct Permission {
/**
* Initializes all values to false
*/
Permission() {
r = 0;
w = 0;
x = 0;
};
/**
* @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) {
r = read;
w = write;
x = execute;
};
/**
* Equality operator between two Permission objects
*/
bool operator==(const Permission &rhs) const { return (this->r == rhs.r && this->w == rhs.w && this->x == rhs.x); };
/**
* Inequality operator between two Permission objects
*/
bool operator!=(const Permission &rhs) const { return !operator==(rhs); };
/**
* @return The value of the permission struct in mmap(2) format
*/
int get() const {
int perm = 0;
if (r) perm |= PROT_READ;
if (w) perm |= PROT_WRITE;
if (x) perm |= PROT_EXEC;
return perm;
};
bool r : 1, w : 1, x : 1;
};
/**
* This holds certain attributes of a chunk of memory: https://switchbrew.org/wiki/SVC#MemoryAttribute
*/
struct MemoryAttribute {
bool IsBorrowed : 1;
bool IsIpcLocked : 1;
bool IsDeviceShared : 1;
bool IsUncached : 1;
};
/**
* This contains information about a chunk of memory: https://switchbrew.org/wiki/SVC#MemoryInfo
*/
struct MemoryInfo {
u64 base_address : 64;
u64 size : 64;
u64 type : 64;
MemoryAttribute memory_attribute;
Permission perms;
u32 ipc_ref_count : 32;
u32 device_ref_count : 32;
u32 : 32;
};
static_assert(sizeof(MemoryInfo) == 0x28);
enum class Type : u32 {
Unmapped = 0x00000000,
Io = 0x00002001,
Normal = 0x00042002,
CodeStatic = 0x00DC7E03,
CodeMutable = 0x03FEBD04,
Heap = 0x037EBD05,
SharedMemory = 0x00402006,
Alias = 0x00482907,
ModuleCodeStatic = 0x00DD7E08,
ModuleCodeMutable = 0x03FFBD09,
Ipc = 0x005C3C0A,
Stack = 0x005C3C0B,
ThreadLocal = 0x0040200C,
TransferMemoryIsolated = 0x015C3C0D,
TransferMemory = 0x005C380E,
ProcessMemory = 0x0040380F,
Reserved = 0x00000010,
NonSecureIpc = 0x005C3811,
NonDeviceIpc = 0x004C2812,
KernelStack = 0x00002013,
CodeReadOnly = 0x00402214,
CodeWritable = 0x00402015
};
/**
* Memory Regions that are mapped by the kernel
*/
enum class Region {
heap, text, rodata, data, bss
};
}

View File

@ -1,9 +1,6 @@
#include <sched.h> #include <sched.h>
#include <linux/uio.h> #include <linux/uio.h>
#include <linux/elf.h> #include <linux/elf.h>
#include <fcntl.h>
#include <unistd.h>
#include <android/sharedmem.h>
#include "os.h" #include "os.h"
#include "nce.h" #include "nce.h"
@ -22,46 +19,41 @@ namespace lightSwitch {
if (status == -1) throw exception(fmt::format("Cannot write registers, PID: {}, Error: {}", pid, strerror(errno))); 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 { instr::brk NCE::ReadBrk(u64 address, pid_t pid) const {
long status = ptrace(PTRACE_PEEKDATA, pid ? pid : curr_pid, address, NULL); 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))); 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)); return *(reinterpret_cast<instr::brk *>(&status));
} }
NCE::~NCE() { void NCE::Initialize(const device_state &state) {
for (auto&region : region_memory_map) { this->state = &state;
munmap(reinterpret_cast<void *>(region.second.address), region.second.size);
};
} }
void NCE::Execute(const device_state &state) { void NCE::Execute() {
this->state = const_cast<device_state *>(&state); int status = 0;
int status; while (!halt && !state->os->process_map.empty() && ((curr_pid = wait(&status)) != -1)) {
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) if (WIFSTOPPED(status) && (WSTOPSIG(status) == SIGTRAP || WSTOPSIG(status) == SIGSTOP)) { // NOLINT(hicpp-signed-bitwise)
auto& curr_regs = register_map[curr_pid]; auto &curr_regs = register_map[curr_pid];
ReadRegisters(curr_regs); ReadRegisters(curr_regs);
auto instr = ReadBrk(curr_regs.pc); auto instr = ReadBrk(curr_regs.pc);
if (instr.verify()) { 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. // 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) { if (instr.value <= constant::svc_last) {
state.os->SvcHandler(static_cast<uint16_t>(instr.value), curr_pid); state->os->SvcHandler(static_cast<u16>(instr.value), curr_pid);
} else if (instr.value > constant::svc_last && instr.value <= constant::svc_last + constant::num_regs) { } 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) // 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); SetRegister(static_cast<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)); 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) } else if (instr.value == constant::brk_rdy)
continue; continue;
else else
throw exception(fmt::format("Received unhandled BRK: 0x{:X}", static_cast<uint64_t>(instr.value))); throw exception(fmt::format("Received unhandled BRK: 0x{:X}", static_cast<u64>(instr.value)));
} }
curr_regs.pc += 4; // Increment program counter by a single instruction (32 bits) curr_regs.pc += 4; // Increment program counter by a single instruction (32 bits)
WriteRegisters(curr_regs); WriteRegisters(curr_regs);
} else { } else {
auto& curr_regs = register_map[curr_pid]; state->logger->Write(Logger::DEBUG, "Thread threw unknown signal, PID: {}, Stop Signal: {}", curr_pid, strsignal(WSTOPSIG(status))); // NOLINT(hicpp-signed-bitwise)
ReadRegisters(curr_regs); state->os->KillThread(curr_pid);
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(); ResumeProcess();
} }
@ -76,9 +68,9 @@ namespace lightSwitch {
bool was_running = PauseProcess(pid); bool was_running = PauseProcess(pid);
user_pt_regs backup_regs{}; user_pt_regs backup_regs{};
ReadRegisters(backup_regs, pid); ReadRegisters(backup_regs, pid);
func_regs.pc = reinterpret_cast<uint64_t>(func); func_regs.pc = reinterpret_cast<u64>(func);
func_regs.sp = backup_regs.sp; 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] func_regs.regs[static_cast<uint>(xreg::x30)] = reinterpret_cast<u64>(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); WriteRegisters(func_regs, pid);
ResumeProcess(pid); ResumeProcess(pid);
func_regs = WaitRdy(pid); func_regs = WaitRdy(pid);
@ -99,7 +91,7 @@ namespace lightSwitch {
WriteRegisters(regs, pid); WriteRegisters(regs, pid);
return regs; return regs;
} else } else
throw exception(fmt::format("An unknown BRK was hit during WaitRdy, PID: {}, BRK value: {}", pid, static_cast<uint64_t>(instr.value))); throw exception(fmt::format("An unknown BRK was hit during WaitRdy, PID: {}, BRK value: {}", pid, static_cast<u64>(instr.value)));
} else } 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) 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)
} }
@ -120,7 +112,7 @@ namespace lightSwitch {
if (status == -1) throw exception(fmt::format("Cannot resume process: {}, Error: {}", pid, strerror(errno))); 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 { void NCE::StartProcess(u64 entry_point, u64 entry_arg, u64 stack_top, u32 handle, pid_t pid) const {
user_pt_regs regs{0}; user_pt_regs regs{0};
regs.sp = stack_top; regs.sp = stack_top;
regs.pc = entry_point; regs.pc = entry_point;
@ -130,165 +122,59 @@ namespace lightSwitch {
ResumeProcess(pid); ResumeProcess(pid);
} }
uint64_t NCE::GetRegister(regs::xreg reg_id, pid_t pid) { u64 NCE::GetRegister(xreg reg_id, pid_t pid) {
return register_map.at(pid ? pid : curr_pid).regs[reg_id]; return register_map.at(pid ? pid : curr_pid).regs[static_cast<uint>(reg_id)];
} }
void NCE::SetRegister(regs::xreg reg_id, uint64_t value, pid_t pid) { void NCE::SetRegister(xreg reg_id, u64 value, pid_t pid) {
register_map.at(pid ? pid : curr_pid).regs[reg_id] = value; register_map.at(pid ? pid : curr_pid).regs[static_cast<uint>(reg_id)] = value;
} }
uint64_t NCE::GetRegister(regs::wreg reg_id, pid_t pid) { u64 NCE::GetRegister(wreg reg_id, pid_t pid) {
return (reinterpret_cast<uint32_t *>(&register_map.at(pid ? pid : curr_pid).regs))[reg_id * 2]; return (reinterpret_cast<u32 *>(&register_map.at(pid ? pid : curr_pid).regs))[static_cast<uint>(reg_id) * 2];
} }
void NCE::SetRegister(regs::wreg reg_id, uint32_t value, pid_t pid) { void NCE::SetRegister(wreg reg_id, u32 value, pid_t pid) {
(reinterpret_cast<uint32_t *>(&register_map.at(pid ? pid : curr_pid).regs))[reg_id * 2] = value; (reinterpret_cast<u32 *>(&register_map.at(pid ? pid : curr_pid).regs))[static_cast<uint>(reg_id) * 2] = value;
} }
uint64_t NCE::GetRegister(regs::sreg reg_id, pid_t pid) { u64 NCE::GetRegister(sreg reg_id, pid_t pid) {
pid = pid ? pid : curr_pid; pid = pid ? pid : curr_pid;
switch (reg_id) { switch (reg_id) {
case regs::pc: case sreg::pc:
return register_map.at(pid).pc; return register_map.at(pid).pc;
case regs::sp: case sreg::sp:
return register_map.at(pid).sp; return register_map.at(pid).sp;
case regs::pstate: case sreg::pstate:
return register_map.at(pid).pstate; return register_map.at(pid).pstate;
default:
return 0;
} }
} }
void NCE::SetRegister(regs::sreg reg_id, uint32_t value, pid_t pid) { void NCE::SetRegister(sreg reg_id, u32 value, pid_t pid) {
pid = pid ? pid : curr_pid; pid = pid ? pid : curr_pid;
switch (reg_id) { switch (reg_id) {
case regs::pc: case sreg::pc:
register_map.at(pid).pc = value; register_map.at(pid).pc = value;
case regs::sp: case sreg::sp:
register_map.at(pid).sp = value; register_map.at(pid).sp = value;
case regs::pstate: case sreg::pstate:
register_map.at(pid).pstate = value; register_map.at(pid).pstate = value;
} }
} }
uint64_t MapSharedFunc(uint64_t address, size_t size, uint64_t perms, uint64_t fd) { std::shared_ptr<kernel::type::KSharedMemory> NCE::MapSharedRegion(const u64 address, const size_t size, const Memory::Permission local_permission, const Memory::Permission remote_permission, const Memory::Type type, const Memory::Region region) {
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) auto item = std::make_shared<kernel::type::KSharedMemory>(*state, size, local_permission, remote_permission, type, 0, 0);
item->Map(address);
memory_map[item->address] = item;
memory_region_map[region] = item;
return item;
} }
uint64_t NCE::MapShared(uint64_t address, size_t size, const memory::Permission perms) { size_t NCE::GetSharedSize() {
int fd = -1; size_t shared_size = 0;
if(state && !state->os->process_map.empty()) { for (auto &region : memory_map) {
fd = ASharedMemory_create("", size); shared_size += region.second->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) return shared_size;
if (ptr == MAP_FAILED)
throw exception(fmt::format("An occurred while mapping shared region: {}", strerror(errno)));
addr_memory_map[address] = {address, size, perms, fd};
return reinterpret_cast<uint64_t>(ptr);
}
uint64_t NCE::MapShared(uint64_t address, size_t size, const memory::Permission perms, const memory::Region region) {
uint64_t addr = MapShared(address,size, perms); // TODO: Change MapShared return type to RegionData
region_memory_map[region] = {addr, size, perms};
return addr;
}
uint64_t RemapSharedFunc(uint64_t address, size_t old_size, size_t size) {
return reinterpret_cast<uint64_t>(mremap(reinterpret_cast<void *>(address), old_size, size, 0));
}
void NCE::RemapShared(uint64_t address, size_t old_size, size_t size) {
if(state && !state->os->process_map.empty()) {
for(auto& process : state->os->process_map) {
user_pt_regs fregs = {0};
fregs.regs[0] = address;
fregs.regs[1] = old_size;
fregs.regs[2] = size;
ExecuteFunction(reinterpret_cast<void *>(RemapSharedFunc), fregs, process.second->main_thread);
if (reinterpret_cast<void *>(fregs.regs[0]) == MAP_FAILED)
throw exception("An error occurred while remapping shared region in child process");
}
}
void *ptr = mremap(reinterpret_cast<void *>(address), old_size, size, 0);
if (ptr == MAP_FAILED)
throw exception(fmt::format("An occurred while remapping shared region: {}", strerror(errno)));
addr_memory_map.at(address).size = size;
}
void NCE::RemapShared(const memory::Region region, size_t size) {
memory::RegionData& region_data = region_memory_map.at(region);
RemapShared(region_data.address, region_data.size, size);
region_data.size = size;
}
uint64_t UpdatePermissionSharedFunc(uint64_t address, size_t size, uint64_t perms) {
return static_cast<uint64_t>(mprotect(reinterpret_cast<void *>(address), size, static_cast<int>(perms)));
}
void NCE::UpdatePermissionShared(uint64_t address, size_t size, const memory::Permission perms) {
if(state && !state->os->process_map.empty()) {
for(auto& process : state->os->process_map) {
user_pt_regs fregs = {0};
fregs.regs[0] = address;
fregs.regs[1] = size;
fregs.regs[2] = static_cast<uint64_t>(perms.get());
ExecuteFunction(reinterpret_cast<void *>(UpdatePermissionSharedFunc), fregs, process.second->main_thread);
if (static_cast<int>(fregs.regs[0]) == -1)
throw exception("An error occurred while updating shared region's permissions in child process");
}
}
if (mprotect(reinterpret_cast<void *>(address), size, perms.get()) == -1)
throw exception(fmt::format("An occurred while updating shared region's permissions: {}", strerror(errno)));
addr_memory_map.at(address).perms = perms;
}
void NCE::UpdatePermissionShared(const memory::Region region, const memory::Permission perms) {
memory::RegionData& region_data = region_memory_map.at(region);
if (region_data.perms != perms) {
UpdatePermissionShared(region_data.address, region_data.size, perms);
region_data.perms = perms;
}
}
uint64_t UnmapSharedFunc(uint64_t address, size_t size) {
return static_cast<uint64_t>(munmap(reinterpret_cast<void *>(address), size));
}
void NCE::UnmapShared(uint64_t address, size_t size) {
if(state && !state->os->process_map.empty()) {
for(auto& process : state->os->process_map) {
user_pt_regs fregs = {0};
fregs.regs[0] = address;
fregs.regs[1] = size;
ExecuteFunction(reinterpret_cast<void *>(UnmapSharedFunc), fregs, process.second->main_thread);
if (static_cast<int>(fregs.regs[0]) == -1)
throw exception("An error occurred while unmapping shared region in child process");
}
}
int err = munmap(reinterpret_cast<void *>(address), size);
if (err == -1)
throw exception(fmt::format("An occurred while unmapping shared region: {}", strerror(errno)));
if (addr_memory_map.at(address).fd!=-1) {
if (close(addr_memory_map.at(address).fd)==-1)
throw exception(fmt::format("An occurred while closing shared file descriptor: {}", strerror(errno)));
}
}
void NCE::UnmapShared(const memory::Region region) {
memory::RegionData& region_data = region_memory_map.at(region);
UnmapShared(region_data.address, region_data.size);
region_memory_map.erase(region);
} }
} }

View File

@ -6,49 +6,50 @@
#include <vector> #include <vector>
#include <unordered_map> #include <unordered_map>
#include "common.h" #include "common.h"
#include "kernel/types/KSharedMemory.h"
namespace lightSwitch { namespace lightSwitch {
class NCE { class NCE {
private: 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 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) 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; const device_state *state; //!< The state of the device
/** /**
* Reads process registers into the `registers` variable * Reads process registers into the `registers` variable
* @param registers A set of registers to fill with values from the process * @param registers A set of registers to fill with values from the process
* @param pid The PID of the process * @param pid The PID of the process
*/ */
void ReadRegisters(user_pt_regs &registers, pid_t pid=0) const; void ReadRegisters(user_pt_regs &registers, pid_t pid = 0) const;
/** /**
* Writes process registers from the `registers` variable * Writes process registers from the `registers` variable
* @param registers The registers to be written by the process * @param registers The registers to be written by the process
* @param pid The PID of the process * @param pid The PID of the process
*/ */
void WriteRegisters(user_pt_regs &registers, pid_t pid=0) const; void WriteRegisters(user_pt_regs &registers, pid_t pid = 0) const;
/** /**
* @param address The address of the BRK instruction * @param address The address of the BRK instruction
* @param pid The PID of the process * @param pid The PID of the process
* @return An instance of BRK with the corresponding values * @return An instance of BRK with the corresponding values
*/ */
instr::brk ReadBrk(uint64_t address, pid_t pid=0) const; instr::brk ReadBrk(u64 address, pid_t pid = 0) const;
public: 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<Memory::Region, std::shared_ptr<kernel::type::KSharedMemory>> memory_region_map; //!< A mapping from every Memory::Region to a shared pointer to it's corresponding kernel::type::KSharedMemory
std::map<uint64_t, memory::RegionData> addr_memory_map; //!< A mapping from every address to it's corresponding memory::RegionData std::map<u64, std::shared_ptr<kernel::type::KSharedMemory>> memory_map; //!< A mapping from every address to a shared pointer to it's corresponding kernel::type::KSharedMemory
/** /**
* Iterates over shared_memory_vec unmapping all memory * Initialize NCE by setting the device_state variable
* @param state The state of the device
*/ */
~NCE(); void Initialize(const device_state &state);
/** /**
* Start managing child processes * Start managing child processes
* @param state The state of the device
*/ */
void Execute(const device_state& state); void Execute();
/** /**
* Execute any arbitrary function on a particular child process * Execute any arbitrary function on a particular child process
@ -76,7 +77,7 @@ namespace lightSwitch {
* Resumes a particular process, does nothing if it was already running * Resumes a particular process, does nothing if it was already running
* @param pid The PID of the process * @param pid The PID of the process
*/ */
void ResumeProcess(pid_t pid=0) const; void ResumeProcess(pid_t pid = 0) const;
/** /**
* Starts a particular process, sets the registers to their expected values and jumps to address * Starts a particular process, sets the registers to their expected values and jumps to address
@ -86,7 +87,7 @@ namespace lightSwitch {
* @param handle The handle of the main thread (Set to value of 1st register) * @param handle The handle of the main thread (Set to value of 1st register)
* @param pid The PID of the process * @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; void StartProcess(u64 address, u64 entry_arg, u64 stack_top, u32 handle, pid_t pid) const;
/** /**
* Get the value of a Xn register * Get the value of a Xn register
@ -94,7 +95,7 @@ namespace lightSwitch {
* @param pid The PID of the process * @param pid The PID of the process
* @return The value of the register * @return The value of the register
*/ */
uint64_t GetRegister(regs::xreg reg_id, pid_t pid=0); u64 GetRegister(xreg reg_id, pid_t pid = 0);
/** /**
* Set the value of a Xn register * Set the value of a Xn register
@ -102,7 +103,7 @@ namespace lightSwitch {
* @param value The value to set * @param value The value to set
* @param pid The PID of the process * @param pid The PID of the process
*/ */
void SetRegister(regs::xreg reg_id, uint64_t value, pid_t pid=0); void SetRegister(xreg reg_id, u64 value, pid_t pid = 0);
/** /**
* Get the value of a Wn register * Get the value of a Wn register
@ -110,7 +111,7 @@ namespace lightSwitch {
* @param pid The PID of the process * @param pid The PID of the process
* @return The value in the register * @return The value in the register
*/ */
uint64_t GetRegister(regs::wreg reg_id, pid_t pid=0); u64 GetRegister(wreg reg_id, pid_t pid = 0);
/** /**
* Set the value of a Wn register * Set the value of a Wn register
@ -118,7 +119,7 @@ namespace lightSwitch {
* @param value The value to set * @param value The value to set
* @param pid The PID of the process * @param pid The PID of the process
*/ */
void SetRegister(regs::wreg reg_id, uint32_t value, pid_t pid=0); void SetRegister(wreg reg_id, u32 value, pid_t pid = 0);
/** /**
* Get the value of a special register * Get the value of a special register
@ -126,7 +127,7 @@ namespace lightSwitch {
* @param pid The PID of the process * @param pid The PID of the process
* @return The value in the register * @return The value in the register
*/ */
uint64_t GetRegister(regs::sreg reg_id, pid_t pid=0); u64 GetRegister(sreg reg_id, pid_t pid = 0);
/** /**
* Set the value of a special register * Set the value of a special register
@ -134,69 +135,22 @@ namespace lightSwitch {
* @param value The value to set * @param value The value to set
* @param pid The PID of the process * @param pid The PID of the process
*/ */
void SetRegister(regs::sreg reg_id, uint32_t value, pid_t pid=0); void SetRegister(sreg reg_id, u32 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 * Map a chunk of shared memory
* @param address The address to map to (Can be 0 if address doesn't matter) * @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 size The size of the chunk of memory
* @param perms The permissions of the memory * @param perms The permissions of the memory
* @param type The type of the memory
* @param region The specific region this memory is mapped for * @param region The specific region this memory is mapped for
* @return The address of the mapped chunk (Use when address is 0) * @return A shared pointer to the kernel::type::KSharedMemory object
*/ */
uint64_t MapShared(uint64_t address, size_t size, const memory::Permission perms, const memory::Region region); std::shared_ptr<kernel::type::KSharedMemory> MapSharedRegion(const u64 address, const size_t size, const Memory::Permission local_permission, const Memory::Permission remote_permission, const Memory::Type type, const Memory::Region region);
/** /**
* Remap a chunk of memory as to change the size occupied by it * @return The total size of allocated shared memory
* @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); size_t GetSharedSize();
/**
* Remap a chunk of memory as to change the size occupied by it
* @param region The region of memory that was mapped
* @param size The new size of the memory
*/
void RemapShared(memory::Region region, size_t size);
/**
* Updates the permissions of a chunk of mapped memory
* @param address The address of the mapped memory
* @param size The size of the mapped memory
* @param perms The new permissions to be set for the memory
*/
void UpdatePermissionShared(uint64_t address, size_t size, const memory::Permission perms);
/**
* Updates the permissions of a chunk of mapped memory
* @param region The region of memory that was mapped
* @param perms The new permissions to be set for the memory
*/
void UpdatePermissionShared(memory::Region region, memory::Permission perms);
/**
* Unmap a particular chunk of mapped memory
* @param address The address of the mapped memory
* @param size The size of the mapped memory
*/
void UnmapShared(uint64_t address, size_t size);
/**
* Unmap a particular chunk of mapped memory
* @param region The region of mapped memory
*/
void UnmapShared(const memory::Region region);
}; };
} }

View File

@ -9,15 +9,16 @@ 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) {} 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) { void OS::Execute(std::string rom_file) {
state.nce->Initialize(state);
std::string rom_ext = rom_file.substr(rom_file.find_last_of('.') + 1); 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); }); 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); if (rom_ext == "nro") loader::NroLoader loader(rom_file, state);
else throw exception("Unsupported ROM extension."); else throw exception("Unsupported ROM extension.");
auto main_process = CreateProcess(state.nce->region_memory_map.at(memory::Region::text).address, constant::def_stack_size); auto main_process = CreateProcess(state.nce->memory_region_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 main_process->thread_map[main_process->main_thread]->Start(); // The kernel itself is responsible for starting the main thread
state.nce->Execute(state); state.nce->Execute();
} }
/** /**
@ -29,9 +30,8 @@ namespace lightSwitch::kernel {
return 0; return 0;
} }
std::shared_ptr<type::KProcess> OS::CreateProcess(u64 address, size_t stack_size) {
std::shared_ptr<type::KProcess> OS::CreateProcess(uint64_t address, size_t stack_size) { auto *stack = static_cast<u8 *>(mmap(nullptr, stack_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_NORESERVE | MAP_ANONYMOUS | MAP_STACK, -1, 0)); // NOLINT(hicpp-signed-bitwise)
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) if (stack == MAP_FAILED)
throw exception("Failed to allocate stack memory"); throw exception("Failed to allocate stack memory");
if (mprotect(stack, PAGE_SIZE, PROT_NONE)) { if (mprotect(stack, PAGE_SIZE, PROT_NONE)) {
@ -39,22 +39,24 @@ namespace lightSwitch::kernel {
throw exception("Failed to create guard pages"); throw exception("Failed to create guard pages");
} }
pid_t pid = clone(&ExecuteChild, stack + stack_size, CLONE_FS | SIGCHLD, nullptr); // NOLINT(hicpp-signed-bitwise) 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))); 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); std::shared_ptr<type::KProcess> process = std::make_shared<kernel::type::KProcess>(pid, address, reinterpret_cast<u64>(stack), stack_size, state);
process_map[pid] = process; process_map[pid] = process;
process_vec.push_back(pid);
state.logger->Write(Logger::DEBUG, "Successfully created process with PID: {}", pid); state.logger->Write(Logger::DEBUG, "Successfully created process with PID: {}", pid);
return process; return process;
} }
void OS::KillThread(pid_t pid) { void OS::KillThread(pid_t pid) {
auto process = process_map.at(pid); auto process = process_map.at(pid);
if(process->main_thread==pid) { if (process->main_thread == pid) {
state.logger->Write(Logger::DEBUG, "Exiting process with PID: {}", pid); state.logger->Write(Logger::DEBUG, "Exiting process with PID: {}", pid);
// Erasing all shared_ptr instances to the process will call the destructor // 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 // 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) { for (auto&[key, value]: process->thread_map) {
process_map.erase(key); process_map.erase(key);
}; };
process_vec.erase(std::remove(process_vec.begin(), process_vec.end(), pid), process_vec.end());
} else { } else {
state.logger->Write(Logger::DEBUG, "Exiting thread with TID: {}", pid); state.logger->Write(Logger::DEBUG, "Exiting thread with TID: {}", pid);
process->handle_table.erase(process->thread_map[pid]->handle); process->handle_table.erase(process->thread_map[pid]->handle);
@ -63,7 +65,7 @@ namespace lightSwitch::kernel {
} }
} }
void OS::SvcHandler(uint16_t svc, pid_t pid) { void OS::SvcHandler(u16 svc, pid_t pid) {
this_process = process_map.at(pid); this_process = process_map.at(pid);
this_thread = this_process->thread_map.at(pid); this_thread = this_process->thread_map.at(pid);
if (svc::svcTable[svc]) { if (svc::svcTable[svc]) {
@ -74,17 +76,15 @@ namespace lightSwitch::kernel {
} }
} }
ipc::IpcResponse OS::IpcHandler(ipc::IpcRequest &request) { ipc::IpcResponse OS::IpcHandler(handle_t handle) {
ipc::IpcResponse response; ipc::IpcRequest request(false, state);
switch (request.req_info->type) { ipc::IpcResponse response(false, state);
case 4: // Request switch (request.header->type) {
response.SetError(0xDEADBEE5); case static_cast<u16>(ipc::CommandType::Request):
response.MoveHandle(0xBAADBEEF); case static_cast<u16>(ipc::CommandType::RequestWithContext):
response.MoveHandle(0xFACCF00D); throw exception("Services are in-progress");
response.Generate(state);
break;
default: default:
throw exception(fmt::format("Unimplemented IPC message type {0}", uint16_t(request.req_info->type))); throw exception(fmt::format("Unimplemented IPC message type {0}", u16(request.header->type)));
} }
return response; return response;
} }

View File

@ -12,11 +12,12 @@ namespace lightSwitch::kernel {
* The OS class manages the interaction between OS components and the underlying hardware in NCE * The OS class manages the interaction between OS components and the underlying hardware in NCE
*/ */
class OS { class OS {
private: private:
device_state state; //!< The state of the device device_state state; //!< The state of the device
public: public:
std::unordered_map<pid_t, std::shared_ptr<type::KProcess>> process_map; //!< The state of the device std::unordered_map<pid_t, std::shared_ptr<type::KProcess>> process_map; //!< A mapping from a process's PID to it's corresponding PID (Threads have their own PID too, so there are overlapping values)
std::vector<pid_t> process_vec; //!< A vector of all processes by their main thread's PID
std::shared_ptr<type::KProcess> this_process; //!< The corresponding KProcess object of the process that's called an SVC 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 std::shared_ptr<type::KThread> this_thread; //!< The corresponding KThread object of the thread that's called an SVC
@ -38,7 +39,7 @@ namespace lightSwitch::kernel {
* @param stack_size The size of the main stack * @param stack_size The size of the main stack
* @return An instance of the KProcess of the created process * @return An instance of the KProcess of the created process
*/ */
std::shared_ptr<type::KProcess> CreateProcess(uint64_t address, size_t stack_size); std::shared_ptr<type::KProcess> CreateProcess(u64 address, size_t stack_size);
/** /**
* Kill a particular thread * Kill a particular thread
@ -50,12 +51,12 @@ namespace lightSwitch::kernel {
* @param svc The ID of the SVC to be called * @param svc The ID of the SVC to be called
* @param pid The PID of the process/thread calling the SVC * @param pid The PID of the process/thread calling the SVC
*/ */
void SvcHandler(uint16_t svc, pid_t pid); void SvcHandler(u16 svc, pid_t pid);
/** /**
* @param req The IPC request to be handled * @param handle The handle of the object
* @return The corresponding response returned by a service * @return The corresponding response returned by a service
*/ */
ipc::IpcResponse IpcHandler(ipc::IpcRequest &req); ipc::IpcResponse IpcHandler(handle_t handle);
}; };
} }

View File

@ -12,6 +12,7 @@ import android.view.Window;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.RelativeLayout; import android.widget.RelativeLayout;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import java.io.File; import java.io.File;

View File

@ -8,7 +8,9 @@ import android.view.ViewGroup;
import android.widget.BaseAdapter; import android.widget.BaseAdapter;
import android.widget.Filter; import android.widget.Filter;
import android.widget.Filterable; import android.widget.Filterable;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import me.xdrop.fuzzywuzzy.FuzzySearch; import me.xdrop.fuzzywuzzy.FuzzySearch;
import me.xdrop.fuzzywuzzy.model.ExtractedResult; import me.xdrop.fuzzywuzzy.model.ExtractedResult;
@ -67,10 +69,10 @@ abstract class HeaderAdapter<ItemType extends BaseItem> extends BaseAdapter impl
header_array.add((String) item); header_array.add((String) item);
type_array_uf.add(new ContentType(header_array.size() - 1, ContentType.Header)); type_array_uf.add(new ContentType(header_array.size() - 1, ContentType.Header));
} }
if(search_term.length()!=0) if (search_term.length() != 0)
this.getFilter().filter(search_term); this.getFilter().filter(search_term);
else else
type_array=type_array_uf; type_array = type_array_uf;
} }
public void save(File file) throws IOException { public void save(File file) throws IOException {

View File

@ -9,20 +9,36 @@ import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.widget.ListView; import android.widget.ListView;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.SearchView; import androidx.appcompat.widget.SearchView;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import org.json.JSONObject; import org.json.JSONObject;
import java.io.*; import java.io.BufferedInputStream;
import java.net.HttpURLConnection; import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.URL; import java.net.URL;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.net.ssl.HttpsURLConnection;
import static java.lang.Thread.interrupted; import static java.lang.Thread.interrupted;
public class LogActivity extends AppCompatActivity { public class LogActivity extends AppCompatActivity {
@ -47,42 +63,42 @@ public class LogActivity extends AppCompatActivity {
try { try {
InputStream inputStream = new FileInputStream(log_file); InputStream inputStream = new FileInputStream(log_file);
reader = new BufferedReader(new InputStreamReader(inputStream)); reader = new BufferedReader(new InputStreamReader(inputStream));
thread = new Thread(new Runnable() { thread = new Thread(new Runnable() {
@Override @Override
public void run() { public void run() {
@SuppressWarnings("deprecation") // Required as FileObserver(File) is only on API level 29 also no AndroidX version present @SuppressWarnings("deprecation") // Required as FileObserver(File) is only on API level 29 also no AndroidX version present
FileObserver observer = new FileObserver(log_file.getPath()) { FileObserver observer = new FileObserver(log_file.getPath()) {
@Override @Override
public void onEvent(int event, String path) { public void onEvent(int event, String path) {
if (event == FileObserver.MODIFY) { if (event == FileObserver.MODIFY) {
try { try {
boolean done = false; boolean done = false;
while (!done) { while (!done) {
final String line = reader.readLine(); final String line = reader.readLine();
done = (line == null); done = (line == null);
if (!done) { if (!done) {
runOnUiThread(new Runnable() { runOnUiThread(new Runnable() {
@Override @Override
public void run() { public void run() {
adapter.add(line); adapter.add(line);
} }
}); });
}
} }
} catch (IOException e) {
Log.w("Logger", "IO Error during access of log file: " + e.getMessage());
Toast.makeText(getApplicationContext(), getString(R.string.io_error) + ": " + e.getMessage(), Toast.LENGTH_LONG).show();
} }
} catch (IOException e) {
Log.w("Logger", "IO Error during access of log file: " + e.getMessage());
Toast.makeText(getApplicationContext(), getString(R.string.io_error) + ": " + e.getMessage(), Toast.LENGTH_LONG).show();
} }
} }
} };
}; observer.onEvent(FileObserver.MODIFY, log_file.getPath());
observer.onEvent(FileObserver.MODIFY, log_file.getPath()); observer.startWatching();
observer.startWatching(); while (!interrupted()) ;
while (!interrupted()) ; observer.stopWatching();
observer.stopWatching(); }
} });
}); thread.start();
thread.start();
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
Log.w("Logger", "IO Error during access of log file: " + e.getMessage()); Log.w("Logger", "IO Error during access of log file: " + e.getMessage());
Toast.makeText(getApplicationContext(), getString(R.string.file_missing), Toast.LENGTH_LONG).show(); Toast.makeText(getApplicationContext(), getString(R.string.file_missing), Toast.LENGTH_LONG).show();
@ -129,10 +145,15 @@ public class LogActivity extends AppCompatActivity {
Thread share_thread = new Thread(new Runnable() { Thread share_thread = new Thread(new Runnable() {
@Override @Override
public void run() { public void run() {
HttpsURLConnection urlConnection = null;
try { try {
URL url = new URL("https://hastebin.com/documents"); URL url = new URL("https://hastebin.com/documents");
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); urlConnection = (HttpsURLConnection) url.openConnection();
urlConnection.setRequestMethod("POST"); urlConnection.setRequestMethod("POST");
urlConnection.setRequestProperty("Host", "hastebin.com");
urlConnection.setRequestProperty("Content-Type", "application/json; charset=utf-8");
urlConnection.setRequestProperty("Referer", "https://hastebin.com/");
urlConnection.setRequestProperty("Connection", "keep-alive");
OutputStream outputStream = new BufferedOutputStream(urlConnection.getOutputStream()); OutputStream outputStream = new BufferedOutputStream(urlConnection.getOutputStream());
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8)); BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8));
FileReader fileReader = new FileReader(log_file); FileReader fileReader = new FileReader(log_file);
@ -143,25 +164,34 @@ public class LogActivity extends AppCompatActivity {
bufferedWriter.flush(); bufferedWriter.flush();
bufferedWriter.close(); bufferedWriter.close();
outputStream.close(); outputStream.close();
urlConnection.connect(); if (urlConnection.getResponseCode() != 200) {
Log.e("LogUpload", "HTTPS Status Code: " + urlConnection.getResponseCode());
throw new Exception();
}
InputStream inputStream = new BufferedInputStream(urlConnection.getInputStream()); InputStream inputStream = new BufferedInputStream(urlConnection.getInputStream());
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
String key = new JSONObject(bufferedReader.lines().collect(Collectors.joining())).getString("key"); String key = new JSONObject(bufferedReader.lines().collect(Collectors.joining())).getString("key");
bufferedReader.close(); bufferedReader.close();
inputStream.close(); inputStream.close();
urlConnection.disconnect();
String result = "https://hastebin.com/" + key; String result = "https://hastebin.com/" + key;
Intent sharingIntent = new Intent(Intent.ACTION_SEND).setType("text/plain").putExtra(Intent.EXTRA_TEXT, result); Intent sharingIntent = new Intent(Intent.ACTION_SEND).setType("text/plain").putExtra(Intent.EXTRA_TEXT, result);
startActivity(Intent.createChooser(sharingIntent, "Share log url with:")); startActivity(Intent.createChooser(sharingIntent, "Share log url with:"));
} catch (Exception e) { } catch (Exception e) {
runOnUiThread(new Runnable() {@Override public void run() {Toast.makeText(getApplicationContext(), getString(R.string.share_error), Toast.LENGTH_LONG).show();}}); runOnUiThread(new Runnable() {
@Override
public void run() {Toast.makeText(getApplicationContext(), getString(R.string.share_error), Toast.LENGTH_LONG).show();}
});
e.printStackTrace(); e.printStackTrace();
} finally {
assert urlConnection != null;
urlConnection.disconnect();
} }
}}); }
});
share_thread.start(); share_thread.start();
try { try {
share_thread.join(1000); share_thread.join(1000);
} catch (InterruptedException e) { } catch (Exception e) {
Toast.makeText(getApplicationContext(), getString(R.string.share_error), Toast.LENGTH_LONG).show(); Toast.makeText(getApplicationContext(), getString(R.string.share_error), Toast.LENGTH_LONG).show();
e.printStackTrace(); e.printStackTrace();
} }

View File

@ -8,8 +8,8 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import com.google.android.material.snackbar.Snackbar;
class LogItem extends BaseItem { class LogItem extends BaseItem {
private String content; private String content;
@ -60,8 +60,8 @@ public class LogAdapter extends HeaderAdapter<LogItem> implements View.OnLongCli
@Override @Override
public boolean onLongClick(View view) { public boolean onLongClick(View view) {
LogItem item = (LogItem) getItem(((ViewHolder) view.getTag()).position); LogItem item = (LogItem) getItem(((ViewHolder) view.getTag()).position);
clipboard.setPrimaryClip(ClipData.newPlainText("Log Message", item.getLevel() + ": " + item.getMessage())); clipboard.setPrimaryClip(ClipData.newPlainText("Log Message", item.getMessage() + " (" + item.getLevel() + ")"));
Toast.makeText(view.getContext(), "Copied to clipboard", Snackbar.LENGTH_LONG).show(); Toast.makeText(view.getContext(), "Copied to clipboard", Toast.LENGTH_LONG).show();
return false; return false;
} }

View File

@ -11,6 +11,7 @@ import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.widget.AdapterView; import android.widget.AdapterView;
import android.widget.ListView; import android.widget.ListView;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.SearchView; import androidx.appcompat.widget.SearchView;
@ -18,6 +19,7 @@ import androidx.appcompat.widget.Toolbar;
import androidx.core.app.ActivityCompat; import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar; import com.google.android.material.snackbar.Snackbar;

View File

@ -1,6 +1,7 @@
package emu.lightswitch; package emu.lightswitch;
import android.os.Bundle; import android.os.Bundle;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;

View File

@ -1,34 +1,34 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt" xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp" android:width="108dp"
android:height="108dp" android:height="108dp"
android:viewportWidth="108" android:viewportWidth="108"
android:viewportHeight="108"> android:viewportHeight="108">
<path <path
android:fillType="evenOdd" android:fillType="evenOdd"
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z" android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
android:strokeWidth="1" android:strokeWidth="1"
android:strokeColor="#00000000"> android:strokeColor="#00000000">
<aapt:attr name="android:fillColor"> <aapt:attr name="android:fillColor">
<gradient <gradient
android:endX="78.5885" android:endX="78.5885"
android:endY="90.9159" android:endY="90.9159"
android:startX="48.7653" android:startX="48.7653"
android:startY="61.0927" android:startY="61.0927"
android:type="linear"> android:type="linear">
<item <item
android:color="#44000000" android:color="#44000000"
android:offset="0.0" /> android:offset="0.0" />
<item <item
android:color="#00000000" android:color="#00000000"
android:offset="1.0" /> android:offset="1.0" />
</gradient> </gradient>
</aapt:attr> </aapt:attr>
</path> </path>
<path <path
android:fillColor="#FFFFFF" android:fillColor="#FFFFFF"
android:fillType="nonZero" android:fillType="nonZero"
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z" android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
android:strokeWidth="1" android:strokeWidth="1"
android:strokeColor="#00000000" /> android:strokeColor="#00000000" />
</vector> </vector>

View File

@ -1,5 +1,10 @@
<vector android:alpha="0.85" android:height="24dp" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:viewportHeight="24.0" android:viewportWidth="24.0" android:width="24dp"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> android:height="24dp"
<path android:fillColor="#FFF" android:pathData="M5,13h14v-2L5,11v2zM3,17h14v-2L3,15v2zM7,7v2h14L21,7L7,7z"/> android:alpha="0.85"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFF"
android:pathData="M5,13h14v-2L5,11v2zM3,17h14v-2L3,15v2zM7,7v2h14L21,7L7,7z" />
</vector> </vector>

View File

@ -1,74 +1,170 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<vector <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="108dp" android:width="108dp"
android:width="108dp" android:height="108dp"
android:viewportHeight="108" android:viewportWidth="108"
android:viewportWidth="108" android:viewportHeight="108">
xmlns:android="http://schemas.android.com/apk/res/android"> <path
<path android:fillColor="#008577" android:fillColor="#008577"
android:pathData="M0,0h108v108h-108z"/> android:pathData="M0,0h108v108h-108z" />
<path android:fillColor="#00000000" android:pathData="M9,0L9,108" <path
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> android:fillColor="#00000000"
<path android:fillColor="#00000000" android:pathData="M19,0L19,108" android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> android:strokeWidth="0.8"
<path android:fillColor="#00000000" android:pathData="M29,0L29,108" android:strokeColor="#33FFFFFF" />
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> <path
<path android:fillColor="#00000000" android:pathData="M39,0L39,108" android:fillColor="#00000000"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> android:pathData="M19,0L19,108"
<path android:fillColor="#00000000" android:pathData="M49,0L49,108" android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> android:strokeColor="#33FFFFFF" />
<path android:fillColor="#00000000" android:pathData="M59,0L59,108" <path
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> android:fillColor="#00000000"
<path android:fillColor="#00000000" android:pathData="M69,0L69,108" android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> android:strokeWidth="0.8"
<path android:fillColor="#00000000" android:pathData="M79,0L79,108" android:strokeColor="#33FFFFFF" />
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> <path
<path android:fillColor="#00000000" android:pathData="M89,0L89,108" android:fillColor="#00000000"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> android:pathData="M39,0L39,108"
<path android:fillColor="#00000000" android:pathData="M99,0L99,108" android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> android:strokeColor="#33FFFFFF" />
<path android:fillColor="#00000000" android:pathData="M0,9L108,9" <path
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> android:fillColor="#00000000"
<path android:fillColor="#00000000" android:pathData="M0,19L108,19" android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> android:strokeWidth="0.8"
<path android:fillColor="#00000000" android:pathData="M0,29L108,29" android:strokeColor="#33FFFFFF" />
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> <path
<path android:fillColor="#00000000" android:pathData="M0,39L108,39" android:fillColor="#00000000"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> android:pathData="M59,0L59,108"
<path android:fillColor="#00000000" android:pathData="M0,49L108,49" android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> android:strokeColor="#33FFFFFF" />
<path android:fillColor="#00000000" android:pathData="M0,59L108,59" <path
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> android:fillColor="#00000000"
<path android:fillColor="#00000000" android:pathData="M0,69L108,69" android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> android:strokeWidth="0.8"
<path android:fillColor="#00000000" android:pathData="M0,79L108,79" android:strokeColor="#33FFFFFF" />
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> <path
<path android:fillColor="#00000000" android:pathData="M0,89L108,89" android:fillColor="#00000000"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> android:pathData="M79,0L79,108"
<path android:fillColor="#00000000" android:pathData="M0,99L108,99" android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> android:strokeColor="#33FFFFFF" />
<path android:fillColor="#00000000" android:pathData="M19,29L89,29" <path
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> android:fillColor="#00000000"
<path android:fillColor="#00000000" android:pathData="M19,39L89,39" android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> android:strokeWidth="0.8"
<path android:fillColor="#00000000" android:pathData="M19,49L89,49" android:strokeColor="#33FFFFFF" />
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> <path
<path android:fillColor="#00000000" android:pathData="M19,59L89,59" android:fillColor="#00000000"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> android:pathData="M99,0L99,108"
<path android:fillColor="#00000000" android:pathData="M19,69L89,69" android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> android:strokeColor="#33FFFFFF" />
<path android:fillColor="#00000000" android:pathData="M19,79L89,79" <path
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> android:fillColor="#00000000"
<path android:fillColor="#00000000" android:pathData="M29,19L29,89" android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> android:strokeWidth="0.8"
<path android:fillColor="#00000000" android:pathData="M39,19L39,89" android:strokeColor="#33FFFFFF" />
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> <path
<path android:fillColor="#00000000" android:pathData="M49,19L49,89" android:fillColor="#00000000"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> android:pathData="M0,19L108,19"
<path android:fillColor="#00000000" android:pathData="M59,19L59,89" android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> android:strokeColor="#33FFFFFF" />
<path android:fillColor="#00000000" android:pathData="M69,19L69,89" <path
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> android:fillColor="#00000000"
<path android:fillColor="#00000000" android:pathData="M79,19L79,89" android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector> </vector>

View File

@ -1,5 +1,10 @@
<vector android:alpha="0.85" android:height="24dp" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:viewportHeight="24.0" android:viewportWidth="24.0" android:width="24dp"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> android:height="24dp"
<path android:fillColor="#FFFF" android:pathData="M7,5h10v2h2L19,3c0,-1.1 -0.9,-1.99 -2,-1.99L7,1c-1.1,0 -2,0.9 -2,2v4h2L7,5zM15.41,16.59L20,12l-4.59,-4.59L14,8.83 17.17,12 14,15.17l1.41,1.42zM10,15.17L6.83,12 10,8.83 8.59,7.41 4,12l4.59,4.59L10,15.17zM17,19L7,19v-2L5,17v4c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2v-4h-2v2z"/> android:alpha="0.85"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFF"
android:pathData="M7,5h10v2h2L19,3c0,-1.1 -0.9,-1.99 -2,-1.99L7,1c-1.1,0 -2,0.9 -2,2v4h2L7,5zM15.41,16.59L20,12l-4.59,-4.59L14,8.83 17.17,12 14,15.17l1.41,1.42zM10,15.17L6.83,12 10,8.83 8.59,7.41 4,12l4.59,4.59L10,15.17zM17,19L7,19v-2L5,17v4c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2v-4h-2v2z" />
</vector> </vector>

View File

@ -1,5 +1,10 @@
<vector android:alpha="0.6" android:height="24dp" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:viewportHeight="24.0" android:viewportWidth="24.0" android:width="24dp"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> android:height="24dp"
<path android:fillColor="#FF000000" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM17,13L7,13v-2h10v2z"/> android:alpha="0.6"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM17,13L7,13v-2h10v2z" />
</vector> </vector>

View File

@ -1,9 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="24.0" android:viewportWidth="24.0"
android:viewportHeight="24.0"> android:viewportHeight="24.0">
<path <path
android:fillColor="#FFFF" android:fillColor="#FFFF"
android:pathData="M12,4L12,1L8,5l4,4L12,6c3.31,0 6,2.69 6,6 0,1.01 -0.25,1.97 -0.7,2.8l1.46,1.46C19.54,15.03 20,13.57 20,12c0,-4.42 -3.58,-8 -8,-8zM12,18c-3.31,0 -6,-2.69 -6,-6 0,-1.01 0.25,-1.97 0.7,-2.8L5.24,7.74C4.46,8.97 4,10.43 4,12c0,4.42 3.58,8 8,8v3l4,-4 -4,-4v3z" /> android:pathData="M12,4L12,1L8,5l4,4L12,6c3.31,0 6,2.69 6,6 0,1.01 -0.25,1.97 -0.7,2.8l1.46,1.46C19.54,15.03 20,13.57 20,12c0,-4.42 -3.58,-8 -8,-8zM12,18c-3.31,0 -6,-2.69 -6,-6 0,-1.01 0.25,-1.97 0.7,-2.8L5.24,7.74C4.46,8.97 4,10.43 4,12c0,4.42 3.58,8 8,8v3l4,-4 -4,-4v3z" />
</vector> </vector>

View File

@ -1,5 +1,11 @@
<vector android:alpha="0.85" android:height="24dp" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:tint="#FFFFFF" android:viewportHeight="24.0" android:width="24dp"
android:viewportWidth="24.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> android:height="24dp"
<path android:fillColor="#FF000000" android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z"/> android:alpha="0.85"
android:tint="#FFFFFF"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z" />
</vector> </vector>

View File

@ -1,5 +1,10 @@
<vector android:alpha="0.85" android:height="24dp" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:viewportHeight="24" android:viewportWidth="24" android:width="24dp"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> android:height="24dp"
<path android:fillColor="#FFFF" android:pathData="M19.1,12.9a2.8,2.8 0,0 0,0.1 -0.9,2.8 2.8,0 0,0 -0.1,-0.9l2.1,-1.6a0.7,0.7 0,0 0,0.1 -0.6L19.4,5.5a0.7,0.7 0,0 0,-0.6 -0.2l-2.4,1a6.5,6.5 0,0 0,-1.6 -0.9l-0.4,-2.6a0.5,0.5 0,0 0,-0.5 -0.4H10.1a0.5,0.5 0,0 0,-0.5 0.4L9.3,5.4a5.6,5.6 0,0 0,-1.7 0.9l-2.4,-1a0.4,0.4 0,0 0,-0.5 0.2l-2,3.4c-0.1,0.2 0,0.4 0.2,0.6l2,1.6a2.8,2.8 0,0 0,-0.1 0.9,2.8 2.8,0 0,0 0.1,0.9L2.8,14.5a0.7,0.7 0,0 0,-0.1 0.6l1.9,3.4a0.7,0.7 0,0 0,0.6 0.2l2.4,-1a6.5,6.5 0,0 0,1.6 0.9l0.4,2.6a0.5,0.5 0,0 0,0.5 0.4h3.8a0.5,0.5 0,0 0,0.5 -0.4l0.3,-2.6a5.6,5.6 0,0 0,1.7 -0.9l2.4,1a0.4,0.4 0,0 0,0.5 -0.2l2,-3.4c0.1,-0.2 0,-0.4 -0.2,-0.6ZM12,15.6A3.6,3.6 0,1 1,15.6 12,3.6 3.6,0 0,1 12,15.6Z"/> android:alpha="0.85"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFF"
android:pathData="M19.1,12.9a2.8,2.8 0,0 0,0.1 -0.9,2.8 2.8,0 0,0 -0.1,-0.9l2.1,-1.6a0.7,0.7 0,0 0,0.1 -0.6L19.4,5.5a0.7,0.7 0,0 0,-0.6 -0.2l-2.4,1a6.5,6.5 0,0 0,-1.6 -0.9l-0.4,-2.6a0.5,0.5 0,0 0,-0.5 -0.4H10.1a0.5,0.5 0,0 0,-0.5 0.4L9.3,5.4a5.6,5.6 0,0 0,-1.7 0.9l-2.4,-1a0.4,0.4 0,0 0,-0.5 0.2l-2,3.4c-0.1,0.2 0,0.4 0.2,0.6l2,1.6a2.8,2.8 0,0 0,-0.1 0.9,2.8 2.8,0 0,0 0.1,0.9L2.8,14.5a0.7,0.7 0,0 0,-0.1 0.6l1.9,3.4a0.7,0.7 0,0 0,0.6 0.2l2.4,-1a6.5,6.5 0,0 0,1.6 0.9l0.4,2.6a0.5,0.5 0,0 0,0.5 0.4h3.8a0.5,0.5 0,0 0,0.5 -0.4l0.3,-2.6a5.6,5.6 0,0 0,1.7 -0.9l2.4,1a0.4,0.4 0,0 0,0.5 -0.2l2,-3.4c0.1,-0.2 0,-0.4 -0.2,-0.6ZM12,15.6A3.6,3.6 0,1 1,15.6 12,3.6 3.6,0 0,1 12,15.6Z" />
</vector> </vector>

View File

@ -1,5 +1,11 @@
<vector android:alpha="0.85" android:height="24dp" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:tint="#FFFFFF" android:viewportHeight="24.0" android:width="24dp"
android:viewportWidth="24.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> android:height="24dp"
<path android:fillColor="#FF000000" android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z"/> android:alpha="0.85"
android:tint="#FFFFFF"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z" />
</vector> </vector>

View File

@ -1,34 +1,34 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
android:padding="15dp"> android:padding="15dp">
<ImageView <ImageView
android:id="@+id/icon" android:id="@+id/icon"
android:layout_width="50dp" android:layout_width="50dp"
android:layout_height="50dp" android:layout_height="50dp"
android:layout_alignParentStart="true" android:layout_alignParentStart="true"
android:layout_alignParentTop="false" android:layout_alignParentTop="false"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:contentDescription="@string/icon" android:contentDescription="@string/icon"
android:src="@drawable/ic_missing_icon" /> android:src="@drawable/ic_missing_icon" />
<TextView <TextView
android:id="@+id/text_title" android:id="@+id/text_title"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="60dp" android:layout_marginStart="60dp"
android:layout_marginTop="5dp" android:layout_marginTop="5dp"
android:textAppearance="?android:attr/textAppearanceListItem" /> android:textAppearance="?android:attr/textAppearanceListItem" />
<TextView <TextView
android:id="@+id/text_subtitle" android:id="@+id/text_subtitle"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/text_title" android:layout_below="@id/text_title"
android:layout_alignStart="@id/text_title" android:layout_alignStart="@id/text_title"
android:textAppearance="?android:attr/textAppearanceListItemSecondary" android:textAppearance="?android:attr/textAppearanceListItemSecondary"
android:textColor="@android:color/tertiary_text_light" /> android:textColor="@android:color/tertiary_text_light" />
</RelativeLayout> </RelativeLayout>

View File

@ -1,29 +1,29 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".LogActivity"> tools:context=".LogActivity">
<androidx.appcompat.widget.Toolbar <androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar" android:id="@+id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?attr/colorPrimary" android:background="?attr/colorPrimary"
android:minHeight="?attr/actionBarSize" android:minHeight="?attr/actionBarSize"
android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar" android:popupTheme="@style/ThemeOverlay.MaterialComponents.Light"
android:popupTheme="@style/ThemeOverlay.MaterialComponents.Light" android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/> app:layout_constraintTop_toTopOf="parent" />
<ListView <ListView
android:id="@+id/log_list" android:id="@+id/log_list"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:transcriptMode="normal" android:fastScrollEnabled="true"
android:fastScrollEnabled="true" android:transcriptMode="normal"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar"/> app:layout_constraintTop_toBottomOf="@+id/toolbar" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,26 +1,27 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="15dp"
android:background="?attr/selectableItemBackground">
<TextView
android:id="@+id/text_title"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="5dp" android:background="?attr/selectableItemBackground"
android:layout_marginTop="2dp" android:orientation="vertical"
android:textSize="15sp" android:padding="15dp">
android:textAppearance="?android:attr/textAppearanceListItem" />
<TextView <TextView
android:id="@+id/text_subtitle" android:id="@+id/text_title"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/text_title" android:layout_marginStart="5dp"
android:layout_alignStart="@id/text_title" android:layout_marginTop="2dp"
android:textAppearance="?android:attr/textAppearanceListItemSecondary" android:textAppearance="?android:attr/textAppearanceListItem"
android:textSize="12sp" android:textSize="15sp" />
android:textColor="@android:color/tertiary_text_light" />
</RelativeLayout> <TextView
android:id="@+id/text_subtitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/text_title"
android:layout_alignStart="@id/text_title"
android:textAppearance="?android:attr/textAppearanceListItemSecondary"
android:textColor="@android:color/tertiary_text_light"
android:textSize="12sp" />
</RelativeLayout>

View File

@ -1,39 +1,39 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".MainActivity"> tools:context=".MainActivity">
<androidx.appcompat.widget.Toolbar <androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar" android:id="@+id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?attr/colorPrimary" android:background="?attr/colorPrimary"
android:minHeight="?attr/actionBarSize" android:minHeight="?attr/actionBarSize"
android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar" android:popupTheme="@style/ThemeOverlay.MaterialComponents.Light"
android:popupTheme="@style/ThemeOverlay.MaterialComponents.Light" android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/> app:layout_constraintTop_toTopOf="parent" />
<ListView <ListView
android:id="@+id/game_list" android:id="@+id/game_list"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/toolbar" /> app:layout_constraintTop_toBottomOf="@+id/toolbar" />
<com.google.android.material.floatingactionbutton.FloatingActionButton <com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/log_fab" android:id="@+id/log_fab"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp" android:layout_marginBottom="16dp"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:srcCompat="@drawable/ic_log" /> app:srcCompat="@drawable/ic_log" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,18 +1,18 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:clickable="false"
android:padding="12dp">
<TextView
android:id="@+id/text_title"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textColor="@color/colorPrimary" android:clickable="false"
android:textSize="13sp" android:orientation="vertical"
android:layout_marginTop="2dp" android:padding="12dp">
android:layout_marginStart="5dp"/>
</RelativeLayout> <TextView
android:id="@+id/text_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_marginTop="2dp"
android:textColor="@color/colorPrimary"
android:textSize="13sp" />
</RelativeLayout>

View File

@ -1,22 +1,22 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?attr/colorPrimary" android:orientation="vertical">
android:minHeight="?attr/actionBarSize"
android:theme="@style/ThemeOverlay.AppCompat.Dark" <androidx.appcompat.widget.Toolbar
app:layout_constraintEnd_toEndOf="parent" android:id="@+id/toolbar"
app:layout_constraintStart_toStartOf="parent" android:layout_width="match_parent"
app:layout_constraintTop_toTopOf="parent" /> android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
android:minHeight="?attr/actionBarSize"
android:theme="@style/ThemeOverlay.AppCompat.Dark"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<FrameLayout <FrameLayout
android:id="@+id/settings" android:id="@+id/settings"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent" />
</LinearLayout> </LinearLayout>

View File

@ -1,20 +1,20 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" <menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<item <item
android:id="@+id/action_search_log" android:id="@+id/action_search_log"
android:icon="@drawable/ic_search" android:icon="@drawable/ic_search"
android:title="@string/search" android:title="@string/search"
app:showAsAction="ifRoom|withText" app:actionViewClass="androidx.appcompat.widget.SearchView"
app:actionViewClass="androidx.appcompat.widget.SearchView"/> app:showAsAction="ifRoom|withText" />
<item <item
android:id="@+id/action_share_log" android:id="@+id/action_share_log"
android:icon="@drawable/ic_share" android:icon="@drawable/ic_share"
android:title="@string/share" android:title="@string/share"
app:showAsAction="ifRoom"/> app:showAsAction="ifRoom" />
<item <item
android:id="@+id/action_clear" android:id="@+id/action_clear"
android:icon="@drawable/ic_clear" android:icon="@drawable/ic_clear"
android:title="@string/clear" android:title="@string/clear"
app:showAsAction="ifRoom"/> app:showAsAction="ifRoom" />
</menu> </menu>

View File

@ -1,20 +1,20 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" <menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<item <item
android:id="@+id/action_search_main" android:id="@+id/action_search_main"
android:icon="@drawable/ic_search" android:icon="@drawable/ic_search"
android:title="@string/search" android:title="@string/search"
app:showAsAction="ifRoom|withText" app:actionViewClass="androidx.appcompat.widget.SearchView"
app:actionViewClass="androidx.appcompat.widget.SearchView"/> app:showAsAction="ifRoom|withText" />
<item <item
android:id="@+id/action_settings" android:id="@+id/action_settings"
android:icon="@drawable/ic_settings" android:icon="@drawable/ic_settings"
android:title="@string/settings" android:title="@string/settings"
app:showAsAction="ifRoom"/> app:showAsAction="ifRoom" />
<item <item
android:id="@+id/action_refresh" android:id="@+id/action_refresh"
android:icon="@drawable/ic_refresh" android:icon="@drawable/ic_refresh"
android:title="@string/refresh" android:title="@string/refresh"
app:showAsAction="ifRoom"/> app:showAsAction="ifRoom" />
</menu> </menu>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/> <background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@mipmap/ic_launcher_foreground"/> <foreground android:drawable="@mipmap/ic_launcher_foreground" />
</adaptive-icon> </adaptive-icon>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/> <background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@mipmap/ic_launcher_foreground"/> <foreground android:drawable="@mipmap/ic_launcher_foreground" />
</adaptive-icon> </adaptive-icon>

View File

@ -6,6 +6,7 @@
<item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item> <item name="colorAccent">@color/colorAccent</item>
</style> </style>
<style name="ToolbarTheme"> <style name="ToolbarTheme">
<!-- Query Text Color --> <!-- Query Text Color -->
<item name="android:textColorPrimary">@android:color/white</item> <item name="android:textColorPrimary">@android:color/white</item>

View File

@ -15,36 +15,36 @@
--> -->
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" <androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceCategory <PreferenceCategory
android:key="category_search" android:key="category_search"
android:title="@string/search"> android:title="@string/search">
<EditTextPreference <EditTextPreference
android:defaultValue="/sdcard/" android:defaultValue="/sdcard/"
app:key="search_location" app:key="search_location"
app:title="@string/search_location" app:title="@string/search_location"
app:useSimpleSummaryProvider="true" /> app:useSimpleSummaryProvider="true" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory <PreferenceCategory
android:key="category_log" android:key="category_log"
android:title="@string/logging"> android:title="@string/logging">
<ListPreference <ListPreference
android:defaultValue="2" android:defaultValue="2"
android:entries="@array/log_level" android:entries="@array/log_level"
android:entryValues="@array/log_level_val" android:entryValues="@array/log_level_val"
app:key="log_level" app:key="log_level"
app:title="@string/log_level" app:title="@string/log_level"
app:useSimpleSummaryProvider="true" /> app:useSimpleSummaryProvider="true" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory <PreferenceCategory
android:key="category_localization" android:key="category_localization"
android:title="@string/localization"> android:title="@string/localization">
<ListPreference <ListPreference
android:defaultValue="sys" android:defaultValue="sys"
android:entries="@array/language_names" android:entries="@array/language_names"
android:entryValues="@array/language_values" android:entryValues="@array/language_values"
app:key="localization_language" app:key="localization_language"
app:title="@string/localization_language" app:title="@string/localization_language"
app:useSimpleSummaryProvider="true" /> app:useSimpleSummaryProvider="true" />
</PreferenceCategory> </PreferenceCategory>
</androidx.preference.PreferenceScreen> </androidx.preference.PreferenceScreen>