//===-- IOHandler.cpp -------------------------------------------*- C++ -*-===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// #include "lldb/lldb-python.h" #include #include "lldb/Breakpoint/BreakpointLocation.h" #include "lldb/Core/IOHandler.h" #include "lldb/Core/Debugger.h" #include "lldb/Core/State.h" #include "lldb/Core/StreamFile.h" #include "lldb/Core/ValueObjectRegister.h" #include "lldb/Host/Editline.h" #include "lldb/Interpreter/CommandCompletions.h" #include "lldb/Interpreter/CommandInterpreter.h" #include "lldb/Symbol/Block.h" #include "lldb/Symbol/Function.h" #include "lldb/Symbol/Symbol.h" #include "lldb/Target/RegisterContext.h" #include "lldb/Target/ThreadPlan.h" #ifndef LLDB_DISABLE_CURSES #include #include #endif using namespace lldb; using namespace lldb_private; IOHandler::IOHandler (Debugger &debugger) : IOHandler (debugger, StreamFileSP(), // Adopt STDIN from top input reader StreamFileSP(), // Adopt STDOUT from top input reader StreamFileSP(), // Adopt STDERR from top input reader 0) // Flags { } IOHandler::IOHandler (Debugger &debugger, const lldb::StreamFileSP &input_sp, const lldb::StreamFileSP &output_sp, const lldb::StreamFileSP &error_sp, uint32_t flags) : m_debugger (debugger), m_input_sp (input_sp), m_output_sp (output_sp), m_error_sp (error_sp), m_flags (flags), m_user_data (NULL), m_done (false), m_active (false) { // If any files are not specified, then adopt them from the top input reader. if (!m_input_sp || !m_output_sp || !m_error_sp) debugger.AdoptTopIOHandlerFilesIfInvalid (m_input_sp, m_output_sp, m_error_sp); } IOHandler::~IOHandler() { } int IOHandler::GetInputFD() { if (m_input_sp) return m_input_sp->GetFile().GetDescriptor(); return -1; } int IOHandler::GetOutputFD() { if (m_output_sp) return m_output_sp->GetFile().GetDescriptor(); return -1; } int IOHandler::GetErrorFD() { if (m_error_sp) return m_error_sp->GetFile().GetDescriptor(); return -1; } FILE * IOHandler::GetInputFILE() { if (m_input_sp) return m_input_sp->GetFile().GetStream(); return NULL; } FILE * IOHandler::GetOutputFILE() { if (m_output_sp) return m_output_sp->GetFile().GetStream(); return NULL; } FILE * IOHandler::GetErrorFILE() { if (m_error_sp) return m_error_sp->GetFile().GetStream(); return NULL; } StreamFileSP & IOHandler::GetInputStreamFile() { return m_input_sp; } StreamFileSP & IOHandler::GetOutputStreamFile() { return m_output_sp; } StreamFileSP & IOHandler::GetErrorStreamFile() { return m_error_sp; } bool IOHandler::GetIsInteractive () { return GetInputStreamFile()->GetFile().GetIsInteractive (); } bool IOHandler::GetIsRealTerminal () { return GetInputStreamFile()->GetFile().GetIsRealTerminal(); } IOHandlerConfirm::IOHandlerConfirm (Debugger &debugger, const char *prompt, bool default_response) : IOHandlerEditline(debugger, NULL, // NULL editline_name means no history loaded/saved NULL, false, // Multi-line *this), m_default_response (default_response), m_user_response (default_response) { StreamString prompt_stream; prompt_stream.PutCString(prompt); if (m_default_response) prompt_stream.Printf(": [Y/n] "); else prompt_stream.Printf(": [y/N] "); SetPrompt (prompt_stream.GetString().c_str()); } IOHandlerConfirm::~IOHandlerConfirm () { } int IOHandlerConfirm::IOHandlerComplete (IOHandler &io_handler, const char *current_line, const char *cursor, const char *last_char, int skip_first_n_matches, int max_matches, StringList &matches) { if (current_line == cursor) { if (m_default_response) { matches.AppendString("y"); } else { matches.AppendString("n"); } } return matches.GetSize(); } void IOHandlerConfirm::IOHandlerInputComplete (IOHandler &io_handler, std::string &line) { if (line.empty()) { // User just hit enter, set the response to the default m_user_response = m_default_response; io_handler.SetIsDone(true); return; } if (line.size() == 1) { switch (line[0]) { case 'y': case 'Y': m_user_response = true; io_handler.SetIsDone(true); return; case 'n': case 'N': m_user_response = false; io_handler.SetIsDone(true); return; default: break; } } if (line == "yes" || line == "YES" || line == "Yes") { m_user_response = true; io_handler.SetIsDone(true); } else if (line == "no" || line == "NO" || line == "No") { m_user_response = false; io_handler.SetIsDone(true); } } int IOHandlerDelegate::IOHandlerComplete (IOHandler &io_handler, const char *current_line, const char *cursor, const char *last_char, int skip_first_n_matches, int max_matches, StringList &matches) { switch (m_completion) { case Completion::None: break; case Completion::LLDBCommand: return io_handler.GetDebugger().GetCommandInterpreter().HandleCompletion (current_line, cursor, last_char, skip_first_n_matches, max_matches, matches); case Completion::Expression: { bool word_complete = false; const char *word_start = cursor; if (cursor > current_line) --word_start; while (word_start > current_line && !isspace(*word_start)) --word_start; CommandCompletions::InvokeCommonCompletionCallbacks (io_handler.GetDebugger().GetCommandInterpreter(), CommandCompletions::eVariablePathCompletion, word_start, skip_first_n_matches, max_matches, NULL, word_complete, matches); size_t num_matches = matches.GetSize(); if (num_matches > 0) { std::string common_prefix; matches.LongestCommonPrefix (common_prefix); const size_t partial_name_len = strlen(word_start); // If we matched a unique single command, add a space... // Only do this if the completer told us this was a complete word, however... if (num_matches == 1 && word_complete) { common_prefix.push_back(' '); } common_prefix.erase (0, partial_name_len); matches.InsertStringAtIndex(0, std::move(common_prefix)); } return num_matches; } break; } return 0; } IOHandlerEditline::IOHandlerEditline (Debugger &debugger, const char *editline_name, // Used for saving history files const char *prompt, bool multi_line, IOHandlerDelegate &delegate) : IOHandlerEditline(debugger, StreamFileSP(), // Inherit input from top input reader StreamFileSP(), // Inherit output from top input reader StreamFileSP(), // Inherit error from top input reader 0, // Flags editline_name, // Used for saving history files prompt, multi_line, delegate) { } IOHandlerEditline::IOHandlerEditline (Debugger &debugger, const lldb::StreamFileSP &input_sp, const lldb::StreamFileSP &output_sp, const lldb::StreamFileSP &error_sp, uint32_t flags, const char *editline_name, // Used for saving history files const char *prompt, bool multi_line, IOHandlerDelegate &delegate) : IOHandler (debugger, input_sp, output_sp, error_sp, flags), m_editline_ap (), m_delegate (delegate), m_prompt (), m_multi_line (multi_line) { SetPrompt(prompt); bool use_editline = false; #ifndef _MSC_VER use_editline = m_input_sp->GetFile().GetIsRealTerminal(); #else use_editline = true; #endif if (use_editline) { m_editline_ap.reset(new Editline (editline_name, prompt ? prompt : "", GetInputFILE (), GetOutputFILE (), GetErrorFILE ())); m_editline_ap->SetLineCompleteCallback (LineCompletedCallback, this); m_editline_ap->SetAutoCompleteCallback (AutoCompleteCallback, this); } } IOHandlerEditline::~IOHandlerEditline () { m_editline_ap.reset(); } bool IOHandlerEditline::GetLine (std::string &line) { if (m_editline_ap) { return m_editline_ap->GetLine(line).Success(); } else { line.clear(); FILE *in = GetInputFILE(); if (in) { if (GetIsInteractive()) { const char *prompt = GetPrompt(); if (prompt && prompt[0]) { FILE *out = GetOutputFILE(); if (out) { ::fprintf(out, "%s", prompt); ::fflush(out); } } } char buffer[256]; bool done = false; bool got_line = false; while (!done) { if (fgets(buffer, sizeof(buffer), in) == NULL) done = true; else { got_line = true; size_t buffer_len = strlen(buffer); assert (buffer[buffer_len] == '\0'); char last_char = buffer[buffer_len-1]; if (last_char == '\r' || last_char == '\n') { done = true; // Strip trailing newlines while (last_char == '\r' || last_char == '\n') { --buffer_len; if (buffer_len == 0) break; last_char = buffer[buffer_len-1]; } } line.append(buffer, buffer_len); } } // We might have gotten a newline on a line by itself // make sure to return true in this case. return got_line; } else { // No more input file, we are done... SetIsDone(true); } return false; } } LineStatus IOHandlerEditline::LineCompletedCallback (Editline *editline, StringList &lines, uint32_t line_idx, Error &error, void *baton) { IOHandlerEditline *editline_reader = (IOHandlerEditline *) baton; return editline_reader->m_delegate.IOHandlerLinesUpdated(*editline_reader, lines, line_idx, error); } int IOHandlerEditline::AutoCompleteCallback (const char *current_line, const char *cursor, const char *last_char, int skip_first_n_matches, int max_matches, StringList &matches, void *baton) { IOHandlerEditline *editline_reader = (IOHandlerEditline *) baton; if (editline_reader) return editline_reader->m_delegate.IOHandlerComplete (*editline_reader, current_line, cursor, last_char, skip_first_n_matches, max_matches, matches); return 0; } const char * IOHandlerEditline::GetPrompt () { if (m_editline_ap) return m_editline_ap->GetPrompt (); else if (m_prompt.empty()) return NULL; return m_prompt.c_str(); } bool IOHandlerEditline::SetPrompt (const char *p) { if (p && p[0]) m_prompt = p; else m_prompt.clear(); if (m_editline_ap) m_editline_ap->SetPrompt (m_prompt.empty() ? NULL : m_prompt.c_str()); return true; } bool IOHandlerEditline::GetLines (StringList &lines) { bool success = false; if (m_editline_ap) { std::string end_token; success = m_editline_ap->GetLines(end_token, lines).Success(); } else { LineStatus lines_status = LineStatus::Success; while (lines_status == LineStatus::Success) { std::string line; if (GetLine(line)) { lines.AppendString(line); Error error; lines_status = m_delegate.IOHandlerLinesUpdated(*this, lines, lines.GetSize() - 1, error); } else { lines_status = LineStatus::Done; } } success = lines.GetSize() > 0; } return success; } // Each IOHandler gets to run until it is done. It should read data // from the "in" and place output into "out" and "err and return // when done. void IOHandlerEditline::Run () { std::string line; while (IsActive()) { if (m_multi_line) { StringList lines; if (GetLines (lines)) { line = lines.CopyList(); m_delegate.IOHandlerInputComplete(*this, line); } else { m_done = true; } } else { if (GetLine(line)) { m_delegate.IOHandlerInputComplete(*this, line); } else { m_done = true; } } } } void IOHandlerEditline::Hide () { if (m_editline_ap && m_editline_ap->GettingLine()) m_editline_ap->Hide(); } void IOHandlerEditline::Refresh () { if (m_editline_ap && m_editline_ap->GettingLine()) m_editline_ap->Refresh(); else { const char *prompt = GetPrompt(); if (prompt && prompt[0]) { FILE *out = GetOutputFILE(); if (out) { ::fprintf(out, "%s", prompt); ::fflush(out); } } } } void IOHandlerEditline::Cancel () { if (m_editline_ap) m_editline_ap->Interrupt (); } void IOHandlerEditline::Interrupt () { if (m_editline_ap) m_editline_ap->Interrupt(); } void IOHandlerEditline::GotEOF() { if (m_editline_ap) m_editline_ap->Interrupt(); } // we may want curses to be disabled for some builds // for instance, windows #ifndef LLDB_DISABLE_CURSES #include "lldb/Core/ValueObject.h" #include "lldb/Symbol/VariableList.h" #include "lldb/Target/Target.h" #include "lldb/Target/Process.h" #include "lldb/Target/Thread.h" #include "lldb/Target/StackFrame.h" #define KEY_RETURN 10 #define KEY_ESCAPE 27 namespace curses { class Menu; class MenuDelegate; class Window; class WindowDelegate; typedef std::shared_ptr MenuSP; typedef std::shared_ptr MenuDelegateSP; typedef std::shared_ptr WindowSP; typedef std::shared_ptr WindowDelegateSP; typedef std::vector Menus; typedef std::vector Windows; typedef std::vector WindowDelegates; #if 0 type summary add -s "x=${var.x}, y=${var.y}" curses::Point type summary add -s "w=${var.width}, h=${var.height}" curses::Size type summary add -s "${var.origin%S} ${var.size%S}" curses::Rect #endif struct Point { int x; int y; Point (int _x = 0, int _y = 0) : x(_x), y(_y) { } void Clear () { x = 0; y = 0; } Point & operator += (const Point &rhs) { x += rhs.x; y += rhs.y; return *this; } void Dump () { printf ("(x=%i, y=%i)\n", x, y); } }; bool operator == (const Point &lhs, const Point &rhs) { return lhs.x == rhs.x && lhs.y == rhs.y; } bool operator != (const Point &lhs, const Point &rhs) { return lhs.x != rhs.x || lhs.y != rhs.y; } struct Size { int width; int height; Size (int w = 0, int h = 0) : width (w), height (h) { } void Clear () { width = 0; height = 0; } void Dump () { printf ("(w=%i, h=%i)\n", width, height); } }; bool operator == (const Size &lhs, const Size &rhs) { return lhs.width == rhs.width && lhs.height == rhs.height; } bool operator != (const Size &lhs, const Size &rhs) { return lhs.width != rhs.width || lhs.height != rhs.height; } struct Rect { Point origin; Size size; Rect () : origin(), size() { } Rect (const Point &p, const Size &s) : origin (p), size (s) { } void Clear () { origin.Clear(); size.Clear(); } void Dump () { printf ("(x=%i, y=%i), w=%i, h=%i)\n", origin.x, origin.y, size.width, size.height); } void Inset (int w, int h) { if (size.width > w*2) size.width -= w*2; origin.x += w; if (size.height > h*2) size.height -= h*2; origin.y += h; } // Return a status bar rectangle which is the last line of // this rectangle. This rectangle will be modified to not // include the status bar area. Rect MakeStatusBar () { Rect status_bar; if (size.height > 1) { status_bar.origin.x = origin.x; status_bar.origin.y = size.height; status_bar.size.width = size.width; status_bar.size.height = 1; --size.height; } return status_bar; } // Return a menubar rectangle which is the first line of // this rectangle. This rectangle will be modified to not // include the menubar area. Rect MakeMenuBar () { Rect menubar; if (size.height > 1) { menubar.origin.x = origin.x; menubar.origin.y = origin.y; menubar.size.width = size.width; menubar.size.height = 1; ++origin.y; --size.height; } return menubar; } void HorizontalSplitPercentage (float top_percentage, Rect &top, Rect &bottom) const { float top_height = top_percentage * size.height; HorizontalSplit (top_height, top, bottom); } void HorizontalSplit (int top_height, Rect &top, Rect &bottom) const { top = *this; if (top_height < size.height) { top.size.height = top_height; bottom.origin.x = origin.x; bottom.origin.y = origin.y + top.size.height; bottom.size.width = size.width; bottom.size.height = size.height - top.size.height; } else { bottom.Clear(); } } void VerticalSplitPercentage (float left_percentage, Rect &left, Rect &right) const { float left_width = left_percentage * size.width; VerticalSplit (left_width, left, right); } void VerticalSplit (int left_width, Rect &left, Rect &right) const { left = *this; if (left_width < size.width) { left.size.width = left_width; right.origin.x = origin.x + left.size.width; right.origin.y = origin.y; right.size.width = size.width - left.size.width; right.size.height = size.height; } else { right.Clear(); } } }; bool operator == (const Rect &lhs, const Rect &rhs) { return lhs.origin == rhs.origin && lhs.size == rhs.size; } bool operator != (const Rect &lhs, const Rect &rhs) { return lhs.origin != rhs.origin || lhs.size != rhs.size; } enum HandleCharResult { eKeyNotHandled = 0, eKeyHandled = 1, eQuitApplication = 2 }; enum class MenuActionResult { Handled, NotHandled, Quit // Exit all menus and quit }; struct KeyHelp { int ch; const char *description; }; class WindowDelegate { public: virtual ~WindowDelegate() { } virtual bool WindowDelegateDraw (Window &window, bool force) { return false; // Drawing not handled } virtual HandleCharResult WindowDelegateHandleChar (Window &window, int key) { return eKeyNotHandled; } virtual const char * WindowDelegateGetHelpText () { return NULL; } virtual KeyHelp * WindowDelegateGetKeyHelp () { return NULL; } }; class HelpDialogDelegate : public WindowDelegate { public: HelpDialogDelegate (const char *text, KeyHelp *key_help_array); virtual ~HelpDialogDelegate(); virtual bool WindowDelegateDraw (Window &window, bool force); virtual HandleCharResult WindowDelegateHandleChar (Window &window, int key); size_t GetNumLines() const { return m_text.GetSize(); } size_t GetMaxLineLength () const { return m_text.GetMaxStringLength(); } protected: StringList m_text; int m_first_visible_line; }; class Window { public: Window (const char *name) : m_name (name), m_window (NULL), m_panel (NULL), m_parent (NULL), m_subwindows (), m_delegate_sp (), m_curr_active_window_idx (UINT32_MAX), m_prev_active_window_idx (UINT32_MAX), m_delete (false), m_needs_update (true), m_can_activate (true), m_is_subwin (false) { } Window (const char *name, WINDOW *w, bool del = true) : m_name (name), m_window (NULL), m_panel (NULL), m_parent (NULL), m_subwindows (), m_delegate_sp (), m_curr_active_window_idx (UINT32_MAX), m_prev_active_window_idx (UINT32_MAX), m_delete (del), m_needs_update (true), m_can_activate (true), m_is_subwin (false) { if (w) Reset(w); } Window (const char *name, const Rect &bounds) : m_name (name), m_window (NULL), m_parent (NULL), m_subwindows (), m_delegate_sp (), m_curr_active_window_idx (UINT32_MAX), m_prev_active_window_idx (UINT32_MAX), m_delete (true), m_needs_update (true), m_can_activate (true), m_is_subwin (false) { Reset (::newwin (bounds.size.height, bounds.size.width, bounds.origin.y, bounds.origin.y)); } virtual ~Window () { RemoveSubWindows (); Reset (); } void Reset (WINDOW *w = NULL, bool del = true) { if (m_window == w) return; if (m_panel) { ::del_panel (m_panel); m_panel = NULL; } if (m_window && m_delete) { ::delwin (m_window); m_window = NULL; m_delete = false; } if (w) { m_window = w; m_panel = ::new_panel (m_window); m_delete = del; } } void AttributeOn (attr_t attr) { ::wattron (m_window, attr); } void AttributeOff (attr_t attr) { ::wattroff (m_window, attr); } void Box (chtype v_char = ACS_VLINE, chtype h_char = ACS_HLINE) { ::box(m_window, v_char, h_char); } void Clear () { ::wclear (m_window); } void Erase () { ::werase (m_window); } Rect GetBounds () { return Rect (GetParentOrigin(), GetSize()); } // Get the rectangle in our parent window int GetChar () { return ::wgetch (m_window); } int GetCursorX () { return getcurx (m_window); } int GetCursorY () { return getcury (m_window); } Rect GetFrame () { return Rect (Point(), GetSize()); } // Get our rectangle in our own coordinate system Point GetParentOrigin() { return Point (GetParentX(), GetParentY()); } Size GetSize() { return Size (GetWidth(), GetHeight()); } int GetParentX () { return getparx (m_window); } int GetParentY () { return getpary (m_window); } int GetMaxX() { return getmaxx (m_window); } int GetMaxY() { return getmaxy (m_window); } int GetWidth() { return GetMaxX(); } int GetHeight() { return GetMaxY(); } void MoveCursor (int x, int y) { ::wmove (m_window, y, x); } void MoveWindow (int x, int y) { MoveWindow(Point(x,y)); } void Resize (int w, int h) { ::wresize(m_window, h, w); } void Resize (const Size &size) { ::wresize(m_window, size.height, size.width); } void PutChar (int ch) { ::waddch (m_window, ch); } void PutCString (const char *s, int len = -1) { ::waddnstr (m_window, s, len); } void Refresh () { ::wrefresh (m_window); } void DeferredRefresh () { // We are using panels, so we don't need to call this... //::wnoutrefresh(m_window); } void SetBackground (int color_pair_idx) { ::wbkgd (m_window,COLOR_PAIR(color_pair_idx)); } void UnderlineOn () { AttributeOn(A_UNDERLINE); } void UnderlineOff () { AttributeOff(A_UNDERLINE); } void PutCStringTruncated (const char *s, int right_pad) { int bytes_left = GetWidth() - GetCursorX(); if (bytes_left > right_pad) { bytes_left -= right_pad; ::waddnstr (m_window, s, bytes_left); } } void MoveWindow (const Point &origin) { const bool moving_window = origin != GetParentOrigin(); if (m_is_subwin && moving_window) { // Can't move subwindows, must delete and re-create Size size = GetSize(); Reset (::subwin (m_parent->m_window, size.height, size.width, origin.y, origin.x), true); } else { ::mvwin (m_window, origin.y, origin.x); } } void SetBounds (const Rect &bounds) { const bool moving_window = bounds.origin != GetParentOrigin(); if (m_is_subwin && moving_window) { // Can't move subwindows, must delete and re-create Reset (::subwin (m_parent->m_window, bounds.size.height, bounds.size.width, bounds.origin.y, bounds.origin.x), true); } else { if (moving_window) MoveWindow(bounds.origin); Resize (bounds.size); } } void Printf (const char *format, ...) __attribute__ ((format (printf, 2, 3))) { va_list args; va_start (args, format); vwprintw(m_window, format, args); va_end (args); } void Touch () { ::touchwin (m_window); if (m_parent) m_parent->Touch(); } WindowSP CreateSubWindow (const char *name, const Rect &bounds, bool make_active) { WindowSP subwindow_sp; if (m_window) { subwindow_sp.reset(new Window(name, ::subwin (m_window, bounds.size.height, bounds.size.width, bounds.origin.y, bounds.origin.x), true)); subwindow_sp->m_is_subwin = true; } else { subwindow_sp.reset(new Window(name, ::newwin (bounds.size.height, bounds.size.width, bounds.origin.y, bounds.origin.x), true)); subwindow_sp->m_is_subwin = false; } subwindow_sp->m_parent = this; if (make_active) { m_prev_active_window_idx = m_curr_active_window_idx; m_curr_active_window_idx = m_subwindows.size(); } m_subwindows.push_back(subwindow_sp); ::top_panel (subwindow_sp->m_panel); m_needs_update = true; return subwindow_sp; } bool RemoveSubWindow (Window *window) { Windows::iterator pos, end = m_subwindows.end(); size_t i = 0; for (pos = m_subwindows.begin(); pos != end; ++pos, ++i) { if ((*pos).get() == window) { if (m_prev_active_window_idx == i) m_prev_active_window_idx = UINT32_MAX; else if (m_prev_active_window_idx != UINT32_MAX && m_prev_active_window_idx > i) --m_prev_active_window_idx; if (m_curr_active_window_idx == i) m_curr_active_window_idx = UINT32_MAX; else if (m_curr_active_window_idx != UINT32_MAX && m_curr_active_window_idx > i) --m_curr_active_window_idx; window->Erase(); m_subwindows.erase(pos); m_needs_update = true; if (m_parent) m_parent->Touch(); else ::touchwin (stdscr); return true; } } return false; } WindowSP FindSubWindow (const char *name) { Windows::iterator pos, end = m_subwindows.end(); size_t i = 0; for (pos = m_subwindows.begin(); pos != end; ++pos, ++i) { if ((*pos)->m_name.compare(name) == 0) return *pos; } return WindowSP(); } void RemoveSubWindows () { m_curr_active_window_idx = UINT32_MAX; m_prev_active_window_idx = UINT32_MAX; for (Windows::iterator pos = m_subwindows.begin(); pos != m_subwindows.end(); pos = m_subwindows.erase(pos)) { (*pos)->Erase(); } if (m_parent) m_parent->Touch(); else ::touchwin (stdscr); } WINDOW * get() { return m_window; } operator WINDOW *() { return m_window; } //---------------------------------------------------------------------- // Window drawing utilities //---------------------------------------------------------------------- void DrawTitleBox (const char *title, const char *bottom_message = NULL) { attr_t attr = 0; if (IsActive()) attr = A_BOLD | COLOR_PAIR(2); else attr = 0; if (attr) AttributeOn(attr); Box(); MoveCursor(3, 0); if (title && title[0]) { PutChar ('<'); PutCString (title); PutChar ('>'); } if (bottom_message && bottom_message[0]) { int bottom_message_length = strlen(bottom_message); int x = GetWidth() - 3 - (bottom_message_length + 2); if (x > 0) { MoveCursor (x, GetHeight() - 1); PutChar ('['); PutCString(bottom_message); PutChar (']'); } else { MoveCursor (1, GetHeight() - 1); PutChar ('['); PutCStringTruncated (bottom_message, 1); } } if (attr) AttributeOff(attr); } virtual void Draw (bool force) { if (m_delegate_sp && m_delegate_sp->WindowDelegateDraw (*this, force)) return; for (auto &subwindow_sp : m_subwindows) subwindow_sp->Draw(force); } bool CreateHelpSubwindow () { if (m_delegate_sp) { const char *text = m_delegate_sp->WindowDelegateGetHelpText (); KeyHelp *key_help = m_delegate_sp->WindowDelegateGetKeyHelp (); if ((text && text[0]) || key_help) { std::auto_ptr help_delegate_ap(new HelpDialogDelegate(text, key_help)); const size_t num_lines = help_delegate_ap->GetNumLines(); const size_t max_length = help_delegate_ap->GetMaxLineLength(); Rect bounds = GetBounds(); bounds.Inset(1, 1); if (max_length + 4 < bounds.size.width) { bounds.origin.x += (bounds.size.width - max_length + 4)/2; bounds.size.width = max_length + 4; } else { if (bounds.size.width > 100) { const int inset_w = bounds.size.width / 4; bounds.origin.x += inset_w; bounds.size.width -= 2*inset_w; } } if (num_lines + 2 < bounds.size.height) { bounds.origin.y += (bounds.size.height - num_lines + 2)/2; bounds.size.height = num_lines + 2; } else { if (bounds.size.height > 100) { const int inset_h = bounds.size.height / 4; bounds.origin.y += inset_h; bounds.size.height -= 2*inset_h; } } WindowSP help_window_sp; Window *parent_window = GetParent(); if (parent_window) help_window_sp = parent_window->CreateSubWindow("Help", bounds, true); else help_window_sp = CreateSubWindow("Help", bounds, true); help_window_sp->SetDelegate(WindowDelegateSP(help_delegate_ap.release())); return true; } } return false; } virtual HandleCharResult HandleChar (int key) { // Always check the active window first HandleCharResult result = eKeyNotHandled; WindowSP active_window_sp = GetActiveWindow (); if (active_window_sp) { result = active_window_sp->HandleChar (key); if (result != eKeyNotHandled) return result; } if (m_delegate_sp) { result = m_delegate_sp->WindowDelegateHandleChar (*this, key); if (result != eKeyNotHandled) return result; } // Then check for any windows that want any keys // that weren't handled. This is typically only // for a menubar. // Make a copy of the subwindows in case any HandleChar() // functions muck with the subwindows. If we don't do this, // we can crash when iterating over the subwindows. Windows subwindows (m_subwindows); for (auto subwindow_sp : subwindows) { if (subwindow_sp->m_can_activate == false) { HandleCharResult result = subwindow_sp->HandleChar(key); if (result != eKeyNotHandled) return result; } } return eKeyNotHandled; } bool SetActiveWindow (Window *window) { const size_t num_subwindows = m_subwindows.size(); for (size_t i=0; im_panel); m_curr_active_window_idx = i; return true; } } return false; } WindowSP GetActiveWindow () { if (!m_subwindows.empty()) { if (m_curr_active_window_idx >= m_subwindows.size()) { if (m_prev_active_window_idx < m_subwindows.size()) { m_curr_active_window_idx = m_prev_active_window_idx; m_prev_active_window_idx = UINT32_MAX; } else if (IsActive()) { m_prev_active_window_idx = UINT32_MAX; m_curr_active_window_idx = UINT32_MAX; // Find first window that wants to be active if this window is active const size_t num_subwindows = m_subwindows.size(); for (size_t i=0; iGetCanBeActive()) { m_curr_active_window_idx = i; break; } } } } if (m_curr_active_window_idx < m_subwindows.size()) return m_subwindows[m_curr_active_window_idx]; } return WindowSP(); } bool GetCanBeActive () const { return m_can_activate; } void SetCanBeActive (bool b) { m_can_activate = b; } const WindowDelegateSP & GetDelegate () const { return m_delegate_sp; } void SetDelegate (const WindowDelegateSP &delegate_sp) { m_delegate_sp = delegate_sp; } Window * GetParent () const { return m_parent; } bool IsActive () const { if (m_parent) return m_parent->GetActiveWindow().get() == this; else return true; // Top level window is always active } void SelectNextWindowAsActive () { // Move active focus to next window const size_t num_subwindows = m_subwindows.size(); if (m_curr_active_window_idx == UINT32_MAX) { uint32_t idx = 0; for (auto subwindow_sp : m_subwindows) { if (subwindow_sp->GetCanBeActive()) { m_curr_active_window_idx = idx; break; } ++idx; } } else if (m_curr_active_window_idx + 1 < num_subwindows) { bool handled = false; m_prev_active_window_idx = m_curr_active_window_idx; for (size_t idx=m_curr_active_window_idx + 1; idxGetCanBeActive()) { m_curr_active_window_idx = idx; handled = true; break; } } if (!handled) { for (size_t idx=0; idx<=m_prev_active_window_idx; ++idx) { if (m_subwindows[idx]->GetCanBeActive()) { m_curr_active_window_idx = idx; break; } } } } else { m_prev_active_window_idx = m_curr_active_window_idx; for (size_t idx=0; idxGetCanBeActive()) { m_curr_active_window_idx = idx; break; } } } } const char * GetName () const { return m_name.c_str(); } protected: std::string m_name; WINDOW *m_window; PANEL *m_panel; Window *m_parent; Windows m_subwindows; WindowDelegateSP m_delegate_sp; uint32_t m_curr_active_window_idx; uint32_t m_prev_active_window_idx; bool m_delete; bool m_needs_update; bool m_can_activate; bool m_is_subwin; private: DISALLOW_COPY_AND_ASSIGN(Window); }; class MenuDelegate { public: virtual ~MenuDelegate() {} virtual MenuActionResult MenuDelegateAction (Menu &menu) = 0; }; class Menu : public WindowDelegate { public: enum class Type { Invalid, Bar, Item, Separator }; // Menubar or separator constructor Menu (Type type); // Menuitem constructor Menu (const char *name, const char *key_name, int key_value, uint64_t identifier); virtual ~ Menu () { } const MenuDelegateSP & GetDelegate () const { return m_delegate_sp; } void SetDelegate (const MenuDelegateSP &delegate_sp) { m_delegate_sp = delegate_sp; } void RecalculateNameLengths(); void AddSubmenu (const MenuSP &menu_sp); int DrawAndRunMenu (Window &window); void DrawMenuTitle (Window &window, bool highlight); virtual bool WindowDelegateDraw (Window &window, bool force); virtual HandleCharResult WindowDelegateHandleChar (Window &window, int key); MenuActionResult ActionPrivate (Menu &menu) { MenuActionResult result = MenuActionResult::NotHandled; if (m_delegate_sp) { result = m_delegate_sp->MenuDelegateAction (menu); if (result != MenuActionResult::NotHandled) return result; } else if (m_parent) { result = m_parent->ActionPrivate(menu); if (result != MenuActionResult::NotHandled) return result; } return m_canned_result; } MenuActionResult Action () { // Call the recursive action so it can try to handle it // with the menu delegate, and if not, try our parent menu return ActionPrivate (*this); } void SetCannedResult (MenuActionResult result) { m_canned_result = result; } Menus & GetSubmenus() { return m_submenus; } const Menus & GetSubmenus() const { return m_submenus; } int GetSelectedSubmenuIndex () const { return m_selected; } void SetSelectedSubmenuIndex (int idx) { m_selected = idx; } Type GetType () const { return m_type; } int GetStartingColumn() const { return m_start_col; } void SetStartingColumn(int col) { m_start_col = col; } int GetKeyValue() const { return m_key_value; } void SetKeyValue(int key_value) { m_key_value = key_value; } std::string & GetName() { return m_name; } std::string & GetKeyName() { return m_key_name; } int GetDrawWidth () const { return m_max_submenu_name_length + m_max_submenu_key_name_length + 8; } uint64_t GetIdentifier() const { return m_identifier; } void SetIdentifier (uint64_t identifier) { m_identifier = identifier; } protected: std::string m_name; std::string m_key_name; uint64_t m_identifier; Type m_type; int m_key_value; int m_start_col; int m_max_submenu_name_length; int m_max_submenu_key_name_length; int m_selected; Menu *m_parent; Menus m_submenus; WindowSP m_menu_window_sp; MenuActionResult m_canned_result; MenuDelegateSP m_delegate_sp; }; // Menubar or separator constructor Menu::Menu (Type type) : m_name (), m_key_name (), m_identifier (0), m_type (type), m_key_value (0), m_start_col (0), m_max_submenu_name_length (0), m_max_submenu_key_name_length (0), m_selected (0), m_parent (NULL), m_submenus (), m_canned_result (MenuActionResult::NotHandled), m_delegate_sp() { } // Menuitem constructor Menu::Menu (const char *name, const char *key_name, int key_value, uint64_t identifier) : m_name (), m_key_name (), m_identifier (identifier), m_type (Type::Invalid), m_key_value (key_value), m_start_col (0), m_max_submenu_name_length (0), m_max_submenu_key_name_length (0), m_selected (0), m_parent (NULL), m_submenus (), m_canned_result (MenuActionResult::NotHandled), m_delegate_sp() { if (name && name[0]) { m_name = name; m_type = Type::Item; if (key_name && key_name[0]) m_key_name = key_name; } else { m_type = Type::Separator; } } void Menu::RecalculateNameLengths() { m_max_submenu_name_length = 0; m_max_submenu_key_name_length = 0; Menus &submenus = GetSubmenus(); const size_t num_submenus = submenus.size(); for (size_t i=0; im_name.size()) m_max_submenu_name_length = submenu->m_name.size(); if (m_max_submenu_key_name_length < submenu->m_key_name.size()) m_max_submenu_key_name_length = submenu->m_key_name.size(); } } void Menu::AddSubmenu (const MenuSP &menu_sp) { menu_sp->m_parent = this; if (m_max_submenu_name_length < menu_sp->m_name.size()) m_max_submenu_name_length = menu_sp->m_name.size(); if (m_max_submenu_key_name_length < menu_sp->m_key_name.size()) m_max_submenu_key_name_length = menu_sp->m_key_name.size(); m_submenus.push_back(menu_sp); } void Menu::DrawMenuTitle (Window &window, bool highlight) { if (m_type == Type::Separator) { window.MoveCursor(0, window.GetCursorY()); window.PutChar(ACS_LTEE); int width = window.GetWidth(); if (width > 2) { width -= 2; for (size_t i=0; i< width; ++i) window.PutChar(ACS_HLINE); } window.PutChar(ACS_RTEE); } else { const int shortcut_key = m_key_value; bool underlined_shortcut = false; const attr_t hilgight_attr = A_REVERSE; if (highlight) window.AttributeOn(hilgight_attr); if (isprint(shortcut_key)) { size_t lower_pos = m_name.find(tolower(shortcut_key)); size_t upper_pos = m_name.find(toupper(shortcut_key)); const char *name = m_name.c_str(); size_t pos = std::min(lower_pos, upper_pos); if (pos != std::string::npos) { underlined_shortcut = true; if (pos > 0) { window.PutCString(name, pos); name += pos; } const attr_t shortcut_attr = A_UNDERLINE|A_BOLD; window.AttributeOn (shortcut_attr); window.PutChar(name[0]); window.AttributeOff(shortcut_attr); name++; if (name[0]) window.PutCString(name); } } if (!underlined_shortcut) { window.PutCString(m_name.c_str()); } if (highlight) window.AttributeOff(hilgight_attr); if (m_key_name.empty()) { if (!underlined_shortcut && isprint(m_key_value)) { window.AttributeOn (COLOR_PAIR(3)); window.Printf (" (%c)", m_key_value); window.AttributeOff (COLOR_PAIR(3)); } } else { window.AttributeOn (COLOR_PAIR(3)); window.Printf (" (%s)", m_key_name.c_str()); window.AttributeOff (COLOR_PAIR(3)); } } } bool Menu::WindowDelegateDraw (Window &window, bool force) { Menus &submenus = GetSubmenus(); const size_t num_submenus = submenus.size(); const int selected_idx = GetSelectedSubmenuIndex(); Menu::Type menu_type = GetType (); switch (menu_type) { case Menu::Type::Bar: { window.SetBackground(2); window.MoveCursor(0, 0); for (size_t i=0; i 0) window.PutChar(' '); menu->SetStartingColumn (window.GetCursorX()); window.PutCString("| "); menu->DrawMenuTitle (window, false); } window.PutCString(" |"); window.DeferredRefresh(); } break; case Menu::Type::Item: { int y = 1; int x = 3; // Draw the menu int cursor_x = 0; int cursor_y = 0; window.Erase(); window.SetBackground(2); window.Box(); for (size_t i=0; iDrawMenuTitle (window, is_selected); } window.MoveCursor(cursor_x, cursor_y); window.DeferredRefresh(); } break; default: case Menu::Type::Separator: break; } return true; // Drawing handled... } HandleCharResult Menu::WindowDelegateHandleChar (Window &window, int key) { HandleCharResult result = eKeyNotHandled; Menus &submenus = GetSubmenus(); const size_t num_submenus = submenus.size(); const int selected_idx = GetSelectedSubmenuIndex(); Menu::Type menu_type = GetType (); if (menu_type == Menu::Type::Bar) { MenuSP run_menu_sp; switch (key) { case KEY_DOWN: case KEY_UP: // Show last menu or first menu if (selected_idx < num_submenus) run_menu_sp = submenus[selected_idx]; else if (!submenus.empty()) run_menu_sp = submenus.front(); result = eKeyHandled; break; case KEY_RIGHT: { ++m_selected; if (m_selected >= num_submenus) m_selected = 0; if (m_selected < num_submenus) run_menu_sp = submenus[m_selected]; else if (!submenus.empty()) run_menu_sp = submenus.front(); result = eKeyHandled; } break; case KEY_LEFT: { --m_selected; if (m_selected < 0) m_selected = num_submenus - 1; if (m_selected < num_submenus) run_menu_sp = submenus[m_selected]; else if (!submenus.empty()) run_menu_sp = submenus.front(); result = eKeyHandled; } break; default: for (size_t i=0; iGetKeyValue() == key) { SetSelectedSubmenuIndex(i); run_menu_sp = submenus[i]; result = eKeyHandled; break; } } break; } if (run_menu_sp) { // Run the action on this menu in case we need to populate the // menu with dynamic content and also in case check marks, and // any other menu decorations need to be caclulated if (run_menu_sp->Action() == MenuActionResult::Quit) return eQuitApplication; Rect menu_bounds; menu_bounds.origin.x = run_menu_sp->GetStartingColumn(); menu_bounds.origin.y = 1; menu_bounds.size.width = run_menu_sp->GetDrawWidth(); menu_bounds.size.height = run_menu_sp->GetSubmenus().size() + 2; if (m_menu_window_sp) window.GetParent()->RemoveSubWindow(m_menu_window_sp.get()); m_menu_window_sp = window.GetParent()->CreateSubWindow (run_menu_sp->GetName().c_str(), menu_bounds, true); m_menu_window_sp->SetDelegate (run_menu_sp); } } else if (menu_type == Menu::Type::Item) { switch (key) { case KEY_DOWN: if (m_submenus.size() > 1) { const int start_select = m_selected; while (++m_selected != start_select) { if (m_selected >= num_submenus) m_selected = 0; if (m_submenus[m_selected]->GetType() == Type::Separator) continue; else break; } return eKeyHandled; } break; case KEY_UP: if (m_submenus.size() > 1) { const int start_select = m_selected; while (--m_selected != start_select) { if (m_selected < 0) m_selected = num_submenus - 1; if (m_submenus[m_selected]->GetType() == Type::Separator) continue; else break; } return eKeyHandled; } break; case KEY_RETURN: if (selected_idx < num_submenus) { if (submenus[selected_idx]->Action() == MenuActionResult::Quit) return eQuitApplication; window.GetParent()->RemoveSubWindow(&window); return eKeyHandled; } break; case KEY_ESCAPE: // Beware: pressing escape key has 1 to 2 second delay in case other chars are entered for escaped sequences window.GetParent()->RemoveSubWindow(&window); return eKeyHandled; default: { bool handled = false; for (size_t i=0; iGetKeyValue() == key) { handled = true; SetSelectedSubmenuIndex(i); window.GetParent()->RemoveSubWindow(&window); if (menu->Action() == MenuActionResult::Quit) return eQuitApplication; return eKeyHandled; } } } break; } } else if (menu_type == Menu::Type::Separator) { } return result; } class Application { public: Application (FILE *in, FILE *out) : m_window_sp(), m_screen (NULL), m_in (in), m_out (out) { } ~Application () { m_window_delegates.clear(); m_window_sp.reset(); if (m_screen) { ::delscreen(m_screen); m_screen = NULL; } } void Initialize () { ::setlocale(LC_ALL, ""); ::setlocale(LC_CTYPE, ""); #if 0 ::initscr(); #else m_screen = ::newterm(NULL, m_out, m_in); #endif ::start_color(); ::curs_set(0); ::noecho(); ::keypad(stdscr,TRUE); } void Terminate () { ::endwin(); } void Run (Debugger &debugger) { bool done = false; int delay_in_tenths_of_a_second = 1; // Alas the threading model in curses is a bit lame so we need to // resort to polling every 0.5 seconds. We could poll for stdin // ourselves and then pass the keys down but then we need to // translate all of the escape sequences ourselves. So we resort to // polling for input because we need to receive async process events // while in this loop. halfdelay(delay_in_tenths_of_a_second); // Poll using some number of tenths of seconds seconds when calling Window::GetChar() ListenerSP listener_sp (new Listener ("lldb.IOHandler.curses.Application")); ConstString broadcaster_class_target(Target::GetStaticBroadcasterClass()); ConstString broadcaster_class_process(Process::GetStaticBroadcasterClass()); ConstString broadcaster_class_thread(Thread::GetStaticBroadcasterClass()); debugger.EnableForwardEvents (listener_sp); bool update = true; #if defined(__APPLE__) std::deque escape_chars; #endif while (!done) { if (update) { m_window_sp->Draw(false); // All windows should be calling Window::DeferredRefresh() instead // of Window::Refresh() so we can do a single update and avoid // any screen blinking update_panels(); // Cursor hiding isn't working on MacOSX, so hide it in the top left corner m_window_sp->MoveCursor(0, 0); doupdate(); update = false; } #if defined(__APPLE__) // Terminal.app doesn't map its function keys correctly, F1-F4 default to: // \033OP, \033OQ, \033OR, \033OS, so lets take care of this here if possible int ch; if (escape_chars.empty()) ch = m_window_sp->GetChar(); else { ch = escape_chars.front(); escape_chars.pop_front(); } if (ch == KEY_ESCAPE) { int ch2 = m_window_sp->GetChar(); if (ch2 == 'O') { int ch3 = m_window_sp->GetChar(); switch (ch3) { case 'P': ch = KEY_F(1); break; case 'Q': ch = KEY_F(2); break; case 'R': ch = KEY_F(3); break; case 'S': ch = KEY_F(4); break; default: escape_chars.push_back(ch2); if (ch3 != -1) escape_chars.push_back(ch3); break; } } else if (ch2 != -1) escape_chars.push_back(ch2); } #else int ch = m_window_sp->GetChar(); #endif if (ch == -1) { if (feof(m_in) || ferror(m_in)) { done = true; } else { // Just a timeout from using halfdelay(), check for events EventSP event_sp; while (listener_sp->PeekAtNextEvent()) { listener_sp->GetNextEvent(event_sp); if (event_sp) { Broadcaster *broadcaster = event_sp->GetBroadcaster(); if (broadcaster) { //uint32_t event_type = event_sp->GetType(); ConstString broadcaster_class (broadcaster->GetBroadcasterClass()); if (broadcaster_class == broadcaster_class_process) { update = true; continue; // Don't get any key, just update our view } } } } } } else { HandleCharResult key_result = m_window_sp->HandleChar(ch); switch (key_result) { case eKeyHandled: update = true; break; case eKeyNotHandled: break; case eQuitApplication: done = true; break; } } } debugger.CancelForwardEvents (listener_sp); } WindowSP & GetMainWindow () { if (!m_window_sp) m_window_sp.reset (new Window ("main", stdscr, false)); return m_window_sp; } WindowDelegates & GetWindowDelegates () { return m_window_delegates; } protected: WindowSP m_window_sp; WindowDelegates m_window_delegates; SCREEN *m_screen; FILE *m_in; FILE *m_out; }; } // namespace curses using namespace curses; struct Row { ValueObjectSP valobj; Row *parent; int row_idx; int x; int y; bool might_have_children; bool expanded; bool calculated_children; std::vector children; Row (const ValueObjectSP &v, Row *p) : valobj (v), parent (p), row_idx(0), x(1), y(1), might_have_children (v ? v->MightHaveChildren() : false), expanded (false), calculated_children (false), children() { } size_t GetDepth () const { if (parent) return 1 + parent->GetDepth(); return 0; } void Expand() { expanded = true; if (!calculated_children) { calculated_children = true; if (valobj) { const size_t num_children = valobj->GetNumChildren(); for (size_t i=0; iGetChildAtIndex(i, true), this)); } } } } void Unexpand () { expanded = false; } void DrawTree (Window &window) { if (parent) parent->DrawTreeForChild (window, this, 0); if (might_have_children) { // It we can get UTF8 characters to work we should try to use the "symbol" // UTF8 string below // const char *symbol = ""; // if (row.expanded) // symbol = "\xe2\x96\xbd "; // else // symbol = "\xe2\x96\xb7 "; // window.PutCString (symbol); // The ACS_DARROW and ACS_RARROW don't look very nice they are just a // 'v' or '>' character... // if (expanded) // window.PutChar (ACS_DARROW); // else // window.PutChar (ACS_RARROW); // Since we can't find any good looking right arrow/down arrow // symbols, just use a diamond... window.PutChar (ACS_DIAMOND); window.PutChar (ACS_HLINE); } } void DrawTreeForChild (Window &window, Row *child, uint32_t reverse_depth) { if (parent) parent->DrawTreeForChild (window, this, reverse_depth + 1); if (&children.back() == child) { // Last child if (reverse_depth == 0) { window.PutChar (ACS_LLCORNER); window.PutChar (ACS_HLINE); } else { window.PutChar (' '); window.PutChar (' '); } } else { if (reverse_depth == 0) { window.PutChar (ACS_LTEE); window.PutChar (ACS_HLINE); } else { window.PutChar (ACS_VLINE); window.PutChar (' '); } } } }; struct DisplayOptions { bool show_types; }; class TreeItem; class TreeDelegate { public: TreeDelegate() {} virtual ~TreeDelegate() {} virtual void TreeDelegateDrawTreeItem (TreeItem &item, Window &window) = 0; virtual void TreeDelegateGenerateChildren (TreeItem &item) = 0; virtual bool TreeDelegateItemSelected (TreeItem &item) = 0; // Return true if we need to update views }; typedef std::shared_ptr TreeDelegateSP; class TreeItem { public: TreeItem (TreeItem *parent, TreeDelegate &delegate, bool might_have_children) : m_parent (parent), m_delegate (delegate), m_identifier (0), m_row_idx (-1), m_children (), m_might_have_children (might_have_children), m_is_expanded (false) { } TreeItem & operator=(const TreeItem &rhs) { if (this != &rhs) { m_parent = rhs.m_parent; m_delegate = rhs.m_delegate; m_identifier = rhs.m_identifier; m_row_idx = rhs.m_row_idx; m_children = rhs.m_children; m_might_have_children = rhs.m_might_have_children; m_is_expanded = rhs.m_is_expanded; } return *this; } size_t GetDepth () const { if (m_parent) return 1 + m_parent->GetDepth(); return 0; } int GetRowIndex () const { return m_row_idx; } void ClearChildren () { m_children.clear(); } void Resize (size_t n, const TreeItem &t) { m_children.resize(n, t); } TreeItem & operator [](size_t i) { return m_children[i]; } void SetRowIndex (int row_idx) { m_row_idx = row_idx; } size_t GetNumChildren () { m_delegate.TreeDelegateGenerateChildren (*this); return m_children.size(); } void ItemWasSelected () { m_delegate.TreeDelegateItemSelected(*this); } void CalculateRowIndexes (int &row_idx) { SetRowIndex(row_idx); ++row_idx; // The root item must calculate its children if (m_parent == NULL) GetNumChildren(); const bool expanded = IsExpanded(); for (auto &item : m_children) { if (expanded) item.CalculateRowIndexes(row_idx); else item.SetRowIndex(-1); } } TreeItem * GetParent () { return m_parent; } bool IsExpanded () const { return m_is_expanded; } void Expand() { m_is_expanded = true; } void Unexpand () { m_is_expanded = false; } bool Draw (Window &window, const int first_visible_row, const uint32_t selected_row_idx, int &row_idx, int &num_rows_left) { if (num_rows_left <= 0) return false; if (m_row_idx >= first_visible_row) { window.MoveCursor(2, row_idx + 1); if (m_parent) m_parent->DrawTreeForChild (window, this, 0); if (m_might_have_children) { // It we can get UTF8 characters to work we should try to use the "symbol" // UTF8 string below // const char *symbol = ""; // if (row.expanded) // symbol = "\xe2\x96\xbd "; // else // symbol = "\xe2\x96\xb7 "; // window.PutCString (symbol); // The ACS_DARROW and ACS_RARROW don't look very nice they are just a // 'v' or '>' character... // if (expanded) // window.PutChar (ACS_DARROW); // else // window.PutChar (ACS_RARROW); // Since we can't find any good looking right arrow/down arrow // symbols, just use a diamond... window.PutChar (ACS_DIAMOND); window.PutChar (ACS_HLINE); } bool highlight = (selected_row_idx == m_row_idx) && window.IsActive(); if (highlight) window.AttributeOn(A_REVERSE); m_delegate.TreeDelegateDrawTreeItem(*this, window); if (highlight) window.AttributeOff(A_REVERSE); ++row_idx; --num_rows_left; } if (num_rows_left <= 0) return false; // We are done drawing... if (IsExpanded()) { for (auto &item : m_children) { // If we displayed all the rows and item.Draw() returns // false we are done drawing and can exit this for loop if (item.Draw(window, first_visible_row, selected_row_idx, row_idx, num_rows_left) == false) break; } } return num_rows_left >= 0; // Return true if not done drawing yet } void DrawTreeForChild (Window &window, TreeItem *child, uint32_t reverse_depth) { if (m_parent) m_parent->DrawTreeForChild (window, this, reverse_depth + 1); if (&m_children.back() == child) { // Last child if (reverse_depth == 0) { window.PutChar (ACS_LLCORNER); window.PutChar (ACS_HLINE); } else { window.PutChar (' '); window.PutChar (' '); } } else { if (reverse_depth == 0) { window.PutChar (ACS_LTEE); window.PutChar (ACS_HLINE); } else { window.PutChar (ACS_VLINE); window.PutChar (' '); } } } TreeItem * GetItemForRowIndex (uint32_t row_idx) { if (m_row_idx == row_idx) return this; if (m_children.empty()) return NULL; if (m_children.back().m_row_idx < row_idx) return NULL; if (IsExpanded()) { for (auto &item : m_children) { TreeItem *selected_item_ptr = item.GetItemForRowIndex(row_idx); if (selected_item_ptr) return selected_item_ptr; } } return NULL; } // void * // GetUserData() const // { // return m_user_data; // } // // void // SetUserData (void *user_data) // { // m_user_data = user_data; // } uint64_t GetIdentifier() const { return m_identifier; } void SetIdentifier (uint64_t identifier) { m_identifier = identifier; } protected: TreeItem *m_parent; TreeDelegate &m_delegate; //void *m_user_data; uint64_t m_identifier; int m_row_idx; // Zero based visible row index, -1 if not visible or for the root item std::vector m_children; bool m_might_have_children; bool m_is_expanded; }; class TreeWindowDelegate : public WindowDelegate { public: TreeWindowDelegate (Debugger &debugger, const TreeDelegateSP &delegate_sp) : m_debugger (debugger), m_delegate_sp (delegate_sp), m_root (NULL, *delegate_sp, true), m_selected_item (NULL), m_num_rows (0), m_selected_row_idx (0), m_first_visible_row (0), m_min_x (0), m_min_y (0), m_max_x (0), m_max_y (0) { } int NumVisibleRows () const { return m_max_y - m_min_y; } virtual bool WindowDelegateDraw (Window &window, bool force) { 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; m_max_y = window.GetHeight() - 1; 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); // 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 { m_selected_item = NULL; } window.DeferredRefresh(); return true; // Drawing handled } virtual const char * WindowDelegateGetHelpText () { return "Thread window keyboard shortcuts:"; } virtual KeyHelp * WindowDelegateGetKeyHelp () { static curses::KeyHelp g_source_view_key_help[] = { { KEY_UP, "Select previous item" }, { KEY_DOWN, "Select next item" }, { KEY_RIGHT, "Expand the selected item" }, { KEY_LEFT, "Unexpand the selected item or select parent if not expanded" }, { KEY_PPAGE, "Page up" }, { KEY_NPAGE, "Page down" }, { 'h', "Show help dialog" }, { ' ', "Toggle item expansion" }, { ',', "Page up" }, { '.', "Page down" }, { '\0', NULL } }; return g_source_view_key_help; } virtual HandleCharResult WindowDelegateHandleChar (Window &window, int c) { switch(c) { case ',': case KEY_PPAGE: // Page up key if (m_first_visible_row > 0) { if (m_first_visible_row > m_max_y) m_first_visible_row -= m_max_y; else m_first_visible_row = 0; m_selected_row_idx = m_first_visible_row; m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); if (m_selected_item) m_selected_item->ItemWasSelected (); } return eKeyHandled; case '.': case KEY_NPAGE: // Page down key if (m_num_rows > m_max_y) { if (m_first_visible_row + m_max_y < m_num_rows) { m_first_visible_row += m_max_y; m_selected_row_idx = m_first_visible_row; m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); if (m_selected_item) m_selected_item->ItemWasSelected (); } } return eKeyHandled; case KEY_UP: if (m_selected_row_idx > 0) { --m_selected_row_idx; m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); if (m_selected_item) m_selected_item->ItemWasSelected (); } return eKeyHandled; case KEY_DOWN: if (m_selected_row_idx + 1 < m_num_rows) { ++m_selected_row_idx; m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); if (m_selected_item) m_selected_item->ItemWasSelected (); } return eKeyHandled; case KEY_RIGHT: if (m_selected_item) { if (!m_selected_item->IsExpanded()) m_selected_item->Expand(); } return eKeyHandled; case KEY_LEFT: if (m_selected_item) { if (m_selected_item->IsExpanded()) m_selected_item->Unexpand(); else if (m_selected_item->GetParent()) { m_selected_row_idx = m_selected_item->GetParent()->GetRowIndex(); m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); if (m_selected_item) m_selected_item->ItemWasSelected (); } } return eKeyHandled; case ' ': // Toggle expansion state when SPACE is pressed if (m_selected_item) { if (m_selected_item->IsExpanded()) m_selected_item->Unexpand(); else m_selected_item->Expand(); } return eKeyHandled; case 'h': window.CreateHelpSubwindow (); return eKeyHandled; default: break; } return eKeyNotHandled; } protected: Debugger &m_debugger; TreeDelegateSP m_delegate_sp; TreeItem m_root; TreeItem *m_selected_item; int m_num_rows; int m_selected_row_idx; int m_first_visible_row; int m_min_x; int m_min_y; int m_max_x; int m_max_y; }; class FrameTreeDelegate : public TreeDelegate { public: FrameTreeDelegate (const ThreadSP &thread_sp) : TreeDelegate(), m_thread_wp() { if (thread_sp) m_thread_wp = thread_sp; } virtual ~FrameTreeDelegate() { } virtual void TreeDelegateDrawTreeItem (TreeItem &item, Window &window) { ThreadSP thread_sp = m_thread_wp.lock(); if (thread_sp) { const uint64_t frame_idx = item.GetIdentifier(); StackFrameSP frame_sp = thread_sp->GetStackFrameAtIndex(frame_idx); if (frame_sp) { StreamString strm; const SymbolContext &sc = frame_sp->GetSymbolContext(eSymbolContextEverything); ExecutionContext exe_ctx (frame_sp); //const char *frame_format = "frame #${frame.index}: ${module.file.basename}{`${function.name}${function.pc-offset}}}"; const char *frame_format = "frame #${frame.index}: {${function.name}${function.pc-offset}}}"; if (Debugger::FormatPrompt (frame_format, &sc, &exe_ctx, NULL, strm)) { int right_pad = 1; window.PutCStringTruncated(strm.GetString().c_str(), right_pad); } } } } virtual void TreeDelegateGenerateChildren (TreeItem &item) { // No children for frames yet... } virtual bool TreeDelegateItemSelected (TreeItem &item) { ThreadSP thread_sp = m_thread_wp.lock(); if (thread_sp) { const uint64_t frame_idx = item.GetIdentifier(); thread_sp->SetSelectedFrameByIndex(frame_idx); return true; } return false; } void SetThread (ThreadSP thread_sp) { m_thread_wp = thread_sp; } protected: ThreadWP m_thread_wp; }; class ThreadTreeDelegate : public TreeDelegate { public: ThreadTreeDelegate (Debugger &debugger) : TreeDelegate(), m_debugger (debugger), m_thread_wp (), m_tid (LLDB_INVALID_THREAD_ID), m_stop_id (UINT32_MAX) { } virtual ~ThreadTreeDelegate() { } virtual void TreeDelegateDrawTreeItem (TreeItem &item, Window &window) { ThreadSP thread_sp = m_thread_wp.lock(); if (thread_sp) { StreamString strm; ExecutionContext exe_ctx (thread_sp); const char *format = "thread #${thread.index}: tid = ${thread.id}{, stop reason = ${thread.stop-reason}}"; if (Debugger::FormatPrompt (format, NULL, &exe_ctx, NULL, strm)) { int right_pad = 1; window.PutCStringTruncated(strm.GetString().c_str(), right_pad); } } } virtual void TreeDelegateGenerateChildren (TreeItem &item) { TargetSP target_sp (m_debugger.GetSelectedTarget()); if (target_sp) { ProcessSP process_sp = target_sp->GetProcessSP(); if (process_sp && process_sp->IsAlive()) { StateType state = process_sp->GetState(); if (StateIsStoppedState(state, true)) { ThreadSP thread_sp = process_sp->GetThreadList().GetSelectedThread(); if (thread_sp) { if (m_stop_id == process_sp->GetStopID() && thread_sp->GetID() == m_tid) return; // Children are already up to date if (m_frame_delegate_sp) m_frame_delegate_sp->SetThread(thread_sp); else { // Always expand the thread item the first time we show it item.Expand(); m_frame_delegate_sp.reset (new FrameTreeDelegate(thread_sp)); } m_stop_id = process_sp->GetStopID(); m_thread_wp = thread_sp; m_tid = thread_sp->GetID(); TreeItem t (&item, *m_frame_delegate_sp, false); size_t num_frames = thread_sp->GetStackFrameCount(); item.Resize (num_frames, t); for (size_t i=0; iGetProcess()->GetThreadList(); Mutex::Locker locker (thread_list.GetMutex()); ThreadSP selected_thread_sp = thread_list.GetSelectedThread(); if (selected_thread_sp->GetID() != thread_sp->GetID()) { thread_list.SetSelectedThreadByID(thread_sp->GetID()); return true; } } return false; } protected: Debugger &m_debugger; ThreadWP m_thread_wp; std::shared_ptr m_frame_delegate_sp; lldb::user_id_t m_tid; uint32_t m_stop_id; }; class ValueObjectListDelegate : public WindowDelegate { public: ValueObjectListDelegate () : m_valobj_list (), m_rows (), m_selected_row (NULL), m_selected_row_idx (0), m_first_visible_row (0), m_num_rows (0), m_max_x (0), m_max_y (0) { } ValueObjectListDelegate (ValueObjectList &valobj_list) : m_valobj_list (valobj_list), m_rows (), m_selected_row (NULL), m_selected_row_idx (0), m_first_visible_row (0), m_num_rows (0), m_max_x (0), m_max_y (0) { SetValues (valobj_list); } virtual ~ValueObjectListDelegate() { } void SetValues (ValueObjectList &valobj_list) { m_selected_row = NULL; m_selected_row_idx = 0; m_first_visible_row = 0; m_num_rows = 0; m_rows.clear(); m_valobj_list = valobj_list; const size_t num_values = m_valobj_list.GetSize(); for (size_t i=0; i 0 && 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; DisplayRows (window, m_rows, g_options); window.DeferredRefresh(); // Get the selected row m_selected_row = GetRowForRowIndex (m_selected_row_idx); // Keep the cursor on the selected row so the highlight and the cursor // are always on the same line if (m_selected_row) window.MoveCursor (m_selected_row->x, m_selected_row->y); return true; // Drawing handled } virtual KeyHelp * WindowDelegateGetKeyHelp () { static curses::KeyHelp g_source_view_key_help[] = { { KEY_UP, "Select previous item" }, { KEY_DOWN, "Select next item" }, { KEY_RIGHT, "Expand selected item" }, { KEY_LEFT, "Unexpand selected item or select parent if not expanded" }, { KEY_PPAGE, "Page up" }, { KEY_NPAGE, "Page down" }, { 'A', "Format as annotated address" }, { 'b', "Format as binary" }, { 'B', "Format as hex bytes with ASCII" }, { 'c', "Format as character" }, { 'd', "Format as a signed integer" }, { 'D', "Format selected value using the default format for the type" }, { 'f', "Format as float" }, { 'h', "Show help dialog" }, { 'i', "Format as instructions" }, { 'o', "Format as octal" }, { 'p', "Format as pointer" }, { 's', "Format as C string" }, { 't', "Toggle showing/hiding type names" }, { 'u', "Format as an unsigned integer" }, { 'x', "Format as hex" }, { 'X', "Format as uppercase hex" }, { ' ', "Toggle item expansion" }, { ',', "Page up" }, { '.', "Page down" }, { '\0', NULL } }; return g_source_view_key_help; } virtual HandleCharResult WindowDelegateHandleChar (Window &window, int c) { switch(c) { case 'x': case 'X': case 'o': case 's': case 'u': case 'd': case 'D': case 'i': case 'A': case 'p': case 'c': case 'b': case 'B': case 'f': // Change the format for the currently selected item if (m_selected_row) m_selected_row->valobj->SetFormat (FormatForChar (c)); return eKeyHandled; case 't': // Toggle showing type names g_options.show_types = !g_options.show_types; return eKeyHandled; case ',': case KEY_PPAGE: // Page up key if (m_first_visible_row > 0) { if (m_first_visible_row > m_max_y) m_first_visible_row -= m_max_y; else m_first_visible_row = 0; m_selected_row_idx = m_first_visible_row; } return eKeyHandled; case '.': case KEY_NPAGE: // Page down key if (m_num_rows > m_max_y) { if (m_first_visible_row + m_max_y < m_num_rows) { m_first_visible_row += m_max_y; m_selected_row_idx = m_first_visible_row; } } return eKeyHandled; case KEY_UP: if (m_selected_row_idx > 0) --m_selected_row_idx; return eKeyHandled; case KEY_DOWN: if (m_selected_row_idx + 1 < m_num_rows) ++m_selected_row_idx; return eKeyHandled; case KEY_RIGHT: if (m_selected_row) { if (!m_selected_row->expanded) m_selected_row->Expand(); } return eKeyHandled; case KEY_LEFT: if (m_selected_row) { if (m_selected_row->expanded) m_selected_row->Unexpand(); else if (m_selected_row->parent) m_selected_row_idx = m_selected_row->parent->row_idx; } return eKeyHandled; case ' ': // Toggle expansion state when SPACE is pressed if (m_selected_row) { if (m_selected_row->expanded) m_selected_row->Unexpand(); else m_selected_row->Expand(); } return eKeyHandled; case 'h': window.CreateHelpSubwindow (); return eKeyHandled; default: break; } return eKeyNotHandled; } protected: ValueObjectList m_valobj_list; std::vector m_rows; Row *m_selected_row; uint32_t m_selected_row_idx; uint32_t m_first_visible_row; uint32_t m_num_rows; int m_min_x; int m_min_y; int m_max_x; int m_max_y; static Format FormatForChar (int c) { switch (c) { case 'x': return eFormatHex; case 'X': return eFormatHexUppercase; case 'o': return eFormatOctal; case 's': return eFormatCString; case 'u': return eFormatUnsigned; case 'd': return eFormatDecimal; case 'D': return eFormatDefault; case 'i': return eFormatInstruction; case 'A': return eFormatAddressInfo; case 'p': return eFormatPointer; case 'c': return eFormatChar; case 'b': return eFormatBinary; case 'B': return eFormatBytesWithASCII; case 'f': return eFormatFloat; } return eFormatDefault; } bool DisplayRowObject (Window &window, Row &row, DisplayOptions &options, bool highlight, bool last_child) { ValueObject *valobj = row.valobj.get(); if (valobj == NULL) return false; const char *type_name = options.show_types ? valobj->GetTypeName().GetCString() : NULL; const char *name = valobj->GetName().GetCString(); const char *value = valobj->GetValueAsCString (); const char *summary = valobj->GetSummaryAsCString (); window.MoveCursor (row.x, row.y); row.DrawTree (window); if (highlight) window.AttributeOn(A_REVERSE); if (type_name && type_name[0]) window.Printf ("(%s) ", type_name); if (name && name[0]) window.PutCString(name); attr_t changd_attr = 0; if (valobj->GetValueDidChange()) changd_attr = COLOR_PAIR(5) | A_BOLD; if (value && value[0]) { window.PutCString(" = "); if (changd_attr) window.AttributeOn(changd_attr); window.PutCString (value); if (changd_attr) window.AttributeOff(changd_attr); } if (summary && summary[0]) { window.PutChar(' '); if (changd_attr) window.AttributeOn(changd_attr); window.PutCString(summary); if (changd_attr) window.AttributeOff(changd_attr); } if (highlight) window.AttributeOff (A_REVERSE); return true; } void DisplayRows (Window &window, std::vector &rows, DisplayOptions &options) { // > 0x25B7 // \/ 0x25BD bool window_is_active = window.IsActive(); for (auto &row : rows) { const bool last_child = row.parent && &rows[rows.size()-1] == &row; // Save the row index in each Row structure row.row_idx = m_num_rows; if ((m_num_rows >= m_first_visible_row) && ((m_num_rows - m_first_visible_row) < NumVisibleRows())) { row.x = m_min_x; row.y = m_num_rows - m_first_visible_row + 1; if (DisplayRowObject (window, row, options, window_is_active && m_num_rows == m_selected_row_idx, last_child)) { ++m_num_rows; } else { row.x = 0; row.y = 0; } } else { row.x = 0; row.y = 0; ++m_num_rows; } if (row.expanded && !row.children.empty()) { DisplayRows (window, row.children, options); } } } int CalculateTotalNumberRows (const std::vector &rows) { int row_count = 0; for (const auto &row : rows) { ++row_count; if (row.expanded) row_count += CalculateTotalNumberRows(row.children); } return row_count; } static Row * GetRowForRowIndexImpl (std::vector &rows, size_t &row_index) { for (auto &row : rows) { if (row_index == 0) return &row; else { --row_index; if (row.expanded && !row.children.empty()) { Row *result = GetRowForRowIndexImpl (row.children, row_index); if (result) return result; } } } return NULL; } Row * GetRowForRowIndex (size_t row_index) { return GetRowForRowIndexImpl (m_rows, row_index); } int NumVisibleRows () const { return m_max_y - m_min_y; } static DisplayOptions g_options; }; class FrameVariablesWindowDelegate : public ValueObjectListDelegate { public: FrameVariablesWindowDelegate (Debugger &debugger) : ValueObjectListDelegate (), m_debugger (debugger), m_frame_block (NULL) { } virtual ~FrameVariablesWindowDelegate() { } virtual const char * WindowDelegateGetHelpText () { return "Frame variable window keyboard shortcuts:"; } virtual bool WindowDelegateDraw (Window &window, bool force) { ExecutionContext exe_ctx (m_debugger.GetCommandInterpreter().GetExecutionContext()); Process *process = exe_ctx.GetProcessPtr(); Block *frame_block = NULL; StackFrame *frame = NULL; if (process) { StateType state = process->GetState(); if (StateIsStoppedState(state, true)) { frame = exe_ctx.GetFramePtr(); if (frame) frame_block = frame->GetFrameBlock (); } else if (StateIsRunningState(state)) { return true; // Don't do any updating when we are running } } ValueObjectList local_values; if (frame_block) { // Only update the variables if they have changed if (m_frame_block != frame_block) { m_frame_block = frame_block; VariableList *locals = frame->GetVariableList(true); if (locals) { const DynamicValueType use_dynamic = eDynamicDontRunTarget; const size_t num_locals = locals->GetSize(); for (size_t i=0; iGetValueObjectForFrameVariable (locals->GetVariableAtIndex(i), use_dynamic)); // Update the values SetValues(local_values); } } } else { m_frame_block = NULL; // Update the values with an empty list if there is no frame SetValues(local_values); } return ValueObjectListDelegate::WindowDelegateDraw (window, force); } protected: Debugger &m_debugger; Block *m_frame_block; }; class RegistersWindowDelegate : public ValueObjectListDelegate { public: RegistersWindowDelegate (Debugger &debugger) : ValueObjectListDelegate (), m_debugger (debugger) { } virtual ~RegistersWindowDelegate() { } virtual const char * WindowDelegateGetHelpText () { return "Register window keyboard shortcuts:"; } virtual bool WindowDelegateDraw (Window &window, bool force) { ExecutionContext exe_ctx (m_debugger.GetCommandInterpreter().GetExecutionContext()); StackFrame *frame = exe_ctx.GetFramePtr(); ValueObjectList value_list; if (frame) { if (frame->GetStackID() != m_stack_id) { m_stack_id = frame->GetStackID(); RegisterContextSP reg_ctx (frame->GetRegisterContext()); if (reg_ctx) { const uint32_t num_sets = reg_ctx->GetRegisterSetCount(); for (uint32_t set_idx = 0; set_idx < num_sets; ++set_idx) { value_list.Append(ValueObjectRegisterSet::Create (frame, reg_ctx, set_idx)); } } SetValues(value_list); } } else { Process *process = exe_ctx.GetProcessPtr(); if (process && process->IsAlive()) return true; // Don't do any updating if we are running else { // Update the values with an empty list if there // is no process or the process isn't alive anymore SetValues(value_list); } } return ValueObjectListDelegate::WindowDelegateDraw (window, force); } protected: Debugger &m_debugger; StackID m_stack_id; }; static const char * CursesKeyToCString (int ch) { static char g_desc[32]; if (ch >= KEY_F0 && ch < KEY_F0 + 64) { snprintf(g_desc, sizeof(g_desc), "F%u", ch - KEY_F0); return g_desc; } switch (ch) { case KEY_DOWN: return "down"; case KEY_UP: return "up"; case KEY_LEFT: return "left"; case KEY_RIGHT: return "right"; case KEY_HOME: return "home"; case KEY_BACKSPACE: return "backspace"; case KEY_DL: return "delete-line"; case KEY_IL: return "insert-line"; case KEY_DC: return "delete-char"; case KEY_IC: return "insert-char"; case KEY_CLEAR: return "clear"; case KEY_EOS: return "clear-to-eos"; case KEY_EOL: return "clear-to-eol"; case KEY_SF: return "scroll-forward"; case KEY_SR: return "scroll-backward"; case KEY_NPAGE: return "page-down"; case KEY_PPAGE: return "page-up"; case KEY_STAB: return "set-tab"; case KEY_CTAB: return "clear-tab"; case KEY_CATAB: return "clear-all-tabs"; case KEY_ENTER: return "enter"; case KEY_PRINT: return "print"; case KEY_LL: return "lower-left key"; case KEY_A1: return "upper left of keypad"; case KEY_A3: return "upper right of keypad"; case KEY_B2: return "center of keypad"; case KEY_C1: return "lower left of keypad"; case KEY_C3: return "lower right of keypad"; case KEY_BTAB: return "back-tab key"; case KEY_BEG: return "begin key"; case KEY_CANCEL: return "cancel key"; case KEY_CLOSE: return "close key"; case KEY_COMMAND: return "command key"; case KEY_COPY: return "copy key"; case KEY_CREATE: return "create key"; case KEY_END: return "end key"; case KEY_EXIT: return "exit key"; case KEY_FIND: return "find key"; case KEY_HELP: return "help key"; case KEY_MARK: return "mark key"; case KEY_MESSAGE: return "message key"; case KEY_MOVE: return "move key"; case KEY_NEXT: return "next key"; case KEY_OPEN: return "open key"; case KEY_OPTIONS: return "options key"; case KEY_PREVIOUS: return "previous key"; case KEY_REDO: return "redo key"; case KEY_REFERENCE: return "reference key"; case KEY_REFRESH: return "refresh key"; case KEY_REPLACE: return "replace key"; case KEY_RESTART: return "restart key"; case KEY_RESUME: return "resume key"; case KEY_SAVE: return "save key"; case KEY_SBEG: return "shifted begin key"; case KEY_SCANCEL: return "shifted cancel key"; case KEY_SCOMMAND: return "shifted command key"; case KEY_SCOPY: return "shifted copy key"; case KEY_SCREATE: return "shifted create key"; case KEY_SDC: return "shifted delete-character key"; case KEY_SDL: return "shifted delete-line key"; case KEY_SELECT: return "select key"; case KEY_SEND: return "shifted end key"; case KEY_SEOL: return "shifted clear-to-end-of-line key"; case KEY_SEXIT: return "shifted exit key"; case KEY_SFIND: return "shifted find key"; case KEY_SHELP: return "shifted help key"; case KEY_SHOME: return "shifted home key"; case KEY_SIC: return "shifted insert-character key"; case KEY_SLEFT: return "shifted left-arrow key"; case KEY_SMESSAGE: return "shifted message key"; case KEY_SMOVE: return "shifted move key"; case KEY_SNEXT: return "shifted next key"; case KEY_SOPTIONS: return "shifted options key"; case KEY_SPREVIOUS: return "shifted previous key"; case KEY_SPRINT: return "shifted print key"; case KEY_SREDO: return "shifted redo key"; case KEY_SREPLACE: return "shifted replace key"; case KEY_SRIGHT: return "shifted right-arrow key"; case KEY_SRSUME: return "shifted resume key"; case KEY_SSAVE: return "shifted save key"; case KEY_SSUSPEND: return "shifted suspend key"; case KEY_SUNDO: return "shifted undo key"; case KEY_SUSPEND: return "suspend key"; case KEY_UNDO: return "undo key"; case KEY_MOUSE: return "Mouse event has occurred"; case KEY_RESIZE: return "Terminal resize event"; case KEY_EVENT: return "We were interrupted by an event"; case KEY_RETURN: return "return"; case ' ': return "space"; case '\t': return "tab"; case KEY_ESCAPE: return "escape"; default: if (isprint(ch)) snprintf(g_desc, sizeof(g_desc), "%c", ch); else snprintf(g_desc, sizeof(g_desc), "\\x%2.2x", ch); return g_desc; } return NULL; } HelpDialogDelegate::HelpDialogDelegate (const char *text, KeyHelp *key_help_array) : m_text (), m_first_visible_line (0) { if (text && text[0]) { m_text.SplitIntoLines(text); m_text.AppendString(""); } if (key_help_array) { for (KeyHelp *key = key_help_array; key->ch; ++key) { StreamString key_description; key_description.Printf("%10s - %s", CursesKeyToCString(key->ch), key->description); m_text.AppendString(std::move(key_description.GetString())); } } } HelpDialogDelegate::~HelpDialogDelegate() { } bool HelpDialogDelegate::WindowDelegateDraw (Window &window, bool force) { window.Erase(); const int window_height = window.GetHeight(); int x = 2; int y = 1; const int min_y = y; const int max_y = window_height - 1 - y; const int num_visible_lines = max_y - min_y + 1; const size_t num_lines = m_text.GetSize(); const char *bottom_message; if (num_lines <= num_visible_lines) bottom_message = "Press any key to exit"; else bottom_message = "Use arrows to scroll, any other key to exit"; window.DrawTitleBox(window.GetName(), bottom_message); while (y <= max_y) { window.MoveCursor(x, y); window.PutCStringTruncated(m_text.GetStringAtIndex(m_first_visible_line + y - min_y), 1); ++y; } return true; } HandleCharResult HelpDialogDelegate::WindowDelegateHandleChar (Window &window, int key) { bool done = false; const size_t num_lines = m_text.GetSize(); const size_t num_visible_lines = window.GetHeight() - 2; if (num_lines <= num_visible_lines) { done = true; // If we have all lines visible and don't need scrolling, then any // key press will cause us to exit } else { switch (key) { case KEY_UP: if (m_first_visible_line > 0) --m_first_visible_line; break; case KEY_DOWN: if (m_first_visible_line + num_visible_lines < num_lines) ++m_first_visible_line; break; case KEY_PPAGE: case ',': if (m_first_visible_line > 0) { if (m_first_visible_line >= num_visible_lines) m_first_visible_line -= num_visible_lines; else m_first_visible_line = 0; } break; case KEY_NPAGE: case '.': if (m_first_visible_line + num_visible_lines < num_lines) { m_first_visible_line += num_visible_lines; if (m_first_visible_line > num_lines) m_first_visible_line = num_lines - num_visible_lines; } break; default: done = true; break; } } if (done) window.GetParent()->RemoveSubWindow(&window); return eKeyHandled; } class ApplicationDelegate : public WindowDelegate, public MenuDelegate { public: enum { eMenuID_LLDB = 1, eMenuID_LLDBAbout, eMenuID_LLDBExit, eMenuID_Target, eMenuID_TargetCreate, eMenuID_TargetDelete, eMenuID_Process, eMenuID_ProcessAttach, eMenuID_ProcessDetach, eMenuID_ProcessLaunch, eMenuID_ProcessContinue, eMenuID_ProcessHalt, eMenuID_ProcessKill, eMenuID_Thread, eMenuID_ThreadStepIn, eMenuID_ThreadStepOver, eMenuID_ThreadStepOut, eMenuID_View, eMenuID_ViewBacktrace, eMenuID_ViewRegisters, eMenuID_ViewSource, eMenuID_ViewVariables, eMenuID_Help, eMenuID_HelpGUIHelp }; ApplicationDelegate (Application &app, Debugger &debugger) : WindowDelegate (), MenuDelegate (), m_app (app), m_debugger (debugger) { } virtual ~ApplicationDelegate () { } virtual bool WindowDelegateDraw (Window &window, bool force) { return false; // Drawing not handled, let standard window drawing happen } virtual HandleCharResult WindowDelegateHandleChar (Window &window, int key) { switch (key) { case '\t': window.SelectNextWindowAsActive(); return eKeyHandled; case 'h': window.CreateHelpSubwindow(); return eKeyHandled; case KEY_ESCAPE: return eQuitApplication; default: break; } return eKeyNotHandled; } virtual const char * WindowDelegateGetHelpText () { return "Welcome to the LLDB curses GUI.\n\n" "Press the TAB key to change the selected view.\n" "Each view has its own keyboard shortcuts, press 'h' to open a dialog to display them.\n\n" "Common key bindings for all views:"; } virtual KeyHelp * WindowDelegateGetKeyHelp () { static curses::KeyHelp g_source_view_key_help[] = { { '\t', "Select next view" }, { 'h', "Show help dialog with view specific key bindings" }, { ',', "Page up" }, { '.', "Page down" }, { KEY_UP, "Select previous" }, { KEY_DOWN, "Select next" }, { KEY_LEFT, "Unexpand or select parent" }, { KEY_RIGHT, "Expand" }, { KEY_PPAGE, "Page up" }, { KEY_NPAGE, "Page down" }, { '\0', NULL } }; return g_source_view_key_help; } virtual MenuActionResult MenuDelegateAction (Menu &menu) { switch (menu.GetIdentifier()) { case eMenuID_ThreadStepIn: { ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); if (exe_ctx.HasThreadScope()) { Process *process = exe_ctx.GetProcessPtr(); if (process && process->IsAlive() && StateIsStoppedState (process->GetState(), true)) exe_ctx.GetThreadRef().StepIn(true, true); } } return MenuActionResult::Handled; case eMenuID_ThreadStepOut: { ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); if (exe_ctx.HasThreadScope()) { Process *process = exe_ctx.GetProcessPtr(); if (process && process->IsAlive() && StateIsStoppedState (process->GetState(), true)) exe_ctx.GetThreadRef().StepOut(); } } return MenuActionResult::Handled; case eMenuID_ThreadStepOver: { ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); if (exe_ctx.HasThreadScope()) { Process *process = exe_ctx.GetProcessPtr(); if (process && process->IsAlive() && StateIsStoppedState (process->GetState(), true)) exe_ctx.GetThreadRef().StepOver(true); } } return MenuActionResult::Handled; case eMenuID_ProcessContinue: { ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); if (exe_ctx.HasProcessScope()) { Process *process = exe_ctx.GetProcessPtr(); if (process && process->IsAlive() && StateIsStoppedState (process->GetState(), true)) process->Resume(); } } return MenuActionResult::Handled; case eMenuID_ProcessKill: { ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); if (exe_ctx.HasProcessScope()) { Process *process = exe_ctx.GetProcessPtr(); if (process && process->IsAlive()) process->Destroy(); } } return MenuActionResult::Handled; case eMenuID_ProcessHalt: { ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); if (exe_ctx.HasProcessScope()) { Process *process = exe_ctx.GetProcessPtr(); if (process && process->IsAlive()) process->Halt(); } } return MenuActionResult::Handled; case eMenuID_ProcessDetach: { ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); if (exe_ctx.HasProcessScope()) { Process *process = exe_ctx.GetProcessPtr(); if (process && process->IsAlive()) process->Detach(false); } } return MenuActionResult::Handled; case eMenuID_Process: { // Populate the menu with all of the threads if the process is stopped when // the Process menu gets selected and is about to display its submenu. Menus &submenus = menu.GetSubmenus(); ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); Process *process = exe_ctx.GetProcessPtr(); if (process && process->IsAlive() && StateIsStoppedState (process->GetState(), true)) { if (submenus.size() == 7) menu.AddSubmenu (MenuSP (new Menu(Menu::Type::Separator))); else if (submenus.size() > 8) submenus.erase (submenus.begin() + 8, submenus.end()); ThreadList &threads = process->GetThreadList(); Mutex::Locker locker (threads.GetMutex()); size_t num_threads = threads.GetSize(); for (size_t i=0; iGetIndexID()); const char *thread_name = thread_sp->GetName(); if (thread_name && thread_name[0]) thread_menu_title.Printf (" %s", thread_name); else { const char *queue_name = thread_sp->GetQueueName(); if (queue_name && queue_name[0]) thread_menu_title.Printf (" %s", queue_name); } menu.AddSubmenu (MenuSP (new Menu(thread_menu_title.GetString().c_str(), NULL, menu_char, thread_sp->GetID()))); } } else if (submenus.size() > 7) { // Remove the separator and any other thread submenu items // that were previously added submenus.erase (submenus.begin() + 7, submenus.end()); } // Since we are adding and removing items we need to recalculate the name lengths menu.RecalculateNameLengths(); } return MenuActionResult::Handled; case eMenuID_ViewVariables: { WindowSP main_window_sp = m_app.GetMainWindow(); WindowSP source_window_sp = main_window_sp->FindSubWindow("Source"); WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables"); WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers"); const Rect source_bounds = source_window_sp->GetBounds(); if (variables_window_sp) { const Rect variables_bounds = variables_window_sp->GetBounds(); main_window_sp->RemoveSubWindow(variables_window_sp.get()); if (registers_window_sp) { // We have a registers window, so give all the area back to the registers window Rect registers_bounds = variables_bounds; registers_bounds.size.width = source_bounds.size.width; registers_window_sp->SetBounds(registers_bounds); } else { // We have no registers window showing so give the bottom // area back to the source view source_window_sp->Resize (source_bounds.size.width, source_bounds.size.height + variables_bounds.size.height); } } else { Rect new_variables_rect; if (registers_window_sp) { // We have a registers window so split the area of the registers // window into two columns where the left hand side will be the // variables and the right hand side will be the registers const Rect variables_bounds = registers_window_sp->GetBounds(); Rect new_registers_rect; variables_bounds.VerticalSplitPercentage (0.50, new_variables_rect, new_registers_rect); registers_window_sp->SetBounds (new_registers_rect); } else { // No variables window, grab the bottom part of the source window Rect new_source_rect; source_bounds.HorizontalSplitPercentage (0.70, new_source_rect, new_variables_rect); source_window_sp->SetBounds (new_source_rect); } WindowSP new_window_sp = main_window_sp->CreateSubWindow ("Variables", new_variables_rect, false); new_window_sp->SetDelegate (WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger))); } touchwin(stdscr); } return MenuActionResult::Handled; case eMenuID_ViewRegisters: { WindowSP main_window_sp = m_app.GetMainWindow(); WindowSP source_window_sp = main_window_sp->FindSubWindow("Source"); WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables"); WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers"); const Rect source_bounds = source_window_sp->GetBounds(); if (registers_window_sp) { if (variables_window_sp) { const Rect variables_bounds = variables_window_sp->GetBounds(); // We have a variables window, so give all the area back to the variables window variables_window_sp->Resize (variables_bounds.size.width + registers_window_sp->GetWidth(), variables_bounds.size.height); } else { // We have no variables window showing so give the bottom // area back to the source view source_window_sp->Resize (source_bounds.size.width, source_bounds.size.height + registers_window_sp->GetHeight()); } main_window_sp->RemoveSubWindow(registers_window_sp.get()); } else { Rect new_regs_rect; if (variables_window_sp) { // We have a variables window, split it into two columns // where the left hand side will be the variables and the // right hand side will be the registers const Rect variables_bounds = variables_window_sp->GetBounds(); Rect new_vars_rect; variables_bounds.VerticalSplitPercentage (0.50, new_vars_rect, new_regs_rect); variables_window_sp->SetBounds (new_vars_rect); } else { // No registers window, grab the bottom part of the source window Rect new_source_rect; source_bounds.HorizontalSplitPercentage (0.70, new_source_rect, new_regs_rect); source_window_sp->SetBounds (new_source_rect); } WindowSP new_window_sp = main_window_sp->CreateSubWindow ("Registers", new_regs_rect, false); new_window_sp->SetDelegate (WindowDelegateSP(new RegistersWindowDelegate(m_debugger))); } touchwin(stdscr); } return MenuActionResult::Handled; case eMenuID_HelpGUIHelp: m_app.GetMainWindow ()->CreateHelpSubwindow(); return MenuActionResult::Handled; default: break; } return MenuActionResult::NotHandled; } protected: Application &m_app; Debugger &m_debugger; }; class StatusBarWindowDelegate : public WindowDelegate { public: StatusBarWindowDelegate (Debugger &debugger) : m_debugger (debugger) { } virtual ~StatusBarWindowDelegate () { } virtual bool WindowDelegateDraw (Window &window, bool force) { ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); Process *process = exe_ctx.GetProcessPtr(); Thread *thread = exe_ctx.GetThreadPtr(); StackFrame *frame = exe_ctx.GetFramePtr(); window.Erase(); window.SetBackground(2); window.MoveCursor (0, 0); if (process) { const StateType state = process->GetState(); window.Printf ("Process: %5" PRIu64 " %10s", process->GetID(), StateAsCString(state)); if (StateIsStoppedState(state, true)) { window.MoveCursor (40, 0); if (thread) window.Printf ("Thread: 0x%4.4" PRIx64, thread->GetID()); window.MoveCursor (60, 0); if (frame) window.Printf ("Frame: %3u PC = 0x%16.16" PRIx64, frame->GetFrameIndex(), frame->GetFrameCodeAddress().GetOpcodeLoadAddress (exe_ctx.GetTargetPtr())); } else if (state == eStateExited) { const char *exit_desc = process->GetExitDescription(); const int exit_status = process->GetExitStatus(); if (exit_desc && exit_desc[0]) window.Printf (" with status = %i (%s)", exit_status, exit_desc); else window.Printf (" with status = %i", exit_status); } } window.DeferredRefresh(); return true; } protected: Debugger &m_debugger; }; class SourceFileWindowDelegate : public WindowDelegate { public: SourceFileWindowDelegate (Debugger &debugger) : WindowDelegate (), m_debugger (debugger), m_sc (), m_file_sp (), m_disassembly_scope (NULL), m_disassembly_sp (), m_disassembly_range (), m_line_width (4), m_selected_line (0), m_pc_line (0), m_stop_id (0), m_frame_idx (UINT32_MAX), m_first_visible_line (0), m_min_x (0), m_min_y (0), m_max_x (0), m_max_y (0) { } virtual ~SourceFileWindowDelegate() { } void Update (const SymbolContext &sc) { m_sc = sc; } uint32_t NumVisibleLines () const { return m_max_y - m_min_y; } virtual const char * WindowDelegateGetHelpText () { return "Source/Disassembly window keyboard shortcuts:"; } virtual KeyHelp * WindowDelegateGetKeyHelp () { static curses::KeyHelp g_source_view_key_help[] = { { KEY_RETURN, "Run to selected line with one shot breakpoint" }, { KEY_UP, "Select previous source line" }, { KEY_DOWN, "Select next source line" }, { KEY_PPAGE, "Page up" }, { KEY_NPAGE, "Page down" }, { 'b', "Set breakpoint on selected source/disassembly line" }, { 'c', "Continue process" }, { 'd', "Detach and resume process" }, { 'D', "Detach with process suspended" }, { 'h', "Show help dialog" }, { 'k', "Kill process" }, { 'n', "Step over (source line)" }, { 'N', "Step over (single instruction)" }, { 'o', "Step out" }, { 's', "Step in (source line)" }, { 'S', "Step in (single instruction)" }, { ',', "Page up" }, { '.', "Page down" }, { '\0', NULL } }; return g_source_view_key_help; } virtual bool WindowDelegateDraw (Window &window, bool force) { ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); Process *process = exe_ctx.GetProcessPtr(); Thread *thread = NULL; bool update_location = false; if (process) { StateType state = process->GetState(); if (StateIsStoppedState(state, true)) { // We are stopped, so it is ok to update_location = true; } } m_min_x = 1; m_min_y = 1; m_max_x = window.GetMaxX()-1; m_max_y = window.GetMaxY()-1; const uint32_t num_visible_lines = NumVisibleLines(); StackFrameSP frame_sp; bool set_selected_line_to_pc = false; if (update_location) { const bool process_alive = process ? process->IsAlive() : false; bool thread_changed = false; if (process_alive) { thread = exe_ctx.GetThreadPtr(); if (thread) { frame_sp = thread->GetSelectedFrame(); auto tid = thread->GetID(); thread_changed = tid != m_tid; m_tid = tid; } else { if (m_tid != LLDB_INVALID_THREAD_ID) { thread_changed = true; m_tid = LLDB_INVALID_THREAD_ID; } } } const uint32_t stop_id = process ? process->GetStopID() : 0; const bool stop_id_changed = stop_id != m_stop_id; bool frame_changed = false; m_stop_id = stop_id; if (frame_sp) { m_sc = frame_sp->GetSymbolContext(eSymbolContextEverything); const uint32_t frame_idx = frame_sp->GetFrameIndex(); frame_changed = frame_idx != m_frame_idx; m_frame_idx = frame_idx; } else { m_sc.Clear(true); frame_changed = m_frame_idx != UINT32_MAX; m_frame_idx = UINT32_MAX; } const bool context_changed = thread_changed || frame_changed || stop_id_changed; if (process_alive) { if (m_sc.line_entry.IsValid()) { m_pc_line = m_sc.line_entry.line; if (m_pc_line != UINT32_MAX) --m_pc_line; // Convert to zero based line number... // Update the selected line if the stop ID changed... if (context_changed) m_selected_line = m_pc_line; if (m_file_sp && m_file_sp->FileSpecMatches(m_sc.line_entry.file)) { // Same file, nothing to do, we should either have the // lines or not (source file missing) if (m_selected_line >= m_first_visible_line) { if (m_selected_line >= m_first_visible_line + num_visible_lines) m_first_visible_line = m_selected_line - 10; } else { if (m_selected_line > 10) m_first_visible_line = m_selected_line - 10; else m_first_visible_line = 0; } } else { // File changed, set selected line to the line with the PC m_selected_line = m_pc_line; m_file_sp = m_debugger.GetSourceManager().GetFile(m_sc.line_entry.file); if (m_file_sp) { const size_t num_lines = m_file_sp->GetNumLines(); int m_line_width = 1; for (size_t n = num_lines; n >= 10; n = n / 10) ++m_line_width; snprintf (m_line_format, sizeof(m_line_format), " %%%iu ", m_line_width); if (num_lines < num_visible_lines || m_selected_line < num_visible_lines) m_first_visible_line = 0; else m_first_visible_line = m_selected_line - 10; } } } else { m_file_sp.reset(); } if (!m_file_sp || m_file_sp->GetNumLines() == 0) { // Show disassembly bool prefer_file_cache = false; if (m_sc.function) { if (m_disassembly_scope != m_sc.function) { m_disassembly_scope = m_sc.function; m_disassembly_sp = m_sc.function->GetInstructions (exe_ctx, NULL, prefer_file_cache); if (m_disassembly_sp) { set_selected_line_to_pc = true; m_disassembly_range = m_sc.function->GetAddressRange(); } else { m_disassembly_range.Clear(); } } else { set_selected_line_to_pc = context_changed; } } else if (m_sc.symbol) { if (m_disassembly_scope != m_sc.symbol) { m_disassembly_scope = m_sc.symbol; m_disassembly_sp = m_sc.symbol->GetInstructions (exe_ctx, NULL, prefer_file_cache); if (m_disassembly_sp) { set_selected_line_to_pc = true; m_disassembly_range.GetBaseAddress() = m_sc.symbol->GetAddress(); m_disassembly_range.SetByteSize(m_sc.symbol->GetByteSize()); } else { m_disassembly_range.Clear(); } } else { set_selected_line_to_pc = context_changed; } } } } else { m_pc_line = UINT32_MAX; } } window.Erase(); window.DrawTitleBox ("Sources"); Target *target = exe_ctx.GetTargetPtr(); const size_t num_source_lines = GetNumSourceLines(); if (num_source_lines > 0) { // Display source BreakpointLines bp_lines; if (target) { BreakpointList &bp_list = target->GetBreakpointList(); const size_t num_bps = bp_list.GetSize(); for (size_t bp_idx=0; bp_idxGetNumLocations(); for (size_t bp_loc_idx=0; bp_loc_idxGetLocationAtIndex(bp_loc_idx); LineEntry bp_loc_line_entry; if (bp_loc_sp->GetAddress().CalculateSymbolContextLineEntry (bp_loc_line_entry)) { if (m_file_sp->GetFileSpec() == bp_loc_line_entry.file) { bp_lines.insert(bp_loc_line_entry.line); } } } } } const attr_t selected_highlight_attr = A_REVERSE; const attr_t pc_highlight_attr = COLOR_PAIR(1); for (int i=0; iGetLineLength(curr_line + 1, false); if (line_len > 0) window.PutCString(m_file_sp->PeekLineData(curr_line + 1), line_len); if (is_pc_line && frame_sp && frame_sp->GetConcreteFrameIndex() == 0) { StopInfoSP stop_info_sp; if (thread) stop_info_sp = thread->GetStopInfo(); if (stop_info_sp) { const char *stop_description = stop_info_sp->GetDescription(); if (stop_description && stop_description[0]) { size_t stop_description_len = strlen(stop_description); int desc_x = window.GetWidth() - stop_description_len - 16; window.Printf ("%*s", desc_x - window.GetCursorX(), ""); //window.MoveCursor(window.GetWidth() - stop_description_len - 15, line_y); window.Printf ("<<< Thread %u: %s ", thread->GetIndexID(), stop_description); } } else { window.Printf ("%*s", window.GetWidth() - window.GetCursorX() - 1, ""); } } if (highlight_attr) window.AttributeOff(highlight_attr); } else { break; } } } else { size_t num_disassembly_lines = GetNumDisassemblyLines(); if (num_disassembly_lines > 0) { // Display disassembly BreakpointAddrs bp_file_addrs; Target *target = exe_ctx.GetTargetPtr(); if (target) { BreakpointList &bp_list = target->GetBreakpointList(); const size_t num_bps = bp_list.GetSize(); for (size_t bp_idx=0; bp_idxGetNumLocations(); for (size_t bp_loc_idx=0; bp_loc_idxGetLocationAtIndex(bp_loc_idx); LineEntry bp_loc_line_entry; const lldb::addr_t file_addr = bp_loc_sp->GetAddress().GetFileAddress(); if (file_addr != LLDB_INVALID_ADDRESS) { if (m_disassembly_range.ContainsFileAddress(file_addr)) bp_file_addrs.insert(file_addr); } } } } const attr_t selected_highlight_attr = A_REVERSE; const attr_t pc_highlight_attr = COLOR_PAIR(1); StreamString strm; InstructionList &insts = m_disassembly_sp->GetInstructionList(); Address pc_address; if (frame_sp) pc_address = frame_sp->GetFrameCodeAddress(); const uint32_t pc_idx = pc_address.IsValid() ? insts.GetIndexOfInstructionAtAddress (pc_address) : UINT32_MAX; if (set_selected_line_to_pc) { m_selected_line = pc_idx; } const uint32_t non_visible_pc_offset = (num_visible_lines / 5); if (m_first_visible_line >= num_disassembly_lines) m_first_visible_line = 0; if (pc_idx < num_disassembly_lines) { if (pc_idx < m_first_visible_line || pc_idx >= m_first_visible_line + num_visible_lines) m_first_visible_line = pc_idx - non_visible_pc_offset; } for (size_t i=0; iGetAddress().GetFileAddress()) != bp_file_addrs.end()) bp_attr = COLOR_PAIR(2); if (bp_attr) window.AttributeOn(bp_attr); window.Printf (" 0x%16.16llx ", inst->GetAddress().GetLoadAddress(target)); if (bp_attr) window.AttributeOff(bp_attr); window.PutChar(ACS_VLINE); // Mark the line with the PC with a diamond if (is_pc_line) window.PutChar(ACS_DIAMOND); else window.PutChar(' '); if (highlight_attr) window.AttributeOn(highlight_attr); const char *mnemonic = inst->GetMnemonic(&exe_ctx); const char *operands = inst->GetOperands(&exe_ctx); const char *comment = inst->GetComment(&exe_ctx); if (mnemonic && mnemonic[0] == '\0') mnemonic = NULL; if (operands && operands[0] == '\0') operands = NULL; if (comment && comment[0] == '\0') comment = NULL; strm.Clear(); if (mnemonic && operands && comment) strm.Printf ("%-8s %-25s ; %s", mnemonic, operands, comment); else if (mnemonic && operands) strm.Printf ("%-8s %s", mnemonic, operands); else if (mnemonic) strm.Printf ("%s", mnemonic); int right_pad = 1; window.PutCStringTruncated(strm.GetString().c_str(), right_pad); if (is_pc_line && frame_sp && frame_sp->GetConcreteFrameIndex() == 0) { StopInfoSP stop_info_sp; if (thread) stop_info_sp = thread->GetStopInfo(); if (stop_info_sp) { const char *stop_description = stop_info_sp->GetDescription(); if (stop_description && stop_description[0]) { size_t stop_description_len = strlen(stop_description); int desc_x = window.GetWidth() - stop_description_len - 16; window.Printf ("%*s", desc_x - window.GetCursorX(), ""); //window.MoveCursor(window.GetWidth() - stop_description_len - 15, line_y); window.Printf ("<<< Thread %u: %s ", thread->GetIndexID(), stop_description); } } else { window.Printf ("%*s", window.GetWidth() - window.GetCursorX() - 1, ""); } } if (highlight_attr) window.AttributeOff(highlight_attr); } } } window.DeferredRefresh(); return true; // Drawing handled } size_t GetNumLines () { size_t num_lines = GetNumSourceLines(); if (num_lines == 0) num_lines = GetNumDisassemblyLines(); return num_lines; } size_t GetNumSourceLines () const { if (m_file_sp) return m_file_sp->GetNumLines(); return 0; } size_t GetNumDisassemblyLines () const { if (m_disassembly_sp) return m_disassembly_sp->GetInstructionList().GetSize(); return 0; } virtual HandleCharResult WindowDelegateHandleChar (Window &window, int c) { const uint32_t num_visible_lines = NumVisibleLines(); const size_t num_lines = GetNumLines (); switch (c) { case ',': case KEY_PPAGE: // Page up key if (m_first_visible_line > num_visible_lines) m_first_visible_line -= num_visible_lines; else m_first_visible_line = 0; m_selected_line = m_first_visible_line; return eKeyHandled; case '.': case KEY_NPAGE: // Page down key { if (m_first_visible_line + num_visible_lines < num_lines) m_first_visible_line += num_visible_lines; else if (num_lines < num_visible_lines) m_first_visible_line = 0; else m_first_visible_line = num_lines - num_visible_lines; m_selected_line = m_first_visible_line; } return eKeyHandled; case KEY_UP: if (m_selected_line > 0) { m_selected_line--; if (m_first_visible_line > m_selected_line) m_first_visible_line = m_selected_line; } return eKeyHandled; case KEY_DOWN: if (m_selected_line + 1 < num_lines) { m_selected_line++; if (m_first_visible_line + num_visible_lines < m_selected_line) m_first_visible_line++; } return eKeyHandled; case '\r': case '\n': case KEY_ENTER: // Set a breakpoint and run to the line using a one shot breakpoint if (GetNumSourceLines() > 0) { ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); if (exe_ctx.HasProcessScope() && exe_ctx.GetProcessRef().IsAlive()) { BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint (NULL, // Don't limit the breakpoint to certain modules m_file_sp->GetFileSpec(), // Source file m_selected_line + 1, // Source line number (m_selected_line is zero based) eLazyBoolCalculate, // Check inlines using global setting eLazyBoolCalculate, // Skip prologue using global setting, false, // internal false); // request_hardware // Make breakpoint one shot bp_sp->GetOptions()->SetOneShot(true); exe_ctx.GetProcessRef().Resume(); } } else if (m_selected_line < GetNumDisassemblyLines()) { const Instruction *inst = m_disassembly_sp->GetInstructionList().GetInstructionAtIndex(m_selected_line).get(); ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); if (exe_ctx.HasTargetScope()) { Address addr = inst->GetAddress(); BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint (addr, // lldb_private::Address false, // internal false); // request_hardware // Make breakpoint one shot bp_sp->GetOptions()->SetOneShot(true); exe_ctx.GetProcessRef().Resume(); } } return eKeyHandled; case 'b': // 'b' == toggle breakpoint on currently selected line if (m_selected_line < GetNumSourceLines()) { ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); if (exe_ctx.HasTargetScope()) { BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint (NULL, // Don't limit the breakpoint to certain modules m_file_sp->GetFileSpec(), // Source file m_selected_line + 1, // Source line number (m_selected_line is zero based) eLazyBoolCalculate, // Check inlines using global setting eLazyBoolCalculate, // Skip prologue using global setting, false, // internal false); // request_hardware } } else if (m_selected_line < GetNumDisassemblyLines()) { const Instruction *inst = m_disassembly_sp->GetInstructionList().GetInstructionAtIndex(m_selected_line).get(); ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); if (exe_ctx.HasTargetScope()) { Address addr = inst->GetAddress(); BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint (addr, // lldb_private::Address false, // internal false); // request_hardware } } return eKeyHandled; case 'd': // 'd' == detach and let run case 'D': // 'D' == detach and keep stopped { ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); if (exe_ctx.HasProcessScope()) exe_ctx.GetProcessRef().Detach(c == 'D'); } return eKeyHandled; case 'k': // 'k' == kill { ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); if (exe_ctx.HasProcessScope()) exe_ctx.GetProcessRef().Destroy(); } return eKeyHandled; case 'c': // 'c' == continue { ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); if (exe_ctx.HasProcessScope()) exe_ctx.GetProcessRef().Resume(); } return eKeyHandled; case 'o': // 'o' == step out { ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); if (exe_ctx.HasThreadScope() && StateIsStoppedState (exe_ctx.GetProcessRef().GetState(), true)) { exe_ctx.GetThreadRef().StepOut(); } } return eKeyHandled; case 'n': // 'n' == step over case 'N': // 'N' == step over instruction { ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); if (exe_ctx.HasThreadScope() && StateIsStoppedState (exe_ctx.GetProcessRef().GetState(), true)) { bool source_step = (c == 'n'); exe_ctx.GetThreadRef().StepOver(source_step); } } return eKeyHandled; case 's': // 's' == step into case 'S': // 'S' == step into instruction { ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); if (exe_ctx.HasThreadScope() && StateIsStoppedState (exe_ctx.GetProcessRef().GetState(), true)) { bool source_step = (c == 's'); bool avoid_code_without_debug_info = true; exe_ctx.GetThreadRef().StepIn(source_step, avoid_code_without_debug_info); } } return eKeyHandled; case 'h': window.CreateHelpSubwindow (); return eKeyHandled; default: break; } return eKeyNotHandled; } protected: typedef std::set BreakpointLines; typedef std::set BreakpointAddrs; Debugger &m_debugger; SymbolContext m_sc; SourceManager::FileSP m_file_sp; SymbolContextScope *m_disassembly_scope; lldb::DisassemblerSP m_disassembly_sp; AddressRange m_disassembly_range; lldb::user_id_t m_tid; char m_line_format[8]; int m_line_width; uint32_t m_selected_line; // The selected line uint32_t m_pc_line; // The line with the PC uint32_t m_stop_id; uint32_t m_frame_idx; int m_first_visible_line; int m_min_x; int m_min_y; int m_max_x; int m_max_y; }; DisplayOptions ValueObjectListDelegate::g_options = { true }; IOHandlerCursesGUI::IOHandlerCursesGUI (Debugger &debugger) : IOHandler (debugger) { } void IOHandlerCursesGUI::Activate () { IOHandler::Activate(); if (!m_app_ap) { m_app_ap.reset (new Application (GetInputFILE(), GetOutputFILE())); // This is both a window and a menu delegate std::shared_ptr app_delegate_sp(new ApplicationDelegate(*m_app_ap, m_debugger)); MenuDelegateSP app_menu_delegate_sp = std::static_pointer_cast(app_delegate_sp); MenuSP lldb_menu_sp(new Menu("LLDB" , "F1", KEY_F(1), ApplicationDelegate::eMenuID_LLDB)); MenuSP exit_menuitem_sp(new Menu("Exit", NULL, 'x', ApplicationDelegate::eMenuID_LLDBExit)); exit_menuitem_sp->SetCannedResult(MenuActionResult::Quit); lldb_menu_sp->AddSubmenu (MenuSP (new Menu("About LLDB", NULL, 'a', ApplicationDelegate::eMenuID_LLDBAbout))); lldb_menu_sp->AddSubmenu (MenuSP (new Menu(Menu::Type::Separator))); lldb_menu_sp->AddSubmenu (exit_menuitem_sp); MenuSP target_menu_sp(new Menu("Target" ,"F2", KEY_F(2), ApplicationDelegate::eMenuID_Target)); target_menu_sp->AddSubmenu (MenuSP (new Menu("Create", NULL, 'c', ApplicationDelegate::eMenuID_TargetCreate))); target_menu_sp->AddSubmenu (MenuSP (new Menu("Delete", NULL, 'd', ApplicationDelegate::eMenuID_TargetDelete))); MenuSP process_menu_sp(new Menu("Process", "F3", KEY_F(3), ApplicationDelegate::eMenuID_Process)); process_menu_sp->AddSubmenu (MenuSP (new Menu("Attach" , NULL, 'a', ApplicationDelegate::eMenuID_ProcessAttach))); process_menu_sp->AddSubmenu (MenuSP (new Menu("Detach" , NULL, 'd', ApplicationDelegate::eMenuID_ProcessDetach))); process_menu_sp->AddSubmenu (MenuSP (new Menu("Launch" , NULL, 'l', ApplicationDelegate::eMenuID_ProcessLaunch))); process_menu_sp->AddSubmenu (MenuSP (new Menu(Menu::Type::Separator))); process_menu_sp->AddSubmenu (MenuSP (new Menu("Continue", NULL, 'c', ApplicationDelegate::eMenuID_ProcessContinue))); process_menu_sp->AddSubmenu (MenuSP (new Menu("Halt" , NULL, 'h', ApplicationDelegate::eMenuID_ProcessHalt))); process_menu_sp->AddSubmenu (MenuSP (new Menu("Kill" , NULL, 'k', ApplicationDelegate::eMenuID_ProcessKill))); MenuSP thread_menu_sp(new Menu("Thread", "F4", KEY_F(4), ApplicationDelegate::eMenuID_Thread)); thread_menu_sp->AddSubmenu (MenuSP (new Menu("Step In" , NULL, 'i', ApplicationDelegate::eMenuID_ThreadStepIn))); thread_menu_sp->AddSubmenu (MenuSP (new Menu("Step Over", NULL, 'v', ApplicationDelegate::eMenuID_ThreadStepOver))); thread_menu_sp->AddSubmenu (MenuSP (new Menu("Step Out" , NULL, 'o', ApplicationDelegate::eMenuID_ThreadStepOut))); MenuSP view_menu_sp(new Menu("View", "F5", KEY_F(5), ApplicationDelegate::eMenuID_View)); view_menu_sp->AddSubmenu (MenuSP (new Menu("Backtrace", NULL, 'b', ApplicationDelegate::eMenuID_ViewBacktrace))); view_menu_sp->AddSubmenu (MenuSP (new Menu("Registers", NULL, 'r', ApplicationDelegate::eMenuID_ViewRegisters))); view_menu_sp->AddSubmenu (MenuSP (new Menu("Source" , NULL, 's', ApplicationDelegate::eMenuID_ViewSource))); view_menu_sp->AddSubmenu (MenuSP (new Menu("Variables", NULL, 'v', ApplicationDelegate::eMenuID_ViewVariables))); MenuSP help_menu_sp(new Menu("Help", "F6", KEY_F(6), ApplicationDelegate::eMenuID_Help)); help_menu_sp->AddSubmenu (MenuSP (new Menu("GUI Help", NULL, 'g', ApplicationDelegate::eMenuID_HelpGUIHelp))); m_app_ap->Initialize(); WindowSP &main_window_sp = m_app_ap->GetMainWindow(); MenuSP menubar_sp(new Menu(Menu::Type::Bar)); menubar_sp->AddSubmenu (lldb_menu_sp); menubar_sp->AddSubmenu (target_menu_sp); menubar_sp->AddSubmenu (process_menu_sp); menubar_sp->AddSubmenu (thread_menu_sp); menubar_sp->AddSubmenu (view_menu_sp); menubar_sp->AddSubmenu (help_menu_sp); menubar_sp->SetDelegate(app_menu_delegate_sp); Rect content_bounds = main_window_sp->GetFrame(); Rect menubar_bounds = content_bounds.MakeMenuBar(); Rect status_bounds = content_bounds.MakeStatusBar(); Rect source_bounds; Rect variables_bounds; Rect threads_bounds; Rect source_variables_bounds; content_bounds.VerticalSplitPercentage(0.80, source_variables_bounds, threads_bounds); source_variables_bounds.HorizontalSplitPercentage(0.70, source_bounds, variables_bounds); WindowSP menubar_window_sp = main_window_sp->CreateSubWindow("Menubar", menubar_bounds, false); // Let the menubar get keys if the active window doesn't handle the // keys that are typed so it can respond to menubar key presses. menubar_window_sp->SetCanBeActive(false); // Don't let the menubar become the active window menubar_window_sp->SetDelegate(menubar_sp); WindowSP source_window_sp (main_window_sp->CreateSubWindow("Source", source_bounds, true)); WindowSP variables_window_sp (main_window_sp->CreateSubWindow("Variables", variables_bounds, false)); WindowSP threads_window_sp (main_window_sp->CreateSubWindow("Threads", threads_bounds, false)); WindowSP status_window_sp (main_window_sp->CreateSubWindow("Status", status_bounds, false)); status_window_sp->SetCanBeActive(false); // Don't let the status bar become the active window main_window_sp->SetDelegate (std::static_pointer_cast(app_delegate_sp)); source_window_sp->SetDelegate (WindowDelegateSP(new SourceFileWindowDelegate(m_debugger))); variables_window_sp->SetDelegate (WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger))); TreeDelegateSP thread_delegate_sp (new ThreadTreeDelegate(m_debugger)); threads_window_sp->SetDelegate (WindowDelegateSP(new TreeWindowDelegate(m_debugger, thread_delegate_sp))); status_window_sp->SetDelegate (WindowDelegateSP(new StatusBarWindowDelegate(m_debugger))); // 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; main_window_sp->CreateHelpSubwindow(); } init_pair (1, COLOR_WHITE , COLOR_BLUE ); init_pair (2, COLOR_BLACK , COLOR_WHITE ); init_pair (3, COLOR_MAGENTA , COLOR_WHITE ); init_pair (4, COLOR_MAGENTA , COLOR_BLACK ); init_pair (5, COLOR_RED , COLOR_BLACK ); } } void IOHandlerCursesGUI::Deactivate () { m_app_ap->Terminate(); } void IOHandlerCursesGUI::Run () { m_app_ap->Run(m_debugger); SetIsDone(true); } IOHandlerCursesGUI::~IOHandlerCursesGUI () { } void IOHandlerCursesGUI::Hide () { } void IOHandlerCursesGUI::Refresh () { } void IOHandlerCursesGUI::Cancel () { } void IOHandlerCursesGUI::Interrupt () { } void IOHandlerCursesGUI::GotEOF() { } #endif // #ifndef LLDB_DISABLE_CURSES