diff --git a/Userland/DevTools/UserspaceEmulator/CMakeLists.txt b/Userland/DevTools/UserspaceEmulator/CMakeLists.txt index e9410a3626..f3b67312e7 100644 --- a/Userland/DevTools/UserspaceEmulator/CMakeLists.txt +++ b/Userland/DevTools/UserspaceEmulator/CMakeLists.txt @@ -19,4 +19,4 @@ set(SOURCES ) serenity_bin(UserspaceEmulator) -target_link_libraries(UserspaceEmulator LibX86 LibDebug LibCore LibPthread) +target_link_libraries(UserspaceEmulator LibX86 LibDebug LibCore LibPthread LibLine) diff --git a/Userland/DevTools/UserspaceEmulator/Emulator.cpp b/Userland/DevTools/UserspaceEmulator/Emulator.cpp index 282f5cbc0b..239f8e6a2c 100644 --- a/Userland/DevTools/UserspaceEmulator/Emulator.cpp +++ b/Userland/DevTools/UserspaceEmulator/Emulator.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2020-2021, Andreas Kling + * Copyright (c) 2021, Leon Albrecht * * SPDX-License-Identifier: BSD-2-Clause */ @@ -12,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -37,12 +39,13 @@ Emulator& Emulator::the() return *s_the; } -Emulator::Emulator(const String& executable_path, const Vector& arguments, const Vector& environment) +Emulator::Emulator(String const& executable_path, Vector const& arguments, Vector const& environment) : m_executable_path(executable_path) , m_arguments(arguments) , m_environment(environment) , m_mmu(*this) , m_cpu(*this) + , m_editor(Line::Editor::construct()) { m_malloc_tracer = make(*this); @@ -118,7 +121,7 @@ void Emulator::setup_stack(Vector aux_vector) for (ssize_t i = aux_vector.size() - 1; i >= 0; --i) { auto& value = aux_vector[i].auxv; - m_cpu.push_buffer((const u8*)&value, sizeof(value)); + m_cpu.push_buffer((u8 const*)&value, sizeof(value)); } m_cpu.push32(shadow_wrap_as_initialized(0)); // char** envp = { envv_entries..., nullptr } @@ -157,7 +160,7 @@ bool Emulator::load_elf() } String interpreter_path; - if (!ELF::validate_program_headers(*(const Elf32_Ehdr*)elf_image_data.data(), elf_image_data.size(), (const u8*)elf_image_data.data(), elf_image_data.size(), &interpreter_path)) { + if (!ELF::validate_program_headers(*(Elf32_Ehdr const*)elf_image_data.data(), elf_image_data.size(), (u8 const*)elf_image_data.data(), elf_image_data.size(), &interpreter_path)) { reportln("failed to validate ELF file"); return false; } @@ -171,7 +174,7 @@ bool Emulator::load_elf() ELF::Image interpreter_image(interpreter_image_data); constexpr FlatPtr interpreter_load_offset = 0x08000000; - interpreter_image.for_each_program_header([&](const ELF::Image::ProgramHeader& program_header) { + interpreter_image.for_each_program_header([&](ELF::Image::ProgramHeader const& program_header) { // Loader is not allowed to have its own TLS regions VERIFY(program_header.type() != PT_TLS); @@ -214,22 +217,28 @@ int Emulator::exec() constexpr bool trace = false; while (!m_shutdown) { - m_cpu.save_base_eip(); + if (m_steps_til_pause) [[likely]] { + m_cpu.save_base_eip(); + auto insn = X86::Instruction::from_stream(m_cpu, true, true); + // Exec cycle + if constexpr (trace) { + outln("{:p} \033[33;1m{}\033[0m", m_cpu.base_eip(), insn.to_string(m_cpu.base_eip(), symbol_provider)); + } - auto insn = X86::Instruction::from_stream(m_cpu, true, true); + (m_cpu.*insn.handler())(insn); - if constexpr (trace) { - outln("{:p} \033[33;1m{}\033[0m", m_cpu.base_eip(), insn.to_string(m_cpu.base_eip(), symbol_provider)); - } + if constexpr (trace) { + m_cpu.dump(); + } - (m_cpu.*insn.handler())(insn); + if (m_pending_signals) [[unlikely]] { + dispatch_one_pending_signal(); + } + if (m_steps_til_pause > 0) + m_steps_til_pause--; - if constexpr (trace) { - m_cpu.dump(); - } - - if (m_pending_signals) [[unlikely]] { - dispatch_one_pending_signal(); + } else { + handle_repl(); } } @@ -239,6 +248,104 @@ int Emulator::exec() return m_exit_status; } +void Emulator::handle_repl() +{ + // Console interface + // FIXME: Previous Instruction**s** + // FIXME: Function names (base, call, jump) + auto saved_eip = m_cpu.eip(); + m_cpu.save_base_eip(); + auto insn = X86::Instruction::from_stream(m_cpu, true, true); + // FIXME: This does not respect inlineing + // another way of getting the current function is at need + if (auto const* region = load_library_from_adress(m_cpu.base_eip())) { + auto separator_index = region->name().find(":").value(); + String lib_name = region->name().substring(0, separator_index); + String lib_path = lib_name; + if (region->name().contains(".so")) + lib_path = String::formatted("/usr/lib/{}", lib_path); + + auto it = m_dynamic_library_cache.find(lib_path); + auto& elf = it->value.debug_info->elf(); + String symbol = elf.symbolicate(m_cpu.base_eip() - region->base()); + outln("[{}]: {}", lib_name, symbol); + } + + outln("==> {}", create_instruction_line(m_cpu.base_eip(), insn)); + for (int i = 0; i < 7; ++i) { + m_cpu.save_base_eip(); + insn = X86::Instruction::from_stream(m_cpu, true, true); + outln(" {}", create_instruction_line(m_cpu.base_eip(), insn)); + } + // We don't want to increase EIP here, we just want the instructions + m_cpu.set_eip(saved_eip); + + outln(); + m_cpu.dump(); + outln(); + + auto line_or_error = m_editor->get_line(">> "); + if (line_or_error.is_error()) + return; + + // FIXME: find a way to find a global symbol-address for run-until-call + auto help = [] { + outln("Available commands:"); + outln("continue, c: Continue the execution"); + outln("quit, q: Quit the execution (this will \"kill\" the program and run checks)"); + outln("ret, r: Run until function returns"); + outln("step, s [count]: Execute [count] instructions and then halt"); + outln("signal, sig [number:int], send signal to emulated program (default: sigint:2)"); + }; + auto line = line_or_error.release_value(); + if (line.is_empty()) { + if (m_editor->history().is_empty()) { + help(); + return; + } + line = m_editor->history().last().entry; + } + + auto parts = line.split_view(' ', false); + m_editor->add_to_history(line); + + if (parts[0].is_one_of("s"sv, "step"sv)) { + if (parts.size() == 1) { + m_steps_til_pause = 1; + return; + } + auto number = AK::StringUtils::convert_to_int(parts[1]); + if (!number.has_value()) { + outln("usage \"step [count]\"\n\tcount can't be less than 1"); + return; + } + m_steps_til_pause = number.value(); + } else if (parts[0].is_one_of("c"sv, "continue"sv)) { + m_steps_til_pause = -1; + } else if (parts[0].is_one_of("r"sv, "ret"sv)) { + m_run_til_return = true; + // FIXME: This may be uninitialized + m_watched_addr = m_mmu.read32({ 0x23, m_cpu.ebp().value() + 4 }).value(); + m_steps_til_pause = -1; + } else if (parts[0].is_one_of("q"sv, "quit"sv)) { + m_shutdown = true; + } else if (parts[0].is_one_of("sig"sv, "signal"sv)) { + if (parts.size() == 1) { + did_receive_signal(SIGINT); + return; + } else if (parts.size() == 2) { + auto number = AK::StringUtils::convert_to_int(parts[1]); + if (number.has_value()) { + did_receive_signal(number.value()); + return; + } + } + outln("Usage: sig [signal:int], default: SINGINT:2"); + } else { + help(); + } +} + Vector Emulator::raw_backtrace() { Vector backtrace; @@ -257,13 +364,13 @@ Vector Emulator::raw_backtrace() return backtrace; } -const MmapRegion* Emulator::find_text_region(FlatPtr address) +MmapRegion const* Emulator::find_text_region(FlatPtr address) { - const MmapRegion* matching_region = nullptr; + MmapRegion const* matching_region = nullptr; mmu().for_each_region([&](auto& region) { if (!is(region)) return IterationDecision::Continue; - const auto& mmap_region = static_cast(region); + auto const& mmap_region = static_cast(region); if (!(mmap_region.is_executable() && address >= mmap_region.base() && address < mmap_region.base() + mmap_region.size())) return IterationDecision::Continue; matching_region = &mmap_region; @@ -272,15 +379,15 @@ const MmapRegion* Emulator::find_text_region(FlatPtr address) return matching_region; } -String Emulator::create_backtrace_line(FlatPtr address) +// FIXME: This interface isn't the nicest +MmapRegion const* Emulator::load_library_from_adress(FlatPtr address) { - auto minimal = String::formatted("=={{{}}}== {:p}", getpid(), (void*)address); - const auto* region = find_text_region(address); + auto const* region = find_text_region(address); if (!region) - return minimal; + return {}; auto separator_index = region->name().find(':'); if (!separator_index.has_value()) - return minimal; + return {}; String lib_name = region->name().substring(0, separator_index.value()); String lib_path = lib_name; @@ -290,11 +397,26 @@ String Emulator::create_backtrace_line(FlatPtr address) if (!m_dynamic_library_cache.contains(lib_path)) { auto file_or_error = MappedFile::map(lib_path); if (file_or_error.is_error()) - return minimal; + return {}; auto debug_info = make(make(file_or_error.value()->bytes())); m_dynamic_library_cache.set(lib_path, CachedELF { file_or_error.release_value(), move(debug_info) }); } + return region; +} + +String Emulator::create_backtrace_line(FlatPtr address) +{ + auto minimal = String::formatted("=={{{}}}== {:p}", getpid(), (void*)address); + auto const* region = load_library_from_adress(address); + if (!region) + return minimal; + // FIXME: This is redundant + auto separator_index = region->name().find(":").value(); + String lib_name = region->name().substring(0, separator_index); + String lib_path = lib_name; + if (region->name().contains(".so")) + lib_path = String::formatted("/usr/lib/{}", lib_path); auto it = m_dynamic_library_cache.find(lib_path); auto& elf = it->value.debug_info->elf(); @@ -309,7 +431,7 @@ String Emulator::create_backtrace_line(FlatPtr address) return line_without_source_info; } -void Emulator::dump_backtrace(const Vector& backtrace) +void Emulator::dump_backtrace(Vector const& backtrace) { for (auto& address : backtrace) { reportln("{}", create_backtrace_line(address)); @@ -321,15 +443,45 @@ void Emulator::dump_backtrace() dump_backtrace(raw_backtrace()); } +String Emulator::create_instruction_line(FlatPtr address, X86::Instruction insn) +{ + auto minimal = String::formatted("{:p}: {}", (void*)address, insn.to_string(address)); + auto const* region = load_library_from_adress(address); + if (!region) + return minimal; + // FIXME: This is redundant + auto separator_index = region->name().find(":").value(); + String lib_name = region->name().substring(0, separator_index); + String lib_path = lib_name; + if (region->name().contains(".so")) + lib_path = String::formatted("/usr/lib/{}", lib_path); + + auto it = m_dynamic_library_cache.find(lib_path); + auto& elf = it->value.debug_info->elf(); + String symbol = elf.symbolicate(address - region->base()); + + auto source_position = it->value.debug_info->get_source_position(address - region->base()); + if (!source_position.has_value()) + return minimal; + + return String::formatted("{:p}: {} \e[34;1m{}\e[0m:{}", (void*)address, insn.to_string(address), LexicalPath(source_position.value().file_path).basename(), source_position.value().line_number); +} + static void emulator_signal_handler(int signum) { Emulator::the().did_receive_signal(signum); } +static void emulator_sigint_handler(int signum) +{ + Emulator::the().did_receive_sigint(signum); +} + void Emulator::register_signal_handlers() { for (int signum = 0; signum < NSIG; ++signum) signal(signum, emulator_signal_handler); + signal(SIGINT, emulator_sigint_handler); } enum class DefaultSignalAction { @@ -488,7 +640,7 @@ void Emulator::setup_signal_trampoline() mmu().add_region(move(trampoline_region)); } -bool Emulator::find_malloc_symbols(const MmapRegion& libc_text) +bool Emulator::find_malloc_symbols(MmapRegion const& libc_text) { auto file_or_error = MappedFile::map("/usr/lib/libc.so"); if (file_or_error.is_error()) @@ -518,14 +670,14 @@ bool Emulator::find_malloc_symbols(const MmapRegion& libc_text) void Emulator::dump_regions() const { - const_cast(m_mmu).for_each_region([&](const Region& region) { + const_cast(m_mmu).for_each_region([&](Region const& region) { reportln("{:p}-{:p} {:c}{:c}{:c} {} {}{}{} ", region.base(), region.end() - 1, region.is_readable() ? 'R' : '-', region.is_writable() ? 'W' : '-', region.is_executable() ? 'X' : '-', - is(region) ? static_cast(region).name() : "", + is(region) ? static_cast(region).name() : "", is(region) ? "(mmap) " : "", region.is_stack() ? "(stack) " : "", region.is_text() ? "(text) " : ""); diff --git a/Userland/DevTools/UserspaceEmulator/Emulator.h b/Userland/DevTools/UserspaceEmulator/Emulator.h index 70badf8a78..bc3081353f 100644 --- a/Userland/DevTools/UserspaceEmulator/Emulator.h +++ b/Userland/DevTools/UserspaceEmulator/Emulator.h @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -29,14 +30,15 @@ class Emulator { public: static Emulator& the(); - Emulator(const String& executable_path, const Vector& arguments, const Vector& environment); + Emulator(String const& executable_path, Vector const& arguments, Vector const& environment); bool load_elf(); void dump_backtrace(); - void dump_backtrace(const Vector&); + void dump_backtrace(Vector const&); Vector raw_backtrace(); int exec(); + void handle_repl(); u32 virt_syscall(u32 function, u32 arg1, u32 arg2, u32 arg3); SoftMMU& mmu() { return m_mmu; } @@ -48,7 +50,34 @@ public: bool is_in_libsystem() const; bool is_in_libc() const; + void pause() + { + m_steps_til_pause = 0; + m_run_til_return = false; + } + ALWAYS_INLINE void return_callback(FlatPtr addr) + { + if (m_run_til_return) [[unlikely]] { + if (addr == m_watched_addr) + pause(); + } + } + ALWAYS_INLINE void call_callback(FlatPtr addr) + { + if (m_run_til_call) [[unlikely]] { + if (addr == m_watched_addr) + pause(); + } + } + void did_receive_signal(int signum) { m_pending_signals |= (1 << signum); } + void did_receive_sigint(int) + { + if (m_steps_til_pause == 0) + m_shutdown = true; + else + pause(); + } void dump_regions() const; @@ -174,15 +203,24 @@ private: int virt$msyscall(FlatPtr); int virt$futex(FlatPtr); - bool find_malloc_symbols(const MmapRegion& libc_text); + bool find_malloc_symbols(MmapRegion const& libc_text); void dispatch_one_pending_signal(); - const MmapRegion* find_text_region(FlatPtr address); + MmapRegion const* find_text_region(FlatPtr address); + MmapRegion const* load_library_from_adress(FlatPtr address); + String symbol_at(FlatPtr address); String create_backtrace_line(FlatPtr address); + String create_instruction_line(FlatPtr address, X86::Instruction insn); bool m_shutdown { false }; int m_exit_status { 0 }; + i64 m_steps_til_pause { -1 }; + bool m_run_til_return { false }; + bool m_run_til_call { false }; + FlatPtr m_watched_addr { 0 }; + RefPtr m_editor; + FlatPtr m_malloc_symbol_start { 0 }; FlatPtr m_malloc_symbol_end { 0 }; FlatPtr m_realloc_symbol_start { 0 }; diff --git a/Userland/DevTools/UserspaceEmulator/SoftCPU.cpp b/Userland/DevTools/UserspaceEmulator/SoftCPU.cpp index 71c0f29f1c..3b506b9234 100644 --- a/Userland/DevTools/UserspaceEmulator/SoftCPU.cpp +++ b/Userland/DevTools/UserspaceEmulator/SoftCPU.cpp @@ -1175,6 +1175,9 @@ void SoftCPU::CALL_RM32(const X86::Instruction& insn) auto address = insn.modrm().read32(*this, insn); warn_if_uninitialized(address, "call rm32"); set_eip(address.value()); + // FIXME: this won't catch at the moment due to us not having a way to set + // the watch point + m_emulator.call_callback(address.value()); } void SoftCPU::CALL_imm16(const X86::Instruction&) { TODO_INSN(); } @@ -1185,6 +1188,9 @@ void SoftCPU::CALL_imm32(const X86::Instruction& insn) { push32(shadow_wrap_as_initialized(eip())); set_eip(eip() + (i32)insn.imm32()); + // FIXME: this won't catch at the moment due to us not having a way to set + // the watch point + m_emulator.call_callback(eip() + (i32)insn.imm32()); } void SoftCPU::CBW(const X86::Instruction&) @@ -3128,6 +3134,8 @@ void SoftCPU::RET(const X86::Instruction& insn) auto ret_address = pop32(); warn_if_uninitialized(ret_address, "ret"); set_eip(ret_address.value()); + + m_emulator.return_callback(ret_address.value()); } void SoftCPU::RETF(const X86::Instruction&) { TODO_INSN(); } @@ -3140,6 +3148,8 @@ void SoftCPU::RET_imm16(const X86::Instruction& insn) warn_if_uninitialized(ret_address, "ret imm16"); set_eip(ret_address.value()); set_esp({ esp().value() + insn.imm16(), esp().shadow() }); + + m_emulator.return_callback(ret_address.value()); } template diff --git a/Userland/DevTools/UserspaceEmulator/main.cpp b/Userland/DevTools/UserspaceEmulator/main.cpp index bf4faa3a30..aafc1bfc8e 100644 --- a/Userland/DevTools/UserspaceEmulator/main.cpp +++ b/Userland/DevTools/UserspaceEmulator/main.cpp @@ -20,11 +20,15 @@ bool g_report_to_debug = false; int main(int argc, char** argv, char** env) { Vector arguments; + bool pause_on_startup { false }; Core::ArgsParser parser; parser.set_stop_on_first_non_option(true); parser.add_option(g_report_to_debug, "Write reports to the debug log", "report-to-debug", 0); + parser.add_option(pause_on_startup, "Pause on startup", "pause", 'p'); + parser.add_positional_argument(arguments, "Command to emulate", "command"); + parser.parse(argc, argv); String executable_path; @@ -59,5 +63,9 @@ int main(int argc, char** argv, char** env) reportln("pthread_setname_np: {}", strerror(rc)); return 1; } + + if (pause_on_startup) + emulator.pause(); + return emulator.exec(); }