447 lines · 13.9 KB · C++
RawDownload
//// Dis VM// File: tool_dispatch.cpp// Author: arr// #include <cassert>#include <thread>#include <vector>#include <debug.h>#include <disvm.h>#include <vm_tools.h>#include <exceptions.h>#include <utils.h>#include "tool_dispatch.h" using disvm::vm_t;using disvm::opcode_t;using disvm::loaded_vm_module_t; using disvm::debug::component_trace_t;using disvm::debug::log_level_t; using disvm::runtime::cookie_t;using disvm::runtime::vm_pc_t;using disvm::runtime::vm_module_t;using disvm::runtime::vm_thread_t;using disvm::runtime::vm_registers_t;using disvm::runtime::vm_alloc_t;using disvm::runtime::vm_string_t;using disvm::runtime::vm_tool_t;using disvm::runtime::vm_tool_dispatch_t;using disvm::runtime::vm_trap_flags_t;using disvm::runtime::vm_event_t;using disvm::runtime::vm_event_context_t;using disvm::runtime::vm_event_callback_t;using disvm::runtime::breakpoint_details_t; // Empty destructor for vm tool 'interface'vm_tool_t::~vm_tool_t(){} vm_tool_dispatch_t::vm_tool_dispatch_t(vm_t &vm)    : _threads_suspended{ false }    , _threads_suspended_count{ 0 }    , _vm { vm }{    _events.cookie_counter = 1;    _breakpoints.cookie_counter = 1;} vm_tool_dispatch_t::~vm_tool_dispatch_t(){    std::lock_guard<std::mutex> lock{ _tools_lock };    for (auto &t : _tools)        t.second->on_unload();} std::size_t vm_tool_dispatch_t::load_tool(std::shared_ptr<vm_tool_t> tool){    static std::size_t _tool_id_counter{ 1 };     if (tool == nullptr)        throw vm_system_exception{ "Tool cannot be null" };     // Keep the lock for the collection until the tool has been completed being loaded.    std::lock_guard<std::mutex> lock{ _tools_lock };    const auto current_id = _tool_id_counter++;    _tools[current_id] = tool;     try    {        // [TODO] For debug builds, it might be useful to wrap the controller in a proxy that records        // the subscribed callbacks and asserts they are all properly unsubscribed during agent unload.        tool->on_load(*this, current_id);    }    catch (...)    {        if (disvm::debug::is_component_tracing_enabled<component_trace_t::tool>())            disvm::debug::log_msg(component_trace_t::tool, log_level_t::warning, "load: tool: failure");         // The ID is tainted and will not be re-used        _tools.erase(current_id);        throw;    }     if (disvm::debug::is_component_tracing_enabled<component_trace_t::tool>())        disvm::debug::log_msg(component_trace_t::tool, log_level_t::debug, "load: tool: %d", current_id);     return current_id;} std::size_t vm_tool_dispatch_t::unload_tool(std::size_t tool_id){    // Keep the lock for the collection until the tool has been completed being unloaded.    std::lock_guard<std::mutex> lock{ _tools_lock };    auto iter = _tools.find(tool_id);    if (iter == _tools.cend())        return _tools.size();     auto tool = iter->second;    _tools.erase(iter);     if (disvm::debug::is_component_tracing_enabled<component_trace_t::tool>())        disvm::debug::log_msg(component_trace_t::tool, log_level_t::debug, "unload: tool: %d", tool_id);     assert(tool != nullptr);    tool->on_unload();     return _tools.size();} namespace{    DEFINE_ENUM_FLAG_OPERATORS(vm_trap_flags_t);} void vm_tool_dispatch_t::on_trap(vm_registers_t &r, vm_trap_flags_t f){    vm_event_context_t cxt{};    cxt.value1.registers = &r;    cxt.value2.trap = f;     // Clear the trap flag    r.trap_flags &= ~f;    _events.fire_event_callbacks(vm_event_t::trap, cxt);} opcode_t vm_tool_dispatch_t::on_breakpoint(vm_registers_t &r){    opcode_t original_op;    cookie_t cookie_id;    std::tie(original_op, cookie_id) = get_original_opcode_and_cookie(r);     vm_event_context_t cxt{};    cxt.value1.registers = &r;    cxt.value2.cookie = cookie_id;     _events.fire_event_callbacks(vm_event_t::breakpoint, cxt);     return original_op;} void vm_tool_dispatch_t::on_exception_raised(vm_registers_t &r, const vm_string_t &id, vm_alloc_t &e){    vm_event_context_t cxt{};    cxt.value1.registers = &r;    cxt.value2.str = &id;    cxt.value3.alloc = &e;     _events.fire_event_callbacks(vm_event_t::exception_raised, cxt);} void vm_tool_dispatch_t::on_exception_unhandled(vm_registers_t &r, const vm_string_t &id, vm_alloc_t &e){    vm_event_context_t cxt{};    cxt.value1.registers = &r;    cxt.value2.str = &id;    cxt.value3.alloc = &e;     _events.fire_event_callbacks(vm_event_t::exception_unhandled, cxt);} void vm_tool_dispatch_t::on_module_vm_load(const loaded_vm_module_t &m){    vm_event_context_t cxt{};    cxt.value1.loaded_mod = &m;     _events.fire_event_callbacks(vm_event_t::module_vm_load, cxt);} void vm_tool_dispatch_t::on_module_thread_load(vm_registers_t &r, std::shared_ptr<vm_module_t> &m){    assert(m != nullptr);     vm_event_context_t cxt{};    cxt.value1.registers = &r;    cxt.value2.module = &m;     _events.fire_event_callbacks(vm_event_t::module_thread_load, cxt);} void vm_tool_dispatch_t::on_thread_begin(vm_thread_t &t){    vm_event_context_t cxt{};    cxt.value1.thread = &t;     _events.fire_event_callbacks(vm_event_t::thread_begin, cxt);} void vm_tool_dispatch_t::on_thread_end(vm_thread_t &t){    vm_event_context_t cxt{};    cxt.value1.thread = &t;     _events.fire_event_callbacks(vm_event_t::thread_end, cxt);} void vm_tool_dispatch_t::on_thread_broken(vm_thread_t &t){    vm_event_context_t cxt{};    cxt.value1.thread = &t;     _events.fire_event_callbacks(vm_event_t::thread_broken, cxt);} void vm_tool_dispatch_t::block_if_thread_suspended(uint32_t thread_id){    if (!_threads_suspended)        return;     std::unique_lock<std::mutex> resume_lock{ _threads_resume_lock };     _threads_suspended_count++;     while (_threads_suspended)        _threads_resume.wait(resume_lock);     _threads_suspended_count--;} vm_t& vm_tool_dispatch_t::get_vm_instance() const{    return _vm;} cookie_t vm_tool_dispatch_t::subscribe_event(vm_event_t evt, vm_event_callback_t cb){    if (!cb)        throw vm_system_exception{ "Invalid callback for event" };     std::lock_guard<std::mutex> lock{ _events.event_lock };     const auto cookie_id = _events.cookie_counter++;     // Store the event callback    auto &event_callbacks = _events.callbacks[evt];    event_callbacks[cookie_id] = cb;     // Store the cookie with the associate event    _events.cookie_to_event[cookie_id] = evt;     if (disvm::debug::is_component_tracing_enabled<component_trace_t::tool>())        disvm::debug::log_msg(component_trace_t::tool, log_level_t::debug, "subscribe: event: %d %d", evt, cookie_id);     return cookie_id;} void vm_tool_dispatch_t::unsubscribe_event(cookie_t cookie_id){    std::lock_guard<std::mutex> lock{ _events.event_lock };     auto iter = _events.cookie_to_event.find(cookie_id);    if (iter == _events.cookie_to_event.cend())        return;     // Erase cookie and event    const auto evt = iter->second;    auto &event_callbacks = _events.callbacks[evt];    event_callbacks.erase(cookie_id);    _events.cookie_to_event.erase(iter);     if (disvm::debug::is_component_tracing_enabled<component_trace_t::tool>())        disvm::debug::log_msg(component_trace_t::tool, log_level_t::debug, "unsubscribe: event: %d %d", evt, cookie_id);} cookie_t vm_tool_dispatch_t::set_breakpoint(std::shared_ptr<vm_module_t> module, vm_pc_t pc){    if (module == nullptr || util::has_flag(module->header.runtime_flag, runtime_flags_t::builtin))        throw vm_system_exception{ "Unable to set breakpoint in supplied module" };     auto &code_section = module->code_section;    if (pc >= static_cast<vm_pc_t>(code_section.size()))        throw vm_system_exception{ "Invalid PC for module" };     const auto real_opcode = code_section[pc].op.opcode;     {        std::lock_guard<std::mutex> lock{ _breakpoints.lock };        auto &pc_map = _breakpoints.original_opcodes[reinterpret_cast<std::uintptr_t>(module.get())];         // If the real opcode is already a breakpoint and it is known, return the current cookie.        if (real_opcode == opcode_t::brkpt)        {            auto iter = pc_map.find(pc);            if (iter != pc_map.cend())                return iter->second.second;        }         const auto cookie_id = _breakpoints.cookie_counter++;         // Map the cookie to the module/pc pair.        _breakpoints.cookie_to_details[cookie_id] = { module, pc, real_opcode };         // Record the original opcode        pc_map[pc] = std::make_pair(real_opcode, cookie_id);         // Replace the current opcode with breakpoint        code_section[pc].op.opcode = opcode_t::brkpt;         if (disvm::debug::is_component_tracing_enabled<component_trace_t::tool>())            disvm::debug::log_msg(component_trace_t::tool, log_level_t::debug, "breakpoint: set: %d %d >>%s<<", cookie_id, pc, module->module_name->str());         return cookie_id;    }} breakpoint_details_t vm_tool_dispatch_t::get_breakpoint_details(cookie_t cookie_id) const{    std::lock_guard<std::mutex> lock{ _breakpoints.lock };     auto iter_cookie = _breakpoints.cookie_to_details.find(cookie_id);    if (iter_cookie == _breakpoints.cookie_to_details.cend())        throw vm_system_exception{ "Unknown breakpoint cookie" };     return iter_cookie->second;} void vm_tool_dispatch_t::clear_breakpoint(cookie_t cookie_id){    std::lock_guard<std::mutex> lock{ _breakpoints.lock };     auto iter_cookie = _breakpoints.cookie_to_details.find(cookie_id);    if (iter_cookie == _breakpoints.cookie_to_details.cend())        throw vm_system_exception{ "Unknown breakpoint cookie" };     auto details = iter_cookie->second;    const auto target_pc = details.pc;     // Determine the original opcode    auto iter_orig = _breakpoints.original_opcodes.find(reinterpret_cast<std::uintptr_t>(details.module.get()));    assert(iter_orig != _breakpoints.original_opcodes.cend());     auto &pc_map = iter_orig->second;    auto iter_pc = pc_map.find(target_pc);    assert(iter_pc != pc_map.cend());        auto original_opcode = iter_pc->second.first;     // Replace the breakpoint opcode with the original    auto &code_section = const_cast<code_section_t &>(details.module->code_section);    assert(code_section[target_pc].op.opcode == opcode_t::brkpt);    code_section[target_pc].op.opcode = original_opcode;     if (disvm::debug::is_component_tracing_enabled<component_trace_t::tool>())        disvm::debug::log_msg(component_trace_t::tool, log_level_t::debug, "breakpoint: unset: %d %d >>%s<<", cookie_id, target_pc, details.module->module_name->str());     // Clean-up the PC mapping to opcode    pc_map.erase(iter_pc);     // If the PC map is empty, erase the module mapping as well    if (pc_map.size() == 0)        _breakpoints.original_opcodes.erase(iter_orig);     // Remove the cookie map    _breakpoints.cookie_to_details.erase(iter_cookie);} void vm_tool_dispatch_t::suspend_all_threads(){    std::lock(_threads_suspend_lock, _threads_resume_lock);    std::lock_guard<std::mutex> suspend_lock{ _threads_suspend_lock, std::adopt_lock };    std::lock_guard<std::mutex> resume_lock{ _threads_resume_lock, std::adopt_lock };     _threads_suspended = true;     const auto &sched = _vm.get_scheduler_control();     const auto sys_thread_count = static_cast<int32_t>(sched.get_system_thread_count());    assert(sys_thread_count != 0);     // Sleep and check until we account for all system threads associated with the VM.    auto suspended = _threads_suspended_count.load();    while ((suspended + 1) < sys_thread_count)    {        std::this_thread::sleep_for(std::chrono::milliseconds{ 20 });        suspended = _threads_suspended_count.load();    }     // If the suspended count is ever equal to the system thread count, then a non-vm thread    // is inside the tool dispatcher which may be okay, but it indicates a type of    // non-invasive debugging that was not originally incorporated into this    // dispatcher design.    assert(suspended < sys_thread_count);} void vm_tool_dispatch_t::resume_all_threads(){    std::lock(_threads_suspend_lock, _threads_resume_lock);    std::lock_guard<std::mutex> suspend_lock{ _threads_suspend_lock, std::adopt_lock };     _threads_suspended = false;     _threads_resume_lock.unlock();    _threads_resume.notify_all();} void vm_tool_dispatch_t::set_thread_trap_flag(const vm_registers_t &r, vm_trap_flags_t flag){    std::lock_guard<std::mutex> suspend_lock{ _threads_resume_lock };    if (!_threads_suspended)        throw vm_system_exception{ "Unable to set thread trap flag while threads are not suspended" };     const_cast<vm_trap_flags_t &>(r.trap_flags) |= flag;} std::tuple<opcode_t, cookie_t> vm_tool_dispatch_t::get_original_opcode_and_cookie(const vm_registers_t &r){    const auto module = r.module_ref->module.get();     if (module == nullptr || util::has_flag(module->header.runtime_flag, runtime_flags_t::builtin))        throw vm_system_exception{ "Unable to determine original opcode in supplied module" };     std::lock_guard<std::mutex> lock{ _breakpoints.lock };     auto iter_orig = _breakpoints.original_opcodes.find(reinterpret_cast<std::uintptr_t>(module));    if (iter_orig != _breakpoints.original_opcodes.cend())    {        auto &pc_map = iter_orig->second;        auto iter_pc = pc_map.find(r.pc);        if (iter_pc != pc_map.cend())            return iter_pc->second;    }     assert(false && "Why are we missing this information?");    return std::make_tuple(opcode_t::runt, 0); // Return NOP instruction?} void vm_tool_dispatch_t::events_t::fire_event_callbacks(vm_event_t event_type, vm_event_context_t &cxt){    auto event_callbacks = std::vector<vm_event_callback_t>{};     {        std::lock_guard<std::mutex> lock_local{ event_lock };         auto &event_callbacks_tmp = callbacks[event_type];        for (auto &c : event_callbacks_tmp)        {            assert(static_cast<bool>(c.second) && "Why is callback empty?");            event_callbacks.push_back(c.second);        }    }     for (auto &c : event_callbacks)        c(event_type, cxt);}