History

Various work for tool support in the VM. Update some type names (i.e. _t) and alter API signatures to be more consistent.

arr committed 10 years ago

Commit e5d3f861 parent bcb413516 files · +514 −45Browse files

Unified · Split

16 files
  1. src/include/debug.h +1 −0
  2. src/include/disvm.h +7 −3
  3. src/include/opcodes.h +4 −1
  4. src/include/runtime.h +37 −10
  5. src/include/utf8.h +2 −2
  6. src/include/vm_tools.h +63 −0
  7. src/vm/channel.cpp +1 −1
  8. src/vm/disvm.gyp +1 −0
  9. src/vm/execution_table.cpp +26 −5
  10. src/vm/scheduler.cpp +10 −3
  11. src/vm/sys/Sysmod.cpp +3 −3
  12. src/vm/thread.cpp +18 −7
  13. src/vm/tool_dispatch.cpp +232 −0
  14. src/vm/tool_dispatch.h +84 −0
  15. src/vm/utf8.cpp +8 −8
  16. src/vm/vm.cpp +17 −2
src/include/debug.h+1−0
@@ -24,6 +24,7 @@ namespace disvm · 23 unmodified lines
2424 stack,
2525 channel,
2626 exception,
27+ tool,
2728 memory, // Noisy component
2829 addressing, // Noisy component [DEBUG build only]
2930
src/include/disvm.h+7−3
@@ -71,6 +71,10 @@ namespace disvm · 70 unmodified lines
7171 // Load the module at the supplied path into the virtual machine
7272 std::shared_ptr<const runtime::vm_module_t> load_module(const char *path);
7373
74+ // Get a collection of all currently loaded modules.
75+ // Note this will only include modules that have been loaded by a call to vm_t::load_module.
76+ std::vector<std::shared_ptr<const runtime::vm_module_t>> get_loaded_modules() const;
77+
7478 // Access the scheduler control for this VM.
7579 runtime::vm_scheduler_control_t &get_scheduler_control() const;
7680
@@ -86,7 +90,7 @@ namespace disvm · 9 unmodified lines
8690 class loaded_module_t final
8791 {
8892 public:
89− loaded_module_t(std::unique_ptr<runtime::vm_string_t> origin, std::shared_ptr<const runtime::vm_module_t> module);
93+ loaded_module_t(std::unique_ptr<runtime::vm_string_t> origin, std::shared_ptr<runtime::vm_module_t> module);
9094 loaded_module_t(const loaded_module_t &) = delete;
9195 loaded_module_t &operator=(const loaded_module_t &) = delete;
9296
@@ -97,11 +101,11 @@ namespace disvm · 4 unmodified lines
97101 std::unique_ptr<runtime::vm_string_t> origin;
98102
99103 // Weak reference to a loaded module
100− std::weak_ptr<const runtime::vm_module_t> module;
104+ std::weak_ptr<runtime::vm_module_t> module;
101105 };
102106
103107 using loaded_modules_t = std::forward_list<loaded_module_t>;
104− std::mutex _modules_lock;
108+ mutable std::mutex _modules_lock;
105109 loaded_modules_t _modules;
106110
107111 std::unique_ptr<runtime::vm_scheduler_t> _scheduler;
src/include/opcodes.h+4−1
@@ -190,9 +190,12 @@ namespace disvm · 189 unmodified lines
190190 expf,
191191 self,
192192
193+ // [SPEC] Added opcode for debugging support.
194+ brkpt,
195+
193196 // markers
194197 first_opcode = invalid,
195− last_opcode = self
198+ last_opcode = brkpt
196199 };
197200 }
198201
src/include/runtime.h+37−10
@@ -21,7 +21,7 @@ · 20 unmodified lines
2121
2222 namespace disvm
2323 {
24− // VM forward declation
24+ // Forward declaration
2525 class vm_t;
2626
2727 namespace runtime
@@ -43,7 +43,7 @@ namespace disvm · 15 unmodified lines
4343 // VM memory management
4444 //
4545
46− // VM forward declation
46+ // Forward declaration
4747 class vm_alloc_t;
4848
4949 // Finalizer callback for allocations.
@@ -75,7 +75,7 @@ namespace disvm · 25 unmodified lines
7575 const vm_alloc_instance_finalizer_t finalizer;
7676 };
7777
78− // Access to type descriptors for VM intrinsic types.
78+ // Access to type descriptors for VM intrinsic types
7979 class intrinsic_type_desc final
8080 {
8181 public:
@@ -262,7 +262,7 @@ namespace disvm · 180 unmodified lines
262262 const bool _is_alloc;
263263 };
264264
265− // VM forward declation
265+ // Forward declaration
266266 class vm_channel_t;
267267
268268 // VM request mutex
@@ -299,10 +299,12 @@ namespace disvm · 30 unmodified lines
299299 public: //static
300300 static std::shared_ptr<const type_descriptor_t> type_desc();
301301
302+ using data_transfer_func_t = void(*)(pointer_t, pointer_t, const type_descriptor_t *);
303+
302304 public:
303305 vm_channel_t(
304306 std::shared_ptr<const type_descriptor_t> td,
305− std::function<void(pointer_t, pointer_t, const type_descriptor_t *)> transfer,
307+ data_transfer_func_t transfer,
306308 word_t buffer_len);
307309 ~vm_channel_t();
308310
@@ -331,7 +333,7 @@ namespace disvm · 22 unmodified lines
331333 private:
332334 std::mutex _data_lock;
333335 std::shared_ptr<const type_descriptor_t> _data_type;
334− std::function<void(pointer_t, pointer_t, const type_descriptor_t *)> _data_transfer;
336+ data_transfer_func_t _data_transfer;
335337
336338 vm_array_t *_data_buffer;
337339 uint32_t _data_buffer_head; // Next element to remove from buffer
@@ -406,7 +408,7 @@ namespace disvm · 68 unmodified lines
406408 using src_data_t = inst_data_generic_t;
407409 using dest_data_t = inst_data_generic_t;
408410
409− // VM forward declation
411+ // Forward declaration
410412 class vm_registers_t;
411413
412414 // Instruction execution operation
@@ -710,6 +712,7 @@ namespace disvm · 297 unmodified lines
710712
711713 // Forward declaration
712714 class vm_thread_t;
715+ class vm_tool_dispatch_t;
713716
714717 // VM registers
715718 class vm_registers_t
@@ -721,6 +724,7 @@ namespace disvm · 5 unmodified lines
721724 ~vm_registers_t();
722725
723726 const vm_thread_t &thread;
727+ std::atomic<vm_tool_dispatch_t *> tool_dispatch;
724728 vm_stack_t stack; // Frame pointer access (FP)
725729 vm_pc_t pc; // Program counter (Index into module code section)
726730 vm_alloc_t *mp_base; // Module data base pointer (MP)
@@ -752,13 +756,17 @@ namespace disvm · 25 unmodified lines
752756 uint32_t parent_thread_id);
753757 vm_thread_t(
754758 vm_module_ref_t &entry,
759+ uint32_t parent_thread_id,
755760 const vm_frame_t &initial_frame,
756− vm_pc_t start_pc,
757− uint32_t parent_thread_id);
761+ vm_pc_t start_pc);
758762
759763 ~vm_thread_t();
760764
761− vm_thread_state_t execute(vm_t &virtual_machine, const uint32_t quanta);
765+ vm_thread_state_t execute(vm_t &vm, const uint32_t quanta);
766+
767+ // There is no way to get the current dispatch value.
768+ // Setting the dispatch to 'null' detaches all tool related behavior
769+ void set_tool_dispatch(vm_tool_dispatch_t *dispatch);
762770
763771 vm_thread_state_t get_state() const;
764772
@@ -834,6 +842,25 @@ namespace disvm · 69 unmodified lines
834842
835843 // Create a garbage collector that does nothing.
836844 std::unique_ptr<vm_garbage_collector_t> create_no_op_gc(vm_t &);
845+
846+ // Forward declaration
847+ class vm_tool_controller_t;
848+
849+ // VM tool interface
850+ // Used to implement tools (e.g. debugger or profiler)
851+ class vm_tool_t
852+ {
853+ public:
854+ virtual ~vm_tool_t() = 0;
855+
856+ // Called when the tool is loaded into the VM
857+ virtual void on_load(vm_t &vm, vm_tool_controller_t &controller, std::size_t tool_id) = 0;
858+
859+ // Called when the tool is unloaded from the VM.
860+ // Note once the tool has been unloaded the vm and controller references supplied during load
861+ // should not be used as they may no longer be valid.
862+ virtual void on_unload() = 0;
863+ };
837864 }
838865 }
839866
src/include/utf8.h+2−2
@@ -33,7 +33,7 @@ namespace disvm · 32 unmodified lines
3333 // See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
3434 std::size_t count_codepoints(const uint8_t *str, const std::size_t max_len);
3535
36− enum class decode_state
36+ enum class decode_state_t
3737 {
3838 accept = 0,
3939 reject = 12
@@ -41,7 +41,7 @@ namespace disvm · 1 unmodified line
4141
4242 // Decode a byte at a time to form a codepoint.
4343 // See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
44− decode_state decode_step(decode_state &state, rune_t &codepoint, uint8_t byte);
44+ decode_state_t decode_step(decode_state_t &state, rune_t &codepoint, uint8_t byte);
4545
4646 // Decode the next codepoint in the supplied string.
4747 // Returns number of characters in string consumed.
src/include/vm_tools.h+63−0
@@ -0,0 +1,63 @@
1+//
2+// Dis VM
3+// File: vm_tools.h
4+// Author: arr
5+//
6+
7+#ifndef _DISVM_SRC_INCLUDE_VM_TOOLS_H_
8+#define _DISVM_SRC_INCLUDE_VM_TOOLS_H_
9+
10+#include <cstdint>
11+#include <vector>
12+#include <memory>
13+#include <functional>
14+#include "runtime.h"
15+
16+namespace disvm
17+{
18+ namespace runtime
19+ {
20+ union vm_context_value_t
21+ {
22+ std::size_t i;
23+ vm_t *vm;
24+ vm_module_t *module;
25+ vm_registers_t *registers;
26+ vm_alloc_t *alloc;
27+ };
28+
29+ struct vm_event_context_t
30+ {
31+ vm_context_value_t value1;
32+ vm_context_value_t value2;
33+ };
34+
35+ enum class vm_event_t
36+ {
37+ module_load, // value1: vm_module_t
38+ module_unload, // value1: vm_module_t
39+ thread_begin, // value1: vm_registers_t
40+ thread_end, // value1: vm_registers_t
41+ exception_raised, // value1: vm_registers_t value2: vm_alloc_t
42+ exception_unhandled, // value1: vm_registers_t value2: vm_alloc_t
43+ breakpoint, // value1: vm_registers_t value2: std::size_t
44+ };
45+
46+ using vm_event_callback_t = std::function<void(vm_event_t, vm_event_context_t &)>;
47+
48+ // VM tool controller
49+ class vm_tool_controller_t
50+ {
51+ public:
52+ // Call the supplied callback when the specified event occurs
53+ virtual std::size_t subscribe_event(vm_event_t, vm_event_callback_t) = 0;
54+ virtual void unsubscribe_event(std::size_t) = 0;
55+
56+ // Set a breakpoint in the supplied module at the specified PC location
57+ virtual std::size_t set_breakpoint(std::shared_ptr<const vm_module_t>, vm_pc_t) = 0;
58+ virtual void clear_breakpoint(std::size_t) = 0;
59+ };
60+ }
61+}
62+
63+#endif // _DISVM_SRC_INCLUDE_VM_TOOLS_H_
src/vm/channel.cpp+1−1
@@ -29,7 +29,7 @@ std::shared_ptr<const type_descriptor_t> vm_channel_t::type_desc() · 28 unmodified lines
2929
3030 vm_channel_t::vm_channel_t(
3131 std::shared_ptr<const type_descriptor_t> td,
32− std::function<void(pointer_t, pointer_t, const type_descriptor_t *)> transfer,
32+ data_transfer_func_t transfer,
3333 word_t buffer_len)
3434 : vm_alloc_t(vm_channel_t::type_desc())
3535 , _data_buffer{ nullptr }
src/vm/disvm.gyp+1−0
@@ -17,6 +17,7 @@ · 16 unmodified lines
1717 'stack.cpp',
1818 'string.cpp',
1919 'thread.cpp',
20+ 'tool_dispatch.cpp',
2021 'utf8.cpp',
2122 'vm.cpp',
2223 'vm_memory.cpp',
src/vm/execution_table.cpp+26−5
@@ -18,6 +18,7 @@ · 17 unmodified lines
1818 #include <exceptions.h>
1919 #include <debug.h>
2020 #include <builtin_module.h>
21+#include "tool_dispatch.h"
2122 #include "execution_table.h"
2223
2324 using namespace disvm;
@@ -186,7 +187,7 @@ namespace · 162 unmodified lines
186187 auto buffer = std::array<char, sizeof("�2147483648")>{};
187188
188189 const auto w = vt_ref<word_t>(r.src);
189− const auto len = ::sprintf(buffer.data(), "%d", w);
190+ const auto len = std::sprintf(buffer.data(), "%d", w);
190191 assert(static_cast<std::size_t>(len) <= buffer.size());
191192
192193 auto new_string = new vm_string_t(len, reinterpret_cast<uint8_t *>(buffer.data()));
@@ -212,7 +213,7 @@ namespace · 19 unmodified lines
212213 auto buffer = std::array<char, sizeof("-2.2250738585072014e-308")>{};
213214
214215 const auto real = vt_ref<real_t>(r.src);
215− const auto len = ::sprintf(buffer.data(), "%g", real);
216+ const auto len = std::sprintf(buffer.data(), "%g", real);
216217 assert(static_cast<std::size_t>(len) <= buffer.size());
217218
218219 auto new_string = new vm_string_t(len, reinterpret_cast<uint8_t *>(buffer.data()));
@@ -238,7 +239,7 @@ namespace · 19 unmodified lines
238239 auto buffer = std::array<char, sizeof("-9223372036854775808")>{};
239240
240241 const auto b = vt_ref<big_t>(r.src);
241− const auto len = ::sprintf(buffer.data(), "%lld", b);
242+ const auto len = std::sprintf(buffer.data(), "%lld", b);
242243 assert(static_cast<std::size_t>(len) <= buffer.size());
243244
244245 auto new_string = new vm_string_t(len, reinterpret_cast<uint8_t *>(buffer.data()));
@@ -643,6 +644,7 @@ namespace · 398 unmodified lines
643644 break;
644645 }
645646
647+ assert(0 <= static_cast<std::size_t>(target_pc) && static_cast<std::size_t>(target_pc) < r.module_ref->code_section.size());
646648 r.pc = target_pc;
647649 }
648650
@@ -1036,7 +1038,7 @@ namespace · 387 unmodified lines
10361038 vm_registers_t &r,
10371039 const vm_t &vm,
10381040 std::shared_ptr<const type_descriptor_t> type_desc,
1039− std::function<void(pointer_t, pointer_t, const type_descriptor_t *)> transfer)
1041+ vm_channel_t::data_transfer_func_t transfer)
10401042 {
10411043 auto buffer_len = word_t{ 0 };
10421044 if (r.mid != r.dest)
@@ -1066,7 +1068,7 @@ namespace · 23 unmodified lines
10661068 {
10671069 auto memory_size = vt_ref<word_t>(r.src);
10681070 auto channel_data_type = type_descriptor_t::create(memory_size);
1069−
1071+
10701072 _newc_(r, vm, channel_data_type, _channel_movm);
10711073 }
10721074
@@ -1669,6 +1671,24 @@ namespace · 596 unmodified lines
16691671 r.pc = handler_pc;
16701672 }
16711673
1674+ // [SPEC] Added to support debugger.
1675+ // The actual opcode for execution can be retrieved from the tool dispatch interface.
1676+ // The current register state is for the real opcode, the breakpoint opcode has no inputs/outputs.
1677+ EXEC_DECL(brkpt)
1678+ {
1679+ auto tool_dispatch = r.tool_dispatch.load();
1680+ if (tool_dispatch == nullptr)
1681+ {
1682+ assert(false && "Breakpoint set but no tool dispatcher found" );
1683+ throw vm_system_exception{ "Breakpoint set but no tool dispatcher found" };
1684+ }
1685+
1686+ const auto original_op = tool_dispatch->on_breakpoint(r, vm);
1687+
1688+ // Continue execution
1689+ vm_exec_table[static_cast<std::size_t>(original_op)](r, vm);
1690+ }
1691+
16721692 //
16731693 // Module operations
16741694 //
@@ -1895,4 +1915,5 @@ const vm_exec_t disvm::runtime::vm_exec_table[static_cast<std::size_t>(opcode_t: · 220 unmodified lines
18951915 expl,
18961916 expf,
18971917 notimpl, // self,
1918+ brkpt,
18981919 };
src/vm/scheduler.cpp+10−3
@@ -5,6 +5,7 @@ · 4 unmodified lines
55 //
66
77 #include <debug.h>
8+#include <iostream>
89 #include <sstream>
910 #include <exceptions.h>
1011 #include "scheduler.h"
@@ -52,8 +53,7 @@ void default_scheduler_t::worker_main(default_scheduler_t &instance) · 41 unmodified lines
5253 if (current_thread != nullptr)
5354 {
5455 const auto &r = current_thread->vm_thread->get_registers();
55− walk_stack(r, [&err_msg](const pointer_t, const vm_pc_t pc, const vm_module_ref_t &module_ref)
56− {
56+ walk_stack(r, [&err_msg](const pointer_t, const vm_pc_t pc, const vm_module_ref_t &module_ref) {
5757 auto module_name = "<No Name>";
5858 if (module_ref.module->module_name != nullptr)
5959 module_name = module_ref.module->module_name->str();
@@ -63,7 +63,9 @@ void default_scheduler_t::worker_main(default_scheduler_t &instance) · 3 unmodified lines
6363 });
6464 }
6565
66− std::fprintf(stderr, "%s\n", err_msg.str().c_str());
66+ auto err_str = err_msg.str();
67+ std::cerr << err_str.c_str() << std::endl;
68+
6769 instance._terminating = true;
6870 instance._worker_event.notify_all();
6971 }
@@ -78,6 +80,11 @@ default_scheduler_t::default_scheduler_t(vm_t &vm, uint32_t system_thread_count, · 8 unmodified lines
7880 , _vm{ vm }
7981 , _vm_thread_quanta{ thread_quanta }
8082 {
83+ if (_worker_thread_count == 0)
84+ throw vm_system_exception{ "Work thread count must be > 0" };
85+
86+ if (_vm_thread_quanta == 0)
87+ throw vm_system_exception{ "Work thread quanta must be > 0" };
8188 }
8289
8390 default_scheduler_t::~default_scheduler_t()
src/vm/sys/Sysmod.cpp+3−3
@@ -292,21 +292,21 @@ Sys_byte2char(vm_registers_t &r, vm_t &vm) · 291 unmodified lines
292292 fp.ret->t1 = 0;
293293 fp.ret->t2 = 0;
294294
295− auto state = utf8::decode_state::accept;
295+ auto state = utf8::decode_state_t::accept;
296296 auto result = rune_t{};
297297 const auto buffer_len = buffer->get_length();
298298 for (auto i = n; i < buffer_len; ++i)
299299 {
300300 auto b = buffer->at<byte_t>(i);
301301 state = utf8::decode_step(state, result, b);
302− if (state == utf8::decode_state::accept)
302+ if (state == utf8::decode_state_t::accept)
303303 {
304304 fp.ret->t0 = result;
305305 fp.ret->t1 = (i - n) + 1; // Number of bytes consumed
306306 fp.ret->t2 = 1;
307307 return;
308308 }
309− else if (state == utf8::decode_state::reject)
309+ else if (state == utf8::decode_state_t::reject)
310310 {
311311 fp.ret->t1 = 1;
312312 return;
src/vm/thread.cpp+18−7
@@ -133,6 +133,7 @@ vm_registers_t::vm_registers_t( · 132 unmodified lines
133133 , thread{ thread }
134134 , pc{ entry.module->header.entry_pc }
135135 , stack{ entry.module->header.stack_extent }
136+ , tool_dispatch{ nullptr }
136137 , src{ nullptr }
137138 , mid{ nullptr }
138139 , dest{ nullptr }
@@ -203,9 +204,9 @@ vm_thread_t::vm_thread_t( · 64 unmodified lines
203204
204205 vm_thread_t::vm_thread_t(
205206 vm_module_ref_t &entry,
207+ uint32_t parent_thread_id,
206208 const vm_frame_t &initial_frame,
207− vm_pc_t start_pc,
208− uint32_t parent_thread_id)
209+ vm_pc_t start_pc)
209210 : vm_alloc_t(vm_thread_t::type_desc())
210211 , _parent_thread_id{ parent_thread_id }
211212 , _registers{ *this, entry }
@@ -236,7 +237,7 @@ vm_thread_t::~vm_thread_t() · 24 unmodified lines
236237 debug::log_msg(debug::component_trace_t::thread, debug::log_level_t::debug, "destroy: vm thread: %d %d\n", _thread_id, _parent_thread_id);
237238 }
238239
239−vm_thread_state_t vm_thread_t::execute(vm_t &virtual_machine, const uint32_t quanta)
240+vm_thread_state_t vm_thread_t::execute(vm_t &vm, const uint32_t quanta)
240241 {
241242 _registers.current_thread_state = vm_thread_state_t::running;
242243 assert(_registers.pc != runtime_constants::invalid_program_counter);
@@ -249,7 +250,9 @@ vm_thread_state_t vm_thread_t::execute(vm_t &virtual_machine, const uint32_t qua · 6 unmodified lines
249250 {
250251 if (_registers.stack.peek_frame() == nullptr)
251252 {
252− debug::log_msg(debug::component_trace_t::thread, debug::log_level_t::debug, "exit: vm thread: stack exhausted: %d\n", _thread_id);
253+ if (debug::is_component_tracing_enabled<debug::component_trace_t::thread>())
254+ debug::log_msg(debug::component_trace_t::thread, debug::log_level_t::debug, "exit: vm thread: stack exhausted: %d\n", _thread_id);
255+
253256 _registers.current_thread_state = vm_thread_state_t::empty_stack;
254257 break;
255258 }
@@ -257,14 +260,16 @@ vm_thread_state_t vm_thread_t::execute(vm_t &virtual_machine, const uint32_t qua · 1 unmodified line
257260 const auto &code_section = _registers.module_ref->code_section;
258261 assert(static_cast<std::size_t>(_registers.pc) < code_section.size());
259262
263+ assert(!_registers.module_ref->is_builtin_module() && "Interpreter thread is unable to execute native instructions");
260264 const auto &inst = code_section[_registers.pc].op;
261− const auto op_code = static_cast<std::size_t>(inst.opcode);
262− if (static_cast<std::size_t>(opcode_t::last_opcode) < op_code)
265+ const auto opcode = static_cast<std::size_t>(inst.opcode);
266+ if (static_cast<std::size_t>(opcode_t::last_opcode) < opcode)
263267 throw vm_system_exception{ "Unknown op-code" };
264268
269+ // Instruction is valid, decode and update registers for operation
265270 decode_address(inst, _registers);
266271 _registers.pc++;
267− vm_exec_table[op_code](_registers, virtual_machine);
272+ vm_exec_table[opcode](_registers, vm);
268273
269274 if (_registers.current_thread_state != vm_thread_state_t::running)
270275 break;
@@ -307,6 +312,12 @@ vm_thread_state_t vm_thread_t::execute(vm_t &virtual_machine, const uint32_t qua · 36 unmodified lines
307312 return _registers.current_thread_state;
308313 }
309314
315+void vm_thread_t::set_tool_dispatch(vm_tool_dispatch_t *dispatch)
316+{
317+ assert((_registers.tool_dispatch.load() == nullptr) != (dispatch == nullptr) && "Dispatch value should toggle between null and non-null");
318+ _registers.tool_dispatch = dispatch;
319+}
320+
310321 vm_thread_state_t vm_thread_t::get_state() const
311322 {
312323 return _registers.current_thread_state;
src/vm/tool_dispatch.cpp+232−0
@@ -0,0 +1,232 @@
1+//
2+// Dis VM
3+// File: tool_dispatch.cpp
4+// Author: arr
5+//
6+
7+#include <cassert>
8+#include <debug.h>
9+#include <vm_tools.h>
10+#include <exceptions.h>
11+#include <unordered_set>
12+#include <utils.h>
13+#include "tool_dispatch.h"
14+
15+using namespace disvm;
16+using namespace disvm::runtime;
17+
18+// Empty destructor for vm tool 'interface'
19+vm_tool_t::~vm_tool_t()
20+{
21+}
22+
23+vm_tool_dispatch_t::vm_tool_dispatch_t(vm_t &vm)
24+ : _vm{ vm }
25+{
26+ _events.cookie_counter = 1;
27+ _breakpoints.cookie_counter = 1;
28+}
29+
30+vm_tool_dispatch_t::~vm_tool_dispatch_t()
31+{
32+ std::lock_guard<std::mutex> lock{ _tools_lock };
33+ for (auto &t : _tools)
34+ t.second->on_unload();
35+}
36+
37+std::size_t vm_tool_dispatch_t::load_tool(std::shared_ptr<runtime::vm_tool_t> tool)
38+{
39+ static std::size_t _tool_id_counter{ 1 };
40+
41+ if (tool == nullptr)
42+ throw vm_system_exception{ "Tool cannot be null" };
43+
44+ // Keep the lock for the collection until the tool has been completed being loaded.
45+ std::lock_guard<std::mutex> lock{ _tools_lock };
46+ const auto current_id = _tool_id_counter++;
47+ _tools[current_id] = tool;
48+
49+ try
50+ {
51+ // [TODO] For debug builds, it might be useful to wrap the controller in a proxy that records
52+ // the subscribed callbacks and asserts they are all properly unsubscribed during agent unload.
53+ tool->on_load(_vm, *this, current_id);
54+ }
55+ catch (...)
56+ {
57+ if (debug::is_component_tracing_enabled<debug::component_trace_t::tool>())
58+ debug::log_msg(debug::component_trace_t::tool, debug::log_level_t::warning, "load: tool: failure");
59+
60+ // The ID is tainted and will not be re-used
61+ _tools.erase(current_id);
62+ throw;
63+ }
64+
65+ if (debug::is_component_tracing_enabled<debug::component_trace_t::tool>())
66+ debug::log_msg(debug::component_trace_t::tool, debug::log_level_t::debug, "load: tool: %d", current_id);
67+
68+ return current_id;
69+}
70+
71+std::size_t vm_tool_dispatch_t::unload_tool(std::size_t tool_id)
72+{
73+ // Keep the lock for the collection until the tool has been completed being unloaded.
74+ std::lock_guard<std::mutex> lock{ _tools_lock };
75+ auto iter = _tools.find(tool_id);
76+ if (iter == _tools.cend())
77+ return _tools.size();
78+
79+ auto tool = iter->second;
80+ _tools.erase(iter);
81+
82+ if (debug::is_component_tracing_enabled<debug::component_trace_t::tool>())
83+ debug::log_msg(debug::component_trace_t::tool, debug::log_level_t::debug, "unload: tool: %d", tool_id);
84+
85+ assert(tool != nullptr);
86+ tool->on_unload();
87+
88+ return _tools.size();
89+}
90+
91+opcode_t vm_tool_dispatch_t::on_breakpoint(vm_registers_t &r, vm_t &vm)
92+{
93+ return get_original_opcode(r.module_ref->module.get(), r.pc - 1);
94+}
95+
96+std::size_t vm_tool_dispatch_t::subscribe_event(vm_event_t evt, vm_event_callback_t cb)
97+{
98+ if (!cb)
99+ throw vm_system_exception{ "Invalid callback for event" };
100+
101+ std::lock_guard<std::mutex> lock{ _events.lock };
102+
103+ const auto cookie_id = _events.cookie_counter++;
104+
105+ // Store the event callback
106+ auto &event_callbacks = _events.callbacks[evt];
107+ event_callbacks[cookie_id] = cb;
108+
109+ // Store the cookie with the associate event
110+ _events.cookie_to_event[cookie_id] = evt;
111+
112+ if (debug::is_component_tracing_enabled<debug::component_trace_t::tool>())
113+ debug::log_msg(debug::component_trace_t::tool, debug::log_level_t::debug, "subscribe: event: %d %d", evt, cookie_id);
114+
115+ return cookie_id;
116+}
117+
118+void vm_tool_dispatch_t::unsubscribe_event(std::size_t cookie_id)
119+{
120+ std::lock_guard<std::mutex> lock{ _events.lock };
121+
122+ auto iter = _events.cookie_to_event.find(cookie_id);
123+ if (iter == _events.cookie_to_event.cend())
124+ return;
125+
126+ // Erase cookie and event
127+ const auto evt = iter->second;
128+ auto &event_callbacks = _events.callbacks[evt];
129+ event_callbacks.erase(cookie_id);
130+ _events.cookie_to_event.erase(iter);
131+
132+ if (debug::is_component_tracing_enabled<debug::component_trace_t::tool>())
133+ debug::log_msg(debug::component_trace_t::tool, debug::log_level_t::debug, "unsubscribe: event: %d %d", evt, cookie_id);
134+}
135+
136+std::size_t vm_tool_dispatch_t::set_breakpoint(std::shared_ptr<const vm_module_t> module, vm_pc_t pc)
137+{
138+ if (module == nullptr || util::has_flag(module->header.runtime_flag, runtime_flags_t::builtin))
139+ throw vm_system_exception{ "Unable to set breakpoint in supplied module" };
140+
141+ // [TODO] This should be re-considered. Removing 'const' is less than ideal.
142+ // Remove the const value since the code section needs to be updated.
143+ auto &code_section = const_cast<code_section_t &>(module->code_section);
144+ if (pc >= code_section.size())
145+ throw vm_system_exception{ "Invalid PC for module" };
146+
147+ const auto real_opcode = code_section[pc].op.opcode;
148+
149+ {
150+ std::lock_guard<std::mutex> lock{ _breakpoints.lock };
151+ if (real_opcode == opcode_t::brkpt)
152+ throw vm_system_exception{ "Breakpoint already set at PC" };
153+
154+ const auto cookie_id = _breakpoints.cookie_counter++;
155+
156+ // Map the cookie to the module/pc pair.
157+ _breakpoints.cookie_to_modulepc[cookie_id] = std::make_pair(module, pc);
158+
159+ // Record the original opcode
160+ auto &pc_map = _breakpoints.original_opcodes[reinterpret_cast<std::uintptr_t>(module.get())];
161+ pc_map[pc] = real_opcode;
162+
163+ // Replace the current opcode with breakpoint
164+ code_section[pc].op.opcode = opcode_t::brkpt;
165+
166+ if (debug::is_component_tracing_enabled<debug::component_trace_t::tool>())
167+ debug::log_msg(debug::component_trace_t::tool, debug::log_level_t::debug, "breakpoint: set: %d %d >>%s<<", cookie_id, pc, module->module_name->str());
168+
169+ return cookie_id;
170+ }
171+}
172+
173+void vm_tool_dispatch_t::clear_breakpoint(std::size_t cookie_id)
174+{
175+ std::lock_guard<std::mutex> lock{ _breakpoints.lock };
176+
177+ // Resolve the cookie to the module/pc pair
178+ auto iter_cookie = _breakpoints.cookie_to_modulepc.find(cookie_id);
179+ if (iter_cookie == _breakpoints.cookie_to_modulepc.cend())
180+ return;
181+
182+ auto mod_pc_pair = iter_cookie->second;
183+ const auto target_pc = mod_pc_pair.second;
184+
185+ // Determine the original opcode
186+ auto iter_orig = _breakpoints.original_opcodes.find(reinterpret_cast<std::uintptr_t>(mod_pc_pair.first.get()));
187+ assert(iter_orig != _breakpoints.original_opcodes.cend());
188+
189+ auto &pc_map = iter_orig->second;
190+ auto iter_pc = pc_map.find(target_pc);
191+ assert(iter_pc != pc_map.cend());
192+
193+ auto original_opcode = iter_pc->second;
194+
195+ // Replace the breakpoint opcode with the original
196+ auto &code_section = const_cast<code_section_t &>(mod_pc_pair.first->code_section);
197+ assert(code_section[target_pc].op.opcode == opcode_t::brkpt);
198+ code_section[target_pc].op.opcode = original_opcode;
199+
200+ if (debug::is_component_tracing_enabled<debug::component_trace_t::tool>())
201+ 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());
202+
203+ // Clean-up the PC mapping to opcode
204+ pc_map.erase(iter_pc);
205+
206+ // If the PC map is empty, erase the module mapping as well
207+ if (pc_map.size() == 0)
208+ _breakpoints.original_opcodes.erase(iter_orig);
209+
210+ // Remove the cookie map
211+ _breakpoints.cookie_to_modulepc.erase(iter_cookie);
212+}
213+
214+opcode_t vm_tool_dispatch_t::get_original_opcode(const vm_module_t *module, vm_pc_t pc)
215+{
216+ if (module == nullptr || util::has_flag(module->header.runtime_flag, runtime_flags_t::builtin))
217+ throw vm_system_exception{ "Unable to determine original opcode in supplied module" };
218+
219+ std::lock_guard<std::mutex> lock{ _breakpoints.lock };
220+
221+ auto iter_orig = _breakpoints.original_opcodes.find(reinterpret_cast<std::uintptr_t>(module));
222+ if (iter_orig != _breakpoints.original_opcodes.cend())
223+ {
224+ auto &pc_map = iter_orig->second;
225+ auto iter_pc = pc_map.find(pc);
226+ if (iter_pc != pc_map.cend())
227+ return iter_pc->second;
228+ }
229+
230+ assert(false && "Why are we missing this information?");
231+ return opcode_t::runt; // Return NOP instruction?
232+}
src/vm/tool_dispatch.h+84−0
@@ -0,0 +1,84 @@
1+//
2+// Dis VM
3+// File: tool_dispatch.h
4+// Author: arr
5+//
6+
7+#ifndef _DISVM_SRC_VM_TOOL_DISPATCH_H_
8+#define _DISVM_SRC_VM_TOOL_DISPATCH_H_
9+
10+#include <cstdint>
11+#include <memory>
12+#include <unordered_map>
13+#include <mutex>
14+#include <runtime.h>
15+#include <vm_tools.h>
16+
17+namespace disvm
18+{
19+ namespace runtime
20+ {
21+ class vm_tool_dispatch_t final : private vm_tool_controller_t
22+ {
23+ public:
24+ vm_tool_dispatch_t(vm_t &vm);
25+
26+ virtual ~vm_tool_dispatch_t();
27+
28+ // Loads a tool for dispatch and returns the tool's ID
29+ std::size_t load_tool(std::shared_ptr<runtime::vm_tool_t> tool);
30+
31+ // Unloads the tool associated with the supplied ID and returns the current tool count
32+ std::size_t unload_tool(std::size_t tool_id);
33+
34+ // Handle a breakpoint event.
35+ // Returns opcode that was replaced by breakpoint opcode.
36+ opcode_t on_breakpoint(vm_registers_t &r, vm_t &vm);
37+
38+ public: // vm_tool_controller_t
39+ std::size_t subscribe_event(vm_event_t evt, vm_event_callback_t cb) override final;
40+ void unsubscribe_event(std::size_t cookie_id) override final;
41+
42+ std::size_t set_breakpoint(std::shared_ptr<const vm_module_t> module, vm_pc_t pc) override final;
43+ void clear_breakpoint(std::size_t cookie_id) override final;
44+
45+ private:
46+ opcode_t get_original_opcode(const vm_module_t *module, vm_pc_t pc);
47+
48+ private:
49+ vm_t &_vm;
50+
51+ using tool_collection_t = std::unordered_map<std::size_t, std::shared_ptr<vm_tool_t>>;
52+ tool_collection_t _tools;
53+ std::mutex _tools_lock;
54+
55+ struct
56+ {
57+ using cookie_to_callback_map_t = std::unordered_map<std::size_t, vm_event_callback_t>;
58+ using event_to_callbacks_map_t = std::unordered_map<vm_event_t, cookie_to_callback_map_t>;
59+ event_to_callbacks_map_t callbacks;
60+
61+ using cookie_to_event_map_t = std::unordered_map<std::size_t, vm_event_t>;
62+ cookie_to_event_map_t cookie_to_event;
63+
64+ std::mutex lock;
65+ std::size_t cookie_counter;
66+ } _events;
67+
68+ struct
69+ {
70+ using cookie_to_modulepc_t = std::unordered_map<vm_pc_t, std::pair<std::shared_ptr<const vm_module_t>, vm_pc_t>>;
71+ cookie_to_modulepc_t cookie_to_modulepc;
72+
73+ using pc_to_opcode_map_t = std::unordered_map<vm_pc_t, opcode_t>;
74+ using module_to_pc_map_t = std::unordered_map<std::uintptr_t, pc_to_opcode_map_t>;
75+ module_to_pc_map_t original_opcodes;
76+
77+ std::mutex lock;
78+ std::size_t cookie_counter;
79+ } _breakpoints;
80+ };
81+ }
82+}
83+
84+#endif // _DISVM_SRC_VM_TOOL_DISPATCH_H_
src/vm/utf8.cpp+8−8
@@ -40,15 +40,15 @@ namespace · 39 unmodified lines
4040 }
4141
4242 // See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
43−utf8::decode_state disvm::runtime::utf8::decode_step(utf8::decode_state &state, rune_t &codepoint, uint8_t byte)
43+utf8::decode_state_t disvm::runtime::utf8::decode_step(utf8::decode_state_t &state, rune_t &codepoint, uint8_t byte)
4444 {
4545 auto type = uint32_t{ utf8d[byte] };
4646
47− codepoint = (state != utf8::decode_state::accept) ?
47+ codepoint = (state != utf8::decode_state_t::accept) ?
4848 (byte & 0x3fu) | (codepoint << 6) :
4949 (0xff >> type) & (byte);
5050
51− state = static_cast<utf8::decode_state>(utf8d[256 + static_cast<uint32_t>(state) + type]);
51+ state = static_cast<utf8::decode_state_t>(utf8d[256 + static_cast<uint32_t>(state) + type]);
5252 return state;
5353 }
5454
@@ -59,15 +59,15 @@ std::size_t disvm::runtime::utf8::count_codepoints(const uint8_t *str, const std · 4 unmodified lines
5959 assert(str != nullptr);
6060 auto single_character = rune_t{};
6161 auto points = std::size_t{ 0 };
62− auto state = utf8::decode_state::accept;
62+ auto state = utf8::decode_state_t::accept;
6363
6464 for (auto i = std::size_t{ 0 }; i < max_len; ++i)
6565 {
66− if (utf8::decode_state::accept == utf8::decode_step(state, single_character, str[i]))
66+ if (utf8::decode_state_t::accept == utf8::decode_step(state, single_character, str[i]))
6767 points++;
6868 }
6969
70− if (state != utf8::decode_state::accept)
70+ if (state != utf8::decode_state_t::accept)
7171 throw invalid_utf8{};
7272
7373 return points;
@@ -76,7 +76,7 @@ std::size_t disvm::runtime::utf8::count_codepoints(const uint8_t *str, const std · 2 unmodified lines
7676 std::size_t disvm::runtime::utf8::decode(const uint8_t *str, rune_t &codepoint)
7777 {
7878 assert(str != nullptr);
79− auto state = utf8::decode_state::accept;
79+ auto state = utf8::decode_state_t::accept;
8080
8181 codepoint = 0;
8282 auto begin = str;
@@ -86,7 +86,7 @@ std::size_t disvm::runtime::utf8::decode(const uint8_t *str, rune_t &codepoint) · 3 unmodified lines
8686 {
8787 curr++;
8888 state = utf8::decode_step(state, codepoint, c);
89− if (state == utf8::decode_state::accept)
89+ if (state == utf8::decode_state_t::accept)
9090 return (curr - begin);
9191 }
9292
src/vm/vm.cpp+17−2
@@ -19,7 +19,7 @@ · 18 unmodified lines
1919 using namespace disvm;
2020 using namespace disvm::runtime;
2121
22−vm_t::loaded_module_t::loaded_module_t(std::unique_ptr<runtime::vm_string_t> origin, std::shared_ptr<const runtime::vm_module_t> module)
22+vm_t::loaded_module_t::loaded_module_t(std::unique_ptr<runtime::vm_string_t> origin, std::shared_ptr<runtime::vm_module_t> module)
2323 : origin{ std::move(origin) }
2424 , module{ module }
2525 {
@@ -126,7 +126,7 @@ const runtime::vm_thread_t& vm_t::fork( · 100 unmodified lines
126126 if (initial_pc < 0 || static_cast<vm_pc_t>(module_ref.code_section.size()) <= initial_pc)
127127 throw vm_system_exception{ "Invalid entry program counter" };
128128
129− auto thread = std::make_unique<vm_thread_t>(module_ref, initial_frame, initial_pc, parent.get_thread_id());
129+ auto thread = std::make_unique<vm_thread_t>(module_ref, parent.get_thread_id(), initial_frame, initial_pc);
130130 return _scheduler->schedule_thread(std::move(thread));
131131 }
132132
@@ -179,6 +179,21 @@ std::shared_ptr<const runtime::vm_module_t> vm_t::load_module(const char *path) · 46 unmodified lines
179179 return new_module;
180180 }
181181
182+std::vector<std::shared_ptr<const runtime::vm_module_t>> vm_t::get_loaded_modules() const
183+{
184+ auto result = std::vector<std::shared_ptr<const runtime::vm_module_t>>{};
185+
186+ std::lock_guard<std::mutex> lock{ _modules_lock };
187+ for (auto &m : _modules)
188+ {
189+ auto loaded_module_maybe = m.module.lock();
190+ if (loaded_module_maybe != nullptr)
191+ result.push_back(loaded_module_maybe);
192+ }
193+
194+ return result;
195+}
196+
182197 runtime::vm_scheduler_control_t &vm_t::get_scheduler_control() const
183198 {
184199 return _scheduler->get_controller();