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