History

Debugger supports breakpoints (set/clear/list) Clean up usage of final and override on various classes Distinguish tool events for vm loading module and thread loading module

arr committed 10 years ago

Commit e2f8e251 parent d8c9a3314 files · +382 −177Browse files

Unified · Split

14 files
  1. src/exec/debugger.cpp +191 −61
  2. src/exec/exec.h +11 −6
  3. src/include/disvm.h +4 −1
  4. src/include/runtime.h +4 −5
  5. src/include/vm_memory.h +13 −11
  6. src/include/vm_tools.h +23 −8
  7. src/vm/execution_table.cpp +8 −2
  8. src/vm/garbage_collector.cpp +3 −3
  9. src/vm/garbage_collector.h +3 −3
  10. src/vm/scheduler.h +6 −6
  11. src/vm/tool_dispatch.cpp +48 −22
  12. src/vm/tool_dispatch.h +22 −16
  13. src/vm/vm.cpp +44 −31
  14. src/vm/vm_memory.cpp +2 −2
src/exec/debugger.cpp+191−61
@@ -9,9 +9,7 @@ · 8 unmodified lines
99 #include <sstream>
1010 #include <string>
1111 #include <algorithm>
12−#include <locale>
1312 #include <map>
14−#include <vm_tools.h>
1513 #include <utils.h>
1614 #include <vm_memory.h>
1715 #include <vm_asm.h>
@@ -76,16 +74,18 @@ namespace · 58 unmodified lines
7674 // Debug command context
7775 struct dbg_cmd_cxt_t
7876 {
79− dbg_cmd_cxt_t(const vm_t &v, const vm_registers_t &r)
80− : initial_register{ r }
77+ dbg_cmd_cxt_t(debugger &d, const vm_registers_t &r)
78+ : dbg{ d }
8179 , exit_break{ false }
82− , vm{ v }
80+ , initial_register{ r }
81+ , vm{ d.controller->get_vm_instance() }
8382 {
8483 current_register = &initial_register;
8584 }
8685
8786 bool exit_break;
8887
88+ debugger &dbg;
8989 const vm_t &vm;
9090 const vm_registers_t *current_register;
9191 const vm_registers_t &initial_register;
@@ -125,10 +125,8 @@ namespace · 33 unmodified lines
125125 auto register_string = std::stringstream{};
126126
127127 register_string
128− << "Registers:\n"
129128 << " Thread ID: " << r.thread.get_thread_id() << "\n"
130− << " Module: " << r.module_ref->module->module_name->str() << "\n"
131− << " PC: " << r.pc << "\n"
129+ << " Module: " << r.module_ref->module->module_name->str() << " PC: " << r.pc << "\n"
132130 << " Opcode: " << r.module_ref->code_section[r.pc].op << "\n";
133131
134132 return register_string.str();
@@ -231,8 +229,12 @@ namespace · 96 unmodified lines
231229 }
232230 else
233231 {
234− ss << "alloc\n"
235− << "\tsize: " << alloc->alloc_type->size_in_bytes << "\n";
232+ ss << "alloc - size: " << alloc->alloc_type->size_in_bytes << "\n";
233+
234+ enum_pointer_fields(*alloc->alloc_type, alloc->get_allocation(), [&ss](pointer_t p, std::size_t o)
235+ {
236+ ss << " [" << o << "] " << vm_alloc_t::from_allocation(p);
237+ });
236238 }
237239
238240 return ss;
@@ -282,21 +284,6 @@ namespace · 43 unmodified lines
282284 return ss;
283285 }
284286
285− std::string to_string_frame_pointers(vm_registers_t &r)
286− {
287− auto frame = r.stack.peek_frame();
288− if (frame == nullptr)
289− return "<empty>";
290−
291− auto frame_string = std::stringstream{};
292− enum_pointer_fields(*frame->frame_type, frame->base(), [&frame_string](pointer_t p)
293− {
294− frame_string << std::showbase << std::hex << reinterpret_cast<uintptr_t>(p) << " : " << std::dec << vm_alloc_t::from_allocation(p);
295− });
296−
297− return frame_string.str();
298− }
299−
300287 void cmd_continue(const std::vector<std::string> &, dbg_cmd_cxt_t &cxt)
301288 {
302289 cxt.exit_break = true;
@@ -311,7 +298,6 @@ namespace · 8 unmodified lines
311298 void cmd_print_threads(const std::vector<std::string> &, dbg_cmd_cxt_t &cxt)
312299 {
313300 auto msg = std::stringstream{};
314− msg << "Threads:\n";
315301 for (auto t : cxt.vm.get_scheduler_control().get_all_threads())
316302 msg << " " << std::setw(5) << t->get_thread_id() << " - " << t->get_state() << "\n";
317303
@@ -321,21 +307,146 @@ namespace · 3 unmodified lines
321307 void cmd_print_modules(const std::vector<std::string> &, dbg_cmd_cxt_t &cxt)
322308 {
323309 auto msg = std::stringstream{};
324− msg << "Modules:\n";
325−
326310 cxt.vm.enum_loaded_modules([&msg](const loaded_vm_module_t &m)
327311 {
328312 auto module_instance = m.module.lock();
329− if (module_instance == nullptr)
330− return;
313+ if (module_instance != nullptr)
314+ {
315+ msg << std::setw(3) << m.load_id << ": "
316+ << std::setw(16) << module_instance->module_name->str()
317+ << std::setw(0) << " - " << m.origin->str() << "\n";
318+ }
331319
332− msg << std::setw(24) << module_instance->module_name->str() << std::setw(0) << " " << m.origin->str() << "\n";
320+ return true;
333321 });
334322
335323 debug_print_info(msg.str());
336324 }
337325
338− void cmd_stacktrace(const std::vector<std::string> &a, dbg_cmd_cxt_t &cxt)
326+ void cmd_breakpoint_set(const std::vector<std::string> &a, dbg_cmd_cxt_t &cxt)
327+ {
328+ if (a.size() < 3)
329+ {
330+ debug_print_error("Must supply an IP and module ID");
331+ return;
332+ }
333+
334+ vm_pc_t pc;
335+ try
336+ {
337+ pc = std::stoul(a[1]);
338+ }
339+ catch (...)
340+ {
341+ debug_print_error("Invalid IP format");
342+ return;
343+ }
344+
345+ uint32_t module_id;
346+ try
347+ {
348+ module_id = std::stoul(a[2]);
349+ }
350+ catch (...)
351+ {
352+ debug_print_error("Invalid module ID format");
353+ return;
354+ }
355+
356+ auto module = std::shared_ptr<vm_module_t>{};
357+ cxt.vm.enum_loaded_modules([module_id, &module](const loaded_vm_module_t &m)
358+ {
359+ if (module_id != m.load_id)
360+ return true;
361+
362+ // Found the matching ID so stop
363+ module = m.module.lock();
364+ return false;
365+ });
366+
367+ if (module == nullptr)
368+ {
369+ debug_print_error("Invalid module ID");
370+ return;
371+ }
372+ else if (util::has_flag(module->header.runtime_flag, runtime_flags_t::builtin))
373+ {
374+ debug_print_error("Unable to set breakpoint in built-in module");
375+ return;
376+ }
377+
378+ if (pc < 0 || module->code_section.size() <= static_cast<std::size_t>(pc))
379+ {
380+ debug_print_error("Invalid IP for module");
381+ return;
382+ }
383+
384+ cxt.dbg.breakpoint_cookies.push_back(cxt.dbg.controller->set_breakpoint(module, pc));
385+ }
386+
387+ void cmd_breakpoint_clear(const std::vector<std::string> &a, dbg_cmd_cxt_t &cxt)
388+ {
389+ if (a.size() == 1)
390+ {
391+ debug_print_error("Must supply a breakpoint ID or wildcard");
392+ return;
393+ }
394+
395+ auto controller = cxt.dbg.controller;
396+ if (a[1].compare("*") == 0)
397+ {
398+ // Clear all breakpoints
399+ for (auto cookie : cxt.dbg.breakpoint_cookies)
400+ controller->clear_breakpoint(cookie);
401+
402+ cxt.dbg.breakpoint_cookies.clear();
403+ return;
404+ }
405+
406+ // Clear specific breakpoint
407+ cookie_t breakpoint_cookie;
408+ try
409+ {
410+ breakpoint_cookie = static_cast<cookie_t>(std::stoul(a[1]));
411+ }
412+ catch (...)
413+ {
414+ debug_print_error("Invalid breakpoint ID format");
415+ return;
416+ }
417+
418+ auto cookie_iter = std::find_if(
419+ std::begin(cxt.dbg.breakpoint_cookies),
420+ std::end(cxt.dbg.breakpoint_cookies),
421+ [breakpoint_cookie] (const cookie_t c) { return c == breakpoint_cookie; });
422+ if (cookie_iter == cxt.dbg.breakpoint_cookies.end())
423+ {
424+ debug_print_error("Unknown breakpoint ID");
425+ return;
426+ }
427+
428+ // Clear the breakpoint
429+ controller->clear_breakpoint(breakpoint_cookie);
430+
431+ // Remove the cookie from the list
432+ cxt.dbg.breakpoint_cookies.erase(cookie_iter);
433+ }
434+
435+ void cmd_breakpoint_list(const std::vector<std::string> &, dbg_cmd_cxt_t &cxt)
436+ {
437+ auto msg = std::stringstream{};
438+ for (auto cookie : cxt.dbg.breakpoint_cookies)
439+ {
440+ const auto details = cxt.dbg.controller->get_breakpoint_details(cookie);
441+ msg << std::setw(3) << cookie << ": "
442+ << std::setw(16) << details.module->module_name->str()
443+ << std::setw(0) << " @ " << details.pc << "\n";
444+ }
445+
446+ debug_print_info(msg.str());
447+ }
448+
449+ void cmd_stackwalk(const std::vector<std::string> &a, dbg_cmd_cxt_t &cxt)
339450 {
340451 auto thread_registers = std::map<uint32_t, const vm_registers_t*>{};
341452 if (a.size() == 1)
@@ -345,7 +456,6 @@ namespace · 3 unmodified lines
345456 else
346457 {
347458 uint32_t thread_id;
348− auto all_threads = cxt.vm.get_scheduler_control().get_all_threads();
349459 if (a[1].compare("*") == 0)
350460 {
351461 thread_id = 0;
@@ -354,7 +464,7 @@ namespace · 2 unmodified lines
354464 {
355465 try
356466 {
357− thread_id = std::stoi(a[1]);
467+ thread_id = std::stoul(a[1]);
358468 }
359469 catch (...)
360470 {
@@ -364,6 +474,7 @@ namespace · 3 unmodified lines
364474 }
365475
366476 // Find the thread(s)
477+ auto all_threads = cxt.vm.get_scheduler_control().get_all_threads();
367478 for (auto t : all_threads)
368479 {
369480 const auto tid = t->get_thread_id();
@@ -406,7 +517,7 @@ namespace · 36 unmodified lines
406517 uint32_t thread_id;
407518 try
408519 {
409− thread_id = std::stoi(a[1]);
520+ thread_id = std::stoul(a[1]);
410521 }
411522 catch (...)
412523 {
@@ -432,7 +543,7 @@ namespace · 19 unmodified lines
432543
433544 void cmd_disassemble(const std::vector<std::string> &a, dbg_cmd_cxt_t &cxt)
434545 {
435− auto disassemble_length = 1;
546+ auto disassemble_length = int{ 1 };
436547 if (a.size() > 1)
437548 {
438549 try
@@ -629,11 +740,17 @@ namespace · 190 unmodified lines
629740 const dbg_cmd_t cmds[] =
630741 {
631742 { "c", "Continue", nullptr, nullptr, cmd_continue },
743+
632744 { "r", "Print registers", nullptr, nullptr, cmd_print_registers },
633745 { "t", "Print all threads", nullptr, nullptr, cmd_print_threads },
634746 { "m", "Print all loaded modules", nullptr, nullptr, cmd_print_modules },
635− { "bt", "Stack walk/back trace for the current or supplied thread", "bt ([0-9]+|\\*)?", "bt, bt 34, bt *", cmd_stacktrace },
636− { "st", "Switch to thread", "st [0-9]+", "st 42", cmd_switchthread },
747+
748+ { "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)", cmd_breakpoint_set },
749+ { "bc", "Clear breakpoint(s)", "bc ([0-9]+|\\*)", "bc 3, bc *", cmd_breakpoint_clear },
750+ { "bl", "List breakpoints", nullptr, nullptr, cmd_breakpoint_list },
751+
752+ { "sw", "Stack walk for the current or supplied thread", "sw ([0-9]+|\\*)?", "sw, sw 34, sw *", cmd_stackwalk },
753+ { "~", "Switch to thread", "~ [0-9]+", "~ 42", cmd_switchthread },
637754 { "d", "Disassemble the next/previous N instructions", "d (-?[0-9]+)?", "d, d -4, d 5", cmd_disassemble },
638755 { "x", "Examine memory", "x [mp|fp] [0-9]+ ([0-9]+)?", "x mp 3, x fp 20 6", cmd_examine },
639756 { "?", "Print help", nullptr, nullptr, cmd_help },
@@ -703,7 +820,7 @@ namespace · 63 unmodified lines
703820 return split_string;
704821 }
705822
706− void prompt(const vm_t &vm, const vm_registers_t &r)
823+ void prompt(debugger &debugger, const vm_registers_t &r)
707824 {
708825 std::cout
709826 << console_modifiers::green_font
@@ -711,10 +828,9 @@ namespace · 1 unmodified line
711828 << "'?' for help\n"
712829 << console_modifiers::reset_all;
713830
714− std::locale loc;
715831 std::string cmd;
716832
717− dbg_cmd_cxt_t cxt{ vm, r };
833+ dbg_cmd_cxt_t cxt{ debugger, r };
718834 for (;;)
719835 {
720836 std::cout
@@ -746,12 +862,17 @@ namespace · 25 unmodified lines
746862 }
747863 }
748864
749−void debugger::on_load(vm_t &vm, vm_tool_controller_t &controller, std::size_t tool_id)
865+debugger::debugger()
866+ : _tool_id{ 0 }
867+ , controller{ nullptr }
868+{ }
869+
870+void debugger::on_load(vm_tool_controller_t &controller_, std::size_t tool_id)
750871 {
751872 _tool_id = tool_id;
752− _vm = &vm;
873+ controller = &controller_;
753874
754− _event_cookies.push_back(controller.subscribe_event(vm_event_t::thread_begin, [](vm_event_t, vm_event_context_t &cxt)
875+ _event_cookies.push_back(controller->subscribe_event(vm_event_t::thread_begin, [](vm_event_t, vm_event_context_t &cxt)
755876 {
756877 auto t = cxt.value1.thread;
757878 auto ss = std::stringstream{};
@@ -765,7 +886,7 @@ void debugger::on_load(vm_t &vm, vm_tool_controller_t &controller, std::size_t t · 7 unmodified lines
765886 debug_print_info(ss.str());
766887 }));
767888
768− _event_cookies.push_back(controller.subscribe_event(vm_event_t::thread_end, [](vm_event_t, vm_event_context_t &cxt)
889+ _event_cookies.push_back(controller->subscribe_event(vm_event_t::thread_end, [](vm_event_t, vm_event_context_t &cxt)
769890 {
770891 auto ss = std::stringstream{};
771892 ss << "Thread "
@@ -775,7 +896,7 @@ void debugger::on_load(vm_t &vm, vm_tool_controller_t &controller, std::size_t t · 3 unmodified lines
775896 debug_print_info(ss.str());
776897 }));
777898
778− _event_cookies.push_back(controller.subscribe_event(vm_event_t::exception_raised, [](vm_event_t, vm_event_context_t &cxt)
899+ _event_cookies.push_back(controller->subscribe_event(vm_event_t::exception_raised, [](vm_event_t, vm_event_context_t &cxt)
779900 {
780901 auto ss = std::stringstream{};
781902 ss << "Exception '"
@@ -785,7 +906,7 @@ void debugger::on_load(vm_t &vm, vm_tool_controller_t &controller, std::size_t t · 3 unmodified lines
785906 debug_print_info(ss.str());
786907 }));
787908
788− _event_cookies.push_back(controller.subscribe_event(vm_event_t::exception_unhandled, [&](vm_event_t, vm_event_context_t &cxt)
909+ _event_cookies.push_back(controller->subscribe_event(vm_event_t::exception_unhandled, [&](vm_event_t, vm_event_context_t &cxt)
789910 {
790911 auto ss = std::stringstream{};
791912 ss << "Unhandled exception '"
@@ -793,37 +914,46 @@ void debugger::on_load(vm_t &vm, vm_tool_controller_t &controller, std::size_t t · 1 unmodified line
793914 << "'";
794915
795916 debug_print_error(ss.str());
796− prompt(*_vm, *cxt.value1.registers);
917+ prompt(*this, *cxt.value1.registers);
797918 }));
798919
799− _event_cookies.push_back(controller.subscribe_event(vm_event_t::module_load, [](vm_event_t, vm_event_context_t &cxt)
920+ _event_cookies.push_back(controller->subscribe_event(vm_event_t::module_thread_load, [&](vm_event_t, vm_event_context_t &cxt)
800921 {
801− auto m = (*cxt.value1.module);
922+ auto m = (*cxt.value2.module);
802923 auto ss = std::stringstream{};
803924 ss << "Module "
804925 << m->module_name->str()
805− << " loaded (";
926+ << " loaded";
806927
807− if (!util::has_flag(m->header.runtime_flag, runtime_flags_t::builtin))
808− ss << cxt.value2.str->str();
809− else
810− ss << "built-in";
928+ if (util::has_flag(m->header.runtime_flag, runtime_flags_t::builtin))
929+ ss << " (built-in)";
811930
812− ss << ")";
813931
814932 debug_print_info(ss.str());
815933 }));
816934
817− _controller = &controller;
935+ _event_cookies.push_back(controller->subscribe_event(vm_event_t::breakpoint, [&](vm_event_t, vm_event_context_t &cxt)
936+ {
937+ auto ss = std::stringstream{};
938+ ss << "Breakpoint hit";
939+ if (cxt.value2.cookie != 0)
940+ ss << " [" << cxt.value2.cookie << "]";
941+
942+ debug_print_info(ss.str());
943+
944+ auto reg_str = registers_to_string(*cxt.value1.registers);
945+ debug_print_info(reg_str);
946+
947+ prompt(*this, *cxt.value1.registers);
948+ }));
818949 }
819950
820951 void debugger::on_unload()
821952 {
822− assert(_controller != nullptr);
953+ assert(controller != nullptr);
823954
824955 for (auto ec : _event_cookies)
825− _controller->unsubscribe_event(ec);
956+ controller->unsubscribe_event(ec);
826957
827− _controller = nullptr;
828− _vm = nullptr;
958+ controller = nullptr;
829959 }
src/exec/exec.h+11−6
@@ -10,20 +10,25 @@ · 9 unmodified lines
1010 #include <cstdint>
1111 #include <vector>
1212 #include <disvm.h>
13+#include <vm_tools.h>
1314
1415 class debugger final : public disvm::runtime::vm_tool_t
1516 {
17+public:
18+ debugger();
19+
1620 public: // vm_tool_t
17− void on_load(disvm::vm_t &vm, disvm::runtime::vm_tool_controller_t &controller, std::size_t tool_id);
21+ void on_load(disvm::runtime::vm_tool_controller_t &controller, std::size_t tool_id) override;
22+
23+ void on_unload() override;
1824
19− void on_unload();
25+public:
26+ disvm::runtime::vm_tool_controller_t *controller;
27+ std::vector<disvm::runtime::cookie_t> breakpoint_cookies;
2028
2129 private:
22− disvm::vm_t *_vm;
2330 std::size_t _tool_id;
24− disvm::runtime::vm_tool_controller_t *_controller;
25− std::vector<std::size_t> _event_cookies;
26− std::vector<std::size_t> _breakpoint_cookies;
31+ std::vector<disvm::runtime::cookie_t> _event_cookies;
2732 };
2833
2934 #endif // _DISVM_SRC_EXEC_EXEC_H_
src/include/disvm.h+4−1
@@ -39,6 +39,8 @@ namespace disvm · 38 unmodified lines
3939 class loaded_vm_module_t final
4040 {
4141 public:
42+ std::size_t load_id;
43+
4244 // Origin of module
4345 std::unique_ptr<runtime::vm_string_t> origin;
4446
@@ -46,7 +48,8 @@ namespace disvm · 1 unmodified line
4648 std::weak_ptr<runtime::vm_module_t> module;
4749 };
4850
49− using loaded_vm_module_callback_t = std::function<void(const loaded_vm_module_t&)>;
51+ // Callback for enumerating loaded modules - return 'false' to stop enumeration, otherwise 'true'
52+ using loaded_vm_module_callback_t = std::function<bool(const loaded_vm_module_t&)>;
5053
5154 // Virtual machine
5255 class vm_t final
src/include/runtime.h+4−5
@@ -824,13 +824,12 @@ namespace disvm · 823 unmodified lines
824824 virtual void set_tool_dispatch_on_all_threads(vm_tool_dispatch_t *dispatch) = 0;
825825 };
826826
827+ // The supplied allocation is guaranteed to be valid for the lifetime of the callback.
828+ using vm_alloc_callback_t = std::function<void(const vm_alloc_t *)>;
829+
827830 // VM garbage collector interface
828831 class vm_garbage_collector_t
829832 {
830− public:
831− // The supplied allocation is guaranteed to be valid for the lifetime of the callback.
832− using vm_alloc_callback_t = std::function<void(const vm_alloc_t *)>;
833−
834833 public:
835834 virtual ~vm_garbage_collector_t() = 0;
836835
@@ -862,7 +861,7 @@ namespace disvm · 25 unmodified lines
862861 virtual ~vm_tool_t() = 0;
863862
864863 // Called when the tool is loaded into the VM
865− virtual void on_load(vm_t &vm, vm_tool_controller_t &controller, std::size_t tool_id) = 0;
864+ virtual void on_load(vm_tool_controller_t &controller, std::size_t tool_id) = 0;
866865
867866 // Called when the tool is unloaded from the VM.
868867 // Note once the tool has been unloaded the vm and controller references supplied during load
src/include/vm_memory.h+13−11
@@ -70,10 +70,11 @@ namespace disvm · 69 unmodified lines
7070 // Increment the ref count of all points in the supplied memory allocation
7171 void inc_ref_count_in_memory(const type_descriptor_t &type_desc, void *data);
7272
73− // Decrement the ref count and if 0 free the allocation.
73+ // Decrement the ref count and if 0 free the allocation
7474 void dec_ref_count_and_free(vm_alloc_t *alloc);
7575
76− using pointer_field_callback_t = std::function<void(pointer_t)>;
76+ // Callback for a pointer field supplying the pointer and byte offset
77+ using pointer_field_callback_t = std::function<void(pointer_t, std::size_t)>;
7778
7879 // The callback will be called on all non-null pointer fields.
7980 inline void enum_pointer_fields(const type_descriptor_t &type_desc, void *data, pointer_field_callback_t callback)
@@ -82,8 +83,9 @@ namespace disvm · 2 unmodified lines
8283 if (type_desc.size_in_bytes == 0)
8384 return;
8485
86+ auto offset_accum = std::size_t{ 0 };
8587 auto memory = reinterpret_cast<word_t *>(data);
86− for (auto i = word_t{ 0 }; i < type_desc.map_in_bytes; ++i, memory += 8)
88+ for (auto i = word_t{ 0 }; i < type_desc.map_in_bytes; ++i, memory += 8, offset_accum += 8)
8789 {
8890 const auto words8 = type_desc.pointer_map[i];
8991 if (words8 != 0)
@@ -91,14 +93,14 @@ namespace disvm · 1 unmodified line
9193 const auto flags = std::bitset<sizeof(words8) * 8>{ words8 };
9294
9395 // Enumerating the flags in reverse order so memory access is sequential.
94− if (flags[7] && (memory[0] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[0]));
95− if (flags[6] && (memory[1] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[1]));
96− if (flags[5] && (memory[2] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[2]));
97− if (flags[4] && (memory[3] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[3]));
98− if (flags[3] && (memory[4] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[4]));
99− if (flags[2] && (memory[5] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[5]));
100− if (flags[1] && (memory[6] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[6]));
101− if (flags[0] && (memory[7] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[7]));
96+ if (flags[7] && (memory[0] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[0]), offset_accum);
97+ if (flags[6] && (memory[1] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[1]), offset_accum + (1 * sizeof(pointer_t)));
98+ if (flags[5] && (memory[2] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[2]), offset_accum + (2 * sizeof(pointer_t)));
99+ if (flags[4] && (memory[3] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[3]), offset_accum + (3 * sizeof(pointer_t)));
100+ if (flags[3] && (memory[4] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[4]), offset_accum + (4 * sizeof(pointer_t)));
101+ if (flags[2] && (memory[5] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[5]), offset_accum + (5 * sizeof(pointer_t)));
102+ if (flags[1] && (memory[6] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[6]), offset_accum + (6 * sizeof(pointer_t)));
103+ if (flags[0] && (memory[7] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[7]), offset_accum + (7 * sizeof(pointer_t)));
102104 }
103105 }
104106 }
src/include/vm_tools.h+23−8
@@ -17,10 +17,12 @@ namespace disvm · 16 unmodified lines
1717 {
1818 namespace runtime
1919 {
20+ using cookie_t = std::size_t;
21+
2022 // Value in event context
2123 union vm_context_value_t
2224 {
23− std::size_t i;
25+ cookie_t cookie;
2426 std::shared_ptr<vm_module_t> *module; // Note this is a pointer to std::shared_ptr<>
2527 vm_registers_t *registers;
2628 const vm_thread_t *thread;
@@ -39,8 +41,11 @@ namespace disvm · 12 unmodified lines
3941 // List of possible events
4042 enum class vm_event_t
4143 {
42− module_load, // value1: std::shared_ptr<vm_module_t>
43− // value2: const vm_string_t - path
44+ module_vm_load, // value1: std::shared_ptr<vm_module_t>
45+ // value2: const vm_string_t - path on disk
46+
47+ module_thread_load, // value1: vm_registers_t
48+ // value2: std::shared_ptr<vm_module_t>
4449
4550 thread_begin, // value1: const vm_thread_t
4651 thread_end, // value1: const vm_thread_t
@@ -54,22 +59,32 @@ namespace disvm · 7 unmodified lines
5459 // value3: const vm_alloc_t - object raised
5560
5661 breakpoint, // value1: vm_registers_t
57− // value2: std::size_t - cookie of breakpoint. '0' indicates the breakpoint was inserted by the VM or compiler.
62+ // value2: cookie_t - cookie of breakpoint. '0' indicates the breakpoint was inserted by the VM or compiler.
5863 };
5964
6065 using vm_event_callback_t = std::function<void(vm_event_t, vm_event_context_t &)>;
6166
67+ struct breakpoint_details_t final
68+ {
69+ std::shared_ptr<vm_module_t> module;
70+ vm_pc_t pc;
71+ };
72+
6273 // VM tool controller
6374 class vm_tool_controller_t
6475 {
6576 public:
77+ // The current vm instance
78+ virtual vm_t& get_vm_instance() const = 0;
79+
6680 // Call the supplied callback when the specified event occurs
67− virtual std::size_t subscribe_event(vm_event_t, vm_event_callback_t) = 0;
68− virtual void unsubscribe_event(std::size_t) = 0;
81+ virtual cookie_t subscribe_event(vm_event_t, vm_event_callback_t) = 0;
82+ virtual void unsubscribe_event(cookie_t) = 0;
6983
7084 // Set a breakpoint in the supplied module at the specified PC location
71− virtual std::size_t set_breakpoint(std::shared_ptr<vm_module_t>, vm_pc_t) = 0;
72− virtual void clear_breakpoint(std::size_t) = 0;
85+ virtual cookie_t set_breakpoint(std::shared_ptr<vm_module_t>, vm_pc_t) = 0;
86+ virtual breakpoint_details_t get_breakpoint_details(cookie_t) const = 0;
87+ virtual void clear_breakpoint(cookie_t) = 0;
7388 };
7489 }
7590 }
src/vm/execution_table.cpp+8−2
@@ -1750,12 +1750,13 @@ namespace · 1749 unmodified lines
17501750 dec_ref_count_and_free(at_val<vm_alloc_t>(r.dest));
17511751 pt_ref(r.dest) = nullptr;
17521752
1753− auto imported_module = std::shared_ptr<const vm_module_t>{};
1753+ auto imported_module = std::shared_ptr<vm_module_t>{};
17541754
17551755 // Handle self loading module optimization.
17561756 if (std::strcmp(str->str(), "$self") == 0)
17571757 {
1758− imported_module = r.module_ref->module;
1758+ // Remove 'const' from the module type in the shared_ptr<>
1759+ imported_module = std::const_pointer_cast<vm_module_t>(r.module_ref->module);
17591760 }
17601761 else
17611762 {
@@ -1779,6 +1780,11 @@ namespace · 17 unmodified lines
17791780
17801781 auto entry_module_ref = new vm_module_ref_t{ imported_module, function_imports };
17811782 pt_ref(r.dest) = entry_module_ref->get_allocation();
1783+
1784+ // Check if the tool dispatcher has been supplied
1785+ auto tool_dispatch = r.tool_dispatch.load();
1786+ if (tool_dispatch != nullptr)
1787+ tool_dispatch->on_module_thread_load(r, imported_module);
17821788 }
17831789 }
17841790
src/vm/garbage_collector.cpp+3−3
@@ -22,7 +22,7 @@ vm_garbage_collector_t::~vm_garbage_collector_t() · 21 unmodified lines
2222
2323 std::unique_ptr<vm_garbage_collector_t> disvm::runtime::create_no_op_gc(vm_t &)
2424 {
25− class no_op_gc : public vm_garbage_collector_t
25+ class no_op_gc final : public vm_garbage_collector_t
2626 {
2727 public:
2828 void track_allocation(vm_alloc_t *) override
@@ -118,7 +118,7 @@ namespace · 89 unmodified lines
118118 {
119119 std::queue<vm_alloc_t *> allocs;
120120
121− auto add_pointer_to_queue = std::function<void(pointer_t p)>{ [&allocs](pointer_t p) -> void
121+ auto add_pointer_to_queue = std::function<void(pointer_t p, std::size_t)>{ [&allocs](pointer_t p, std::size_t) -> void
122122 {
123123 auto a = vm_alloc_t::from_allocation(p);
124124 allocs.push(a);
@@ -155,7 +155,7 @@ namespace · 30 unmodified lines
155155 debug::log_msg(debug::component_trace_t::garbage_collector, debug::log_level_t::debug, "gc: roots found: %" PRIuPTR "\n", allocs.size());
156156
157157 // If logging is enabled update the callback with logging
158− add_pointer_to_queue = std::function<void(pointer_t p)>{ [&allocs](pointer_t p) -> void
158+ add_pointer_to_queue = std::function<void(pointer_t p, std::size_t)>{ [&allocs](pointer_t p, std::size_t) -> void
159159 {
160160 auto a = vm_alloc_t::from_allocation(p);
161161 debug::log_msg(debug::component_trace_t::garbage_collector, debug::log_level_t::debug, "\tref: %#" PRIxPTR "\n", a);
src/vm/garbage_collector.h+3−3
@@ -26,11 +26,11 @@ namespace disvm · 25 unmodified lines
2626 ~default_garbage_collector_t();
2727
2828 public: // vm_garbage_collector_t
29− void track_allocation(vm_alloc_t *alloc) override final;
29+ void track_allocation(vm_alloc_t *alloc) override;
3030
31− void enum_tracked_allocations(vm_alloc_callback_t callback) const override final;
31+ void enum_tracked_allocations(vm_alloc_callback_t callback) const override;
3232
33− bool collect(std::vector<std::shared_ptr<const vm_thread_t>> &threads) override final;
33+ bool collect(std::vector<std::shared_ptr<const vm_thread_t>> &threads) override;
3434
3535 private:
3636 vm_t &_vm;
src/vm/scheduler.h+6−6
@@ -34,18 +34,18 @@ namespace disvm · 33 unmodified lines
3434 ~default_scheduler_t();
3535
3636 public: // vm_scheduler_t
37− bool is_idle() const override final;
37+ bool is_idle() const override;
3838
39− vm_scheduler_control_t &get_controller() const override final;
39+ vm_scheduler_control_t &get_controller() const override;
4040
41− const vm_thread_t& schedule_thread(std::unique_ptr<vm_thread_t> thread) override final;
41+ const vm_thread_t& schedule_thread(std::unique_ptr<vm_thread_t> thread) override;
4242
43− void set_tool_dispatch_on_all_threads(vm_tool_dispatch_t *dispatch) override final;
43+ void set_tool_dispatch_on_all_threads(vm_tool_dispatch_t *dispatch) override;
4444
4545 public: // vm_scheduler_control_t
46− void enqueue_blocked_thread(uint32_t thread_id) override final;
46+ void enqueue_blocked_thread(uint32_t thread_id) override;
4747
48− std::vector<std::shared_ptr<const vm_thread_t>> get_all_threads() const override final;
48+ std::vector<std::shared_ptr<const vm_thread_t>> get_all_threads() const override;
4949
5050 private:
5151 struct thread_instance_t final
src/vm/tool_dispatch.cpp+48−22
@@ -50,7 +50,7 @@ std::size_t vm_tool_dispatch_t::load_tool(std::shared_ptr<runtime::vm_tool_t> to · 49 unmodified lines
5050 {
5151 // [TODO] For debug builds, it might be useful to wrap the controller in a proxy that records
5252 // the subscribed callbacks and asserts they are all properly unsubscribed during agent unload.
53− tool->on_load(_vm, *this, current_id);
53+ tool->on_load(*this, current_id);
5454 }
5555 catch (...)
5656 {
@@ -91,12 +91,12 @@ std::size_t vm_tool_dispatch_t::unload_tool(std::size_t tool_id) · 34 unmodified lines
9191 opcode_t vm_tool_dispatch_t::on_breakpoint(vm_registers_t &r)
9292 {
9393 opcode_t original_op;
94− std::size_t cookie_id;
95− std::tie(original_op, cookie_id) = get_original_opcode(r);
94+ cookie_t cookie_id;
95+ std::tie(original_op, cookie_id) = get_original_opcode_and_cookie(r);
9696
9797 vm_event_context_t cxt{};
9898 cxt.value1.registers = &r;
99− cxt.value2.i = cookie_id;
99+ cxt.value2.cookie = cookie_id;
100100
101101 _events.fire_event_callbacks(vm_event_t::breakpoint, cxt);
102102
@@ -123,7 +123,7 @@ void vm_tool_dispatch_t::on_exception_unhandled(vm_registers_t &r, const vm_stri · 20 unmodified lines
123123 _events.fire_event_callbacks(vm_event_t::exception_unhandled, cxt);
124124 }
125125
126−void vm_tool_dispatch_t::on_module_load(std::shared_ptr<vm_module_t> &m, const vm_string_t &path)
126+void vm_tool_dispatch_t::on_module_vm_load(std::shared_ptr<vm_module_t> &m, const vm_string_t &path)
127127 {
128128 assert(m != nullptr);
129129
@@ -131,7 +131,18 @@ void vm_tool_dispatch_t::on_module_load(std::shared_ptr<vm_module_t> &m, const v · 1 unmodified line
131131 cxt.value1.module = &m;
132132 cxt.value2.str = &path;
133133
134− _events.fire_event_callbacks(vm_event_t::module_load, cxt);
134+ _events.fire_event_callbacks(vm_event_t::module_vm_load, cxt);
135+}
136+
137+void vm_tool_dispatch_t::on_module_thread_load(vm_registers_t &r, std::shared_ptr<vm_module_t> &m)
138+{
139+ assert(m != nullptr);
140+
141+ vm_event_context_t cxt{};
142+ cxt.value1.registers = &r;
143+ cxt.value2.module = &m;
144+
145+ _events.fire_event_callbacks(vm_event_t::module_thread_load, cxt);
135146 }
136147
137148 void vm_tool_dispatch_t::on_thread_begin(vm_thread_t &t)
@@ -150,7 +161,12 @@ void vm_tool_dispatch_t::on_thread_end(vm_thread_t &t) · 12 unmodified lines
150161 _events.fire_event_callbacks(vm_event_t::thread_end, cxt);
151162 }
152163
153−std::size_t vm_tool_dispatch_t::subscribe_event(vm_event_t evt, vm_event_callback_t cb)
164+vm_t& vm_tool_dispatch_t::get_vm_instance() const
165+{
166+ return _vm;
167+}
168+
169+cookie_t vm_tool_dispatch_t::subscribe_event(vm_event_t evt, vm_event_callback_t cb)
154170 {
155171 if (!cb)
156172 throw vm_system_exception{ "Invalid callback for event" };
@@ -172,7 +188,7 @@ std::size_t vm_tool_dispatch_t::subscribe_event(vm_event_t evt, vm_event_callbac · 15 unmodified lines
172188 return cookie_id;
173189 }
174190
175−void vm_tool_dispatch_t::unsubscribe_event(std::size_t cookie_id)
191+void vm_tool_dispatch_t::unsubscribe_event(cookie_t cookie_id)
176192 {
177193 std::lock_guard<std::mutex> lock{ _events.lock };
178194
@@ -190,7 +206,7 @@ void vm_tool_dispatch_t::unsubscribe_event(std::size_t cookie_id) · 11 unmodified lines
190206 debug::log_msg(debug::component_trace_t::tool, debug::log_level_t::debug, "unsubscribe: event: %d %d", evt, cookie_id);
191207 }
192208
193−std::size_t vm_tool_dispatch_t::set_breakpoint(std::shared_ptr<vm_module_t> module, vm_pc_t pc)
209+cookie_t vm_tool_dispatch_t::set_breakpoint(std::shared_ptr<vm_module_t> module, vm_pc_t pc)
194210 {
195211 if (module == nullptr || util::has_flag(module->header.runtime_flag, runtime_flags_t::builtin))
196212 throw vm_system_exception{ "Unable to set breakpoint in supplied module" };
@@ -209,7 +225,7 @@ std::size_t vm_tool_dispatch_t::set_breakpoint(std::shared_ptr<vm_module_t> modu · 12 unmodified lines
209225 const auto cookie_id = _breakpoints.cookie_counter++;
210226
211227 // Map the cookie to the module/pc pair.
212− _breakpoints.cookie_to_modulepc[cookie_id] = std::make_pair(module, pc);
228+ _breakpoints.cookie_to_details[cookie_id] = { module, pc };
213229
214230 // Record the original opcode
215231 auto &pc_map = _breakpoints.original_opcodes[reinterpret_cast<std::uintptr_t>(module.get())];
@@ -225,20 +241,30 @@ std::size_t vm_tool_dispatch_t::set_breakpoint(std::shared_ptr<vm_module_t> modu · 9 unmodified lines
225241 }
226242 }
227243
228−void vm_tool_dispatch_t::clear_breakpoint(std::size_t cookie_id)
244+breakpoint_details_t vm_tool_dispatch_t::get_breakpoint_details(cookie_t cookie_id) const
229245 {
230246 std::lock_guard<std::mutex> lock{ _breakpoints.lock };
231247
232− // Resolve the cookie to the module/pc pair
233− auto iter_cookie = _breakpoints.cookie_to_modulepc.find(cookie_id);
234− if (iter_cookie == _breakpoints.cookie_to_modulepc.cend())
235− return;
248+ auto iter_cookie = _breakpoints.cookie_to_details.find(cookie_id);
249+ if (iter_cookie == _breakpoints.cookie_to_details.cend())
250+ throw vm_system_exception{ "Unknown breakpoint cookie" };
251+
252+ return iter_cookie->second;
253+}
254+
255+void vm_tool_dispatch_t::clear_breakpoint(cookie_t cookie_id)
256+{
257+ std::lock_guard<std::mutex> lock{ _breakpoints.lock };
258+
259+ auto iter_cookie = _breakpoints.cookie_to_details.find(cookie_id);
260+ if (iter_cookie == _breakpoints.cookie_to_details.cend())
261+ throw vm_system_exception{ "Unknown breakpoint cookie" };
236262
237− auto mod_pc_pair = iter_cookie->second;
238− const auto target_pc = mod_pc_pair.second;
263+ auto details = iter_cookie->second;
264+ const auto target_pc = details.pc;
239265
240266 // Determine the original opcode
241− auto iter_orig = _breakpoints.original_opcodes.find(reinterpret_cast<std::uintptr_t>(mod_pc_pair.first.get()));
267+ auto iter_orig = _breakpoints.original_opcodes.find(reinterpret_cast<std::uintptr_t>(details.module.get()));
242268 assert(iter_orig != _breakpoints.original_opcodes.cend());
243269
244270 auto &pc_map = iter_orig->second;
@@ -248,12 +274,12 @@ void vm_tool_dispatch_t::clear_breakpoint(std::size_t cookie_id) · 3 unmodified lines
248274 auto original_opcode = iter_pc->second.first;
249275
250276 // Replace the breakpoint opcode with the original
251− auto &code_section = const_cast<code_section_t &>(mod_pc_pair.first->code_section);
277+ auto &code_section = const_cast<code_section_t &>(details.module->code_section);
252278 assert(code_section[target_pc].op.opcode == opcode_t::brkpt);
253279 code_section[target_pc].op.opcode = original_opcode;
254280
255281 if (debug::is_component_tracing_enabled<debug::component_trace_t::tool>())
256− debug::log_msg(debug::component_trace_t::tool, debug::log_level_t::debug, "breakpoint: unset: %d %d >>%s<<", cookie_id, target_pc, mod_pc_pair.first->module_name->str());
282+ debug::log_msg(debug::component_trace_t::tool, debug::log_level_t::debug, "breakpoint: unset: %d %d >>%s<<", cookie_id, target_pc, details.module->module_name->str());
257283
258284 // Clean-up the PC mapping to opcode
259285 pc_map.erase(iter_pc);
@@ -263,10 +289,10 @@ void vm_tool_dispatch_t::clear_breakpoint(std::size_t cookie_id) · 3 unmodified lines
263289 _breakpoints.original_opcodes.erase(iter_orig);
264290
265291 // Remove the cookie map
266− _breakpoints.cookie_to_modulepc.erase(iter_cookie);
292+ _breakpoints.cookie_to_details.erase(iter_cookie);
267293 }
268294
269−std::tuple<opcode_t, std::size_t> vm_tool_dispatch_t::get_original_opcode(const vm_registers_t &r)
295+std::tuple<opcode_t, cookie_t> vm_tool_dispatch_t::get_original_opcode_and_cookie(const vm_registers_t &r)
270296 {
271297 const auto module = r.module_ref->module.get();
272298 const auto current_pc = r.pc - 1;
src/vm/tool_dispatch.h+22−16
@@ -42,8 +42,11 @@ namespace disvm · 41 unmodified lines
4242 // Callback on an unhandled exception
4343 void on_exception_unhandled(vm_registers_t &r, const vm_string_t &id, vm_alloc_t &e);
4444
45− // Callback on module load
46− void on_module_load(std::shared_ptr<vm_module_t> &m, const vm_string_t &path);
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);
47+
48+ // Callback for when a thread loads a module into its scope
49+ void on_module_thread_load(vm_registers_t &r, std::shared_ptr<vm_module_t> &m);
4750
4851 // Callback when thread lifetime begins.
4952 // The thread has not been scheduled and no opcodes have been executed.
@@ -53,14 +56,17 @@ namespace disvm · 3 unmodified lines
5356 void on_thread_end(vm_thread_t &t);
5457
5558 public: // vm_tool_controller_t
56− std::size_t subscribe_event(vm_event_t evt, vm_event_callback_t cb) override final;
57− void unsubscribe_event(std::size_t cookie_id) override final;
59+ vm_t& get_vm_instance() const override;
60+
61+ cookie_t subscribe_event(vm_event_t evt, vm_event_callback_t cb) override;
62+ void unsubscribe_event(cookie_t cookie_id) override;
5863
59− std::size_t set_breakpoint(std::shared_ptr<vm_module_t> module, vm_pc_t pc) override final;
60− void clear_breakpoint(std::size_t cookie_id) override final;
64+ cookie_t set_breakpoint(std::shared_ptr<vm_module_t> module, vm_pc_t pc) override;
65+ breakpoint_details_t get_breakpoint_details(cookie_t) const override;
66+ void clear_breakpoint(cookie_t cookie_id) override;
6167
6268 private:
63− std::tuple<opcode_t, std::size_t> get_original_opcode(const vm_registers_t &r);
69+ std::tuple<opcode_t, cookie_t> get_original_opcode_and_cookie(const vm_registers_t &r);
6470
6571 private:
6672 vm_t &_vm;
@@ -73,28 +79,28 @@ namespace disvm · 6 unmodified lines
7379 {
7480 void fire_event_callbacks(vm_event_t, vm_event_context_t &);
7581
76− using cookie_to_callback_map_t = std::unordered_map<std::size_t, vm_event_callback_t>;
82+ using cookie_to_callback_map_t = std::unordered_map<cookie_t, vm_event_callback_t>;
7783 using event_to_callbacks_map_t = std::unordered_map<vm_event_t, cookie_to_callback_map_t>;
7884 event_to_callbacks_map_t callbacks;
7985
80− using cookie_to_event_map_t = std::unordered_map<std::size_t, vm_event_t>;
86+ using cookie_to_event_map_t = std::unordered_map<cookie_t, vm_event_t>;
8187 cookie_to_event_map_t cookie_to_event;
8288
83− std::mutex lock;
84− std::size_t cookie_counter;
89+ mutable std::mutex lock;
90+ cookie_t cookie_counter;
8591 } _events;
8692
8793 struct
8894 {
89− using cookie_to_modulepc_t = std::unordered_map<std::size_t, std::pair<std::shared_ptr<const vm_module_t>, vm_pc_t>>;
90− cookie_to_modulepc_t cookie_to_modulepc;
95+ using cookie_to_details_t = std::unordered_map<cookie_t, breakpoint_details_t>;
96+ cookie_to_details_t cookie_to_details;
9197
92− using pc_to_opcode_cookie_map_t = std::unordered_map<vm_pc_t, std::pair<opcode_t, std::size_t>>;
98+ using pc_to_opcode_cookie_map_t = std::unordered_map<vm_pc_t, std::pair<opcode_t, cookie_t>>;
9399 using module_to_pc_map_t = std::unordered_map<std::uintptr_t, pc_to_opcode_cookie_map_t>;
94100 module_to_pc_map_t original_opcodes;
95101
96− std::mutex lock;
97− std::size_t cookie_counter;
102+ mutable std::mutex lock;
103+ cookie_t cookie_counter;
98104 } _breakpoints;
99105 };
100106 }
src/vm/vm.cpp+44−31
@@ -126,53 +126,63 @@ std::shared_ptr<runtime::vm_module_t> vm_t::load_module(const char *path) · 125 unmodified lines
126126 if (path == nullptr)
127127 throw vm_user_exception{ "Invalid module path" };
128128
129− std::lock_guard<std::mutex> lock{ _modules_lock };
130−
131− // Check if the module is already loaded.
132− auto iter = std::begin(_modules);
133− while (iter != std::end(_modules))
129+ auto new_module_iter = loaded_modules_t::const_iterator{};
130+ auto new_module = std::shared_ptr<vm_module_t>{};
134131 {
135− if (0 == ::strcmp(iter->origin->str(), path))
136− {
137− auto module = iter->module.lock();
132+ std::lock_guard<std::mutex> lock{ _modules_lock };
138133
139− // If an entry exists but is null, the module was loaded before. Read the module again and return.
140− if (module == nullptr)
134+ // Check if the module is already loaded.
135+ auto iter = std::begin(_modules);
136+ while (iter != std::end(_modules))
137+ {
138+ if (0 == ::strcmp(iter->origin->str(), path))
141139 {
142− module = std::shared_ptr<vm_module_t>{ std::move(read_module(path)) };
143− iter->module = module;
140+ auto module = iter->module.lock();
141+
142+ // If an entry exists but is null, the module was loaded before. Read the module again and return.
143+ if (module == nullptr)
144+ {
145+ module = std::shared_ptr<vm_module_t>{ std::move(read_module(path)) };
146+ iter->module = module;
144147
145− debug::log_msg(debug::component_trace_t::module, debug::log_level_t::debug, "reload: vm module: >>%s<<\n", path);
148+ debug::log_msg(debug::component_trace_t::module, debug::log_level_t::debug, "reload: vm module: >>%s<<\n", path);
149+ }
150+
151+ return module;
146152 }
147153
148− return module;
154+ ++iter;
149155 }
150156
151− ++iter;
152− }
157+ if (path[0] == BUILTIN_MODULE_PREFIX_CHAR)
158+ {
159+ new_module = std::move(builtin::get_builtin_module(path));
160+ }
161+ else
162+ {
163+ // Read in the module
164+ new_module = std::move(read_module(path));
165+ }
153166
154− auto new_module = std::shared_ptr<vm_module_t>{};
155− if (path[0] == BUILTIN_MODULE_PREFIX_CHAR)
156− {
157− new_module = std::move(builtin::get_builtin_module(path));
158− }
159− else
160− {
161− // Read in the module
162− new_module = std::move(read_module(path));
167+ auto path_local = std::make_unique<vm_string_t>(std::strlen(path), reinterpret_cast<const uint8_t *>(path));
168+
169+ auto load_id_next = std::size_t{ 0 };
170+ if (!_modules.empty())
171+ load_id_next = (_modules.front().load_id + 1);
172+
173+ _modules.push_front(std::move(loaded_vm_module_t{ load_id_next, std::move(path_local), new_module }));
174+ new_module_iter = _modules.begin();
163175 }
164176
165− auto path_local = std::make_unique<vm_string_t>(std::strlen(path), reinterpret_cast<const uint8_t *>(path));
166177 if (_tool_dispatch != nullptr)
167178 {
168179 std::lock_guard<std::mutex> lock{ _tool_dispatch_lock };
169180 if (_tool_dispatch != nullptr)
170− _tool_dispatch->on_module_load(new_module, *path_local);
181+ _tool_dispatch->on_module_vm_load(new_module, *new_module_iter->origin);
171182 }
172183
173− _modules.push_front(std::move(loaded_vm_module_t{ std::move(path_local), new_module }));
174−
175− debug::log_msg(debug::component_trace_t::module, debug::log_level_t::debug, "load: vm module: >>%s<<\n", path);
184+ if (debug::is_component_tracing_enabled<debug::component_trace_t::module>())
185+ debug::log_msg(debug::component_trace_t::module, debug::log_level_t::debug, "load: vm module: >>%s<<\n", path);
176186
177187 return new_module;
178188 }
@@ -184,7 +194,10 @@ void vm_t::enum_loaded_modules(loaded_vm_module_callback_t callback) const · 5 unmodified lines
184194
185195 std::lock_guard<std::mutex> lock{ _modules_lock };
186196 for (auto &m : _modules)
187− callback(m);
197+ {
198+ if (!callback(m))
199+ return;
200+ }
188201 }
189202
190203 runtime::vm_scheduler_control_t &vm_t::get_scheduler_control() const
src/vm/vm_memory.cpp+2−2
@@ -170,13 +170,13 @@ void disvm::runtime::init_memory(const type_descriptor_t &type_desc, void *data) · 169 unmodified lines
170170
171171 namespace
172172 {
173− void dec_ref_and_free_pointer_field(pointer_t pointer_field)
173+ void dec_ref_and_free_pointer_field(pointer_t pointer_field, std::size_t)
174174 {
175175 assert(pointer_field != nullptr);
176176 dec_ref_count_and_free(vm_alloc_t::from_allocation(pointer_field));
177177 }
178178
179− void add_ref_pointer_field(pointer_t pointer_field)
179+ void add_ref_pointer_field(pointer_t pointer_field, std::size_t)
180180 {
181181 assert(pointer_field != nullptr);
182182 vm_alloc_t::from_allocation(pointer_field)->add_ref();