From 78e7f5cca7cb4c8dbfe71ba7a0851ac53990f611 Mon Sep 17 00:00:00 2001 From: T0b1 Date: Sun, 4 Jun 2023 22:08:49 +0200 Subject: [PATCH] breakpoints --- src/backend/backend.cpp | 5 +- src/backend/debug_backend.h | 6 +- src/backend/lldb/lldb_backend.cpp | 89 +++++++++++++- src/backend/lldb/lldb_backend.h | 11 ++ src/frontend/frontend.cpp | 17 ++- src/frontend/frontend.h | 1 + src/frontend/target.h | 8 ++ src/frontend/window.cpp | 192 +++++++++++++++++++++++++++++- src/frontend/window.h | 12 +- src/imgui_user.h | 15 +++ src/msg.h | 1 + todos.txt | 1 + 12 files changed, 341 insertions(+), 17 deletions(-) create mode 100644 todos.txt diff --git a/src/backend/backend.cpp b/src/backend/backend.cpp index 259e366..41f2bf8 100644 --- a/src/backend/backend.cpp +++ b/src/backend/backend.cpp @@ -4,11 +4,12 @@ using namespace dbgui::backend; Backend::~Backend() {} -void Backend::send_state_change(TargetState new_state, StateChangeReason reason) +void Backend::send_state_change(TargetState new_state, StateChangeReason reason, + uint64_t extra) { auto msg = std::make_unique( BackToFront::MsgType::state_change, - BackToFront::StateChange{new_state, reason}); + BackToFront::StateChange{new_state, reason, extra}); this->send_msg(std::move(msg)); } diff --git a/src/backend/debug_backend.h b/src/backend/debug_backend.h index c5069ec..c0fd214 100644 --- a/src/backend/debug_backend.h +++ b/src/backend/debug_backend.h @@ -29,6 +29,9 @@ namespace dbgui::backend virtual void add_data_node(const data::DataNode &) = 0; virtual void remove_data_node(uint64_t id) = 0; + virtual void add_breakpoint(uint64_t addr, size_t id) = 0; + virtual void remove_breakpoint(size_t id) = 0; + auto retrieve_msg_for_frontend() -> std::optional> { @@ -51,7 +54,8 @@ namespace dbgui::backend _back_front_msgs.emplace_back(std::move(msg)); } - void send_state_change(TargetState new_state, StateChangeReason reason); + void send_state_change(TargetState new_state, StateChangeReason reason, + uint64_t extra = 0); void send_proc_info(BackToFront::InitialProcessInfo &&); void send_reg_change(BackToFront::RegsChanged &&); void send_thread_change(BackToFront::ThreadChange &&); diff --git a/src/backend/lldb/lldb_backend.cpp b/src/backend/lldb/lldb_backend.cpp index 58c3fad..6a63fb0 100644 --- a/src/backend/lldb/lldb_backend.cpp +++ b/src/backend/lldb/lldb_backend.cpp @@ -12,6 +12,7 @@ #include #include #include +#include using namespace dbgui::backend; @@ -132,13 +133,43 @@ void LLDBBackend::handle_state_change(lldb::StateType state) switch (state) { case eStateStopped: + { this->check_reg_changes(); this->check_thread_changes(); this->check_frame_changes(); this->check_data_changes(); _state = TargetState::paused; - this->send_state_change(TargetState::paused, StateChangeReason::unknown); + // TODO: does the selected thread auto-switch? + auto sel_thread = _process->GetSelectedThread(); + auto change_reason = StateChangeReason::unknown; + auto extra = 0; + switch (sel_thread.GetStopReason()) + { + case eStopReasonBreakpoint: + { + // what is location id/N for breakpoints? + // does this have to do when there are multiple + // place the same source code ends up? + auto bp_id = sel_thread.GetStopReasonDataAtIndex(0); + + // clang-format bugs out on this for some reason + // clang-format off + auto it = std::find_if( + _breakpoints.begin(), _breakpoints.end(), + [bp_id](const auto &el) { return el.lldb_id == bp_id; }); + // clang-format on + if (it != _breakpoints.end()) + { + change_reason = StateChangeReason::breakpoint; + extra = it->id; + } + break; + } + } + + this->send_state_change(TargetState::paused, change_reason, extra); break; + } case eStateRunning: case eStateStepping: _state = TargetState::running; @@ -337,7 +368,7 @@ void LLDBBackend::dump_threads() end = start + 0x100; } - auto buf = std::vector{}; + /*auto buf = std::vector{}; buf.resize(end - start); auto err = SBError{}; _target.ReadMemory(SBAddress{start, _target}, buf.data(), buf.size(), err); @@ -359,7 +390,7 @@ void LLDBBackend::dump_threads() { printf("Selfprint: %s%s\n", inst.GetMnemonic(_target), inst.GetOperands(_target)); - } + }*/ //printf("Disasm: %s\n", frame.Disassemble()); } else @@ -982,9 +1013,9 @@ dbgui::data::DataResult LLDBBackend::calc_data_res(const data::DataNode &node) auto addr = inst.GetAddress().GetLoadAddress(_target); const auto len = inst.GetByteSize(); - SBStream stream{}; - inst.GetDescription(stream); - printf("Got inst: %.*s\n", stream.GetSize(), stream.GetData()); + //SBStream stream{}; + //inst.GetDescription(stream); + //printf("Got inst: %.*s\n", stream.GetSize(), stream.GetData()); if (mnem.size() > 255 || op.size() > 255 || comm.size() > 255) { @@ -1023,6 +1054,52 @@ dbgui::data::DataResult LLDBBackend::calc_data_res(const data::DataNode &node) exit(1); } +void LLDBBackend::add_breakpoint(uint64_t addr, size_t id) +{ + std::lock_guard g{_data_lock}; + if (!_process) + { + return; + } + + if (std::find_if(_breakpoints.begin(), _breakpoints.end(), + [id](const auto &el) { return el.id == id; }) + != _breakpoints.end()) + { + printf("Trying to register same breakpoint id\n"); + exit(1); + } + + auto bp = _target.BreakpointCreateByAddress(addr); + _breakpoints.push_back(Breakpoint{.id = id, .lldb_id = bp.GetID()}); +} + +void LLDBBackend::remove_breakpoint(size_t id) +{ + std::lock_guard g{_data_lock}; + if (!_process) + { + return; + } + + auto it = std::find_if(_breakpoints.begin(), _breakpoints.end(), + [id](const auto &el) { return el.id == id; }); + + if (it == _breakpoints.end()) + { + printf("Trying to delete nonexistant breakpoint\n"); + exit(1); + } + + // TODO: error handling? + _target.BreakpointDelete(it->lldb_id); + // TODO: this should check if there are no pending events in the debugger event queue + // to make sure we did not hit the breakpoint before deleting it + // maybe really add like a custom event type to send to the event queue? + // afterwards it should send a bp deleted msg so the UI can sync this, too + _breakpoints.erase(it); +} + /* Reg output for x64 Got register set General Purpose Registers diff --git a/src/backend/lldb/lldb_backend.h b/src/backend/lldb/lldb_backend.h index 07f7dd2..2f2688b 100644 --- a/src/backend/lldb/lldb_backend.h +++ b/src/backend/lldb/lldb_backend.h @@ -40,6 +40,12 @@ namespace dbgui::backend std::string display_name; }; + struct Breakpoint + { + size_t id; + lldb::break_id_t lldb_id; + }; + // TODO: source_init_file: false LLDBBackend(std::string filename); virtual ~LLDBBackend(); @@ -56,6 +62,9 @@ namespace dbgui::backend void add_data_node(const data::DataNode &) override; void remove_data_node(uint64_t id) override; + void add_breakpoint(uint64_t addr, size_t id) override; + void remove_breakpoint(size_t id) override; + private: void run_msg_loop(); void wait_for_debug_events(); @@ -92,5 +101,7 @@ namespace dbgui::backend std::vector _dag_linear = {}; bool _dag_linear_valid = false; std::vector _cached_data_results = {}; + + std::vector _breakpoints = {}; }; } // namespace dbgui::backend \ No newline at end of file diff --git a/src/frontend/frontend.cpp b/src/frontend/frontend.cpp index 49986ef..8e1b74d 100644 --- a/src/frontend/frontend.cpp +++ b/src/frontend/frontend.cpp @@ -24,6 +24,7 @@ Frontend::Frontend() _windows.push_back(Window::create_threads(window_id++)); _windows.push_back(Window::create_frames(window_id++)); _windows.push_back(Window::create_disas(window_id++)); + _windows.push_back(Window::create_bp(window_id++)); } void Frontend::run_frame() @@ -182,7 +183,13 @@ void Frontend::draw_header() ImGui::BeginDisabled(); } - if (ImGui::Button("Step Over")) {} + if (ImGui::Button("Step Over")) + { + if (this->target->backend->step_over()) + { + // TODO: already disable the UI into running mode + } + } if (ImGui::Button("Step Into")) { if (this->target->backend->step_into()) @@ -190,7 +197,13 @@ void Frontend::draw_header() // TODO: already disable the UI into running mode } } - ImGui::Button("Step Out"); + if (ImGui::Button("Step Out")) + { + if (this->target->backend->step_out()) + { + // TODO: already disable the UI into running mode + } + } if (!is_paused) { diff --git a/src/frontend/frontend.h b/src/frontend/frontend.h index 7ada723..64f4431 100644 --- a/src/frontend/frontend.h +++ b/src/frontend/frontend.h @@ -7,6 +7,7 @@ #include #include +#define IMGUI_INCLUDE_IMGUI_USER_H #include "imgui.h" #include "frontend/target.h" #include "backend/debug_backend.h" diff --git a/src/frontend/target.h b/src/frontend/target.h index 64bc72e..d1cac3d 100644 --- a/src/frontend/target.h +++ b/src/frontend/target.h @@ -41,6 +41,13 @@ namespace dbgui::frontend std::string display_name; }; + struct Breakpoint + { + bool removed = false; + // TODO: srcloc + uint64_t addr; + }; + Target(std::string filename); TargetState state = TargetState::stopped; @@ -52,6 +59,7 @@ namespace dbgui::frontend std::vector reg_sets; std::vector> threads; std::vector> frames; + std::vector breakpoints; std::shared_ptr backend = nullptr; }; diff --git a/src/frontend/window.cpp b/src/frontend/window.cpp index b6a6e87..0b988bf 100644 --- a/src/frontend/window.cpp +++ b/src/frontend/window.cpp @@ -1,5 +1,7 @@ #include "window.h" #include "frontend.h" +#include +#include "imgui_internal.h" using namespace dbgui; using namespace dbgui::frontend; @@ -18,6 +20,9 @@ bool Window::draw(Frontend &frontend) case disassembly: return std::get(this->data).draw(frontend); break; + case breakpoints: + return std::get(this->data).draw(frontend); + break; default: printf("Unhandled window draw: %u\n", this->type); exit(1); } } @@ -59,6 +64,15 @@ Window Window::create_disas(size_t id) DisasmWindow{.id = id_str, .open = true, .first = true}}; } +Window Window::create_bp(size_t id) +{ + auto id_str = std::string{"Breakpoints##"}; + id_str.append(std::to_string(id)); + + return Window{.type = WindowType::breakpoints, + .data = BreakpointWindow{.id = id_str, .open = true}}; +} + bool RegWindow::draw(const Frontend &frontend) { //ImGui::SetNextWindowDockID(frontend.dock_id, ImGuiCond_Appearing); @@ -74,6 +88,12 @@ bool RegWindow::draw(const Frontend &frontend) return false; } + if (frontend.target->state == TargetState::running) + { + ImGui::PushStyleColor(ImGuiCol_Text, + ImGui::GetStyleColorVec4(ImGuiCol_TextDisabled)); + } + if (ImGui::BeginTable("table", 1, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { @@ -151,6 +171,11 @@ bool RegWindow::draw(const Frontend &frontend) ImGui::EndTable(); } + if (frontend.target->state == TargetState::running) + { + ImGui::PopStyleColor(); + } + ImGui::End(); return false; } @@ -170,6 +195,12 @@ bool ThreadWindow::draw(const Frontend &frontend) return false; } + if (frontend.target->state == TargetState::running) + { + ImGui::PushStyleColor(ImGuiCol_Text, + ImGui::GetStyleColorVec4(ImGuiCol_TextDisabled)); + } + if (ImGui::BeginTable("table", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { @@ -211,6 +242,11 @@ bool ThreadWindow::draw(const Frontend &frontend) ImGui::EndTable(); } + if (frontend.target->state == TargetState::running) + { + ImGui::PopStyleColor(); + } + ImGui::End(); return false; } @@ -230,6 +266,12 @@ bool FrameWindow::draw(const Frontend &frontend) return false; } + if (frontend.target->state == TargetState::running) + { + ImGui::PushStyleColor(ImGuiCol_Text, + ImGui::GetStyleColorVec4(ImGuiCol_TextDisabled)); + } + if (ImGui::BeginTable("table", 1, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { @@ -262,6 +304,11 @@ bool FrameWindow::draw(const Frontend &frontend) ImGui::EndTable(); } + if (frontend.target->state == TargetState::running) + { + ImGui::PopStyleColor(); + } + ImGui::End(); return false; } @@ -293,6 +340,12 @@ bool DisasmWindow::draw(Frontend &frontend) return false; } + if (frontend.target->state == TargetState::running) + { + ImGui::PushStyleColor(ImGuiCol_Text, + ImGui::GetStyleColorVec4(ImGuiCol_TextDisabled)); + } + if (first) { auto found = false; @@ -343,12 +396,12 @@ bool DisasmWindow::draw(Frontend &frontend) auto pad = ImGui::GetStyle().CellPadding; pad.x += 10.f; - ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, pad); - if (ImGui::BeginTable("table", 2, + if (ImGui::BeginTable("table", 3, ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg | ImGuiTableFlags_PadOuterX)) { ImGui::TableSetupScrollFreeze(0, 1); + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, 10.f); ImGui::TableSetupColumn("Address"); ImGui::TableSetupColumn("Instruction"); ImGui::TableHeadersRow(); @@ -360,6 +413,58 @@ bool DisasmWindow::draw(Frontend &frontend) ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); + ImGui::PushID(idx); + // TODO: draw nice break sign + auto &bps = frontend.target->breakpoints; + const auto bp_it = + std::find_if(bps.begin(), bps.end(), + [inst](const auto &el) { return el.addr == inst.addr; }); + const auto is_bp = bp_it != bps.end() && !bp_it->removed; + if (is_bp) + { + auto win_pos = ImGui::GetWindowPos(); + auto pos = ImGui::GetCursorPos(); + pos.x += win_pos.x; + pos.y += win_pos.y - ImGui::GetScrollY(); + pos.y += (ImGui::GetTextLineHeight() - 10.f) / 2.f; + auto end_pos = pos; + end_pos.x += 10.f; + end_pos.y += 10.f; + ImGui::GetWindowDrawList()->AddRectFilled(pos, end_pos, + IM_COL32(255, 0, 0, 255)); + } + if (ImGui::InvisibleButton("Break", + ImVec2{10.f, ImGui::GetTextLineHeight()})) + { + if (!is_bp) + { + size_t idx = 0; + for (; idx < bps.size(); ++idx) + { + if (bps[idx].removed) + { + break; + } + } + if (idx == bps.size()) + { + bps.push_back( + Target::Breakpoint{.removed = false, .addr = inst.addr}); + } else + { + bps[idx] = Target::Breakpoint{.removed = false, .addr = inst.addr}; + } + frontend.target->backend->add_breakpoint(inst.addr, idx); + } else + { + frontend.target->backend->remove_breakpoint(bp_it - bps.begin()); + bp_it->removed = true; + } + } + + ImGui::PopID(); + + ImGui::TableNextColumn(); if (!this->ip_unsuccessful && inst.addr == ip) { // TODO: color config @@ -374,7 +479,7 @@ bool DisasmWindow::draw(Frontend &frontend) // TODO: make some unified handler for int vars ImGui::PushID(idx); - ImGui::Text("%lX", inst.addr); + ImGui::Text("%lX ", inst.addr); if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) { @@ -397,13 +502,17 @@ bool DisasmWindow::draw(Frontend &frontend) ImGui::TableNextColumn(); const int mnem_space = max_mnem_len - inst.mnem_len + 1; - const int op_space = max_op_len - inst.op_len + 1; + const int op_space = /*max_op_len - inst.op_len +*/ 1; ImGui::Text(inst.fmt_str.c_str(), mnem_space, ' ', op_space, ' '); } ImGui::EndTable(); } - ImGui::PopStyleVar(); + + if (frontend.target->state == TargetState::running) + { + ImGui::PopStyleColor(); + } ImGui::End(); @@ -411,6 +520,79 @@ bool DisasmWindow::draw(Frontend &frontend) return false; } +bool BreakpointWindow::draw(Frontend &frontend) +{ + if (!ImGui::Begin(this->id.c_str())) + { + ImGui::End(); + return false; + } + + if (!frontend.target) + { + ImGui::End(); + return false; + } + + if (ImGui::BeginTable("table", 2, + ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) + { + ImGui::TableSetupColumn("ID"); + ImGui::TableSetupColumn("Address"); + ImGui::TableHeadersRow(); + + auto &bps = frontend.target->breakpoints; + for (size_t idx = 0; idx < bps.size(); ++idx) + { + auto &bp = bps[idx]; + if (bp.removed) + { + continue; + } + + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + + ImGui::PushID(idx); + + ImGui::Text("%zu", idx); + ImGui::TableNextColumn(); + + ImGui::Text("%lX", bp.addr); + + auto min_pos = + ImGui::TableGetCellBgRect(ImGui::GetCurrentTable(), 0).GetTL(); + auto max_pos = + ImGui::TableGetCellBgRect(ImGui::GetCurrentTable(), 1).GetBR(); + + if (ImGui::IsMouseClicked(ImGuiMouseButton_Right) + && ImGui::IsMouseHoveringRect(min_pos, max_pos, false)) + { + ImGui::OpenPopup("Context", ImGuiPopupFlags_NoOpenOverExistingPopup); + } + + if (ImGui::BeginPopup("Context")) + { + if (ImGui::Selectable("Remove")) + { + printf("Removing bp %zu\n", idx); + frontend.target->backend->remove_breakpoint(idx); + bp.removed = true; + } + // TODO: disable/endable? + ImGui::EndPopup(); + } + + ImGui::PopID(); + } + + ImGui::EndTable(); + } + + ImGui::End(); + return false; +} + void Window::handle_data_res(const BackToFront::DataResult &result) { switch (this->type) diff --git a/src/frontend/window.h b/src/frontend/window.h index c0f63db..66f004c 100644 --- a/src/frontend/window.h +++ b/src/frontend/window.h @@ -20,6 +20,7 @@ namespace dbgui::frontend frames, threads, disassembly, + breakpoints, }; struct RegWindow @@ -98,11 +99,19 @@ namespace dbgui::frontend std::vector insts; }; + struct BreakpointWindow + { + bool draw(Frontend &); + + std::string id; + bool open; + }; + struct Window { WindowType type; std::variant + DisasmWindow, BreakpointWindow> data; // if true, window is closed and should be deleted @@ -112,6 +121,7 @@ namespace dbgui::frontend static Window create_threads(size_t window_id); static Window create_frames(size_t window_id); static Window create_disas(size_t window_id); + static Window create_bp(size_t window_id); void handle_data_res(const BackToFront::DataResult &result); }; diff --git a/src/imgui_user.h b/src/imgui_user.h index e69de29..1cfe6f8 100644 --- a/src/imgui_user.h +++ b/src/imgui_user.h @@ -0,0 +1,15 @@ +#pragma once + +namespace ImGui +{ + struct StyleColorPush + { + StyleColorPush(ImGuiCol col, ImVec4 val) + { + ImGui::PushStyleColor(col, val); + } + StyleColorPush(ImGuiCol col, ImU32 val) { ImGui::PushStyleColor(col, val); } + + ~StyleColorPush() { ImGui::PopStyleColor(); } + }; +} // namespace ImGui \ No newline at end of file diff --git a/src/msg.h b/src/msg.h index 3275566..369147f 100644 --- a/src/msg.h +++ b/src/msg.h @@ -77,6 +77,7 @@ namespace dbgui { TargetState new_state; StateChangeReason reason; + uint64_t extra; }; struct IPChange diff --git a/todos.txt b/todos.txt new file mode 100644 index 0000000..e736870 --- /dev/null +++ b/todos.txt @@ -0,0 +1 @@ +make sure all the ImGui::Text stuff is converted to TextUnformatted unless formatting is needed \ No newline at end of file