Unified · Split
| @@ -11,7 +11,7 @@ · 10 unmodified lines | ||
| 11 | 11 | #include <string> |
| 12 | 12 | #include <algorithm> |
| 13 | 13 | #include <map> |
| 14 | −#include <unordered_set> | |
| 14 | +#include <set> | |
| 15 | 15 | #include <utils.h> |
| 16 | 16 | #include <vm_memory.h> |
| 17 | 17 | #include <vm_asm.h> |
| @@ -91,28 +91,31 @@ namespace · 73 unmodified lines | ||
| 91 | 91 | { |
| 92 | 92 | static std::string last_successful_cmd_option; |
| 93 | 93 | |
| 94 | − dbg_cmd_cxt_t(debugger &d, const vm_registers_t &r) | |
| 94 | + dbg_cmd_cxt_t(debugger &d, vm_registers_t &r, bool transient_context = false) | |
| 95 | 95 | : dbg{ d } |
| 96 | 96 | , exit_break{ false } |
| 97 | + , current_register{ &r } | |
| 97 | 98 | , initial_register{ r } |
| 99 | + , transient_context{ transient_context } | |
| 98 | 100 | , vm{ d.controller->get_vm_instance() } |
| 99 | 101 | { |
| 100 | − current_register = &initial_register; | |
| 101 | 102 | last_successful_cmd = dbg.get_option(last_successful_cmd_option); |
| 102 | 103 | } |
| 103 | 104 | |
| 104 | 105 | ~dbg_cmd_cxt_t() |
| 105 | 106 | { |
| 106 | − dbg.set_option(last_successful_cmd_option, std::move(last_successful_cmd)); | |
| 107 | + if (!transient_context) | |
| 108 | + dbg.set_option(last_successful_cmd_option, std::move(last_successful_cmd)); | |
| 107 | 109 | } |
| 108 | 110 | |
| 111 | + const bool transient_context; | |
| 109 | 112 | bool exit_break; |
| 110 | 113 | std::string last_successful_cmd; |
| 111 | 114 | |
| 112 | 115 | debugger &dbg; |
| 113 | 116 | const vm_t &vm; |
| 114 | 117 | const vm_registers_t *current_register; |
| 115 | − const vm_registers_t &initial_register; | |
| 118 | + vm_registers_t &initial_register; | |
| 116 | 119 | }; |
| 117 | 120 | |
| 118 | 121 | std::string dbg_cmd_cxt_t::last_successful_cmd_option = "last_successful_cmd"; |
| @@ -168,7 +171,7 @@ namespace · 49 unmodified lines | ||
| 168 | 171 | |
| 169 | 172 | if (!r.module_ref->is_builtin_module()) |
| 170 | 173 | { |
| 171 | − assert(r.pc < static_cast<word_t>(r.module_ref->code_section.size())); | |
| 174 | + assert(static_cast<std::size_t>(r.pc) < r.module_ref->code_section.size()); | |
| 172 | 175 | const auto &op = r.module_ref->code_section[r.pc].op; |
| 173 | 176 | register_string |
| 174 | 177 | << "\n Src: " << op.source |
| @@ -332,6 +335,12 @@ namespace · 157 unmodified lines | ||
| 332 | 335 | cxt.exit_break = true; |
| 333 | 336 | } |
| 334 | 337 | |
| 338 | + void cmd_step(const std::vector<std::string> &, dbg_cmd_cxt_t &cxt) | |
| 339 | + { | |
| 340 | + cxt.dbg.controller->set_thread_trap_flag(*cxt.current_register, vm_trap_flags_t::instruction); | |
| 341 | + cxt.exit_break = true; | |
| 342 | + } | |
| 343 | + | |
| 335 | 344 | void cmd_term(const std::vector<std::string> &, dbg_cmd_cxt_t &) |
| 336 | 345 | { |
| 337 | 346 | throw vm_term_request{}; |
| @@ -347,7 +356,7 @@ namespace · 9 unmodified lines | ||
| 347 | 356 | { |
| 348 | 357 | auto msg = std::stringstream{}; |
| 349 | 358 | for (auto t : cxt.vm.get_scheduler_control().get_all_threads()) |
| 350 | − msg << " " << std::setw(5) << t->get_thread_id() << " - " << t->get_state() << "\n"; | |
| 359 | + msg << " " << std::setw(5) << t->get_thread_id() << " - " << t->get_registers().current_thread_state << "\n"; | |
| 351 | 360 | |
| 352 | 361 | debug_print_info(msg.str()); |
| 353 | 362 | } |
| @@ -843,9 +852,10 @@ namespace · 489 unmodified lines | ||
| 843 | 852 | << console_modifiers::reset_all; |
| 844 | 853 | } |
| 845 | 854 | |
| 846 | − std::unordered_set<std::string> dbg_options = | |
| 855 | + std::set<std::string> dbg_options = | |
| 847 | 856 | { |
| 848 | − { "bp-cmd" } | |
| 857 | + { "bp-cmd" }, | |
| 858 | + { "step-cmd" } | |
| 849 | 859 | }; |
| 850 | 860 | |
| 851 | 861 | void set_value_cmd(const std::vector<std::string> &args, dbg_cmd_cxt_t &cxt) |
| @@ -889,6 +899,7 @@ namespace · 37 unmodified lines | ||
| 889 | 899 | const dbg_cmd_t cmds[] = |
| 890 | 900 | { |
| 891 | 901 | { "c", "Continue", nullptr, nullptr, cmd_continue }, |
| 902 | + { "s", "Step over", nullptr, nullptr, cmd_step }, | |
| 892 | 903 | { "term", "Terminate the VM instance", nullptr, nullptr, cmd_term }, |
| 893 | 904 | |
| 894 | 905 | { "r", "Print registers", nullptr, nullptr, cmd_print_registers }, |
| @@ -969,15 +980,19 @@ namespace · 74 unmodified lines | ||
| 969 | 980 | } |
| 970 | 981 | } |
| 971 | 982 | |
| 972 | − void prompt(debugger &debugger, const vm_registers_t &r) | |
| 983 | + void prompt(debugger &debugger, vm_registers_t &r) | |
| 973 | 984 | { |
| 974 | 985 | debugger.controller->suspend_all_threads(); |
| 975 | 986 | |
| 976 | − std::cout | |
| 977 | − << console_modifiers::green_font | |
| 978 | − << console_modifiers::bold | |
| 979 | − << "'?' for help\n" | |
| 980 | − << console_modifiers::reset_all; | |
| 987 | + if (debugger.first_break) | |
| 988 | + { | |
| 989 | + debugger.first_break = false; | |
| 990 | + std::cout | |
| 991 | + << console_modifiers::green_font | |
| 992 | + << console_modifiers::bold | |
| 993 | + << "'?' for help\n" | |
| 994 | + << console_modifiers::reset_all; | |
| 995 | + } | |
| 981 | 996 | |
| 982 | 997 | std::string cmd; |
| 983 | 998 | |
| @@ -1018,6 +1033,7 @@ debugger::debugger(const debugger_options opt) · 34 unmodified lines | ||
| 1018 | 1033 | : _options{ opt } |
| 1019 | 1034 | , _tool_id{ 0 } |
| 1020 | 1035 | , controller{ nullptr } |
| 1036 | + , first_break{ true } | |
| 1021 | 1037 | { } |
| 1022 | 1038 | |
| 1023 | 1039 | std::vector<std::string> debugger::resolve_to_function_source_line( |
| @@ -1203,11 +1219,23 @@ void debugger::on_load(vm_tool_controller_t &controller_, std::size_t tool_id) · 179 unmodified lines | ||
| 1203 | 1219 | |
| 1204 | 1220 | debug_print_info(ss.str()); |
| 1205 | 1221 | |
| 1206 | − auto bp_cmd = get_option({ "bp-cmd" }); | |
| 1207 | − if (!bp_cmd.empty()) | |
| 1222 | + auto cmd = get_option({ "bp-cmd" }); | |
| 1223 | + if (!cmd.empty()) | |
| 1224 | + { | |
| 1225 | + dbg_cmd_cxt_t dbg_cxt{ *this, *cxt.value1.registers, true }; | |
| 1226 | + execute_single_command(dbg_cxt, std::move(cmd)); | |
| 1227 | + } | |
| 1228 | + | |
| 1229 | + prompt(*this, *cxt.value1.registers); | |
| 1230 | + })); | |
| 1231 | + | |
| 1232 | + _event_cookies.emplace(controller->subscribe_event(vm_event_t::trap, [&](vm_event_t, vm_event_context_t &cxt) | |
| 1233 | + { | |
| 1234 | + auto cmd = get_option({ "step-cmd" }); | |
| 1235 | + if (!cmd.empty()) | |
| 1208 | 1236 | { |
| 1209 | − dbg_cmd_cxt_t dbg_cxt{ *this, *cxt.value1.registers }; | |
| 1210 | − execute_single_command(dbg_cxt, std::move(bp_cmd)); | |
| 1237 | + dbg_cmd_cxt_t dbg_cxt{ *this, *cxt.value1.registers, true }; | |
| 1238 | + execute_single_command(dbg_cxt, std::move(cmd)); | |
| 1211 | 1239 | } |
| 1212 | 1240 | |
| 1213 | 1241 | prompt(*this, *cxt.value1.registers); |
| @@ -61,6 +61,7 @@ public: // vm_tool_t · 60 unmodified lines | ||
| 61 | 61 | public: |
| 62 | 62 | disvm::runtime::vm_tool_controller_t *controller; |
| 63 | 63 | std::unordered_set<disvm::runtime::cookie_t> breakpoint_cookies; |
| 64 | + bool first_break; | |
| 64 | 65 | |
| 65 | 66 | private: |
| 66 | 67 | void load_symbols(const disvm::loaded_vm_module_t &); |
| @@ -709,7 +709,7 @@ namespace disvm · 708 unmodified lines | ||
| 709 | 709 | const vm_string_t &exception_id); |
| 710 | 710 | |
| 711 | 711 | // VM thread states |
| 712 | − enum class vm_thread_state_t : uint16_t | |
| 712 | + enum class vm_thread_state_t : uint8_t | |
| 713 | 713 | { |
| 714 | 714 | unknown = 0, |
| 715 | 715 | |
| @@ -729,6 +729,13 @@ namespace disvm · 13 unmodified lines | ||
| 729 | 729 | broken, // thread crashed - the scheduler is free to terminate the entire VM if any thread enters this state. |
| 730 | 730 | }; |
| 731 | 731 | |
| 732 | + // VM trap flags | |
| 733 | + enum class vm_trap_flags_t : uint8_t | |
| 734 | + { | |
| 735 | + none = 0, | |
| 736 | + instruction = 1 << 0, | |
| 737 | + }; | |
| 738 | + | |
| 732 | 739 | // Forward declaration |
| 733 | 740 | class vm_thread_t; |
| 734 | 741 | class vm_tool_dispatch_t; |
| @@ -738,11 +745,11 @@ namespace disvm · 3 unmodified lines | ||
| 738 | 745 | { |
| 739 | 746 | public: |
| 740 | 747 | vm_registers_t( |
| 741 | − const vm_thread_t &thread, | |
| 748 | + vm_thread_t &thread, | |
| 742 | 749 | vm_module_ref_t &entry); |
| 743 | 750 | ~vm_registers_t(); |
| 744 | 751 | |
| 745 | − const vm_thread_t &thread; | |
| 752 | + vm_thread_t &thread; | |
| 746 | 753 | std::atomic<vm_tool_dispatch_t *> tool_dispatch; |
| 747 | 754 | vm_stack_t stack; // Frame pointer access (FP) |
| 748 | 755 | vm_pc_t pc; // Current Program counter (Index into module code section) |
| @@ -751,8 +758,9 @@ namespace disvm · 2 unmodified lines | ||
| 751 | 758 | vm_module_ref_t *module_ref; // Module reference |
| 752 | 759 | vm_request_mutex_t request_mutex; |
| 753 | 760 | |
| 754 | − vm_thread_state_t current_thread_state; | |
| 755 | 761 | uint16_t current_thread_quanta; |
| 762 | + vm_thread_state_t current_thread_state; | |
| 763 | + vm_trap_flags_t trap_flags; | |
| 756 | 764 | |
| 757 | 765 | pointer_t src; |
| 758 | 766 | pointer_t mid; |
| @@ -795,8 +803,6 @@ namespace disvm · 36 unmodified lines | ||
| 795 | 803 | // this vm thread. |
| 796 | 804 | void set_tool_dispatch(vm_tool_dispatch_t *dispatch); |
| 797 | 805 | |
| 798 | − vm_thread_state_t get_state() const; | |
| 799 | − | |
| 800 | 806 | // This function will only return a non-null |
| 801 | 807 | // value if this thread is in the broken state. |
| 802 | 808 | const char *get_error_message() const; |
| @@ -23,6 +23,7 @@ namespace disvm · 22 unmodified lines | ||
| 23 | 23 | union vm_context_value_t |
| 24 | 24 | { |
| 25 | 25 | cookie_t cookie; |
| 26 | + vm_trap_flags_t trap; | |
| 26 | 27 | std::shared_ptr<vm_module_t> *module; // Note this is a pointer to std::shared_ptr<> |
| 27 | 28 | vm_registers_t *registers; |
| 28 | 29 | const vm_thread_t *thread; |
| @@ -61,6 +62,9 @@ namespace disvm · 32 unmodified lines | ||
| 61 | 62 | |
| 62 | 63 | breakpoint, // value1: vm_registers_t |
| 63 | 64 | // value2: cookie_t - cookie of breakpoint. '0' indicates the breakpoint was inserted by the VM or compiler. |
| 65 | + | |
| 66 | + trap, // value1: vm_registers_t - The trap flag is unset prior to the event be fired. | |
| 67 | + // value2: vm_trap_flags_t | |
| 64 | 68 | }; |
| 65 | 69 | |
| 66 | 70 | using vm_event_callback_t = std::function<void(vm_event_t, vm_event_context_t &)>; |
| @@ -91,6 +95,7 @@ namespace disvm · 24 unmodified lines | ||
| 91 | 95 | // Manipulate thread execution |
| 92 | 96 | virtual void suspend_all_threads() = 0; |
| 93 | 97 | virtual void resume_all_threads() = 0; |
| 98 | + virtual void set_thread_trap_flag(const vm_registers_t &, vm_trap_flags_t flag) = 0; | |
| 94 | 99 | }; |
| 95 | 100 | } |
| 96 | 101 | } |
| @@ -130,7 +130,7 @@ vm_scheduler_control_t &default_scheduler_t::get_controller() const · 129 unmodified lines | ||
| 130 | 130 | const vm_thread_t& default_scheduler_t::schedule_thread(std::unique_ptr<runtime::vm_thread_t> thread) |
| 131 | 131 | { |
| 132 | 132 | assert(thread != nullptr); |
| 133 | − if (thread->get_state() != vm_thread_state_t::ready) | |
| 133 | + if (thread->get_registers().current_thread_state != vm_thread_state_t::ready) | |
| 134 | 134 | throw vm_system_exception{ "Scheduled thread in invalid state" }; |
| 135 | 135 | |
| 136 | 136 | if (debug::is_component_tracing_enabled<debug::component_trace_t::scheduler>()) |
| @@ -284,7 +284,7 @@ std::shared_ptr<default_scheduler_t::thread_instance_t> default_scheduler_t::nex · 147 unmodified lines | ||
| 284 | 284 | // Check if the passed in thread should be enqueued |
| 285 | 285 | if (prev_thread != nullptr) |
| 286 | 286 | { |
| 287 | − const auto current_state = prev_thread->vm_thread->get_state(); | |
| 287 | + const auto current_state = prev_thread->vm_thread->get_registers().current_thread_state; | |
| 288 | 288 | |
| 289 | 289 | // This system thread releases ownership of the vm thread after it has been enqueued |
| 290 | 290 | std::lock_guard<std::mutex> vm_thread_ownership{ prev_thread->system_thread_ownership, std::adopt_lock }; |
| @@ -367,7 +367,7 @@ void default_scheduler_t::perform_gc(bool is_gc_thread, std::unique_lock<std::mu · 76 unmodified lines | ||
| 367 | 367 | for (auto &p : _all_vm_threads) |
| 368 | 368 | { |
| 369 | 369 | auto &vm_thread = p.second->vm_thread; |
| 370 | − if (vm_thread->get_state() != vm_thread_state_t::broken) | |
| 370 | + if (vm_thread->get_registers().current_thread_state != vm_thread_state_t::broken) | |
| 371 | 371 | result.push_back(vm_thread); |
| 372 | 372 | } |
| 373 | 373 | |
| @@ -12,6 +12,7 @@ · 11 unmodified lines | ||
| 12 | 12 | #include <exceptions.h> |
| 13 | 13 | #include <vm_memory.h> |
| 14 | 14 | #include <debug.h> |
| 15 | +#include <utils.h> | |
| 15 | 16 | #include "execution_table.h" |
| 16 | 17 | #include "tool_dispatch.h" |
| 17 | 18 | |
| @@ -51,10 +52,11 @@ namespace · 33 unmodified lines | ||
| 51 | 52 | } |
| 52 | 53 | |
| 53 | 54 | vm_registers_t::vm_registers_t( |
| 54 | − const vm_thread_t &thread, | |
| 55 | + vm_thread_t &thread, | |
| 55 | 56 | vm_module_ref_t &entry) |
| 56 | 57 | : current_thread_state{ vm_thread_state_t::ready } |
| 57 | 58 | , current_thread_quanta{ 0 } |
| 59 | + , trap_flags{ vm_trap_flags_t::none } | |
| 58 | 60 | , module_ref{ &entry } |
| 59 | 61 | , mp_base{ entry.mp_base } |
| 60 | 62 | , thread{ thread } |
| @@ -174,25 +176,37 @@ namespace · 113 unmodified lines | ||
| 174 | 176 | { |
| 175 | 177 | struct execute_with_tool_t final |
| 176 | 178 | { |
| 177 | − static void begin_exec_loop(vm_registers_t ®isters, vm_t &) | |
| 179 | + static void begin_exec_loop(vm_registers_t &r, vm_t &) | |
| 178 | 180 | { |
| 179 | − auto tool_dispatch = registers.tool_dispatch.load(); | |
| 181 | + auto tool_dispatch = r.tool_dispatch.load(); | |
| 180 | 182 | assert(tool_dispatch != nullptr); |
| 181 | 183 | |
| 182 | − tool_dispatch->block_if_thread_suspended(registers.thread.get_thread_id()); | |
| 184 | + tool_dispatch->block_if_thread_suspended(r.thread.get_thread_id()); | |
| 185 | + } | |
| 186 | + | |
| 187 | + static void after_exec(vm_registers_t &r, vm_t &) | |
| 188 | + { | |
| 189 | + if (r.trap_flags == vm_trap_flags_t::none | |
| 190 | + || !util::has_flag(r.trap_flags, vm_trap_flags_t::instruction)) | |
| 191 | + return; | |
| 192 | + | |
| 193 | + auto tool_dispatch = r.tool_dispatch.load(); | |
| 194 | + assert(tool_dispatch != nullptr); | |
| 195 | + | |
| 196 | + tool_dispatch->on_trap(r, vm_trap_flags_t::instruction); | |
| 183 | 197 | } |
| 184 | 198 | }; |
| 185 | 199 | |
| 186 | 200 | struct execute_normal_t final |
| 187 | 201 | { |
| 188 | 202 | static void begin_exec_loop(vm_registers_t &, vm_t &) { } |
| 203 | + static void after_exec(vm_registers_t &, vm_t &) { } | |
| 189 | 204 | }; |
| 190 | 205 | |
| 191 | 206 | template<typename EXEC_DETOUR> |
| 192 | 207 | void execute_impl(vm_registers_t &r, vm_t &vm) |
| 193 | 208 | { |
| 194 | − auto &execution_duration = r.current_thread_quanta; | |
| 195 | − for (; 0 != execution_duration; --execution_duration) | |
| 209 | + for (; 0 != r.current_thread_quanta; --r.current_thread_quanta) | |
| 196 | 210 | { |
| 197 | 211 | EXEC_DETOUR::begin_exec_loop(r, vm); |
| 198 | 212 | assert(r.stack.peek_frame() != nullptr && "Thread state should not be running with empty stack"); |
| @@ -212,6 +226,8 @@ namespace · 13 unmodified lines | ||
| 212 | 226 | vm_exec_table[opcode](r, vm); |
| 213 | 227 | r.pc = r.next_pc; |
| 214 | 228 | |
| 229 | + EXEC_DETOUR::after_exec(r, vm); | |
| 230 | + | |
| 215 | 231 | if (r.current_thread_state != vm_thread_state_t::running) |
| 216 | 232 | break; |
| 217 | 233 | } |
| @@ -306,11 +322,6 @@ void vm_thread_t::set_tool_dispatch(vm_tool_dispatch_t *dispatch) · 88 unmodified lines | ||
| 306 | 322 | _registers.tool_dispatch = dispatch; |
| 307 | 323 | } |
| 308 | 324 | |
| 309 | −vm_thread_state_t vm_thread_t::get_state() const | |
| 310 | −{ | |
| 311 | − return _registers.current_thread_state; | |
| 312 | −} | |
| 313 | − | |
| 314 | 325 | const char *vm_thread_t::get_error_message() const |
| 315 | 326 | { |
| 316 | 327 | return _error_message; |
| @@ -329,4 +340,4 @@ uint32_t vm_thread_t::get_parent_thread_id() const · 12 unmodified lines | ||
| 329 | 340 | const vm_registers_t& vm_thread_t::get_registers() const |
| 330 | 341 | { |
| 331 | 342 | return _registers; |
| 332 | −} | |
| 343 | +} | |
| @@ -92,6 +92,22 @@ std::size_t vm_tool_dispatch_t::unload_tool(std::size_t tool_id) · 91 unmodified lines | ||
| 92 | 92 | return _tools.size(); |
| 93 | 93 | } |
| 94 | 94 | |
| 95 | +namespace | |
| 96 | +{ | |
| 97 | + DEFINE_ENUM_FLAG_OPERATORS(vm_trap_flags_t); | |
| 98 | +} | |
| 99 | + | |
| 100 | +void vm_tool_dispatch_t::on_trap(vm_registers_t &r, vm_trap_flags_t f) | |
| 101 | +{ | |
| 102 | + vm_event_context_t cxt{}; | |
| 103 | + cxt.value1.registers = &r; | |
| 104 | + cxt.value2.trap = f; | |
| 105 | + | |
| 106 | + // Clear the trap flag | |
| 107 | + r.trap_flags &= ~f; | |
| 108 | + _events.fire_event_callbacks(vm_event_t::trap, cxt); | |
| 109 | +} | |
| 110 | + | |
| 95 | 111 | opcode_t vm_tool_dispatch_t::on_breakpoint(vm_registers_t &r) |
| 96 | 112 | { |
| 97 | 113 | opcode_t original_op; |
| @@ -361,6 +377,15 @@ void vm_tool_dispatch_t::resume_all_threads() · 263 unmodified lines | ||
| 361 | 377 | _threads_resume.notify_all(); |
| 362 | 378 | } |
| 363 | 379 | |
| 380 | +void vm_tool_dispatch_t::set_thread_trap_flag(const vm_registers_t &r, vm_trap_flags_t flag) | |
| 381 | +{ | |
| 382 | + std::lock_guard<std::mutex> suspend_lock{ _threads_resume_lock }; | |
| 383 | + if (!_threads_suspended) | |
| 384 | + throw vm_system_exception{ "Unable to set thread trap flag while threads are not suspended" }; | |
| 385 | + | |
| 386 | + const_cast<vm_trap_flags_t &>(r.trap_flags) |= flag; | |
| 387 | +} | |
| 388 | + | |
| 364 | 389 | std::tuple<opcode_t, cookie_t> vm_tool_dispatch_t::get_original_opcode_and_cookie(const vm_registers_t &r) |
| 365 | 390 | { |
| 366 | 391 | const auto module = r.module_ref->module.get(); |
| @@ -32,6 +32,9 @@ namespace disvm · 31 unmodified lines | ||
| 32 | 32 | // Unloads the tool associated with the supplied ID and returns the current tool count |
| 33 | 33 | std::size_t unload_tool(std::size_t tool_id); |
| 34 | 34 | |
| 35 | + // Callback for a trap event. | |
| 36 | + void on_trap(vm_registers_t &r, vm_trap_flags_t f); | |
| 37 | + | |
| 35 | 38 | // Callback on a breakpoint event. |
| 36 | 39 | // Returns opcode that was replaced by breakpoint opcode. |
| 37 | 40 | opcode_t on_breakpoint(vm_registers_t &r); |
| @@ -74,6 +77,7 @@ namespace disvm · 36 unmodified lines | ||
| 74 | 77 | |
| 75 | 78 | void suspend_all_threads() override; |
| 76 | 79 | void resume_all_threads() override; |
| 80 | + void set_thread_trap_flag(const vm_registers_t &, vm_trap_flags_t flag) override; | |
| 77 | 81 | |
| 78 | 82 | private: |
| 79 | 83 | std::tuple<opcode_t, cookie_t> get_original_opcode_and_cookie(const vm_registers_t &r); |