//// Dis VM// File: debugger.cpp// Author: arr// #include <iostream>#include <fstream>#include <iomanip>#include <sstream>#include <string>#include <algorithm>#include <map>#include <set>#include <utils.h>#include <vm_memory.h>#include <vm_asm.h>#include <exceptions.h>#include "exec.h" using disvm::vm_t;using disvm::loaded_vm_module_t;using disvm::opcode_t; using disvm::runtime::type_descriptor_t;using disvm::runtime::intrinsic_type_desc;using disvm::runtime::word_t;using disvm::runtime::pointer_t;using disvm::runtime::module_id_t;using disvm::runtime::runtime_flags_t;using disvm::runtime::cookie_t;using disvm::runtime::vm_pc_t;using disvm::runtime::vm_registers_t;using disvm::runtime::vm_module_t;using disvm::runtime::vm_module_ref_t;using disvm::runtime::vm_alloc_t;using disvm::runtime::vm_string_t;using disvm::runtime::vm_array_t;using disvm::runtime::vm_list_t;using disvm::runtime::vm_channel_t;using disvm::runtime::vm_thread_state_t;using disvm::runtime::vm_trap_flags_t;using disvm::runtime::vm_term_request;using disvm::runtime::vm_tool_controller_t;using disvm::runtime::vm_event_t;using disvm::runtime::vm_event_context_t; using disvm::symbol::function_name_format_t;using disvm::symbol::advance_pc_t;using disvm::symbol::symbol_data_t; namespace{ // https://en.wikipedia.org/wiki/ANSI_escape_code namespace console_modifiers {#define CSI "\33[" const auto black_font = CSI "30m"; const auto red_font = CSI "31m"; const auto green_font = CSI "32m"; const auto yellow_font = CSI "33m"; const auto blue_font = CSI "34m"; const auto magenta_font = CSI "35m"; const auto cyan_font = CSI "36m"; const auto white_font = CSI "37m"; const auto black_bkg = CSI "40m"; const auto red_bkg = CSI "41m"; const auto green_bkg = CSI "42m"; const auto yellow_bkg = CSI "43m"; const auto blue_bkg = CSI "44m"; const auto magenta_bkg = CSI "45m"; const auto cyan_bkg = CSI "46m"; const auto white_bkg = CSI "47m"; const auto bold = CSI "1m"; const auto reset_all = CSI "0m"; #undef CSI } bool vm_debug_enabled = false; template<typename T> struct vm_dbg_type final { T t; }; struct debug_cmd_error_t : std::runtime_error { debug_cmd_error_t(const char *message) : std::runtime_error{ message } { } }; void debug_print_error(const char *str) { std::cout << console_modifiers::red_font << console_modifiers::bold << "Error: " << str << console_modifiers::reset_all << "\n"; } void debug_print_error(const std::string &str) { debug_print_error(str.c_str()); } void debug_print_info(const std::string &str) { std::cout << console_modifiers::yellow_font << console_modifiers::bold << str << console_modifiers::reset_all << "\n"; } // Debug command context struct dbg_cmd_cxt_t { static std::string last_successful_cmd_option; dbg_cmd_cxt_t(debugger &d, vm_registers_t &r, bool transient_context = false) : dbg{ d } , exit_break{ false } , current_register{ &r } , initial_register{ r } , transient_context{ transient_context } , vm{ d.controller->get_vm_instance() } { last_successful_cmd = dbg.get_option(last_successful_cmd_option); } ~dbg_cmd_cxt_t() { if (!transient_context) dbg.set_option(last_successful_cmd_option, std::move(last_successful_cmd)); } const bool transient_context; bool exit_break; std::string last_successful_cmd; debugger &dbg; const vm_t &vm; const vm_registers_t *current_register; vm_registers_t &initial_register; }; std::string dbg_cmd_cxt_t::last_successful_cmd_option = "last_successful_cmd"; // Debug command exec operation using dbg_cmd_exec_t = void(*)(const std::vector<std::string> &, dbg_cmd_cxt_t &cxt); // Debug command struct dbg_cmd_t { const char *cmd; const char *description; const char *syntax; const char *example; dbg_cmd_exec_t exec; }; std::string stack_to_string(const vm_registers_t &r, debugger &dbg) { auto msg = std::stringstream{}; walk_stack(r, [&msg, &dbg](const pointer_t, const vm_pc_t pc, const vm_module_ref_t &module_ref) { auto func_maybe = dbg.resolve_to_function_source_line(module_ref.module->vm_id, pc, pc, function_name_format_t::name); if (!func_maybe.empty()) { assert(func_maybe.size() == 1); msg << module_ref.module->module_name->str() << "!" << func_maybe[0] << " "; } else { auto module_name = "<No Name>"; if (module_ref.module->module_name != nullptr) module_name = module_ref.module->module_name->str(); msg << module_name; } msg << "@" << pc << "\n"; return true; }); return msg.str(); } std::string registers_to_string(const vm_registers_t &r) { auto register_string = std::stringstream{}; register_string << " Thread ID: " << r.thread.get_thread_id() << "\n Module: " << r.module_ref->module->module_name->str() << "\n PC: " << r.pc; if (!r.module_ref->is_builtin_module()) { assert(static_cast<std::size_t>(r.pc) < r.module_ref->code_section.size()); const auto &op = r.module_ref->code_section[r.pc].op; register_string << "\n Src: " << op.source << "\n Mid: " << op.middle << "\n Dst: " << op.destination; } register_string << "\n"; return register_string.str(); } std::ostream& operator<<(std::ostream &ss, const std::shared_ptr<const type_descriptor_t> &t) { ss << "size: " << t->size_in_bytes; #ifndef NDEBUG ss << " n: " << t->debug_type_name;#endif return ss; } std::ostream& operator<<(std::ostream &ss, const vm_string_t *s) { auto escaped_string = std::stringstream{}; auto str = s->str(); if (str != nullptr) { while (*str != '\0') { switch (*str) { case '\\': escaped_string << "\\\\"; break; case '\a': escaped_string << "\\a"; break; case '\b': escaped_string << "\\b"; break; case '\f': escaped_string << "\\f"; break; case '\n': escaped_string << "\\n"; break; case '\r': escaped_string << "\\r"; break; case '\t': escaped_string << "\\t"; break; case '\v': escaped_string << "\\v"; break; default: escaped_string << *str; } str++; } } ss << escaped_string.str() << " length: " << s->get_length() << " - string"; return ss; } std::ostream& operator<<(std::ostream &ss, const vm_array_t *a) { ss << "length: " << a->get_length() << " - array of " << a->get_element_type(); return ss; } std::ostream& operator<<(std::ostream &ss, const vm_list_t *l) { ss << "length: " << l->get_length() << " - list of " << l->get_element_type(); return ss; } std::ostream& operator<<(std::ostream &ss, const vm_channel_t *c) { ss << "buffer size: " << c->get_buffer_size() << " - channel of" << c->get_data_type(); return ss; } std::ostream& operator<<(std::ostream &ss, const vm_dbg_type<const vm_alloc_t *> &dbg_alloc) { if (!vm_debug_enabled) return ss; return ss << " [[ref: " << dbg_alloc.t->get_ref_count() << " addr: " << reinterpret_cast<const void *>(dbg_alloc.t) << " gc_res: " << reinterpret_cast<const void *>(dbg_alloc.t->gc_reserved) << "]]"; } std::ostream& safe_alloc_print(std::ostream &ss, const vm_alloc_t *alloc, int recurse_depth) { if (alloc == nullptr) return ss << "<nil>"; vm_dbg_type<const vm_alloc_t *> dbg_alloc{ alloc }; if (alloc->alloc_type == intrinsic_type_desc::type<vm_string_t>()) { ss << vm_alloc_t::from_allocation<vm_string_t>(alloc->get_allocation()) << dbg_alloc; } else if (alloc->alloc_type == intrinsic_type_desc::type<vm_array_t>()) { ss << vm_alloc_t::from_allocation<vm_array_t>(alloc->get_allocation()) << dbg_alloc; } else if (alloc->alloc_type == intrinsic_type_desc::type<vm_list_t>()) { ss << vm_alloc_t::from_allocation<vm_list_t>(alloc->get_allocation()) << dbg_alloc; } else if (alloc->alloc_type == intrinsic_type_desc::type<vm_channel_t>()) { ss << vm_alloc_t::from_allocation<vm_channel_t>(alloc->get_allocation()) << dbg_alloc; } else { ss << alloc->alloc_type << " - alloc" << dbg_alloc; const auto base_address = reinterpret_cast<std::uintptr_t>(alloc->get_allocation()); enum_pointer_fields(*alloc->alloc_type, alloc->get_allocation(), [&ss, recurse_depth, base_address](pointer_t *p) { ss << "\n"; std::fill_n(std::ostream_iterator<std::ostream::char_type>{ ss }, 4 * recurse_depth, ' '); const auto p_address = reinterpret_cast<std::uintptr_t>(p); ss << "[" << std::size_t{ p_address - base_address } << "] "; if (recurse_depth < 2) safe_alloc_print(ss, vm_alloc_t::from_allocation(*p), recurse_depth + 1); else ss << "..."; }); } return ss; } std::ostream& operator<<(std::ostream &ss, const vm_alloc_t *alloc) { return safe_alloc_print(ss, alloc, 0); } std::ostream& operator<<(std::ostream &ss, const vm_thread_state_t s) { switch (s) { case vm_thread_state_t::unknown: ss << "unknown"; break; case vm_thread_state_t::blocked_in_alt: ss << "blocked-in-alt"; break; case vm_thread_state_t::blocked_sending: ss << "blocked-sending"; break; case vm_thread_state_t::blocked_receiving: ss << "blocked-receiving"; break; case vm_thread_state_t::debug: ss << "debug"; break; case vm_thread_state_t::ready: ss << "ready"; break; case vm_thread_state_t::running: ss << "running"; break; case vm_thread_state_t::release: ss << "release"; break; case vm_thread_state_t::exiting: ss << "exiting"; break; case vm_thread_state_t::empty_stack: ss << "empty-stack"; break; case vm_thread_state_t::broken: ss << "broken"; break; default: ss << "??"; } return ss; } void cmd_continue(const std::vector<std::string> &, dbg_cmd_cxt_t &cxt) { cxt.exit_break = true; } void cmd_step(const std::vector<std::string> &, dbg_cmd_cxt_t &cxt) { cxt.dbg.controller->set_thread_trap_flag(*cxt.current_register, vm_trap_flags_t::instruction); cxt.exit_break = true; } void cmd_term(const std::vector<std::string> &, dbg_cmd_cxt_t &) { throw vm_term_request{}; } void cmd_print_registers(const std::vector<std::string> &, dbg_cmd_cxt_t &cxt) { auto s = registers_to_string(*cxt.current_register); debug_print_info(s); } void cmd_print_threads(const std::vector<std::string> &, dbg_cmd_cxt_t &cxt) { auto msg = std::stringstream{}; for (auto t : cxt.vm.get_scheduler_control().get_all_threads()) msg << " " << std::setw(5) << t->get_thread_id() << " - " << t->get_registers().current_thread_state << "\n"; debug_print_info(msg.str()); } void cmd_print_modules(const std::vector<std::string> &, dbg_cmd_cxt_t &cxt) { auto msg = std::stringstream{}; cxt.vm.enum_loaded_modules([&msg, &cxt](const loaded_vm_module_t &m) { auto module_instance = m.module.lock(); if (module_instance != nullptr) { msg << std::setw(3) << m.vm_id << ": " << std::setw(16) << module_instance->module_name->str() << std::setw(0) << " - " << m.origin->str(); if (cxt.dbg.symbols_exist(m.vm_id)) msg << " [symbols loaded]"; msg << "\n"; } return true; }); debug_print_info(msg.str()); } void cmd_print_symbols(const std::vector<std::string> &a, dbg_cmd_cxt_t &cxt) { if (a.size() < 2) throw debug_cmd_error_t{ "Must supply a module ID" }; module_id_t vm_id; try { vm_id = std::stoul(a[1]); } catch (...) { throw debug_cmd_error_t{ "Invalid module ID format" }; } if (!cxt.dbg.symbols_exist(vm_id)) throw debug_cmd_error_t{ "No symbols loaded for module" }; // Interpret the last non-module ID value as the partial name to match. const char *name_to_match = nullptr; if (a.size() > 2) name_to_match = a.back().c_str(); auto msg = std::stringstream{}; auto module_funcs = cxt.dbg.get_module_functions(vm_id, name_to_match); msg << " [PC range] Function"; for (auto &f : module_funcs) msg << "\n " << f; debug_print_info(msg.str()); } void cmd_breakpoint_set(const std::vector<std::string> &a, dbg_cmd_cxt_t &cxt) { if (a.size() < 2) throw debug_cmd_error_t{ "Must supply an IP" }; Lines 1–500 of 1412. Load more
// // Dis VM // File: debugger.cpp // Author: arr // #include <iostream> #include <fstream> #include <iomanip> #include <sstream> #include <string> #include <algorithm> #include <map> #include <set> #include <utils.h> #include <vm_memory.h> #include <vm_asm.h> #include <exceptions.h> #include "exec.h" using disvm::vm_t; using disvm::loaded_vm_module_t; using disvm::opcode_t; using disvm::runtime::type_descriptor_t; using disvm::runtime::intrinsic_type_desc; using disvm::runtime::word_t; using disvm::runtime::pointer_t; using disvm::runtime::module_id_t; using disvm::runtime::runtime_flags_t; using disvm::runtime::cookie_t; using disvm::runtime::vm_pc_t; using disvm::runtime::vm_registers_t; using disvm::runtime::vm_module_t; using disvm::runtime::vm_module_ref_t; using disvm::runtime::vm_alloc_t; using disvm::runtime::vm_string_t; using disvm::runtime::vm_array_t; using disvm::runtime::vm_list_t; using disvm::runtime::vm_channel_t; using disvm::runtime::vm_thread_state_t; using disvm::runtime::vm_trap_flags_t; using disvm::runtime::vm_term_request; using disvm::runtime::vm_tool_controller_t; using disvm::runtime::vm_event_t; using disvm::runtime::vm_event_context_t; using disvm::symbol::function_name_format_t; using disvm::symbol::advance_pc_t; using disvm::symbol::symbol_data_t; namespace { // https://en.wikipedia.org/wiki/ANSI_escape_code namespace console_modifiers { #define CSI "\33[" const auto black_font = CSI "30m"; const auto red_font = CSI "31m"; const auto green_font = CSI "32m"; const auto yellow_font = CSI "33m"; const auto blue_font = CSI "34m"; const auto magenta_font = CSI "35m"; const auto cyan_font = CSI "36m"; const auto white_font = CSI "37m"; const auto black_bkg = CSI "40m"; const auto red_bkg = CSI "41m"; const auto green_bkg = CSI "42m"; const auto yellow_bkg = CSI "43m"; const auto blue_bkg = CSI "44m"; const auto magenta_bkg = CSI "45m"; const auto cyan_bkg = CSI "46m"; const auto white_bkg = CSI "47m"; const auto bold = CSI "1m"; const auto reset_all = CSI "0m"; #undef CSI } bool vm_debug_enabled = false; template<typename T> struct vm_dbg_type final { T t; }; struct debug_cmd_error_t : std::runtime_error { debug_cmd_error_t(const char *message) : std::runtime_error{ message } { } }; void debug_print_error(const char *str) { std::cout << console_modifiers::red_font << console_modifiers::bold << "Error: " << str << console_modifiers::reset_all << "\n"; } void debug_print_error(const std::string &str) { debug_print_error(str.c_str()); } void debug_print_info(const std::string &str) { std::cout << console_modifiers::yellow_font << console_modifiers::bold << str << console_modifiers::reset_all << "\n"; } // Debug command context struct dbg_cmd_cxt_t { static std::string last_successful_cmd_option; dbg_cmd_cxt_t(debugger &d, vm_registers_t &r, bool transient_context = false) : dbg{ d } , exit_break{ false } , current_register{ &r } , initial_register{ r } , transient_context{ transient_context } , vm{ d.controller->get_vm_instance() } { last_successful_cmd = dbg.get_option(last_successful_cmd_option); } ~dbg_cmd_cxt_t() { if (!transient_context) dbg.set_option(last_successful_cmd_option, std::move(last_successful_cmd)); } const bool transient_context; bool exit_break; std::string last_successful_cmd; debugger &dbg; const vm_t &vm; const vm_registers_t *current_register; vm_registers_t &initial_register; }; std::string dbg_cmd_cxt_t::last_successful_cmd_option = "last_successful_cmd"; // Debug command exec operation using dbg_cmd_exec_t = void(*)(const std::vector<std::string> &, dbg_cmd_cxt_t &cxt); // Debug command struct dbg_cmd_t { const char *cmd; const char *description; const char *syntax; const char *example; dbg_cmd_exec_t exec; }; std::string stack_to_string(const vm_registers_t &r, debugger &dbg) { auto msg = std::stringstream{}; walk_stack(r, [&msg, &dbg](const pointer_t, const vm_pc_t pc, const vm_module_ref_t &module_ref) { auto func_maybe = dbg.resolve_to_function_source_line(module_ref.module->vm_id, pc, pc, function_name_format_t::name); if (!func_maybe.empty()) { assert(func_maybe.size() == 1); msg << module_ref.module->module_name->str() << "!" << func_maybe[0] << " "; } else { auto module_name = "<No Name>"; if (module_ref.module->module_name != nullptr) module_name = module_ref.module->module_name->str(); msg << module_name; } msg << "@" << pc << "\n"; return true; }); return msg.str(); } std::string registers_to_string(const vm_registers_t &r) { auto register_string = std::stringstream{}; register_string << " Thread ID: " << r.thread.get_thread_id() << "\n Module: " << r.module_ref->module->module_name->str() << "\n PC: " << r.pc; if (!r.module_ref->is_builtin_module()) { assert(static_cast<std::size_t>(r.pc) < r.module_ref->code_section.size()); const auto &op = r.module_ref->code_section[r.pc].op; register_string << "\n Src: " << op.source << "\n Mid: " << op.middle << "\n Dst: " << op.destination; } register_string << "\n"; return register_string.str(); } std::ostream& operator<<(std::ostream &ss, const std::shared_ptr<const type_descriptor_t> &t) { ss << "size: " << t->size_in_bytes; #ifndef NDEBUG ss << " n: " << t->debug_type_name; #endif return ss; } std::ostream& operator<<(std::ostream &ss, const vm_string_t *s) { auto escaped_string = std::stringstream{}; auto str = s->str(); if (str != nullptr) { while (*str != '\0') { switch (*str) { case '\\': escaped_string << "\\\\"; break; case '\a': escaped_string << "\\a"; break; case '\b': escaped_string << "\\b"; break; case '\f': escaped_string << "\\f"; break; case '\n': escaped_string << "\\n"; break; case '\r': escaped_string << "\\r"; break; case '\t': escaped_string << "\\t"; break; case '\v': escaped_string << "\\v"; break; default: escaped_string << *str; } str++; } } ss << escaped_string.str() << " length: " << s->get_length() << " - string"; return ss; } std::ostream& operator<<(std::ostream &ss, const vm_array_t *a) { ss << "length: " << a->get_length() << " - array of " << a->get_element_type(); return ss; } std::ostream& operator<<(std::ostream &ss, const vm_list_t *l) { ss << "length: " << l->get_length() << " - list of " << l->get_element_type(); return ss; } std::ostream& operator<<(std::ostream &ss, const vm_channel_t *c) { ss << "buffer size: " << c->get_buffer_size() << " - channel of" << c->get_data_type(); return ss; } std::ostream& operator<<(std::ostream &ss, const vm_dbg_type<const vm_alloc_t *> &dbg_alloc) { if (!vm_debug_enabled) return ss; return ss << " [[ref: " << dbg_alloc.t->get_ref_count() << " addr: " << reinterpret_cast<const void *>(dbg_alloc.t) << " gc_res: " << reinterpret_cast<const void *>(dbg_alloc.t->gc_reserved) << "]]"; } std::ostream& safe_alloc_print(std::ostream &ss, const vm_alloc_t *alloc, int recurse_depth) { if (alloc == nullptr) return ss << "<nil>"; vm_dbg_type<const vm_alloc_t *> dbg_alloc{ alloc }; if (alloc->alloc_type == intrinsic_type_desc::type<vm_string_t>()) { ss << vm_alloc_t::from_allocation<vm_string_t>(alloc->get_allocation()) << dbg_alloc; } else if (alloc->alloc_type == intrinsic_type_desc::type<vm_array_t>()) { ss << vm_alloc_t::from_allocation<vm_array_t>(alloc->get_allocation()) << dbg_alloc; } else if (alloc->alloc_type == intrinsic_type_desc::type<vm_list_t>()) { ss << vm_alloc_t::from_allocation<vm_list_t>(alloc->get_allocation()) << dbg_alloc; } else if (alloc->alloc_type == intrinsic_type_desc::type<vm_channel_t>()) { ss << vm_alloc_t::from_allocation<vm_channel_t>(alloc->get_allocation()) << dbg_alloc; } else { ss << alloc->alloc_type << " - alloc" << dbg_alloc; const auto base_address = reinterpret_cast<std::uintptr_t>(alloc->get_allocation()); enum_pointer_fields(*alloc->alloc_type, alloc->get_allocation(), [&ss, recurse_depth, base_address](pointer_t *p) { ss << "\n"; std::fill_n(std::ostream_iterator<std::ostream::char_type>{ ss }, 4 * recurse_depth, ' '); const auto p_address = reinterpret_cast<std::uintptr_t>(p); ss << "[" << std::size_t{ p_address - base_address } << "] "; if (recurse_depth < 2) safe_alloc_print(ss, vm_alloc_t::from_allocation(*p), recurse_depth + 1); else ss << "..."; }); } return ss; } std::ostream& operator<<(std::ostream &ss, const vm_alloc_t *alloc) { return safe_alloc_print(ss, alloc, 0); } std::ostream& operator<<(std::ostream &ss, const vm_thread_state_t s) { switch (s) { case vm_thread_state_t::unknown: ss << "unknown"; break; case vm_thread_state_t::blocked_in_alt: ss << "blocked-in-alt"; break; case vm_thread_state_t::blocked_sending: ss << "blocked-sending"; break; case vm_thread_state_t::blocked_receiving: ss << "blocked-receiving"; break; case vm_thread_state_t::debug: ss << "debug"; break; case vm_thread_state_t::ready: ss << "ready"; break; case vm_thread_state_t::running: ss << "running"; break; case vm_thread_state_t::release: ss << "release"; break; case vm_thread_state_t::exiting: ss << "exiting"; break; case vm_thread_state_t::empty_stack: ss << "empty-stack"; break; case vm_thread_state_t::broken: ss << "broken"; break; default: ss << "??"; } return ss; } void cmd_continue(const std::vector<std::string> &, dbg_cmd_cxt_t &cxt) { cxt.exit_break = true; } void cmd_step(const std::vector<std::string> &, dbg_cmd_cxt_t &cxt) { cxt.dbg.controller->set_thread_trap_flag(*cxt.current_register, vm_trap_flags_t::instruction); cxt.exit_break = true; } void cmd_term(const std::vector<std::string> &, dbg_cmd_cxt_t &) { throw vm_term_request{}; } void cmd_print_registers(const std::vector<std::string> &, dbg_cmd_cxt_t &cxt) { auto s = registers_to_string(*cxt.current_register); debug_print_info(s); } void cmd_print_threads(const std::vector<std::string> &, dbg_cmd_cxt_t &cxt) { auto msg = std::stringstream{}; for (auto t : cxt.vm.get_scheduler_control().get_all_threads()) msg << " " << std::setw(5) << t->get_thread_id() << " - " << t->get_registers().current_thread_state << "\n"; debug_print_info(msg.str()); } void cmd_print_modules(const std::vector<std::string> &, dbg_cmd_cxt_t &cxt) { auto msg = std::stringstream{}; cxt.vm.enum_loaded_modules([&msg, &cxt](const loaded_vm_module_t &m) { auto module_instance = m.module.lock(); if (module_instance != nullptr) { msg << std::setw(3) << m.vm_id << ": " << std::setw(16) << module_instance->module_name->str() << std::setw(0) << " - " << m.origin->str(); if (cxt.dbg.symbols_exist(m.vm_id)) msg << " [symbols loaded]"; msg << "\n"; } return true; }); debug_print_info(msg.str()); } void cmd_print_symbols(const std::vector<std::string> &a, dbg_cmd_cxt_t &cxt) { if (a.size() < 2) throw debug_cmd_error_t{ "Must supply a module ID" }; module_id_t vm_id; try { vm_id = std::stoul(a[1]); } catch (...) { throw debug_cmd_error_t{ "Invalid module ID format" }; } if (!cxt.dbg.symbols_exist(vm_id)) throw debug_cmd_error_t{ "No symbols loaded for module" }; // Interpret the last non-module ID value as the partial name to match. const char *name_to_match = nullptr; if (a.size() > 2) name_to_match = a.back().c_str(); auto msg = std::stringstream{}; auto module_funcs = cxt.dbg.get_module_functions(vm_id, name_to_match); msg << " [PC range] Function"; for (auto &f : module_funcs) msg << "\n " << f; debug_print_info(msg.str()); } void cmd_breakpoint_set(const std::vector<std::string> &a, dbg_cmd_cxt_t &cxt) { if (a.size() < 2) throw debug_cmd_error_t{ "Must supply an IP" };