From 86afee9b5c4954fda84d500b85328cca18ee3de4 Mon Sep 17 00:00:00 2001 From: hachem Date: Fri, 28 Aug 2026 21:32:31 +0200 Subject: feat: breakpoint conditions and skip counts --- src/debugger/debugger.cpp | 83 +++++++++++++++++++++++++++++++++++++++++++---- src/debugger/debugger.h | 2 ++ src/debugger/types.h | 3 ++ src/ui/ui.cpp | 29 ++++++++++++++++- 4 files changed, 109 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/debugger/debugger.cpp b/src/debugger/debugger.cpp index c5cc615..00bdda7 100644 --- a/src/debugger/debugger.cpp +++ b/src/debugger/debugger.cpp @@ -45,7 +45,11 @@ namespace Hsdbg llvm_prefix / "bin" / "debugserver", }; #elif defined(HSDBG_LINUX) - return { llvm_prefix / "bin" / "lldb-server" }; + return { + llvm_prefix / "bin" / "lldb-server", + "/usr/bin/lldb-server", + "/usr/local/bin/lldb-server", + }; #else return {}; #endif @@ -74,7 +78,12 @@ namespace Hsdbg return; } - Log::warn("debugger: no debug server found, launching a target will fail"); + Log::warn("debugger: no debug server found, leaving lldb to locate one"); + } + + auto text_or(const char* text, std::string_view fallback) -> std::string + { + return text != nullptr && text[0] != '\0' ? std::string(text) : std::string(fallback); } auto path_of(lldb::SBFileSpec spec) -> std::filesystem::path @@ -181,11 +190,6 @@ namespace Hsdbg constexpr uint32_t MAX_VARIABLE_DEPTH = 3; constexpr uint32_t MAX_VARIABLE_CHILDREN = 64; - auto text_or(const char* text, std::string_view fallback) -> std::string - { - return text != nullptr && text[0] != '\0' ? std::string(text) : std::string(fallback); - } - auto frame_of(lldb::SBProcess& process, uint64_t thread_id, uint32_t frame_index) -> lldb::SBFrame { lldb::SBThread thread = process.GetThreadByID(thread_id); @@ -425,6 +429,19 @@ namespace Hsdbg if (spec.stop_at_entry) info.SetLaunchFlags(info.GetLaunchFlags() | lldb::eLaunchFlagStopAtEntry); + // lldb counts an ignore count down as hits are consumed, so a rerun has + // to start it over or it would only ever skip once + for (const Breakpoint& breakpoint : m_breakpoints) + { + if (breakpoint.backend_id == 0) + continue; + + lldb::SBBreakpoint source = m_session->target.FindBreakpointByID(breakpoint.backend_id); + + if (source.IsValid()) + source.SetIgnoreCount(breakpoint.ignore_count); + } + set_state(TargetState::Launching); lldb::SBError error; @@ -745,6 +762,51 @@ namespace Hsdbg return true; } + auto Debugger::set_breakpoint_condition(uint32_t id, std::string_view condition) -> bool + { + Breakpoint* breakpoint = find_breakpoint(id); + if (breakpoint == nullptr) + return false; + + breakpoint->condition = condition; + + if (breakpoint->backend_id != 0 && m_session->target.IsValid()) + { + lldb::SBBreakpoint source = m_session->target.FindBreakpointByID(breakpoint->backend_id); + + if (source.IsValid()) + source.SetCondition(breakpoint->condition.c_str()); + } + + if (breakpoint->condition.empty()) + Log::info("debugger: breakpoint {} is unconditional", id); + else + Log::info("debugger: breakpoint {} stops when '{}'", id, breakpoint->condition); + + return true; + } + + auto Debugger::set_breakpoint_ignore_count(uint32_t id, uint32_t count) -> bool + { + Breakpoint* breakpoint = find_breakpoint(id); + if (breakpoint == nullptr) + return false; + + breakpoint->ignore_count = count; + + if (breakpoint->backend_id != 0 && m_session->target.IsValid()) + { + lldb::SBBreakpoint source = m_session->target.FindBreakpointByID(breakpoint->backend_id); + + if (source.IsValid()) + source.SetIgnoreCount(count); + } + + Log::info("debugger: breakpoint {} ignores {} hits", id, count); + + return true; + } + auto Debugger::clear_breakpoints() -> void { if (m_session->target.IsValid()) @@ -1190,6 +1252,13 @@ namespace Hsdbg created.SetEnabled(breakpoint.enabled); + // a breakpoint can be conditioned before any target exists to carry it + if (!breakpoint.condition.empty()) + created.SetCondition(breakpoint.condition.c_str()); + + if (breakpoint.ignore_count != 0) + created.SetIgnoreCount(breakpoint.ignore_count); + breakpoint.backend_id = created.GetID(); read_back(m_session->target, created, breakpoint); diff --git a/src/debugger/debugger.h b/src/debugger/debugger.h index c7e5324..82a993a 100644 --- a/src/debugger/debugger.h +++ b/src/debugger/debugger.h @@ -45,6 +45,8 @@ namespace Hsdbg auto add_function_breakpoint(std::string_view function) -> uint32_t; auto remove_breakpoint(uint32_t id) -> bool; auto set_breakpoint_enabled(uint32_t id, bool enabled) -> bool; + auto set_breakpoint_condition(uint32_t id, std::string_view condition) -> bool; + auto set_breakpoint_ignore_count(uint32_t id, uint32_t count) -> bool; auto clear_breakpoints() -> void; auto find_breakpoint(uint32_t id) -> Breakpoint*; auto breakpoints() const -> std::span { return m_breakpoints; } diff --git a/src/debugger/types.h b/src/debugger/types.h index 0023694..dec74e5 100644 --- a/src/debugger/types.h +++ b/src/debugger/types.h @@ -86,6 +86,9 @@ namespace Hsdbg std::string function; uint64_t address = 0; std::string condition; + + // how many hits lldb lets through before it starts stopping + uint32_t ignore_count = 0; uint32_t hit_count = 0; bool enabled = true; bool resolved = false; diff --git a/src/ui/ui.cpp b/src/ui/ui.cpp index 8ba1155..01c5165 100644 --- a/src/ui/ui.cpp +++ b/src/ui/ui.cpp @@ -523,12 +523,14 @@ namespace Hsdbg ImGuiTableFlags_SizingStretchProp | ImGuiTableFlags_ScrollY; - if (ImGui::BeginTable("##breakpoints", 6, flags)) + if (ImGui::BeginTable("##breakpoints", 8, flags)) { ImGui::TableSetupColumn("on", ImGuiTableColumnFlags_WidthFixed, 26.0f); ImGui::TableSetupColumn("id", ImGuiTableColumnFlags_WidthFixed, 30.0f); ImGui::TableSetupColumn("location"); ImGui::TableSetupColumn("address"); + ImGui::TableSetupColumn("condition"); + ImGui::TableSetupColumn("skip", ImGuiTableColumnFlags_WidthFixed, 50.0f); ImGui::TableSetupColumn("hits", ImGuiTableColumnFlags_WidthFixed, 40.0f); ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, 26.0f); ImGui::TableSetupScrollFreeze(0, 1); @@ -577,6 +579,31 @@ namespace Hsdbg ImGui::TextDisabled("pending"); } + // imgui keeps its own buffer while an input is focused, so + // these copies only seed it and receive the final text + ImGui::TableNextColumn(); + std::string condition = breakpoint.condition; + ImGui::SetNextItemWidth(-1.0f); + + if (ImGui::InputTextWithHint("##condition", + "stop when", + &condition, + ImGuiInputTextFlags_EnterReturnsTrue)) + { + debugger.set_breakpoint_condition(breakpoint.id, condition); + } + + ImGui::TableNextColumn(); + int ignore_count = static_cast(breakpoint.ignore_count); + ImGui::SetNextItemWidth(-1.0f); + + if (ImGui::InputInt("##skip", &ignore_count, 0, 0, + ImGuiInputTextFlags_EnterReturnsTrue)) + { + debugger.set_breakpoint_ignore_count(breakpoint.id, + static_cast(std::max(ignore_count, 0))); + } + ImGui::TableNextColumn(); ImGui::Text("%u", breakpoint.hit_count); -- cgit v1.3