if (thread_id != 0) break; } } } if (thread_registers.empty()) throw debug_cmd_error_t{ "Unknown thread ID" }; auto msg = std::stringstream{}; for (auto t : thread_registers) { msg << "Thread " << t.first << ":\n"; msg << stack_to_string(*t.second, cxt.dbg); msg << "\n"; } debug_print_info(msg.str()); } void cmd_switchthread(const std::vector<std::string> &a, dbg_cmd_cxt_t &cxt) { if (a.size() == 1) throw debug_cmd_error_t{ "Must supply a target thread ID" }; uint32_t thread_id; try { thread_id = std::stoul(a[1]); } catch (...) { throw debug_cmd_error_t{ "Invalid thread ID format" }; } auto all_threads = cxt.vm.get_scheduler_control().get_all_threads(); // Find the thread for (auto t : all_threads) { const auto tid = t->get_thread_id(); if (tid == thread_id) { cxt.current_register = &t->get_registers(); return; } } throw debug_cmd_error_t{ "Unknown thread ID" }; } void cmd_disassemble(const std::vector<std::string> &a, dbg_cmd_cxt_t &cxt) { auto disassemble_length = int{ 4 }; if (a.size() > 1) { try { disassemble_length = std::stoi(a[1]); } catch (...) { throw debug_cmd_error_t{ "Invalid disassemble count format" }; } } const auto &r = *cxt.current_register; const auto &code_section = r.module_ref->code_section; auto begin_index = static_cast<std::size_t>(r.pc); auto end_index = static_cast<std::size_t>(r.pc + 1); if (disassemble_length < 0) { const auto begin_maybe = r.pc - (-disassemble_length) + 1; begin_index = static_cast<std::size_t>(std::max(begin_maybe, 0)); } else { const auto end_maybe = static_cast<std::size_t>(r.pc + disassemble_length); end_index = std::min(end_maybe, code_section.size()); } auto msg = std::stringstream{}; for (auto i = begin_index; i < end_index; ++i) { auto op = code_section[i].op; auto op_prefix = " "; // If the opcode is a breakpoint then try and replace it with the real opcode if (op.opcode == opcode_t::brkpt) { // Look up each of the known cookies to try and find the matching PC and module for (auto bp_cookie : cxt.dbg.breakpoint_cookies) { const auto details = cxt.dbg.controller->get_breakpoint_details(bp_cookie); if (details.pc == i && details.module == r.module_ref->module) { op.opcode = details.original_opcode; op_prefix = " b"; break; } } } msg << op_prefix << std::setw(5) << i << ": " << op << "\n"; } debug_print_info(msg.str()); } void cmd_examine(const std::vector<std::string> &a, dbg_cmd_cxt_t &cxt) { if (a.size() < 3) throw debug_cmd_error_t{ "Invalid number of arguments" }; std::shared_ptr<const type_descriptor_t> base_type; word_t *base_pointer; auto &base_ptr_id = a[1]; if (base_ptr_id.compare("mp") == 0) { base_type = cxt.current_register->mp_base->alloc_type; base_pointer = reinterpret_cast<word_t *>(cxt.current_register->mp_base->get_allocation()); } else if (base_ptr_id.compare("fp") == 0) { auto frame = cxt.current_register->stack.peek_frame(); if (frame == nullptr) throw debug_cmd_error_t{ "Invalid frame pointer " }; base_type = frame->frame_type; base_pointer = reinterpret_cast<word_t *>(frame->base()); } else { throw debug_cmd_error_t{ "Invalid memory base pointer" }; } assert(base_type != nullptr && base_pointer != nullptr); const auto pointer_size = static_cast<int>(sizeof(pointer_t)); auto byte_offset1 = int{ 0 }; auto byte_offset2 = int{ -1 }; try { byte_offset1 = std::stoi(a[2]); // The 4th argument is optional if (a.size() >= 4) byte_offset2 = std::stoi(a[3]); } catch (...) { throw debug_cmd_error_t{ "Invalid memory offset format" }; } if (0 > byte_offset1 || byte_offset1 >= base_type->size_in_bytes) throw debug_cmd_error_t{ "First offset out of range" }; auto msg = std::stringstream{}; if (byte_offset2 != -1) msg << " " << byte_offset2 << "(" << byte_offset1 << "(" << a[1] << ")):\n"; else msg << " " << byte_offset1 << "(" << a[1] << "):\n"; auto value_is_pointer = bool{ false }; const auto pointer_offset1 = byte_offset1 / pointer_size; auto value_to_examine = reinterpret_cast<pointer_t>(base_pointer[pointer_offset1]); if (runtime::is_offset_pointer(*base_type, pointer_offset1)) { value_is_pointer = true; auto alloc_to_examine = vm_alloc_t::from_allocation(value_to_examine); if (byte_offset2 != -1) { if (alloc_to_examine == nullptr) throw debug_cmd_error_t{ "First offset is nil" }; if (0 > byte_offset2 || byte_offset2 >= alloc_to_examine->alloc_type->size_in_bytes) throw debug_cmd_error_t{ "Second offset out of range" }; // Update data pointer for 2nd indirection base_pointer = reinterpret_cast<word_t *>(alloc_to_examine->get_allocation()); base_type = alloc_to_examine->alloc_type; const auto pointer_offset2 = byte_offset2 / pointer_size; value_to_examine = reinterpret_cast<pointer_t>(base_pointer[pointer_offset2]); value_is_pointer = runtime::is_offset_pointer(*base_type, pointer_offset2); } } if (value_is_pointer) msg << " " << vm_alloc_t::from_allocation(value_to_examine); else msg << " " << std::setw(8) << std::showbase << std::hex << value_to_examine << std::dec; debug_print_info(msg.str()); } // Forward declaration void print_help(); void cmd_help(const std::vector<std::string> &, dbg_cmd_cxt_t &) { print_help(); } // ee void cmd_tlk_tilde_star(const std::vector<std::string> &, dbg_cmd_cxt_t &) { std::cout << console_modifiers::black_font << console_modifiers::white_bkg << "H" << console_modifiers::reset_all << console_modifiers::red_font << console_modifiers::bold << "e" << console_modifiers::green_font << console_modifiers::bold << "l" << console_modifiers::yellow_font << console_modifiers::bold << "l" << console_modifiers::blue_font << console_modifiers::bold << "o" << console_modifiers::magenta_font << console_modifiers::bold << ", " << console_modifiers::black_font << console_modifiers::white_bkg << "W" << console_modifiers::reset_all << console_modifiers::red_font << console_modifiers::bold << "o" << console_modifiers::green_font << console_modifiers::bold << "r" << console_modifiers::yellow_font << console_modifiers::bold << "l" << console_modifiers::blue_font << console_modifiers::bold << "d" << console_modifiers::magenta_font << console_modifiers::bold << "!\n" << console_modifiers::reset_all; } void set_value_cmd(const std::vector<std::string> &args, dbg_cmd_cxt_t &) { if (args.size() == 0) { // [TODO] Print all options current state return; } else if (0 == args[0].compare("help")) { // [TODO] Print help for 'set' command return; } } const dbg_cmd_t cmds[] = { { "c", "Continue", nullptr, nullptr, cmd_continue }, { "term", "Terminate the VM instance", nullptr, nullptr, cmd_term }, { "r", "Print registers", nullptr, nullptr, cmd_print_registers }, { "t", "Print all threads", nullptr, nullptr, cmd_print_threads }, { "m", "Print all loaded modules", nullptr, nullptr, cmd_print_modules }, { "bs", "Set breakpoint at the specified IP in a loaded module", "bs [0-9]+ [0-9]*", "bs 3 5 (Breakpoint at IP 3 in module 5), bs 4 (Breakpoint at IP 4 in current module)", cmd_breakpoint_set }, { "bc", "Clear breakpoint(s)", "bc ([0-9]+|\\*)", "bc 3, bc *", cmd_breakpoint_clear }, { "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]+)?", "d, d -4, d 5", cmd_disassemble }, { "x", "Examine memory", "x [mp|fp] [0-9]+ ([0-9]+)?", "x mp 3, x fp 20 6", cmd_examine }, //{ "set" , "Set a debugger option", "set [ _a-zA-Z0-9]*", "'set help' for available options", set_value_cmd }, { "?", "Print help", nullptr, nullptr, cmd_help }, // Undocumented commands { "tlk~*", nullptr, nullptr, nullptr, cmd_tlk_tilde_star } }; dbg_cmd_exec_t find_cmd(const std::string &cmd_maybe) { for (auto i = std::size_t{}; i < sizeof(cmds) / sizeof(cmds[0]); ++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 ss = std::stringstream{}; ss << "Commands:\n"; for (auto i = std::size_t{}; i < sizeof(cmds) / sizeof(cmds[0]); ++i) { auto &c = cmds[i]; // Ignore if undocumented if (c.description == nullptr) continue; ss << " " << c.cmd << "\t" << c.description << "\n"; if (c.syntax != nullptr) ss << "\t Syntax: " << c.syntax << "\n"; if (c.example != nullptr) ss << "\t Example(s): " << c.example << "\n"; } debug_print_info(ss.str()); } void prompt(debugger &debugger, const vm_registers_t &r) { debugger.controller->suspend_all_threads(); 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); const auto cmd_tokens = util::split(cmd); if (cmd_tokens.empty()) continue; try { auto cmd_exec = find_cmd(cmd_tokens[0]); cmd_exec(cmd_tokens, cxt); if (cxt.exit_break) break; } catch (const debug_cmd_error_t &e) { debug_print_error(e.what()); } } debugger.controller->resume_all_threads(); }} std::ostream& operator<<(std::ostream &ss, const debugger_options o){ ss << "Break on enter: " << std::boolalpha << util::has_flag(o, debugger_options::break_on_enter) << "\n" << "Break on module load: " << std::boolalpha << util::has_flag(o, debugger_options::break_on_module_load) << "\n" << "Break on exception: " << std::boolalpha << 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 }{ } std::string debugger::resolve_function_source_line(std::shared_ptr<const vm_module_t> m, vm_pc_t pc){ if (m == nullptr) return{}; auto iter = _vm_id_to_symbol_debug.find(m->vm_id); if (iter == std::end(_vm_id_to_symbol_debug)) return{}; std::stringstream ss; auto &d = *(iter->second); // Position the debug symbol iterator d.set_current_pc(pc); const auto src = d.current_source_location(); ss << m->module_name->str() << '!' << d.current_function_name(symbol::function_name_format_t::declaration) << " at " << d.get_source_by_id(src.source_id) << ':' << src.begin_line; return ss.str();} 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 (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 (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 (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; controller->set_breakpoint(m, m->header.entry_pc); } if (util::has_flag(_options, debugger_options::break_on_module_load)) prompt(*this, *cxt.value1.registers); })); Lines 501–1000 of 1094. Previous lines Load more
if (thread_id != 0) break; } } } if (thread_registers.empty()) throw debug_cmd_error_t{ "Unknown thread ID" }; auto msg = std::stringstream{}; for (auto t : thread_registers) { msg << "Thread " << t.first << ":\n"; msg << stack_to_string(*t.second, cxt.dbg); msg << "\n"; } debug_print_info(msg.str()); } void cmd_switchthread(const std::vector<std::string> &a, dbg_cmd_cxt_t &cxt) { if (a.size() == 1) throw debug_cmd_error_t{ "Must supply a target thread ID" }; uint32_t thread_id; try { thread_id = std::stoul(a[1]); } catch (...) { throw debug_cmd_error_t{ "Invalid thread ID format" }; } auto all_threads = cxt.vm.get_scheduler_control().get_all_threads(); // Find the thread for (auto t : all_threads) { const auto tid = t->get_thread_id(); if (tid == thread_id) { cxt.current_register = &t->get_registers(); return; } } throw debug_cmd_error_t{ "Unknown thread ID" }; } void cmd_disassemble(const std::vector<std::string> &a, dbg_cmd_cxt_t &cxt) { auto disassemble_length = int{ 4 }; if (a.size() > 1) { try { disassemble_length = std::stoi(a[1]); } catch (...) { throw debug_cmd_error_t{ "Invalid disassemble count format" }; } } const auto &r = *cxt.current_register; const auto &code_section = r.module_ref->code_section; auto begin_index = static_cast<std::size_t>(r.pc); auto end_index = static_cast<std::size_t>(r.pc + 1); if (disassemble_length < 0) { const auto begin_maybe = r.pc - (-disassemble_length) + 1; begin_index = static_cast<std::size_t>(std::max(begin_maybe, 0)); } else { const auto end_maybe = static_cast<std::size_t>(r.pc + disassemble_length); end_index = std::min(end_maybe, code_section.size()); } auto msg = std::stringstream{}; for (auto i = begin_index; i < end_index; ++i) { auto op = code_section[i].op; auto op_prefix = " "; // If the opcode is a breakpoint then try and replace it with the real opcode if (op.opcode == opcode_t::brkpt) { // Look up each of the known cookies to try and find the matching PC and module for (auto bp_cookie : cxt.dbg.breakpoint_cookies) { const auto details = cxt.dbg.controller->get_breakpoint_details(bp_cookie); if (details.pc == i && details.module == r.module_ref->module) { op.opcode = details.original_opcode; op_prefix = " b"; break; } } } msg << op_prefix << std::setw(5) << i << ": " << op << "\n"; } debug_print_info(msg.str()); } void cmd_examine(const std::vector<std::string> &a, dbg_cmd_cxt_t &cxt) { if (a.size() < 3) throw debug_cmd_error_t{ "Invalid number of arguments" }; std::shared_ptr<const type_descriptor_t> base_type; word_t *base_pointer; auto &base_ptr_id = a[1]; if (base_ptr_id.compare("mp") == 0) { base_type = cxt.current_register->mp_base->alloc_type; base_pointer = reinterpret_cast<word_t *>(cxt.current_register->mp_base->get_allocation()); } else if (base_ptr_id.compare("fp") == 0) { auto frame = cxt.current_register->stack.peek_frame(); if (frame == nullptr) throw debug_cmd_error_t{ "Invalid frame pointer " }; base_type = frame->frame_type; base_pointer = reinterpret_cast<word_t *>(frame->base()); } else { throw debug_cmd_error_t{ "Invalid memory base pointer" }; } assert(base_type != nullptr && base_pointer != nullptr); const auto pointer_size = static_cast<int>(sizeof(pointer_t)); auto byte_offset1 = int{ 0 }; auto byte_offset2 = int{ -1 }; try { byte_offset1 = std::stoi(a[2]); // The 4th argument is optional if (a.size() >= 4) byte_offset2 = std::stoi(a[3]); } catch (...) { throw debug_cmd_error_t{ "Invalid memory offset format" }; } if (0 > byte_offset1 || byte_offset1 >= base_type->size_in_bytes) throw debug_cmd_error_t{ "First offset out of range" }; auto msg = std::stringstream{}; if (byte_offset2 != -1) msg << " " << byte_offset2 << "(" << byte_offset1 << "(" << a[1] << ")):\n"; else msg << " " << byte_offset1 << "(" << a[1] << "):\n"; auto value_is_pointer = bool{ false }; const auto pointer_offset1 = byte_offset1 / pointer_size; auto value_to_examine = reinterpret_cast<pointer_t>(base_pointer[pointer_offset1]); if (runtime::is_offset_pointer(*base_type, pointer_offset1)) { value_is_pointer = true; auto alloc_to_examine = vm_alloc_t::from_allocation(value_to_examine); if (byte_offset2 != -1) { if (alloc_to_examine == nullptr) throw debug_cmd_error_t{ "First offset is nil" }; if (0 > byte_offset2 || byte_offset2 >= alloc_to_examine->alloc_type->size_in_bytes) throw debug_cmd_error_t{ "Second offset out of range" }; // Update data pointer for 2nd indirection base_pointer = reinterpret_cast<word_t *>(alloc_to_examine->get_allocation()); base_type = alloc_to_examine->alloc_type; const auto pointer_offset2 = byte_offset2 / pointer_size; value_to_examine = reinterpret_cast<pointer_t>(base_pointer[pointer_offset2]); value_is_pointer = runtime::is_offset_pointer(*base_type, pointer_offset2); } } if (value_is_pointer) msg << " " << vm_alloc_t::from_allocation(value_to_examine); else msg << " " << std::setw(8) << std::showbase << std::hex << value_to_examine << std::dec; debug_print_info(msg.str()); } // Forward declaration void print_help(); void cmd_help(const std::vector<std::string> &, dbg_cmd_cxt_t &) { print_help(); } // ee void cmd_tlk_tilde_star(const std::vector<std::string> &, dbg_cmd_cxt_t &) { std::cout << console_modifiers::black_font << console_modifiers::white_bkg << "H" << console_modifiers::reset_all << console_modifiers::red_font << console_modifiers::bold << "e" << console_modifiers::green_font << console_modifiers::bold << "l" << console_modifiers::yellow_font << console_modifiers::bold << "l" << console_modifiers::blue_font << console_modifiers::bold << "o" << console_modifiers::magenta_font << console_modifiers::bold << ", " << console_modifiers::black_font << console_modifiers::white_bkg << "W" << console_modifiers::reset_all << console_modifiers::red_font << console_modifiers::bold << "o" << console_modifiers::green_font << console_modifiers::bold << "r" << console_modifiers::yellow_font << console_modifiers::bold << "l" << console_modifiers::blue_font << console_modifiers::bold << "d" << console_modifiers::magenta_font << console_modifiers::bold << "!\n" << console_modifiers::reset_all; } void set_value_cmd(const std::vector<std::string> &args, dbg_cmd_cxt_t &) { if (args.size() == 0) { // [TODO] Print all options current state return; } else if (0 == args[0].compare("help")) { // [TODO] Print help for 'set' command return; } } const dbg_cmd_t cmds[] = { { "c", "Continue", nullptr, nullptr, cmd_continue }, { "term", "Terminate the VM instance", nullptr, nullptr, cmd_term }, { "r", "Print registers", nullptr, nullptr, cmd_print_registers }, { "t", "Print all threads", nullptr, nullptr, cmd_print_threads }, { "m", "Print all loaded modules", nullptr, nullptr, cmd_print_modules }, { "bs", "Set breakpoint at the specified IP in a loaded module", "bs [0-9]+ [0-9]*", "bs 3 5 (Breakpoint at IP 3 in module 5), bs 4 (Breakpoint at IP 4 in current module)", cmd_breakpoint_set }, { "bc", "Clear breakpoint(s)", "bc ([0-9]+|\\*)", "bc 3, bc *", cmd_breakpoint_clear }, { "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]+)?", "d, d -4, d 5", cmd_disassemble }, { "x", "Examine memory", "x [mp|fp] [0-9]+ ([0-9]+)?", "x mp 3, x fp 20 6", cmd_examine }, //{ "set" , "Set a debugger option", "set [ _a-zA-Z0-9]*", "'set help' for available options", set_value_cmd }, { "?", "Print help", nullptr, nullptr, cmd_help }, // Undocumented commands { "tlk~*", nullptr, nullptr, nullptr, cmd_tlk_tilde_star } }; dbg_cmd_exec_t find_cmd(const std::string &cmd_maybe) { for (auto i = std::size_t{}; i < sizeof(cmds) / sizeof(cmds[0]); ++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 ss = std::stringstream{}; ss << "Commands:\n"; for (auto i = std::size_t{}; i < sizeof(cmds) / sizeof(cmds[0]); ++i) { auto &c = cmds[i]; // Ignore if undocumented if (c.description == nullptr) continue; ss << " " << c.cmd << "\t" << c.description << "\n"; if (c.syntax != nullptr) ss << "\t Syntax: " << c.syntax << "\n"; if (c.example != nullptr) ss << "\t Example(s): " << c.example << "\n"; } debug_print_info(ss.str()); } void prompt(debugger &debugger, const vm_registers_t &r) { debugger.controller->suspend_all_threads(); 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); const auto cmd_tokens = util::split(cmd); if (cmd_tokens.empty()) continue; try { auto cmd_exec = find_cmd(cmd_tokens[0]); cmd_exec(cmd_tokens, cxt); if (cxt.exit_break) break; } catch (const debug_cmd_error_t &e) { debug_print_error(e.what()); } } debugger.controller->resume_all_threads(); } } std::ostream& operator<<(std::ostream &ss, const debugger_options o) { ss << "Break on enter: " << std::boolalpha << util::has_flag(o, debugger_options::break_on_enter) << "\n" << "Break on module load: " << std::boolalpha << util::has_flag(o, debugger_options::break_on_module_load) << "\n" << "Break on exception: " << std::boolalpha << 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 } { } std::string debugger::resolve_function_source_line(std::shared_ptr<const vm_module_t> m, vm_pc_t pc) { if (m == nullptr) return{}; auto iter = _vm_id_to_symbol_debug.find(m->vm_id); if (iter == std::end(_vm_id_to_symbol_debug)) return{}; std::stringstream ss; auto &d = *(iter->second); // Position the debug symbol iterator d.set_current_pc(pc); const auto src = d.current_source_location(); ss << m->module_name->str() << '!' << d.current_function_name(symbol::function_name_format_t::declaration) << " at " << d.get_source_by_id(src.source_id) << ':' << src.begin_line; return ss.str(); } 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 (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 (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 (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; controller->set_breakpoint(m, m->header.entry_pc); } if (util::has_flag(_options, debugger_options::break_on_module_load)) prompt(*this, *cxt.value1.registers); }));