diff --git a/app/src/main/cpp/lightswitch.cpp b/app/src/main/cpp/lightswitch.cpp index 3a502abd..46675663 100644 --- a/app/src/main/cpp/lightswitch.cpp +++ b/app/src/main/cpp/lightswitch.cpp @@ -6,26 +6,18 @@ #include "switch/device.h" #include "switch/common.h" -std::thread *game_thread; - -void signal_handle(int sig_no) { - throw lightSwitch::exception("A signal has been raised: " + std::to_string(sig_no)); -} +std::thread *emu_thread; void thread_main(std::string rom_path, std::string pref_path, std::string log_path) { auto log = std::make_shared(log_path); log->write(lightSwitch::Logger::INFO, "Launching ROM {0}", rom_path); -// long long i = 0; -// while(true){ -// log->write(lightSwitch::Logger::INFO, "#{0}", i); -// sleep(1); -// i++; -// } + auto settings = std::make_shared(pref_path); try { lightSwitch::device device(log, settings); device.run(rom_path); - log->write(lightSwitch::Logger::INFO, "Emulation has ended!"); + + log->write(lightSwitch::Logger::INFO, "Emulation has ended."); } catch (std::exception &e) { log->write(lightSwitch::Logger::ERROR, e.what()); } catch (...) { @@ -40,10 +32,11 @@ Java_emu_lightswitch_MainActivity_loadFile(JNIEnv *env, jobject instance, jstrin const char *rom_path = env->GetStringUTFChars(rom_path_, 0); const char *pref_path = env->GetStringUTFChars(pref_path_, 0); const char *log_path = env->GetStringUTFChars(log_path_, 0); - // std::signal(SIGABRT, signal_handle); - if (game_thread) pthread_kill(game_thread->native_handle(), SIGABRT); + + if (emu_thread) pthread_kill(emu_thread->native_handle(), SIGABRT); + // Running on UI thread is not a good idea, any crashes and such will be propagated - game_thread = new std::thread(thread_main, std::string(rom_path, strlen(rom_path)), std::string(pref_path, strlen(pref_path)), std::string(log_path, strlen(log_path))); + emu_thread = new std::thread(thread_main, std::string(rom_path, strlen(rom_path)), std::string(pref_path, strlen(pref_path)), std::string(log_path, strlen(log_path))); env->ReleaseStringUTFChars(rom_path_, rom_path); env->ReleaseStringUTFChars(pref_path_, pref_path); env->ReleaseStringUTFChars(log_path_, log_path); diff --git a/app/src/main/cpp/switch/common.h b/app/src/main/cpp/switch/common.h index 526a39c1..fd6911d3 100644 --- a/app/src/main/cpp/switch/common.h +++ b/app/src/main/cpp/switch/common.h @@ -63,9 +63,8 @@ namespace lightSwitch { struct device_state { std::shared_ptr cpu; - std::shared_ptr mem; + std::shared_ptr memory; std::shared_ptr settings; std::shared_ptr logger; }; - //typedef std::shared_ptr device_state; } \ No newline at end of file diff --git a/app/src/main/cpp/switch/constant.h b/app/src/main/cpp/switch/constant.h index 1f22490a..a5b7410a 100644 --- a/app/src/main/cpp/switch/constant.h +++ b/app/src/main/cpp/switch/constant.h @@ -23,7 +23,6 @@ namespace lightSwitch { namespace instr { // https://developer.arm.com/docs/ddi0596/latest/base-instructions-alphabetic-order/brk-breakpoint-instruction - // For some reason if value is set to uint16_t it isn't read correctly ? struct brk { brk(uint16_t val) { start = 0x0; // First 5 bits of an BRK instruction are 0 @@ -35,9 +34,9 @@ namespace lightSwitch { return (start == 0x0 && end == 0x6A1); } - uint8_t start:5; - uint32_t value:16; - uint16_t end:11; + uint8_t start : 5; + uint32_t value : 16; + uint16_t end : 11; }; // https://developer.arm.com/docs/ddi0596/latest/base-instructions-alphabetic-order/svc-supervisor-call @@ -46,9 +45,9 @@ namespace lightSwitch { return (start == 0x1 && end == 0x6A0); } - uint8_t start:5; - uint32_t value:16; - uint16_t end:11; + uint8_t start : 5; + uint32_t value : 16; + uint16_t end : 11; }; // https://developer.arm.com/docs/ddi0596/latest/base-instructions-alphabetic-order/mrs-move-system-register @@ -57,16 +56,12 @@ namespace lightSwitch { return (end == 0xD53); } - uint8_t Xt:5; - uint32_t Sreg:15; - uint16_t end:12; + uint8_t dst_reg : 5; + uint32_t src_reg : 15; + uint16_t end : 12; }; }; - enum xreg { - x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21, x22, x23, x24, x25, x26, x27, x28, x29, x30 - }; - enum wreg { - w0, w1, w2, w3, w4, w5, w6, w7, w8, w9, w10, w11, w12, w13, w14, w15, w16, w17, w18, w19, w20, w21, w22, w23, w24, w25, w26, w27, w28, w29, w30 - }; + 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 }; } \ No newline at end of file diff --git a/app/src/main/cpp/switch/device.h b/app/src/main/cpp/switch/device.h index 40ba0ccb..4b4ce192 100644 --- a/app/src/main/cpp/switch/device.h +++ b/app/src/main/cpp/switch/device.h @@ -10,27 +10,18 @@ namespace lightSwitch { std::shared_ptr memory; os::OS os; device_state state; - const std::map ext_case = { - {"nro", 1}, - {"NRO", 1} - }; public: device(std::shared_ptr &logger, std::shared_ptr &settings) : cpu(new hw::Cpu()), memory(new hw::Memory()), state{cpu, memory, settings, logger}, os({cpu, memory, settings, logger}) {}; void run(std::string rom_file) { - try { - switch (ext_case.at(rom_file.substr(rom_file.find_last_of('.') + 1))) { - case 1: { - loader::NroLoader loader(rom_file, state); - break; - } - default: - break; - } - cpu->Execute(hw::Memory::text, memory, os.SvcHandler, &state); - } catch (std::out_of_range &e) { - throw exception("The ROM extension wasn't recognized."); - } + std::string rom_ext = rom_file.substr(rom_file.find_last_of('.') + 1); + std::transform(rom_ext.begin(), rom_ext.end(), rom_ext.begin(), + [](unsigned char c){ return std::tolower(c); }); + + if (rom_ext == "nro") loader::NroLoader loader(rom_file, state); + else throw exception("Unsupported ROM extension."); + + cpu->Execute(hw::Memory::text, memory, os.SvcHandler, &state); } }; }; \ No newline at end of file diff --git a/app/src/main/cpp/switch/hw/cpu.cpp b/app/src/main/cpp/switch/hw/cpu.cpp index 70cf6e60..7ca7857a 100644 --- a/app/src/main/cpp/switch/hw/cpu.cpp +++ b/app/src/main/cpp/switch/hw/cpu.cpp @@ -8,30 +8,30 @@ namespace lightSwitch::hw { long *Cpu::ReadMemory(uint64_t address) { // Return a single word (32-bit) status = ptrace(PTRACE_PEEKDATA, child, address, NULL); - if (status == -1) throw std::runtime_error("Cannot read memory"); + if (status == -1) throw std::runtime_error("Cannot read memory!"); return &status; } void Cpu::WriteMemory(uint64_t address) { // Write a single word (32-bit) status = ptrace(PTRACE_GETREGSET, child, NT_PRSTATUS, &iov); - if (status == -1) throw std::runtime_error("Cannot write memory"); + if (status == -1) throw std::runtime_error("Cannot write memory!"); } void Cpu::ReadRegisters() { // Read all registers into 'regs' iov = {®s, sizeof(regs)}; status = ptrace(PTRACE_GETREGSET, child, NT_PRSTATUS, &iov); - if (status == -1) throw std::runtime_error("Cannot read registers"); + if (status == -1) throw std::runtime_error("Cannot read registers!"); } void Cpu::WriteRegisters() { // Write all registers from 'regs' iov = {®s, sizeof(regs)}; status = ptrace(PTRACE_SETREGSET, child, NT_PRSTATUS, &iov); - if (status == -1) throw std::runtime_error("Cannot write registers"); + if (status == -1) throw std::runtime_error("Cannot write registers!"); } void Cpu::ResumeProcess() { // Resumes a process stopped due to a signal status = ptrace(PTRACE_CONT, child, NULL, NULL); - if (status == -1) throw std::runtime_error("Cannot resume process"); + if (status == -1) throw std::runtime_error("Cannot resume process!"); } void Cpu::WriteBreakpoint(uint64_t address_, uint64_t size) { @@ -39,13 +39,20 @@ namespace lightSwitch::hw { for (uint64_t iter = 0; iter < size; iter++) { auto instr_svc = reinterpret_cast(address + iter); auto instr_mrs = reinterpret_cast(address + iter); + if (instr_svc->verify()) { - // syslog(LOG_WARNING, "Found SVC call: 0x%X, At location 0x%X", instr_svc->value, ((uint64_t)address)+iter); +#ifdef NDEBUG + syslog(LOG_WARNING, "Found SVC call: 0x%X, At location 0x%X", instr_svc->value, ((uint64_t)address)+iter); +#endif + instr::brk brk(static_cast(instr_svc->value)); address[iter] = *reinterpret_cast(&brk); - } else if (instr_mrs->verify() && instr_mrs->Sreg == constant::tpidrro_el0) { - // syslog(LOG_WARNING, "Found MRS call: 0x%X, At location 0x%X", instr_mrs->Xt, ((uint64_t)address)+iter); - instr::brk brk(static_cast(constant::svc_last + 1 + instr_mrs->Xt)); + } else if (instr_mrs->verify() && instr_mrs->src_reg == constant::tpidrro_el0) { +#ifdef NDEBUG + syslog(LOG_WARNING, "Found MRS call: 0x%X, At location 0x%X", instr_mrs->dst_reg, ((uint64_t)address)+iter); +#endif + + instr::brk brk(static_cast(constant::svc_last + 1 + instr_mrs->dst_reg)); address[iter] = *reinterpret_cast(&brk); } } @@ -53,22 +60,31 @@ namespace lightSwitch::hw { void Cpu::Execute(Memory::Region region, std::shared_ptr memory, std::function svc_handler, void *device) { tls = memory->region_map.at(hw::Memory::tls).address; - hw::Memory::RegionData rom = memory->region_map.at(hw::Memory::text); - WriteBreakpoint(rom.address, rom.size); - child = ExecuteChild(rom.address); + + hw::Memory::RegionData exec = memory->region_map.at(hw::Memory::text); + WriteBreakpoint(exec.address, exec.size); + + child = ExecuteChild(exec.address); + int stat = 0; while (waitpid(child, &stat, 0)) { if (WIFSTOPPED(stat)) { ReadRegisters(); - //syslog(LOG_INFO, "PC is at 0x%X", regs.pc); + +#ifdef NDEBUG + syslog(LOG_INFO, "PC is at 0x%X", regs.pc); +#endif + if (!regs.pc || regs.pc == 0xBADC0DE) break; + // We store the instruction value as the immediate value. 0x0 to 0x7F are SVC, 0x80 to 0x9E is MRS for TPIDRRO_EL0. auto instr = reinterpret_cast(ReadMemory(regs.pc)); if (instr->verify()) { if (instr->value <= constant::svc_last) { svc_handler(static_cast(instr->value), device); syslog(LOG_ERR, "SVC has been called 0x%X", instr->value); - if (stop) break; + + if (halt) break; } else if (instr->value > constant::svc_last && instr->value <= constant::svc_last + constant::num_regs) { // Catch MRS that reads the value of TPIDRRO_EL0 (TLS) // https://switchbrew.org/wiki/Thread_Local_Storage @@ -76,27 +92,30 @@ namespace lightSwitch::hw { syslog(LOG_ERR, "MRS has been called 0x%X", instr->value - (constant::svc_last + 1)); } else syslog(LOG_ERR, "Received unhandled BRK 0x%X", instr->value); } + regs.pc += 4; // Increment program counter by a single instruction (32 bits) WriteRegisters(); - } else if (WIFEXITED(stat)) - break; + } else if (WIFEXITED(stat)) break; + ResumeProcess(); } + kill(child, SIGABRT); child = 0; - stop = false; + halt = false; // TODO: Global variable } pid_t Cpu::ExecuteChild(uint64_t address) { pid_t pid = fork(); if (!pid) { ptrace(PTRACE_TRACEME, 0, NULL, NULL); - asm volatile("BR %0"::"r"(address)); + asm volatile("br %0" :: "r"(address)); } + return pid; } - void Cpu::StopExecution() { stop = true; } + void Cpu::StopExecution() { halt = true; } uint64_t Cpu::GetRegister(xreg reg_id) { return regs.regs[reg_id]; diff --git a/app/src/main/cpp/switch/hw/cpu.h b/app/src/main/cpp/switch/hw/cpu.h index ef300c52..3fa84e5f 100644 --- a/app/src/main/cpp/switch/hw/cpu.h +++ b/app/src/main/cpp/switch/hw/cpu.h @@ -13,7 +13,7 @@ namespace lightSwitch::hw { class Cpu { private: - bool stop = false; + bool halt = false; long status = 0; pid_t child; iovec iov; diff --git a/app/src/main/cpp/switch/hw/memory.cpp b/app/src/main/cpp/switch/hw/memory.cpp index 4b3e9182..4bb1b562 100644 --- a/app/src/main/cpp/switch/hw/memory.cpp +++ b/app/src/main/cpp/switch/hw/memory.cpp @@ -13,19 +13,23 @@ namespace lightSwitch::hw { void *ptr = mmap((void *) address, size, PROT_EXEC | PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON | MAP_FIXED, 0, 0); if (ptr == MAP_FAILED) throw exception("An occurred while mapping region: " + std::string(strerror(errno))); + region_map.insert(std::pair(region, {address, size})); } void Memory::Remap(Region region, size_t size) { RegionData region_data = region_map.at(region); + void *ptr = mremap(reinterpret_cast(region_data.address), region_data.size, size, 0); if (ptr == MAP_FAILED) throw exception("An occurred while remapping region: " + std::string(strerror(errno))); + region_map[region].size = size; } void Memory::Unmap(Region region) { RegionData region_data = region_map.at(region); + int err = munmap(reinterpret_cast(region_data.address), region_data.size); if (err == -1) throw exception("An occurred while unmapping region: " + std::string(strerror(errno))); diff --git a/app/src/main/cpp/switch/loader/nro.cpp b/app/src/main/cpp/switch/loader/nro.cpp index 51cc75b1..5d4eb3e1 100644 --- a/app/src/main/cpp/switch/loader/nro.cpp +++ b/app/src/main/cpp/switch/loader/nro.cpp @@ -6,7 +6,7 @@ namespace lightSwitch::loader { NroHeader header{}; ReadOffset((uint32_t *) &header, 0x0, sizeof(NroHeader)); if (header.magic != constant::nro_magic) - throw exception(fmt::format("Invalid NRO magic 0x{0:x}", header.magic)); + throw exception(fmt::format("Invalid NRO magic! 0x{0:X}", header.magic)); auto text = new uint32_t[header.text.size](); auto ro = new uint32_t[header.ro.size](); @@ -16,18 +16,25 @@ namespace lightSwitch::loader { ReadOffset(ro, header.ro.offset, header.ro.size); ReadOffset(data, header.data.offset, header.data.size); - state.mem->Map(constant::base_addr, header.text.size, hw::Memory::text); - state.logger->write(Logger::DEBUG, "Successfully mapped region .text to 0x{0:x}, size is 0x{1:x}.", constant::base_addr, header.text.size); - state.mem->Map(constant::base_addr + header.text.size, header.ro.size, hw::Memory::rodata); - state.logger->write(Logger::DEBUG, "Successfully mapped region .ro to 0x{0:x}, size is 0x{1:x}.", constant::base_addr + header.text.size, header.ro.size); - state.mem->Map(constant::base_addr + header.text.size + header.ro.size, header.data.size, hw::Memory::data); - state.logger->write(Logger::DEBUG, "Successfully mapped region .data to 0x{0:x}, size is 0x{1:x}.", constant::base_addr + header.text.size + header.ro.size, header.data.size); - state.mem->Map(constant::base_addr + header.text.size + header.ro.size + header.data.size, header.bssSize, hw::Memory::bss); - state.logger->write(Logger::DEBUG, "Successfully mapped region .bss to 0x{0:x}, size is 0x{1:x}.", constant::base_addr + header.text.size + header.ro.size + header.data.size, header.bssSize); + state.memory->Map(constant::base_addr, header.text.size, hw::Memory::text); + state.logger->write(Logger::DEBUG, "Successfully mapped region .text @ 0x{0:X}, Size = 0x{1:X}", + constant::base_addr, header.text.size); - state.mem->Write(text, constant::base_addr, header.text.size); - state.mem->Write(ro, constant::base_addr + header.text.size, header.ro.size); - state.mem->Write(data, constant::base_addr + header.text.size + header.ro.size, header.data.size); + state.memory->Map(constant::base_addr + header.text.size, header.ro.size, hw::Memory::rodata); + state.logger->write(Logger::DEBUG, "Successfully mapped region .ro @ 0x{0:X}, Size = 0x{1:X}", + constant::base_addr + header.text.size, header.ro.size); + + state.memory->Map(constant::base_addr + header.text.size + header.ro.size, header.data.size, hw::Memory::data); + state.logger->write(Logger::DEBUG, "Successfully mapped region .data @ 0x{0:X}, Size = 0x{1:X}", + constant::base_addr + header.text.size + header.ro.size, header.data.size); + + state.memory->Map(constant::base_addr + header.text.size + header.ro.size + header.data.size, header.bssSize, hw::Memory::bss); + state.logger->write(Logger::DEBUG, "Successfully mapped region .bss @ 0x{0:X}, Size = 0x{1:X}", + constant::base_addr + header.text.size + header.ro.size + header.data.size, header.bssSize); + + state.memory->Write(text, constant::base_addr, header.text.size); + state.memory->Write(ro, constant::base_addr + header.text.size, header.ro.size); + state.memory->Write(data, constant::base_addr + header.text.size + header.ro.size, header.data.size); delete[] text; delete[] ro; diff --git a/app/src/main/cpp/switch/os/ipc.cpp b/app/src/main/cpp/switch/os/ipc.cpp index 9c5a6f90..6db52cd3 100644 --- a/app/src/main/cpp/switch/os/ipc.cpp +++ b/app/src/main/cpp/switch/os/ipc.cpp @@ -13,12 +13,12 @@ namespace lightSwitch::os::ipc { data_pos = ((data_pos - 1) & ~(15U)) + 16; // ceil data_pos with multiples 16 data_ptr = &tls_ptr[data_pos + sizeof(command_struct)]; - state.logger->write(Logger::DEBUG, "Type: 0x{:x}", (uint8_t) req_info->type); + state.logger->write(Logger::DEBUG, "Type: 0x{:X}", (uint8_t) req_info->type); state.logger->write(Logger::DEBUG, "X descriptors: {}", (uint8_t) req_info->x_no); state.logger->write(Logger::DEBUG, "A descriptors: {}", (uint8_t) req_info->a_no); state.logger->write(Logger::DEBUG, "B descriptors: {}", (uint8_t) req_info->b_no); state.logger->write(Logger::DEBUG, "W descriptors: {}", (uint8_t) req_info->w_no); - state.logger->write(Logger::DEBUG, "Raw data offset: 0x{:x}", data_pos); + state.logger->write(Logger::DEBUG, "Raw data offset: 0x{:X}", data_pos); state.logger->write(Logger::DEBUG, "Raw data size: {}", (uint16_t) req_info->data_sz); state.logger->write(Logger::DEBUG, "Payload Command ID: {}", *((uint32_t *) &tls_ptr[data_pos + 8])); } diff --git a/app/src/main/cpp/switch/os/os.cpp b/app/src/main/cpp/switch/os/os.cpp index 2851e7f1..cd7b3b61 100644 --- a/app/src/main/cpp/switch/os/os.cpp +++ b/app/src/main/cpp/switch/os/os.cpp @@ -9,7 +9,7 @@ namespace lightSwitch::os { if (svc::svcTable[svc]) (*svc::svcTable[svc])(state); else { - state.logger->write(Logger::ERROR, "Unimplemented SVC 0x{0:x}", svc); + state.logger->write(Logger::ERROR, "Unimplemented SVC 0x{0:X}", svc); state.cpu->StopExecution(); } } diff --git a/app/src/main/cpp/switch/os/svc.cpp b/app/src/main/cpp/switch/os/svc.cpp index 248dfe27..aab80eff 100644 --- a/app/src/main/cpp/switch/os/svc.cpp +++ b/app/src/main/cpp/switch/os/svc.cpp @@ -7,28 +7,35 @@ namespace lightSwitch::os::svc { void ConnectToNamedPort(device_state &state) { char port[constant::port_size]{0}; - state.mem->Read(port, state.cpu->GetRegister(xreg::x1), constant::port_size); + state.memory->Read(port, state.cpu->GetRegister(xreg::x1), constant::port_size); + if (std::strcmp(port, "sm:") == 0) state.cpu->SetRegister(wreg::w1, constant::sm_handle); else { state.logger->write(Logger::ERROR, "svcConnectToNamedPort tried connecting to invalid port \"{0}\"", port); state.cpu->StopExecution(); } + state.cpu->SetRegister(wreg::w0, 0); } void SendSyncRequest(device_state &state) { - state.logger->write(Logger::DEBUG, "svcSendSyncRequest called for handle 0x{0:x}.", state.cpu->GetRegister(xreg::x0)); + state.logger->write(Logger::DEBUG, "svcSendSyncRequest called for handle 0x{0:X}.", state.cpu->GetRegister(xreg::x0)); + uint8_t tls[constant::tls_ipc_size]; - state.mem->Read(&tls, constant::tls_addr, constant::tls_ipc_size); + state.memory->Read(&tls, constant::tls_addr, constant::tls_ipc_size); + ipc::IpcRequest request(tls, state); + state.cpu->SetRegister(wreg::w0, 0); } void OutputDebugString(device_state &state) { std::string debug(state.cpu->GetRegister(xreg::x1), '\0'); - state.mem->Read((void *) debug.data(), state.cpu->GetRegister(xreg::x0), state.cpu->GetRegister(xreg::x1)); - state.logger->write(Logger::INFO, "ROM Output: {0}", debug.c_str()); + state.memory->Read((void *) debug.data(), state.cpu->GetRegister(xreg::x0), state.cpu->GetRegister(xreg::x1)); + + state.logger->write(Logger::INFO, "svcOutputDebugString: {0}", debug.c_str()); + state.cpu->SetRegister(wreg::w0, 0); }