Unified · Split
| @@ -9,9 +9,7 @@ · 8 unmodified lines | ||
| 9 | 9 | #include <sstream> |
| 10 | 10 | #include <string> |
| 11 | 11 | #include <algorithm> |
| 12 | −#include <locale> | |
| 13 | 12 | #include <map> |
| 14 | −#include <vm_tools.h> | |
| 15 | 13 | #include <utils.h> |
| 16 | 14 | #include <vm_memory.h> |
| 17 | 15 | #include <vm_asm.h> |
| @@ -76,16 +74,18 @@ namespace · 58 unmodified lines | ||
| 76 | 74 | // Debug command context |
| 77 | 75 | struct dbg_cmd_cxt_t |
| 78 | 76 | { |
| 79 | − dbg_cmd_cxt_t(const vm_t &v, const vm_registers_t &r) | |
| 80 | − : initial_register{ r } | |
| 77 | + dbg_cmd_cxt_t(debugger &d, const vm_registers_t &r) | |
| 78 | + : dbg{ d } | |
| 81 | 79 | , exit_break{ false } |
| 82 | − , vm{ v } | |
| 80 | + , initial_register{ r } | |
| 81 | + , vm{ d.controller->get_vm_instance() } | |
| 83 | 82 | { |
| 84 | 83 | current_register = &initial_register; |
| 85 | 84 | } |
| 86 | 85 | |
| 87 | 86 | bool exit_break; |
| 88 | 87 | |
| 88 | + debugger &dbg; | |
| 89 | 89 | const vm_t &vm; |
| 90 | 90 | const vm_registers_t *current_register; |
| 91 | 91 | const vm_registers_t &initial_register; |
| @@ -125,10 +125,8 @@ namespace · 33 unmodified lines | ||
| 125 | 125 | auto register_string = std::stringstream{}; |
| 126 | 126 | |
| 127 | 127 | register_string |
| 128 | − << "Registers:\n" | |
| 129 | 128 | << " Thread ID: " << r.thread.get_thread_id() << "\n" |
| 130 | − << " Module: " << r.module_ref->module->module_name->str() << "\n" | |
| 131 | − << " PC: " << r.pc << "\n" | |
| 129 | + << " Module: " << r.module_ref->module->module_name->str() << " PC: " << r.pc << "\n" | |
| 132 | 130 | << " Opcode: " << r.module_ref->code_section[r.pc].op << "\n"; |
| 133 | 131 | |
| 134 | 132 | return register_string.str(); |
| @@ -231,8 +229,12 @@ namespace · 96 unmodified lines | ||
| 231 | 229 | } |
| 232 | 230 | else |
| 233 | 231 | { |
| 234 | − ss << "alloc\n" | |
| 235 | − << "\tsize: " << alloc->alloc_type->size_in_bytes << "\n"; | |
| 232 | + ss << "alloc - size: " << alloc->alloc_type->size_in_bytes << "\n"; | |
| 233 | + | |
| 234 | + enum_pointer_fields(*alloc->alloc_type, alloc->get_allocation(), [&ss](pointer_t p, std::size_t o) | |
| 235 | + { | |
| 236 | + ss << " [" << o << "] " << vm_alloc_t::from_allocation(p); | |
| 237 | + }); | |
| 236 | 238 | } |
| 237 | 239 | |
| 238 | 240 | return ss; |
| @@ -282,21 +284,6 @@ namespace · 43 unmodified lines | ||
| 282 | 284 | return ss; |
| 283 | 285 | } |
| 284 | 286 | |
| 285 | − std::string to_string_frame_pointers(vm_registers_t &r) | |
| 286 | − { | |
| 287 | − auto frame = r.stack.peek_frame(); | |
| 288 | − if (frame == nullptr) | |
| 289 | − return "<empty>"; | |
| 290 | − | |
| 291 | − auto frame_string = std::stringstream{}; | |
| 292 | − enum_pointer_fields(*frame->frame_type, frame->base(), [&frame_string](pointer_t p) | |
| 293 | − { | |
| 294 | − frame_string << std::showbase << std::hex << reinterpret_cast<uintptr_t>(p) << " : " << std::dec << vm_alloc_t::from_allocation(p); | |
| 295 | − }); | |
| 296 | − | |
| 297 | − return frame_string.str(); | |
| 298 | − } | |
| 299 | − | |
| 300 | 287 | void cmd_continue(const std::vector<std::string> &, dbg_cmd_cxt_t &cxt) |
| 301 | 288 | { |
| 302 | 289 | cxt.exit_break = true; |
| @@ -311,7 +298,6 @@ namespace · 8 unmodified lines | ||
| 311 | 298 | void cmd_print_threads(const std::vector<std::string> &, dbg_cmd_cxt_t &cxt) |
| 312 | 299 | { |
| 313 | 300 | auto msg = std::stringstream{}; |
| 314 | − msg << "Threads:\n"; | |
| 315 | 301 | for (auto t : cxt.vm.get_scheduler_control().get_all_threads()) |
| 316 | 302 | msg << " " << std::setw(5) << t->get_thread_id() << " - " << t->get_state() << "\n"; |
| 317 | 303 | |
| @@ -321,21 +307,146 @@ namespace · 3 unmodified lines | ||
| 321 | 307 | void cmd_print_modules(const std::vector<std::string> &, dbg_cmd_cxt_t &cxt) |
| 322 | 308 | { |
| 323 | 309 | auto msg = std::stringstream{}; |
| 324 | − msg << "Modules:\n"; | |
| 325 | − | |
| 326 | 310 | cxt.vm.enum_loaded_modules([&msg](const loaded_vm_module_t &m) |
| 327 | 311 | { |
| 328 | 312 | auto module_instance = m.module.lock(); |
| 329 | − if (module_instance == nullptr) | |
| 330 | − return; | |
| 313 | + if (module_instance != nullptr) | |
| 314 | + { | |
| 315 | + msg << std::setw(3) << m.load_id << ": " | |
| 316 | + << std::setw(16) << module_instance->module_name->str() | |
| 317 | + << std::setw(0) << " - " << m.origin->str() << "\n"; | |
| 318 | + } | |
| 331 | 319 | |
| 332 | − msg << std::setw(24) << module_instance->module_name->str() << std::setw(0) << " " << m.origin->str() << "\n"; | |
| 320 | + return true; | |
| 333 | 321 | }); |
| 334 | 322 | |
| 335 | 323 | debug_print_info(msg.str()); |
| 336 | 324 | } |
| 337 | 325 | |
| 338 | − void cmd_stacktrace(const std::vector<std::string> &a, dbg_cmd_cxt_t &cxt) | |
| 326 | + void cmd_breakpoint_set(const std::vector<std::string> &a, dbg_cmd_cxt_t &cxt) | |
| 327 | + { | |
| 328 | + if (a.size() < 3) | |
| 329 | + { | |
| 330 | + debug_print_error("Must supply an IP and module ID"); | |
| 331 | + return; | |
| 332 | + } | |
| 333 | + | |
| 334 | + vm_pc_t pc; | |
| 335 | + try | |
| 336 | + { | |
| 337 | + pc = std::stoul(a[1]); | |
| 338 | + } | |
| 339 | + catch (...) | |
| 340 | + { | |
| 341 | + debug_print_error("Invalid IP format"); | |
| 342 | + return; | |
| 343 | + } | |
| 344 | + | |
| 345 | + uint32_t module_id; | |
| 346 | + try | |
| 347 | + { | |
| 348 | + module_id = std::stoul(a[2]); | |
| 349 | + } | |
| 350 | + catch (...) | |
| 351 | + { | |
| 352 | + debug_print_error("Invalid module ID format"); | |
| 353 | + return; | |
| 354 | + } | |
| 355 | + | |
| 356 | + auto module = std::shared_ptr<vm_module_t>{}; | |
| 357 | + cxt.vm.enum_loaded_modules([module_id, &module](const loaded_vm_module_t &m) | |
| 358 | + { | |
| 359 | + if (module_id != m.load_id) | |
| 360 | + return true; | |
| 361 | + | |
| 362 | + // Found the matching ID so stop | |
| 363 | + module = m.module.lock(); | |
| 364 | + return false; | |
| 365 | + }); | |
| 366 | + | |
| 367 | + if (module == nullptr) | |
| 368 | + { | |
| 369 | + debug_print_error("Invalid module ID"); | |
| 370 | + return; | |
| 371 | + } | |
| 372 | + else if (util::has_flag(module->header.runtime_flag, runtime_flags_t::builtin)) | |
| 373 | + { | |
| 374 | + debug_print_error("Unable to set breakpoint in built-in module"); | |
| 375 | + return; | |
| 376 | + } | |
| 377 | + | |
| 378 | + if (pc < 0 || module->code_section.size() <= static_cast<std::size_t>(pc)) | |
| 379 | + { | |
| 380 | + debug_print_error("Invalid IP for module"); | |
| 381 | + return; | |
| 382 | + } | |
| 383 | + | |
| 384 | + cxt.dbg.breakpoint_cookies.push_back(cxt.dbg.controller->set_breakpoint(module, pc)); | |
| 385 | + } | |
| 386 | + | |
| 387 | + void cmd_breakpoint_clear(const std::vector<std::string> &a, dbg_cmd_cxt_t &cxt) | |
| 388 | + { | |
| 389 | + if (a.size() == 1) | |
| 390 | + { | |
| 391 | + debug_print_error("Must supply a breakpoint ID or wildcard"); | |
| 392 | + return; | |
| 393 | + } | |
| 394 | + | |
| 395 | + auto controller = cxt.dbg.controller; | |
| 396 | + if (a[1].compare("*") == 0) | |
| 397 | + { | |
| 398 | + // Clear all breakpoints | |
| 399 | + for (auto cookie : cxt.dbg.breakpoint_cookies) | |
| 400 | + controller->clear_breakpoint(cookie); | |
| 401 | + | |
| 402 | + cxt.dbg.breakpoint_cookies.clear(); | |
| 403 | + return; | |
| 404 | + } | |
| 405 | + | |
| 406 | + // Clear specific breakpoint | |
| 407 | + cookie_t breakpoint_cookie; | |
| 408 | + try | |
| 409 | + { | |
| 410 | + breakpoint_cookie = static_cast<cookie_t>(std::stoul(a[1])); | |
| 411 | + } | |
| 412 | + catch (...) | |
| 413 | + { | |
| 414 | + debug_print_error("Invalid breakpoint ID format"); | |
| 415 | + return; | |
| 416 | + } | |
| 417 | + | |
| 418 | + auto cookie_iter = std::find_if( | |
| 419 | + std::begin(cxt.dbg.breakpoint_cookies), | |
| 420 | + std::end(cxt.dbg.breakpoint_cookies), | |
| 421 | + [breakpoint_cookie] (const cookie_t c) { return c == breakpoint_cookie; }); | |
| 422 | + if (cookie_iter == cxt.dbg.breakpoint_cookies.end()) | |
| 423 | + { | |
| 424 | + debug_print_error("Unknown breakpoint ID"); | |
| 425 | + return; | |
| 426 | + } | |
| 427 | + | |
| 428 | + // Clear the breakpoint | |
| 429 | + controller->clear_breakpoint(breakpoint_cookie); | |
| 430 | + | |
| 431 | + // Remove the cookie from the list | |
| 432 | + cxt.dbg.breakpoint_cookies.erase(cookie_iter); | |
| 433 | + } | |
| 434 | + | |
| 435 | + void cmd_breakpoint_list(const std::vector<std::string> &, dbg_cmd_cxt_t &cxt) | |
| 436 | + { | |
| 437 | + auto msg = std::stringstream{}; | |
| 438 | + for (auto cookie : cxt.dbg.breakpoint_cookies) | |
| 439 | + { | |
| 440 | + const auto details = cxt.dbg.controller->get_breakpoint_details(cookie); | |
| 441 | + msg << std::setw(3) << cookie << ": " | |
| 442 | + << std::setw(16) << details.module->module_name->str() | |
| 443 | + << std::setw(0) << " @ " << details.pc << "\n"; | |
| 444 | + } | |
| 445 | + | |
| 446 | + debug_print_info(msg.str()); | |
| 447 | + } | |
| 448 | + | |
| 449 | + void cmd_stackwalk(const std::vector<std::string> &a, dbg_cmd_cxt_t &cxt) | |
| 339 | 450 | { |
| 340 | 451 | auto thread_registers = std::map<uint32_t, const vm_registers_t*>{}; |
| 341 | 452 | if (a.size() == 1) |
| @@ -345,7 +456,6 @@ namespace · 3 unmodified lines | ||
| 345 | 456 | else |
| 346 | 457 | { |
| 347 | 458 | uint32_t thread_id; |
| 348 | − auto all_threads = cxt.vm.get_scheduler_control().get_all_threads(); | |
| 349 | 459 | if (a[1].compare("*") == 0) |
| 350 | 460 | { |
| 351 | 461 | thread_id = 0; |
| @@ -354,7 +464,7 @@ namespace · 2 unmodified lines | ||
| 354 | 464 | { |
| 355 | 465 | try |
| 356 | 466 | { |
| 357 | − thread_id = std::stoi(a[1]); | |
| 467 | + thread_id = std::stoul(a[1]); | |
| 358 | 468 | } |
| 359 | 469 | catch (...) |
| 360 | 470 | { |
| @@ -364,6 +474,7 @@ namespace · 3 unmodified lines | ||
| 364 | 474 | } |
| 365 | 475 | |
| 366 | 476 | // Find the thread(s) |
| 477 | + auto all_threads = cxt.vm.get_scheduler_control().get_all_threads(); | |
| 367 | 478 | for (auto t : all_threads) |
| 368 | 479 | { |
| 369 | 480 | const auto tid = t->get_thread_id(); |
| @@ -406,7 +517,7 @@ namespace · 36 unmodified lines | ||
| 406 | 517 | uint32_t thread_id; |
| 407 | 518 | try |
| 408 | 519 | { |
| 409 | − thread_id = std::stoi(a[1]); | |
| 520 | + thread_id = std::stoul(a[1]); | |
| 410 | 521 | } |
| 411 | 522 | catch (...) |
| 412 | 523 | { |
| @@ -432,7 +543,7 @@ namespace · 19 unmodified lines | ||
| 432 | 543 | |
| 433 | 544 | void cmd_disassemble(const std::vector<std::string> &a, dbg_cmd_cxt_t &cxt) |
| 434 | 545 | { |
| 435 | − auto disassemble_length = 1; | |
| 546 | + auto disassemble_length = int{ 1 }; | |
| 436 | 547 | if (a.size() > 1) |
| 437 | 548 | { |
| 438 | 549 | try |
| @@ -629,11 +740,17 @@ namespace · 190 unmodified lines | ||
| 629 | 740 | const dbg_cmd_t cmds[] = |
| 630 | 741 | { |
| 631 | 742 | { "c", "Continue", nullptr, nullptr, cmd_continue }, |
| 743 | + | |
| 632 | 744 | { "r", "Print registers", nullptr, nullptr, cmd_print_registers }, |
| 633 | 745 | { "t", "Print all threads", nullptr, nullptr, cmd_print_threads }, |
| 634 | 746 | { "m", "Print all loaded modules", nullptr, nullptr, cmd_print_modules }, |
| 635 | − { "bt", "Stack walk/back trace for the current or supplied thread", "bt ([0-9]+|\\*)?", "bt, bt 34, bt *", cmd_stacktrace }, | |
| 636 | − { "st", "Switch to thread", "st [0-9]+", "st 42", cmd_switchthread }, | |
| 747 | + | |
| 748 | + { "bs", "Set breakpoint at the specified IP in a loaded module", "bs [0-9]+ [0-9]+", "bs 3 5 (Breakpoint at IP 3 in module 5)", cmd_breakpoint_set }, | |
| 749 | + { "bc", "Clear breakpoint(s)", "bc ([0-9]+|\\*)", "bc 3, bc *", cmd_breakpoint_clear }, | |
| 750 | + { "bl", "List breakpoints", nullptr, nullptr, cmd_breakpoint_list }, | |
| 751 | + | |
| 752 | + { "sw", "Stack walk for the current or supplied thread", "sw ([0-9]+|\\*)?", "sw, sw 34, sw *", cmd_stackwalk }, | |
| 753 | + { "~", "Switch to thread", "~ [0-9]+", "~ 42", cmd_switchthread }, | |
| 637 | 754 | { "d", "Disassemble the next/previous N instructions", "d (-?[0-9]+)?", "d, d -4, d 5", cmd_disassemble }, |
| 638 | 755 | { "x", "Examine memory", "x [mp|fp] [0-9]+ ([0-9]+)?", "x mp 3, x fp 20 6", cmd_examine }, |
| 639 | 756 | { "?", "Print help", nullptr, nullptr, cmd_help }, |
| @@ -703,7 +820,7 @@ namespace · 63 unmodified lines | ||
| 703 | 820 | return split_string; |
| 704 | 821 | } |
| 705 | 822 | |
| 706 | − void prompt(const vm_t &vm, const vm_registers_t &r) | |
| 823 | + void prompt(debugger &debugger, const vm_registers_t &r) | |
| 707 | 824 | { |
| 708 | 825 | std::cout |
| 709 | 826 | << console_modifiers::green_font |
| @@ -711,10 +828,9 @@ namespace · 1 unmodified line | ||
| 711 | 828 | << "'?' for help\n" |
| 712 | 829 | << console_modifiers::reset_all; |
| 713 | 830 | |
| 714 | − std::locale loc; | |
| 715 | 831 | std::string cmd; |
| 716 | 832 | |
| 717 | − dbg_cmd_cxt_t cxt{ vm, r }; | |
| 833 | + dbg_cmd_cxt_t cxt{ debugger, r }; | |
| 718 | 834 | for (;;) |
| 719 | 835 | { |
| 720 | 836 | std::cout |
| @@ -746,12 +862,17 @@ namespace · 25 unmodified lines | ||
| 746 | 862 | } |
| 747 | 863 | } |
| 748 | 864 | |
| 749 | −void debugger::on_load(vm_t &vm, vm_tool_controller_t &controller, std::size_t tool_id) | |
| 865 | +debugger::debugger() | |
| 866 | + : _tool_id{ 0 } | |
| 867 | + , controller{ nullptr } | |
| 868 | +{ } | |
| 869 | + | |
| 870 | +void debugger::on_load(vm_tool_controller_t &controller_, std::size_t tool_id) | |
| 750 | 871 | { |
| 751 | 872 | _tool_id = tool_id; |
| 752 | − _vm = &vm; | |
| 873 | + controller = &controller_; | |
| 753 | 874 | |
| 754 | − _event_cookies.push_back(controller.subscribe_event(vm_event_t::thread_begin, [](vm_event_t, vm_event_context_t &cxt) | |
| 875 | + _event_cookies.push_back(controller->subscribe_event(vm_event_t::thread_begin, [](vm_event_t, vm_event_context_t &cxt) | |
| 755 | 876 | { |
| 756 | 877 | auto t = cxt.value1.thread; |
| 757 | 878 | auto ss = std::stringstream{}; |
| @@ -765,7 +886,7 @@ void debugger::on_load(vm_t &vm, vm_tool_controller_t &controller, std::size_t t · 7 unmodified lines | ||
| 765 | 886 | debug_print_info(ss.str()); |
| 766 | 887 | })); |
| 767 | 888 | |
| 768 | − _event_cookies.push_back(controller.subscribe_event(vm_event_t::thread_end, [](vm_event_t, vm_event_context_t &cxt) | |
| 889 | + _event_cookies.push_back(controller->subscribe_event(vm_event_t::thread_end, [](vm_event_t, vm_event_context_t &cxt) | |
| 769 | 890 | { |
| 770 | 891 | auto ss = std::stringstream{}; |
| 771 | 892 | ss << "Thread " |
| @@ -775,7 +896,7 @@ void debugger::on_load(vm_t &vm, vm_tool_controller_t &controller, std::size_t t · 3 unmodified lines | ||
| 775 | 896 | debug_print_info(ss.str()); |
| 776 | 897 | })); |
| 777 | 898 | |
| 778 | − _event_cookies.push_back(controller.subscribe_event(vm_event_t::exception_raised, [](vm_event_t, vm_event_context_t &cxt) | |
| 899 | + _event_cookies.push_back(controller->subscribe_event(vm_event_t::exception_raised, [](vm_event_t, vm_event_context_t &cxt) | |
| 779 | 900 | { |
| 780 | 901 | auto ss = std::stringstream{}; |
| 781 | 902 | ss << "Exception '" |
| @@ -785,7 +906,7 @@ void debugger::on_load(vm_t &vm, vm_tool_controller_t &controller, std::size_t t · 3 unmodified lines | ||
| 785 | 906 | debug_print_info(ss.str()); |
| 786 | 907 | })); |
| 787 | 908 | |
| 788 | − _event_cookies.push_back(controller.subscribe_event(vm_event_t::exception_unhandled, [&](vm_event_t, vm_event_context_t &cxt) | |
| 909 | + _event_cookies.push_back(controller->subscribe_event(vm_event_t::exception_unhandled, [&](vm_event_t, vm_event_context_t &cxt) | |
| 789 | 910 | { |
| 790 | 911 | auto ss = std::stringstream{}; |
| 791 | 912 | ss << "Unhandled exception '" |
| @@ -793,37 +914,46 @@ void debugger::on_load(vm_t &vm, vm_tool_controller_t &controller, std::size_t t · 1 unmodified line | ||
| 793 | 914 | << "'"; |
| 794 | 915 | |
| 795 | 916 | debug_print_error(ss.str()); |
| 796 | − prompt(*_vm, *cxt.value1.registers); | |
| 917 | + prompt(*this, *cxt.value1.registers); | |
| 797 | 918 | })); |
| 798 | 919 | |
| 799 | − _event_cookies.push_back(controller.subscribe_event(vm_event_t::module_load, [](vm_event_t, vm_event_context_t &cxt) | |
| 920 | + _event_cookies.push_back(controller->subscribe_event(vm_event_t::module_thread_load, [&](vm_event_t, vm_event_context_t &cxt) | |
| 800 | 921 | { |
| 801 | − auto m = (*cxt.value1.module); | |
| 922 | + auto m = (*cxt.value2.module); | |
| 802 | 923 | auto ss = std::stringstream{}; |
| 803 | 924 | ss << "Module " |
| 804 | 925 | << m->module_name->str() |
| 805 | − << " loaded ("; | |
| 926 | + << " loaded"; | |
| 806 | 927 | |
| 807 | − if (!util::has_flag(m->header.runtime_flag, runtime_flags_t::builtin)) | |
| 808 | − ss << cxt.value2.str->str(); | |
| 809 | − else | |
| 810 | − ss << "built-in"; | |
| 928 | + if (util::has_flag(m->header.runtime_flag, runtime_flags_t::builtin)) | |
| 929 | + ss << " (built-in)"; | |
| 811 | 930 | |
| 812 | − ss << ")"; | |
| 813 | 931 | |
| 814 | 932 | debug_print_info(ss.str()); |
| 815 | 933 | })); |
| 816 | 934 | |
| 817 | − _controller = &controller; | |
| 935 | + _event_cookies.push_back(controller->subscribe_event(vm_event_t::breakpoint, [&](vm_event_t, vm_event_context_t &cxt) | |
| 936 | + { | |
| 937 | + auto ss = std::stringstream{}; | |
| 938 | + ss << "Breakpoint hit"; | |
| 939 | + if (cxt.value2.cookie != 0) | |
| 940 | + ss << " [" << cxt.value2.cookie << "]"; | |
| 941 | + | |
| 942 | + debug_print_info(ss.str()); | |
| 943 | + | |
| 944 | + auto reg_str = registers_to_string(*cxt.value1.registers); | |
| 945 | + debug_print_info(reg_str); | |
| 946 | + | |
| 947 | + prompt(*this, *cxt.value1.registers); | |
| 948 | + })); | |
| 818 | 949 | } |
| 819 | 950 | |
| 820 | 951 | void debugger::on_unload() |
| 821 | 952 | { |
| 822 | − assert(_controller != nullptr); | |
| 953 | + assert(controller != nullptr); | |
| 823 | 954 | |
| 824 | 955 | for (auto ec : _event_cookies) |
| 825 | − _controller->unsubscribe_event(ec); | |
| 956 | + controller->unsubscribe_event(ec); | |
| 826 | 957 | |
| 827 | − _controller = nullptr; | |
| 828 | − _vm = nullptr; | |
| 958 | + controller = nullptr; | |
| 829 | 959 | } |
| @@ -10,20 +10,25 @@ · 9 unmodified lines | ||
| 10 | 10 | #include <cstdint> |
| 11 | 11 | #include <vector> |
| 12 | 12 | #include <disvm.h> |
| 13 | +#include <vm_tools.h> | |
| 13 | 14 | |
| 14 | 15 | class debugger final : public disvm::runtime::vm_tool_t |
| 15 | 16 | { |
| 17 | +public: | |
| 18 | + debugger(); | |
| 19 | + | |
| 16 | 20 | public: // vm_tool_t |
| 17 | − void on_load(disvm::vm_t &vm, disvm::runtime::vm_tool_controller_t &controller, std::size_t tool_id); | |
| 21 | + void on_load(disvm::runtime::vm_tool_controller_t &controller, std::size_t tool_id) override; | |
| 22 | + | |
| 23 | + void on_unload() override; | |
| 18 | 24 | |
| 19 | − void on_unload(); | |
| 25 | +public: | |
| 26 | + disvm::runtime::vm_tool_controller_t *controller; | |
| 27 | + std::vector<disvm::runtime::cookie_t> breakpoint_cookies; | |
| 20 | 28 | |
| 21 | 29 | private: |
| 22 | − disvm::vm_t *_vm; | |
| 23 | 30 | std::size_t _tool_id; |
| 24 | − disvm::runtime::vm_tool_controller_t *_controller; | |
| 25 | − std::vector<std::size_t> _event_cookies; | |
| 26 | − std::vector<std::size_t> _breakpoint_cookies; | |
| 31 | + std::vector<disvm::runtime::cookie_t> _event_cookies; | |
| 27 | 32 | }; |
| 28 | 33 | |
| 29 | 34 | #endif // _DISVM_SRC_EXEC_EXEC_H_ |
| @@ -39,6 +39,8 @@ namespace disvm · 38 unmodified lines | ||
| 39 | 39 | class loaded_vm_module_t final |
| 40 | 40 | { |
| 41 | 41 | public: |
| 42 | + std::size_t load_id; | |
| 43 | + | |
| 42 | 44 | // Origin of module |
| 43 | 45 | std::unique_ptr<runtime::vm_string_t> origin; |
| 44 | 46 | |
| @@ -46,7 +48,8 @@ namespace disvm · 1 unmodified line | ||
| 46 | 48 | std::weak_ptr<runtime::vm_module_t> module; |
| 47 | 49 | }; |
| 48 | 50 | |
| 49 | − using loaded_vm_module_callback_t = std::function<void(const loaded_vm_module_t&)>; | |
| 51 | + // Callback for enumerating loaded modules - return 'false' to stop enumeration, otherwise 'true' | |
| 52 | + using loaded_vm_module_callback_t = std::function<bool(const loaded_vm_module_t&)>; | |
| 50 | 53 | |
| 51 | 54 | // Virtual machine |
| 52 | 55 | class vm_t final |
| @@ -824,13 +824,12 @@ namespace disvm · 823 unmodified lines | ||
| 824 | 824 | virtual void set_tool_dispatch_on_all_threads(vm_tool_dispatch_t *dispatch) = 0; |
| 825 | 825 | }; |
| 826 | 826 | |
| 827 | + // The supplied allocation is guaranteed to be valid for the lifetime of the callback. | |
| 828 | + using vm_alloc_callback_t = std::function<void(const vm_alloc_t *)>; | |
| 829 | + | |
| 827 | 830 | // VM garbage collector interface |
| 828 | 831 | class vm_garbage_collector_t |
| 829 | 832 | { |
| 830 | − public: | |
| 831 | − // The supplied allocation is guaranteed to be valid for the lifetime of the callback. | |
| 832 | − using vm_alloc_callback_t = std::function<void(const vm_alloc_t *)>; | |
| 833 | − | |
| 834 | 833 | public: |
| 835 | 834 | virtual ~vm_garbage_collector_t() = 0; |
| 836 | 835 | |
| @@ -862,7 +861,7 @@ namespace disvm · 25 unmodified lines | ||
| 862 | 861 | virtual ~vm_tool_t() = 0; |
| 863 | 862 | |
| 864 | 863 | // Called when the tool is loaded into the VM |
| 865 | − virtual void on_load(vm_t &vm, vm_tool_controller_t &controller, std::size_t tool_id) = 0; | |
| 864 | + virtual void on_load(vm_tool_controller_t &controller, std::size_t tool_id) = 0; | |
| 866 | 865 | |
| 867 | 866 | // Called when the tool is unloaded from the VM. |
| 868 | 867 | // Note once the tool has been unloaded the vm and controller references supplied during load |
| @@ -70,10 +70,11 @@ namespace disvm · 69 unmodified lines | ||
| 70 | 70 | // Increment the ref count of all points in the supplied memory allocation |
| 71 | 71 | void inc_ref_count_in_memory(const type_descriptor_t &type_desc, void *data); |
| 72 | 72 | |
| 73 | − // Decrement the ref count and if 0 free the allocation. | |
| 73 | + // Decrement the ref count and if 0 free the allocation | |
| 74 | 74 | void dec_ref_count_and_free(vm_alloc_t *alloc); |
| 75 | 75 | |
| 76 | − using pointer_field_callback_t = std::function<void(pointer_t)>; | |
| 76 | + // Callback for a pointer field supplying the pointer and byte offset | |
| 77 | + using pointer_field_callback_t = std::function<void(pointer_t, std::size_t)>; | |
| 77 | 78 | |
| 78 | 79 | // The callback will be called on all non-null pointer fields. |
| 79 | 80 | inline void enum_pointer_fields(const type_descriptor_t &type_desc, void *data, pointer_field_callback_t callback) |
| @@ -82,8 +83,9 @@ namespace disvm · 2 unmodified lines | ||
| 82 | 83 | if (type_desc.size_in_bytes == 0) |
| 83 | 84 | return; |
| 84 | 85 | |
| 86 | + auto offset_accum = std::size_t{ 0 }; | |
| 85 | 87 | auto memory = reinterpret_cast<word_t *>(data); |
| 86 | − for (auto i = word_t{ 0 }; i < type_desc.map_in_bytes; ++i, memory += 8) | |
| 88 | + for (auto i = word_t{ 0 }; i < type_desc.map_in_bytes; ++i, memory += 8, offset_accum += 8) | |
| 87 | 89 | { |
| 88 | 90 | const auto words8 = type_desc.pointer_map[i]; |
| 89 | 91 | if (words8 != 0) |
| @@ -91,14 +93,14 @@ namespace disvm · 1 unmodified line | ||
| 91 | 93 | const auto flags = std::bitset<sizeof(words8) * 8>{ words8 }; |
| 92 | 94 | |
| 93 | 95 | // Enumerating the flags in reverse order so memory access is sequential. |
| 94 | − if (flags[7] && (memory[0] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[0])); | |
| 95 | − if (flags[6] && (memory[1] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[1])); | |
| 96 | − if (flags[5] && (memory[2] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[2])); | |
| 97 | − if (flags[4] && (memory[3] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[3])); | |
| 98 | − if (flags[3] && (memory[4] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[4])); | |
| 99 | − if (flags[2] && (memory[5] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[5])); | |
| 100 | − if (flags[1] && (memory[6] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[6])); | |
| 101 | − if (flags[0] && (memory[7] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[7])); | |
| 96 | + if (flags[7] && (memory[0] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[0]), offset_accum); | |
| 97 | + if (flags[6] && (memory[1] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[1]), offset_accum + (1 * sizeof(pointer_t))); | |
| 98 | + if (flags[5] && (memory[2] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[2]), offset_accum + (2 * sizeof(pointer_t))); | |
| 99 | + if (flags[4] && (memory[3] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[3]), offset_accum + (3 * sizeof(pointer_t))); | |
| 100 | + if (flags[3] && (memory[4] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[4]), offset_accum + (4 * sizeof(pointer_t))); | |
| 101 | + if (flags[2] && (memory[5] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[5]), offset_accum + (5 * sizeof(pointer_t))); | |
| 102 | + if (flags[1] && (memory[6] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[6]), offset_accum + (6 * sizeof(pointer_t))); | |
| 103 | + if (flags[0] && (memory[7] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[7]), offset_accum + (7 * sizeof(pointer_t))); | |
| 102 | 104 | } |
| 103 | 105 | } |
| 104 | 106 | } |
| @@ -17,10 +17,12 @@ namespace disvm · 16 unmodified lines | ||
| 17 | 17 | { |
| 18 | 18 | namespace runtime |
| 19 | 19 | { |
| 20 | + using cookie_t = std::size_t; | |
| 21 | + | |
| 20 | 22 | // Value in event context |
| 21 | 23 | union vm_context_value_t |
| 22 | 24 | { |
| 23 | − std::size_t i; | |
| 25 | + cookie_t cookie; | |
| 24 | 26 | std::shared_ptr<vm_module_t> *module; // Note this is a pointer to std::shared_ptr<> |
| 25 | 27 | vm_registers_t *registers; |
| 26 | 28 | const vm_thread_t *thread; |
| @@ -39,8 +41,11 @@ namespace disvm · 12 unmodified lines | ||
| 39 | 41 | // List of possible events |
| 40 | 42 | enum class vm_event_t |
| 41 | 43 | { |
| 42 | − module_load, // value1: std::shared_ptr<vm_module_t> | |
| 43 | − // value2: const vm_string_t - path | |
| 44 | + module_vm_load, // value1: std::shared_ptr<vm_module_t> | |
| 45 | + // value2: const vm_string_t - path on disk | |
| 46 | + | |
| 47 | + module_thread_load, // value1: vm_registers_t | |
| 48 | + // value2: std::shared_ptr<vm_module_t> | |
| 44 | 49 | |
| 45 | 50 | thread_begin, // value1: const vm_thread_t |
| 46 | 51 | thread_end, // value1: const vm_thread_t |
| @@ -54,22 +59,32 @@ namespace disvm · 7 unmodified lines | ||
| 54 | 59 | // value3: const vm_alloc_t - object raised |
| 55 | 60 | |
| 56 | 61 | breakpoint, // value1: vm_registers_t |
| 57 | − // value2: std::size_t - cookie of breakpoint. '0' indicates the breakpoint was inserted by the VM or compiler. | |
| 62 | + // value2: cookie_t - cookie of breakpoint. '0' indicates the breakpoint was inserted by the VM or compiler. | |
| 58 | 63 | }; |
| 59 | 64 | |
| 60 | 65 | using vm_event_callback_t = std::function<void(vm_event_t, vm_event_context_t &)>; |
| 61 | 66 | |
| 67 | + struct breakpoint_details_t final | |
| 68 | + { | |
| 69 | + std::shared_ptr<vm_module_t> module; | |
| 70 | + vm_pc_t pc; | |
| 71 | + }; | |
| 72 | + | |
| 62 | 73 | // VM tool controller |
| 63 | 74 | class vm_tool_controller_t |
| 64 | 75 | { |
| 65 | 76 | public: |
| 77 | + // The current vm instance | |
| 78 | + virtual vm_t& get_vm_instance() const = 0; | |
| 79 | + | |
| 66 | 80 | // Call the supplied callback when the specified event occurs |
| 67 | − virtual std::size_t subscribe_event(vm_event_t, vm_event_callback_t) = 0; | |
| 68 | − virtual void unsubscribe_event(std::size_t) = 0; | |
| 81 | + virtual cookie_t subscribe_event(vm_event_t, vm_event_callback_t) = 0; | |
| 82 | + virtual void unsubscribe_event(cookie_t) = 0; | |
| 69 | 83 | |
| 70 | 84 | // Set a breakpoint in the supplied module at the specified PC location |
| 71 | − virtual std::size_t set_breakpoint(std::shared_ptr<vm_module_t>, vm_pc_t) = 0; | |
| 72 | − virtual void clear_breakpoint(std::size_t) = 0; | |
| 85 | + virtual cookie_t set_breakpoint(std::shared_ptr<vm_module_t>, vm_pc_t) = 0; | |
| 86 | + virtual breakpoint_details_t get_breakpoint_details(cookie_t) const = 0; | |
| 87 | + virtual void clear_breakpoint(cookie_t) = 0; | |
| 73 | 88 | }; |
| 74 | 89 | } |
| 75 | 90 | } |
| @@ -1750,12 +1750,13 @@ namespace · 1749 unmodified lines | ||
| 1750 | 1750 | dec_ref_count_and_free(at_val<vm_alloc_t>(r.dest)); |
| 1751 | 1751 | pt_ref(r.dest) = nullptr; |
| 1752 | 1752 | |
| 1753 | − auto imported_module = std::shared_ptr<const vm_module_t>{}; | |
| 1753 | + auto imported_module = std::shared_ptr<vm_module_t>{}; | |
| 1754 | 1754 | |
| 1755 | 1755 | // Handle self loading module optimization. |
| 1756 | 1756 | if (std::strcmp(str->str(), "$self") == 0) |
| 1757 | 1757 | { |
| 1758 | − imported_module = r.module_ref->module; | |
| 1758 | + // Remove 'const' from the module type in the shared_ptr<> | |
| 1759 | + imported_module = std::const_pointer_cast<vm_module_t>(r.module_ref->module); | |
| 1759 | 1760 | } |
| 1760 | 1761 | else |
| 1761 | 1762 | { |
| @@ -1779,6 +1780,11 @@ namespace · 17 unmodified lines | ||
| 1779 | 1780 | |
| 1780 | 1781 | auto entry_module_ref = new vm_module_ref_t{ imported_module, function_imports }; |
| 1781 | 1782 | pt_ref(r.dest) = entry_module_ref->get_allocation(); |
| 1783 | + | |
| 1784 | + // Check if the tool dispatcher has been supplied | |
| 1785 | + auto tool_dispatch = r.tool_dispatch.load(); | |
| 1786 | + if (tool_dispatch != nullptr) | |
| 1787 | + tool_dispatch->on_module_thread_load(r, imported_module); | |
| 1782 | 1788 | } |
| 1783 | 1789 | } |
| 1784 | 1790 | |
| @@ -22,7 +22,7 @@ vm_garbage_collector_t::~vm_garbage_collector_t() · 21 unmodified lines | ||
| 22 | 22 | |
| 23 | 23 | std::unique_ptr<vm_garbage_collector_t> disvm::runtime::create_no_op_gc(vm_t &) |
| 24 | 24 | { |
| 25 | − class no_op_gc : public vm_garbage_collector_t | |
| 25 | + class no_op_gc final : public vm_garbage_collector_t | |
| 26 | 26 | { |
| 27 | 27 | public: |
| 28 | 28 | void track_allocation(vm_alloc_t *) override |
| @@ -118,7 +118,7 @@ namespace · 89 unmodified lines | ||
| 118 | 118 | { |
| 119 | 119 | std::queue<vm_alloc_t *> allocs; |
| 120 | 120 | |
| 121 | − auto add_pointer_to_queue = std::function<void(pointer_t p)>{ [&allocs](pointer_t p) -> void | |
| 121 | + auto add_pointer_to_queue = std::function<void(pointer_t p, std::size_t)>{ [&allocs](pointer_t p, std::size_t) -> void | |
| 122 | 122 | { |
| 123 | 123 | auto a = vm_alloc_t::from_allocation(p); |
| 124 | 124 | allocs.push(a); |
| @@ -155,7 +155,7 @@ namespace · 30 unmodified lines | ||
| 155 | 155 | debug::log_msg(debug::component_trace_t::garbage_collector, debug::log_level_t::debug, "gc: roots found: %" PRIuPTR "\n", allocs.size()); |
| 156 | 156 | |
| 157 | 157 | // If logging is enabled update the callback with logging |
| 158 | − add_pointer_to_queue = std::function<void(pointer_t p)>{ [&allocs](pointer_t p) -> void | |
| 158 | + add_pointer_to_queue = std::function<void(pointer_t p, std::size_t)>{ [&allocs](pointer_t p, std::size_t) -> void | |
| 159 | 159 | { |
| 160 | 160 | auto a = vm_alloc_t::from_allocation(p); |
| 161 | 161 | debug::log_msg(debug::component_trace_t::garbage_collector, debug::log_level_t::debug, "\tref: %#" PRIxPTR "\n", a); |
| @@ -26,11 +26,11 @@ namespace disvm · 25 unmodified lines | ||
| 26 | 26 | ~default_garbage_collector_t(); |
| 27 | 27 | |
| 28 | 28 | public: // vm_garbage_collector_t |
| 29 | − void track_allocation(vm_alloc_t *alloc) override final; | |
| 29 | + void track_allocation(vm_alloc_t *alloc) override; | |
| 30 | 30 | |
| 31 | − void enum_tracked_allocations(vm_alloc_callback_t callback) const override final; | |
| 31 | + void enum_tracked_allocations(vm_alloc_callback_t callback) const override; | |
| 32 | 32 | |
| 33 | − bool collect(std::vector<std::shared_ptr<const vm_thread_t>> &threads) override final; | |
| 33 | + bool collect(std::vector<std::shared_ptr<const vm_thread_t>> &threads) override; | |
| 34 | 34 | |
| 35 | 35 | private: |
| 36 | 36 | vm_t &_vm; |
| @@ -34,18 +34,18 @@ namespace disvm · 33 unmodified lines | ||
| 34 | 34 | ~default_scheduler_t(); |
| 35 | 35 | |
| 36 | 36 | public: // vm_scheduler_t |
| 37 | − bool is_idle() const override final; | |
| 37 | + bool is_idle() const override; | |
| 38 | 38 | |
| 39 | − vm_scheduler_control_t &get_controller() const override final; | |
| 39 | + vm_scheduler_control_t &get_controller() const override; | |
| 40 | 40 | |
| 41 | − const vm_thread_t& schedule_thread(std::unique_ptr<vm_thread_t> thread) override final; | |
| 41 | + const vm_thread_t& schedule_thread(std::unique_ptr<vm_thread_t> thread) override; | |
| 42 | 42 | |
| 43 | − void set_tool_dispatch_on_all_threads(vm_tool_dispatch_t *dispatch) override final; | |
| 43 | + void set_tool_dispatch_on_all_threads(vm_tool_dispatch_t *dispatch) override; | |
| 44 | 44 | |
| 45 | 45 | public: // vm_scheduler_control_t |
| 46 | − void enqueue_blocked_thread(uint32_t thread_id) override final; | |
| 46 | + void enqueue_blocked_thread(uint32_t thread_id) override; | |
| 47 | 47 | |
| 48 | − std::vector<std::shared_ptr<const vm_thread_t>> get_all_threads() const override final; | |
| 48 | + std::vector<std::shared_ptr<const vm_thread_t>> get_all_threads() const override; | |
| 49 | 49 | |
| 50 | 50 | private: |
| 51 | 51 | struct thread_instance_t final |
| @@ -50,7 +50,7 @@ std::size_t vm_tool_dispatch_t::load_tool(std::shared_ptr<runtime::vm_tool_t> to · 49 unmodified lines | ||
| 50 | 50 | { |
| 51 | 51 | // [TODO] For debug builds, it might be useful to wrap the controller in a proxy that records |
| 52 | 52 | // the subscribed callbacks and asserts they are all properly unsubscribed during agent unload. |
| 53 | − tool->on_load(_vm, *this, current_id); | |
| 53 | + tool->on_load(*this, current_id); | |
| 54 | 54 | } |
| 55 | 55 | catch (...) |
| 56 | 56 | { |
| @@ -91,12 +91,12 @@ std::size_t vm_tool_dispatch_t::unload_tool(std::size_t tool_id) · 34 unmodified lines | ||
| 91 | 91 | opcode_t vm_tool_dispatch_t::on_breakpoint(vm_registers_t &r) |
| 92 | 92 | { |
| 93 | 93 | opcode_t original_op; |
| 94 | − std::size_t cookie_id; | |
| 95 | − std::tie(original_op, cookie_id) = get_original_opcode(r); | |
| 94 | + cookie_t cookie_id; | |
| 95 | + std::tie(original_op, cookie_id) = get_original_opcode_and_cookie(r); | |
| 96 | 96 | |
| 97 | 97 | vm_event_context_t cxt{}; |
| 98 | 98 | cxt.value1.registers = &r; |
| 99 | − cxt.value2.i = cookie_id; | |
| 99 | + cxt.value2.cookie = cookie_id; | |
| 100 | 100 | |
| 101 | 101 | _events.fire_event_callbacks(vm_event_t::breakpoint, cxt); |
| 102 | 102 | |
| @@ -123,7 +123,7 @@ void vm_tool_dispatch_t::on_exception_unhandled(vm_registers_t &r, const vm_stri · 20 unmodified lines | ||
| 123 | 123 | _events.fire_event_callbacks(vm_event_t::exception_unhandled, cxt); |
| 124 | 124 | } |
| 125 | 125 | |
| 126 | −void vm_tool_dispatch_t::on_module_load(std::shared_ptr<vm_module_t> &m, const vm_string_t &path) | |
| 126 | +void vm_tool_dispatch_t::on_module_vm_load(std::shared_ptr<vm_module_t> &m, const vm_string_t &path) | |
| 127 | 127 | { |
| 128 | 128 | assert(m != nullptr); |
| 129 | 129 | |
| @@ -131,7 +131,18 @@ void vm_tool_dispatch_t::on_module_load(std::shared_ptr<vm_module_t> &m, const v · 1 unmodified line | ||
| 131 | 131 | cxt.value1.module = &m; |
| 132 | 132 | cxt.value2.str = &path; |
| 133 | 133 | |
| 134 | − _events.fire_event_callbacks(vm_event_t::module_load, cxt); | |
| 134 | + _events.fire_event_callbacks(vm_event_t::module_vm_load, cxt); | |
| 135 | +} | |
| 136 | + | |
| 137 | +void vm_tool_dispatch_t::on_module_thread_load(vm_registers_t &r, std::shared_ptr<vm_module_t> &m) | |
| 138 | +{ | |
| 139 | + assert(m != nullptr); | |
| 140 | + | |
| 141 | + vm_event_context_t cxt{}; | |
| 142 | + cxt.value1.registers = &r; | |
| 143 | + cxt.value2.module = &m; | |
| 144 | + | |
| 145 | + _events.fire_event_callbacks(vm_event_t::module_thread_load, cxt); | |
| 135 | 146 | } |
| 136 | 147 | |
| 137 | 148 | void vm_tool_dispatch_t::on_thread_begin(vm_thread_t &t) |
| @@ -150,7 +161,12 @@ void vm_tool_dispatch_t::on_thread_end(vm_thread_t &t) · 12 unmodified lines | ||
| 150 | 161 | _events.fire_event_callbacks(vm_event_t::thread_end, cxt); |
| 151 | 162 | } |
| 152 | 163 | |
| 153 | −std::size_t vm_tool_dispatch_t::subscribe_event(vm_event_t evt, vm_event_callback_t cb) | |
| 164 | +vm_t& vm_tool_dispatch_t::get_vm_instance() const | |
| 165 | +{ | |
| 166 | + return _vm; | |
| 167 | +} | |
| 168 | + | |
| 169 | +cookie_t vm_tool_dispatch_t::subscribe_event(vm_event_t evt, vm_event_callback_t cb) | |
| 154 | 170 | { |
| 155 | 171 | if (!cb) |
| 156 | 172 | throw vm_system_exception{ "Invalid callback for event" }; |
| @@ -172,7 +188,7 @@ std::size_t vm_tool_dispatch_t::subscribe_event(vm_event_t evt, vm_event_callbac · 15 unmodified lines | ||
| 172 | 188 | return cookie_id; |
| 173 | 189 | } |
| 174 | 190 | |
| 175 | −void vm_tool_dispatch_t::unsubscribe_event(std::size_t cookie_id) | |
| 191 | +void vm_tool_dispatch_t::unsubscribe_event(cookie_t cookie_id) | |
| 176 | 192 | { |
| 177 | 193 | std::lock_guard<std::mutex> lock{ _events.lock }; |
| 178 | 194 | |
| @@ -190,7 +206,7 @@ void vm_tool_dispatch_t::unsubscribe_event(std::size_t cookie_id) · 11 unmodified lines | ||
| 190 | 206 | debug::log_msg(debug::component_trace_t::tool, debug::log_level_t::debug, "unsubscribe: event: %d %d", evt, cookie_id); |
| 191 | 207 | } |
| 192 | 208 | |
| 193 | −std::size_t vm_tool_dispatch_t::set_breakpoint(std::shared_ptr<vm_module_t> module, vm_pc_t pc) | |
| 209 | +cookie_t vm_tool_dispatch_t::set_breakpoint(std::shared_ptr<vm_module_t> module, vm_pc_t pc) | |
| 194 | 210 | { |
| 195 | 211 | if (module == nullptr || util::has_flag(module->header.runtime_flag, runtime_flags_t::builtin)) |
| 196 | 212 | throw vm_system_exception{ "Unable to set breakpoint in supplied module" }; |
| @@ -209,7 +225,7 @@ std::size_t vm_tool_dispatch_t::set_breakpoint(std::shared_ptr<vm_module_t> modu · 12 unmodified lines | ||
| 209 | 225 | const auto cookie_id = _breakpoints.cookie_counter++; |
| 210 | 226 | |
| 211 | 227 | // Map the cookie to the module/pc pair. |
| 212 | − _breakpoints.cookie_to_modulepc[cookie_id] = std::make_pair(module, pc); | |
| 228 | + _breakpoints.cookie_to_details[cookie_id] = { module, pc }; | |
| 213 | 229 | |
| 214 | 230 | // Record the original opcode |
| 215 | 231 | auto &pc_map = _breakpoints.original_opcodes[reinterpret_cast<std::uintptr_t>(module.get())]; |
| @@ -225,20 +241,30 @@ std::size_t vm_tool_dispatch_t::set_breakpoint(std::shared_ptr<vm_module_t> modu · 9 unmodified lines | ||
| 225 | 241 | } |
| 226 | 242 | } |
| 227 | 243 | |
| 228 | −void vm_tool_dispatch_t::clear_breakpoint(std::size_t cookie_id) | |
| 244 | +breakpoint_details_t vm_tool_dispatch_t::get_breakpoint_details(cookie_t cookie_id) const | |
| 229 | 245 | { |
| 230 | 246 | std::lock_guard<std::mutex> lock{ _breakpoints.lock }; |
| 231 | 247 | |
| 232 | − // Resolve the cookie to the module/pc pair | |
| 233 | − auto iter_cookie = _breakpoints.cookie_to_modulepc.find(cookie_id); | |
| 234 | − if (iter_cookie == _breakpoints.cookie_to_modulepc.cend()) | |
| 235 | − return; | |
| 248 | + auto iter_cookie = _breakpoints.cookie_to_details.find(cookie_id); | |
| 249 | + if (iter_cookie == _breakpoints.cookie_to_details.cend()) | |
| 250 | + throw vm_system_exception{ "Unknown breakpoint cookie" }; | |
| 251 | + | |
| 252 | + return iter_cookie->second; | |
| 253 | +} | |
| 254 | + | |
| 255 | +void vm_tool_dispatch_t::clear_breakpoint(cookie_t cookie_id) | |
| 256 | +{ | |
| 257 | + std::lock_guard<std::mutex> lock{ _breakpoints.lock }; | |
| 258 | + | |
| 259 | + auto iter_cookie = _breakpoints.cookie_to_details.find(cookie_id); | |
| 260 | + if (iter_cookie == _breakpoints.cookie_to_details.cend()) | |
| 261 | + throw vm_system_exception{ "Unknown breakpoint cookie" }; | |
| 236 | 262 | |
| 237 | − auto mod_pc_pair = iter_cookie->second; | |
| 238 | − const auto target_pc = mod_pc_pair.second; | |
| 263 | + auto details = iter_cookie->second; | |
| 264 | + const auto target_pc = details.pc; | |
| 239 | 265 | |
| 240 | 266 | // Determine the original opcode |
| 241 | − auto iter_orig = _breakpoints.original_opcodes.find(reinterpret_cast<std::uintptr_t>(mod_pc_pair.first.get())); | |
| 267 | + auto iter_orig = _breakpoints.original_opcodes.find(reinterpret_cast<std::uintptr_t>(details.module.get())); | |
| 242 | 268 | assert(iter_orig != _breakpoints.original_opcodes.cend()); |
| 243 | 269 | |
| 244 | 270 | auto &pc_map = iter_orig->second; |
| @@ -248,12 +274,12 @@ void vm_tool_dispatch_t::clear_breakpoint(std::size_t cookie_id) · 3 unmodified lines | ||
| 248 | 274 | auto original_opcode = iter_pc->second.first; |
| 249 | 275 | |
| 250 | 276 | // Replace the breakpoint opcode with the original |
| 251 | − auto &code_section = const_cast<code_section_t &>(mod_pc_pair.first->code_section); | |
| 277 | + auto &code_section = const_cast<code_section_t &>(details.module->code_section); | |
| 252 | 278 | assert(code_section[target_pc].op.opcode == opcode_t::brkpt); |
| 253 | 279 | code_section[target_pc].op.opcode = original_opcode; |
| 254 | 280 | |
| 255 | 281 | if (debug::is_component_tracing_enabled<debug::component_trace_t::tool>()) |
| 256 | − debug::log_msg(debug::component_trace_t::tool, debug::log_level_t::debug, "breakpoint: unset: %d %d >>%s<<", cookie_id, target_pc, mod_pc_pair.first->module_name->str()); | |
| 282 | + debug::log_msg(debug::component_trace_t::tool, debug::log_level_t::debug, "breakpoint: unset: %d %d >>%s<<", cookie_id, target_pc, details.module->module_name->str()); | |
| 257 | 283 | |
| 258 | 284 | // Clean-up the PC mapping to opcode |
| 259 | 285 | pc_map.erase(iter_pc); |
| @@ -263,10 +289,10 @@ void vm_tool_dispatch_t::clear_breakpoint(std::size_t cookie_id) · 3 unmodified lines | ||
| 263 | 289 | _breakpoints.original_opcodes.erase(iter_orig); |
| 264 | 290 | |
| 265 | 291 | // Remove the cookie map |
| 266 | − _breakpoints.cookie_to_modulepc.erase(iter_cookie); | |
| 292 | + _breakpoints.cookie_to_details.erase(iter_cookie); | |
| 267 | 293 | } |
| 268 | 294 | |
| 269 | −std::tuple<opcode_t, std::size_t> vm_tool_dispatch_t::get_original_opcode(const vm_registers_t &r) | |
| 295 | +std::tuple<opcode_t, cookie_t> vm_tool_dispatch_t::get_original_opcode_and_cookie(const vm_registers_t &r) | |
| 270 | 296 | { |
| 271 | 297 | const auto module = r.module_ref->module.get(); |
| 272 | 298 | const auto current_pc = r.pc - 1; |
| @@ -42,8 +42,11 @@ namespace disvm · 41 unmodified lines | ||
| 42 | 42 | // Callback on an unhandled exception |
| 43 | 43 | void on_exception_unhandled(vm_registers_t &r, const vm_string_t &id, vm_alloc_t &e); |
| 44 | 44 | |
| 45 | − // Callback on module load | |
| 46 | − void on_module_load(std::shared_ptr<vm_module_t> &m, const vm_string_t &path); | |
| 45 | + // Callback for when the VM loads a module from disk | |
| 46 | + void on_module_vm_load(std::shared_ptr<vm_module_t> &m, const vm_string_t &path); | |
| 47 | + | |
| 48 | + // Callback for when a thread loads a module into its scope | |
| 49 | + void on_module_thread_load(vm_registers_t &r, std::shared_ptr<vm_module_t> &m); | |
| 47 | 50 | |
| 48 | 51 | // Callback when thread lifetime begins. |
| 49 | 52 | // The thread has not been scheduled and no opcodes have been executed. |
| @@ -53,14 +56,17 @@ namespace disvm · 3 unmodified lines | ||
| 53 | 56 | void on_thread_end(vm_thread_t &t); |
| 54 | 57 | |
| 55 | 58 | public: // vm_tool_controller_t |
| 56 | − std::size_t subscribe_event(vm_event_t evt, vm_event_callback_t cb) override final; | |
| 57 | − void unsubscribe_event(std::size_t cookie_id) override final; | |
| 59 | + vm_t& get_vm_instance() const override; | |
| 60 | + | |
| 61 | + cookie_t subscribe_event(vm_event_t evt, vm_event_callback_t cb) override; | |
| 62 | + void unsubscribe_event(cookie_t cookie_id) override; | |
| 58 | 63 | |
| 59 | − std::size_t set_breakpoint(std::shared_ptr<vm_module_t> module, vm_pc_t pc) override final; | |
| 60 | − void clear_breakpoint(std::size_t cookie_id) override final; | |
| 64 | + cookie_t set_breakpoint(std::shared_ptr<vm_module_t> module, vm_pc_t pc) override; | |
| 65 | + breakpoint_details_t get_breakpoint_details(cookie_t) const override; | |
| 66 | + void clear_breakpoint(cookie_t cookie_id) override; | |
| 61 | 67 | |
| 62 | 68 | private: |
| 63 | − std::tuple<opcode_t, std::size_t> get_original_opcode(const vm_registers_t &r); | |
| 69 | + std::tuple<opcode_t, cookie_t> get_original_opcode_and_cookie(const vm_registers_t &r); | |
| 64 | 70 | |
| 65 | 71 | private: |
| 66 | 72 | vm_t &_vm; |
| @@ -73,28 +79,28 @@ namespace disvm · 6 unmodified lines | ||
| 73 | 79 | { |
| 74 | 80 | void fire_event_callbacks(vm_event_t, vm_event_context_t &); |
| 75 | 81 | |
| 76 | − using cookie_to_callback_map_t = std::unordered_map<std::size_t, vm_event_callback_t>; | |
| 82 | + using cookie_to_callback_map_t = std::unordered_map<cookie_t, vm_event_callback_t>; | |
| 77 | 83 | using event_to_callbacks_map_t = std::unordered_map<vm_event_t, cookie_to_callback_map_t>; |
| 78 | 84 | event_to_callbacks_map_t callbacks; |
| 79 | 85 | |
| 80 | − using cookie_to_event_map_t = std::unordered_map<std::size_t, vm_event_t>; | |
| 86 | + using cookie_to_event_map_t = std::unordered_map<cookie_t, vm_event_t>; | |
| 81 | 87 | cookie_to_event_map_t cookie_to_event; |
| 82 | 88 | |
| 83 | − std::mutex lock; | |
| 84 | − std::size_t cookie_counter; | |
| 89 | + mutable std::mutex lock; | |
| 90 | + cookie_t cookie_counter; | |
| 85 | 91 | } _events; |
| 86 | 92 | |
| 87 | 93 | struct |
| 88 | 94 | { |
| 89 | − using cookie_to_modulepc_t = std::unordered_map<std::size_t, std::pair<std::shared_ptr<const vm_module_t>, vm_pc_t>>; | |
| 90 | − cookie_to_modulepc_t cookie_to_modulepc; | |
| 95 | + using cookie_to_details_t = std::unordered_map<cookie_t, breakpoint_details_t>; | |
| 96 | + cookie_to_details_t cookie_to_details; | |
| 91 | 97 | |
| 92 | − using pc_to_opcode_cookie_map_t = std::unordered_map<vm_pc_t, std::pair<opcode_t, std::size_t>>; | |
| 98 | + using pc_to_opcode_cookie_map_t = std::unordered_map<vm_pc_t, std::pair<opcode_t, cookie_t>>; | |
| 93 | 99 | using module_to_pc_map_t = std::unordered_map<std::uintptr_t, pc_to_opcode_cookie_map_t>; |
| 94 | 100 | module_to_pc_map_t original_opcodes; |
| 95 | 101 | |
| 96 | − std::mutex lock; | |
| 97 | − std::size_t cookie_counter; | |
| 102 | + mutable std::mutex lock; | |
| 103 | + cookie_t cookie_counter; | |
| 98 | 104 | } _breakpoints; |
| 99 | 105 | }; |
| 100 | 106 | } |
| @@ -126,53 +126,63 @@ std::shared_ptr<runtime::vm_module_t> vm_t::load_module(const char *path) · 125 unmodified lines | ||
| 126 | 126 | if (path == nullptr) |
| 127 | 127 | throw vm_user_exception{ "Invalid module path" }; |
| 128 | 128 | |
| 129 | − std::lock_guard<std::mutex> lock{ _modules_lock }; | |
| 130 | − | |
| 131 | − // Check if the module is already loaded. | |
| 132 | − auto iter = std::begin(_modules); | |
| 133 | − while (iter != std::end(_modules)) | |
| 129 | + auto new_module_iter = loaded_modules_t::const_iterator{}; | |
| 130 | + auto new_module = std::shared_ptr<vm_module_t>{}; | |
| 134 | 131 | { |
| 135 | − if (0 == ::strcmp(iter->origin->str(), path)) | |
| 136 | − { | |
| 137 | − auto module = iter->module.lock(); | |
| 132 | + std::lock_guard<std::mutex> lock{ _modules_lock }; | |
| 138 | 133 | |
| 139 | − // If an entry exists but is null, the module was loaded before. Read the module again and return. | |
| 140 | − if (module == nullptr) | |
| 134 | + // Check if the module is already loaded. | |
| 135 | + auto iter = std::begin(_modules); | |
| 136 | + while (iter != std::end(_modules)) | |
| 137 | + { | |
| 138 | + if (0 == ::strcmp(iter->origin->str(), path)) | |
| 141 | 139 | { |
| 142 | − module = std::shared_ptr<vm_module_t>{ std::move(read_module(path)) }; | |
| 143 | − iter->module = module; | |
| 140 | + auto module = iter->module.lock(); | |
| 141 | + | |
| 142 | + // If an entry exists but is null, the module was loaded before. Read the module again and return. | |
| 143 | + if (module == nullptr) | |
| 144 | + { | |
| 145 | + module = std::shared_ptr<vm_module_t>{ std::move(read_module(path)) }; | |
| 146 | + iter->module = module; | |
| 144 | 147 | |
| 145 | − debug::log_msg(debug::component_trace_t::module, debug::log_level_t::debug, "reload: vm module: >>%s<<\n", path); | |
| 148 | + debug::log_msg(debug::component_trace_t::module, debug::log_level_t::debug, "reload: vm module: >>%s<<\n", path); | |
| 149 | + } | |
| 150 | + | |
| 151 | + return module; | |
| 146 | 152 | } |
| 147 | 153 | |
| 148 | − return module; | |
| 154 | + ++iter; | |
| 149 | 155 | } |
| 150 | 156 | |
| 151 | − ++iter; | |
| 152 | − } | |
| 157 | + if (path[0] == BUILTIN_MODULE_PREFIX_CHAR) | |
| 158 | + { | |
| 159 | + new_module = std::move(builtin::get_builtin_module(path)); | |
| 160 | + } | |
| 161 | + else | |
| 162 | + { | |
| 163 | + // Read in the module | |
| 164 | + new_module = std::move(read_module(path)); | |
| 165 | + } | |
| 153 | 166 | |
| 154 | − auto new_module = std::shared_ptr<vm_module_t>{}; | |
| 155 | − if (path[0] == BUILTIN_MODULE_PREFIX_CHAR) | |
| 156 | − { | |
| 157 | − new_module = std::move(builtin::get_builtin_module(path)); | |
| 158 | − } | |
| 159 | − else | |
| 160 | − { | |
| 161 | − // Read in the module | |
| 162 | − new_module = std::move(read_module(path)); | |
| 167 | + auto path_local = std::make_unique<vm_string_t>(std::strlen(path), reinterpret_cast<const uint8_t *>(path)); | |
| 168 | + | |
| 169 | + auto load_id_next = std::size_t{ 0 }; | |
| 170 | + if (!_modules.empty()) | |
| 171 | + load_id_next = (_modules.front().load_id + 1); | |
| 172 | + | |
| 173 | + _modules.push_front(std::move(loaded_vm_module_t{ load_id_next, std::move(path_local), new_module })); | |
| 174 | + new_module_iter = _modules.begin(); | |
| 163 | 175 | } |
| 164 | 176 | |
| 165 | − auto path_local = std::make_unique<vm_string_t>(std::strlen(path), reinterpret_cast<const uint8_t *>(path)); | |
| 166 | 177 | if (_tool_dispatch != nullptr) |
| 167 | 178 | { |
| 168 | 179 | std::lock_guard<std::mutex> lock{ _tool_dispatch_lock }; |
| 169 | 180 | if (_tool_dispatch != nullptr) |
| 170 | − _tool_dispatch->on_module_load(new_module, *path_local); | |
| 181 | + _tool_dispatch->on_module_vm_load(new_module, *new_module_iter->origin); | |
| 171 | 182 | } |
| 172 | 183 | |
| 173 | − _modules.push_front(std::move(loaded_vm_module_t{ std::move(path_local), new_module })); | |
| 174 | − | |
| 175 | − debug::log_msg(debug::component_trace_t::module, debug::log_level_t::debug, "load: vm module: >>%s<<\n", path); | |
| 184 | + if (debug::is_component_tracing_enabled<debug::component_trace_t::module>()) | |
| 185 | + debug::log_msg(debug::component_trace_t::module, debug::log_level_t::debug, "load: vm module: >>%s<<\n", path); | |
| 176 | 186 | |
| 177 | 187 | return new_module; |
| 178 | 188 | } |
| @@ -184,7 +194,10 @@ void vm_t::enum_loaded_modules(loaded_vm_module_callback_t callback) const · 5 unmodified lines | ||
| 184 | 194 | |
| 185 | 195 | std::lock_guard<std::mutex> lock{ _modules_lock }; |
| 186 | 196 | for (auto &m : _modules) |
| 187 | − callback(m); | |
| 197 | + { | |
| 198 | + if (!callback(m)) | |
| 199 | + return; | |
| 200 | + } | |
| 188 | 201 | } |
| 189 | 202 | |
| 190 | 203 | runtime::vm_scheduler_control_t &vm_t::get_scheduler_control() const |
| @@ -170,13 +170,13 @@ void disvm::runtime::init_memory(const type_descriptor_t &type_desc, void *data) · 169 unmodified lines | ||
| 170 | 170 | |
| 171 | 171 | namespace |
| 172 | 172 | { |
| 173 | − void dec_ref_and_free_pointer_field(pointer_t pointer_field) | |
| 173 | + void dec_ref_and_free_pointer_field(pointer_t pointer_field, std::size_t) | |
| 174 | 174 | { |
| 175 | 175 | assert(pointer_field != nullptr); |
| 176 | 176 | dec_ref_count_and_free(vm_alloc_t::from_allocation(pointer_field)); |
| 177 | 177 | } |
| 178 | 178 | |
| 179 | − void add_ref_pointer_field(pointer_t pointer_field) | |
| 179 | + void add_ref_pointer_field(pointer_t pointer_field, std::size_t) | |
| 180 | 180 | { |
| 181 | 181 | assert(pointer_field != nullptr); |
| 182 | 182 | vm_alloc_t::from_allocation(pointer_field)->add_ref(); |