Unified · Split
| @@ -24,6 +24,7 @@ namespace disvm · 23 unmodified lines | |||
| 24 | stack, | 24 | stack, |
| 25 | channel, | 25 | channel, |
| 26 | exception, | 26 | exception, |
| 27 | tool, | ||
| 27 | memory, // Noisy component | 28 | memory, // Noisy component |
| 28 | addressing, // Noisy component [DEBUG build only] | 29 | addressing, // Noisy component [DEBUG build only] |
| 29 | 30 | ||
| @@ -71,6 +71,10 @@ namespace disvm · 70 unmodified lines | |||
| 71 | // Load the module at the supplied path into the virtual machine | 71 | // Load the module at the supplied path into the virtual machine |
| 72 | std::shared_ptr<const runtime::vm_module_t> load_module(const char *path); | 72 | std::shared_ptr<const runtime::vm_module_t> load_module(const char *path); |
| 73 | 73 | ||
| 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 | |||
| 74 | // Access the scheduler control for this VM. | 78 | // Access the scheduler control for this VM. |
| 75 | runtime::vm_scheduler_control_t &get_scheduler_control() const; | 79 | runtime::vm_scheduler_control_t &get_scheduler_control() const; |
| 76 | 80 | ||
| @@ -86,7 +90,7 @@ namespace disvm · 9 unmodified lines | |||
| 86 | class loaded_module_t final | 90 | class loaded_module_t final |
| 87 | { | 91 | { |
| 88 | public: | 92 | 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); |
| 90 | loaded_module_t(const loaded_module_t &) = delete; | 94 | loaded_module_t(const loaded_module_t &) = delete; |
| 91 | loaded_module_t &operator=(const loaded_module_t &) = delete; | 95 | loaded_module_t &operator=(const loaded_module_t &) = delete; |
| 92 | 96 | ||
| @@ -97,11 +101,11 @@ namespace disvm · 4 unmodified lines | |||
| 97 | std::unique_ptr<runtime::vm_string_t> origin; | 101 | std::unique_ptr<runtime::vm_string_t> origin; |
| 98 | 102 | ||
| 99 | // Weak reference to a loaded module | 103 | // 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; |
| 101 | }; | 105 | }; |
| 102 | 106 | ||
| 103 | using loaded_modules_t = std::forward_list<loaded_module_t>; | 107 | using loaded_modules_t = std::forward_list<loaded_module_t>; |
| 104 | std::mutex _modules_lock; | 108 | mutable std::mutex _modules_lock; |
| 105 | loaded_modules_t _modules; | 109 | loaded_modules_t _modules; |
| 106 | 110 | ||
| 107 | std::unique_ptr<runtime::vm_scheduler_t> _scheduler; | 111 | std::unique_ptr<runtime::vm_scheduler_t> _scheduler; |
| @@ -190,9 +190,12 @@ namespace disvm · 189 unmodified lines | |||
| 190 | expf, | 190 | expf, |
| 191 | self, | 191 | self, |
| 192 | 192 | ||
| 193 | // [SPEC] Added opcode for debugging support. | ||
| 194 | brkpt, | ||
| 195 | |||
| 193 | // markers | 196 | // markers |
| 194 | first_opcode = invalid, | 197 | first_opcode = invalid, |
| 195 | last_opcode = self | 198 | last_opcode = brkpt |
| 196 | }; | 199 | }; |
| 197 | } | 200 | } |
| 198 | 201 | ||
| @@ -21,7 +21,7 @@ · 20 unmodified lines | |||
| 21 | 21 | ||
| 22 | namespace disvm | 22 | namespace disvm |
| 23 | { | 23 | { |
| 24 | // VM forward declation | 24 | // Forward declaration |
| 25 | class vm_t; | 25 | class vm_t; |
| 26 | 26 | ||
| 27 | namespace runtime | 27 | namespace runtime |
| @@ -43,7 +43,7 @@ namespace disvm · 15 unmodified lines | |||
| 43 | // VM memory management | 43 | // VM memory management |
| 44 | // | 44 | // |
| 45 | 45 | ||
| 46 | // VM forward declation | 46 | // Forward declaration |
| 47 | class vm_alloc_t; | 47 | class vm_alloc_t; |
| 48 | 48 | ||
| 49 | // Finalizer callback for allocations. | 49 | // Finalizer callback for allocations. |
| @@ -75,7 +75,7 @@ namespace disvm · 25 unmodified lines | |||
| 75 | const vm_alloc_instance_finalizer_t finalizer; | 75 | const vm_alloc_instance_finalizer_t finalizer; |
| 76 | }; | 76 | }; |
| 77 | 77 | ||
| 78 | // Access to type descriptors for VM intrinsic types. | 78 | // Access to type descriptors for VM intrinsic types |
| 79 | class intrinsic_type_desc final | 79 | class intrinsic_type_desc final |
| 80 | { | 80 | { |
| 81 | public: | 81 | public: |
| @@ -262,7 +262,7 @@ namespace disvm · 180 unmodified lines | |||
| 262 | const bool _is_alloc; | 262 | const bool _is_alloc; |
| 263 | }; | 263 | }; |
| 264 | 264 | ||
| 265 | // VM forward declation | 265 | // Forward declaration |
| 266 | class vm_channel_t; | 266 | class vm_channel_t; |
| 267 | 267 | ||
| 268 | // VM request mutex | 268 | // VM request mutex |
| @@ -299,10 +299,12 @@ namespace disvm · 30 unmodified lines | |||
| 299 | public: //static | 299 | public: //static |
| 300 | static std::shared_ptr<const type_descriptor_t> type_desc(); | 300 | static std::shared_ptr<const type_descriptor_t> type_desc(); |
| 301 | 301 | ||
| 302 | using data_transfer_func_t = void(*)(pointer_t, pointer_t, const type_descriptor_t *); | ||
| 303 | |||
| 302 | public: | 304 | public: |
| 303 | vm_channel_t( | 305 | vm_channel_t( |
| 304 | std::shared_ptr<const type_descriptor_t> td, | 306 | 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, |
| 306 | word_t buffer_len); | 308 | word_t buffer_len); |
| 307 | ~vm_channel_t(); | 309 | ~vm_channel_t(); |
| 308 | 310 | ||
| @@ -331,7 +333,7 @@ namespace disvm · 22 unmodified lines | |||
| 331 | private: | 333 | private: |
| 332 | std::mutex _data_lock; | 334 | std::mutex _data_lock; |
| 333 | std::shared_ptr<const type_descriptor_t> _data_type; | 335 | 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; |
| 335 | 337 | ||
| 336 | vm_array_t *_data_buffer; | 338 | vm_array_t *_data_buffer; |
| 337 | uint32_t _data_buffer_head; // Next element to remove from buffer | 339 | uint32_t _data_buffer_head; // Next element to remove from buffer |
| @@ -406,7 +408,7 @@ namespace disvm · 68 unmodified lines | |||
| 406 | using src_data_t = inst_data_generic_t; | 408 | using src_data_t = inst_data_generic_t; |
| 407 | using dest_data_t = inst_data_generic_t; | 409 | using dest_data_t = inst_data_generic_t; |
| 408 | 410 | ||
| 409 | // VM forward declation | 411 | // Forward declaration |
| 410 | class vm_registers_t; | 412 | class vm_registers_t; |
| 411 | 413 | ||
| 412 | // Instruction execution operation | 414 | // Instruction execution operation |
| @@ -710,6 +712,7 @@ namespace disvm · 297 unmodified lines | |||
| 710 | 712 | ||
| 711 | // Forward declaration | 713 | // Forward declaration |
| 712 | class vm_thread_t; | 714 | class vm_thread_t; |
| 715 | class vm_tool_dispatch_t; | ||
| 713 | 716 | ||
| 714 | // VM registers | 717 | // VM registers |
| 715 | class vm_registers_t | 718 | class vm_registers_t |
| @@ -721,6 +724,7 @@ namespace disvm · 5 unmodified lines | |||
| 721 | ~vm_registers_t(); | 724 | ~vm_registers_t(); |
| 722 | 725 | ||
| 723 | const vm_thread_t &thread; | 726 | const vm_thread_t &thread; |
| 727 | std::atomic<vm_tool_dispatch_t *> tool_dispatch; | ||
| 724 | vm_stack_t stack; // Frame pointer access (FP) | 728 | vm_stack_t stack; // Frame pointer access (FP) |
| 725 | vm_pc_t pc; // Program counter (Index into module code section) | 729 | vm_pc_t pc; // Program counter (Index into module code section) |
| 726 | vm_alloc_t *mp_base; // Module data base pointer (MP) | 730 | vm_alloc_t *mp_base; // Module data base pointer (MP) |
| @@ -752,13 +756,17 @@ namespace disvm · 25 unmodified lines | |||
| 752 | uint32_t parent_thread_id); | 756 | uint32_t parent_thread_id); |
| 753 | vm_thread_t( | 757 | vm_thread_t( |
| 754 | vm_module_ref_t &entry, | 758 | vm_module_ref_t &entry, |
| 759 | uint32_t parent_thread_id, | ||
| 755 | const vm_frame_t &initial_frame, | 760 | const vm_frame_t &initial_frame, |
| 756 | vm_pc_t start_pc, | 761 | vm_pc_t start_pc); |
| 757 | uint32_t parent_thread_id); | ||
| 758 | 762 | ||
| 759 | ~vm_thread_t(); | 763 | ~vm_thread_t(); |
| 760 | 764 | ||
| 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); | ||
| 762 | 770 | ||
| 763 | vm_thread_state_t get_state() const; | 771 | vm_thread_state_t get_state() const; |
| 764 | 772 | ||
| @@ -834,6 +842,25 @@ namespace disvm · 69 unmodified lines | |||
| 834 | 842 | ||
| 835 | // Create a garbage collector that does nothing. | 843 | // Create a garbage collector that does nothing. |
| 836 | std::unique_ptr<vm_garbage_collector_t> create_no_op_gc(vm_t &); | 844 | 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 | }; | ||
| 837 | } | 864 | } |
| 838 | } | 865 | } |
| 839 | 866 | ||
| @@ -33,7 +33,7 @@ namespace disvm · 32 unmodified lines | |||
| 33 | // See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. | 33 | // See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. |
| 34 | std::size_t count_codepoints(const uint8_t *str, const std::size_t max_len); | 34 | std::size_t count_codepoints(const uint8_t *str, const std::size_t max_len); |
| 35 | 35 | ||
| 36 | enum class decode_state | 36 | enum class decode_state_t |
| 37 | { | 37 | { |
| 38 | accept = 0, | 38 | accept = 0, |
| 39 | reject = 12 | 39 | reject = 12 |
| @@ -41,7 +41,7 @@ namespace disvm · 1 unmodified line | |||
| 41 | 41 | ||
| 42 | // Decode a byte at a time to form a codepoint. | 42 | // Decode a byte at a time to form a codepoint. |
| 43 | // See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. | 43 | // 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); |
| 45 | 45 | ||
| 46 | // Decode the next codepoint in the supplied string. | 46 | // Decode the next codepoint in the supplied string. |
| 47 | // Returns number of characters in string consumed. | 47 | // Returns number of characters in string consumed. |
| @@ -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_ | ||
| @@ -29,7 +29,7 @@ std::shared_ptr<const type_descriptor_t> vm_channel_t::type_desc() · 28 unmodified lines | |||
| 29 | 29 | ||
| 30 | vm_channel_t::vm_channel_t( | 30 | vm_channel_t::vm_channel_t( |
| 31 | std::shared_ptr<const type_descriptor_t> td, | 31 | 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, |
| 33 | word_t buffer_len) | 33 | word_t buffer_len) |
| 34 | : vm_alloc_t(vm_channel_t::type_desc()) | 34 | : vm_alloc_t(vm_channel_t::type_desc()) |
| 35 | , _data_buffer{ nullptr } | 35 | , _data_buffer{ nullptr } |
| @@ -17,6 +17,7 @@ · 16 unmodified lines | |||
| 17 | 'stack.cpp', | 17 | 'stack.cpp', |
| 18 | 'string.cpp', | 18 | 'string.cpp', |
| 19 | 'thread.cpp', | 19 | 'thread.cpp', |
| 20 | 'tool_dispatch.cpp', | ||
| 20 | 'utf8.cpp', | 21 | 'utf8.cpp', |
| 21 | 'vm.cpp', | 22 | 'vm.cpp', |
| 22 | 'vm_memory.cpp', | 23 | 'vm_memory.cpp', |
| @@ -18,6 +18,7 @@ · 17 unmodified lines | |||
| 18 | #include <exceptions.h> | 18 | #include <exceptions.h> |
| 19 | #include <debug.h> | 19 | #include <debug.h> |
| 20 | #include <builtin_module.h> | 20 | #include <builtin_module.h> |
| 21 | #include "tool_dispatch.h" | ||
| 21 | #include "execution_table.h" | 22 | #include "execution_table.h" |
| 22 | 23 | ||
| 23 | using namespace disvm; | 24 | using namespace disvm; |
| @@ -186,7 +187,7 @@ namespace · 162 unmodified lines | |||
| 186 | auto buffer = std::array<char, sizeof("�2147483648")>{}; | 187 | auto buffer = std::array<char, sizeof("�2147483648")>{}; |
| 187 | 188 | ||
| 188 | const auto w = vt_ref<word_t>(r.src); | 189 | 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); |
| 190 | assert(static_cast<std::size_t>(len) <= buffer.size()); | 191 | assert(static_cast<std::size_t>(len) <= buffer.size()); |
| 191 | 192 | ||
| 192 | auto new_string = new vm_string_t(len, reinterpret_cast<uint8_t *>(buffer.data())); | 193 | auto new_string = new vm_string_t(len, reinterpret_cast<uint8_t *>(buffer.data())); |
| @@ -212,7 +213,7 @@ namespace · 19 unmodified lines | |||
| 212 | auto buffer = std::array<char, sizeof("-2.2250738585072014e-308")>{}; | 213 | auto buffer = std::array<char, sizeof("-2.2250738585072014e-308")>{}; |
| 213 | 214 | ||
| 214 | const auto real = vt_ref<real_t>(r.src); | 215 | 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); |
| 216 | assert(static_cast<std::size_t>(len) <= buffer.size()); | 217 | assert(static_cast<std::size_t>(len) <= buffer.size()); |
| 217 | 218 | ||
| 218 | auto new_string = new vm_string_t(len, reinterpret_cast<uint8_t *>(buffer.data())); | 219 | auto new_string = new vm_string_t(len, reinterpret_cast<uint8_t *>(buffer.data())); |
| @@ -238,7 +239,7 @@ namespace · 19 unmodified lines | |||
| 238 | auto buffer = std::array<char, sizeof("-9223372036854775808")>{}; | 239 | auto buffer = std::array<char, sizeof("-9223372036854775808")>{}; |
| 239 | 240 | ||
| 240 | const auto b = vt_ref<big_t>(r.src); | 241 | 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); |
| 242 | assert(static_cast<std::size_t>(len) <= buffer.size()); | 243 | assert(static_cast<std::size_t>(len) <= buffer.size()); |
| 243 | 244 | ||
| 244 | auto new_string = new vm_string_t(len, reinterpret_cast<uint8_t *>(buffer.data())); | 245 | auto new_string = new vm_string_t(len, reinterpret_cast<uint8_t *>(buffer.data())); |
| @@ -643,6 +644,7 @@ namespace · 398 unmodified lines | |||
| 643 | break; | 644 | break; |
| 644 | } | 645 | } |
| 645 | 646 | ||
| 647 | assert(0 <= static_cast<std::size_t>(target_pc) && static_cast<std::size_t>(target_pc) < r.module_ref->code_section.size()); | ||
| 646 | r.pc = target_pc; | 648 | r.pc = target_pc; |
| 647 | } | 649 | } |
| 648 | 650 | ||
| @@ -1036,7 +1038,7 @@ namespace · 387 unmodified lines | |||
| 1036 | vm_registers_t &r, | 1038 | vm_registers_t &r, |
| 1037 | const vm_t &vm, | 1039 | const vm_t &vm, |
| 1038 | std::shared_ptr<const type_descriptor_t> type_desc, | 1040 | 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) |
| 1040 | { | 1042 | { |
| 1041 | auto buffer_len = word_t{ 0 }; | 1043 | auto buffer_len = word_t{ 0 }; |
| 1042 | if (r.mid != r.dest) | 1044 | if (r.mid != r.dest) |
| @@ -1066,7 +1068,7 @@ namespace · 23 unmodified lines | |||
| 1066 | { | 1068 | { |
| 1067 | auto memory_size = vt_ref<word_t>(r.src); | 1069 | auto memory_size = vt_ref<word_t>(r.src); |
| 1068 | auto channel_data_type = type_descriptor_t::create(memory_size); | 1070 | auto channel_data_type = type_descriptor_t::create(memory_size); |
| 1069 | 1071 | ||
| 1070 | _newc_(r, vm, channel_data_type, _channel_movm); | 1072 | _newc_(r, vm, channel_data_type, _channel_movm); |
| 1071 | } | 1073 | } |
| 1072 | 1074 | ||
| @@ -1669,6 +1671,24 @@ namespace · 596 unmodified lines | |||
| 1669 | r.pc = handler_pc; | 1671 | r.pc = handler_pc; |
| 1670 | } | 1672 | } |
| 1671 | 1673 | ||
| 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 | |||
| 1672 | // | 1692 | // |
| 1673 | // Module operations | 1693 | // Module operations |
| 1674 | // | 1694 | // |
| @@ -1895,4 +1915,5 @@ const vm_exec_t disvm::runtime::vm_exec_table[static_cast<std::size_t>(opcode_t: · 220 unmodified lines | |||
| 1895 | expl, | 1915 | expl, |
| 1896 | expf, | 1916 | expf, |
| 1897 | notimpl, // self, | 1917 | notimpl, // self, |
| 1918 | brkpt, | ||
| 1898 | }; | 1919 | }; |
| @@ -5,6 +5,7 @@ · 4 unmodified lines | |||
| 5 | // | 5 | // |
| 6 | 6 | ||
| 7 | #include <debug.h> | 7 | #include <debug.h> |
| 8 | #include <iostream> | ||
| 8 | #include <sstream> | 9 | #include <sstream> |
| 9 | #include <exceptions.h> | 10 | #include <exceptions.h> |
| 10 | #include "scheduler.h" | 11 | #include "scheduler.h" |
| @@ -52,8 +53,7 @@ void default_scheduler_t::worker_main(default_scheduler_t &instance) · 41 unmodified lines | |||
| 52 | if (current_thread != nullptr) | 53 | if (current_thread != nullptr) |
| 53 | { | 54 | { |
| 54 | const auto &r = current_thread->vm_thread->get_registers(); | 55 | 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 | walk_stack(r, [&err_msg](const pointer_t, const vm_pc_t pc, const vm_module_ref_t &module_ref) { |
| 56 | { | ||
| 57 | auto module_name = "<No Name>"; | 57 | auto module_name = "<No Name>"; |
| 58 | if (module_ref.module->module_name != nullptr) | 58 | if (module_ref.module->module_name != nullptr) |
| 59 | module_name = module_ref.module->module_name->str(); | 59 | 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 | |||
| 63 | }); | 63 | }); |
| 64 | } | 64 | } |
| 65 | 65 | ||
| 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 | |||
| 67 | instance._terminating = true; | 69 | instance._terminating = true; |
| 68 | instance._worker_event.notify_all(); | 70 | instance._worker_event.notify_all(); |
| 69 | } | 71 | } |
| @@ -78,6 +80,11 @@ default_scheduler_t::default_scheduler_t(vm_t &vm, uint32_t system_thread_count, · 8 unmodified lines | |||
| 78 | , _vm{ vm } | 80 | , _vm{ vm } |
| 79 | , _vm_thread_quanta{ thread_quanta } | 81 | , _vm_thread_quanta{ thread_quanta } |
| 80 | { | 82 | { |
| 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" }; | ||
| 81 | } | 88 | } |
| 82 | 89 | ||
| 83 | default_scheduler_t::~default_scheduler_t() | 90 | default_scheduler_t::~default_scheduler_t() |
| @@ -292,21 +292,21 @@ Sys_byte2char(vm_registers_t &r, vm_t &vm) · 291 unmodified lines | |||
| 292 | fp.ret->t1 = 0; | 292 | fp.ret->t1 = 0; |
| 293 | fp.ret->t2 = 0; | 293 | fp.ret->t2 = 0; |
| 294 | 294 | ||
| 295 | auto state = utf8::decode_state::accept; | 295 | auto state = utf8::decode_state_t::accept; |
| 296 | auto result = rune_t{}; | 296 | auto result = rune_t{}; |
| 297 | const auto buffer_len = buffer->get_length(); | 297 | const auto buffer_len = buffer->get_length(); |
| 298 | for (auto i = n; i < buffer_len; ++i) | 298 | for (auto i = n; i < buffer_len; ++i) |
| 299 | { | 299 | { |
| 300 | auto b = buffer->at<byte_t>(i); | 300 | auto b = buffer->at<byte_t>(i); |
| 301 | state = utf8::decode_step(state, result, b); | 301 | state = utf8::decode_step(state, result, b); |
| 302 | if (state == utf8::decode_state::accept) | 302 | if (state == utf8::decode_state_t::accept) |
| 303 | { | 303 | { |
| 304 | fp.ret->t0 = result; | 304 | fp.ret->t0 = result; |
| 305 | fp.ret->t1 = (i - n) + 1; // Number of bytes consumed | 305 | fp.ret->t1 = (i - n) + 1; // Number of bytes consumed |
| 306 | fp.ret->t2 = 1; | 306 | fp.ret->t2 = 1; |
| 307 | return; | 307 | return; |
| 308 | } | 308 | } |
| 309 | else if (state == utf8::decode_state::reject) | 309 | else if (state == utf8::decode_state_t::reject) |
| 310 | { | 310 | { |
| 311 | fp.ret->t1 = 1; | 311 | fp.ret->t1 = 1; |
| 312 | return; | 312 | return; |
| @@ -133,6 +133,7 @@ vm_registers_t::vm_registers_t( · 132 unmodified lines | |||
| 133 | , thread{ thread } | 133 | , thread{ thread } |
| 134 | , pc{ entry.module->header.entry_pc } | 134 | , pc{ entry.module->header.entry_pc } |
| 135 | , stack{ entry.module->header.stack_extent } | 135 | , stack{ entry.module->header.stack_extent } |
| 136 | , tool_dispatch{ nullptr } | ||
| 136 | , src{ nullptr } | 137 | , src{ nullptr } |
| 137 | , mid{ nullptr } | 138 | , mid{ nullptr } |
| 138 | , dest{ nullptr } | 139 | , dest{ nullptr } |
| @@ -203,9 +204,9 @@ vm_thread_t::vm_thread_t( · 64 unmodified lines | |||
| 203 | 204 | ||
| 204 | vm_thread_t::vm_thread_t( | 205 | vm_thread_t::vm_thread_t( |
| 205 | vm_module_ref_t &entry, | 206 | vm_module_ref_t &entry, |
| 207 | uint32_t parent_thread_id, | ||
| 206 | const vm_frame_t &initial_frame, | 208 | const vm_frame_t &initial_frame, |
| 207 | vm_pc_t start_pc, | 209 | vm_pc_t start_pc) |
| 208 | uint32_t parent_thread_id) | ||
| 209 | : vm_alloc_t(vm_thread_t::type_desc()) | 210 | : vm_alloc_t(vm_thread_t::type_desc()) |
| 210 | , _parent_thread_id{ parent_thread_id } | 211 | , _parent_thread_id{ parent_thread_id } |
| 211 | , _registers{ *this, entry } | 212 | , _registers{ *this, entry } |
| @@ -236,7 +237,7 @@ vm_thread_t::~vm_thread_t() · 24 unmodified lines | |||
| 236 | debug::log_msg(debug::component_trace_t::thread, debug::log_level_t::debug, "destroy: vm thread: %d %d\n", _thread_id, _parent_thread_id); | 237 | debug::log_msg(debug::component_trace_t::thread, debug::log_level_t::debug, "destroy: vm thread: %d %d\n", _thread_id, _parent_thread_id); |
| 237 | } | 238 | } |
| 238 | 239 | ||
| 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) |
| 240 | { | 241 | { |
| 241 | _registers.current_thread_state = vm_thread_state_t::running; | 242 | _registers.current_thread_state = vm_thread_state_t::running; |
| 242 | assert(_registers.pc != runtime_constants::invalid_program_counter); | 243 | 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 | |||
| 249 | { | 250 | { |
| 250 | if (_registers.stack.peek_frame() == nullptr) | 251 | if (_registers.stack.peek_frame() == nullptr) |
| 251 | { | 252 | { |
| 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 | |||
| 253 | _registers.current_thread_state = vm_thread_state_t::empty_stack; | 256 | _registers.current_thread_state = vm_thread_state_t::empty_stack; |
| 254 | break; | 257 | break; |
| 255 | } | 258 | } |
| @@ -257,14 +260,16 @@ vm_thread_state_t vm_thread_t::execute(vm_t &virtual_machine, const uint32_t qua · 1 unmodified line | |||
| 257 | const auto &code_section = _registers.module_ref->code_section; | 260 | const auto &code_section = _registers.module_ref->code_section; |
| 258 | assert(static_cast<std::size_t>(_registers.pc) < code_section.size()); | 261 | assert(static_cast<std::size_t>(_registers.pc) < code_section.size()); |
| 259 | 262 | ||
| 263 | assert(!_registers.module_ref->is_builtin_module() && "Interpreter thread is unable to execute native instructions"); | ||
| 260 | const auto &inst = code_section[_registers.pc].op; | 264 | const auto &inst = code_section[_registers.pc].op; |
| 261 | const auto op_code = static_cast<std::size_t>(inst.opcode); | 265 | const auto opcode = static_cast<std::size_t>(inst.opcode); |
| 262 | if (static_cast<std::size_t>(opcode_t::last_opcode) < op_code) | 266 | if (static_cast<std::size_t>(opcode_t::last_opcode) < opcode) |
| 263 | throw vm_system_exception{ "Unknown op-code" }; | 267 | throw vm_system_exception{ "Unknown op-code" }; |
| 264 | 268 | ||
| 269 | // Instruction is valid, decode and update registers for operation | ||
| 265 | decode_address(inst, _registers); | 270 | decode_address(inst, _registers); |
| 266 | _registers.pc++; | 271 | _registers.pc++; |
| 267 | vm_exec_table[op_code](_registers, virtual_machine); | 272 | vm_exec_table[opcode](_registers, vm); |
| 268 | 273 | ||
| 269 | if (_registers.current_thread_state != vm_thread_state_t::running) | 274 | if (_registers.current_thread_state != vm_thread_state_t::running) |
| 270 | break; | 275 | break; |
| @@ -307,6 +312,12 @@ vm_thread_state_t vm_thread_t::execute(vm_t &virtual_machine, const uint32_t qua · 36 unmodified lines | |||
| 307 | return _registers.current_thread_state; | 312 | return _registers.current_thread_state; |
| 308 | } | 313 | } |
| 309 | 314 | ||
| 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 | |||
| 310 | vm_thread_state_t vm_thread_t::get_state() const | 321 | vm_thread_state_t vm_thread_t::get_state() const |
| 311 | { | 322 | { |
| 312 | return _registers.current_thread_state; | 323 | return _registers.current_thread_state; |
| @@ -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 | } | ||
| @@ -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_ | ||
| @@ -40,15 +40,15 @@ namespace · 39 unmodified lines | |||
| 40 | } | 40 | } |
| 41 | 41 | ||
| 42 | // See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. | 42 | // 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) |
| 44 | { | 44 | { |
| 45 | auto type = uint32_t{ utf8d[byte] }; | 45 | auto type = uint32_t{ utf8d[byte] }; |
| 46 | 46 | ||
| 47 | codepoint = (state != utf8::decode_state::accept) ? | 47 | codepoint = (state != utf8::decode_state_t::accept) ? |
| 48 | (byte & 0x3fu) | (codepoint << 6) : | 48 | (byte & 0x3fu) | (codepoint << 6) : |
| 49 | (0xff >> type) & (byte); | 49 | (0xff >> type) & (byte); |
| 50 | 50 | ||
| 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]); |
| 52 | return state; | 52 | return state; |
| 53 | } | 53 | } |
| 54 | 54 | ||
| @@ -59,15 +59,15 @@ std::size_t disvm::runtime::utf8::count_codepoints(const uint8_t *str, const std · 4 unmodified lines | |||
| 59 | assert(str != nullptr); | 59 | assert(str != nullptr); |
| 60 | auto single_character = rune_t{}; | 60 | auto single_character = rune_t{}; |
| 61 | auto points = std::size_t{ 0 }; | 61 | auto points = std::size_t{ 0 }; |
| 62 | auto state = utf8::decode_state::accept; | 62 | auto state = utf8::decode_state_t::accept; |
| 63 | 63 | ||
| 64 | for (auto i = std::size_t{ 0 }; i < max_len; ++i) | 64 | for (auto i = std::size_t{ 0 }; i < max_len; ++i) |
| 65 | { | 65 | { |
| 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])) |
| 67 | points++; | 67 | points++; |
| 68 | } | 68 | } |
| 69 | 69 | ||
| 70 | if (state != utf8::decode_state::accept) | 70 | if (state != utf8::decode_state_t::accept) |
| 71 | throw invalid_utf8{}; | 71 | throw invalid_utf8{}; |
| 72 | 72 | ||
| 73 | return points; | 73 | return points; |
| @@ -76,7 +76,7 @@ std::size_t disvm::runtime::utf8::count_codepoints(const uint8_t *str, const std · 2 unmodified lines | |||
| 76 | std::size_t disvm::runtime::utf8::decode(const uint8_t *str, rune_t &codepoint) | 76 | std::size_t disvm::runtime::utf8::decode(const uint8_t *str, rune_t &codepoint) |
| 77 | { | 77 | { |
| 78 | assert(str != nullptr); | 78 | assert(str != nullptr); |
| 79 | auto state = utf8::decode_state::accept; | 79 | auto state = utf8::decode_state_t::accept; |
| 80 | 80 | ||
| 81 | codepoint = 0; | 81 | codepoint = 0; |
| 82 | auto begin = str; | 82 | auto begin = str; |
| @@ -86,7 +86,7 @@ std::size_t disvm::runtime::utf8::decode(const uint8_t *str, rune_t &codepoint) · 3 unmodified lines | |||
| 86 | { | 86 | { |
| 87 | curr++; | 87 | curr++; |
| 88 | state = utf8::decode_step(state, codepoint, c); | 88 | state = utf8::decode_step(state, codepoint, c); |
| 89 | if (state == utf8::decode_state::accept) | 89 | if (state == utf8::decode_state_t::accept) |
| 90 | return (curr - begin); | 90 | return (curr - begin); |
| 91 | } | 91 | } |
| 92 | 92 | ||
| @@ -19,7 +19,7 @@ · 18 unmodified lines | |||
| 19 | using namespace disvm; | 19 | using namespace disvm; |
| 20 | using namespace disvm::runtime; | 20 | using namespace disvm::runtime; |
| 21 | 21 | ||
| 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) |
| 23 | : origin{ std::move(origin) } | 23 | : origin{ std::move(origin) } |
| 24 | , module{ module } | 24 | , module{ module } |
| 25 | { | 25 | { |
| @@ -126,7 +126,7 @@ const runtime::vm_thread_t& vm_t::fork( · 100 unmodified lines | |||
| 126 | if (initial_pc < 0 || static_cast<vm_pc_t>(module_ref.code_section.size()) <= initial_pc) | 126 | if (initial_pc < 0 || static_cast<vm_pc_t>(module_ref.code_section.size()) <= initial_pc) |
| 127 | throw vm_system_exception{ "Invalid entry program counter" }; | 127 | throw vm_system_exception{ "Invalid entry program counter" }; |
| 128 | 128 | ||
| 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); |
| 130 | return _scheduler->schedule_thread(std::move(thread)); | 130 | return _scheduler->schedule_thread(std::move(thread)); |
| 131 | } | 131 | } |
| 132 | 132 | ||
| @@ -179,6 +179,21 @@ std::shared_ptr<const runtime::vm_module_t> vm_t::load_module(const char *path) · 46 unmodified lines | |||
| 179 | return new_module; | 179 | return new_module; |
| 180 | } | 180 | } |
| 181 | 181 | ||
| 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 | |||
| 182 | runtime::vm_scheduler_control_t &vm_t::get_scheduler_control() const | 197 | runtime::vm_scheduler_control_t &vm_t::get_scheduler_control() const |
| 183 | { | 198 | { |
| 184 | return _scheduler->get_controller(); | 199 | return _scheduler->get_controller(); |