Unified · Split
| @@ -366,10 +366,7 @@ namespace · 365 unmodified lines | ||
| 366 | 366 | |
| 367 | 367 | if (parts.size() == 3) |
| 368 | 368 | { |
| 369 | − item.size_in_bytes = std::strtol(parts[2].c_str(), nullptr, 10); | |
| 370 | − if (item.size_in_bytes == 0 && errno != 0) | |
| 371 | − throw std::runtime_error{ "Invalid adt size" }; | |
| 372 | − | |
| 369 | + item.size_in_bytes = std::stoi(parts[2]); | |
| 373 | 370 | item.id_table = read_id_table(r, tt, fc); |
| 374 | 371 | } |
| 375 | 372 | |
| @@ -429,9 +426,6 @@ namespace · 53 unmodified lines | ||
| 429 | 426 | { |
| 430 | 427 | auto type_index = r.get_as_string_until('\n'); |
| 431 | 428 | const auto idx = std::stoi(type_index); |
| 432 | − if (idx == 0) | |
| 433 | − throw std::runtime_error{ "Invalid type index" }; | |
| 434 | − | |
| 435 | 429 | t.value_index_1 = type_table_t::types_mask | idx; |
| 436 | 430 | break; |
| 437 | 431 | } |
| @@ -681,6 +675,11 @@ namespace · 243 unmodified lines | ||
| 681 | 675 | return _file->header.module_name; |
| 682 | 676 | } |
| 683 | 677 | |
| 678 | + size_t get_instruction_count() const | |
| 679 | + { | |
| 680 | + return _file->pc_table.size(); | |
| 681 | + } | |
| 682 | + | |
| 684 | 683 | std::unique_ptr<symbol_debug_t> get_debug() override |
| 685 | 684 | { |
| 686 | 685 | return std::make_unique<sbl_symbol_debug>(_file); |
| @@ -5,6 +5,7 @@ · 4 unmodified lines | ||
| 5 | 5 | // |
| 6 | 6 | |
| 7 | 7 | #include <iostream> |
| 8 | +#include <fstream> | |
| 8 | 9 | #include <iomanip> |
| 9 | 10 | #include <sstream> |
| 10 | 11 | #include <string> |
| @@ -117,16 +118,26 @@ namespace · 106 unmodified lines | ||
| 117 | 118 | dbg_cmd_exec_t exec; |
| 118 | 119 | }; |
| 119 | 120 | |
| 120 | − std::string stack_to_string(const vm_registers_t &r) | |
| 121 | + std::string stack_to_string(const vm_registers_t &r, debugger &dbg) | |
| 121 | 122 | { |
| 122 | 123 | auto msg = std::stringstream{}; |
| 123 | − walk_stack(r, [&msg](const pointer_t, const vm_pc_t pc, const vm_module_ref_t &module_ref) | |
| 124 | + walk_stack(r, [&msg, &dbg](const pointer_t, const vm_pc_t pc, const vm_module_ref_t &module_ref) | |
| 124 | 125 | { |
| 125 | − auto module_name = "<No Name>"; | |
| 126 | − if (module_ref.module->module_name != nullptr) | |
| 127 | − module_name = module_ref.module->module_name->str(); | |
| 126 | + auto resolved_name = dbg.resolve_function_source_line(module_ref.module, pc); | |
| 127 | + if (!resolved_name.empty()) | |
| 128 | + { | |
| 129 | + msg << resolved_name; | |
| 130 | + } | |
| 131 | + else | |
| 132 | + { | |
| 133 | + auto module_name = "<No Name>"; | |
| 134 | + if (module_ref.module->module_name != nullptr) | |
| 135 | + module_name = module_ref.module->module_name->str(); | |
| 136 | + | |
| 137 | + msg << module_name << "@" << pc; | |
| 138 | + } | |
| 128 | 139 | |
| 129 | − msg << module_name << "@" << pc << "\n"; | |
| 140 | + msg << "\n"; | |
| 130 | 141 | return true; |
| 131 | 142 | }); |
| 132 | 143 | |
| @@ -215,7 +226,7 @@ namespace · 82 unmodified lines | ||
| 215 | 226 | ss << "channel\n" |
| 216 | 227 | << "\tbuffer size: " << c->get_buffer_size() << "\n"; |
| 217 | 228 | |
| 218 | − return ss; | |
| 229 | + return ss; | |
| 219 | 230 | } |
| 220 | 231 | |
| 221 | 232 | std::ostream& operator<<(std::ostream &ss, const vm_alloc_t *alloc) |
| @@ -330,7 +341,7 @@ namespace · 108 unmodified lines | ||
| 330 | 341 | auto module_instance = m.module.lock(); |
| 331 | 342 | if (module_instance != nullptr) |
| 332 | 343 | { |
| 333 | − msg << std::setw(3) << m.load_id << ": " | |
| 344 | + msg << std::setw(3) << m.vm_id << ": " | |
| 334 | 345 | << std::setw(16) << module_instance->module_name->str() |
| 335 | 346 | << std::setw(0) << " - " << m.origin->str() << "\n"; |
| 336 | 347 | } |
| @@ -372,7 +383,7 @@ namespace · 35 unmodified lines | ||
| 372 | 383 | |
| 373 | 384 | cxt.vm.enum_loaded_modules([module_id, &module](const loaded_vm_module_t &m) |
| 374 | 385 | { |
| 375 | − if (module_id != m.load_id) | |
| 386 | + if (module_id != m.vm_id) | |
| 376 | 387 | return true; |
| 377 | 388 | |
| 378 | 389 | // Found the matching ID so stop |
| @@ -500,7 +511,7 @@ namespace · 121 unmodified lines | ||
| 500 | 511 | for (auto t : thread_registers) |
| 501 | 512 | { |
| 502 | 513 | msg << "Thread " << t.first << ":\n"; |
| 503 | − msg << stack_to_string(*t.second); | |
| 514 | + msg << stack_to_string(*t.second, cxt.dbg); | |
| 504 | 515 | msg << "\n"; |
| 505 | 516 | } |
| 506 | 517 | |
| @@ -856,6 +867,30 @@ debugger::debugger(const debugger_options opt) · 349 unmodified lines | ||
| 856 | 867 | , controller{ nullptr } |
| 857 | 868 | { } |
| 858 | 869 | |
| 870 | +std::string debugger::resolve_function_source_line(std::shared_ptr<const vm_module_t> m, vm_pc_t pc) | |
| 871 | +{ | |
| 872 | + if (m == nullptr) | |
| 873 | + return{}; | |
| 874 | + | |
| 875 | + auto iter = _vm_id_to_symbol_debug.find(m->vm_id); | |
| 876 | + if (iter == std::end(_vm_id_to_symbol_debug)) | |
| 877 | + return{}; | |
| 878 | + | |
| 879 | + std::stringstream ss; | |
| 880 | + auto &d = *(iter->second); | |
| 881 | + | |
| 882 | + // Position the debug symbol iterator | |
| 883 | + d.set_current_pc(pc); | |
| 884 | + | |
| 885 | + ss << m->module_name->str() | |
| 886 | + << '!' | |
| 887 | + << d.current_function_name() | |
| 888 | + << ' ' | |
| 889 | + << d.current_source_location().begin_line; | |
| 890 | + | |
| 891 | + return ss.str(); | |
| 892 | +} | |
| 893 | + | |
| 859 | 894 | void debugger::on_load(vm_tool_controller_t &controller_, std::size_t tool_id) |
| 860 | 895 | { |
| 861 | 896 | _tool_id = tool_id; |
| @@ -909,6 +944,13 @@ void debugger::on_load(vm_tool_controller_t &controller_, std::size_t tool_id) · 47 unmodified lines | ||
| 909 | 944 | prompt(*this, *cxt.value1.registers); |
| 910 | 945 | })); |
| 911 | 946 | |
| 947 | + _event_cookies.emplace(controller->subscribe_event(vm_event_t::module_vm_load, [&](vm_event_t, vm_event_context_t &cxt) | |
| 948 | + { | |
| 949 | + auto loaded_mod = cxt.value1.loaded_mod; | |
| 950 | + assert(loaded_mod != nullptr); | |
| 951 | + load_symbols(*loaded_mod); | |
| 952 | + })); | |
| 953 | + | |
| 912 | 954 | _event_cookies.emplace(controller->subscribe_event(vm_event_t::module_thread_load, [&](vm_event_t, vm_event_context_t &cxt) |
| 913 | 955 | { |
| 914 | 956 | auto m = (*cxt.value2.module); |
| @@ -960,4 +1002,73 @@ void debugger::on_unload() · 45 unmodified lines | ||
| 960 | 1002 | controller->unsubscribe_event(ec); |
| 961 | 1003 | |
| 962 | 1004 | controller = nullptr; |
| 1005 | +} | |
| 1006 | + | |
| 1007 | +void debugger::load_symbols(const disvm::loaded_vm_module_t &loaded_mod) | |
| 1008 | +{ | |
| 1009 | + auto m = loaded_mod.module.lock(); | |
| 1010 | + assert(m != nullptr && "Loaded module event should have valid module reference"); | |
| 1011 | + | |
| 1012 | + auto symbol_extension = ".sbl"; | |
| 1013 | + std::string symbol_path_maybe{ loaded_mod.origin->str() }; | |
| 1014 | + const auto ext_index = symbol_path_maybe.find_last_of('.'); | |
| 1015 | + if (ext_index == std::string::npos) | |
| 1016 | + { | |
| 1017 | + // Module doesn't have an extension so just append the symbol extension. | |
| 1018 | + symbol_path_maybe.append(symbol_extension); | |
| 1019 | + } | |
| 1020 | + else | |
| 1021 | + { | |
| 1022 | + // Replace the module extension with the symbol extension. | |
| 1023 | + symbol_path_maybe.replace(ext_index, std::string::npos, symbol_extension); | |
| 1024 | + } | |
| 1025 | + | |
| 1026 | + std::ifstream sbl_file{ symbol_path_maybe, std::ifstream::in }; | |
| 1027 | + if (!sbl_file.is_open()) | |
| 1028 | + return; | |
| 1029 | + | |
| 1030 | + std::unique_ptr<symbol::symbol_data_t> symbol_data; | |
| 1031 | + try | |
| 1032 | + { | |
| 1033 | + symbol_data = symbol::read(sbl_file); | |
| 1034 | + } | |
| 1035 | + catch (const std::runtime_error &e) | |
| 1036 | + { | |
| 1037 | + // File with assumed symbol name/extension is not the correct format | |
| 1038 | + std::stringstream msg; | |
| 1039 | + msg << "Assumed symbol file '" | |
| 1040 | + << symbol_path_maybe | |
| 1041 | + << "' has invalid symbol format. (" | |
| 1042 | + << e.what() | |
| 1043 | + << ")"; | |
| 1044 | + debug_print_error(msg.str()); | |
| 1045 | + return; | |
| 1046 | + } | |
| 1047 | + | |
| 1048 | + // Check if the symbol data matches the loaded module | |
| 1049 | + if (0 != symbol_data->get_module_name().compare(m->module_name->str()) | |
| 1050 | + || symbol_data->get_instruction_count() != m->code_section.size()) | |
| 1051 | + { | |
| 1052 | + // Symbol file does not match the loaded modules - stale file? | |
| 1053 | + std::stringstream msg; | |
| 1054 | + msg << "Symbol file '" | |
| 1055 | + << symbol_path_maybe | |
| 1056 | + << "' does not match loaded module '" | |
| 1057 | + << loaded_mod.origin->str() | |
| 1058 | + << "'."; | |
| 1059 | + debug_print_error(msg.str()); | |
| 1060 | + return; | |
| 1061 | + } | |
| 1062 | + | |
| 1063 | + const auto vm_id = loaded_mod.vm_id; | |
| 1064 | + assert(_vm_id_to_symbol_debug.find(vm_id) == std::end(_vm_id_to_symbol_debug) && "Module VM ID should be unique"); | |
| 1065 | + _vm_id_to_symbol_debug[vm_id] = symbol_data->get_debug(); | |
| 1066 | + | |
| 1067 | + std::stringstream msg; | |
| 1068 | + msg << "Symbol file '" | |
| 1069 | + << symbol_path_maybe | |
| 1070 | + << "' loaded for '" | |
| 1071 | + << loaded_mod.origin->str() | |
| 1072 | + << "'."; | |
| 1073 | + debug_print_info(msg.str()); | |
| 963 | 1074 | } |
| @@ -16,9 +16,10 @@ · 15 unmodified lines | ||
| 16 | 16 | enum class debugger_options |
| 17 | 17 | { |
| 18 | 18 | none = 0, |
| 19 | − break_on_enter = 1, | |
| 20 | − break_on_module_load = 2, | |
| 21 | − break_on_exception = 4, | |
| 19 | + break_on_enter = (1 << 0), | |
| 20 | + break_on_module_load = (1 << 1), | |
| 21 | + break_on_exception = (1 << 2), | |
| 22 | + disable_symbol_loading = (1 << 3), | |
| 22 | 23 | }; |
| 23 | 24 | |
| 24 | 25 | DEFINE_ENUM_FLAG_OPERATORS(debugger_options); |
| @@ -30,6 +31,8 @@ class debugger final : public disvm::runtime::vm_tool_t · 5 unmodified lines | ||
| 30 | 31 | public: |
| 31 | 32 | debugger(const debugger_options); |
| 32 | 33 | |
| 34 | + std::string resolve_function_source_line(std::shared_ptr<const disvm::runtime::vm_module_t> m, disvm::runtime::vm_pc_t pc); | |
| 35 | + | |
| 33 | 36 | public: // vm_tool_t |
| 34 | 37 | void on_load(disvm::runtime::vm_tool_controller_t &, std::size_t tool_id) override; |
| 35 | 38 | |
| @@ -40,9 +43,12 @@ public: · 4 unmodified lines | ||
| 40 | 43 | std::unordered_set<disvm::runtime::cookie_t> breakpoint_cookies; |
| 41 | 44 | |
| 42 | 45 | private: |
| 46 | + void load_symbols(const disvm::loaded_vm_module_t &); | |
| 47 | + | |
| 43 | 48 | debugger_options _options; |
| 44 | 49 | std::size_t _tool_id; |
| 45 | 50 | std::unordered_set<disvm::runtime::cookie_t> _event_cookies; |
| 51 | + std::unordered_map<std::size_t, std::unique_ptr<disvm::symbol::symbol_debug_t>> _vm_id_to_symbol_debug; | |
| 46 | 52 | }; |
| 47 | 53 | |
| 48 | 54 | #endif // _DISVM_SRC_EXEC_EXEC_H_ |
| @@ -39,7 +39,7 @@ namespace disvm · 38 unmodified lines | ||
| 39 | 39 | class loaded_vm_module_t final |
| 40 | 40 | { |
| 41 | 41 | public: |
| 42 | − std::size_t load_id; | |
| 42 | + runtime::module_id_t vm_id; | |
| 43 | 43 | |
| 44 | 44 | // Origin of module |
| 45 | 45 | std::unique_ptr<runtime::vm_string_t> origin; |
| @@ -89,7 +89,7 @@ namespace disvm · 43 unmodified lines | ||
| 89 | 89 | const runtime::vm_frame_t &initial_frame, |
| 90 | 90 | runtime::vm_pc_t initial_pc); |
| 91 | 91 | |
| 92 | − // Load the module at the supplied path into the virtual machine | |
| 92 | + // Load the module at the supplied path into the virtual machine. | |
| 93 | 93 | std::shared_ptr<runtime::vm_module_t> load_module(const char *path); |
| 94 | 94 | |
| 95 | 95 | // Enumerate all loaded modules - this can include references to modules that |
| @@ -566,6 +566,8 @@ namespace disvm · 565 unmodified lines | ||
| 566 | 566 | using import_section_t = std::vector<import_vm_module_t>; |
| 567 | 567 | using handler_section_t = std::vector<handler_t>; |
| 568 | 568 | |
| 569 | + using module_id_t = std::size_t; | |
| 570 | + | |
| 569 | 571 | // VM module |
| 570 | 572 | struct vm_module_t |
| 571 | 573 | { |
| @@ -575,6 +577,10 @@ namespace disvm · 3 unmodified lines | ||
| 575 | 577 | |
| 576 | 578 | ~vm_module_t(); |
| 577 | 579 | |
| 580 | + // This is a unique ID assigned to the module by the VM. It is associated with the path of the module and is idempotent | |
| 581 | + // with respect to the number of times the module is loaded. It is considered read-only. | |
| 582 | + module_id_t vm_id; | |
| 583 | + | |
| 578 | 584 | // Contains a magic number, digital signature, runtime flags, sizes of the other sections, and a description of the entry point |
| 579 | 585 | header_t header; |
| 580 | 586 | |
| @@ -441,6 +441,9 @@ namespace disvm · 440 unmodified lines | ||
| 441 | 441 | // Get name of the module |
| 442 | 442 | virtual const std::string& get_module_name() const = 0; |
| 443 | 443 | |
| 444 | + // Get instruction count in the symbol data | |
| 445 | + virtual size_t get_instruction_count() const = 0; | |
| 446 | + | |
| 444 | 447 | // Get interface over symbol data targeted for debugging |
| 445 | 448 | virtual std::unique_ptr<symbol_debug_t> get_debug() = 0; |
| 446 | 449 | }; |
| @@ -28,6 +28,7 @@ namespace disvm · 27 unmodified lines | ||
| 28 | 28 | const vm_thread_t *thread; |
| 29 | 29 | const vm_string_t *str; |
| 30 | 30 | const vm_alloc_t *alloc; |
| 31 | + const loaded_vm_module_t *loaded_mod; | |
| 31 | 32 | }; |
| 32 | 33 | |
| 33 | 34 | // Event context |
| @@ -41,8 +42,7 @@ namespace disvm · 7 unmodified lines | ||
| 41 | 42 | // List of possible events |
| 42 | 43 | enum class vm_event_t |
| 43 | 44 | { |
| 44 | − module_vm_load, // value1: std::shared_ptr<vm_module_t> | |
| 45 | − // value2: const vm_string_t - path on disk | |
| 45 | + module_vm_load, // value1: const loaded_vm_module_t | |
| 46 | 46 | |
| 47 | 47 | module_thread_load, // value1: vm_registers_t |
| 48 | 48 | // value2: std::shared_ptr<vm_module_t> |
| @@ -126,13 +126,10 @@ void vm_tool_dispatch_t::on_exception_unhandled(vm_registers_t &r, const vm_stri · 125 unmodified lines | ||
| 126 | 126 | _events.fire_event_callbacks(vm_event_t::exception_unhandled, cxt); |
| 127 | 127 | } |
| 128 | 128 | |
| 129 | −void vm_tool_dispatch_t::on_module_vm_load(std::shared_ptr<vm_module_t> &m, const vm_string_t &path) | |
| 129 | +void vm_tool_dispatch_t::on_module_vm_load(const loaded_vm_module_t &m) | |
| 130 | 130 | { |
| 131 | − assert(m != nullptr); | |
| 132 | − | |
| 133 | 131 | vm_event_context_t cxt{}; |
| 134 | − cxt.value1.module = &m; | |
| 135 | − cxt.value2.str = &path; | |
| 132 | + cxt.value1.loaded_mod = &m; | |
| 136 | 133 | |
| 137 | 134 | _events.fire_event_callbacks(vm_event_t::module_vm_load, cxt); |
| 138 | 135 | } |
| @@ -43,9 +43,9 @@ namespace disvm · 42 unmodified lines | ||
| 43 | 43 | void on_exception_unhandled(vm_registers_t &r, const vm_string_t &id, vm_alloc_t &e); |
| 44 | 44 | |
| 45 | 45 | // Callback for when the VM loads a module from disk |
| 46 | − void on_module_vm_load(std::shared_ptr<vm_module_t> &m, const vm_string_t &path); | |
| 46 | + void on_module_vm_load(const loaded_vm_module_t &loaded_module); | |
| 47 | 47 | |
| 48 | − // Callback for when a thread loads a module into its scope | |
| 48 | + // Callback for when a thread loads a module into its scope. | |
| 49 | 49 | void on_module_thread_load(vm_registers_t &r, std::shared_ptr<vm_module_t> &m); |
| 50 | 50 | |
| 51 | 51 | // Callback when thread lifetime begins. |
| @@ -134,6 +134,8 @@ std::shared_ptr<runtime::vm_module_t> vm_t::load_module(const char *path) · 133 unmodified lines | ||
| 134 | 134 | if (module == nullptr) |
| 135 | 135 | { |
| 136 | 136 | module = std::shared_ptr<vm_module_t>{ std::move(read_module(path)) }; |
| 137 | + module->vm_id = iter->vm_id; | |
| 138 | + | |
| 137 | 139 | iter->module = module; |
| 138 | 140 | |
| 139 | 141 | debug::log_msg(debug::component_trace_t::module, debug::log_level_t::debug, "reload: vm module: >>%s<<\n", path); |
| @@ -157,11 +159,12 @@ std::shared_ptr<runtime::vm_module_t> vm_t::load_module(const char *path) · 17 unmodified lines | ||
| 157 | 159 | |
| 158 | 160 | auto path_local = std::make_unique<vm_string_t>(std::strlen(path), reinterpret_cast<const uint8_t *>(path)); |
| 159 | 161 | |
| 160 | − auto load_id_next = std::size_t{ 0 }; | |
| 162 | + auto vm_id_next = std::size_t{ 1 }; | |
| 161 | 163 | if (!_modules.empty()) |
| 162 | − load_id_next = (_modules.front().load_id + 1); | |
| 164 | + vm_id_next = (_modules.front().vm_id + 1); | |
| 163 | 165 | |
| 164 | − _modules.push_front(std::move(loaded_vm_module_t{ load_id_next, std::move(path_local), new_module })); | |
| 166 | + new_module->vm_id = vm_id_next; | |
| 167 | + _modules.push_front(std::move(loaded_vm_module_t{ vm_id_next, std::move(path_local), new_module })); | |
| 165 | 168 | new_module_iter = _modules.begin(); |
| 166 | 169 | } |
| 167 | 170 | |
| @@ -169,7 +172,7 @@ std::shared_ptr<runtime::vm_module_t> vm_t::load_module(const char *path) · 1 unmodified line | ||
| 169 | 172 | { |
| 170 | 173 | std::lock_guard<std::mutex> lock{ _tool_dispatch_lock }; |
| 171 | 174 | if (_tool_dispatch != nullptr) |
| 172 | − _tool_dispatch->on_module_vm_load(new_module, *new_module_iter->origin); | |
| 175 | + _tool_dispatch->on_module_vm_load(*new_module_iter); | |
| 173 | 176 | } |
| 174 | 177 | |
| 175 | 178 | if (debug::is_component_tracing_enabled<debug::component_trace_t::module>()) |