From b26e1efc424ad840143f02a96246cc666ee99e72 Mon Sep 17 00:00:00 2001 From: Omar Emara Date: Tue, 17 Aug 2021 16:58:05 -0700 Subject: [PATCH] [LLDB][GUI] Add Breakpoints window This patch adds a breakpoints window that lists all breakpoints and breakpoints locations. The window is implemented as a tree, where the first level is the breakpoints and the second level is breakpoints locations. The tree delegate was hardcoded to only draw when there is a process, which is not necessary for breakpoints, so the relevant logic was abstracted in the TreeDelegateShouldDraw method. Reviewed By: clayborg Differential Revision: https://reviews.llvm.org/D107386 --- lldb/source/Core/IOHandlerCursesGUI.cpp | 409 +++++++++++++++++++++++++++----- 1 file changed, 356 insertions(+), 53 deletions(-) diff --git a/lldb/source/Core/IOHandlerCursesGUI.cpp b/lldb/source/Core/IOHandlerCursesGUI.cpp index f1872af..0ae41de 100644 --- a/lldb/source/Core/IOHandlerCursesGUI.cpp +++ b/lldb/source/Core/IOHandlerCursesGUI.cpp @@ -45,6 +45,7 @@ #include "lldb/Core/ValueObject.h" #include "lldb/Core/ValueObjectRegister.h" #include "lldb/Symbol/Block.h" +#include "lldb/Symbol/CompileUnit.h" #include "lldb/Symbol/Function.h" #include "lldb/Symbol/Symbol.h" #include "lldb/Symbol/VariableList.h" @@ -3764,9 +3765,14 @@ public: TreeItem *&selected_item) { return; } - virtual bool TreeDelegateItemSelected( - TreeItem &item) = 0; // Return true if we need to update views + // This is invoked when a tree item is selected. If true is returned, the + // views are updated. + virtual bool TreeDelegateItemSelected(TreeItem &item) = 0; virtual bool TreeDelegateExpandRootByDefault() { return false; } + // This is mostly useful for root tree delegates. If false is returned, + // drawing will be skipped completely. This is needed, for instance, in + // skipping drawing of the threads tree if there is no running process. + virtual bool TreeDelegateShouldDraw() { return true; } }; typedef std::shared_ptr TreeDelegateSP; @@ -3956,6 +3962,16 @@ public: void SetIdentifier(uint64_t identifier) { m_identifier = identifier; } + const std::string &GetText() const { return m_text; } + + void SetText(const char *text) { + if (text == nullptr) { + m_text.clear(); + return; + } + m_text = text; + } + void SetMightHaveChildren(bool b) { m_might_have_children = b; } protected: @@ -3963,6 +3979,7 @@ protected: TreeDelegate &m_delegate; void *m_user_data; uint64_t m_identifier; + std::string m_text; int m_row_idx; // Zero based visible row index, -1 if not visible or for the // root item std::vector m_children; @@ -3981,21 +3998,6 @@ public: int NumVisibleRows() const { return m_max_y - m_min_y; } bool WindowDelegateDraw(Window &window, bool force) override { - ExecutionContext exe_ctx( - m_debugger.GetCommandInterpreter().GetExecutionContext()); - Process *process = exe_ctx.GetProcessPtr(); - - bool display_content = false; - if (process) { - StateType state = process->GetState(); - if (StateIsStoppedState(state, true)) { - // We are stopped, so it is ok to - display_content = true; - } else if (StateIsRunningState(state)) { - return true; // Don't do any updating when we are running - } - } - m_min_x = 2; m_min_y = 1; m_max_x = window.GetWidth() - 1; @@ -4004,35 +4006,36 @@ public: window.Erase(); window.DrawTitleBox(window.GetName()); - if (display_content) { - const int num_visible_rows = NumVisibleRows(); - m_num_rows = 0; - m_root.CalculateRowIndexes(m_num_rows); - m_delegate_sp->TreeDelegateUpdateSelection(m_root, m_selected_row_idx, - m_selected_item); - - // If we unexpanded while having something selected our total number of - // rows is less than the num visible rows, then make sure we show all the - // rows by setting the first visible row accordingly. - if (m_first_visible_row > 0 && m_num_rows < num_visible_rows) - m_first_visible_row = 0; - - // Make sure the selected row is always visible - if (m_selected_row_idx < m_first_visible_row) - m_first_visible_row = m_selected_row_idx; - else if (m_first_visible_row + num_visible_rows <= m_selected_row_idx) - m_first_visible_row = m_selected_row_idx - num_visible_rows + 1; - - int row_idx = 0; - int num_rows_left = num_visible_rows; - m_root.Draw(window, m_first_visible_row, m_selected_row_idx, row_idx, - num_rows_left); - // Get the selected row - m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); - } else { + if (!m_delegate_sp->TreeDelegateShouldDraw()) { m_selected_item = nullptr; + return true; } + const int num_visible_rows = NumVisibleRows(); + m_num_rows = 0; + m_root.CalculateRowIndexes(m_num_rows); + m_delegate_sp->TreeDelegateUpdateSelection(m_root, m_selected_row_idx, + m_selected_item); + + // If we unexpanded while having something selected our total number of + // rows is less than the num visible rows, then make sure we show all the + // rows by setting the first visible row accordingly. + if (m_first_visible_row > 0 && m_num_rows < num_visible_rows) + m_first_visible_row = 0; + + // Make sure the selected row is always visible + if (m_selected_row_idx < m_first_visible_row) + m_first_visible_row = m_selected_row_idx; + else if (m_first_visible_row + num_visible_rows <= m_selected_row_idx) + m_first_visible_row = m_selected_row_idx - num_visible_rows + 1; + + int row_idx = 0; + int num_rows_left = num_visible_rows; + m_root.Draw(window, m_first_visible_row, m_selected_row_idx, row_idx, + num_rows_left); + // Get the selected row + m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); + return true; // Drawing handled } @@ -4160,6 +4163,23 @@ protected: int m_max_y; }; +// A tree delegate that just draws the text member of the tree item, it doesn't +// have any children or actions. +class TextTreeDelegate : public TreeDelegate { +public: + TextTreeDelegate() : TreeDelegate() {} + + ~TextTreeDelegate() override = default; + + void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override { + window.PutCStringTruncated(1, item.GetText().c_str()); + } + + void TreeDelegateGenerateChildren(TreeItem &item) override {} + + bool TreeDelegateItemSelected(TreeItem &item) override { return false; } +}; + class FrameTreeDelegate : public TreeDelegate { public: FrameTreeDelegate() : TreeDelegate() { @@ -4324,6 +4344,17 @@ public: .GetProcessSP(); } + bool TreeDelegateShouldDraw() override { + ProcessSP process = GetProcess(); + if (!process) + return false; + + if (StateIsRunningState(process->GetState())) + return false; + + return true; + } + void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override { ProcessSP process_sp = GetProcess(); if (process_sp && process_sp->IsAlive()) { @@ -4415,6 +4446,240 @@ protected: FormatEntity::Entry m_format; }; +class BreakpointLocationTreeDelegate : public TreeDelegate { +public: + BreakpointLocationTreeDelegate(Debugger &debugger) + : TreeDelegate(), m_debugger(debugger) {} + + ~BreakpointLocationTreeDelegate() override = default; + + Process *GetProcess() { + ExecutionContext exe_ctx( + m_debugger.GetCommandInterpreter().GetExecutionContext()); + return exe_ctx.GetProcessPtr(); + } + + BreakpointLocationSP GetBreakpointLocation(const TreeItem &item) { + Breakpoint *breakpoint = (Breakpoint *)item.GetUserData(); + return breakpoint->GetLocationAtIndex(item.GetIdentifier()); + } + + void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override { + BreakpointLocationSP breakpoint_location = GetBreakpointLocation(item); + Process *process = GetProcess(); + StreamString stream; + stream.Printf("%i.%i: ", breakpoint_location->GetBreakpoint().GetID(), + breakpoint_location->GetID()); + Address address = breakpoint_location->GetAddress(); + address.Dump(&stream, process, Address::DumpStyleResolvedDescription, + Address::DumpStyleInvalid); + window.PutCStringTruncated(1, stream.GetString().str().c_str()); + } + + StringList ComputeDetailsList(BreakpointLocationSP breakpoint_location) { + StringList details; + + Address address = breakpoint_location->GetAddress(); + SymbolContext symbol_context; + address.CalculateSymbolContext(&symbol_context); + + if (symbol_context.module_sp) { + StreamString module_stream; + module_stream.PutCString("module = "); + symbol_context.module_sp->GetFileSpec().Dump( + module_stream.AsRawOstream()); + details.AppendString(module_stream.GetString()); + } + + if (symbol_context.comp_unit != nullptr) { + StreamString compile_unit_stream; + compile_unit_stream.PutCString("compile unit = "); + symbol_context.comp_unit->GetPrimaryFile().GetFilename().Dump( + &compile_unit_stream); + details.AppendString(compile_unit_stream.GetString()); + + if (symbol_context.function != nullptr) { + StreamString function_stream; + function_stream.PutCString("function = "); + function_stream.PutCString( + symbol_context.function->GetName().AsCString("")); + details.AppendString(function_stream.GetString()); + } + + if (symbol_context.line_entry.line > 0) { + StreamString location_stream; + location_stream.PutCString("location = "); + symbol_context.line_entry.DumpStopContext(&location_stream, true); + details.AppendString(location_stream.GetString()); + } + + } else { + if (symbol_context.symbol) { + StreamString symbol_stream; + if (breakpoint_location->IsReExported()) + symbol_stream.PutCString("re-exported target = "); + else + symbol_stream.PutCString("symbol = "); + symbol_stream.PutCString( + symbol_context.symbol->GetName().AsCString("")); + details.AppendString(symbol_stream.GetString()); + } + } + + Process *process = GetProcess(); + + StreamString address_stream; + address.Dump(&address_stream, process, Address::DumpStyleLoadAddress, + Address::DumpStyleModuleWithFileAddress); + details.AppendString(address_stream.GetString()); + + BreakpointSiteSP breakpoint_site = breakpoint_location->GetBreakpointSite(); + if (breakpoint_location->IsIndirect() && breakpoint_site) { + Address resolved_address; + resolved_address.SetLoadAddress(breakpoint_site->GetLoadAddress(), + &breakpoint_location->GetTarget()); + Symbol *resolved_symbol = resolved_address.CalculateSymbolContextSymbol(); + if (resolved_symbol) { + StreamString indirect_target_stream; + indirect_target_stream.PutCString("indirect target = "); + indirect_target_stream.PutCString( + resolved_symbol->GetName().GetCString()); + details.AppendString(indirect_target_stream.GetString()); + } + } + + bool is_resolved = breakpoint_location->IsResolved(); + StreamString resolved_stream; + resolved_stream.Printf("resolved = %s", is_resolved ? "true" : "false"); + details.AppendString(resolved_stream.GetString()); + + bool is_hardware = is_resolved && breakpoint_site->IsHardware(); + StreamString hardware_stream; + hardware_stream.Printf("hardware = %s", is_hardware ? "true" : "false"); + details.AppendString(hardware_stream.GetString()); + + StreamString hit_count_stream; + hit_count_stream.Printf("hit count = %-4u", + breakpoint_location->GetHitCount()); + details.AppendString(hit_count_stream.GetString()); + + return details; + } + + void TreeDelegateGenerateChildren(TreeItem &item) override { + BreakpointLocationSP breakpoint_location = GetBreakpointLocation(item); + StringList details = ComputeDetailsList(breakpoint_location); + + if (!m_string_delegate_sp) + m_string_delegate_sp = std::make_shared(); + TreeItem details_tree_item(&item, *m_string_delegate_sp, false); + + item.Resize(details.GetSize(), details_tree_item); + for (size_t i = 0; i < details.GetSize(); i++) { + item[i].SetText(details.GetStringAtIndex(i)); + } + } + + bool TreeDelegateItemSelected(TreeItem &item) override { return false; } + +protected: + Debugger &m_debugger; + std::shared_ptr m_string_delegate_sp; +}; + +class BreakpointTreeDelegate : public TreeDelegate { +public: + BreakpointTreeDelegate(Debugger &debugger) + : TreeDelegate(), m_debugger(debugger), + m_breakpoint_location_delegate_sp() {} + + ~BreakpointTreeDelegate() override = default; + + BreakpointSP GetBreakpoint(const TreeItem &item) { + TargetSP target = m_debugger.GetSelectedTarget(); + BreakpointList &breakpoints = target->GetBreakpointList(false); + return breakpoints.GetBreakpointAtIndex(item.GetIdentifier()); + } + + void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override { + BreakpointSP breakpoint = GetBreakpoint(item); + StreamString stream; + stream.Format("{0}: ", breakpoint->GetID()); + breakpoint->GetResolverDescription(&stream); + breakpoint->GetFilterDescription(&stream); + window.PutCStringTruncated(1, stream.GetString().str().c_str()); + } + + void TreeDelegateGenerateChildren(TreeItem &item) override { + BreakpointSP breakpoint = GetBreakpoint(item); + + if (!m_breakpoint_location_delegate_sp) + m_breakpoint_location_delegate_sp = + std::make_shared(m_debugger); + TreeItem breakpoint_location_tree_item( + &item, *m_breakpoint_location_delegate_sp, true); + + item.Resize(breakpoint->GetNumLocations(), breakpoint_location_tree_item); + for (size_t i = 0; i < breakpoint->GetNumLocations(); i++) { + item[i].SetIdentifier(i); + item[i].SetUserData(breakpoint.get()); + } + } + + bool TreeDelegateItemSelected(TreeItem &item) override { return false; } + +protected: + Debugger &m_debugger; + std::shared_ptr + m_breakpoint_location_delegate_sp; +}; + +class BreakpointsTreeDelegate : public TreeDelegate { +public: + BreakpointsTreeDelegate(Debugger &debugger) + : TreeDelegate(), m_debugger(debugger), m_breakpoint_delegate_sp() {} + + ~BreakpointsTreeDelegate() override = default; + + bool TreeDelegateShouldDraw() override { + TargetSP target = m_debugger.GetSelectedTarget(); + if (!target) + return false; + + return true; + } + + void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override { + window.PutCString("Breakpoints"); + } + + void TreeDelegateGenerateChildren(TreeItem &item) override { + TargetSP target = m_debugger.GetSelectedTarget(); + + BreakpointList &breakpoints = target->GetBreakpointList(false); + std::unique_lock lock; + breakpoints.GetListMutex(lock); + + if (!m_breakpoint_delegate_sp) + m_breakpoint_delegate_sp = + std::make_shared(m_debugger); + TreeItem breakpoint_tree_item(&item, *m_breakpoint_delegate_sp, true); + + item.Resize(breakpoints.GetSize(), breakpoint_tree_item); + for (size_t i = 0; i < breakpoints.GetSize(); i++) { + item[i].SetIdentifier(i); + } + } + + bool TreeDelegateItemSelected(TreeItem &item) override { return false; } + + bool TreeDelegateExpandRootByDefault() override { return true; } + +protected: + Debugger &m_debugger; + std::shared_ptr m_breakpoint_delegate_sp; +}; + class ValueObjectListDelegate : public WindowDelegate { public: ValueObjectListDelegate() : m_rows() {} @@ -5216,6 +5481,7 @@ public: eMenuID_ViewRegisters, eMenuID_ViewSource, eMenuID_ViewVariables, + eMenuID_ViewBreakpoints, eMenuID_Help, eMenuID_HelpGUIHelp @@ -5430,8 +5696,8 @@ public: // previously added submenus.erase(submenus.begin() + 7, submenus.end()); } - // Since we are adding and removing items we need to recalculate the name - // lengths + // Since we are adding and removing items we need to recalculate the + // name lengths menu.RecalculateNameLengths(); } return MenuActionResult::Handled; @@ -5539,6 +5805,39 @@ public: } return MenuActionResult::Handled; + case eMenuID_ViewBreakpoints: { + WindowSP main_window_sp = m_app.GetMainWindow(); + WindowSP threads_window_sp = main_window_sp->FindSubWindow("Threads"); + WindowSP breakpoints_window_sp = + main_window_sp->FindSubWindow("Breakpoints"); + const Rect threads_bounds = threads_window_sp->GetBounds(); + + // If a breakpoints window already exists, remove it and give the area + // it used to occupy to the threads window. If it doesn't exist, split + // the threads window horizontally into two windows where the top window + // is the threads window and the bottom window is a newly added + // breakpoints window. + if (breakpoints_window_sp) { + threads_window_sp->Resize(threads_bounds.size.width, + threads_bounds.size.height + + breakpoints_window_sp->GetHeight()); + main_window_sp->RemoveSubWindow(breakpoints_window_sp.get()); + } else { + Rect new_threads_bounds, breakpoints_bounds; + threads_bounds.HorizontalSplitPercentage(0.70, new_threads_bounds, + breakpoints_bounds); + threads_window_sp->SetBounds(new_threads_bounds); + breakpoints_window_sp = main_window_sp->CreateSubWindow( + "Breakpoints", breakpoints_bounds, false); + TreeDelegateSP breakpoints_delegate_sp( + new BreakpointsTreeDelegate(m_debugger)); + breakpoints_window_sp->SetDelegate(WindowDelegateSP( + new TreeWindowDelegate(m_debugger, breakpoints_delegate_sp))); + } + touchwin(stdscr); + return MenuActionResult::Handled; + } + case eMenuID_HelpGUIHelp: m_app.GetMainWindow()->CreateHelpSubwindow(); return MenuActionResult::Handled; @@ -5731,8 +6030,8 @@ public: m_selected_line = m_pc_line; if (m_file_sp && m_file_sp->GetFileSpec() == m_sc.line_entry.file) { - // Same file, nothing to do, we should either have the lines or not - // (source file missing) + // Same file, nothing to do, we should either have the lines or + // not (source file missing) if (m_selected_line >= static_cast(m_first_visible_line)) { if (m_selected_line >= m_first_visible_line + num_visible_lines) m_first_visible_line = m_selected_line - 10; @@ -5854,8 +6153,8 @@ public: window.MoveCursor(1, line_y); const bool is_pc_line = curr_line == m_pc_line; const bool line_is_selected = m_selected_line == curr_line; - // Highlight the line as the PC line first, then if the selected line - // isn't the same as the PC line, highlight it differently + // Highlight the line as the PC line first, then if the selected + // line isn't the same as the PC line, highlight it differently attr_t highlight_attr = 0; attr_t bp_attr = 0; if (is_pc_line) @@ -5994,8 +6293,8 @@ public: window.MoveCursor(1, line_y); const bool is_pc_line = frame_sp && inst_idx == pc_idx; const bool line_is_selected = m_selected_line == inst_idx; - // Highlight the line as the PC line first, then if the selected line - // isn't the same as the PC line, highlight it differently + // Highlight the line as the PC line first, then if the selected + // line isn't the same as the PC line, highlight it differently attr_t highlight_attr = 0; attr_t bp_attr = 0; if (is_pc_line) @@ -6459,7 +6758,7 @@ void IOHandlerCursesGUI::Activate() { MenuSP view_menu_sp( new Menu("View", "F5", KEY_F(5), ApplicationDelegate::eMenuID_View)); view_menu_sp->AddSubmenu( - MenuSP(new Menu("Backtrace", nullptr, 'b', + MenuSP(new Menu("Backtrace", nullptr, 't', ApplicationDelegate::eMenuID_ViewBacktrace))); view_menu_sp->AddSubmenu( MenuSP(new Menu("Registers", nullptr, 'r', @@ -6469,6 +6768,9 @@ void IOHandlerCursesGUI::Activate() { view_menu_sp->AddSubmenu( MenuSP(new Menu("Variables", nullptr, 'v', ApplicationDelegate::eMenuID_ViewVariables))); + view_menu_sp->AddSubmenu( + MenuSP(new Menu("Breakpoints", nullptr, 'b', + ApplicationDelegate::eMenuID_ViewBreakpoints))); MenuSP help_menu_sp( new Menu("Help", "F6", KEY_F(6), ApplicationDelegate::eMenuID_Help)); @@ -6529,7 +6831,8 @@ void IOHandlerCursesGUI::Activate() { status_window_sp->SetDelegate( WindowDelegateSP(new StatusBarWindowDelegate(m_debugger))); - // Show the main help window once the first time the curses GUI is launched + // Show the main help window once the first time the curses GUI is + // launched static bool g_showed_help = false; if (!g_showed_help) { g_showed_help = true; -- 2.7.4