mirror of
https://github.com/skyline-emu/skyline.git
synced 2024-11-25 23:54:15 +01:00
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:
parent
9e1e06c64b
commit
a54f5ff578
8
.gitignore
vendored
8
.gitignore
vendored
@ -61,7 +61,8 @@ build/
|
||||
local.properties
|
||||
|
||||
# External native build folder generated in Android Studio 2.2 and later
|
||||
.externalNativeBuild
|
||||
.externalNativeBuild/
|
||||
.cxx/
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
@ -93,6 +94,5 @@ captures/
|
||||
# VSCode
|
||||
.vscode/
|
||||
|
||||
# Android Studio
|
||||
.cxx/
|
||||
|
||||
# Discord plugin for IntelliJ IDEA
|
||||
.idea\discord.xml
|
||||
|
8
.idea/codeStyles/Project.xml
generated
8
.idea/codeStyles/Project.xml
generated
@ -4,6 +4,8 @@
|
||||
<option name="WRAP_WHEN_TYPING_REACHES_RIGHT_MARGIN" value="true" />
|
||||
<option name="SOFT_MARGINS" value="80,140" />
|
||||
<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="KEEP_STRUCTURES_IN_ONE_LINE" value="true" />
|
||||
<option name="FUNCTION_PARAMETERS_WRAP" value="0" />
|
||||
@ -31,6 +33,12 @@
|
||||
<option name="KEEP_MULTIPLE_EXPRESSIONS_IN_ONE_LINE" value="true" />
|
||||
<option name="WRAP_LONG_LINES" value="true" />
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="ObjectiveC">
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
<option name="LABEL_INDENT_SIZE" value="-1" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="XML">
|
||||
<arrangement>
|
||||
<rules>
|
||||
|
3
.idea/discord.xml
generated
3
.idea/discord.xml
generated
@ -4,7 +4,4 @@
|
||||
<component name="DiscordProjectSettings">
|
||||
<option name="show" value="true" />
|
||||
</component>
|
||||
<component name="ProjectNotificationSettings">
|
||||
<option name="askShowProject" value="false" />
|
||||
</component>
|
||||
</project>
|
@ -1,6 +1,5 @@
|
||||
cmake_minimum_required(VERSION 3.8)
|
||||
project(Lightswitch VERSION 1 LANGUAGES CXX)
|
||||
set_property(GLOBAL PROPERTY CMAKE_CXX_STANDARD 17 PROPERTY CMAKE_CXX_STANDARD_REQUIRED TRUE)
|
||||
|
||||
set(BUILD_TESTING OFF)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
@ -25,6 +24,8 @@ add_library(lightswitch SHARED
|
||||
${source_DIR}/switch/kernel/service.cpp
|
||||
${source_DIR}/switch/kernel/types/KProcess.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_compile_options(lightswitch PRIVATE -Wno-c++17-extensions)
|
||||
|
@ -15,7 +15,8 @@
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme"
|
||||
tools:ignore="GoogleAppIndexingWarning">
|
||||
<activity android:name=".LogActivity"
|
||||
<activity
|
||||
android:name=".LogActivity"
|
||||
android:label="@string/log"
|
||||
android:parentActivityName=".MainActivity">
|
||||
<meta-data
|
||||
|
@ -32,6 +32,7 @@ extern "C" JNIEXPORT void JNICALL Java_emu_lightswitch_MainActivity_loadFile(JNI
|
||||
if (emu_thread) {
|
||||
halt = true; // This'll cause execution to stop after the next breakpoint
|
||||
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
|
||||
|
@ -3,33 +3,7 @@
|
||||
#include <syslog.h>
|
||||
|
||||
namespace lightSwitch {
|
||||
namespace memory {
|
||||
Permission::Permission() {
|
||||
r = 0;
|
||||
w = 0;
|
||||
x = 0;
|
||||
}
|
||||
|
||||
Permission::Permission(bool read, bool write, bool execute) {
|
||||
r = read;
|
||||
w = write;
|
||||
x = execute;
|
||||
}
|
||||
|
||||
bool Permission::operator==(const Permission &rhs) const { return (this->r == rhs.r && this->w == rhs.w && this->x == rhs.x); }
|
||||
|
||||
bool Permission::operator!=(const Permission &rhs) const { return !operator==(rhs); }
|
||||
|
||||
int Permission::get() const {
|
||||
int perm = 0;
|
||||
if (r) perm |= PROT_READ;
|
||||
if (w) perm |= PROT_WRITE;
|
||||
if (x) perm |= PROT_EXEC;
|
||||
return perm;
|
||||
}
|
||||
}
|
||||
|
||||
Settings::Settings(std::string pref_xml) {
|
||||
Settings::Settings(std::string &pref_xml) {
|
||||
tinyxml2::XMLDocument pref;
|
||||
if (pref.LoadFile(pref_xml.c_str()))
|
||||
throw exception("TinyXML2 Error: " + std::string(pref.ErrorStr()));
|
||||
@ -38,13 +12,10 @@ namespace lightSwitch {
|
||||
switch (elem->Value()[0]) {
|
||||
case 's':
|
||||
string_map.insert(
|
||||
std::pair<char *, char *>((char *) elem->FindAttribute("name")->Value(),
|
||||
(char *) elem->GetText()));
|
||||
std::pair<char *, char *>((char *) elem->FindAttribute("name")->Value(), (char *) elem->GetText()));
|
||||
break;
|
||||
case 'b':
|
||||
bool_map.insert(
|
||||
std::pair<char *, bool>((char *) elem->FindAttribute("name")->Value(),
|
||||
elem->FindAttribute("value")->BoolValue()));
|
||||
bool_map.insert(std::pair<char *, bool>((char *) elem->FindAttribute("name")->Value(), elem->FindAttribute("value")->BoolValue()));
|
||||
default:
|
||||
break;
|
||||
};
|
||||
|
@ -18,49 +18,71 @@
|
||||
|
||||
namespace lightSwitch {
|
||||
// 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 uint32_t handle_t; //!< The type of an handle
|
||||
typedef u32 handle_t; //!< The type of an handle
|
||||
|
||||
namespace constant {
|
||||
// Memory
|
||||
constexpr uint64_t base_addr = 0x8000000; //!< The address space base
|
||||
constexpr uint64_t base_size = 0x7FF8000000; //!< The size of the address space
|
||||
constexpr uint64_t total_phy_mem = 0xF8000000; // ~4 GB of RAM
|
||||
constexpr u64 base_addr = 0x8000000; //!< The address space base
|
||||
constexpr u64 map_addr = base_addr + 0x80000000; //!< The address of the map region
|
||||
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_heap_size = PAGE_SIZE; //!< The default amount of heap
|
||||
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
|
||||
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
|
||||
constexpr uint8_t num_regs = 31; //!< The amount of registers that ARMv8 has
|
||||
constexpr uint16_t svc_last = 0x7F; //!< The index of the last SVC
|
||||
constexpr uint16_t brk_rdy = 0xFF; //!< This is reserved for our kernel's to know when a process/thread is ready
|
||||
constexpr uint32_t tpidrro_el0 = 0x5E83; //!< ID of tpidrro_el0 in MRS
|
||||
constexpr u8 num_regs = 31; //!< The amount of registers that ARMv8 has
|
||||
constexpr u16 svc_last = 0x7F; //!< The index of the last SVC
|
||||
constexpr u16 brk_rdy = 0xFF; //!< This is reserved for our kernel's to know when a process/thread is ready
|
||||
constexpr u32 tpidrro_el0 = 0x5E83; //!< ID of tpidrro_el0 in MRS
|
||||
// IPC
|
||||
constexpr size_t tls_ipc_size = 0x100; //!< The size of the IPC command buffer in a TLS slot
|
||||
constexpr uint64_t sm_handle = 0xd000; //!< sm:'s handle
|
||||
constexpr uint8_t port_size = 0x8; //!< The size of a port name string
|
||||
constexpr uint32_t ipc_sfco = 0x4F434653; //!< SFCO in reverse
|
||||
constexpr handle_t sm_handle = 0xD000; //!< sm:'s handle
|
||||
constexpr u8 port_size = 0x8; //!< The size of a port name string
|
||||
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
|
||||
constexpr uint32_t base_handle_index = 0xD001; // The index of the base handle
|
||||
constexpr uint8_t default_priority = 31; //!< The default priority of a process
|
||||
constexpr handle_t base_handle_index = sm_handle + 1; // The index of the base handle
|
||||
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<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 {
|
||||
/**
|
||||
* 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 {
|
||||
/**
|
||||
* Creates a BRK instruction with a specific immediate value, used for generating BRK opcodes
|
||||
* @param val The immediate value of the instruction
|
||||
*/
|
||||
brk(uint16_t val) {
|
||||
brk(u16 val) {
|
||||
start = 0x0; // First 5 bits of an BRK instruction are 0
|
||||
value = val;
|
||||
end = 0x6A1; // Last 11 bits of an BRK instruction stored as uint16_t
|
||||
end = 0x6A1; // Last 11 bits of an BRK instruction stored as u16
|
||||
}
|
||||
|
||||
/**
|
||||
@ -70,13 +92,15 @@ namespace lightSwitch {
|
||||
return (start == 0x0 && end == 0x6A1);
|
||||
}
|
||||
|
||||
uint8_t start : 5;
|
||||
uint32_t value : 16;
|
||||
uint16_t end : 11;
|
||||
u8 start : 5;
|
||||
u32 value : 16;
|
||||
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 {
|
||||
/**
|
||||
@ -86,13 +110,15 @@ namespace lightSwitch {
|
||||
return (start == 0x1 && end == 0x6A0);
|
||||
}
|
||||
|
||||
uint8_t start : 5;
|
||||
uint32_t value : 16;
|
||||
uint16_t end : 11;
|
||||
u8 start : 5;
|
||||
u32 value : 16;
|
||||
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 {
|
||||
/**
|
||||
@ -102,73 +128,20 @@ namespace lightSwitch {
|
||||
return (end == 0xD53);
|
||||
}
|
||||
|
||||
uint8_t dst_reg : 5;
|
||||
uint32_t src_reg : 15;
|
||||
uint16_t end : 12;
|
||||
u8 dst_reg : 5;
|
||||
u32 src_reg : 15;
|
||||
u16 end : 12;
|
||||
};
|
||||
|
||||
static_assert(sizeof(mrs) == sizeof(u32));
|
||||
};
|
||||
|
||||
/**
|
||||
* Read about ARMv8 registers here: https://developer.arm.com/docs/100878/latest/registers
|
||||
*/
|
||||
namespace regs {
|
||||
enum xreg { x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21, x22, x23, x24, x25, x26, x27, x28, x29, x30 };
|
||||
enum wreg { w0, w1, w2, w3, w4, w5, w6, w7, w8, w9, w10, w11, w12, w13, w14, w15, w16, w17, w18, w19, w20, w21, w22, w23, w24, w25, w26, w27, w28, w29, w30 };
|
||||
enum sreg { sp, pc, pstate };
|
||||
}
|
||||
|
||||
namespace memory {
|
||||
/**
|
||||
* The Permission struct holds the permission of a particular chunk of memory
|
||||
*/
|
||||
struct Permission {
|
||||
/**
|
||||
* Initializes all values to false
|
||||
*/
|
||||
Permission();
|
||||
|
||||
/**
|
||||
* @param read If memory has read permission
|
||||
* @param write If memory has write permission
|
||||
* @param execute If memory has execute permission
|
||||
*/
|
||||
Permission(bool read, bool write, bool execute);
|
||||
|
||||
/**
|
||||
* Equality operator between two Permission objects
|
||||
*/
|
||||
bool operator==(const Permission &rhs) const;
|
||||
|
||||
/**
|
||||
* Inequality operator between two Permission objects
|
||||
*/
|
||||
bool operator!=(const Permission &rhs) const;
|
||||
|
||||
/**
|
||||
* @return The value of the permission struct in mmap(2) format
|
||||
*/
|
||||
int get() const;
|
||||
|
||||
bool r, w, x;
|
||||
};
|
||||
|
||||
/**
|
||||
* Memory Regions that are mapped by the kernel
|
||||
*/
|
||||
enum class Region {
|
||||
heap, tls, text, rodata, data, bss
|
||||
};
|
||||
|
||||
/**
|
||||
* The RegionData struct holds information about a corresponding Region of memory such as address and size
|
||||
*/
|
||||
struct RegionData {
|
||||
uint64_t address;
|
||||
size_t size;
|
||||
Permission perms;
|
||||
int fd;
|
||||
};
|
||||
}
|
||||
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 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 class sreg { sp, pc, pstate };
|
||||
|
||||
/**
|
||||
* The Settings class is used to access the parameters set in the Java component of the application
|
||||
@ -188,7 +161,7 @@ namespace lightSwitch {
|
||||
/**
|
||||
* @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
|
||||
@ -263,6 +236,7 @@ namespace lightSwitch {
|
||||
namespace kernel {
|
||||
namespace type {
|
||||
class KProcess;
|
||||
|
||||
class KThread;
|
||||
}
|
||||
class OS;
|
||||
|
@ -4,99 +4,130 @@
|
||||
#include "types/KProcess.h"
|
||||
|
||||
namespace lightSwitch::kernel::ipc {
|
||||
IpcRequest::IpcRequest(uint8_t *tls_ptr, device_state &state) : req_info((CommandStruct *) tls_ptr) {
|
||||
state.logger->Write(Logger::DEBUG, "Enable handle descriptor: {0}", (bool) req_info->handle_desc);
|
||||
|
||||
data_offset = 8;
|
||||
if (req_info->handle_desc) {
|
||||
HandleDescriptor handledesc = *(HandleDescriptor *) (&tls_ptr[8]);
|
||||
state.logger->Write(Logger::DEBUG, "Moving {} handles and copying {} handles (0x{:X})", uint8_t(handledesc.move_count), uint8_t(handledesc.copy_count), tls_ptr[2]);
|
||||
data_offset += 4 + handledesc.copy_count * 4 + handledesc.move_count * 4;
|
||||
IpcRequest::IpcRequest(bool is_domain, device_state &state) : is_domain(is_domain), state(state), tls() {
|
||||
u8 *curr_ptr = tls.data();
|
||||
state.this_process->ReadMemory(curr_ptr, state.this_thread->tls, constant::tls_ipc_size);
|
||||
header = reinterpret_cast<CommandHeader *>(curr_ptr);
|
||||
curr_ptr += sizeof(CommandHeader);
|
||||
if (header->handle_desc) {
|
||||
handle_desc = reinterpret_cast<HandleDescriptor *>(curr_ptr);
|
||||
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));
|
||||
curr_ptr += sizeof(handle_t);
|
||||
}
|
||||
|
||||
if (req_info->x_no || req_info->a_no || req_info->b_no || req_info->w_no)
|
||||
state.logger->Write(Logger::ERROR, "IPC - Descriptors");
|
||||
|
||||
// Align to 16 bytes
|
||||
data_offset = ((data_offset - 1) & ~(15U)) + 16; // ceil data_offset with multiples 16
|
||||
data_ptr = &tls_ptr[data_offset + sizeof(req_info)];
|
||||
|
||||
state.logger->Write(Logger::DEBUG, "Type: 0x{:X}", (uint8_t) req_info->type);
|
||||
state.logger->Write(Logger::DEBUG, "X descriptors: {}", (uint8_t) req_info->x_no);
|
||||
state.logger->Write(Logger::DEBUG, "A descriptors: {}", (uint8_t) req_info->a_no);
|
||||
state.logger->Write(Logger::DEBUG, "B descriptors: {}", (uint8_t) req_info->b_no);
|
||||
state.logger->Write(Logger::DEBUG, "W descriptors: {}", (uint8_t) req_info->w_no);
|
||||
state.logger->Write(Logger::DEBUG, "Raw data offset: 0x{:X}", data_offset);
|
||||
state.logger->Write(Logger::DEBUG, "Raw data size: {}", (uint16_t) req_info->data_sz);
|
||||
state.logger->Write(Logger::DEBUG, "Payload Command ID: {0} (0x{0:X})", *((uint32_t *) &tls_ptr[data_offset + 8]));
|
||||
for (uint index = 0; handle_desc->move_count > index; index++) {
|
||||
move_handles.push_back(*reinterpret_cast<handle_t *>(curr_ptr));
|
||||
curr_ptr += sizeof(handle_t);
|
||||
}
|
||||
|
||||
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]);
|
||||
for (uint index = 0; header->x_no > index; index++) {
|
||||
vec_buf_x.push_back(reinterpret_cast<BufferDescriptorX *>(curr_ptr));
|
||||
curr_ptr += sizeof(BufferDescriptorX);
|
||||
}
|
||||
|
||||
IpcResponse::~IpcResponse() {
|
||||
delete[] tls_ptr;
|
||||
for (uint index = 0; header->a_no > index; index++) {
|
||||
vec_buf_a.push_back(reinterpret_cast<BufferDescriptorABW *>(curr_ptr));
|
||||
curr_ptr += sizeof(BufferDescriptorABW);
|
||||
}
|
||||
|
||||
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 (uint index = 0; header->b_no > index; index++) {
|
||||
vec_buf_b.push_back(reinterpret_cast<BufferDescriptorABW *>(curr_ptr));
|
||||
curr_ptr += sizeof(BufferDescriptorABW);
|
||||
}
|
||||
for (auto& moved_handle : moved_handles) {
|
||||
state.logger->Write(Logger::DEBUG, "Moving handle to 0x{:X}", 12 + handle_index * 4);
|
||||
tls_ptr[3 + handle_index] = moved_handle;
|
||||
handle_index += 1;
|
||||
for (uint index = 0; header->w_no > index; index++) {
|
||||
vec_buf_w.push_back(reinterpret_cast<BufferDescriptorABW *>(curr_ptr));
|
||||
curr_ptr += sizeof(BufferDescriptorABW);
|
||||
}
|
||||
|
||||
data_offset += 4 + copied_handles.size() * 4 + moved_handles.size() * 4;
|
||||
}
|
||||
|
||||
state.logger->Write(Logger::DEBUG, "Data offset: 0x{:X}", data_offset);
|
||||
|
||||
// Align to 16 bytes
|
||||
data_offset = ((data_offset - 1) & ~(15U)) + 16; // ceil data_offset with multiples 16
|
||||
|
||||
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) {
|
||||
tls_ptr[data_offset >> 2] = static_cast<uint32_t>(moved_handles.size());
|
||||
data_offset += 0x10;
|
||||
domain = reinterpret_cast<DomainHeaderRequest *>(raw_ptr);
|
||||
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;
|
||||
IpcResponse::IpcResponse(bool is_domain, device_state &state) : is_domain(is_domain), state(state) {}
|
||||
|
||||
// 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::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::SetError(uint32_t _error_code) { error_code = _error_code; }
|
||||
|
||||
template<typename T>
|
||||
void IpcResponse::WriteValue() {
|
||||
|
||||
}
|
||||
|
||||
void IpcResponse::CopyHandle(uint32_t handle) { copied_handles.push_back(handle); }
|
||||
|
||||
void IpcResponse::MoveHandle(uint32_t handle) { moved_handles.push_back(handle); }
|
||||
}
|
||||
|
@ -2,68 +2,224 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include "switch/common.h"
|
||||
|
||||
namespace lightSwitch::kernel::ipc {
|
||||
struct CommandStruct {
|
||||
// https://switchbrew.org/wiki/IPC_Marshalling#IPC_Command_Structure
|
||||
uint16_t type : 16;
|
||||
uint8_t x_no : 4;
|
||||
uint8_t a_no : 4;
|
||||
uint8_t b_no : 4;
|
||||
uint8_t w_no : 4;
|
||||
uint16_t data_sz : 10;
|
||||
uint8_t c_flags : 4;
|
||||
uint32_t : 17;
|
||||
/**
|
||||
* This bit-field structure holds the header of an IPC command.
|
||||
* https://switchbrew.org/wiki/IPC_Marshalling#IPC_Command_Structure
|
||||
*/
|
||||
struct CommandHeader {
|
||||
u16 type : 16;
|
||||
u8 x_no : 4;
|
||||
u8 a_no : 4;
|
||||
u8 b_no : 4;
|
||||
u8 w_no : 4;
|
||||
u32 raw_sz : 10;
|
||||
u8 c_flag : 4;
|
||||
u32 : 17;
|
||||
bool handle_desc : 1;
|
||||
};
|
||||
static_assert(sizeof(CommandHeader) == 8);
|
||||
|
||||
/**
|
||||
* This reflects the value in CommandStruct::type
|
||||
*/
|
||||
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;
|
||||
uint8_t copy_count : 4;
|
||||
uint8_t move_count : 4;
|
||||
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 {
|
||||
private:
|
||||
uint8_t *data_ptr;
|
||||
uint32_t data_offset;
|
||||
device_state &state;
|
||||
|
||||
public:
|
||||
CommandStruct *req_info;
|
||||
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;
|
||||
|
||||
IpcRequest(uint8_t *tlsPtr, device_state &state);
|
||||
|
||||
template<typename T>
|
||||
T GetValue();
|
||||
IpcRequest(bool is_domain, device_state &state);
|
||||
};
|
||||
|
||||
class IpcResponse {
|
||||
private:
|
||||
uint32_t *tls_ptr{};
|
||||
uint32_t data_offset{}; // Offset to raw data relative to tls_ptr
|
||||
std::vector<u8> arg_vec;
|
||||
device_state &state;
|
||||
|
||||
bool is_domain{}; // TODO
|
||||
uint32_t error_code{};
|
||||
|
||||
CommandStruct resp_info;
|
||||
std::vector<uint32_t> copied_handles;
|
||||
std::vector<uint32_t> moved_handles;
|
||||
std::vector<uint8_t> data;
|
||||
uint16_t data_pos{}; // Position in raw data relative to data_offset
|
||||
public:
|
||||
IpcResponse();
|
||||
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;
|
||||
|
||||
~IpcResponse();
|
||||
|
||||
void Generate(device_state &state);
|
||||
|
||||
void SetError(uint32_t _error_code);
|
||||
IpcResponse(bool is_domain, device_state &state);
|
||||
|
||||
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 MoveHandle(uint32_t handle);
|
||||
void WriteTls();
|
||||
};
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include "service.h"
|
||||
|
||||
namespace lightSwitch::kernel::service {
|
||||
|
||||
Service::Service() {
|
||||
|
||||
}
|
||||
|
@ -1,8 +1,18 @@
|
||||
#include "../../common.h"
|
||||
#include "../ipc.h"
|
||||
|
||||
namespace lightSwitch::kernel::service {
|
||||
class BaseService {
|
||||
virtual const char* name() = 0;
|
||||
BaseService() {}
|
||||
protected:
|
||||
device_state &state;
|
||||
|
||||
public:
|
||||
BaseService(device_state &state) : state(state) {}
|
||||
|
||||
virtual const char *Name() = 0;
|
||||
|
||||
virtual ipc::IpcResponse HandleSyncRequest(ipc::IpcRequest &request) = 0;
|
||||
|
||||
|
||||
};
|
||||
}
|
||||
|
@ -6,20 +6,36 @@
|
||||
|
||||
namespace lightSwitch::kernel::svc {
|
||||
void SetHeapSize(device_state &state) {
|
||||
uint64_t addr = state.this_process->MapPrivate(0, state.nce->GetRegister(regs::w1), {true, true, false}, memory::Region::heap);
|
||||
state.nce->SetRegister(regs::w0, constant::status::success);
|
||||
state.nce->SetRegister(regs::x1, addr);
|
||||
auto heap = state.this_process->MapPrivateRegion(0, state.nce->GetRegister(wreg::w1), {true, true, false}, Memory::Type::Heap, Memory::Region::heap);
|
||||
state.nce->SetRegister(wreg::w0, constant::status::success);
|
||||
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) {
|
||||
// TODO: Check if the values supplied by the process are actually valid & Support Core Mask potentially ?
|
||||
auto thread = state.this_process->CreateThread(state.nce->GetRegister(regs::x1), state.nce->GetRegister(regs::x2), state.nce->GetRegister(regs::x3), static_cast<uint8_t>(state.nce->GetRegister(regs::w4)));
|
||||
state.nce->SetRegister(regs::w0, constant::status::success);
|
||||
state.nce->SetRegister(regs::w1, thread->handle);
|
||||
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(wreg::w0, constant::status::success);
|
||||
state.nce->SetRegister(wreg::w1, thread->handle);
|
||||
}
|
||||
|
||||
void StartThread(device_state &state) {
|
||||
auto& object = state.this_process->handle_table.at(static_cast<const unsigned int &>(state.nce->GetRegister(regs::w0)));
|
||||
auto &object = state.this_process->handle_table.at(static_cast<const unsigned int &>(state.nce->GetRegister(wreg::w0)));
|
||||
if (object->type == type::KObjectType::KThread) {
|
||||
std::static_pointer_cast<type::KThread>(object)->Start();
|
||||
} else
|
||||
@ -31,16 +47,16 @@ namespace lightSwitch::kernel::svc {
|
||||
}
|
||||
|
||||
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) {
|
||||
state.nce->SetRegister(regs::w0, constant::status::success);
|
||||
state.nce->SetRegister(regs::w1, std::static_pointer_cast<type::KThread>(object)->priority);
|
||||
state.nce->SetRegister(wreg::w0, constant::status::success);
|
||||
state.nce->SetRegister(wreg::w1, std::static_pointer_cast<type::KThread>(object)->priority);
|
||||
} else
|
||||
throw exception("GetThreadPriority was called on a non-KThread object");
|
||||
}
|
||||
|
||||
void SetThreadPriority(device_state &state) {
|
||||
auto& object = state.this_process->handle_table.at(static_cast<const unsigned int &>(state.nce->GetRegister(regs::w0)));
|
||||
auto &object = state.this_process->handle_table.at(static_cast<const unsigned int &>(state.nce->GetRegister(wreg::w0)));
|
||||
if (object->type == type::KObjectType::KThread) {
|
||||
std::static_pointer_cast<type::KThread>(object)->Start();
|
||||
} else
|
||||
@ -48,7 +64,7 @@ namespace lightSwitch::kernel::svc {
|
||||
}
|
||||
|
||||
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) {
|
||||
case (type::KObjectType::KThread):
|
||||
state.os->KillThread(std::static_pointer_cast<type::KThread>(object)->pid);
|
||||
@ -56,78 +72,100 @@ namespace lightSwitch::kernel::svc {
|
||||
case (type::KObjectType::KProcess):
|
||||
state.os->KillThread(std::static_pointer_cast<type::KProcess>(object)->main_thread);
|
||||
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) {
|
||||
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)
|
||||
state.nce->SetRegister(regs::w1, constant::sm_handle);
|
||||
state.nce->SetRegister(wreg::w1, constant::sm_handle);
|
||||
else
|
||||
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) {
|
||||
state.logger->Write(Logger::DEBUG, "svcSendSyncRequest called for handle 0x{:X}.", state.nce->GetRegister(regs::x0));
|
||||
uint8_t tls[constant::tls_ipc_size];
|
||||
state.os->this_process->ReadMemory(&tls, state.this_thread->tls, constant::tls_ipc_size);
|
||||
ipc::IpcRequest request(tls, state);
|
||||
state.os->IpcHandler(request);
|
||||
state.logger->Write(Logger::DEBUG, "----------------------------svcSendSyncRequest Start-----------------------");
|
||||
state.logger->Write(Logger::DEBUG, "svcSendSyncRequest called for handle 0x{:X}.", state.nce->GetRegister(xreg::x0));
|
||||
state.os->IpcHandler(static_cast<handle_t>(state.nce->GetRegister(xreg::x0)));
|
||||
state.nce->SetRegister(wreg::w0, constant::status::success);
|
||||
state.nce->SetRegister(wreg::w19, constant::status::success);
|
||||
state.logger->Write(Logger::DEBUG, "----------------------------svcSendSyncRequest End-------------------------");
|
||||
}
|
||||
|
||||
void OutputDebugString(device_state &state) {
|
||||
std::string debug(state.nce->GetRegister(regs::x1), '\0');
|
||||
state.os->this_process->ReadMemory((void *) debug.data(), state.nce->GetRegister(regs::x0), state.nce->GetRegister(regs::x1));
|
||||
std::string debug(state.nce->GetRegister(xreg::x1), '\0');
|
||||
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.nce->SetRegister(regs::w0, 0);
|
||||
state.nce->SetRegister(wreg::w0, 0);
|
||||
}
|
||||
|
||||
void GetInfo(device_state &state) {
|
||||
state.logger->Write(Logger::DEBUG, "svcGetInfo called with ID0: {}, ID1: {}", state.nce->GetRegister(regs::w1), state.nce->GetRegister(regs::x3));
|
||||
switch (state.nce->GetRegister(regs::w1)) {
|
||||
state.logger->Write(Logger::DEBUG, "svcGetInfo called with ID0: {}, ID1: {}", state.nce->GetRegister(wreg::w1), state.nce->GetRegister(xreg::x3));
|
||||
switch (state.nce->GetRegister(wreg::w1)) {
|
||||
case constant::infoState::AllowedCpuIdBitmask:
|
||||
case constant::infoState::AllowedThreadPriorityMask:
|
||||
case constant::infoState::IsCurrentProcessBeingDebugged:
|
||||
case constant::infoState::TitleId:
|
||||
case constant::infoState::PrivilegedProcessId:
|
||||
state.nce->SetRegister(regs::x1, 0);
|
||||
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;
|
||||
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;
|
||||
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;
|
||||
case constant::infoState::AddressSpaceBaseAddr:
|
||||
state.nce->SetRegister(regs::x1, constant::base_addr);
|
||||
state.nce->SetRegister(xreg::x1, constant::base_addr);
|
||||
break;
|
||||
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;
|
||||
case constant::infoState::PersonalMmHeapSize:
|
||||
state.nce->SetRegister(regs::x1, constant::total_phy_mem);
|
||||
state.nce->SetRegister(xreg::x1, constant::total_phy_mem);
|
||||
break;
|
||||
case constant::infoState::PersonalMmHeapUsage:
|
||||
state.nce->SetRegister(regs::x1, state.os->this_process->memory_map.at(memory::Region::heap).address + state.this_process->main_thread_stack_sz);
|
||||
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;
|
||||
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;
|
||||
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;
|
||||
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;
|
||||
default:
|
||||
state.logger->Write(Logger::WARN, "Unimplemented svcGetInfo with ID0: {}, ID1: {}", state.nce->GetRegister(regs::w1), state.nce->GetRegister(regs::x3));
|
||||
state.nce->SetRegister(regs::w0, constant::status::unimpl);
|
||||
state.logger->Write(Logger::WARN, "Unimplemented svcGetInfo with ID0: {}, ID1: {}", state.nce->GetRegister(wreg::w1), state.nce->GetRegister(xreg::x3));
|
||||
state.nce->SetRegister(wreg::w0, constant::status::unimpl);
|
||||
return;
|
||||
}
|
||||
state.nce->SetRegister(regs::w0, constant::status::success);
|
||||
state.nce->SetRegister(wreg::w0, constant::status::success);
|
||||
}
|
||||
|
||||
void ExitProcess(device_state &state) {
|
||||
|
@ -4,50 +4,53 @@
|
||||
#include "../common.h"
|
||||
#include "switch/os.h"
|
||||
|
||||
namespace lightSwitch::constant {
|
||||
namespace infoState {
|
||||
namespace lightSwitch {
|
||||
namespace constant::infoState {
|
||||
// 1.0.0+
|
||||
constexpr uint8_t AllowedCpuIdBitmask = 0x0;
|
||||
constexpr uint8_t AllowedThreadPriorityMask = 0x1;
|
||||
constexpr uint8_t AliasRegionBaseAddr = 0x2;
|
||||
constexpr uint8_t AliasRegionSize = 0x3;
|
||||
constexpr uint8_t HeapRegionBaseAddr = 0x4;
|
||||
constexpr uint8_t HeapRegionSize = 0x5;
|
||||
constexpr uint8_t TotalMemoryAvailable = 0x6;
|
||||
constexpr uint8_t TotalMemoryUsage = 0x7;
|
||||
constexpr uint8_t IsCurrentProcessBeingDebugged = 0x8;
|
||||
constexpr uint8_t ResourceLimit = 0x9;
|
||||
constexpr uint8_t IdleTickCount = 0xA;
|
||||
constexpr uint8_t RandomEntropy = 0xB;
|
||||
constexpr u8 AllowedCpuIdBitmask = 0x0;
|
||||
constexpr u8 AllowedThreadPriorityMask = 0x1;
|
||||
constexpr u8 AliasRegionBaseAddr = 0x2;
|
||||
constexpr u8 AliasRegionSize = 0x3;
|
||||
constexpr u8 HeapRegionBaseAddr = 0x4;
|
||||
constexpr u8 HeapRegionSize = 0x5;
|
||||
constexpr u8 TotalMemoryAvailable = 0x6;
|
||||
constexpr u8 TotalMemoryUsage = 0x7;
|
||||
constexpr u8 IsCurrentProcessBeingDebugged = 0x8;
|
||||
constexpr u8 ResourceLimit = 0x9;
|
||||
constexpr u8 IdleTickCount = 0xA;
|
||||
constexpr u8 RandomEntropy = 0xB;
|
||||
// 2.0.0+
|
||||
constexpr uint8_t AddressSpaceBaseAddr = 0xC;
|
||||
constexpr uint8_t AddressSpaceSize = 0xD;
|
||||
constexpr uint8_t StackRegionBaseAddr = 0xE;
|
||||
constexpr uint8_t StackRegionSize = 0xF;
|
||||
constexpr u8 AddressSpaceBaseAddr = 0xC;
|
||||
constexpr u8 AddressSpaceSize = 0xD;
|
||||
constexpr u8 StackRegionBaseAddr = 0xE;
|
||||
constexpr u8 StackRegionSize = 0xF;
|
||||
// 3.0.0+
|
||||
constexpr uint8_t PersonalMmHeapSize = 0x10;
|
||||
constexpr uint8_t PersonalMmHeapUsage = 0x11;
|
||||
constexpr uint8_t TitleId = 0x12;
|
||||
constexpr u8 PersonalMmHeapSize = 0x10;
|
||||
constexpr u8 PersonalMmHeapUsage = 0x11;
|
||||
constexpr u8 TitleId = 0x12;
|
||||
// 4.0.0+
|
||||
constexpr uint8_t PrivilegedProcessId = 0x13;
|
||||
constexpr u8 PrivilegedProcessId = 0x13;
|
||||
// 5.0.0+
|
||||
constexpr uint8_t UserExceptionContextAddr = 0x14;
|
||||
constexpr u8 UserExceptionContextAddr = 0x14;
|
||||
// 6.0.0+
|
||||
constexpr uint8_t TotalMemoryAvailableWithoutMmHeap = 0x15;
|
||||
constexpr uint8_t TotalMemoryUsedWithoutMmHeap = 0x16;
|
||||
};
|
||||
namespace status {
|
||||
constexpr uint32_t success = 0x0; //!< "Success"
|
||||
constexpr uint32_t unimpl = 0x177202; //!< "Unimplemented behaviour"
|
||||
}
|
||||
constexpr u8 TotalMemoryAvailableWithoutMmHeap = 0x15;
|
||||
constexpr u8 TotalMemoryUsedWithoutMmHeap = 0x16;
|
||||
};
|
||||
namespace kernel::svc {
|
||||
namespace structs {
|
||||
|
||||
}
|
||||
|
||||
namespace lightSwitch::kernel::svc {
|
||||
/**
|
||||
* Set the process heap to a given size (https://switchbrew.org/wiki/SVC#svcSetHeapSize)
|
||||
*/
|
||||
void SetHeapSize(device_state &state);
|
||||
|
||||
/**
|
||||
* 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 QueryMemory(device_state &state);
|
||||
|
||||
/**
|
||||
* Exits the current process (https://switchbrew.org/wiki/SVC#svcExitProcess)
|
||||
*/
|
||||
@ -113,7 +116,7 @@ namespace lightSwitch::kernel::svc {
|
||||
nullptr, // 0x03
|
||||
nullptr, // 0x04
|
||||
nullptr, // 0x05
|
||||
nullptr, // 0x06
|
||||
QueryMemory, // 0x06
|
||||
ExitProcess, // 0x07
|
||||
CreateThread, // 0x08
|
||||
StartThread, // 0x09
|
||||
@ -237,3 +240,4 @@ namespace lightSwitch::kernel::svc {
|
||||
nullptr // 0x7f
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -4,12 +4,14 @@
|
||||
|
||||
namespace lightSwitch::kernel::type {
|
||||
enum class KObjectType {
|
||||
KThread, KProcess
|
||||
KThread, KProcess, KSharedMemory
|
||||
};
|
||||
|
||||
class KObject {
|
||||
public:
|
||||
uint32_t handle;
|
||||
u32 handle;
|
||||
KObjectType type;
|
||||
|
||||
KObject(handle_t handle, KObjectType type) : handle(handle), type(type) {}
|
||||
};
|
||||
}
|
||||
|
76
app/src/main/cpp/switch/kernel/types/KPrivateMemory.cpp
Normal file
76
app/src/main/cpp/switch/kernel/types/KPrivateMemory.cpp
Normal 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;
|
||||
}
|
||||
};
|
50
app/src/main/cpp/switch/kernel/types/KPrivateMemory.h
Normal file
50
app/src/main/cpp/switch/kernel/types/KPrivateMemory.h
Normal 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();
|
||||
};
|
||||
}
|
@ -5,16 +5,16 @@
|
||||
#include <utility>
|
||||
|
||||
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())
|
||||
throw exception("Trying to get TLS slot from full page");
|
||||
slot[index] = true;
|
||||
return Get(index++); // ++ on right will cause increment after evaluation of expression
|
||||
}
|
||||
|
||||
uint64_t KProcess::tls_page_t::Get(uint8_t slot_no) {
|
||||
u64 KProcess::tls_page_t::Get(u8 slot_no) {
|
||||
if (slot_no >= constant::tls_slots)
|
||||
throw exception("TLS slot is out of range");
|
||||
return address + (constant::tls_slot_size * slot_no);
|
||||
@ -24,26 +24,31 @@ namespace lightSwitch::kernel::type {
|
||||
return slot[constant::tls_slots - 1];
|
||||
}
|
||||
|
||||
uint64_t KProcess::GetTLSSlot(bool init) {
|
||||
u64 KProcess::GetTLSSlot(bool init) {
|
||||
if (!init)
|
||||
for (auto &tls_page: tls_pages) {
|
||||
if (!tls_page->Full())
|
||||
return tls_page->ReserveSlot();
|
||||
}
|
||||
uint64_t address = MapPrivate(0, PAGE_SIZE, {true, true, false}, memory::Region::tls);
|
||||
tls_pages.push_back(std::make_shared<tls_page_t>(address));
|
||||
auto tls_mem = std::make_shared<KPrivateMemory>(KPrivateMemory(state, 0, 0, PAGE_SIZE, {true, true, false}, Memory::Type::ThreadLocal, main_thread));
|
||||
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();
|
||||
if (init)
|
||||
tls_page->ReserveSlot(); // User-mode exception handling
|
||||
return tls_page->ReserveSlot();
|
||||
}
|
||||
|
||||
KProcess::KProcess(pid_t pid, uint64_t entry_point, uint64_t stack_base, uint64_t stack_size, const device_state &state, handle_t handle) : state(state), handle(handle), main_thread_stack_sz(stack_size), KObject(handle, KObjectType::KProcess) {
|
||||
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;
|
||||
main_thread = pid;
|
||||
state.nce->WaitRdy(pid);
|
||||
thread_map[main_thread] = std::make_shared<KThread>(handle_index, pid, entry_point, 0, stack_base + stack_size, GetTLSSlot(true), constant::default_priority, this, state);
|
||||
NewHandle(std::static_pointer_cast<KObject>(thread_map[main_thread]));
|
||||
MapPrivateRegion(0, constant::def_heap_size, {true, true, true}, Memory::Type::Heap, Memory::Region::heap);
|
||||
for (auto ®ion : 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)
|
||||
if (mem_fd == -1) throw exception(fmt::format("Cannot open file descriptor to /proc/{}/mem", pid));
|
||||
}
|
||||
@ -61,12 +66,12 @@ namespace lightSwitch::kernel::type {
|
||||
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)
|
||||
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};
|
||||
fregs.regs[0] = entry_point;
|
||||
fregs.regs[1] = stack_top;
|
||||
@ -78,111 +83,19 @@ namespace lightSwitch::kernel::type {
|
||||
return thread;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T KProcess::ReadMemory(uint64_t address) const {
|
||||
T item{};
|
||||
ReadMemory(&item, address, sizeof(T));
|
||||
return item;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void KProcess::WriteMemory(T &item, uint64_t address) const {
|
||||
WriteMemory(&item, address, sizeof(T));
|
||||
}
|
||||
|
||||
void KProcess::ReadMemory(void *destination, uint64_t offset, size_t size) const {
|
||||
state.logger->Write(Logger::DEBUG, "ReadMemory for DE: 0x{:X}, OF: 0x{:X}, SZ: 0x{:X}", (uint64_t) destination, offset, size);
|
||||
void KProcess::ReadMemory(void *destination, u64 offset, size_t size) const {
|
||||
pread64(mem_fd, destination, size, offset);
|
||||
}
|
||||
|
||||
void KProcess::WriteMemory(void *source, uint64_t offset, size_t size) const {
|
||||
state.logger->Write(Logger::DEBUG, "WriteMemory for SRC: 0x{:X}, OF: 0x{:X}, SZ: 0x{:X}", (uint64_t) source, offset, size);
|
||||
void KProcess::WriteMemory(void *source, u64 offset, size_t size) const {
|
||||
pwrite64(mem_fd, source, size, offset);
|
||||
}
|
||||
|
||||
uint64_t MapPrivateFunc(uint64_t address, size_t size, uint64_t perms) {
|
||||
int flags = MAP_PRIVATE | MAP_ANONYMOUS; // NOLINT(hicpp-signed-bitwise)
|
||||
if (address) flags |= MAP_FIXED; // NOLINT(hicpp-signed-bitwise)
|
||||
return reinterpret_cast<uint64_t>(mmap(reinterpret_cast<void *>(address), size, static_cast<int>(perms), flags, -1, 0));
|
||||
}
|
||||
|
||||
uint64_t KProcess::MapPrivate(uint64_t address, size_t size, const memory::Permission perms) {
|
||||
user_pt_regs fregs = {0};
|
||||
fregs.regs[0] = address;
|
||||
fregs.regs[1] = size;
|
||||
fregs.regs[2] = static_cast<uint64_t>(perms.get());
|
||||
state.nce->ExecuteFunction(reinterpret_cast<void *>(MapPrivateFunc), fregs, main_thread);
|
||||
if (reinterpret_cast<void *>(fregs.regs[0]) == MAP_FAILED)
|
||||
throw exception("An error occurred while mapping private region");
|
||||
state.logger->Write(Logger::DEBUG, "MapPrivate for ADR: 0x{:X}, SZ: 0x{:X}", fregs.regs[0], size);
|
||||
return fregs.regs[0];
|
||||
}
|
||||
|
||||
uint64_t KProcess::MapPrivate(uint64_t address, size_t size, const memory::Permission perms, const memory::Region region) {
|
||||
uint64_t addr = MapPrivate(address,size, perms);
|
||||
memory_map.insert(std::pair<memory::Region, memory::RegionData>(region, {addr, size, perms}));
|
||||
return addr;
|
||||
}
|
||||
|
||||
uint64_t RemapPrivateFunc(uint64_t address, size_t old_size, size_t size) {
|
||||
return (uint64_t) mremap(reinterpret_cast<void *>(address), old_size, size, 0);
|
||||
}
|
||||
|
||||
void KProcess::RemapPrivate(uint64_t address, size_t old_size, size_t size) {
|
||||
user_pt_regs fregs = {0};
|
||||
fregs.regs[0] = address;
|
||||
fregs.regs[1] = old_size;
|
||||
fregs.regs[2] = size;
|
||||
state.nce->ExecuteFunction(reinterpret_cast<void *>(RemapPrivateFunc), fregs, main_thread);
|
||||
if (reinterpret_cast<void *>(fregs.regs[0]) == MAP_FAILED)
|
||||
throw exception("An error occurred while remapping private region");
|
||||
}
|
||||
|
||||
void KProcess::RemapPrivate(const memory::Region region, size_t size) {
|
||||
memory::RegionData region_data = memory_map.at(region);
|
||||
RemapPrivate(region_data.address, region_data.size, size);
|
||||
region_data.size = size;
|
||||
}
|
||||
|
||||
int UpdatePermissionPrivateFunc(uint64_t address, size_t size, uint64_t perms) {
|
||||
return mprotect(reinterpret_cast<void *>(address), size, static_cast<int>(perms));
|
||||
}
|
||||
|
||||
void KProcess::UpdatePermissionPrivate(uint64_t address, size_t size, const memory::Permission perms) {
|
||||
user_pt_regs fregs = {0};
|
||||
fregs.regs[0] = address;
|
||||
fregs.regs[1] = size;
|
||||
fregs.regs[2] = static_cast<uint64_t>(perms.get());
|
||||
state.nce->ExecuteFunction(reinterpret_cast<void *>(UpdatePermissionPrivateFunc), fregs, main_thread);
|
||||
if (static_cast<int>(fregs.regs[0]) == -1)
|
||||
throw exception("An error occurred while updating private region's permissions");
|
||||
}
|
||||
|
||||
void KProcess::UpdatePermissionPrivate(const memory::Region region, const memory::Permission perms) {
|
||||
memory::RegionData region_data = memory_map.at(region);
|
||||
if (region_data.perms != perms) {
|
||||
UpdatePermissionPrivate(region_data.address, region_data.size, perms);
|
||||
region_data.perms = perms;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t UnmapPrivateFunc(uint64_t address, size_t size) {
|
||||
return static_cast<uint64_t>(munmap(reinterpret_cast<void *>(address), size));
|
||||
}
|
||||
|
||||
void KProcess::UnmapPrivate(uint64_t address, size_t size) {
|
||||
user_pt_regs fregs = {0};
|
||||
fregs.regs[0] = address;
|
||||
fregs.regs[1] = size;
|
||||
state.nce->ExecuteFunction(reinterpret_cast<void *>(UnmapPrivateFunc), fregs, main_thread);
|
||||
if (static_cast<int>(fregs.regs[0]) == -1)
|
||||
throw exception("An error occurred while unmapping private region");
|
||||
}
|
||||
|
||||
void KProcess::UnmapPrivate(const memory::Region region) {
|
||||
memory::RegionData region_data = memory_map.at(region);
|
||||
UnmapPrivate(region_data.address, region_data.size);
|
||||
memory_map.erase(region);
|
||||
std::shared_ptr<KPrivateMemory> KProcess::MapPrivateRegion(u64 address, size_t size, const Memory::Permission perms, const Memory::Type type, const Memory::Region region) {
|
||||
auto item = std::make_shared<KPrivateMemory>(state, address, 0, size, perms, type, main_thread);
|
||||
memory_map[item->address] = item;
|
||||
memory_region_map[region] = item;
|
||||
return item;
|
||||
}
|
||||
|
||||
handle_t KProcess::NewHandle(std::shared_ptr<KObject> obj) {
|
||||
|
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "KThread.h"
|
||||
#include "KPrivateMemory.h"
|
||||
|
||||
namespace lightSwitch::kernel::type {
|
||||
/**
|
||||
@ -15,35 +16,39 @@ namespace lightSwitch::kernel::type {
|
||||
* Read more about TLS here: https://switchbrew.org/wiki/Thread_Local_Storage
|
||||
*/
|
||||
struct tls_page_t {
|
||||
uint64_t address; //!< The address of the page allocated for TLS
|
||||
uint8_t index = 0; //!< The slots are assigned sequentially, this holds the index of the last TLS slot reserved
|
||||
u64 address; //!< The address of the page allocated for TLS
|
||||
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
|
||||
|
||||
/**
|
||||
* @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
|
||||
* @return The address of the reserved slot
|
||||
*/
|
||||
uint64_t ReserveSlot();
|
||||
u64 ReserveSlot();
|
||||
|
||||
/**
|
||||
* Returns the address of a particular slot
|
||||
* @param slot_no The number of the slot to be returned
|
||||
* @return The address of the specified slot
|
||||
*/
|
||||
uint64_t Get(uint8_t slot_no);
|
||||
u64 Get(u8 slot_no);
|
||||
|
||||
/**
|
||||
* @return If the whole page is full or not
|
||||
*/
|
||||
bool Full();
|
||||
};
|
||||
|
||||
/**
|
||||
* @param init If this initializes the first page (As the first TLS slot is reserved)
|
||||
* @return The address of a free TLS slot
|
||||
*/
|
||||
uint64_t GetTLSSlot(bool init);
|
||||
u64 GetTLSSlot(bool init);
|
||||
|
||||
int mem_fd; //!< The file descriptor to the memory of the process
|
||||
const device_state &state; //!< The state of the device
|
||||
@ -54,7 +59,8 @@ namespace lightSwitch::kernel::type {
|
||||
handle_t handle_index = constant::base_handle_index; //!< This is used to keep track of what to map as an handle
|
||||
pid_t main_thread; //!< The PID of the main thread
|
||||
size_t main_thread_stack_sz; //!< The size of the main thread's stack (All other threads map stack themselves so we don't know the size per-se)
|
||||
std::map<memory::Region, memory::RegionData> memory_map; //!< A mapping from every memory::Region to it's corresponding memory::RegionData which holds it's address and size
|
||||
std::map<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<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
|
||||
@ -68,7 +74,7 @@ namespace lightSwitch::kernel::type {
|
||||
* @param state The state of the device
|
||||
* @param handle A handle to the process, this isn't used if the kernel creates the process
|
||||
*/
|
||||
KProcess(pid_t pid, uint64_t entry_point, uint64_t stack_base, uint64_t stack_size, const device_state& state, handle_t handle=0);
|
||||
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
|
||||
@ -83,7 +89,7 @@ namespace lightSwitch::kernel::type {
|
||||
* @param priority The priority of the thread
|
||||
* @return An instance of KThread class for the corresponding thread
|
||||
*/
|
||||
std::shared_ptr<KThread> CreateThread(uint64_t entry_point, uint64_t entry_arg, uint64_t stack_top, uint8_t priority);
|
||||
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
|
||||
@ -92,7 +98,11 @@ namespace lightSwitch::kernel::type {
|
||||
* @return An object of type T with read data
|
||||
*/
|
||||
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
|
||||
@ -101,7 +111,9 @@ namespace lightSwitch::kernel::type {
|
||||
* @param address The address of the object
|
||||
*/
|
||||
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
|
||||
@ -109,7 +121,7 @@ namespace lightSwitch::kernel::type {
|
||||
* @param offset The address to read from in process memory
|
||||
* @param size The amount of memory to be read
|
||||
*/
|
||||
void ReadMemory(void *destination, uint64_t offset, size_t size) const;
|
||||
void ReadMemory(void *destination, u64 offset, size_t size) const;
|
||||
|
||||
/**
|
||||
* 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 size The amount of memory to be written
|
||||
*/
|
||||
void WriteMemory(void *source, uint64_t offset, size_t size) const;
|
||||
|
||||
/**
|
||||
* Map a chunk of process local memory (private memory)
|
||||
* @param address The address to map to (Can be 0 if address doesn't matter)
|
||||
* @param size The size of the chunk of memory
|
||||
* @param perms The permissions of the memory
|
||||
* @return The address of the mapped chunk (Use when address is 0)
|
||||
*/
|
||||
uint64_t MapPrivate(uint64_t address, size_t size, const memory::Permission perms);
|
||||
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
|
||||
* @param type The type of the memory
|
||||
* @param region The specific region this memory is mapped for
|
||||
* @return The address of the mapped chunk (Use when address is 0)
|
||||
*/
|
||||
uint64_t MapPrivate(uint64_t address, size_t size, const memory::Permission perms, const memory::Region region);
|
||||
|
||||
/**
|
||||
* Remap a chunk of memory as to change the size occupied by it
|
||||
* @param address The address of the mapped memory
|
||||
* @param old_size The current size of the memory
|
||||
* @param size The new size of the memory
|
||||
*/
|
||||
void RemapPrivate(uint64_t address, size_t old_size, size_t size);
|
||||
|
||||
/**
|
||||
* Remap a chunk of memory as to change the size occupied by it
|
||||
* @param region The region of memory that was mapped
|
||||
* @param size The new size of the memory
|
||||
*/
|
||||
void RemapPrivate(const memory::Region region, size_t size);
|
||||
|
||||
/**
|
||||
* Updates the permissions of a chunk of mapped memory
|
||||
* @param address The address of the mapped memory
|
||||
* @param size The size of the mapped memory
|
||||
* @param perms The new permissions to be set for the memory
|
||||
*/
|
||||
void UpdatePermissionPrivate(uint64_t address, size_t size, const memory::Permission perms);
|
||||
|
||||
/**
|
||||
* Updates the permissions of a chunk of mapped memory
|
||||
* @param region The region of memory that was mapped
|
||||
* @param perms The new permissions to be set for the memory
|
||||
*/
|
||||
void UpdatePermissionPrivate(const memory::Region region, const memory::Permission perms);
|
||||
|
||||
/**
|
||||
* Unmap a particular chunk of mapped memory
|
||||
* @param address The address of the mapped memory
|
||||
* @param size The size of the mapped memory
|
||||
*/
|
||||
void UnmapPrivate(uint64_t address, size_t size);
|
||||
|
||||
/**
|
||||
* Unmap a particular chunk of mapped memory
|
||||
* @param region The region of mapped memory
|
||||
*/
|
||||
void UnmapPrivate(const memory::Region region);
|
||||
std::shared_ptr<KPrivateMemory> MapPrivateRegion(u64 address, size_t size, const Memory::Permission perms, const Memory::Type type, const Memory::Region region);
|
||||
|
||||
/**
|
||||
* Creates a new handle to a KObject and adds it to the process handle_table
|
||||
|
119
app/src/main/cpp/switch/kernel/types/KSharedMemory.cpp
Normal file
119
app/src/main/cpp/switch/kernel/types/KSharedMemory.cpp
Normal 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;
|
||||
}
|
||||
};
|
67
app/src/main/cpp/switch/kernel/types/KSharedMemory.h
Normal file
67
app/src/main/cpp/switch/kernel/types/KSharedMemory.h
Normal 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);
|
||||
};
|
||||
}
|
@ -4,7 +4,7 @@
|
||||
#include "../../nce.h"
|
||||
|
||||
namespace lightSwitch::kernel::type {
|
||||
KThread::KThread(handle_t handle, pid_t pid, uint64_t entry_point, uint64_t entry_arg, uint64_t stack_top, uint64_t tls, uint8_t priority, KProcess* parent, const device_state &state) : handle(handle), pid(pid), entry_point(entry_point), entry_arg(entry_arg), stack_top(stack_top), tls(tls), priority(priority), parent(parent), state(state), KObject(handle, KObjectType::KThread) {
|
||||
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);
|
||||
}
|
||||
|
||||
@ -17,9 +17,9 @@ namespace lightSwitch::kernel::type {
|
||||
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;
|
||||
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)
|
||||
throw exception(fmt::format("Couldn't set process priority to {} for PID: {}", li_priority, pid));
|
||||
}
|
||||
|
@ -10,15 +10,15 @@ namespace lightSwitch::kernel::type {
|
||||
private:
|
||||
KProcess *parent; //!< The parent process of this thread
|
||||
const device_state &state; //!< The state of the device
|
||||
uint64_t entry_point; //!< The address to start execution at
|
||||
uint64_t entry_arg; //!< An argument to pass to the process on entry
|
||||
u64 entry_point; //!< The address to start execution at
|
||||
u64 entry_arg; //!< An argument to pass to the process on entry
|
||||
|
||||
public:
|
||||
handle_t handle; //!< The handle of the current thread in it's parent process's handle table
|
||||
pid_t pid; //!< The PID of the current thread (As in kernel PID and not PGID [In short, Linux implements threads as processes that share a lot of stuff at the kernel level])
|
||||
uint64_t stack_top; //!< The top of the stack (Where it starts growing downwards from)
|
||||
uint64_t tls; //!< The address of TLS (Thread Local Storage) slot assigned to the current thread
|
||||
uint8_t priority; //!< Hold the priority of a thread in Nintendo format
|
||||
u64 stack_top; //!< The top of the stack (Where it starts growing downwards from)
|
||||
u64 tls; //!< The address of TLS (Thread Local Storage) slot assigned to the current thread
|
||||
u8 priority; //!< Hold the priority of a thread in Nintendo format
|
||||
|
||||
/**
|
||||
* @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 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.
|
||||
@ -48,6 +48,6 @@ namespace lightSwitch::kernel::type {
|
||||
* We rescale the priority from Nintendo scale to that of Android.
|
||||
* @param priority The priority of the thread in Nintendo format
|
||||
*/
|
||||
void UpdatePriority(uint8_t priority);
|
||||
void UpdatePriority(u8 priority);
|
||||
};
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ namespace lightSwitch::loader {
|
||||
* @param size The amount to read in bytes
|
||||
*/
|
||||
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.read(reinterpret_cast<char *>(output), size);
|
||||
}
|
||||
|
@ -4,42 +4,40 @@
|
||||
namespace lightSwitch::loader {
|
||||
NroLoader::NroLoader(std::string file_path, const device_state &state) : Loader(file_path) {
|
||||
NroHeader header{};
|
||||
ReadOffset((uint32_t *) &header, 0x0, sizeof(NroHeader));
|
||||
ReadOffset((u32 *) &header, 0x0, sizeof(NroHeader));
|
||||
if (header.magic != constant::nro_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.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.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.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);
|
||||
|
||||
ReadOffset(reinterpret_cast<uint8_t *>(constant::base_addr), header.text.offset, header.text.size);
|
||||
ReadOffset(reinterpret_cast<uint8_t *>(constant::base_addr + header.text.size), header.ro.offset, header.ro.size);
|
||||
ReadOffset(reinterpret_cast<uint8_t *>(constant::base_addr + header.text.size + header.ro.size), header.data.offset, header.data.size);
|
||||
ReadOffset(reinterpret_cast<u8 *>(constant::base_addr), header.text.offset, header.text.size);
|
||||
ReadOffset(reinterpret_cast<u8 *>(constant::base_addr + header.text.size), header.ro.offset, header.ro.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
|
||||
auto address = (uint32_t *) constant::base_addr + header.text.offset;
|
||||
size_t text_size = header.text.size / sizeof(uint32_t);
|
||||
auto address = (u32 *) constant::base_addr + header.text.offset;
|
||||
size_t text_size = header.text.size / sizeof(u32);
|
||||
for (size_t iter = 0; iter < text_size; iter++) {
|
||||
auto instr_svc = reinterpret_cast<instr::svc *>(address + iter);
|
||||
auto instr_mrs = reinterpret_cast<instr::mrs *>(address + iter);
|
||||
|
||||
if (instr_svc->verify()) {
|
||||
instr::brk brk(static_cast<uint16_t>(instr_svc->value));
|
||||
address[iter] = *reinterpret_cast<uint32_t *>(&brk);
|
||||
instr::brk brk(static_cast<u16>(instr_svc->value));
|
||||
address[iter] = *reinterpret_cast<u32 *>(&brk);
|
||||
} else if (instr_mrs->verify() && instr_mrs->src_reg == constant::tpidrro_el0) {
|
||||
instr::brk brk(static_cast<uint16_t>(constant::svc_last + 1 + instr_mrs->dst_reg));
|
||||
address[iter] = *reinterpret_cast<uint32_t *>(&brk);
|
||||
instr::brk brk(static_cast<u16>(constant::svc_last + 1 + instr_mrs->dst_reg));
|
||||
address[iter] = *reinterpret_cast<u32 *>(&brk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,28 +7,28 @@ namespace lightSwitch::loader {
|
||||
class NroLoader : public Loader {
|
||||
private:
|
||||
struct NroSegmentHeader {
|
||||
uint32_t offset;
|
||||
uint32_t size;
|
||||
u32 offset;
|
||||
u32 size;
|
||||
}; //!< The structure of a single Segment descriptor in the NRO's header
|
||||
|
||||
struct NroHeader {
|
||||
uint32_t : 32;
|
||||
uint32_t mod_offset;
|
||||
uint64_t : 64;
|
||||
u32 : 32;
|
||||
u32 mod_offset;
|
||||
u64 : 64;
|
||||
|
||||
uint32_t magic;
|
||||
uint32_t version;
|
||||
uint32_t size;
|
||||
uint32_t flags;
|
||||
u32 magic;
|
||||
u32 version;
|
||||
u32 size;
|
||||
u32 flags;
|
||||
|
||||
NroSegmentHeader text;
|
||||
NroSegmentHeader ro;
|
||||
NroSegmentHeader data;
|
||||
|
||||
uint32_t bssSize;
|
||||
uint32_t : 32;
|
||||
uint64_t build_id[4];
|
||||
uint64_t : 64;
|
||||
u32 bssSize;
|
||||
u32 : 32;
|
||||
u64 build_id[4];
|
||||
u64 : 64;
|
||||
|
||||
NroSegmentHeader api_info;
|
||||
NroSegmentHeader dynstr;
|
||||
|
110
app/src/main/cpp/switch/memory.h
Normal file
110
app/src/main/cpp/switch/memory.h
Normal 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
|
||||
};
|
||||
}
|
@ -1,9 +1,6 @@
|
||||
#include <sched.h>
|
||||
#include <linux/uio.h>
|
||||
#include <linux/elf.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <android/sharedmem.h>
|
||||
#include "os.h"
|
||||
#include "nce.h"
|
||||
|
||||
@ -22,22 +19,19 @@ namespace lightSwitch {
|
||||
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);
|
||||
if (status == -1) throw exception(fmt::format("Cannot read instruction from memory, Address: {}, PID: {}, Error: {}", address, pid, strerror(errno)));
|
||||
return *(reinterpret_cast<instr::brk *>(&status));
|
||||
}
|
||||
|
||||
NCE::~NCE() {
|
||||
for (auto®ion : region_memory_map) {
|
||||
munmap(reinterpret_cast<void *>(region.second.address), region.second.size);
|
||||
};
|
||||
void NCE::Initialize(const device_state &state) {
|
||||
this->state = &state;
|
||||
}
|
||||
|
||||
void NCE::Execute(const device_state &state) {
|
||||
this->state = const_cast<device_state *>(&state);
|
||||
int status;
|
||||
while (!halt && !state.os->process_map.empty() && ((curr_pid = wait(&status)) != -1)) {
|
||||
void NCE::Execute() {
|
||||
int status = 0;
|
||||
while (!halt && !state->os->process_map.empty() && ((curr_pid = wait(&status)) != -1)) {
|
||||
if (WIFSTOPPED(status) && (WSTOPSIG(status) == SIGTRAP || WSTOPSIG(status) == SIGSTOP)) { // NOLINT(hicpp-signed-bitwise)
|
||||
auto &curr_regs = register_map[curr_pid];
|
||||
ReadRegisters(curr_regs);
|
||||
@ -45,23 +39,21 @@ namespace lightSwitch {
|
||||
if (instr.verify()) {
|
||||
// We store the instruction value as the immediate value in BRK. 0x0 to 0x7F are SVC, 0x80 to 0x9E is MRS for TPIDRRO_EL0.
|
||||
if (instr.value <= constant::svc_last) {
|
||||
state.os->SvcHandler(static_cast<uint16_t>(instr.value), curr_pid);
|
||||
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) {
|
||||
// Catch MRS that reads the value of TPIDRRO_EL0 (TLS)
|
||||
SetRegister(regs::xreg(instr.value - (constant::svc_last + 1)), state.os->process_map.at(curr_pid)->thread_map.at(curr_pid)->tls);
|
||||
state.logger->Write(Logger::DEBUG, "\"MRS X{}, TPIDRRO_EL0\" has been called", instr.value - (constant::svc_last + 1));
|
||||
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));
|
||||
} else if (instr.value == constant::brk_rdy)
|
||||
continue;
|
||||
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)
|
||||
WriteRegisters(curr_regs);
|
||||
} else {
|
||||
auto& curr_regs = register_map[curr_pid];
|
||||
ReadRegisters(curr_regs);
|
||||
state.logger->Write(Logger::DEBUG, "Thread threw unknown signal, PID: {}, Status: 0x{:X}, INSTR: 0x{:X}", curr_pid, status, *(uint32_t*)curr_regs.pc);
|
||||
state.os->KillThread(curr_pid);
|
||||
state->logger->Write(Logger::DEBUG, "Thread threw unknown signal, PID: {}, Stop Signal: {}", curr_pid, strsignal(WSTOPSIG(status))); // NOLINT(hicpp-signed-bitwise)
|
||||
state->os->KillThread(curr_pid);
|
||||
}
|
||||
ResumeProcess();
|
||||
}
|
||||
@ -76,9 +68,9 @@ namespace lightSwitch {
|
||||
bool was_running = PauseProcess(pid);
|
||||
user_pt_regs backup_regs{};
|
||||
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.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);
|
||||
ResumeProcess(pid);
|
||||
func_regs = WaitRdy(pid);
|
||||
@ -99,7 +91,7 @@ namespace lightSwitch {
|
||||
WriteRegisters(regs, pid);
|
||||
return regs;
|
||||
} else
|
||||
throw exception(fmt::format("An unknown BRK was hit during WaitRdy, PID: {}, BRK value: {}", pid, static_cast<uint64_t>(instr.value)));
|
||||
throw exception(fmt::format("An unknown BRK was hit during WaitRdy, PID: {}, BRK value: {}", pid, static_cast<u64>(instr.value)));
|
||||
} else
|
||||
throw exception(fmt::format("An unknown signal was caused during WaitRdy, PID: {}, Status: 0x{:X}, Signal: {}", pid, status, strsignal(WSTOPSIG(status)))); // NOLINT(hicpp-signed-bitwise)
|
||||
}
|
||||
@ -120,7 +112,7 @@ namespace lightSwitch {
|
||||
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};
|
||||
regs.sp = stack_top;
|
||||
regs.pc = entry_point;
|
||||
@ -130,165 +122,59 @@ namespace lightSwitch {
|
||||
ResumeProcess(pid);
|
||||
}
|
||||
|
||||
uint64_t NCE::GetRegister(regs::xreg reg_id, pid_t pid) {
|
||||
return register_map.at(pid ? pid : curr_pid).regs[reg_id];
|
||||
u64 NCE::GetRegister(xreg reg_id, pid_t pid) {
|
||||
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) {
|
||||
register_map.at(pid ? pid : curr_pid).regs[reg_id] = value;
|
||||
void NCE::SetRegister(xreg reg_id, u64 value, pid_t pid) {
|
||||
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) {
|
||||
return (reinterpret_cast<uint32_t *>(®ister_map.at(pid ? pid : curr_pid).regs))[reg_id * 2];
|
||||
u64 NCE::GetRegister(wreg reg_id, pid_t pid) {
|
||||
return (reinterpret_cast<u32 *>(®ister_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) {
|
||||
(reinterpret_cast<uint32_t *>(®ister_map.at(pid ? pid : curr_pid).regs))[reg_id * 2] = value;
|
||||
void NCE::SetRegister(wreg reg_id, u32 value, pid_t pid) {
|
||||
(reinterpret_cast<u32 *>(®ister_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;
|
||||
switch (reg_id) {
|
||||
case regs::pc:
|
||||
case sreg::pc:
|
||||
return register_map.at(pid).pc;
|
||||
case regs::sp:
|
||||
case sreg::sp:
|
||||
return register_map.at(pid).sp;
|
||||
case regs::pstate:
|
||||
case sreg::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;
|
||||
switch (reg_id) {
|
||||
case regs::pc:
|
||||
case sreg::pc:
|
||||
register_map.at(pid).pc = value;
|
||||
case regs::sp:
|
||||
case sreg::sp:
|
||||
register_map.at(pid).sp = value;
|
||||
case regs::pstate:
|
||||
case sreg::pstate:
|
||||
register_map.at(pid).pstate = value;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t MapSharedFunc(uint64_t address, size_t size, uint64_t perms, uint64_t fd) {
|
||||
return reinterpret_cast<uint64_t>(mmap(reinterpret_cast<void *>(address), size, static_cast<int>(perms), MAP_PRIVATE | MAP_ANONYMOUS | ((address) ? MAP_FIXED : 0), static_cast<int>(fd), 0)); // NOLINT(hicpp-signed-bitwise)
|
||||
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) {
|
||||
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) {
|
||||
int fd = -1;
|
||||
if(state && !state->os->process_map.empty()) {
|
||||
fd = ASharedMemory_create("", size);
|
||||
for(auto& process : state->os->process_map) {
|
||||
user_pt_regs fregs = {0};
|
||||
fregs.regs[0] = address;
|
||||
fregs.regs[1] = size;
|
||||
fregs.regs[2] = static_cast<uint64_t>(perms.get());
|
||||
fregs.regs[3] = static_cast<uint64_t>(fd);
|
||||
ExecuteFunction(reinterpret_cast<void *>(MapSharedFunc), fregs, process.second->main_thread);
|
||||
if (reinterpret_cast<void *>(fregs.regs[0]) == MAP_FAILED)
|
||||
throw exception("An error occurred while mapping shared region in child process");
|
||||
address = fregs.regs[0]; // In case address was 0, this ensures all processes allocate the same address
|
||||
}
|
||||
}
|
||||
void *ptr = mmap((void *) address, size, perms.get(), MAP_PRIVATE | MAP_ANONYMOUS | ((address) ? MAP_FIXED : 0), fd, 0); // NOLINT(hicpp-signed-bitwise)
|
||||
if (ptr == MAP_FAILED)
|
||||
throw exception(fmt::format("An occurred while mapping shared region: {}", strerror(errno)));
|
||||
addr_memory_map[address] = {address, size, perms, fd};
|
||||
return reinterpret_cast<uint64_t>(ptr);
|
||||
}
|
||||
|
||||
uint64_t NCE::MapShared(uint64_t address, size_t size, const memory::Permission perms, const memory::Region region) {
|
||||
uint64_t addr = MapShared(address,size, perms); // TODO: Change MapShared return type to RegionData
|
||||
region_memory_map[region] = {addr, size, perms};
|
||||
return addr;
|
||||
}
|
||||
|
||||
uint64_t RemapSharedFunc(uint64_t address, size_t old_size, size_t size) {
|
||||
return reinterpret_cast<uint64_t>(mremap(reinterpret_cast<void *>(address), old_size, size, 0));
|
||||
}
|
||||
|
||||
void NCE::RemapShared(uint64_t address, size_t old_size, size_t size) {
|
||||
if(state && !state->os->process_map.empty()) {
|
||||
for(auto& process : state->os->process_map) {
|
||||
user_pt_regs fregs = {0};
|
||||
fregs.regs[0] = address;
|
||||
fregs.regs[1] = old_size;
|
||||
fregs.regs[2] = size;
|
||||
ExecuteFunction(reinterpret_cast<void *>(RemapSharedFunc), fregs, process.second->main_thread);
|
||||
if (reinterpret_cast<void *>(fregs.regs[0]) == MAP_FAILED)
|
||||
throw exception("An error occurred while remapping shared region in child process");
|
||||
}
|
||||
}
|
||||
void *ptr = mremap(reinterpret_cast<void *>(address), old_size, size, 0);
|
||||
if (ptr == MAP_FAILED)
|
||||
throw exception(fmt::format("An occurred while remapping shared region: {}", strerror(errno)));
|
||||
addr_memory_map.at(address).size = size;
|
||||
}
|
||||
|
||||
void NCE::RemapShared(const memory::Region region, size_t size) {
|
||||
memory::RegionData& region_data = region_memory_map.at(region);
|
||||
RemapShared(region_data.address, region_data.size, size);
|
||||
region_data.size = size;
|
||||
}
|
||||
|
||||
uint64_t UpdatePermissionSharedFunc(uint64_t address, size_t size, uint64_t perms) {
|
||||
return static_cast<uint64_t>(mprotect(reinterpret_cast<void *>(address), size, static_cast<int>(perms)));
|
||||
}
|
||||
|
||||
void NCE::UpdatePermissionShared(uint64_t address, size_t size, const memory::Permission perms) {
|
||||
if(state && !state->os->process_map.empty()) {
|
||||
for(auto& process : state->os->process_map) {
|
||||
user_pt_regs fregs = {0};
|
||||
fregs.regs[0] = address;
|
||||
fregs.regs[1] = size;
|
||||
fregs.regs[2] = static_cast<uint64_t>(perms.get());
|
||||
ExecuteFunction(reinterpret_cast<void *>(UpdatePermissionSharedFunc), fregs, process.second->main_thread);
|
||||
if (static_cast<int>(fregs.regs[0]) == -1)
|
||||
throw exception("An error occurred while updating shared region's permissions in child process");
|
||||
}
|
||||
}
|
||||
if (mprotect(reinterpret_cast<void *>(address), size, perms.get()) == -1)
|
||||
throw exception(fmt::format("An occurred while updating shared region's permissions: {}", strerror(errno)));
|
||||
addr_memory_map.at(address).perms = perms;
|
||||
}
|
||||
|
||||
void NCE::UpdatePermissionShared(const memory::Region region, const memory::Permission perms) {
|
||||
memory::RegionData& region_data = region_memory_map.at(region);
|
||||
if (region_data.perms != perms) {
|
||||
UpdatePermissionShared(region_data.address, region_data.size, perms);
|
||||
region_data.perms = perms;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t UnmapSharedFunc(uint64_t address, size_t size) {
|
||||
return static_cast<uint64_t>(munmap(reinterpret_cast<void *>(address), size));
|
||||
}
|
||||
|
||||
void NCE::UnmapShared(uint64_t address, size_t size) {
|
||||
if(state && !state->os->process_map.empty()) {
|
||||
for(auto& process : state->os->process_map) {
|
||||
user_pt_regs fregs = {0};
|
||||
fregs.regs[0] = address;
|
||||
fregs.regs[1] = size;
|
||||
ExecuteFunction(reinterpret_cast<void *>(UnmapSharedFunc), fregs, process.second->main_thread);
|
||||
if (static_cast<int>(fregs.regs[0]) == -1)
|
||||
throw exception("An error occurred while unmapping shared region in child process");
|
||||
}
|
||||
}
|
||||
int err = munmap(reinterpret_cast<void *>(address), size);
|
||||
if (err == -1)
|
||||
throw exception(fmt::format("An occurred while unmapping shared region: {}", strerror(errno)));
|
||||
if (addr_memory_map.at(address).fd!=-1) {
|
||||
if (close(addr_memory_map.at(address).fd)==-1)
|
||||
throw exception(fmt::format("An occurred while closing shared file descriptor: {}", strerror(errno)));
|
||||
}
|
||||
}
|
||||
|
||||
void NCE::UnmapShared(const memory::Region region) {
|
||||
memory::RegionData& region_data = region_memory_map.at(region);
|
||||
UnmapShared(region_data.address, region_data.size);
|
||||
region_memory_map.erase(region);
|
||||
size_t NCE::GetSharedSize() {
|
||||
size_t shared_size = 0;
|
||||
for (auto ®ion : memory_map) {
|
||||
shared_size += region.second->size;
|
||||
}
|
||||
return shared_size;
|
||||
}
|
||||
}
|
||||
|
@ -6,13 +6,14 @@
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include "common.h"
|
||||
#include "kernel/types/KSharedMemory.h"
|
||||
|
||||
namespace lightSwitch {
|
||||
class NCE {
|
||||
private:
|
||||
pid_t curr_pid = 0; //!< The PID of the process currently being handled, this is so the PID won't have to be passed into functions like ReadRegister redundantly
|
||||
std::unordered_map<pid_t, user_pt_regs> register_map; //!< A map of all PIDs and their corresponding registers (Whenever they were last updated)
|
||||
device_state *state;
|
||||
const device_state *state; //!< The state of the device
|
||||
|
||||
/**
|
||||
* Reads process registers into the `registers` variable
|
||||
@ -33,22 +34,22 @@ namespace lightSwitch {
|
||||
* @param pid The PID of the process
|
||||
* @return An instance of BRK with the corresponding values
|
||||
*/
|
||||
instr::brk ReadBrk(uint64_t address, pid_t pid=0) const;
|
||||
instr::brk ReadBrk(u64 address, pid_t pid = 0) const;
|
||||
|
||||
public:
|
||||
std::map<memory::Region, memory::RegionData> region_memory_map; //!< A mapping from every memory::Region to it's corresponding memory::RegionData which holds it's address, size and fd
|
||||
std::map<uint64_t, memory::RegionData> addr_memory_map; //!< A mapping from every address to it's corresponding memory::RegionData
|
||||
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<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
|
||||
* @param state The state of the device
|
||||
*/
|
||||
void Execute(const device_state& state);
|
||||
void Execute();
|
||||
|
||||
/**
|
||||
* Execute any arbitrary function on a particular child process
|
||||
@ -86,7 +87,7 @@ namespace lightSwitch {
|
||||
* @param handle The handle of the main thread (Set to value of 1st register)
|
||||
* @param pid The PID of the process
|
||||
*/
|
||||
void StartProcess(uint64_t address, uint64_t entry_arg, uint64_t stack_top, uint32_t handle, pid_t pid) const;
|
||||
void StartProcess(u64 address, u64 entry_arg, u64 stack_top, u32 handle, pid_t pid) const;
|
||||
|
||||
/**
|
||||
* Get the value of a Xn register
|
||||
@ -94,7 +95,7 @@ namespace lightSwitch {
|
||||
* @param pid The PID of the process
|
||||
* @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
|
||||
@ -102,7 +103,7 @@ namespace lightSwitch {
|
||||
* @param value The value to set
|
||||
* @param pid The PID of the process
|
||||
*/
|
||||
void SetRegister(regs::xreg reg_id, uint64_t value, pid_t pid=0);
|
||||
void SetRegister(xreg reg_id, u64 value, pid_t pid = 0);
|
||||
|
||||
/**
|
||||
* Get the value of a Wn register
|
||||
@ -110,7 +111,7 @@ namespace lightSwitch {
|
||||
* @param pid The PID of the process
|
||||
* @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
|
||||
@ -118,7 +119,7 @@ namespace lightSwitch {
|
||||
* @param value The value to set
|
||||
* @param pid The PID of the process
|
||||
*/
|
||||
void SetRegister(regs::wreg reg_id, uint32_t value, pid_t pid=0);
|
||||
void SetRegister(wreg reg_id, u32 value, pid_t pid = 0);
|
||||
|
||||
/**
|
||||
* Get the value of a special register
|
||||
@ -126,7 +127,7 @@ namespace lightSwitch {
|
||||
* @param pid The PID of the process
|
||||
* @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
|
||||
@ -134,69 +135,22 @@ namespace lightSwitch {
|
||||
* @param value The value to set
|
||||
* @param pid The PID of the process
|
||||
*/
|
||||
void SetRegister(regs::sreg reg_id, uint32_t value, pid_t pid=0);
|
||||
|
||||
// TODO: Shared Memory mappings don't update after child process has been created
|
||||
/**
|
||||
* Map a chunk of shared memory
|
||||
* @param address The address to map to (Can be 0 if address doesn't matter)
|
||||
* @param size The size of the chunk of memory
|
||||
* @param perms The permissions of the memory
|
||||
* @return The address of the mapped chunk (Use when address is 0)
|
||||
*/
|
||||
uint64_t MapShared(uint64_t address, size_t size, const memory::Permission perms);
|
||||
void SetRegister(sreg reg_id, u32 value, pid_t pid = 0);
|
||||
|
||||
/**
|
||||
* Map a chunk of shared memory
|
||||
* @param address The address to map to (Can be 0 if address doesn't matter)
|
||||
* @param size The size of the chunk of memory
|
||||
* @param perms The permissions of the memory
|
||||
* @param type The type of the memory
|
||||
* @param region The specific region this memory is mapped for
|
||||
* @return The address of the mapped chunk (Use when address is 0)
|
||||
* @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
|
||||
* @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
|
||||
* @return The total size of allocated shared memory
|
||||
*/
|
||||
void RemapShared(uint64_t address, size_t old_size, size_t size);
|
||||
|
||||
/**
|
||||
* Remap a chunk of memory as to change the size occupied by it
|
||||
* @param region The region of memory that was mapped
|
||||
* @param size The new size of the memory
|
||||
*/
|
||||
void RemapShared(memory::Region region, size_t size);
|
||||
|
||||
/**
|
||||
* Updates the permissions of a chunk of mapped memory
|
||||
* @param address The address of the mapped memory
|
||||
* @param size The size of the mapped memory
|
||||
* @param perms The new permissions to be set for the memory
|
||||
*/
|
||||
void UpdatePermissionShared(uint64_t address, size_t size, const memory::Permission perms);
|
||||
|
||||
/**
|
||||
* Updates the permissions of a chunk of mapped memory
|
||||
* @param region The region of memory that was mapped
|
||||
* @param perms The new permissions to be set for the memory
|
||||
*/
|
||||
void UpdatePermissionShared(memory::Region region, memory::Permission perms);
|
||||
|
||||
/**
|
||||
* Unmap a particular chunk of mapped memory
|
||||
* @param address The address of the mapped memory
|
||||
* @param size The size of the mapped memory
|
||||
*/
|
||||
void UnmapShared(uint64_t address, size_t size);
|
||||
|
||||
/**
|
||||
* Unmap a particular chunk of mapped memory
|
||||
* @param region The region of mapped memory
|
||||
*/
|
||||
void UnmapShared(const memory::Region region);
|
||||
size_t GetSharedSize();
|
||||
};
|
||||
}
|
||||
|
@ -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) {}
|
||||
|
||||
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::transform(rom_ext.begin(), rom_ext.end(), rom_ext.begin(), [](unsigned char c) { return std::tolower(c); });
|
||||
|
||||
if (rom_ext == "nro") loader::NroLoader loader(rom_file, state);
|
||||
else throw exception("Unsupported ROM extension.");
|
||||
|
||||
auto main_process = CreateProcess(state.nce->region_memory_map.at(memory::Region::text).address, constant::def_stack_size);
|
||||
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
|
||||
state.nce->Execute(state);
|
||||
state.nce->Execute();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -29,9 +30,8 @@ namespace lightSwitch::kernel {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
std::shared_ptr<type::KProcess> OS::CreateProcess(uint64_t address, size_t stack_size) {
|
||||
auto *stack = static_cast<uint8_t *>(mmap(nullptr, stack_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_NORESERVE | MAP_ANONYMOUS | MAP_STACK, -1, 0)); // NOLINT(hicpp-signed-bitwise)
|
||||
std::shared_ptr<type::KProcess> OS::CreateProcess(u64 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)
|
||||
if (stack == MAP_FAILED)
|
||||
throw exception("Failed to allocate stack memory");
|
||||
if (mprotect(stack, PAGE_SIZE, PROT_NONE)) {
|
||||
@ -40,8 +40,9 @@ namespace lightSwitch::kernel {
|
||||
}
|
||||
pid_t pid = clone(&ExecuteChild, stack + stack_size, CLONE_FS | SIGCHLD, nullptr); // NOLINT(hicpp-signed-bitwise)
|
||||
if (pid == -1) throw exception(fmt::format("Call to clone() has failed: {}", strerror(errno)));
|
||||
std::shared_ptr<type::KProcess> process = std::make_shared<kernel::type::KProcess>(pid, address, reinterpret_cast<uint64_t>(stack), stack_size, state);
|
||||
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_vec.push_back(pid);
|
||||
state.logger->Write(Logger::DEBUG, "Successfully created process with PID: {}", pid);
|
||||
return process;
|
||||
}
|
||||
@ -55,6 +56,7 @@ namespace lightSwitch::kernel {
|
||||
for (auto&[key, value]: process->thread_map) {
|
||||
process_map.erase(key);
|
||||
};
|
||||
process_vec.erase(std::remove(process_vec.begin(), process_vec.end(), pid), process_vec.end());
|
||||
} else {
|
||||
state.logger->Write(Logger::DEBUG, "Exiting thread with TID: {}", pid);
|
||||
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_thread = this_process->thread_map.at(pid);
|
||||
if (svc::svcTable[svc]) {
|
||||
@ -74,17 +76,15 @@ namespace lightSwitch::kernel {
|
||||
}
|
||||
}
|
||||
|
||||
ipc::IpcResponse OS::IpcHandler(ipc::IpcRequest &request) {
|
||||
ipc::IpcResponse response;
|
||||
switch (request.req_info->type) {
|
||||
case 4: // Request
|
||||
response.SetError(0xDEADBEE5);
|
||||
response.MoveHandle(0xBAADBEEF);
|
||||
response.MoveHandle(0xFACCF00D);
|
||||
response.Generate(state);
|
||||
break;
|
||||
ipc::IpcResponse OS::IpcHandler(handle_t handle) {
|
||||
ipc::IpcRequest request(false, state);
|
||||
ipc::IpcResponse response(false, state);
|
||||
switch (request.header->type) {
|
||||
case static_cast<u16>(ipc::CommandType::Request):
|
||||
case static_cast<u16>(ipc::CommandType::RequestWithContext):
|
||||
throw exception("Services are in-progress");
|
||||
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;
|
||||
}
|
||||
|
@ -16,7 +16,8 @@ namespace lightSwitch::kernel {
|
||||
device_state state; //!< The state of the device
|
||||
|
||||
public:
|
||||
std::unordered_map<pid_t, std::shared_ptr<type::KProcess>> process_map; //!< The state of the device
|
||||
std::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::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
|
||||
* @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
|
||||
@ -50,12 +51,12 @@ namespace lightSwitch::kernel {
|
||||
* @param svc The ID of the SVC to be called
|
||||
* @param pid The PID of the process/thread calling the SVC
|
||||
*/
|
||||
void SvcHandler(uint16_t svc, pid_t pid);
|
||||
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
|
||||
*/
|
||||
ipc::IpcResponse IpcHandler(ipc::IpcRequest &req);
|
||||
ipc::IpcResponse IpcHandler(handle_t handle);
|
||||
};
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import android.view.Window;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.io.File;
|
||||
|
@ -8,7 +8,9 @@ import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.Filter;
|
||||
import android.widget.Filterable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import me.xdrop.fuzzywuzzy.FuzzySearch;
|
||||
import me.xdrop.fuzzywuzzy.model.ExtractedResult;
|
||||
|
||||
|
@ -9,20 +9,36 @@ import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.ListView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.SearchView;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.io.BufferedInputStream;
|
||||
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.nio.charset.StandardCharsets;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
|
||||
import static java.lang.Thread.interrupted;
|
||||
|
||||
public class LogActivity extends AppCompatActivity {
|
||||
@ -129,10 +145,15 @@ public class LogActivity extends AppCompatActivity {
|
||||
Thread share_thread = new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
HttpsURLConnection urlConnection = null;
|
||||
try {
|
||||
URL url = new URL("https://hastebin.com/documents");
|
||||
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
|
||||
urlConnection = (HttpsURLConnection) url.openConnection();
|
||||
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());
|
||||
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8));
|
||||
FileReader fileReader = new FileReader(log_file);
|
||||
@ -143,25 +164,34 @@ public class LogActivity extends AppCompatActivity {
|
||||
bufferedWriter.flush();
|
||||
bufferedWriter.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());
|
||||
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
|
||||
String key = new JSONObject(bufferedReader.lines().collect(Collectors.joining())).getString("key");
|
||||
bufferedReader.close();
|
||||
inputStream.close();
|
||||
urlConnection.disconnect();
|
||||
String result = "https://hastebin.com/" + key;
|
||||
Intent sharingIntent = new Intent(Intent.ACTION_SEND).setType("text/plain").putExtra(Intent.EXTRA_TEXT, result);
|
||||
startActivity(Intent.createChooser(sharingIntent, "Share log url with:"));
|
||||
} 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();
|
||||
} finally {
|
||||
assert urlConnection != null;
|
||||
urlConnection.disconnect();
|
||||
}
|
||||
}});
|
||||
}
|
||||
});
|
||||
share_thread.start();
|
||||
try {
|
||||
share_thread.join(1000);
|
||||
} catch (InterruptedException e) {
|
||||
} catch (Exception e) {
|
||||
Toast.makeText(getApplicationContext(), getString(R.string.share_error), Toast.LENGTH_LONG).show();
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
@ -8,8 +8,8 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
|
||||
class LogItem extends BaseItem {
|
||||
private String content;
|
||||
@ -60,8 +60,8 @@ public class LogAdapter extends HeaderAdapter<LogItem> implements View.OnLongCli
|
||||
@Override
|
||||
public boolean onLongClick(View view) {
|
||||
LogItem item = (LogItem) getItem(((ViewHolder) view.getTag()).position);
|
||||
clipboard.setPrimaryClip(ClipData.newPlainText("Log Message", item.getLevel() + ": " + item.getMessage()));
|
||||
Toast.makeText(view.getContext(), "Copied to clipboard", Snackbar.LENGTH_LONG).show();
|
||||
clipboard.setPrimaryClip(ClipData.newPlainText("Log Message", item.getMessage() + " (" + item.getLevel() + ")"));
|
||||
Toast.makeText(view.getContext(), "Copied to clipboard", Toast.LENGTH_LONG).show();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,7 @@ import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ListView;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.SearchView;
|
||||
@ -18,6 +19,7 @@ import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
package emu.lightswitch;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
|
@ -1,5 +1,10 @@
|
||||
<vector android:alpha="0.85" android:height="24dp"
|
||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#FFF" android:pathData="M5,13h14v-2L5,11v2zM3,17h14v-2L3,15v2zM7,7v2h14L21,7L7,7z"/>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
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>
|
||||
|
@ -1,74 +1,170 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector
|
||||
android:height="108dp"
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:viewportHeight="108"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#008577"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#008577"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
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>
|
||||
|
@ -1,5 +1,10 @@
|
||||
<vector android:alpha="0.85" android:height="24dp"
|
||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<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 xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
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>
|
||||
|
@ -1,5 +1,10 @@
|
||||
<vector android:alpha="0.6" android:height="24dp"
|
||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<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 xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
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>
|
||||
|
@ -1,5 +1,11 @@
|
||||
<vector android:alpha="0.85" android:height="24dp"
|
||||
android:tint="#FFFFFF" android:viewportHeight="24.0"
|
||||
android:viewportWidth="24.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<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 xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
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>
|
||||
|
@ -1,5 +1,10 @@
|
||||
<vector android:alpha="0.85" android:height="24dp"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<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 xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
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>
|
||||
|
@ -1,5 +1,11 @@
|
||||
<vector android:alpha="0.85" android:height="24dp"
|
||||
android:tint="#FFFFFF" android:viewportHeight="24.0"
|
||||
android:viewportWidth="24.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<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 xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
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>
|
||||
|
@ -12,8 +12,8 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/colorPrimary"
|
||||
android:minHeight="?attr/actionBarSize"
|
||||
android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar"
|
||||
android:popupTheme="@style/ThemeOverlay.MaterialComponents.Light"
|
||||
android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
@ -22,8 +22,8 @@
|
||||
android:id="@+id/log_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:transcriptMode="normal"
|
||||
android:fastScrollEnabled="true"
|
||||
android:transcriptMode="normal"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/toolbar" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -2,17 +2,18 @@
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:orientation="vertical"
|
||||
android:padding="15dp"
|
||||
android:background="?attr/selectableItemBackground">
|
||||
android:padding="15dp">
|
||||
|
||||
<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:textSize="15sp"
|
||||
android:textAppearance="?android:attr/textAppearanceListItem" />
|
||||
android:textAppearance="?android:attr/textAppearanceListItem"
|
||||
android:textSize="15sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_subtitle"
|
||||
@ -21,6 +22,6 @@
|
||||
android:layout_below="@id/text_title"
|
||||
android:layout_alignStart="@id/text_title"
|
||||
android:textAppearance="?android:attr/textAppearanceListItemSecondary"
|
||||
android:textSize="12sp"
|
||||
android:textColor="@android:color/tertiary_text_light" />
|
||||
android:textColor="@android:color/tertiary_text_light"
|
||||
android:textSize="12sp" />
|
||||
</RelativeLayout>
|
@ -12,8 +12,8 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/colorPrimary"
|
||||
android:minHeight="?attr/actionBarSize"
|
||||
android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar"
|
||||
android:popupTheme="@style/ThemeOverlay.MaterialComponents.Light"
|
||||
android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
@ -2,17 +2,17 @@
|
||||
<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:orientation="vertical"
|
||||
android:padding="12dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/colorPrimary"
|
||||
android:textSize="13sp"
|
||||
android:layout_marginStart="5dp"
|
||||
android:layout_marginTop="2dp"
|
||||
android:layout_marginStart="5dp"/>
|
||||
android:textColor="@color/colorPrimary"
|
||||
android:textSize="13sp" />
|
||||
|
||||
</RelativeLayout>
|
@ -5,8 +5,8 @@
|
||||
android:id="@+id/action_search_log"
|
||||
android:icon="@drawable/ic_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
|
||||
android:id="@+id/action_share_log"
|
||||
android:icon="@drawable/ic_share"
|
||||
|
@ -5,8 +5,8 @@
|
||||
android:id="@+id/action_search_main"
|
||||
android:icon="@drawable/ic_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
|
||||
android:id="@+id/action_settings"
|
||||
android:icon="@drawable/ic_settings"
|
||||
|
@ -6,6 +6,7 @@
|
||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||
<item name="colorAccent">@color/colorAccent</item>
|
||||
</style>
|
||||
|
||||
<style name="ToolbarTheme">
|
||||
<!-- Query Text Color -->
|
||||
<item name="android:textColorPrimary">@android:color/white</item>
|
||||
|
Loading…
Reference in New Issue
Block a user