{ "bl", "List breakpoints", nullptr, nullptr, cmd_breakpoint_list }, { "sw", "Stack walk for the current or supplied thread", "sw ([0-9]+|\\*)?", "sw, sw 34, sw *", cmd_stackwalk }, { "~", "Switch to thread", "~ [0-9]+", "~ 42", cmd_switchthread }, { "d", "Disassemble the next/previous N instructions (default N = 4)", "d (@?-?[0-9]+)? (-?[0-9]+)?", "d, d -4, d 5, d @36 3", cmd_disassemble }, { "x", "Examine memory", "x [mp|fp] [0-9]+ ([0-9]+)?", "x mp 3, x fp 20 6", cmd_examine }, { "set" , "Set a debug option", "set [ _a-zA-Z0-9]*", "'set' for available options", set_value_cmd }, { "?", "Print help", nullptr, nullptr, cmd_help }, // Undocumented commands { "tlk~*", nullptr, nullptr, nullptr, cmd_tlk_tilde_star } }; const auto cmds_length = sizeof(cmds) / sizeof(cmds[0]); dbg_cmd_exec_t find_cmd(const std::string &cmd_maybe) { for (auto i = std::size_t{}; i < cmds_length; ++i) { if (cmd_maybe.compare(cmds[i].cmd) == 0) return cmds[i].exec; } // [TODO] Didn't find the cmd, try ignoring case? throw debug_cmd_error_t{ "Unknown command. '?' for help." }; } void print_help() { auto msg = std::stringstream{}; msg << "Commands:\n"; for (auto i = std::size_t{}; i < cmds_length; ++i) { auto &c = cmds[i]; // Ignore if undocumented if (c.description == nullptr) continue; msg << " " << c.cmd << " " << c.description << "\n"; if (c.syntax != nullptr) msg << " Syntax: " << c.syntax << "\n"; if (c.example != nullptr) msg << " Example(s): " << c.example << "\n"; } debug_print_info(msg.str()); } void execute_single_command(dbg_cmd_cxt_t &cxt, std::string cmd) { const auto cmd_tokens = disvm::util::split(cmd); if (cmd_tokens.empty()) return; try { auto cmd_exec = find_cmd(cmd_tokens[0]); cmd_exec(cmd_tokens, cxt); cxt.last_successful_cmd = std::move(cmd); } catch (const debug_cmd_error_t &e) { debug_print_error(e.what()); } } void prompt(debugger &debugger, vm_registers_t &r) { debugger.controller->suspend_all_threads(); if (debugger.first_break) { debugger.first_break = false; std::cout << console_modifiers::green_font << console_modifiers::bold << "'?' for help\n" << console_modifiers::reset_all; } std::string cmd; dbg_cmd_cxt_t cxt{ debugger, r }; for (;;) { std::cout << console_modifiers::green_font << console_modifiers::bold << cxt.current_register->thread.get_thread_id() << " >>> " << console_modifiers::reset_all; std::getline(std::cin, cmd); if (cmd.empty()) cmd = cxt.last_successful_cmd; assert(!cxt.exit_break && "Precondition on exit_break state before execute failed"); execute_single_command(cxt, std::move(cmd)); if (cxt.exit_break) break; } debugger.controller->resume_all_threads(); }} std::ostream& operator<<(std::ostream &ss, const debugger_options o){ ss << "Break on enter: " << std::boolalpha << disvm::util::has_flag(o, debugger_options::break_on_enter) << "\n" << "Break on module load: " << std::boolalpha << disvm::util::has_flag(o, debugger_options::break_on_module_load) << "\n" << "Break on exception: " << std::boolalpha << disvm::util::has_flag(o, debugger_options::break_on_exception) << "\n"; return ss;} debugger::debugger(const debugger_options opt) : _options{ opt } , _tool_id{ 0 } , controller{ nullptr } , first_break{ true }{ } std::vector<std::string> debugger::resolve_to_function_source_line( module_id_t vm_id, vm_pc_t begin_pc, vm_pc_t end_pc, function_name_format_t fmt) const{ auto iter = _vm_id_to_symbol_info.find(vm_id); if (iter == std::cend(_vm_id_to_symbol_info)) return{}; auto &d = *(iter->second.iter); // Position the debug symbol iterator d.set_current_pc(begin_pc); std::vector<std::string> resolved_functions_by_pc(end_pc - begin_pc + 1); vm_pc_t next_pc = begin_pc; do { if (next_pc > end_pc) break; std::stringstream ss; const auto function_name = d.current_function_name(fmt); if (!function_name.empty()) ss << function_name << " at "; const auto source_loc = d.current_source_location(); ss << d.get_source_by_id(source_loc.begin_source_id) << ':' << source_loc.begin_line; resolved_functions_by_pc[next_pc - begin_pc] = std::move(ss.str()); } while (d.try_advance_pc(advance_pc_t::next_debug_statement, &next_pc)); return resolved_functions_by_pc;} std::vector<std::string> debugger::get_module_functions(module_id_t vm_id, const char *match) const{ std::vector<std::string> funcs; auto iter = _vm_id_to_symbol_info.find(vm_id); if (iter != std::cend(_vm_id_to_symbol_info)) { auto f_infos = iter->second.data->get_functions(function_name_format_t::declaration); for (auto &fi : f_infos) { // Only return functions that match the supplied string if (match != nullptr && fi.name.find(match) == std::string::npos) continue; std::stringstream ss; ss << "[" << std::setw(5) << fi.entry_pc << "," << std::setw(5) << fi.limit_pc << "] " << fi.name; funcs.push_back(ss.str()); } } return funcs;} bool debugger::symbols_exist(module_id_t vm_id) const{ return _vm_id_to_symbol_info.find(vm_id) != std::cend(_vm_id_to_symbol_info);} std::string debugger::get_option(const std::string &option) const{ auto iter = _debugger_options.find(option); if (iter == std::cend(_debugger_options)) return{}; return iter->second;} void debugger::set_option(const std::string &option, std::string value){ _debugger_options[option] = std::move(value);} void debugger::on_load(vm_tool_controller_t &controller_, std::size_t tool_id){ _tool_id = tool_id; controller = &controller_; _event_cookies.emplace(controller->subscribe_event(vm_event_t::thread_begin, [](vm_event_t, vm_event_context_t &cxt) { auto t = cxt.value1.thread; auto ss = std::stringstream{}; ss << "Thread " << t->get_thread_id() << " started in " << t->get_registers().module_ref->module->module_name->str() << " @" << t->get_registers().pc; debug_print_info(ss.str()); })); _event_cookies.emplace(controller->subscribe_event(vm_event_t::thread_end, [](vm_event_t, vm_event_context_t &cxt) { auto ss = std::stringstream{}; ss << "Thread " << cxt.value1.thread->get_thread_id() << " ended."; debug_print_info(ss.str()); })); _event_cookies.emplace(controller->subscribe_event(vm_event_t::exception_raised, [&](vm_event_t, vm_event_context_t &cxt) { auto ss = std::stringstream{}; ss << "Exception '" << cxt.value2.str->str() << "' thrown."; debug_print_info(ss.str()); if (disvm::util::has_flag(_options, debugger_options::break_on_exception)) prompt(*this, *cxt.value1.registers); })); _event_cookies.emplace(controller->subscribe_event(vm_event_t::exception_unhandled, [&](vm_event_t, vm_event_context_t &cxt) { auto ss = std::stringstream{}; ss << "Unhandled exception '" << cxt.value2.str->str() << "'"; debug_print_error(ss.str()); prompt(*this, *cxt.value1.registers); })); _event_cookies.emplace(controller->subscribe_event(vm_event_t::module_vm_load, [&](vm_event_t, vm_event_context_t &cxt) { auto loaded_mod = cxt.value1.loaded_mod; assert(loaded_mod != nullptr); load_symbols(*loaded_mod); })); _event_cookies.emplace(controller->subscribe_event(vm_event_t::module_thread_load, [&](vm_event_t, vm_event_context_t &cxt) { auto m = (*cxt.value2.module); auto ss = std::stringstream{}; ss << "Module " << m->module_name->str() << " loaded"; if (disvm::util::has_flag(m->header.runtime_flag, runtime_flags_t::builtin)) ss << " (built-in)"; debug_print_info(ss.str()); // If the break on enter flag was set, it will be true // and the first module load will be the user entry module. if (disvm::util::has_flag(_options, debugger_options::break_on_enter)) { // Reset the break on enter flag and set a breakpoint // on the entry PC for the module - 'break on enter' _options ^= debugger_options::break_on_enter; breakpoint_cookies.emplace(controller->set_breakpoint(m, m->header.entry_pc)); } if (disvm::util::has_flag(_options, debugger_options::break_on_module_load)) prompt(*this, *cxt.value1.registers); })); _event_cookies.emplace(controller->subscribe_event(vm_event_t::breakpoint, [&](vm_event_t, vm_event_context_t &cxt) { auto ss = std::stringstream{}; ss << "Breakpoint hit"; if (cxt.value2.cookie != 0) ss << " [" << cxt.value2.cookie << "]"; debug_print_info(ss.str()); auto cmd = get_option({ "bp-cmd" }); if (!cmd.empty()) { dbg_cmd_cxt_t dbg_cxt{ *this, *cxt.value1.registers, true }; execute_single_command(dbg_cxt, std::move(cmd)); } prompt(*this, *cxt.value1.registers); })); _event_cookies.emplace(controller->subscribe_event(vm_event_t::trap, [&](vm_event_t, vm_event_context_t &cxt) { auto cmd = get_option({ "step-cmd" }); if (!cmd.empty()) { dbg_cmd_cxt_t dbg_cxt{ *this, *cxt.value1.registers, true }; execute_single_command(dbg_cxt, std::move(cmd)); } prompt(*this, *cxt.value1.registers); }));} void debugger::on_unload(){ assert(controller != nullptr); for (auto ec : _event_cookies) controller->unsubscribe_event(ec); controller = nullptr;} void debugger::load_symbols(const disvm::loaded_vm_module_t &loaded_mod){ auto m = loaded_mod.module.lock(); assert(m != nullptr && "Loaded module event should have valid module reference"); auto symbol_extension = ".sbl"; std::string symbol_path_maybe{ loaded_mod.origin->str() }; const auto ext_index = symbol_path_maybe.find_last_of('.'); if (ext_index == std::string::npos) { // Module doesn't have an extension so just append the symbol extension. symbol_path_maybe.append(symbol_extension); } else { // Replace the module extension with the symbol extension. symbol_path_maybe.replace(ext_index, std::string::npos, symbol_extension); } std::ifstream sbl_file{ symbol_path_maybe, std::ifstream::in }; if (!sbl_file.is_open()) return; std::unique_ptr<symbol_data_t> symbol_data; try { symbol_data = disvm::symbol::read(sbl_file); } catch (const std::runtime_error &e) { // File with assumed symbol name/extension is not the correct format auto msg = std::stringstream{}; msg << "Assumed symbol file '" << symbol_path_maybe << "' has invalid symbol format. (" << e.what() << ")"; debug_print_error(msg.str()); return; } // Check if the symbol data matches the loaded module if (0 != symbol_data->get_module_name().compare(m->module_name->str()) || symbol_data->get_instruction_count() != m->code_section.size()) { // Symbol file does not match the loaded modules - stale file? auto msg = std::stringstream{}; msg << "Symbol file '" << symbol_path_maybe << "' does not match loaded module '" << loaded_mod.origin->str() << "'."; debug_print_error(msg.str()); return; } const auto vm_id = loaded_mod.vm_id; assert(_vm_id_to_symbol_info.find(vm_id) == std::end(_vm_id_to_symbol_info) && "Module VM ID should be unique"); auto symbol_iter = symbol_data->get_pc_iter(); _vm_id_to_symbol_info[vm_id] = { std::move(symbol_data), std::move(symbol_iter) }; auto msg = std::stringstream{}; msg << "Symbol file '" << symbol_path_maybe << "' loaded for '" << loaded_mod.origin->str() << "'."; debug_print_info(msg.str());}Lines 1001–1412 of 1412. Previous lines
{ "bl", "List breakpoints", nullptr, nullptr, cmd_breakpoint_list }, { "sw", "Stack walk for the current or supplied thread", "sw ([0-9]+|\\*)?", "sw, sw 34, sw *", cmd_stackwalk }, { "~", "Switch to thread", "~ [0-9]+", "~ 42", cmd_switchthread }, { "d", "Disassemble the next/previous N instructions (default N = 4)", "d (@?-?[0-9]+)? (-?[0-9]+)?", "d, d -4, d 5, d @36 3", cmd_disassemble }, { "x", "Examine memory", "x [mp|fp] [0-9]+ ([0-9]+)?", "x mp 3, x fp 20 6", cmd_examine }, { "set" , "Set a debug option", "set [ _a-zA-Z0-9]*", "'set' for available options", set_value_cmd }, { "?", "Print help", nullptr, nullptr, cmd_help }, // Undocumented commands { "tlk~*", nullptr, nullptr, nullptr, cmd_tlk_tilde_star } }; const auto cmds_length = sizeof(cmds) / sizeof(cmds[0]); dbg_cmd_exec_t find_cmd(const std::string &cmd_maybe) { for (auto i = std::size_t{}; i < cmds_length; ++i) { if (cmd_maybe.compare(cmds[i].cmd) == 0) return cmds[i].exec; } // [TODO] Didn't find the cmd, try ignoring case? throw debug_cmd_error_t{ "Unknown command. '?' for help." }; } void print_help() { auto msg = std::stringstream{}; msg << "Commands:\n"; for (auto i = std::size_t{}; i < cmds_length; ++i) { auto &c = cmds[i]; // Ignore if undocumented if (c.description == nullptr) continue; msg << " " << c.cmd << " " << c.description << "\n"; if (c.syntax != nullptr) msg << " Syntax: " << c.syntax << "\n"; if (c.example != nullptr) msg << " Example(s): " << c.example << "\n"; } debug_print_info(msg.str()); } void execute_single_command(dbg_cmd_cxt_t &cxt, std::string cmd) { const auto cmd_tokens = disvm::util::split(cmd); if (cmd_tokens.empty()) return; try { auto cmd_exec = find_cmd(cmd_tokens[0]); cmd_exec(cmd_tokens, cxt); cxt.last_successful_cmd = std::move(cmd); } catch (const debug_cmd_error_t &e) { debug_print_error(e.what()); } } void prompt(debugger &debugger, vm_registers_t &r) { debugger.controller->suspend_all_threads(); if (debugger.first_break) { debugger.first_break = false; std::cout << console_modifiers::green_font << console_modifiers::bold << "'?' for help\n" << console_modifiers::reset_all; } std::string cmd; dbg_cmd_cxt_t cxt{ debugger, r }; for (;;) { std::cout << console_modifiers::green_font << console_modifiers::bold << cxt.current_register->thread.get_thread_id() << " >>> " << console_modifiers::reset_all; std::getline(std::cin, cmd); if (cmd.empty()) cmd = cxt.last_successful_cmd; assert(!cxt.exit_break && "Precondition on exit_break state before execute failed"); execute_single_command(cxt, std::move(cmd)); if (cxt.exit_break) break; } debugger.controller->resume_all_threads(); } } std::ostream& operator<<(std::ostream &ss, const debugger_options o) { ss << "Break on enter: " << std::boolalpha << disvm::util::has_flag(o, debugger_options::break_on_enter) << "\n" << "Break on module load: " << std::boolalpha << disvm::util::has_flag(o, debugger_options::break_on_module_load) << "\n" << "Break on exception: " << std::boolalpha << disvm::util::has_flag(o, debugger_options::break_on_exception) << "\n"; return ss; } debugger::debugger(const debugger_options opt) : _options{ opt } , _tool_id{ 0 } , controller{ nullptr } , first_break{ true } { } std::vector<std::string> debugger::resolve_to_function_source_line( module_id_t vm_id, vm_pc_t begin_pc, vm_pc_t end_pc, function_name_format_t fmt) const { auto iter = _vm_id_to_symbol_info.find(vm_id); if (iter == std::cend(_vm_id_to_symbol_info)) return{}; auto &d = *(iter->second.iter); // Position the debug symbol iterator d.set_current_pc(begin_pc); std::vector<std::string> resolved_functions_by_pc(end_pc - begin_pc + 1); vm_pc_t next_pc = begin_pc; do { if (next_pc > end_pc) break; std::stringstream ss; const auto function_name = d.current_function_name(fmt); if (!function_name.empty()) ss << function_name << " at "; const auto source_loc = d.current_source_location(); ss << d.get_source_by_id(source_loc.begin_source_id) << ':' << source_loc.begin_line; resolved_functions_by_pc[next_pc - begin_pc] = std::move(ss.str()); } while (d.try_advance_pc(advance_pc_t::next_debug_statement, &next_pc)); return resolved_functions_by_pc; } std::vector<std::string> debugger::get_module_functions(module_id_t vm_id, const char *match) const { std::vector<std::string> funcs; auto iter = _vm_id_to_symbol_info.find(vm_id); if (iter != std::cend(_vm_id_to_symbol_info)) { auto f_infos = iter->second.data->get_functions(function_name_format_t::declaration); for (auto &fi : f_infos) { // Only return functions that match the supplied string if (match != nullptr && fi.name.find(match) == std::string::npos) continue; std::stringstream ss; ss << "[" << std::setw(5) << fi.entry_pc << "," << std::setw(5) << fi.limit_pc << "] " << fi.name; funcs.push_back(ss.str()); } } return funcs; } bool debugger::symbols_exist(module_id_t vm_id) const { return _vm_id_to_symbol_info.find(vm_id) != std::cend(_vm_id_to_symbol_info); } std::string debugger::get_option(const std::string &option) const { auto iter = _debugger_options.find(option); if (iter == std::cend(_debugger_options)) return{}; return iter->second; } void debugger::set_option(const std::string &option, std::string value) { _debugger_options[option] = std::move(value); } void debugger::on_load(vm_tool_controller_t &controller_, std::size_t tool_id) { _tool_id = tool_id; controller = &controller_; _event_cookies.emplace(controller->subscribe_event(vm_event_t::thread_begin, [](vm_event_t, vm_event_context_t &cxt) { auto t = cxt.value1.thread; auto ss = std::stringstream{}; ss << "Thread " << t->get_thread_id() << " started in " << t->get_registers().module_ref->module->module_name->str() << " @" << t->get_registers().pc; debug_print_info(ss.str()); })); _event_cookies.emplace(controller->subscribe_event(vm_event_t::thread_end, [](vm_event_t, vm_event_context_t &cxt) { auto ss = std::stringstream{}; ss << "Thread " << cxt.value1.thread->get_thread_id() << " ended."; debug_print_info(ss.str()); })); _event_cookies.emplace(controller->subscribe_event(vm_event_t::exception_raised, [&](vm_event_t, vm_event_context_t &cxt) { auto ss = std::stringstream{}; ss << "Exception '" << cxt.value2.str->str() << "' thrown."; debug_print_info(ss.str()); if (disvm::util::has_flag(_options, debugger_options::break_on_exception)) prompt(*this, *cxt.value1.registers); })); _event_cookies.emplace(controller->subscribe_event(vm_event_t::exception_unhandled, [&](vm_event_t, vm_event_context_t &cxt) { auto ss = std::stringstream{}; ss << "Unhandled exception '" << cxt.value2.str->str() << "'"; debug_print_error(ss.str()); prompt(*this, *cxt.value1.registers); })); _event_cookies.emplace(controller->subscribe_event(vm_event_t::module_vm_load, [&](vm_event_t, vm_event_context_t &cxt) { auto loaded_mod = cxt.value1.loaded_mod; assert(loaded_mod != nullptr); load_symbols(*loaded_mod); })); _event_cookies.emplace(controller->subscribe_event(vm_event_t::module_thread_load, [&](vm_event_t, vm_event_context_t &cxt) { auto m = (*cxt.value2.module); auto ss = std::stringstream{}; ss << "Module " << m->module_name->str() << " loaded"; if (disvm::util::has_flag(m->header.runtime_flag, runtime_flags_t::builtin)) ss << " (built-in)"; debug_print_info(ss.str()); // If the break on enter flag was set, it will be true // and the first module load will be the user entry module. if (disvm::util::has_flag(_options, debugger_options::break_on_enter)) { // Reset the break on enter flag and set a breakpoint // on the entry PC for the module - 'break on enter' _options ^= debugger_options::break_on_enter; breakpoint_cookies.emplace(controller->set_breakpoint(m, m->header.entry_pc)); } if (disvm::util::has_flag(_options, debugger_options::break_on_module_load)) prompt(*this, *cxt.value1.registers); })); _event_cookies.emplace(controller->subscribe_event(vm_event_t::breakpoint, [&](vm_event_t, vm_event_context_t &cxt) { auto ss = std::stringstream{}; ss << "Breakpoint hit"; if (cxt.value2.cookie != 0) ss << " [" << cxt.value2.cookie << "]"; debug_print_info(ss.str()); auto cmd = get_option({ "bp-cmd" }); if (!cmd.empty()) { dbg_cmd_cxt_t dbg_cxt{ *this, *cxt.value1.registers, true }; execute_single_command(dbg_cxt, std::move(cmd)); } prompt(*this, *cxt.value1.registers); })); _event_cookies.emplace(controller->subscribe_event(vm_event_t::trap, [&](vm_event_t, vm_event_context_t &cxt) { auto cmd = get_option({ "step-cmd" }); if (!cmd.empty()) { dbg_cmd_cxt_t dbg_cxt{ *this, *cxt.value1.registers, true }; execute_single_command(dbg_cxt, std::move(cmd)); } prompt(*this, *cxt.value1.registers); })); } void debugger::on_unload() { assert(controller != nullptr); for (auto ec : _event_cookies) controller->unsubscribe_event(ec); controller = nullptr; } void debugger::load_symbols(const disvm::loaded_vm_module_t &loaded_mod) { auto m = loaded_mod.module.lock(); assert(m != nullptr && "Loaded module event should have valid module reference"); auto symbol_extension = ".sbl"; std::string symbol_path_maybe{ loaded_mod.origin->str() }; const auto ext_index = symbol_path_maybe.find_last_of('.'); if (ext_index == std::string::npos) { // Module doesn't have an extension so just append the symbol extension. symbol_path_maybe.append(symbol_extension); } else { // Replace the module extension with the symbol extension. symbol_path_maybe.replace(ext_index, std::string::npos, symbol_extension); } std::ifstream sbl_file{ symbol_path_maybe, std::ifstream::in }; if (!sbl_file.is_open()) return; std::unique_ptr<symbol_data_t> symbol_data; try { symbol_data = disvm::symbol::read(sbl_file); } catch (const std::runtime_error &e) { // File with assumed symbol name/extension is not the correct format auto msg = std::stringstream{}; msg << "Assumed symbol file '" << symbol_path_maybe << "' has invalid symbol format. (" << e.what() << ")"; debug_print_error(msg.str()); return; } // Check if the symbol data matches the loaded module if (0 != symbol_data->get_module_name().compare(m->module_name->str()) || symbol_data->get_instruction_count() != m->code_section.size()) { // Symbol file does not match the loaded modules - stale file? auto msg = std::stringstream{}; msg << "Symbol file '" << symbol_path_maybe << "' does not match loaded module '" << loaded_mod.origin->str() << "'."; debug_print_error(msg.str()); return; } const auto vm_id = loaded_mod.vm_id; assert(_vm_id_to_symbol_info.find(vm_id) == std::end(_vm_id_to_symbol_info) && "Module VM ID should be unique"); auto symbol_iter = symbol_data->get_pc_iter(); _vm_id_to_symbol_info[vm_id] = { std::move(symbol_data), std::move(symbol_iter) }; auto msg = std::stringstream{}; msg << "Symbol file '" << symbol_path_maybe << "' loaded for '" << loaded_mod.origin->str() << "'."; debug_print_info(msg.str()); }