1 //===-- IOHandlerCursesGUI.cpp --------------------------------------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "lldb/Core/IOHandlerCursesGUI.h" 10 #include "lldb/Host/Config.h" 11 12 #if LLDB_ENABLE_CURSES 13 #if CURSES_HAVE_NCURSES_CURSES_H 14 #include <ncurses/curses.h> 15 #include <ncurses/panel.h> 16 #else 17 #include <curses.h> 18 #include <panel.h> 19 #endif 20 #endif 21 22 #if defined(__APPLE__) 23 #include <deque> 24 #endif 25 #include <string> 26 27 #include "lldb/Core/Debugger.h" 28 #include "lldb/Core/StreamFile.h" 29 #include "lldb/Host/File.h" 30 #include "lldb/Utility/Predicate.h" 31 #include "lldb/Utility/Status.h" 32 #include "lldb/Utility/StreamString.h" 33 #include "lldb/Utility/StringList.h" 34 #include "lldb/lldb-forward.h" 35 36 #include "lldb/Interpreter/CommandCompletions.h" 37 #include "lldb/Interpreter/CommandInterpreter.h" 38 39 #if LLDB_ENABLE_CURSES 40 #include "lldb/Breakpoint/BreakpointLocation.h" 41 #include "lldb/Core/Module.h" 42 #include "lldb/Core/ValueObject.h" 43 #include "lldb/Core/ValueObjectRegister.h" 44 #include "lldb/Symbol/Block.h" 45 #include "lldb/Symbol/Function.h" 46 #include "lldb/Symbol/Symbol.h" 47 #include "lldb/Symbol/VariableList.h" 48 #include "lldb/Target/Process.h" 49 #include "lldb/Target/RegisterContext.h" 50 #include "lldb/Target/StackFrame.h" 51 #include "lldb/Target/StopInfo.h" 52 #include "lldb/Target/Target.h" 53 #include "lldb/Target/Thread.h" 54 #include "lldb/Utility/State.h" 55 #endif 56 57 #include "llvm/ADT/StringRef.h" 58 59 #ifdef _WIN32 60 #include "lldb/Host/windows/windows.h" 61 #endif 62 63 #include <memory> 64 #include <mutex> 65 66 #include <assert.h> 67 #include <ctype.h> 68 #include <errno.h> 69 #include <locale.h> 70 #include <stdint.h> 71 #include <stdio.h> 72 #include <string.h> 73 #include <type_traits> 74 75 using namespace lldb; 76 using namespace lldb_private; 77 using llvm::None; 78 using llvm::Optional; 79 using llvm::StringRef; 80 81 // we may want curses to be disabled for some builds for instance, windows 82 #if LLDB_ENABLE_CURSES 83 84 #define KEY_RETURN 10 85 #define KEY_ESCAPE 27 86 87 namespace curses { 88 class Menu; 89 class MenuDelegate; 90 class Window; 91 class WindowDelegate; 92 typedef std::shared_ptr<Menu> MenuSP; 93 typedef std::shared_ptr<MenuDelegate> MenuDelegateSP; 94 typedef std::shared_ptr<Window> WindowSP; 95 typedef std::shared_ptr<WindowDelegate> WindowDelegateSP; 96 typedef std::vector<MenuSP> Menus; 97 typedef std::vector<WindowSP> Windows; 98 typedef std::vector<WindowDelegateSP> WindowDelegates; 99 100 #if 0 101 type summary add -s "x=${var.x}, y=${var.y}" curses::Point 102 type summary add -s "w=${var.width}, h=${var.height}" curses::Size 103 type summary add -s "${var.origin%S} ${var.size%S}" curses::Rect 104 #endif 105 106 struct Point { 107 int x; 108 int y; 109 110 Point(int _x = 0, int _y = 0) : x(_x), y(_y) {} 111 112 void Clear() { 113 x = 0; 114 y = 0; 115 } 116 117 Point &operator+=(const Point &rhs) { 118 x += rhs.x; 119 y += rhs.y; 120 return *this; 121 } 122 123 void Dump() { printf("(x=%i, y=%i)\n", x, y); } 124 }; 125 126 bool operator==(const Point &lhs, const Point &rhs) { 127 return lhs.x == rhs.x && lhs.y == rhs.y; 128 } 129 130 bool operator!=(const Point &lhs, const Point &rhs) { 131 return lhs.x != rhs.x || lhs.y != rhs.y; 132 } 133 134 struct Size { 135 int width; 136 int height; 137 Size(int w = 0, int h = 0) : width(w), height(h) {} 138 139 void Clear() { 140 width = 0; 141 height = 0; 142 } 143 144 void Dump() { printf("(w=%i, h=%i)\n", width, height); } 145 }; 146 147 bool operator==(const Size &lhs, const Size &rhs) { 148 return lhs.width == rhs.width && lhs.height == rhs.height; 149 } 150 151 bool operator!=(const Size &lhs, const Size &rhs) { 152 return lhs.width != rhs.width || lhs.height != rhs.height; 153 } 154 155 struct Rect { 156 Point origin; 157 Size size; 158 159 Rect() : origin(), size() {} 160 161 Rect(const Point &p, const Size &s) : origin(p), size(s) {} 162 163 void Clear() { 164 origin.Clear(); 165 size.Clear(); 166 } 167 168 void Dump() { 169 printf("(x=%i, y=%i), w=%i, h=%i)\n", origin.x, origin.y, size.width, 170 size.height); 171 } 172 173 void Inset(int w, int h) { 174 if (size.width > w * 2) 175 size.width -= w * 2; 176 origin.x += w; 177 178 if (size.height > h * 2) 179 size.height -= h * 2; 180 origin.y += h; 181 } 182 183 // Return a status bar rectangle which is the last line of this rectangle. 184 // This rectangle will be modified to not include the status bar area. 185 Rect MakeStatusBar() { 186 Rect status_bar; 187 if (size.height > 1) { 188 status_bar.origin.x = origin.x; 189 status_bar.origin.y = size.height; 190 status_bar.size.width = size.width; 191 status_bar.size.height = 1; 192 --size.height; 193 } 194 return status_bar; 195 } 196 197 // Return a menubar rectangle which is the first line of this rectangle. This 198 // rectangle will be modified to not include the menubar area. 199 Rect MakeMenuBar() { 200 Rect menubar; 201 if (size.height > 1) { 202 menubar.origin.x = origin.x; 203 menubar.origin.y = origin.y; 204 menubar.size.width = size.width; 205 menubar.size.height = 1; 206 ++origin.y; 207 --size.height; 208 } 209 return menubar; 210 } 211 212 void HorizontalSplitPercentage(float top_percentage, Rect &top, 213 Rect &bottom) const { 214 float top_height = top_percentage * size.height; 215 HorizontalSplit(top_height, top, bottom); 216 } 217 218 void HorizontalSplit(int top_height, Rect &top, Rect &bottom) const { 219 top = *this; 220 if (top_height < size.height) { 221 top.size.height = top_height; 222 bottom.origin.x = origin.x; 223 bottom.origin.y = origin.y + top.size.height; 224 bottom.size.width = size.width; 225 bottom.size.height = size.height - top.size.height; 226 } else { 227 bottom.Clear(); 228 } 229 } 230 231 void VerticalSplitPercentage(float left_percentage, Rect &left, 232 Rect &right) const { 233 float left_width = left_percentage * size.width; 234 VerticalSplit(left_width, left, right); 235 } 236 237 void VerticalSplit(int left_width, Rect &left, Rect &right) const { 238 left = *this; 239 if (left_width < size.width) { 240 left.size.width = left_width; 241 right.origin.x = origin.x + left.size.width; 242 right.origin.y = origin.y; 243 right.size.width = size.width - left.size.width; 244 right.size.height = size.height; 245 } else { 246 right.Clear(); 247 } 248 } 249 }; 250 251 bool operator==(const Rect &lhs, const Rect &rhs) { 252 return lhs.origin == rhs.origin && lhs.size == rhs.size; 253 } 254 255 bool operator!=(const Rect &lhs, const Rect &rhs) { 256 return lhs.origin != rhs.origin || lhs.size != rhs.size; 257 } 258 259 enum HandleCharResult { 260 eKeyNotHandled = 0, 261 eKeyHandled = 1, 262 eQuitApplication = 2 263 }; 264 265 enum class MenuActionResult { 266 Handled, 267 NotHandled, 268 Quit // Exit all menus and quit 269 }; 270 271 struct KeyHelp { 272 int ch; 273 const char *description; 274 }; 275 276 // COLOR_PAIR index names 277 enum { 278 // First 16 colors are 8 black background and 8 blue background colors, 279 // needed by OutputColoredStringTruncated(). 280 BlackOnBlack = 1, 281 RedOnBlack, 282 GreenOnBlack, 283 YellowOnBlack, 284 BlueOnBlack, 285 MagentaOnBlack, 286 CyanOnBlack, 287 WhiteOnBlack, 288 BlackOnBlue, 289 RedOnBlue, 290 GreenOnBlue, 291 YellowOnBlue, 292 BlueOnBlue, 293 MagentaOnBlue, 294 CyanOnBlue, 295 WhiteOnBlue, 296 // Other colors, as needed. 297 BlackOnWhite, 298 MagentaOnWhite, 299 LastColorPairIndex = MagentaOnWhite 300 }; 301 302 class WindowDelegate { 303 public: 304 virtual ~WindowDelegate() = default; 305 306 virtual bool WindowDelegateDraw(Window &window, bool force) { 307 return false; // Drawing not handled 308 } 309 310 virtual HandleCharResult WindowDelegateHandleChar(Window &window, int key) { 311 return eKeyNotHandled; 312 } 313 314 virtual const char *WindowDelegateGetHelpText() { return nullptr; } 315 316 virtual KeyHelp *WindowDelegateGetKeyHelp() { return nullptr; } 317 }; 318 319 class HelpDialogDelegate : public WindowDelegate { 320 public: 321 HelpDialogDelegate(const char *text, KeyHelp *key_help_array); 322 323 ~HelpDialogDelegate() override; 324 325 bool WindowDelegateDraw(Window &window, bool force) override; 326 327 HandleCharResult WindowDelegateHandleChar(Window &window, int key) override; 328 329 size_t GetNumLines() const { return m_text.GetSize(); } 330 331 size_t GetMaxLineLength() const { return m_text.GetMaxStringLength(); } 332 333 protected: 334 StringList m_text; 335 int m_first_visible_line; 336 }; 337 338 class Window { 339 public: 340 Window(const char *name) 341 : m_name(name), m_window(nullptr), m_panel(nullptr), m_parent(nullptr), 342 m_subwindows(), m_delegate_sp(), m_curr_active_window_idx(UINT32_MAX), 343 m_prev_active_window_idx(UINT32_MAX), m_delete(false), 344 m_needs_update(true), m_can_activate(true), m_is_subwin(false) {} 345 346 Window(const char *name, WINDOW *w, bool del = true) 347 : m_name(name), m_window(nullptr), m_panel(nullptr), m_parent(nullptr), 348 m_subwindows(), m_delegate_sp(), m_curr_active_window_idx(UINT32_MAX), 349 m_prev_active_window_idx(UINT32_MAX), m_delete(del), 350 m_needs_update(true), m_can_activate(true), m_is_subwin(false) { 351 if (w) 352 Reset(w); 353 } 354 355 Window(const char *name, const Rect &bounds) 356 : m_name(name), m_window(nullptr), m_parent(nullptr), m_subwindows(), 357 m_delegate_sp(), m_curr_active_window_idx(UINT32_MAX), 358 m_prev_active_window_idx(UINT32_MAX), m_delete(true), 359 m_needs_update(true), m_can_activate(true), m_is_subwin(false) { 360 Reset(::newwin(bounds.size.height, bounds.size.width, bounds.origin.y, 361 bounds.origin.y)); 362 } 363 364 virtual ~Window() { 365 RemoveSubWindows(); 366 Reset(); 367 } 368 369 void Reset(WINDOW *w = nullptr, bool del = true) { 370 if (m_window == w) 371 return; 372 373 if (m_panel) { 374 ::del_panel(m_panel); 375 m_panel = nullptr; 376 } 377 if (m_window && m_delete) { 378 ::delwin(m_window); 379 m_window = nullptr; 380 m_delete = false; 381 } 382 if (w) { 383 m_window = w; 384 m_panel = ::new_panel(m_window); 385 m_delete = del; 386 } 387 } 388 389 void AttributeOn(attr_t attr) { ::wattron(m_window, attr); } 390 void AttributeOff(attr_t attr) { ::wattroff(m_window, attr); } 391 void Box(chtype v_char = ACS_VLINE, chtype h_char = ACS_HLINE) { 392 ::box(m_window, v_char, h_char); 393 } 394 void Clear() { ::wclear(m_window); } 395 void Erase() { ::werase(m_window); } 396 Rect GetBounds() const { 397 return Rect(GetParentOrigin(), GetSize()); 398 } // Get the rectangle in our parent window 399 int GetChar() { return ::wgetch(m_window); } 400 int GetCursorX() const { return getcurx(m_window); } 401 int GetCursorY() const { return getcury(m_window); } 402 Rect GetFrame() const { 403 return Rect(Point(), GetSize()); 404 } // Get our rectangle in our own coordinate system 405 Point GetParentOrigin() const { return Point(GetParentX(), GetParentY()); } 406 Size GetSize() const { return Size(GetWidth(), GetHeight()); } 407 int GetParentX() const { return getparx(m_window); } 408 int GetParentY() const { return getpary(m_window); } 409 int GetMaxX() const { return getmaxx(m_window); } 410 int GetMaxY() const { return getmaxy(m_window); } 411 int GetWidth() const { return GetMaxX(); } 412 int GetHeight() const { return GetMaxY(); } 413 void MoveCursor(int x, int y) { ::wmove(m_window, y, x); } 414 void MoveWindow(int x, int y) { MoveWindow(Point(x, y)); } 415 void Resize(int w, int h) { ::wresize(m_window, h, w); } 416 void Resize(const Size &size) { 417 ::wresize(m_window, size.height, size.width); 418 } 419 void PutChar(int ch) { ::waddch(m_window, ch); } 420 void PutCString(const char *s, int len = -1) { ::waddnstr(m_window, s, len); } 421 void SetBackground(int color_pair_idx) { 422 ::wbkgd(m_window, COLOR_PAIR(color_pair_idx)); 423 } 424 425 void PutCStringTruncated(int right_pad, const char *s, int len = -1) { 426 int bytes_left = GetWidth() - GetCursorX(); 427 if (bytes_left > right_pad) { 428 bytes_left -= right_pad; 429 ::waddnstr(m_window, s, len < 0 ? bytes_left : std::min(bytes_left, len)); 430 } 431 } 432 433 void MoveWindow(const Point &origin) { 434 const bool moving_window = origin != GetParentOrigin(); 435 if (m_is_subwin && moving_window) { 436 // Can't move subwindows, must delete and re-create 437 Size size = GetSize(); 438 Reset(::subwin(m_parent->m_window, size.height, size.width, origin.y, 439 origin.x), 440 true); 441 } else { 442 ::mvwin(m_window, origin.y, origin.x); 443 } 444 } 445 446 void SetBounds(const Rect &bounds) { 447 const bool moving_window = bounds.origin != GetParentOrigin(); 448 if (m_is_subwin && moving_window) { 449 // Can't move subwindows, must delete and re-create 450 Reset(::subwin(m_parent->m_window, bounds.size.height, bounds.size.width, 451 bounds.origin.y, bounds.origin.x), 452 true); 453 } else { 454 if (moving_window) 455 MoveWindow(bounds.origin); 456 Resize(bounds.size); 457 } 458 } 459 460 void Printf(const char *format, ...) __attribute__((format(printf, 2, 3))) { 461 va_list args; 462 va_start(args, format); 463 vwprintw(m_window, format, args); 464 va_end(args); 465 } 466 467 void PrintfTruncated(int right_pad, const char *format, ...) 468 __attribute__((format(printf, 3, 4))) { 469 va_list args; 470 va_start(args, format); 471 StreamString strm; 472 strm.PrintfVarArg(format, args); 473 va_end(args); 474 PutCStringTruncated(right_pad, strm.GetData()); 475 } 476 477 size_t LimitLengthToRestOfLine(size_t length) const { 478 return std::min<size_t>(length, std::max(0, GetWidth() - GetCursorX() - 1)); 479 } 480 481 // Curses doesn't allow direct output of color escape sequences, but that's 482 // how we get source lines from the Highligher class. Read the line and 483 // convert color escape sequences to curses color attributes. Use 484 // first_skip_count to skip leading visible characters. Returns false if all 485 // visible characters were skipped due to first_skip_count. 486 bool OutputColoredStringTruncated(int right_pad, StringRef string, 487 size_t skip_first_count, 488 bool use_blue_background) { 489 attr_t saved_attr; 490 short saved_pair; 491 bool result = false; 492 wattr_get(m_window, &saved_attr, &saved_pair, nullptr); 493 if (use_blue_background) 494 ::wattron(m_window, COLOR_PAIR(WhiteOnBlue)); 495 while (!string.empty()) { 496 size_t esc_pos = string.find('\x1b'); 497 if (esc_pos == StringRef::npos) { 498 string = string.substr(skip_first_count); 499 if (!string.empty()) { 500 PutCStringTruncated(right_pad, string.data(), string.size()); 501 result = true; 502 } 503 break; 504 } 505 if (esc_pos > 0) { 506 if (skip_first_count > 0) { 507 int skip = std::min(esc_pos, skip_first_count); 508 string = string.substr(skip); 509 skip_first_count -= skip; 510 esc_pos -= skip; 511 } 512 if (esc_pos > 0) { 513 PutCStringTruncated(right_pad, string.data(), esc_pos); 514 result = true; 515 string = string.drop_front(esc_pos); 516 } 517 } 518 bool consumed = string.consume_front("\x1b"); 519 assert(consumed); 520 UNUSED_IF_ASSERT_DISABLED(consumed); 521 // This is written to match our Highlighter classes, which seem to 522 // generate only foreground color escape sequences. If necessary, this 523 // will need to be extended. 524 if (!string.consume_front("[")) { 525 llvm::errs() << "Missing '[' in color escape sequence.\n"; 526 continue; 527 } 528 // Only 8 basic foreground colors and reset, our Highlighter doesn't use 529 // anything else. 530 int value; 531 if (!!string.consumeInteger(10, value) || // Returns false on success. 532 !(value == 0 || (value >= 30 && value <= 37))) { 533 llvm::errs() << "No valid color code in color escape sequence.\n"; 534 continue; 535 } 536 if (!string.consume_front("m")) { 537 llvm::errs() << "Missing 'm' in color escape sequence.\n"; 538 continue; 539 } 540 if (value == 0) { // Reset. 541 wattr_set(m_window, saved_attr, saved_pair, nullptr); 542 if (use_blue_background) 543 ::wattron(m_window, COLOR_PAIR(WhiteOnBlue)); 544 } else { 545 // Mapped directly to first 16 color pairs (black/blue background). 546 ::wattron(m_window, 547 COLOR_PAIR(value - 30 + 1 + (use_blue_background ? 8 : 0))); 548 } 549 } 550 wattr_set(m_window, saved_attr, saved_pair, nullptr); 551 return result; 552 } 553 554 void Touch() { 555 ::touchwin(m_window); 556 if (m_parent) 557 m_parent->Touch(); 558 } 559 560 WindowSP CreateSubWindow(const char *name, const Rect &bounds, 561 bool make_active) { 562 auto get_window = [this, &bounds]() { 563 return m_window 564 ? ::subwin(m_window, bounds.size.height, bounds.size.width, 565 bounds.origin.y, bounds.origin.x) 566 : ::newwin(bounds.size.height, bounds.size.width, 567 bounds.origin.y, bounds.origin.x); 568 }; 569 WindowSP subwindow_sp = std::make_shared<Window>(name, get_window(), true); 570 subwindow_sp->m_is_subwin = subwindow_sp.operator bool(); 571 subwindow_sp->m_parent = this; 572 if (make_active) { 573 m_prev_active_window_idx = m_curr_active_window_idx; 574 m_curr_active_window_idx = m_subwindows.size(); 575 } 576 m_subwindows.push_back(subwindow_sp); 577 ::top_panel(subwindow_sp->m_panel); 578 m_needs_update = true; 579 return subwindow_sp; 580 } 581 582 bool RemoveSubWindow(Window *window) { 583 Windows::iterator pos, end = m_subwindows.end(); 584 size_t i = 0; 585 for (pos = m_subwindows.begin(); pos != end; ++pos, ++i) { 586 if ((*pos).get() == window) { 587 if (m_prev_active_window_idx == i) 588 m_prev_active_window_idx = UINT32_MAX; 589 else if (m_prev_active_window_idx != UINT32_MAX && 590 m_prev_active_window_idx > i) 591 --m_prev_active_window_idx; 592 593 if (m_curr_active_window_idx == i) 594 m_curr_active_window_idx = UINT32_MAX; 595 else if (m_curr_active_window_idx != UINT32_MAX && 596 m_curr_active_window_idx > i) 597 --m_curr_active_window_idx; 598 window->Erase(); 599 m_subwindows.erase(pos); 600 m_needs_update = true; 601 if (m_parent) 602 m_parent->Touch(); 603 else 604 ::touchwin(stdscr); 605 return true; 606 } 607 } 608 return false; 609 } 610 611 WindowSP FindSubWindow(const char *name) { 612 Windows::iterator pos, end = m_subwindows.end(); 613 size_t i = 0; 614 for (pos = m_subwindows.begin(); pos != end; ++pos, ++i) { 615 if ((*pos)->m_name == name) 616 return *pos; 617 } 618 return WindowSP(); 619 } 620 621 void RemoveSubWindows() { 622 m_curr_active_window_idx = UINT32_MAX; 623 m_prev_active_window_idx = UINT32_MAX; 624 for (Windows::iterator pos = m_subwindows.begin(); 625 pos != m_subwindows.end(); pos = m_subwindows.erase(pos)) { 626 (*pos)->Erase(); 627 } 628 if (m_parent) 629 m_parent->Touch(); 630 else 631 ::touchwin(stdscr); 632 } 633 634 WINDOW *get() { return m_window; } 635 636 operator WINDOW *() { return m_window; } 637 638 // Window drawing utilities 639 void DrawTitleBox(const char *title, const char *bottom_message = nullptr) { 640 attr_t attr = 0; 641 if (IsActive()) 642 attr = A_BOLD | COLOR_PAIR(BlackOnWhite); 643 else 644 attr = 0; 645 if (attr) 646 AttributeOn(attr); 647 648 Box(); 649 MoveCursor(3, 0); 650 651 if (title && title[0]) { 652 PutChar('<'); 653 PutCString(title); 654 PutChar('>'); 655 } 656 657 if (bottom_message && bottom_message[0]) { 658 int bottom_message_length = strlen(bottom_message); 659 int x = GetWidth() - 3 - (bottom_message_length + 2); 660 661 if (x > 0) { 662 MoveCursor(x, GetHeight() - 1); 663 PutChar('['); 664 PutCString(bottom_message); 665 PutChar(']'); 666 } else { 667 MoveCursor(1, GetHeight() - 1); 668 PutChar('['); 669 PutCStringTruncated(1, bottom_message); 670 } 671 } 672 if (attr) 673 AttributeOff(attr); 674 } 675 676 virtual void Draw(bool force) { 677 if (m_delegate_sp && m_delegate_sp->WindowDelegateDraw(*this, force)) 678 return; 679 680 for (auto &subwindow_sp : m_subwindows) 681 subwindow_sp->Draw(force); 682 } 683 684 bool CreateHelpSubwindow() { 685 if (m_delegate_sp) { 686 const char *text = m_delegate_sp->WindowDelegateGetHelpText(); 687 KeyHelp *key_help = m_delegate_sp->WindowDelegateGetKeyHelp(); 688 if ((text && text[0]) || key_help) { 689 std::unique_ptr<HelpDialogDelegate> help_delegate_up( 690 new HelpDialogDelegate(text, key_help)); 691 const size_t num_lines = help_delegate_up->GetNumLines(); 692 const size_t max_length = help_delegate_up->GetMaxLineLength(); 693 Rect bounds = GetBounds(); 694 bounds.Inset(1, 1); 695 if (max_length + 4 < static_cast<size_t>(bounds.size.width)) { 696 bounds.origin.x += (bounds.size.width - max_length + 4) / 2; 697 bounds.size.width = max_length + 4; 698 } else { 699 if (bounds.size.width > 100) { 700 const int inset_w = bounds.size.width / 4; 701 bounds.origin.x += inset_w; 702 bounds.size.width -= 2 * inset_w; 703 } 704 } 705 706 if (num_lines + 2 < static_cast<size_t>(bounds.size.height)) { 707 bounds.origin.y += (bounds.size.height - num_lines + 2) / 2; 708 bounds.size.height = num_lines + 2; 709 } else { 710 if (bounds.size.height > 100) { 711 const int inset_h = bounds.size.height / 4; 712 bounds.origin.y += inset_h; 713 bounds.size.height -= 2 * inset_h; 714 } 715 } 716 WindowSP help_window_sp; 717 Window *parent_window = GetParent(); 718 if (parent_window) 719 help_window_sp = parent_window->CreateSubWindow("Help", bounds, true); 720 else 721 help_window_sp = CreateSubWindow("Help", bounds, true); 722 help_window_sp->SetDelegate( 723 WindowDelegateSP(help_delegate_up.release())); 724 return true; 725 } 726 } 727 return false; 728 } 729 730 virtual HandleCharResult HandleChar(int key) { 731 // Always check the active window first 732 HandleCharResult result = eKeyNotHandled; 733 WindowSP active_window_sp = GetActiveWindow(); 734 if (active_window_sp) { 735 result = active_window_sp->HandleChar(key); 736 if (result != eKeyNotHandled) 737 return result; 738 } 739 740 if (m_delegate_sp) { 741 result = m_delegate_sp->WindowDelegateHandleChar(*this, key); 742 if (result != eKeyNotHandled) 743 return result; 744 } 745 746 // Then check for any windows that want any keys that weren't handled. This 747 // is typically only for a menubar. Make a copy of the subwindows in case 748 // any HandleChar() functions muck with the subwindows. If we don't do 749 // this, we can crash when iterating over the subwindows. 750 Windows subwindows(m_subwindows); 751 for (auto subwindow_sp : subwindows) { 752 if (!subwindow_sp->m_can_activate) { 753 HandleCharResult result = subwindow_sp->HandleChar(key); 754 if (result != eKeyNotHandled) 755 return result; 756 } 757 } 758 759 return eKeyNotHandled; 760 } 761 762 WindowSP GetActiveWindow() { 763 if (!m_subwindows.empty()) { 764 if (m_curr_active_window_idx >= m_subwindows.size()) { 765 if (m_prev_active_window_idx < m_subwindows.size()) { 766 m_curr_active_window_idx = m_prev_active_window_idx; 767 m_prev_active_window_idx = UINT32_MAX; 768 } else if (IsActive()) { 769 m_prev_active_window_idx = UINT32_MAX; 770 m_curr_active_window_idx = UINT32_MAX; 771 772 // Find first window that wants to be active if this window is active 773 const size_t num_subwindows = m_subwindows.size(); 774 for (size_t i = 0; i < num_subwindows; ++i) { 775 if (m_subwindows[i]->GetCanBeActive()) { 776 m_curr_active_window_idx = i; 777 break; 778 } 779 } 780 } 781 } 782 783 if (m_curr_active_window_idx < m_subwindows.size()) 784 return m_subwindows[m_curr_active_window_idx]; 785 } 786 return WindowSP(); 787 } 788 789 bool GetCanBeActive() const { return m_can_activate; } 790 791 void SetCanBeActive(bool b) { m_can_activate = b; } 792 793 void SetDelegate(const WindowDelegateSP &delegate_sp) { 794 m_delegate_sp = delegate_sp; 795 } 796 797 Window *GetParent() const { return m_parent; } 798 799 bool IsActive() const { 800 if (m_parent) 801 return m_parent->GetActiveWindow().get() == this; 802 else 803 return true; // Top level window is always active 804 } 805 806 void SelectNextWindowAsActive() { 807 // Move active focus to next window 808 const int num_subwindows = m_subwindows.size(); 809 int start_idx = 0; 810 if (m_curr_active_window_idx != UINT32_MAX) { 811 m_prev_active_window_idx = m_curr_active_window_idx; 812 start_idx = m_curr_active_window_idx + 1; 813 } 814 for (int idx = start_idx; idx < num_subwindows; ++idx) { 815 if (m_subwindows[idx]->GetCanBeActive()) { 816 m_curr_active_window_idx = idx; 817 return; 818 } 819 } 820 for (int idx = 0; idx < start_idx; ++idx) { 821 if (m_subwindows[idx]->GetCanBeActive()) { 822 m_curr_active_window_idx = idx; 823 break; 824 } 825 } 826 } 827 828 void SelectPreviousWindowAsActive() { 829 // Move active focus to previous window 830 const int num_subwindows = m_subwindows.size(); 831 int start_idx = num_subwindows - 1; 832 if (m_curr_active_window_idx != UINT32_MAX) { 833 m_prev_active_window_idx = m_curr_active_window_idx; 834 start_idx = m_curr_active_window_idx - 1; 835 } 836 for (int idx = start_idx; idx >= 0; --idx) { 837 if (m_subwindows[idx]->GetCanBeActive()) { 838 m_curr_active_window_idx = idx; 839 return; 840 } 841 } 842 for (int idx = num_subwindows - 1; idx > start_idx; --idx) { 843 if (m_subwindows[idx]->GetCanBeActive()) { 844 m_curr_active_window_idx = idx; 845 break; 846 } 847 } 848 } 849 850 const char *GetName() const { return m_name.c_str(); } 851 852 protected: 853 std::string m_name; 854 WINDOW *m_window; 855 PANEL *m_panel; 856 Window *m_parent; 857 Windows m_subwindows; 858 WindowDelegateSP m_delegate_sp; 859 uint32_t m_curr_active_window_idx; 860 uint32_t m_prev_active_window_idx; 861 bool m_delete; 862 bool m_needs_update; 863 bool m_can_activate; 864 bool m_is_subwin; 865 866 private: 867 Window(const Window &) = delete; 868 const Window &operator=(const Window &) = delete; 869 }; 870 871 class MenuDelegate { 872 public: 873 virtual ~MenuDelegate() = default; 874 875 virtual MenuActionResult MenuDelegateAction(Menu &menu) = 0; 876 }; 877 878 class Menu : public WindowDelegate { 879 public: 880 enum class Type { Invalid, Bar, Item, Separator }; 881 882 // Menubar or separator constructor 883 Menu(Type type); 884 885 // Menuitem constructor 886 Menu(const char *name, const char *key_name, int key_value, 887 uint64_t identifier); 888 889 ~Menu() override = default; 890 891 const MenuDelegateSP &GetDelegate() const { return m_delegate_sp; } 892 893 void SetDelegate(const MenuDelegateSP &delegate_sp) { 894 m_delegate_sp = delegate_sp; 895 } 896 897 void RecalculateNameLengths(); 898 899 void AddSubmenu(const MenuSP &menu_sp); 900 901 int DrawAndRunMenu(Window &window); 902 903 void DrawMenuTitle(Window &window, bool highlight); 904 905 bool WindowDelegateDraw(Window &window, bool force) override; 906 907 HandleCharResult WindowDelegateHandleChar(Window &window, int key) override; 908 909 MenuActionResult ActionPrivate(Menu &menu) { 910 MenuActionResult result = MenuActionResult::NotHandled; 911 if (m_delegate_sp) { 912 result = m_delegate_sp->MenuDelegateAction(menu); 913 if (result != MenuActionResult::NotHandled) 914 return result; 915 } else if (m_parent) { 916 result = m_parent->ActionPrivate(menu); 917 if (result != MenuActionResult::NotHandled) 918 return result; 919 } 920 return m_canned_result; 921 } 922 923 MenuActionResult Action() { 924 // Call the recursive action so it can try to handle it with the menu 925 // delegate, and if not, try our parent menu 926 return ActionPrivate(*this); 927 } 928 929 void SetCannedResult(MenuActionResult result) { m_canned_result = result; } 930 931 Menus &GetSubmenus() { return m_submenus; } 932 933 const Menus &GetSubmenus() const { return m_submenus; } 934 935 int GetSelectedSubmenuIndex() const { return m_selected; } 936 937 void SetSelectedSubmenuIndex(int idx) { m_selected = idx; } 938 939 Type GetType() const { return m_type; } 940 941 int GetStartingColumn() const { return m_start_col; } 942 943 void SetStartingColumn(int col) { m_start_col = col; } 944 945 int GetKeyValue() const { return m_key_value; } 946 947 std::string &GetName() { return m_name; } 948 949 int GetDrawWidth() const { 950 return m_max_submenu_name_length + m_max_submenu_key_name_length + 8; 951 } 952 953 uint64_t GetIdentifier() const { return m_identifier; } 954 955 void SetIdentifier(uint64_t identifier) { m_identifier = identifier; } 956 957 protected: 958 std::string m_name; 959 std::string m_key_name; 960 uint64_t m_identifier; 961 Type m_type; 962 int m_key_value; 963 int m_start_col; 964 int m_max_submenu_name_length; 965 int m_max_submenu_key_name_length; 966 int m_selected; 967 Menu *m_parent; 968 Menus m_submenus; 969 WindowSP m_menu_window_sp; 970 MenuActionResult m_canned_result; 971 MenuDelegateSP m_delegate_sp; 972 }; 973 974 // Menubar or separator constructor 975 Menu::Menu(Type type) 976 : m_name(), m_key_name(), m_identifier(0), m_type(type), m_key_value(0), 977 m_start_col(0), m_max_submenu_name_length(0), 978 m_max_submenu_key_name_length(0), m_selected(0), m_parent(nullptr), 979 m_submenus(), m_canned_result(MenuActionResult::NotHandled), 980 m_delegate_sp() {} 981 982 // Menuitem constructor 983 Menu::Menu(const char *name, const char *key_name, int key_value, 984 uint64_t identifier) 985 : m_name(), m_key_name(), m_identifier(identifier), m_type(Type::Invalid), 986 m_key_value(key_value), m_start_col(0), m_max_submenu_name_length(0), 987 m_max_submenu_key_name_length(0), m_selected(0), m_parent(nullptr), 988 m_submenus(), m_canned_result(MenuActionResult::NotHandled), 989 m_delegate_sp() { 990 if (name && name[0]) { 991 m_name = name; 992 m_type = Type::Item; 993 if (key_name && key_name[0]) 994 m_key_name = key_name; 995 } else { 996 m_type = Type::Separator; 997 } 998 } 999 1000 void Menu::RecalculateNameLengths() { 1001 m_max_submenu_name_length = 0; 1002 m_max_submenu_key_name_length = 0; 1003 Menus &submenus = GetSubmenus(); 1004 const size_t num_submenus = submenus.size(); 1005 for (size_t i = 0; i < num_submenus; ++i) { 1006 Menu *submenu = submenus[i].get(); 1007 if (static_cast<size_t>(m_max_submenu_name_length) < submenu->m_name.size()) 1008 m_max_submenu_name_length = submenu->m_name.size(); 1009 if (static_cast<size_t>(m_max_submenu_key_name_length) < 1010 submenu->m_key_name.size()) 1011 m_max_submenu_key_name_length = submenu->m_key_name.size(); 1012 } 1013 } 1014 1015 void Menu::AddSubmenu(const MenuSP &menu_sp) { 1016 menu_sp->m_parent = this; 1017 if (static_cast<size_t>(m_max_submenu_name_length) < menu_sp->m_name.size()) 1018 m_max_submenu_name_length = menu_sp->m_name.size(); 1019 if (static_cast<size_t>(m_max_submenu_key_name_length) < 1020 menu_sp->m_key_name.size()) 1021 m_max_submenu_key_name_length = menu_sp->m_key_name.size(); 1022 m_submenus.push_back(menu_sp); 1023 } 1024 1025 void Menu::DrawMenuTitle(Window &window, bool highlight) { 1026 if (m_type == Type::Separator) { 1027 window.MoveCursor(0, window.GetCursorY()); 1028 window.PutChar(ACS_LTEE); 1029 int width = window.GetWidth(); 1030 if (width > 2) { 1031 width -= 2; 1032 for (int i = 0; i < width; ++i) 1033 window.PutChar(ACS_HLINE); 1034 } 1035 window.PutChar(ACS_RTEE); 1036 } else { 1037 const int shortcut_key = m_key_value; 1038 bool underlined_shortcut = false; 1039 const attr_t highlight_attr = A_REVERSE; 1040 if (highlight) 1041 window.AttributeOn(highlight_attr); 1042 if (llvm::isPrint(shortcut_key)) { 1043 size_t lower_pos = m_name.find(tolower(shortcut_key)); 1044 size_t upper_pos = m_name.find(toupper(shortcut_key)); 1045 const char *name = m_name.c_str(); 1046 size_t pos = std::min<size_t>(lower_pos, upper_pos); 1047 if (pos != std::string::npos) { 1048 underlined_shortcut = true; 1049 if (pos > 0) { 1050 window.PutCString(name, pos); 1051 name += pos; 1052 } 1053 const attr_t shortcut_attr = A_UNDERLINE | A_BOLD; 1054 window.AttributeOn(shortcut_attr); 1055 window.PutChar(name[0]); 1056 window.AttributeOff(shortcut_attr); 1057 name++; 1058 if (name[0]) 1059 window.PutCString(name); 1060 } 1061 } 1062 1063 if (!underlined_shortcut) { 1064 window.PutCString(m_name.c_str()); 1065 } 1066 1067 if (highlight) 1068 window.AttributeOff(highlight_attr); 1069 1070 if (m_key_name.empty()) { 1071 if (!underlined_shortcut && llvm::isPrint(m_key_value)) { 1072 window.AttributeOn(COLOR_PAIR(MagentaOnWhite)); 1073 window.Printf(" (%c)", m_key_value); 1074 window.AttributeOff(COLOR_PAIR(MagentaOnWhite)); 1075 } 1076 } else { 1077 window.AttributeOn(COLOR_PAIR(MagentaOnWhite)); 1078 window.Printf(" (%s)", m_key_name.c_str()); 1079 window.AttributeOff(COLOR_PAIR(MagentaOnWhite)); 1080 } 1081 } 1082 } 1083 1084 bool Menu::WindowDelegateDraw(Window &window, bool force) { 1085 Menus &submenus = GetSubmenus(); 1086 const size_t num_submenus = submenus.size(); 1087 const int selected_idx = GetSelectedSubmenuIndex(); 1088 Menu::Type menu_type = GetType(); 1089 switch (menu_type) { 1090 case Menu::Type::Bar: { 1091 window.SetBackground(BlackOnWhite); 1092 window.MoveCursor(0, 0); 1093 for (size_t i = 0; i < num_submenus; ++i) { 1094 Menu *menu = submenus[i].get(); 1095 if (i > 0) 1096 window.PutChar(' '); 1097 menu->SetStartingColumn(window.GetCursorX()); 1098 window.PutCString("| "); 1099 menu->DrawMenuTitle(window, false); 1100 } 1101 window.PutCString(" |"); 1102 } break; 1103 1104 case Menu::Type::Item: { 1105 int y = 1; 1106 int x = 3; 1107 // Draw the menu 1108 int cursor_x = 0; 1109 int cursor_y = 0; 1110 window.Erase(); 1111 window.SetBackground(BlackOnWhite); 1112 window.Box(); 1113 for (size_t i = 0; i < num_submenus; ++i) { 1114 const bool is_selected = (i == static_cast<size_t>(selected_idx)); 1115 window.MoveCursor(x, y + i); 1116 if (is_selected) { 1117 // Remember where we want the cursor to be 1118 cursor_x = x - 1; 1119 cursor_y = y + i; 1120 } 1121 submenus[i]->DrawMenuTitle(window, is_selected); 1122 } 1123 window.MoveCursor(cursor_x, cursor_y); 1124 } break; 1125 1126 default: 1127 case Menu::Type::Separator: 1128 break; 1129 } 1130 return true; // Drawing handled... 1131 } 1132 1133 HandleCharResult Menu::WindowDelegateHandleChar(Window &window, int key) { 1134 HandleCharResult result = eKeyNotHandled; 1135 1136 Menus &submenus = GetSubmenus(); 1137 const size_t num_submenus = submenus.size(); 1138 const int selected_idx = GetSelectedSubmenuIndex(); 1139 Menu::Type menu_type = GetType(); 1140 if (menu_type == Menu::Type::Bar) { 1141 MenuSP run_menu_sp; 1142 switch (key) { 1143 case KEY_DOWN: 1144 case KEY_UP: 1145 // Show last menu or first menu 1146 if (selected_idx < static_cast<int>(num_submenus)) 1147 run_menu_sp = submenus[selected_idx]; 1148 else if (!submenus.empty()) 1149 run_menu_sp = submenus.front(); 1150 result = eKeyHandled; 1151 break; 1152 1153 case KEY_RIGHT: 1154 ++m_selected; 1155 if (m_selected >= static_cast<int>(num_submenus)) 1156 m_selected = 0; 1157 if (m_selected < static_cast<int>(num_submenus)) 1158 run_menu_sp = submenus[m_selected]; 1159 else if (!submenus.empty()) 1160 run_menu_sp = submenus.front(); 1161 result = eKeyHandled; 1162 break; 1163 1164 case KEY_LEFT: 1165 --m_selected; 1166 if (m_selected < 0) 1167 m_selected = num_submenus - 1; 1168 if (m_selected < static_cast<int>(num_submenus)) 1169 run_menu_sp = submenus[m_selected]; 1170 else if (!submenus.empty()) 1171 run_menu_sp = submenus.front(); 1172 result = eKeyHandled; 1173 break; 1174 1175 default: 1176 for (size_t i = 0; i < num_submenus; ++i) { 1177 if (submenus[i]->GetKeyValue() == key) { 1178 SetSelectedSubmenuIndex(i); 1179 run_menu_sp = submenus[i]; 1180 result = eKeyHandled; 1181 break; 1182 } 1183 } 1184 break; 1185 } 1186 1187 if (run_menu_sp) { 1188 // Run the action on this menu in case we need to populate the menu with 1189 // dynamic content and also in case check marks, and any other menu 1190 // decorations need to be calculated 1191 if (run_menu_sp->Action() == MenuActionResult::Quit) 1192 return eQuitApplication; 1193 1194 Rect menu_bounds; 1195 menu_bounds.origin.x = run_menu_sp->GetStartingColumn(); 1196 menu_bounds.origin.y = 1; 1197 menu_bounds.size.width = run_menu_sp->GetDrawWidth(); 1198 menu_bounds.size.height = run_menu_sp->GetSubmenus().size() + 2; 1199 if (m_menu_window_sp) 1200 window.GetParent()->RemoveSubWindow(m_menu_window_sp.get()); 1201 1202 m_menu_window_sp = window.GetParent()->CreateSubWindow( 1203 run_menu_sp->GetName().c_str(), menu_bounds, true); 1204 m_menu_window_sp->SetDelegate(run_menu_sp); 1205 } 1206 } else if (menu_type == Menu::Type::Item) { 1207 switch (key) { 1208 case KEY_DOWN: 1209 if (m_submenus.size() > 1) { 1210 const int start_select = m_selected; 1211 while (++m_selected != start_select) { 1212 if (static_cast<size_t>(m_selected) >= num_submenus) 1213 m_selected = 0; 1214 if (m_submenus[m_selected]->GetType() == Type::Separator) 1215 continue; 1216 else 1217 break; 1218 } 1219 return eKeyHandled; 1220 } 1221 break; 1222 1223 case KEY_UP: 1224 if (m_submenus.size() > 1) { 1225 const int start_select = m_selected; 1226 while (--m_selected != start_select) { 1227 if (m_selected < static_cast<int>(0)) 1228 m_selected = num_submenus - 1; 1229 if (m_submenus[m_selected]->GetType() == Type::Separator) 1230 continue; 1231 else 1232 break; 1233 } 1234 return eKeyHandled; 1235 } 1236 break; 1237 1238 case KEY_RETURN: 1239 if (static_cast<size_t>(selected_idx) < num_submenus) { 1240 if (submenus[selected_idx]->Action() == MenuActionResult::Quit) 1241 return eQuitApplication; 1242 window.GetParent()->RemoveSubWindow(&window); 1243 return eKeyHandled; 1244 } 1245 break; 1246 1247 case KEY_ESCAPE: // Beware: pressing escape key has 1 to 2 second delay in 1248 // case other chars are entered for escaped sequences 1249 window.GetParent()->RemoveSubWindow(&window); 1250 return eKeyHandled; 1251 1252 default: 1253 for (size_t i = 0; i < num_submenus; ++i) { 1254 Menu *menu = submenus[i].get(); 1255 if (menu->GetKeyValue() == key) { 1256 SetSelectedSubmenuIndex(i); 1257 window.GetParent()->RemoveSubWindow(&window); 1258 if (menu->Action() == MenuActionResult::Quit) 1259 return eQuitApplication; 1260 return eKeyHandled; 1261 } 1262 } 1263 break; 1264 } 1265 } else if (menu_type == Menu::Type::Separator) { 1266 } 1267 return result; 1268 } 1269 1270 class Application { 1271 public: 1272 Application(FILE *in, FILE *out) 1273 : m_window_sp(), m_screen(nullptr), m_in(in), m_out(out) {} 1274 1275 ~Application() { 1276 m_window_delegates.clear(); 1277 m_window_sp.reset(); 1278 if (m_screen) { 1279 ::delscreen(m_screen); 1280 m_screen = nullptr; 1281 } 1282 } 1283 1284 void Initialize() { 1285 ::setlocale(LC_ALL, ""); 1286 ::setlocale(LC_CTYPE, ""); 1287 m_screen = ::newterm(nullptr, m_out, m_in); 1288 ::start_color(); 1289 ::curs_set(0); 1290 ::noecho(); 1291 ::keypad(stdscr, TRUE); 1292 } 1293 1294 void Terminate() { ::endwin(); } 1295 1296 void Run(Debugger &debugger) { 1297 bool done = false; 1298 int delay_in_tenths_of_a_second = 1; 1299 1300 // Alas the threading model in curses is a bit lame so we need to resort to 1301 // polling every 0.5 seconds. We could poll for stdin ourselves and then 1302 // pass the keys down but then we need to translate all of the escape 1303 // sequences ourselves. So we resort to polling for input because we need 1304 // to receive async process events while in this loop. 1305 1306 halfdelay(delay_in_tenths_of_a_second); // Poll using some number of tenths 1307 // of seconds seconds when calling 1308 // Window::GetChar() 1309 1310 ListenerSP listener_sp( 1311 Listener::MakeListener("lldb.IOHandler.curses.Application")); 1312 ConstString broadcaster_class_process(Process::GetStaticBroadcasterClass()); 1313 debugger.EnableForwardEvents(listener_sp); 1314 1315 m_update_screen = true; 1316 #if defined(__APPLE__) 1317 std::deque<int> escape_chars; 1318 #endif 1319 1320 while (!done) { 1321 if (m_update_screen) { 1322 m_window_sp->Draw(false); 1323 // All windows should be calling Window::DeferredRefresh() instead of 1324 // Window::Refresh() so we can do a single update and avoid any screen 1325 // blinking 1326 update_panels(); 1327 1328 // Cursor hiding isn't working on MacOSX, so hide it in the top left 1329 // corner 1330 m_window_sp->MoveCursor(0, 0); 1331 1332 doupdate(); 1333 m_update_screen = false; 1334 } 1335 1336 #if defined(__APPLE__) 1337 // Terminal.app doesn't map its function keys correctly, F1-F4 default 1338 // to: \033OP, \033OQ, \033OR, \033OS, so lets take care of this here if 1339 // possible 1340 int ch; 1341 if (escape_chars.empty()) 1342 ch = m_window_sp->GetChar(); 1343 else { 1344 ch = escape_chars.front(); 1345 escape_chars.pop_front(); 1346 } 1347 if (ch == KEY_ESCAPE) { 1348 int ch2 = m_window_sp->GetChar(); 1349 if (ch2 == 'O') { 1350 int ch3 = m_window_sp->GetChar(); 1351 switch (ch3) { 1352 case 'P': 1353 ch = KEY_F(1); 1354 break; 1355 case 'Q': 1356 ch = KEY_F(2); 1357 break; 1358 case 'R': 1359 ch = KEY_F(3); 1360 break; 1361 case 'S': 1362 ch = KEY_F(4); 1363 break; 1364 default: 1365 escape_chars.push_back(ch2); 1366 if (ch3 != -1) 1367 escape_chars.push_back(ch3); 1368 break; 1369 } 1370 } else if (ch2 != -1) 1371 escape_chars.push_back(ch2); 1372 } 1373 #else 1374 int ch = m_window_sp->GetChar(); 1375 1376 #endif 1377 if (ch == -1) { 1378 if (feof(m_in) || ferror(m_in)) { 1379 done = true; 1380 } else { 1381 // Just a timeout from using halfdelay(), check for events 1382 EventSP event_sp; 1383 while (listener_sp->PeekAtNextEvent()) { 1384 listener_sp->GetEvent(event_sp, std::chrono::seconds(0)); 1385 1386 if (event_sp) { 1387 Broadcaster *broadcaster = event_sp->GetBroadcaster(); 1388 if (broadcaster) { 1389 // uint32_t event_type = event_sp->GetType(); 1390 ConstString broadcaster_class( 1391 broadcaster->GetBroadcasterClass()); 1392 if (broadcaster_class == broadcaster_class_process) { 1393 m_update_screen = true; 1394 continue; // Don't get any key, just update our view 1395 } 1396 } 1397 } 1398 } 1399 } 1400 } else { 1401 HandleCharResult key_result = m_window_sp->HandleChar(ch); 1402 switch (key_result) { 1403 case eKeyHandled: 1404 m_update_screen = true; 1405 break; 1406 case eKeyNotHandled: 1407 if (ch == 12) { // Ctrl+L, force full redraw 1408 redrawwin(m_window_sp->get()); 1409 m_update_screen = true; 1410 } 1411 break; 1412 case eQuitApplication: 1413 done = true; 1414 break; 1415 } 1416 } 1417 } 1418 1419 debugger.CancelForwardEvents(listener_sp); 1420 } 1421 1422 WindowSP &GetMainWindow() { 1423 if (!m_window_sp) 1424 m_window_sp = std::make_shared<Window>("main", stdscr, false); 1425 return m_window_sp; 1426 } 1427 1428 void TerminalSizeChanged() { 1429 ::endwin(); 1430 ::refresh(); 1431 Rect content_bounds = m_window_sp->GetFrame(); 1432 m_window_sp->SetBounds(content_bounds); 1433 if (WindowSP menubar_window_sp = m_window_sp->FindSubWindow("Menubar")) 1434 menubar_window_sp->SetBounds(content_bounds.MakeMenuBar()); 1435 if (WindowSP status_window_sp = m_window_sp->FindSubWindow("Status")) 1436 status_window_sp->SetBounds(content_bounds.MakeStatusBar()); 1437 1438 WindowSP source_window_sp = m_window_sp->FindSubWindow("Source"); 1439 WindowSP variables_window_sp = m_window_sp->FindSubWindow("Variables"); 1440 WindowSP registers_window_sp = m_window_sp->FindSubWindow("Registers"); 1441 WindowSP threads_window_sp = m_window_sp->FindSubWindow("Threads"); 1442 1443 Rect threads_bounds; 1444 Rect source_variables_bounds; 1445 content_bounds.VerticalSplitPercentage(0.80, source_variables_bounds, 1446 threads_bounds); 1447 if (threads_window_sp) 1448 threads_window_sp->SetBounds(threads_bounds); 1449 else 1450 source_variables_bounds = content_bounds; 1451 1452 Rect source_bounds; 1453 Rect variables_registers_bounds; 1454 source_variables_bounds.HorizontalSplitPercentage( 1455 0.70, source_bounds, variables_registers_bounds); 1456 if (variables_window_sp || registers_window_sp) { 1457 if (variables_window_sp && registers_window_sp) { 1458 Rect variables_bounds; 1459 Rect registers_bounds; 1460 variables_registers_bounds.VerticalSplitPercentage( 1461 0.50, variables_bounds, registers_bounds); 1462 variables_window_sp->SetBounds(variables_bounds); 1463 registers_window_sp->SetBounds(registers_bounds); 1464 } else if (variables_window_sp) { 1465 variables_window_sp->SetBounds(variables_registers_bounds); 1466 } else { 1467 registers_window_sp->SetBounds(variables_registers_bounds); 1468 } 1469 } else { 1470 source_bounds = source_variables_bounds; 1471 } 1472 1473 source_window_sp->SetBounds(source_bounds); 1474 1475 touchwin(stdscr); 1476 redrawwin(m_window_sp->get()); 1477 m_update_screen = true; 1478 } 1479 1480 protected: 1481 WindowSP m_window_sp; 1482 WindowDelegates m_window_delegates; 1483 SCREEN *m_screen; 1484 FILE *m_in; 1485 FILE *m_out; 1486 bool m_update_screen = false; 1487 }; 1488 1489 } // namespace curses 1490 1491 using namespace curses; 1492 1493 struct Row { 1494 ValueObjectManager value; 1495 Row *parent; 1496 // The process stop ID when the children were calculated. 1497 uint32_t children_stop_id = 0; 1498 int row_idx = 0; 1499 int x = 1; 1500 int y = 1; 1501 bool might_have_children; 1502 bool expanded = false; 1503 bool calculated_children = false; 1504 std::vector<Row> children; 1505 1506 Row(const ValueObjectSP &v, Row *p) 1507 : value(v, lldb::eDynamicDontRunTarget, true), parent(p), 1508 might_have_children(v ? v->MightHaveChildren() : false) {} 1509 1510 size_t GetDepth() const { 1511 if (parent) 1512 return 1 + parent->GetDepth(); 1513 return 0; 1514 } 1515 1516 void Expand() { expanded = true; } 1517 1518 std::vector<Row> &GetChildren() { 1519 ProcessSP process_sp = value.GetProcessSP(); 1520 auto stop_id = process_sp->GetStopID(); 1521 if (process_sp && stop_id != children_stop_id) { 1522 children_stop_id = stop_id; 1523 calculated_children = false; 1524 } 1525 if (!calculated_children) { 1526 children.clear(); 1527 calculated_children = true; 1528 ValueObjectSP valobj = value.GetSP(); 1529 if (valobj) { 1530 const size_t num_children = valobj->GetNumChildren(); 1531 for (size_t i = 0; i < num_children; ++i) { 1532 children.push_back(Row(valobj->GetChildAtIndex(i, true), this)); 1533 } 1534 } 1535 } 1536 return children; 1537 } 1538 1539 void Unexpand() { 1540 expanded = false; 1541 calculated_children = false; 1542 children.clear(); 1543 } 1544 1545 void DrawTree(Window &window) { 1546 if (parent) 1547 parent->DrawTreeForChild(window, this, 0); 1548 1549 if (might_have_children) { 1550 // It we can get UTF8 characters to work we should try to use the 1551 // "symbol" UTF8 string below 1552 // const char *symbol = ""; 1553 // if (row.expanded) 1554 // symbol = "\xe2\x96\xbd "; 1555 // else 1556 // symbol = "\xe2\x96\xb7 "; 1557 // window.PutCString (symbol); 1558 1559 // The ACS_DARROW and ACS_RARROW don't look very nice they are just a 'v' 1560 // or '>' character... 1561 // if (expanded) 1562 // window.PutChar (ACS_DARROW); 1563 // else 1564 // window.PutChar (ACS_RARROW); 1565 // Since we can't find any good looking right arrow/down arrow symbols, 1566 // just use a diamond... 1567 window.PutChar(ACS_DIAMOND); 1568 window.PutChar(ACS_HLINE); 1569 } 1570 } 1571 1572 void DrawTreeForChild(Window &window, Row *child, uint32_t reverse_depth) { 1573 if (parent) 1574 parent->DrawTreeForChild(window, this, reverse_depth + 1); 1575 1576 if (&GetChildren().back() == child) { 1577 // Last child 1578 if (reverse_depth == 0) { 1579 window.PutChar(ACS_LLCORNER); 1580 window.PutChar(ACS_HLINE); 1581 } else { 1582 window.PutChar(' '); 1583 window.PutChar(' '); 1584 } 1585 } else { 1586 if (reverse_depth == 0) { 1587 window.PutChar(ACS_LTEE); 1588 window.PutChar(ACS_HLINE); 1589 } else { 1590 window.PutChar(ACS_VLINE); 1591 window.PutChar(' '); 1592 } 1593 } 1594 } 1595 }; 1596 1597 struct DisplayOptions { 1598 bool show_types; 1599 }; 1600 1601 class TreeItem; 1602 1603 class TreeDelegate { 1604 public: 1605 TreeDelegate() = default; 1606 virtual ~TreeDelegate() = default; 1607 1608 virtual void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) = 0; 1609 virtual void TreeDelegateGenerateChildren(TreeItem &item) = 0; 1610 virtual bool TreeDelegateItemSelected( 1611 TreeItem &item) = 0; // Return true if we need to update views 1612 }; 1613 1614 typedef std::shared_ptr<TreeDelegate> TreeDelegateSP; 1615 1616 class TreeItem { 1617 public: 1618 TreeItem(TreeItem *parent, TreeDelegate &delegate, bool might_have_children) 1619 : m_parent(parent), m_delegate(delegate), m_user_data(nullptr), 1620 m_identifier(0), m_row_idx(-1), m_children(), 1621 m_might_have_children(might_have_children), m_is_expanded(false) {} 1622 1623 TreeItem &operator=(const TreeItem &rhs) { 1624 if (this != &rhs) { 1625 m_parent = rhs.m_parent; 1626 m_delegate = rhs.m_delegate; 1627 m_user_data = rhs.m_user_data; 1628 m_identifier = rhs.m_identifier; 1629 m_row_idx = rhs.m_row_idx; 1630 m_children = rhs.m_children; 1631 m_might_have_children = rhs.m_might_have_children; 1632 m_is_expanded = rhs.m_is_expanded; 1633 } 1634 return *this; 1635 } 1636 1637 TreeItem(const TreeItem &) = default; 1638 1639 size_t GetDepth() const { 1640 if (m_parent) 1641 return 1 + m_parent->GetDepth(); 1642 return 0; 1643 } 1644 1645 int GetRowIndex() const { return m_row_idx; } 1646 1647 void ClearChildren() { m_children.clear(); } 1648 1649 void Resize(size_t n, const TreeItem &t) { m_children.resize(n, t); } 1650 1651 TreeItem &operator[](size_t i) { return m_children[i]; } 1652 1653 void SetRowIndex(int row_idx) { m_row_idx = row_idx; } 1654 1655 size_t GetNumChildren() { 1656 m_delegate.TreeDelegateGenerateChildren(*this); 1657 return m_children.size(); 1658 } 1659 1660 void ItemWasSelected() { m_delegate.TreeDelegateItemSelected(*this); } 1661 1662 void CalculateRowIndexes(int &row_idx) { 1663 SetRowIndex(row_idx); 1664 ++row_idx; 1665 1666 const bool expanded = IsExpanded(); 1667 1668 // The root item must calculate its children, or we must calculate the 1669 // number of children if the item is expanded 1670 if (m_parent == nullptr || expanded) 1671 GetNumChildren(); 1672 1673 for (auto &item : m_children) { 1674 if (expanded) 1675 item.CalculateRowIndexes(row_idx); 1676 else 1677 item.SetRowIndex(-1); 1678 } 1679 } 1680 1681 TreeItem *GetParent() { return m_parent; } 1682 1683 bool IsExpanded() const { return m_is_expanded; } 1684 1685 void Expand() { m_is_expanded = true; } 1686 1687 void Unexpand() { m_is_expanded = false; } 1688 1689 bool Draw(Window &window, const int first_visible_row, 1690 const uint32_t selected_row_idx, int &row_idx, int &num_rows_left) { 1691 if (num_rows_left <= 0) 1692 return false; 1693 1694 if (m_row_idx >= first_visible_row) { 1695 window.MoveCursor(2, row_idx + 1); 1696 1697 if (m_parent) 1698 m_parent->DrawTreeForChild(window, this, 0); 1699 1700 if (m_might_have_children) { 1701 // It we can get UTF8 characters to work we should try to use the 1702 // "symbol" UTF8 string below 1703 // const char *symbol = ""; 1704 // if (row.expanded) 1705 // symbol = "\xe2\x96\xbd "; 1706 // else 1707 // symbol = "\xe2\x96\xb7 "; 1708 // window.PutCString (symbol); 1709 1710 // The ACS_DARROW and ACS_RARROW don't look very nice they are just a 1711 // 'v' or '>' character... 1712 // if (expanded) 1713 // window.PutChar (ACS_DARROW); 1714 // else 1715 // window.PutChar (ACS_RARROW); 1716 // Since we can't find any good looking right arrow/down arrow symbols, 1717 // just use a diamond... 1718 window.PutChar(ACS_DIAMOND); 1719 window.PutChar(ACS_HLINE); 1720 } 1721 bool highlight = (selected_row_idx == static_cast<size_t>(m_row_idx)) && 1722 window.IsActive(); 1723 1724 if (highlight) 1725 window.AttributeOn(A_REVERSE); 1726 1727 m_delegate.TreeDelegateDrawTreeItem(*this, window); 1728 1729 if (highlight) 1730 window.AttributeOff(A_REVERSE); 1731 ++row_idx; 1732 --num_rows_left; 1733 } 1734 1735 if (num_rows_left <= 0) 1736 return false; // We are done drawing... 1737 1738 if (IsExpanded()) { 1739 for (auto &item : m_children) { 1740 // If we displayed all the rows and item.Draw() returns false we are 1741 // done drawing and can exit this for loop 1742 if (!item.Draw(window, first_visible_row, selected_row_idx, row_idx, 1743 num_rows_left)) 1744 break; 1745 } 1746 } 1747 return num_rows_left >= 0; // Return true if not done drawing yet 1748 } 1749 1750 void DrawTreeForChild(Window &window, TreeItem *child, 1751 uint32_t reverse_depth) { 1752 if (m_parent) 1753 m_parent->DrawTreeForChild(window, this, reverse_depth + 1); 1754 1755 if (&m_children.back() == child) { 1756 // Last child 1757 if (reverse_depth == 0) { 1758 window.PutChar(ACS_LLCORNER); 1759 window.PutChar(ACS_HLINE); 1760 } else { 1761 window.PutChar(' '); 1762 window.PutChar(' '); 1763 } 1764 } else { 1765 if (reverse_depth == 0) { 1766 window.PutChar(ACS_LTEE); 1767 window.PutChar(ACS_HLINE); 1768 } else { 1769 window.PutChar(ACS_VLINE); 1770 window.PutChar(' '); 1771 } 1772 } 1773 } 1774 1775 TreeItem *GetItemForRowIndex(uint32_t row_idx) { 1776 if (static_cast<uint32_t>(m_row_idx) == row_idx) 1777 return this; 1778 if (m_children.empty()) 1779 return nullptr; 1780 if (IsExpanded()) { 1781 for (auto &item : m_children) { 1782 TreeItem *selected_item_ptr = item.GetItemForRowIndex(row_idx); 1783 if (selected_item_ptr) 1784 return selected_item_ptr; 1785 } 1786 } 1787 return nullptr; 1788 } 1789 1790 void *GetUserData() const { return m_user_data; } 1791 1792 void SetUserData(void *user_data) { m_user_data = user_data; } 1793 1794 uint64_t GetIdentifier() const { return m_identifier; } 1795 1796 void SetIdentifier(uint64_t identifier) { m_identifier = identifier; } 1797 1798 void SetMightHaveChildren(bool b) { m_might_have_children = b; } 1799 1800 protected: 1801 TreeItem *m_parent; 1802 TreeDelegate &m_delegate; 1803 void *m_user_data; 1804 uint64_t m_identifier; 1805 int m_row_idx; // Zero based visible row index, -1 if not visible or for the 1806 // root item 1807 std::vector<TreeItem> m_children; 1808 bool m_might_have_children; 1809 bool m_is_expanded; 1810 }; 1811 1812 class TreeWindowDelegate : public WindowDelegate { 1813 public: 1814 TreeWindowDelegate(Debugger &debugger, const TreeDelegateSP &delegate_sp) 1815 : m_debugger(debugger), m_delegate_sp(delegate_sp), 1816 m_root(nullptr, *delegate_sp, true), m_selected_item(nullptr), 1817 m_num_rows(0), m_selected_row_idx(0), m_first_visible_row(0), 1818 m_min_x(0), m_min_y(0), m_max_x(0), m_max_y(0) {} 1819 1820 int NumVisibleRows() const { return m_max_y - m_min_y; } 1821 1822 bool WindowDelegateDraw(Window &window, bool force) override { 1823 ExecutionContext exe_ctx( 1824 m_debugger.GetCommandInterpreter().GetExecutionContext()); 1825 Process *process = exe_ctx.GetProcessPtr(); 1826 1827 bool display_content = false; 1828 if (process) { 1829 StateType state = process->GetState(); 1830 if (StateIsStoppedState(state, true)) { 1831 // We are stopped, so it is ok to 1832 display_content = true; 1833 } else if (StateIsRunningState(state)) { 1834 return true; // Don't do any updating when we are running 1835 } 1836 } 1837 1838 m_min_x = 2; 1839 m_min_y = 1; 1840 m_max_x = window.GetWidth() - 1; 1841 m_max_y = window.GetHeight() - 1; 1842 1843 window.Erase(); 1844 window.DrawTitleBox(window.GetName()); 1845 1846 if (display_content) { 1847 const int num_visible_rows = NumVisibleRows(); 1848 m_num_rows = 0; 1849 m_root.CalculateRowIndexes(m_num_rows); 1850 1851 // If we unexpanded while having something selected our total number of 1852 // rows is less than the num visible rows, then make sure we show all the 1853 // rows by setting the first visible row accordingly. 1854 if (m_first_visible_row > 0 && m_num_rows < num_visible_rows) 1855 m_first_visible_row = 0; 1856 1857 // Make sure the selected row is always visible 1858 if (m_selected_row_idx < m_first_visible_row) 1859 m_first_visible_row = m_selected_row_idx; 1860 else if (m_first_visible_row + num_visible_rows <= m_selected_row_idx) 1861 m_first_visible_row = m_selected_row_idx - num_visible_rows + 1; 1862 1863 int row_idx = 0; 1864 int num_rows_left = num_visible_rows; 1865 m_root.Draw(window, m_first_visible_row, m_selected_row_idx, row_idx, 1866 num_rows_left); 1867 // Get the selected row 1868 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); 1869 } else { 1870 m_selected_item = nullptr; 1871 } 1872 1873 return true; // Drawing handled 1874 } 1875 1876 const char *WindowDelegateGetHelpText() override { 1877 return "Thread window keyboard shortcuts:"; 1878 } 1879 1880 KeyHelp *WindowDelegateGetKeyHelp() override { 1881 static curses::KeyHelp g_source_view_key_help[] = { 1882 {KEY_UP, "Select previous item"}, 1883 {KEY_DOWN, "Select next item"}, 1884 {KEY_RIGHT, "Expand the selected item"}, 1885 {KEY_LEFT, 1886 "Unexpand the selected item or select parent if not expanded"}, 1887 {KEY_PPAGE, "Page up"}, 1888 {KEY_NPAGE, "Page down"}, 1889 {'h', "Show help dialog"}, 1890 {' ', "Toggle item expansion"}, 1891 {',', "Page up"}, 1892 {'.', "Page down"}, 1893 {'\0', nullptr}}; 1894 return g_source_view_key_help; 1895 } 1896 1897 HandleCharResult WindowDelegateHandleChar(Window &window, int c) override { 1898 switch (c) { 1899 case ',': 1900 case KEY_PPAGE: 1901 // Page up key 1902 if (m_first_visible_row > 0) { 1903 if (m_first_visible_row > m_max_y) 1904 m_first_visible_row -= m_max_y; 1905 else 1906 m_first_visible_row = 0; 1907 m_selected_row_idx = m_first_visible_row; 1908 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); 1909 if (m_selected_item) 1910 m_selected_item->ItemWasSelected(); 1911 } 1912 return eKeyHandled; 1913 1914 case '.': 1915 case KEY_NPAGE: 1916 // Page down key 1917 if (m_num_rows > m_max_y) { 1918 if (m_first_visible_row + m_max_y < m_num_rows) { 1919 m_first_visible_row += m_max_y; 1920 m_selected_row_idx = m_first_visible_row; 1921 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); 1922 if (m_selected_item) 1923 m_selected_item->ItemWasSelected(); 1924 } 1925 } 1926 return eKeyHandled; 1927 1928 case KEY_UP: 1929 if (m_selected_row_idx > 0) { 1930 --m_selected_row_idx; 1931 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); 1932 if (m_selected_item) 1933 m_selected_item->ItemWasSelected(); 1934 } 1935 return eKeyHandled; 1936 1937 case KEY_DOWN: 1938 if (m_selected_row_idx + 1 < m_num_rows) { 1939 ++m_selected_row_idx; 1940 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); 1941 if (m_selected_item) 1942 m_selected_item->ItemWasSelected(); 1943 } 1944 return eKeyHandled; 1945 1946 case KEY_RIGHT: 1947 if (m_selected_item) { 1948 if (!m_selected_item->IsExpanded()) 1949 m_selected_item->Expand(); 1950 } 1951 return eKeyHandled; 1952 1953 case KEY_LEFT: 1954 if (m_selected_item) { 1955 if (m_selected_item->IsExpanded()) 1956 m_selected_item->Unexpand(); 1957 else if (m_selected_item->GetParent()) { 1958 m_selected_row_idx = m_selected_item->GetParent()->GetRowIndex(); 1959 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); 1960 if (m_selected_item) 1961 m_selected_item->ItemWasSelected(); 1962 } 1963 } 1964 return eKeyHandled; 1965 1966 case ' ': 1967 // Toggle expansion state when SPACE is pressed 1968 if (m_selected_item) { 1969 if (m_selected_item->IsExpanded()) 1970 m_selected_item->Unexpand(); 1971 else 1972 m_selected_item->Expand(); 1973 } 1974 return eKeyHandled; 1975 1976 case 'h': 1977 window.CreateHelpSubwindow(); 1978 return eKeyHandled; 1979 1980 default: 1981 break; 1982 } 1983 return eKeyNotHandled; 1984 } 1985 1986 protected: 1987 Debugger &m_debugger; 1988 TreeDelegateSP m_delegate_sp; 1989 TreeItem m_root; 1990 TreeItem *m_selected_item; 1991 int m_num_rows; 1992 int m_selected_row_idx; 1993 int m_first_visible_row; 1994 int m_min_x; 1995 int m_min_y; 1996 int m_max_x; 1997 int m_max_y; 1998 }; 1999 2000 class FrameTreeDelegate : public TreeDelegate { 2001 public: 2002 FrameTreeDelegate() : TreeDelegate() { 2003 FormatEntity::Parse( 2004 "frame #${frame.index}: {${function.name}${function.pc-offset}}}", 2005 m_format); 2006 } 2007 2008 ~FrameTreeDelegate() override = default; 2009 2010 void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override { 2011 Thread *thread = (Thread *)item.GetUserData(); 2012 if (thread) { 2013 const uint64_t frame_idx = item.GetIdentifier(); 2014 StackFrameSP frame_sp = thread->GetStackFrameAtIndex(frame_idx); 2015 if (frame_sp) { 2016 StreamString strm; 2017 const SymbolContext &sc = 2018 frame_sp->GetSymbolContext(eSymbolContextEverything); 2019 ExecutionContext exe_ctx(frame_sp); 2020 if (FormatEntity::Format(m_format, strm, &sc, &exe_ctx, nullptr, 2021 nullptr, false, false)) { 2022 int right_pad = 1; 2023 window.PutCStringTruncated(right_pad, strm.GetString().str().c_str()); 2024 } 2025 } 2026 } 2027 } 2028 2029 void TreeDelegateGenerateChildren(TreeItem &item) override { 2030 // No children for frames yet... 2031 } 2032 2033 bool TreeDelegateItemSelected(TreeItem &item) override { 2034 Thread *thread = (Thread *)item.GetUserData(); 2035 if (thread) { 2036 thread->GetProcess()->GetThreadList().SetSelectedThreadByID( 2037 thread->GetID()); 2038 const uint64_t frame_idx = item.GetIdentifier(); 2039 thread->SetSelectedFrameByIndex(frame_idx); 2040 return true; 2041 } 2042 return false; 2043 } 2044 2045 protected: 2046 FormatEntity::Entry m_format; 2047 }; 2048 2049 class ThreadTreeDelegate : public TreeDelegate { 2050 public: 2051 ThreadTreeDelegate(Debugger &debugger) 2052 : TreeDelegate(), m_debugger(debugger), m_tid(LLDB_INVALID_THREAD_ID), 2053 m_stop_id(UINT32_MAX) { 2054 FormatEntity::Parse("thread #${thread.index}: tid = ${thread.id}{, stop " 2055 "reason = ${thread.stop-reason}}", 2056 m_format); 2057 } 2058 2059 ~ThreadTreeDelegate() override = default; 2060 2061 ProcessSP GetProcess() { 2062 return m_debugger.GetCommandInterpreter() 2063 .GetExecutionContext() 2064 .GetProcessSP(); 2065 } 2066 2067 ThreadSP GetThread(const TreeItem &item) { 2068 ProcessSP process_sp = GetProcess(); 2069 if (process_sp) 2070 return process_sp->GetThreadList().FindThreadByID(item.GetIdentifier()); 2071 return ThreadSP(); 2072 } 2073 2074 void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override { 2075 ThreadSP thread_sp = GetThread(item); 2076 if (thread_sp) { 2077 StreamString strm; 2078 ExecutionContext exe_ctx(thread_sp); 2079 if (FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, nullptr, 2080 nullptr, false, false)) { 2081 int right_pad = 1; 2082 window.PutCStringTruncated(right_pad, strm.GetString().str().c_str()); 2083 } 2084 } 2085 } 2086 2087 void TreeDelegateGenerateChildren(TreeItem &item) override { 2088 ProcessSP process_sp = GetProcess(); 2089 if (process_sp && process_sp->IsAlive()) { 2090 StateType state = process_sp->GetState(); 2091 if (StateIsStoppedState(state, true)) { 2092 ThreadSP thread_sp = GetThread(item); 2093 if (thread_sp) { 2094 if (m_stop_id == process_sp->GetStopID() && 2095 thread_sp->GetID() == m_tid) 2096 return; // Children are already up to date 2097 if (!m_frame_delegate_sp) { 2098 // Always expand the thread item the first time we show it 2099 m_frame_delegate_sp = std::make_shared<FrameTreeDelegate>(); 2100 } 2101 2102 m_stop_id = process_sp->GetStopID(); 2103 m_tid = thread_sp->GetID(); 2104 2105 TreeItem t(&item, *m_frame_delegate_sp, false); 2106 size_t num_frames = thread_sp->GetStackFrameCount(); 2107 item.Resize(num_frames, t); 2108 for (size_t i = 0; i < num_frames; ++i) { 2109 item[i].SetUserData(thread_sp.get()); 2110 item[i].SetIdentifier(i); 2111 } 2112 } 2113 return; 2114 } 2115 } 2116 item.ClearChildren(); 2117 } 2118 2119 bool TreeDelegateItemSelected(TreeItem &item) override { 2120 ProcessSP process_sp = GetProcess(); 2121 if (process_sp && process_sp->IsAlive()) { 2122 StateType state = process_sp->GetState(); 2123 if (StateIsStoppedState(state, true)) { 2124 ThreadSP thread_sp = GetThread(item); 2125 if (thread_sp) { 2126 ThreadList &thread_list = thread_sp->GetProcess()->GetThreadList(); 2127 std::lock_guard<std::recursive_mutex> guard(thread_list.GetMutex()); 2128 ThreadSP selected_thread_sp = thread_list.GetSelectedThread(); 2129 if (selected_thread_sp->GetID() != thread_sp->GetID()) { 2130 thread_list.SetSelectedThreadByID(thread_sp->GetID()); 2131 return true; 2132 } 2133 } 2134 } 2135 } 2136 return false; 2137 } 2138 2139 protected: 2140 Debugger &m_debugger; 2141 std::shared_ptr<FrameTreeDelegate> m_frame_delegate_sp; 2142 lldb::user_id_t m_tid; 2143 uint32_t m_stop_id; 2144 FormatEntity::Entry m_format; 2145 }; 2146 2147 class ThreadsTreeDelegate : public TreeDelegate { 2148 public: 2149 ThreadsTreeDelegate(Debugger &debugger) 2150 : TreeDelegate(), m_thread_delegate_sp(), m_debugger(debugger), 2151 m_stop_id(UINT32_MAX) { 2152 FormatEntity::Parse("process ${process.id}{, name = ${process.name}}", 2153 m_format); 2154 } 2155 2156 ~ThreadsTreeDelegate() override = default; 2157 2158 ProcessSP GetProcess() { 2159 return m_debugger.GetCommandInterpreter() 2160 .GetExecutionContext() 2161 .GetProcessSP(); 2162 } 2163 2164 void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override { 2165 ProcessSP process_sp = GetProcess(); 2166 if (process_sp && process_sp->IsAlive()) { 2167 StreamString strm; 2168 ExecutionContext exe_ctx(process_sp); 2169 if (FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, nullptr, 2170 nullptr, false, false)) { 2171 int right_pad = 1; 2172 window.PutCStringTruncated(right_pad, strm.GetString().str().c_str()); 2173 } 2174 } 2175 } 2176 2177 void TreeDelegateGenerateChildren(TreeItem &item) override { 2178 ProcessSP process_sp = GetProcess(); 2179 if (process_sp && process_sp->IsAlive()) { 2180 StateType state = process_sp->GetState(); 2181 if (StateIsStoppedState(state, true)) { 2182 const uint32_t stop_id = process_sp->GetStopID(); 2183 if (m_stop_id == stop_id) 2184 return; // Children are already up to date 2185 2186 m_stop_id = stop_id; 2187 2188 if (!m_thread_delegate_sp) { 2189 // Always expand the thread item the first time we show it 2190 // item.Expand(); 2191 m_thread_delegate_sp = 2192 std::make_shared<ThreadTreeDelegate>(m_debugger); 2193 } 2194 2195 TreeItem t(&item, *m_thread_delegate_sp, false); 2196 ThreadList &threads = process_sp->GetThreadList(); 2197 std::lock_guard<std::recursive_mutex> guard(threads.GetMutex()); 2198 size_t num_threads = threads.GetSize(); 2199 item.Resize(num_threads, t); 2200 for (size_t i = 0; i < num_threads; ++i) { 2201 item[i].SetIdentifier(threads.GetThreadAtIndex(i)->GetID()); 2202 item[i].SetMightHaveChildren(true); 2203 } 2204 return; 2205 } 2206 } 2207 item.ClearChildren(); 2208 } 2209 2210 bool TreeDelegateItemSelected(TreeItem &item) override { return false; } 2211 2212 protected: 2213 std::shared_ptr<ThreadTreeDelegate> m_thread_delegate_sp; 2214 Debugger &m_debugger; 2215 uint32_t m_stop_id; 2216 FormatEntity::Entry m_format; 2217 }; 2218 2219 class ValueObjectListDelegate : public WindowDelegate { 2220 public: 2221 ValueObjectListDelegate() 2222 : m_rows(), m_selected_row(nullptr), m_selected_row_idx(0), 2223 m_first_visible_row(0), m_num_rows(0), m_max_x(0), m_max_y(0) {} 2224 2225 ValueObjectListDelegate(ValueObjectList &valobj_list) 2226 : m_rows(), m_selected_row(nullptr), m_selected_row_idx(0), 2227 m_first_visible_row(0), m_num_rows(0), m_max_x(0), m_max_y(0) { 2228 SetValues(valobj_list); 2229 } 2230 2231 ~ValueObjectListDelegate() override = default; 2232 2233 void SetValues(ValueObjectList &valobj_list) { 2234 m_selected_row = nullptr; 2235 m_selected_row_idx = 0; 2236 m_first_visible_row = 0; 2237 m_num_rows = 0; 2238 m_rows.clear(); 2239 for (auto &valobj_sp : valobj_list.GetObjects()) 2240 m_rows.push_back(Row(valobj_sp, nullptr)); 2241 } 2242 2243 bool WindowDelegateDraw(Window &window, bool force) override { 2244 m_num_rows = 0; 2245 m_min_x = 2; 2246 m_min_y = 1; 2247 m_max_x = window.GetWidth() - 1; 2248 m_max_y = window.GetHeight() - 1; 2249 2250 window.Erase(); 2251 window.DrawTitleBox(window.GetName()); 2252 2253 const int num_visible_rows = NumVisibleRows(); 2254 const int num_rows = CalculateTotalNumberRows(m_rows); 2255 2256 // If we unexpanded while having something selected our total number of 2257 // rows is less than the num visible rows, then make sure we show all the 2258 // rows by setting the first visible row accordingly. 2259 if (m_first_visible_row > 0 && num_rows < num_visible_rows) 2260 m_first_visible_row = 0; 2261 2262 // Make sure the selected row is always visible 2263 if (m_selected_row_idx < m_first_visible_row) 2264 m_first_visible_row = m_selected_row_idx; 2265 else if (m_first_visible_row + num_visible_rows <= m_selected_row_idx) 2266 m_first_visible_row = m_selected_row_idx - num_visible_rows + 1; 2267 2268 DisplayRows(window, m_rows, g_options); 2269 2270 // Get the selected row 2271 m_selected_row = GetRowForRowIndex(m_selected_row_idx); 2272 // Keep the cursor on the selected row so the highlight and the cursor are 2273 // always on the same line 2274 if (m_selected_row) 2275 window.MoveCursor(m_selected_row->x, m_selected_row->y); 2276 2277 return true; // Drawing handled 2278 } 2279 2280 KeyHelp *WindowDelegateGetKeyHelp() override { 2281 static curses::KeyHelp g_source_view_key_help[] = { 2282 {KEY_UP, "Select previous item"}, 2283 {KEY_DOWN, "Select next item"}, 2284 {KEY_RIGHT, "Expand selected item"}, 2285 {KEY_LEFT, "Unexpand selected item or select parent if not expanded"}, 2286 {KEY_PPAGE, "Page up"}, 2287 {KEY_NPAGE, "Page down"}, 2288 {'A', "Format as annotated address"}, 2289 {'b', "Format as binary"}, 2290 {'B', "Format as hex bytes with ASCII"}, 2291 {'c', "Format as character"}, 2292 {'d', "Format as a signed integer"}, 2293 {'D', "Format selected value using the default format for the type"}, 2294 {'f', "Format as float"}, 2295 {'h', "Show help dialog"}, 2296 {'i', "Format as instructions"}, 2297 {'o', "Format as octal"}, 2298 {'p', "Format as pointer"}, 2299 {'s', "Format as C string"}, 2300 {'t', "Toggle showing/hiding type names"}, 2301 {'u', "Format as an unsigned integer"}, 2302 {'x', "Format as hex"}, 2303 {'X', "Format as uppercase hex"}, 2304 {' ', "Toggle item expansion"}, 2305 {',', "Page up"}, 2306 {'.', "Page down"}, 2307 {'\0', nullptr}}; 2308 return g_source_view_key_help; 2309 } 2310 2311 HandleCharResult WindowDelegateHandleChar(Window &window, int c) override { 2312 switch (c) { 2313 case 'x': 2314 case 'X': 2315 case 'o': 2316 case 's': 2317 case 'u': 2318 case 'd': 2319 case 'D': 2320 case 'i': 2321 case 'A': 2322 case 'p': 2323 case 'c': 2324 case 'b': 2325 case 'B': 2326 case 'f': 2327 // Change the format for the currently selected item 2328 if (m_selected_row) { 2329 auto valobj_sp = m_selected_row->value.GetSP(); 2330 if (valobj_sp) 2331 valobj_sp->SetFormat(FormatForChar(c)); 2332 } 2333 return eKeyHandled; 2334 2335 case 't': 2336 // Toggle showing type names 2337 g_options.show_types = !g_options.show_types; 2338 return eKeyHandled; 2339 2340 case ',': 2341 case KEY_PPAGE: 2342 // Page up key 2343 if (m_first_visible_row > 0) { 2344 if (static_cast<int>(m_first_visible_row) > m_max_y) 2345 m_first_visible_row -= m_max_y; 2346 else 2347 m_first_visible_row = 0; 2348 m_selected_row_idx = m_first_visible_row; 2349 } 2350 return eKeyHandled; 2351 2352 case '.': 2353 case KEY_NPAGE: 2354 // Page down key 2355 if (m_num_rows > static_cast<size_t>(m_max_y)) { 2356 if (m_first_visible_row + m_max_y < m_num_rows) { 2357 m_first_visible_row += m_max_y; 2358 m_selected_row_idx = m_first_visible_row; 2359 } 2360 } 2361 return eKeyHandled; 2362 2363 case KEY_UP: 2364 if (m_selected_row_idx > 0) 2365 --m_selected_row_idx; 2366 return eKeyHandled; 2367 2368 case KEY_DOWN: 2369 if (m_selected_row_idx + 1 < m_num_rows) 2370 ++m_selected_row_idx; 2371 return eKeyHandled; 2372 2373 case KEY_RIGHT: 2374 if (m_selected_row) { 2375 if (!m_selected_row->expanded) 2376 m_selected_row->Expand(); 2377 } 2378 return eKeyHandled; 2379 2380 case KEY_LEFT: 2381 if (m_selected_row) { 2382 if (m_selected_row->expanded) 2383 m_selected_row->Unexpand(); 2384 else if (m_selected_row->parent) 2385 m_selected_row_idx = m_selected_row->parent->row_idx; 2386 } 2387 return eKeyHandled; 2388 2389 case ' ': 2390 // Toggle expansion state when SPACE is pressed 2391 if (m_selected_row) { 2392 if (m_selected_row->expanded) 2393 m_selected_row->Unexpand(); 2394 else 2395 m_selected_row->Expand(); 2396 } 2397 return eKeyHandled; 2398 2399 case 'h': 2400 window.CreateHelpSubwindow(); 2401 return eKeyHandled; 2402 2403 default: 2404 break; 2405 } 2406 return eKeyNotHandled; 2407 } 2408 2409 protected: 2410 std::vector<Row> m_rows; 2411 Row *m_selected_row; 2412 uint32_t m_selected_row_idx; 2413 uint32_t m_first_visible_row; 2414 uint32_t m_num_rows; 2415 int m_min_x; 2416 int m_min_y; 2417 int m_max_x; 2418 int m_max_y; 2419 2420 static Format FormatForChar(int c) { 2421 switch (c) { 2422 case 'x': 2423 return eFormatHex; 2424 case 'X': 2425 return eFormatHexUppercase; 2426 case 'o': 2427 return eFormatOctal; 2428 case 's': 2429 return eFormatCString; 2430 case 'u': 2431 return eFormatUnsigned; 2432 case 'd': 2433 return eFormatDecimal; 2434 case 'D': 2435 return eFormatDefault; 2436 case 'i': 2437 return eFormatInstruction; 2438 case 'A': 2439 return eFormatAddressInfo; 2440 case 'p': 2441 return eFormatPointer; 2442 case 'c': 2443 return eFormatChar; 2444 case 'b': 2445 return eFormatBinary; 2446 case 'B': 2447 return eFormatBytesWithASCII; 2448 case 'f': 2449 return eFormatFloat; 2450 } 2451 return eFormatDefault; 2452 } 2453 2454 bool DisplayRowObject(Window &window, Row &row, DisplayOptions &options, 2455 bool highlight, bool last_child) { 2456 ValueObject *valobj = row.value.GetSP().get(); 2457 2458 if (valobj == nullptr) 2459 return false; 2460 2461 const char *type_name = 2462 options.show_types ? valobj->GetTypeName().GetCString() : nullptr; 2463 const char *name = valobj->GetName().GetCString(); 2464 const char *value = valobj->GetValueAsCString(); 2465 const char *summary = valobj->GetSummaryAsCString(); 2466 2467 window.MoveCursor(row.x, row.y); 2468 2469 row.DrawTree(window); 2470 2471 if (highlight) 2472 window.AttributeOn(A_REVERSE); 2473 2474 if (type_name && type_name[0]) 2475 window.PrintfTruncated(1, "(%s) ", type_name); 2476 2477 if (name && name[0]) 2478 window.PutCStringTruncated(1, name); 2479 2480 attr_t changd_attr = 0; 2481 if (valobj->GetValueDidChange()) 2482 changd_attr = COLOR_PAIR(RedOnBlack) | A_BOLD; 2483 2484 if (value && value[0]) { 2485 window.PutCStringTruncated(1, " = "); 2486 if (changd_attr) 2487 window.AttributeOn(changd_attr); 2488 window.PutCStringTruncated(1, value); 2489 if (changd_attr) 2490 window.AttributeOff(changd_attr); 2491 } 2492 2493 if (summary && summary[0]) { 2494 window.PutCStringTruncated(1, " "); 2495 if (changd_attr) 2496 window.AttributeOn(changd_attr); 2497 window.PutCStringTruncated(1, summary); 2498 if (changd_attr) 2499 window.AttributeOff(changd_attr); 2500 } 2501 2502 if (highlight) 2503 window.AttributeOff(A_REVERSE); 2504 2505 return true; 2506 } 2507 2508 void DisplayRows(Window &window, std::vector<Row> &rows, 2509 DisplayOptions &options) { 2510 // > 0x25B7 2511 // \/ 0x25BD 2512 2513 bool window_is_active = window.IsActive(); 2514 for (auto &row : rows) { 2515 const bool last_child = row.parent && &rows[rows.size() - 1] == &row; 2516 // Save the row index in each Row structure 2517 row.row_idx = m_num_rows; 2518 if ((m_num_rows >= m_first_visible_row) && 2519 ((m_num_rows - m_first_visible_row) < 2520 static_cast<size_t>(NumVisibleRows()))) { 2521 row.x = m_min_x; 2522 row.y = m_num_rows - m_first_visible_row + 1; 2523 if (DisplayRowObject(window, row, options, 2524 window_is_active && 2525 m_num_rows == m_selected_row_idx, 2526 last_child)) { 2527 ++m_num_rows; 2528 } else { 2529 row.x = 0; 2530 row.y = 0; 2531 } 2532 } else { 2533 row.x = 0; 2534 row.y = 0; 2535 ++m_num_rows; 2536 } 2537 2538 auto &children = row.GetChildren(); 2539 if (row.expanded && !children.empty()) { 2540 DisplayRows(window, children, options); 2541 } 2542 } 2543 } 2544 2545 int CalculateTotalNumberRows(std::vector<Row> &rows) { 2546 int row_count = 0; 2547 for (auto &row : rows) { 2548 ++row_count; 2549 if (row.expanded) 2550 row_count += CalculateTotalNumberRows(row.GetChildren()); 2551 } 2552 return row_count; 2553 } 2554 2555 static Row *GetRowForRowIndexImpl(std::vector<Row> &rows, size_t &row_index) { 2556 for (auto &row : rows) { 2557 if (row_index == 0) 2558 return &row; 2559 else { 2560 --row_index; 2561 auto &children = row.GetChildren(); 2562 if (row.expanded && !children.empty()) { 2563 Row *result = GetRowForRowIndexImpl(children, row_index); 2564 if (result) 2565 return result; 2566 } 2567 } 2568 } 2569 return nullptr; 2570 } 2571 2572 Row *GetRowForRowIndex(size_t row_index) { 2573 return GetRowForRowIndexImpl(m_rows, row_index); 2574 } 2575 2576 int NumVisibleRows() const { return m_max_y - m_min_y; } 2577 2578 static DisplayOptions g_options; 2579 }; 2580 2581 class FrameVariablesWindowDelegate : public ValueObjectListDelegate { 2582 public: 2583 FrameVariablesWindowDelegate(Debugger &debugger) 2584 : ValueObjectListDelegate(), m_debugger(debugger), 2585 m_frame_block(nullptr) {} 2586 2587 ~FrameVariablesWindowDelegate() override = default; 2588 2589 const char *WindowDelegateGetHelpText() override { 2590 return "Frame variable window keyboard shortcuts:"; 2591 } 2592 2593 bool WindowDelegateDraw(Window &window, bool force) override { 2594 ExecutionContext exe_ctx( 2595 m_debugger.GetCommandInterpreter().GetExecutionContext()); 2596 Process *process = exe_ctx.GetProcessPtr(); 2597 Block *frame_block = nullptr; 2598 StackFrame *frame = nullptr; 2599 2600 if (process) { 2601 StateType state = process->GetState(); 2602 if (StateIsStoppedState(state, true)) { 2603 frame = exe_ctx.GetFramePtr(); 2604 if (frame) 2605 frame_block = frame->GetFrameBlock(); 2606 } else if (StateIsRunningState(state)) { 2607 return true; // Don't do any updating when we are running 2608 } 2609 } 2610 2611 ValueObjectList local_values; 2612 if (frame_block) { 2613 // Only update the variables if they have changed 2614 if (m_frame_block != frame_block) { 2615 m_frame_block = frame_block; 2616 2617 VariableList *locals = frame->GetVariableList(true); 2618 if (locals) { 2619 const DynamicValueType use_dynamic = eDynamicDontRunTarget; 2620 for (const VariableSP &local_sp : *locals) { 2621 ValueObjectSP value_sp = 2622 frame->GetValueObjectForFrameVariable(local_sp, use_dynamic); 2623 if (value_sp) { 2624 ValueObjectSP synthetic_value_sp = value_sp->GetSyntheticValue(); 2625 if (synthetic_value_sp) 2626 local_values.Append(synthetic_value_sp); 2627 else 2628 local_values.Append(value_sp); 2629 } 2630 } 2631 // Update the values 2632 SetValues(local_values); 2633 } 2634 } 2635 } else { 2636 m_frame_block = nullptr; 2637 // Update the values with an empty list if there is no frame 2638 SetValues(local_values); 2639 } 2640 2641 return ValueObjectListDelegate::WindowDelegateDraw(window, force); 2642 } 2643 2644 protected: 2645 Debugger &m_debugger; 2646 Block *m_frame_block; 2647 }; 2648 2649 class RegistersWindowDelegate : public ValueObjectListDelegate { 2650 public: 2651 RegistersWindowDelegate(Debugger &debugger) 2652 : ValueObjectListDelegate(), m_debugger(debugger) {} 2653 2654 ~RegistersWindowDelegate() override = default; 2655 2656 const char *WindowDelegateGetHelpText() override { 2657 return "Register window keyboard shortcuts:"; 2658 } 2659 2660 bool WindowDelegateDraw(Window &window, bool force) override { 2661 ExecutionContext exe_ctx( 2662 m_debugger.GetCommandInterpreter().GetExecutionContext()); 2663 StackFrame *frame = exe_ctx.GetFramePtr(); 2664 2665 ValueObjectList value_list; 2666 if (frame) { 2667 if (frame->GetStackID() != m_stack_id) { 2668 m_stack_id = frame->GetStackID(); 2669 RegisterContextSP reg_ctx(frame->GetRegisterContext()); 2670 if (reg_ctx) { 2671 const uint32_t num_sets = reg_ctx->GetRegisterSetCount(); 2672 for (uint32_t set_idx = 0; set_idx < num_sets; ++set_idx) { 2673 value_list.Append( 2674 ValueObjectRegisterSet::Create(frame, reg_ctx, set_idx)); 2675 } 2676 } 2677 SetValues(value_list); 2678 } 2679 } else { 2680 Process *process = exe_ctx.GetProcessPtr(); 2681 if (process && process->IsAlive()) 2682 return true; // Don't do any updating if we are running 2683 else { 2684 // Update the values with an empty list if there is no process or the 2685 // process isn't alive anymore 2686 SetValues(value_list); 2687 } 2688 } 2689 return ValueObjectListDelegate::WindowDelegateDraw(window, force); 2690 } 2691 2692 protected: 2693 Debugger &m_debugger; 2694 StackID m_stack_id; 2695 }; 2696 2697 static const char *CursesKeyToCString(int ch) { 2698 static char g_desc[32]; 2699 if (ch >= KEY_F0 && ch < KEY_F0 + 64) { 2700 snprintf(g_desc, sizeof(g_desc), "F%u", ch - KEY_F0); 2701 return g_desc; 2702 } 2703 switch (ch) { 2704 case KEY_DOWN: 2705 return "down"; 2706 case KEY_UP: 2707 return "up"; 2708 case KEY_LEFT: 2709 return "left"; 2710 case KEY_RIGHT: 2711 return "right"; 2712 case KEY_HOME: 2713 return "home"; 2714 case KEY_BACKSPACE: 2715 return "backspace"; 2716 case KEY_DL: 2717 return "delete-line"; 2718 case KEY_IL: 2719 return "insert-line"; 2720 case KEY_DC: 2721 return "delete-char"; 2722 case KEY_IC: 2723 return "insert-char"; 2724 case KEY_CLEAR: 2725 return "clear"; 2726 case KEY_EOS: 2727 return "clear-to-eos"; 2728 case KEY_EOL: 2729 return "clear-to-eol"; 2730 case KEY_SF: 2731 return "scroll-forward"; 2732 case KEY_SR: 2733 return "scroll-backward"; 2734 case KEY_NPAGE: 2735 return "page-down"; 2736 case KEY_PPAGE: 2737 return "page-up"; 2738 case KEY_STAB: 2739 return "set-tab"; 2740 case KEY_CTAB: 2741 return "clear-tab"; 2742 case KEY_CATAB: 2743 return "clear-all-tabs"; 2744 case KEY_ENTER: 2745 return "enter"; 2746 case KEY_PRINT: 2747 return "print"; 2748 case KEY_LL: 2749 return "lower-left key"; 2750 case KEY_A1: 2751 return "upper left of keypad"; 2752 case KEY_A3: 2753 return "upper right of keypad"; 2754 case KEY_B2: 2755 return "center of keypad"; 2756 case KEY_C1: 2757 return "lower left of keypad"; 2758 case KEY_C3: 2759 return "lower right of keypad"; 2760 case KEY_BTAB: 2761 return "back-tab key"; 2762 case KEY_BEG: 2763 return "begin key"; 2764 case KEY_CANCEL: 2765 return "cancel key"; 2766 case KEY_CLOSE: 2767 return "close key"; 2768 case KEY_COMMAND: 2769 return "command key"; 2770 case KEY_COPY: 2771 return "copy key"; 2772 case KEY_CREATE: 2773 return "create key"; 2774 case KEY_END: 2775 return "end key"; 2776 case KEY_EXIT: 2777 return "exit key"; 2778 case KEY_FIND: 2779 return "find key"; 2780 case KEY_HELP: 2781 return "help key"; 2782 case KEY_MARK: 2783 return "mark key"; 2784 case KEY_MESSAGE: 2785 return "message key"; 2786 case KEY_MOVE: 2787 return "move key"; 2788 case KEY_NEXT: 2789 return "next key"; 2790 case KEY_OPEN: 2791 return "open key"; 2792 case KEY_OPTIONS: 2793 return "options key"; 2794 case KEY_PREVIOUS: 2795 return "previous key"; 2796 case KEY_REDO: 2797 return "redo key"; 2798 case KEY_REFERENCE: 2799 return "reference key"; 2800 case KEY_REFRESH: 2801 return "refresh key"; 2802 case KEY_REPLACE: 2803 return "replace key"; 2804 case KEY_RESTART: 2805 return "restart key"; 2806 case KEY_RESUME: 2807 return "resume key"; 2808 case KEY_SAVE: 2809 return "save key"; 2810 case KEY_SBEG: 2811 return "shifted begin key"; 2812 case KEY_SCANCEL: 2813 return "shifted cancel key"; 2814 case KEY_SCOMMAND: 2815 return "shifted command key"; 2816 case KEY_SCOPY: 2817 return "shifted copy key"; 2818 case KEY_SCREATE: 2819 return "shifted create key"; 2820 case KEY_SDC: 2821 return "shifted delete-character key"; 2822 case KEY_SDL: 2823 return "shifted delete-line key"; 2824 case KEY_SELECT: 2825 return "select key"; 2826 case KEY_SEND: 2827 return "shifted end key"; 2828 case KEY_SEOL: 2829 return "shifted clear-to-end-of-line key"; 2830 case KEY_SEXIT: 2831 return "shifted exit key"; 2832 case KEY_SFIND: 2833 return "shifted find key"; 2834 case KEY_SHELP: 2835 return "shifted help key"; 2836 case KEY_SHOME: 2837 return "shifted home key"; 2838 case KEY_SIC: 2839 return "shifted insert-character key"; 2840 case KEY_SLEFT: 2841 return "shifted left-arrow key"; 2842 case KEY_SMESSAGE: 2843 return "shifted message key"; 2844 case KEY_SMOVE: 2845 return "shifted move key"; 2846 case KEY_SNEXT: 2847 return "shifted next key"; 2848 case KEY_SOPTIONS: 2849 return "shifted options key"; 2850 case KEY_SPREVIOUS: 2851 return "shifted previous key"; 2852 case KEY_SPRINT: 2853 return "shifted print key"; 2854 case KEY_SREDO: 2855 return "shifted redo key"; 2856 case KEY_SREPLACE: 2857 return "shifted replace key"; 2858 case KEY_SRIGHT: 2859 return "shifted right-arrow key"; 2860 case KEY_SRSUME: 2861 return "shifted resume key"; 2862 case KEY_SSAVE: 2863 return "shifted save key"; 2864 case KEY_SSUSPEND: 2865 return "shifted suspend key"; 2866 case KEY_SUNDO: 2867 return "shifted undo key"; 2868 case KEY_SUSPEND: 2869 return "suspend key"; 2870 case KEY_UNDO: 2871 return "undo key"; 2872 case KEY_MOUSE: 2873 return "Mouse event has occurred"; 2874 case KEY_RESIZE: 2875 return "Terminal resize event"; 2876 #ifdef KEY_EVENT 2877 case KEY_EVENT: 2878 return "We were interrupted by an event"; 2879 #endif 2880 case KEY_RETURN: 2881 return "return"; 2882 case ' ': 2883 return "space"; 2884 case '\t': 2885 return "tab"; 2886 case KEY_ESCAPE: 2887 return "escape"; 2888 default: 2889 if (llvm::isPrint(ch)) 2890 snprintf(g_desc, sizeof(g_desc), "%c", ch); 2891 else 2892 snprintf(g_desc, sizeof(g_desc), "\\x%2.2x", ch); 2893 return g_desc; 2894 } 2895 return nullptr; 2896 } 2897 2898 HelpDialogDelegate::HelpDialogDelegate(const char *text, 2899 KeyHelp *key_help_array) 2900 : m_text(), m_first_visible_line(0) { 2901 if (text && text[0]) { 2902 m_text.SplitIntoLines(text); 2903 m_text.AppendString(""); 2904 } 2905 if (key_help_array) { 2906 for (KeyHelp *key = key_help_array; key->ch; ++key) { 2907 StreamString key_description; 2908 key_description.Printf("%10s - %s", CursesKeyToCString(key->ch), 2909 key->description); 2910 m_text.AppendString(key_description.GetString()); 2911 } 2912 } 2913 } 2914 2915 HelpDialogDelegate::~HelpDialogDelegate() = default; 2916 2917 bool HelpDialogDelegate::WindowDelegateDraw(Window &window, bool force) { 2918 window.Erase(); 2919 const int window_height = window.GetHeight(); 2920 int x = 2; 2921 int y = 1; 2922 const int min_y = y; 2923 const int max_y = window_height - 1 - y; 2924 const size_t num_visible_lines = max_y - min_y + 1; 2925 const size_t num_lines = m_text.GetSize(); 2926 const char *bottom_message; 2927 if (num_lines <= num_visible_lines) 2928 bottom_message = "Press any key to exit"; 2929 else 2930 bottom_message = "Use arrows to scroll, any other key to exit"; 2931 window.DrawTitleBox(window.GetName(), bottom_message); 2932 while (y <= max_y) { 2933 window.MoveCursor(x, y); 2934 window.PutCStringTruncated( 2935 1, m_text.GetStringAtIndex(m_first_visible_line + y - min_y)); 2936 ++y; 2937 } 2938 return true; 2939 } 2940 2941 HandleCharResult HelpDialogDelegate::WindowDelegateHandleChar(Window &window, 2942 int key) { 2943 bool done = false; 2944 const size_t num_lines = m_text.GetSize(); 2945 const size_t num_visible_lines = window.GetHeight() - 2; 2946 2947 if (num_lines <= num_visible_lines) { 2948 done = true; 2949 // If we have all lines visible and don't need scrolling, then any key 2950 // press will cause us to exit 2951 } else { 2952 switch (key) { 2953 case KEY_UP: 2954 if (m_first_visible_line > 0) 2955 --m_first_visible_line; 2956 break; 2957 2958 case KEY_DOWN: 2959 if (m_first_visible_line + num_visible_lines < num_lines) 2960 ++m_first_visible_line; 2961 break; 2962 2963 case KEY_PPAGE: 2964 case ',': 2965 if (m_first_visible_line > 0) { 2966 if (static_cast<size_t>(m_first_visible_line) >= num_visible_lines) 2967 m_first_visible_line -= num_visible_lines; 2968 else 2969 m_first_visible_line = 0; 2970 } 2971 break; 2972 2973 case KEY_NPAGE: 2974 case '.': 2975 if (m_first_visible_line + num_visible_lines < num_lines) { 2976 m_first_visible_line += num_visible_lines; 2977 if (static_cast<size_t>(m_first_visible_line) > num_lines) 2978 m_first_visible_line = num_lines - num_visible_lines; 2979 } 2980 break; 2981 2982 default: 2983 done = true; 2984 break; 2985 } 2986 } 2987 if (done) 2988 window.GetParent()->RemoveSubWindow(&window); 2989 return eKeyHandled; 2990 } 2991 2992 class ApplicationDelegate : public WindowDelegate, public MenuDelegate { 2993 public: 2994 enum { 2995 eMenuID_LLDB = 1, 2996 eMenuID_LLDBAbout, 2997 eMenuID_LLDBExit, 2998 2999 eMenuID_Target, 3000 eMenuID_TargetCreate, 3001 eMenuID_TargetDelete, 3002 3003 eMenuID_Process, 3004 eMenuID_ProcessAttach, 3005 eMenuID_ProcessDetachResume, 3006 eMenuID_ProcessDetachSuspended, 3007 eMenuID_ProcessLaunch, 3008 eMenuID_ProcessContinue, 3009 eMenuID_ProcessHalt, 3010 eMenuID_ProcessKill, 3011 3012 eMenuID_Thread, 3013 eMenuID_ThreadStepIn, 3014 eMenuID_ThreadStepOver, 3015 eMenuID_ThreadStepOut, 3016 3017 eMenuID_View, 3018 eMenuID_ViewBacktrace, 3019 eMenuID_ViewRegisters, 3020 eMenuID_ViewSource, 3021 eMenuID_ViewVariables, 3022 3023 eMenuID_Help, 3024 eMenuID_HelpGUIHelp 3025 }; 3026 3027 ApplicationDelegate(Application &app, Debugger &debugger) 3028 : WindowDelegate(), MenuDelegate(), m_app(app), m_debugger(debugger) {} 3029 3030 ~ApplicationDelegate() override = default; 3031 3032 bool WindowDelegateDraw(Window &window, bool force) override { 3033 return false; // Drawing not handled, let standard window drawing happen 3034 } 3035 3036 HandleCharResult WindowDelegateHandleChar(Window &window, int key) override { 3037 switch (key) { 3038 case '\t': 3039 window.SelectNextWindowAsActive(); 3040 return eKeyHandled; 3041 3042 case KEY_BTAB: 3043 window.SelectPreviousWindowAsActive(); 3044 return eKeyHandled; 3045 3046 case 'h': 3047 window.CreateHelpSubwindow(); 3048 return eKeyHandled; 3049 3050 case KEY_ESCAPE: 3051 return eQuitApplication; 3052 3053 default: 3054 break; 3055 } 3056 return eKeyNotHandled; 3057 } 3058 3059 const char *WindowDelegateGetHelpText() override { 3060 return "Welcome to the LLDB curses GUI.\n\n" 3061 "Press the TAB key to change the selected view.\n" 3062 "Each view has its own keyboard shortcuts, press 'h' to open a " 3063 "dialog to display them.\n\n" 3064 "Common key bindings for all views:"; 3065 } 3066 3067 KeyHelp *WindowDelegateGetKeyHelp() override { 3068 static curses::KeyHelp g_source_view_key_help[] = { 3069 {'\t', "Select next view"}, 3070 {KEY_BTAB, "Select previous view"}, 3071 {'h', "Show help dialog with view specific key bindings"}, 3072 {',', "Page up"}, 3073 {'.', "Page down"}, 3074 {KEY_UP, "Select previous"}, 3075 {KEY_DOWN, "Select next"}, 3076 {KEY_LEFT, "Unexpand or select parent"}, 3077 {KEY_RIGHT, "Expand"}, 3078 {KEY_PPAGE, "Page up"}, 3079 {KEY_NPAGE, "Page down"}, 3080 {'\0', nullptr}}; 3081 return g_source_view_key_help; 3082 } 3083 3084 MenuActionResult MenuDelegateAction(Menu &menu) override { 3085 switch (menu.GetIdentifier()) { 3086 case eMenuID_ThreadStepIn: { 3087 ExecutionContext exe_ctx = 3088 m_debugger.GetCommandInterpreter().GetExecutionContext(); 3089 if (exe_ctx.HasThreadScope()) { 3090 Process *process = exe_ctx.GetProcessPtr(); 3091 if (process && process->IsAlive() && 3092 StateIsStoppedState(process->GetState(), true)) 3093 exe_ctx.GetThreadRef().StepIn(true); 3094 } 3095 } 3096 return MenuActionResult::Handled; 3097 3098 case eMenuID_ThreadStepOut: { 3099 ExecutionContext exe_ctx = 3100 m_debugger.GetCommandInterpreter().GetExecutionContext(); 3101 if (exe_ctx.HasThreadScope()) { 3102 Process *process = exe_ctx.GetProcessPtr(); 3103 if (process && process->IsAlive() && 3104 StateIsStoppedState(process->GetState(), true)) 3105 exe_ctx.GetThreadRef().StepOut(); 3106 } 3107 } 3108 return MenuActionResult::Handled; 3109 3110 case eMenuID_ThreadStepOver: { 3111 ExecutionContext exe_ctx = 3112 m_debugger.GetCommandInterpreter().GetExecutionContext(); 3113 if (exe_ctx.HasThreadScope()) { 3114 Process *process = exe_ctx.GetProcessPtr(); 3115 if (process && process->IsAlive() && 3116 StateIsStoppedState(process->GetState(), true)) 3117 exe_ctx.GetThreadRef().StepOver(true); 3118 } 3119 } 3120 return MenuActionResult::Handled; 3121 3122 case eMenuID_ProcessContinue: { 3123 ExecutionContext exe_ctx = 3124 m_debugger.GetCommandInterpreter().GetExecutionContext(); 3125 if (exe_ctx.HasProcessScope()) { 3126 Process *process = exe_ctx.GetProcessPtr(); 3127 if (process && process->IsAlive() && 3128 StateIsStoppedState(process->GetState(), true)) 3129 process->Resume(); 3130 } 3131 } 3132 return MenuActionResult::Handled; 3133 3134 case eMenuID_ProcessKill: { 3135 ExecutionContext exe_ctx = 3136 m_debugger.GetCommandInterpreter().GetExecutionContext(); 3137 if (exe_ctx.HasProcessScope()) { 3138 Process *process = exe_ctx.GetProcessPtr(); 3139 if (process && process->IsAlive()) 3140 process->Destroy(false); 3141 } 3142 } 3143 return MenuActionResult::Handled; 3144 3145 case eMenuID_ProcessHalt: { 3146 ExecutionContext exe_ctx = 3147 m_debugger.GetCommandInterpreter().GetExecutionContext(); 3148 if (exe_ctx.HasProcessScope()) { 3149 Process *process = exe_ctx.GetProcessPtr(); 3150 if (process && process->IsAlive()) 3151 process->Halt(); 3152 } 3153 } 3154 return MenuActionResult::Handled; 3155 3156 case eMenuID_ProcessDetachResume: 3157 case eMenuID_ProcessDetachSuspended: { 3158 ExecutionContext exe_ctx = 3159 m_debugger.GetCommandInterpreter().GetExecutionContext(); 3160 if (exe_ctx.HasProcessScope()) { 3161 Process *process = exe_ctx.GetProcessPtr(); 3162 if (process && process->IsAlive()) 3163 process->Detach(menu.GetIdentifier() == 3164 eMenuID_ProcessDetachSuspended); 3165 } 3166 } 3167 return MenuActionResult::Handled; 3168 3169 case eMenuID_Process: { 3170 // Populate the menu with all of the threads if the process is stopped 3171 // when the Process menu gets selected and is about to display its 3172 // submenu. 3173 Menus &submenus = menu.GetSubmenus(); 3174 ExecutionContext exe_ctx = 3175 m_debugger.GetCommandInterpreter().GetExecutionContext(); 3176 Process *process = exe_ctx.GetProcessPtr(); 3177 if (process && process->IsAlive() && 3178 StateIsStoppedState(process->GetState(), true)) { 3179 if (submenus.size() == 7) 3180 menu.AddSubmenu(MenuSP(new Menu(Menu::Type::Separator))); 3181 else if (submenus.size() > 8) 3182 submenus.erase(submenus.begin() + 8, submenus.end()); 3183 3184 ThreadList &threads = process->GetThreadList(); 3185 std::lock_guard<std::recursive_mutex> guard(threads.GetMutex()); 3186 size_t num_threads = threads.GetSize(); 3187 for (size_t i = 0; i < num_threads; ++i) { 3188 ThreadSP thread_sp = threads.GetThreadAtIndex(i); 3189 char menu_char = '\0'; 3190 if (i < 9) 3191 menu_char = '1' + i; 3192 StreamString thread_menu_title; 3193 thread_menu_title.Printf("Thread %u", thread_sp->GetIndexID()); 3194 const char *thread_name = thread_sp->GetName(); 3195 if (thread_name && thread_name[0]) 3196 thread_menu_title.Printf(" %s", thread_name); 3197 else { 3198 const char *queue_name = thread_sp->GetQueueName(); 3199 if (queue_name && queue_name[0]) 3200 thread_menu_title.Printf(" %s", queue_name); 3201 } 3202 menu.AddSubmenu( 3203 MenuSP(new Menu(thread_menu_title.GetString().str().c_str(), 3204 nullptr, menu_char, thread_sp->GetID()))); 3205 } 3206 } else if (submenus.size() > 7) { 3207 // Remove the separator and any other thread submenu items that were 3208 // previously added 3209 submenus.erase(submenus.begin() + 7, submenus.end()); 3210 } 3211 // Since we are adding and removing items we need to recalculate the name 3212 // lengths 3213 menu.RecalculateNameLengths(); 3214 } 3215 return MenuActionResult::Handled; 3216 3217 case eMenuID_ViewVariables: { 3218 WindowSP main_window_sp = m_app.GetMainWindow(); 3219 WindowSP source_window_sp = main_window_sp->FindSubWindow("Source"); 3220 WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables"); 3221 WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers"); 3222 const Rect source_bounds = source_window_sp->GetBounds(); 3223 3224 if (variables_window_sp) { 3225 const Rect variables_bounds = variables_window_sp->GetBounds(); 3226 3227 main_window_sp->RemoveSubWindow(variables_window_sp.get()); 3228 3229 if (registers_window_sp) { 3230 // We have a registers window, so give all the area back to the 3231 // registers window 3232 Rect registers_bounds = variables_bounds; 3233 registers_bounds.size.width = source_bounds.size.width; 3234 registers_window_sp->SetBounds(registers_bounds); 3235 } else { 3236 // We have no registers window showing so give the bottom area back 3237 // to the source view 3238 source_window_sp->Resize(source_bounds.size.width, 3239 source_bounds.size.height + 3240 variables_bounds.size.height); 3241 } 3242 } else { 3243 Rect new_variables_rect; 3244 if (registers_window_sp) { 3245 // We have a registers window so split the area of the registers 3246 // window into two columns where the left hand side will be the 3247 // variables and the right hand side will be the registers 3248 const Rect variables_bounds = registers_window_sp->GetBounds(); 3249 Rect new_registers_rect; 3250 variables_bounds.VerticalSplitPercentage(0.50, new_variables_rect, 3251 new_registers_rect); 3252 registers_window_sp->SetBounds(new_registers_rect); 3253 } else { 3254 // No registers window, grab the bottom part of the source window 3255 Rect new_source_rect; 3256 source_bounds.HorizontalSplitPercentage(0.70, new_source_rect, 3257 new_variables_rect); 3258 source_window_sp->SetBounds(new_source_rect); 3259 } 3260 WindowSP new_window_sp = main_window_sp->CreateSubWindow( 3261 "Variables", new_variables_rect, false); 3262 new_window_sp->SetDelegate( 3263 WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger))); 3264 } 3265 touchwin(stdscr); 3266 } 3267 return MenuActionResult::Handled; 3268 3269 case eMenuID_ViewRegisters: { 3270 WindowSP main_window_sp = m_app.GetMainWindow(); 3271 WindowSP source_window_sp = main_window_sp->FindSubWindow("Source"); 3272 WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables"); 3273 WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers"); 3274 const Rect source_bounds = source_window_sp->GetBounds(); 3275 3276 if (registers_window_sp) { 3277 if (variables_window_sp) { 3278 const Rect variables_bounds = variables_window_sp->GetBounds(); 3279 3280 // We have a variables window, so give all the area back to the 3281 // variables window 3282 variables_window_sp->Resize(variables_bounds.size.width + 3283 registers_window_sp->GetWidth(), 3284 variables_bounds.size.height); 3285 } else { 3286 // We have no variables window showing so give the bottom area back 3287 // to the source view 3288 source_window_sp->Resize(source_bounds.size.width, 3289 source_bounds.size.height + 3290 registers_window_sp->GetHeight()); 3291 } 3292 main_window_sp->RemoveSubWindow(registers_window_sp.get()); 3293 } else { 3294 Rect new_regs_rect; 3295 if (variables_window_sp) { 3296 // We have a variables window, split it into two columns where the 3297 // left hand side will be the variables and the right hand side will 3298 // be the registers 3299 const Rect variables_bounds = variables_window_sp->GetBounds(); 3300 Rect new_vars_rect; 3301 variables_bounds.VerticalSplitPercentage(0.50, new_vars_rect, 3302 new_regs_rect); 3303 variables_window_sp->SetBounds(new_vars_rect); 3304 } else { 3305 // No variables window, grab the bottom part of the source window 3306 Rect new_source_rect; 3307 source_bounds.HorizontalSplitPercentage(0.70, new_source_rect, 3308 new_regs_rect); 3309 source_window_sp->SetBounds(new_source_rect); 3310 } 3311 WindowSP new_window_sp = 3312 main_window_sp->CreateSubWindow("Registers", new_regs_rect, false); 3313 new_window_sp->SetDelegate( 3314 WindowDelegateSP(new RegistersWindowDelegate(m_debugger))); 3315 } 3316 touchwin(stdscr); 3317 } 3318 return MenuActionResult::Handled; 3319 3320 case eMenuID_HelpGUIHelp: 3321 m_app.GetMainWindow()->CreateHelpSubwindow(); 3322 return MenuActionResult::Handled; 3323 3324 default: 3325 break; 3326 } 3327 3328 return MenuActionResult::NotHandled; 3329 } 3330 3331 protected: 3332 Application &m_app; 3333 Debugger &m_debugger; 3334 }; 3335 3336 class StatusBarWindowDelegate : public WindowDelegate { 3337 public: 3338 StatusBarWindowDelegate(Debugger &debugger) : m_debugger(debugger) { 3339 FormatEntity::Parse("Thread: ${thread.id%tid}", m_format); 3340 } 3341 3342 ~StatusBarWindowDelegate() override = default; 3343 3344 bool WindowDelegateDraw(Window &window, bool force) override { 3345 ExecutionContext exe_ctx = 3346 m_debugger.GetCommandInterpreter().GetExecutionContext(); 3347 Process *process = exe_ctx.GetProcessPtr(); 3348 Thread *thread = exe_ctx.GetThreadPtr(); 3349 StackFrame *frame = exe_ctx.GetFramePtr(); 3350 window.Erase(); 3351 window.SetBackground(BlackOnWhite); 3352 window.MoveCursor(0, 0); 3353 if (process) { 3354 const StateType state = process->GetState(); 3355 window.Printf("Process: %5" PRIu64 " %10s", process->GetID(), 3356 StateAsCString(state)); 3357 3358 if (StateIsStoppedState(state, true)) { 3359 StreamString strm; 3360 if (thread && FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, 3361 nullptr, nullptr, false, false)) { 3362 window.MoveCursor(40, 0); 3363 window.PutCStringTruncated(1, strm.GetString().str().c_str()); 3364 } 3365 3366 window.MoveCursor(60, 0); 3367 if (frame) 3368 window.Printf("Frame: %3u PC = 0x%16.16" PRIx64, 3369 frame->GetFrameIndex(), 3370 frame->GetFrameCodeAddress().GetOpcodeLoadAddress( 3371 exe_ctx.GetTargetPtr())); 3372 } else if (state == eStateExited) { 3373 const char *exit_desc = process->GetExitDescription(); 3374 const int exit_status = process->GetExitStatus(); 3375 if (exit_desc && exit_desc[0]) 3376 window.Printf(" with status = %i (%s)", exit_status, exit_desc); 3377 else 3378 window.Printf(" with status = %i", exit_status); 3379 } 3380 } 3381 return true; 3382 } 3383 3384 protected: 3385 Debugger &m_debugger; 3386 FormatEntity::Entry m_format; 3387 }; 3388 3389 class SourceFileWindowDelegate : public WindowDelegate { 3390 public: 3391 SourceFileWindowDelegate(Debugger &debugger) 3392 : WindowDelegate(), m_debugger(debugger), m_sc(), m_file_sp(), 3393 m_disassembly_scope(nullptr), m_disassembly_sp(), m_disassembly_range(), 3394 m_title(), m_line_width(4), m_selected_line(0), m_pc_line(0), 3395 m_stop_id(0), m_frame_idx(UINT32_MAX), m_first_visible_line(0), 3396 m_first_visible_column(0), m_min_x(0), m_min_y(0), m_max_x(0), 3397 m_max_y(0) {} 3398 3399 ~SourceFileWindowDelegate() override = default; 3400 3401 void Update(const SymbolContext &sc) { m_sc = sc; } 3402 3403 uint32_t NumVisibleLines() const { return m_max_y - m_min_y; } 3404 3405 const char *WindowDelegateGetHelpText() override { 3406 return "Source/Disassembly window keyboard shortcuts:"; 3407 } 3408 3409 KeyHelp *WindowDelegateGetKeyHelp() override { 3410 static curses::KeyHelp g_source_view_key_help[] = { 3411 {KEY_RETURN, "Run to selected line with one shot breakpoint"}, 3412 {KEY_UP, "Select previous source line"}, 3413 {KEY_DOWN, "Select next source line"}, 3414 {KEY_LEFT, "Scroll to the left"}, 3415 {KEY_RIGHT, "Scroll to the right"}, 3416 {KEY_PPAGE, "Page up"}, 3417 {KEY_NPAGE, "Page down"}, 3418 {'b', "Set breakpoint on selected source/disassembly line"}, 3419 {'c', "Continue process"}, 3420 {'D', "Detach with process suspended"}, 3421 {'h', "Show help dialog"}, 3422 {'n', "Step over (source line)"}, 3423 {'N', "Step over (single instruction)"}, 3424 {'f', "Step out (finish)"}, 3425 {'s', "Step in (source line)"}, 3426 {'S', "Step in (single instruction)"}, 3427 {'u', "Frame up"}, 3428 {'d', "Frame down"}, 3429 {',', "Page up"}, 3430 {'.', "Page down"}, 3431 {'\0', nullptr}}; 3432 return g_source_view_key_help; 3433 } 3434 3435 bool WindowDelegateDraw(Window &window, bool force) override { 3436 ExecutionContext exe_ctx = 3437 m_debugger.GetCommandInterpreter().GetExecutionContext(); 3438 Process *process = exe_ctx.GetProcessPtr(); 3439 Thread *thread = nullptr; 3440 3441 bool update_location = false; 3442 if (process) { 3443 StateType state = process->GetState(); 3444 if (StateIsStoppedState(state, true)) { 3445 // We are stopped, so it is ok to 3446 update_location = true; 3447 } 3448 } 3449 3450 m_min_x = 1; 3451 m_min_y = 2; 3452 m_max_x = window.GetMaxX() - 1; 3453 m_max_y = window.GetMaxY() - 1; 3454 3455 const uint32_t num_visible_lines = NumVisibleLines(); 3456 StackFrameSP frame_sp; 3457 bool set_selected_line_to_pc = false; 3458 3459 if (update_location) { 3460 const bool process_alive = process ? process->IsAlive() : false; 3461 bool thread_changed = false; 3462 if (process_alive) { 3463 thread = exe_ctx.GetThreadPtr(); 3464 if (thread) { 3465 frame_sp = thread->GetSelectedFrame(); 3466 auto tid = thread->GetID(); 3467 thread_changed = tid != m_tid; 3468 m_tid = tid; 3469 } else { 3470 if (m_tid != LLDB_INVALID_THREAD_ID) { 3471 thread_changed = true; 3472 m_tid = LLDB_INVALID_THREAD_ID; 3473 } 3474 } 3475 } 3476 const uint32_t stop_id = process ? process->GetStopID() : 0; 3477 const bool stop_id_changed = stop_id != m_stop_id; 3478 bool frame_changed = false; 3479 m_stop_id = stop_id; 3480 m_title.Clear(); 3481 if (frame_sp) { 3482 m_sc = frame_sp->GetSymbolContext(eSymbolContextEverything); 3483 if (m_sc.module_sp) { 3484 m_title.Printf( 3485 "%s", m_sc.module_sp->GetFileSpec().GetFilename().GetCString()); 3486 ConstString func_name = m_sc.GetFunctionName(); 3487 if (func_name) 3488 m_title.Printf("`%s", func_name.GetCString()); 3489 } 3490 const uint32_t frame_idx = frame_sp->GetFrameIndex(); 3491 frame_changed = frame_idx != m_frame_idx; 3492 m_frame_idx = frame_idx; 3493 } else { 3494 m_sc.Clear(true); 3495 frame_changed = m_frame_idx != UINT32_MAX; 3496 m_frame_idx = UINT32_MAX; 3497 } 3498 3499 const bool context_changed = 3500 thread_changed || frame_changed || stop_id_changed; 3501 3502 if (process_alive) { 3503 if (m_sc.line_entry.IsValid()) { 3504 m_pc_line = m_sc.line_entry.line; 3505 if (m_pc_line != UINT32_MAX) 3506 --m_pc_line; // Convert to zero based line number... 3507 // Update the selected line if the stop ID changed... 3508 if (context_changed) 3509 m_selected_line = m_pc_line; 3510 3511 if (m_file_sp && m_file_sp->GetFileSpec() == m_sc.line_entry.file) { 3512 // Same file, nothing to do, we should either have the lines or not 3513 // (source file missing) 3514 if (m_selected_line >= static_cast<size_t>(m_first_visible_line)) { 3515 if (m_selected_line >= m_first_visible_line + num_visible_lines) 3516 m_first_visible_line = m_selected_line - 10; 3517 } else { 3518 if (m_selected_line > 10) 3519 m_first_visible_line = m_selected_line - 10; 3520 else 3521 m_first_visible_line = 0; 3522 } 3523 } else { 3524 // File changed, set selected line to the line with the PC 3525 m_selected_line = m_pc_line; 3526 m_file_sp = 3527 m_debugger.GetSourceManager().GetFile(m_sc.line_entry.file); 3528 if (m_file_sp) { 3529 const size_t num_lines = m_file_sp->GetNumLines(); 3530 m_line_width = 1; 3531 for (size_t n = num_lines; n >= 10; n = n / 10) 3532 ++m_line_width; 3533 3534 if (num_lines < num_visible_lines || 3535 m_selected_line < num_visible_lines) 3536 m_first_visible_line = 0; 3537 else 3538 m_first_visible_line = m_selected_line - 10; 3539 } 3540 } 3541 } else { 3542 m_file_sp.reset(); 3543 } 3544 3545 if (!m_file_sp || m_file_sp->GetNumLines() == 0) { 3546 // Show disassembly 3547 bool prefer_file_cache = false; 3548 if (m_sc.function) { 3549 if (m_disassembly_scope != m_sc.function) { 3550 m_disassembly_scope = m_sc.function; 3551 m_disassembly_sp = m_sc.function->GetInstructions( 3552 exe_ctx, nullptr, prefer_file_cache); 3553 if (m_disassembly_sp) { 3554 set_selected_line_to_pc = true; 3555 m_disassembly_range = m_sc.function->GetAddressRange(); 3556 } else { 3557 m_disassembly_range.Clear(); 3558 } 3559 } else { 3560 set_selected_line_to_pc = context_changed; 3561 } 3562 } else if (m_sc.symbol) { 3563 if (m_disassembly_scope != m_sc.symbol) { 3564 m_disassembly_scope = m_sc.symbol; 3565 m_disassembly_sp = m_sc.symbol->GetInstructions( 3566 exe_ctx, nullptr, prefer_file_cache); 3567 if (m_disassembly_sp) { 3568 set_selected_line_to_pc = true; 3569 m_disassembly_range.GetBaseAddress() = 3570 m_sc.symbol->GetAddress(); 3571 m_disassembly_range.SetByteSize(m_sc.symbol->GetByteSize()); 3572 } else { 3573 m_disassembly_range.Clear(); 3574 } 3575 } else { 3576 set_selected_line_to_pc = context_changed; 3577 } 3578 } 3579 } 3580 } else { 3581 m_pc_line = UINT32_MAX; 3582 } 3583 } 3584 3585 const int window_width = window.GetWidth(); 3586 window.Erase(); 3587 window.DrawTitleBox("Sources"); 3588 if (!m_title.GetString().empty()) { 3589 window.AttributeOn(A_REVERSE); 3590 window.MoveCursor(1, 1); 3591 window.PutChar(' '); 3592 window.PutCStringTruncated(1, m_title.GetString().str().c_str()); 3593 int x = window.GetCursorX(); 3594 if (x < window_width - 1) { 3595 window.Printf("%*s", window_width - x - 1, ""); 3596 } 3597 window.AttributeOff(A_REVERSE); 3598 } 3599 3600 Target *target = exe_ctx.GetTargetPtr(); 3601 const size_t num_source_lines = GetNumSourceLines(); 3602 if (num_source_lines > 0) { 3603 // Display source 3604 BreakpointLines bp_lines; 3605 if (target) { 3606 BreakpointList &bp_list = target->GetBreakpointList(); 3607 const size_t num_bps = bp_list.GetSize(); 3608 for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) { 3609 BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx); 3610 const size_t num_bps_locs = bp_sp->GetNumLocations(); 3611 for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) { 3612 BreakpointLocationSP bp_loc_sp = 3613 bp_sp->GetLocationAtIndex(bp_loc_idx); 3614 LineEntry bp_loc_line_entry; 3615 if (bp_loc_sp->GetAddress().CalculateSymbolContextLineEntry( 3616 bp_loc_line_entry)) { 3617 if (m_file_sp->GetFileSpec() == bp_loc_line_entry.file) { 3618 bp_lines.insert(bp_loc_line_entry.line); 3619 } 3620 } 3621 } 3622 } 3623 } 3624 3625 const attr_t selected_highlight_attr = A_REVERSE; 3626 const attr_t pc_highlight_attr = COLOR_PAIR(BlackOnBlue); 3627 3628 for (size_t i = 0; i < num_visible_lines; ++i) { 3629 const uint32_t curr_line = m_first_visible_line + i; 3630 if (curr_line < num_source_lines) { 3631 const int line_y = m_min_y + i; 3632 window.MoveCursor(1, line_y); 3633 const bool is_pc_line = curr_line == m_pc_line; 3634 const bool line_is_selected = m_selected_line == curr_line; 3635 // Highlight the line as the PC line first, then if the selected line 3636 // isn't the same as the PC line, highlight it differently 3637 attr_t highlight_attr = 0; 3638 attr_t bp_attr = 0; 3639 if (is_pc_line) 3640 highlight_attr = pc_highlight_attr; 3641 else if (line_is_selected) 3642 highlight_attr = selected_highlight_attr; 3643 3644 if (bp_lines.find(curr_line + 1) != bp_lines.end()) 3645 bp_attr = COLOR_PAIR(BlackOnWhite); 3646 3647 if (bp_attr) 3648 window.AttributeOn(bp_attr); 3649 3650 window.Printf(" %*u ", m_line_width, curr_line + 1); 3651 3652 if (bp_attr) 3653 window.AttributeOff(bp_attr); 3654 3655 window.PutChar(ACS_VLINE); 3656 // Mark the line with the PC with a diamond 3657 if (is_pc_line) 3658 window.PutChar(ACS_DIAMOND); 3659 else 3660 window.PutChar(' '); 3661 3662 if (highlight_attr) 3663 window.AttributeOn(highlight_attr); 3664 3665 StreamString lineStream; 3666 m_file_sp->DisplaySourceLines(curr_line + 1, {}, 0, 0, &lineStream); 3667 StringRef line = lineStream.GetString(); 3668 if (line.endswith("\n")) 3669 line = line.drop_back(); 3670 bool wasWritten = window.OutputColoredStringTruncated( 3671 1, line, m_first_visible_column, line_is_selected); 3672 if (line_is_selected && !wasWritten) { 3673 // Draw an empty space to show the selected line if empty, 3674 // or draw '<' if nothing is visible because of scrolling too much 3675 // to the right. 3676 window.PutCStringTruncated( 3677 1, line.empty() && m_first_visible_column == 0 ? " " : "<"); 3678 } 3679 3680 if (is_pc_line && frame_sp && 3681 frame_sp->GetConcreteFrameIndex() == 0) { 3682 StopInfoSP stop_info_sp; 3683 if (thread) 3684 stop_info_sp = thread->GetStopInfo(); 3685 if (stop_info_sp) { 3686 const char *stop_description = stop_info_sp->GetDescription(); 3687 if (stop_description && stop_description[0]) { 3688 size_t stop_description_len = strlen(stop_description); 3689 int desc_x = window_width - stop_description_len - 16; 3690 if (desc_x - window.GetCursorX() > 0) 3691 window.Printf("%*s", desc_x - window.GetCursorX(), ""); 3692 window.MoveCursor(window_width - stop_description_len - 16, 3693 line_y); 3694 const attr_t stop_reason_attr = COLOR_PAIR(WhiteOnBlue); 3695 window.AttributeOn(stop_reason_attr); 3696 window.PrintfTruncated(1, " <<< Thread %u: %s ", 3697 thread->GetIndexID(), stop_description); 3698 window.AttributeOff(stop_reason_attr); 3699 } 3700 } else { 3701 window.Printf("%*s", window_width - window.GetCursorX() - 1, ""); 3702 } 3703 } 3704 if (highlight_attr) 3705 window.AttributeOff(highlight_attr); 3706 } else { 3707 break; 3708 } 3709 } 3710 } else { 3711 size_t num_disassembly_lines = GetNumDisassemblyLines(); 3712 if (num_disassembly_lines > 0) { 3713 // Display disassembly 3714 BreakpointAddrs bp_file_addrs; 3715 Target *target = exe_ctx.GetTargetPtr(); 3716 if (target) { 3717 BreakpointList &bp_list = target->GetBreakpointList(); 3718 const size_t num_bps = bp_list.GetSize(); 3719 for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) { 3720 BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx); 3721 const size_t num_bps_locs = bp_sp->GetNumLocations(); 3722 for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; 3723 ++bp_loc_idx) { 3724 BreakpointLocationSP bp_loc_sp = 3725 bp_sp->GetLocationAtIndex(bp_loc_idx); 3726 LineEntry bp_loc_line_entry; 3727 const lldb::addr_t file_addr = 3728 bp_loc_sp->GetAddress().GetFileAddress(); 3729 if (file_addr != LLDB_INVALID_ADDRESS) { 3730 if (m_disassembly_range.ContainsFileAddress(file_addr)) 3731 bp_file_addrs.insert(file_addr); 3732 } 3733 } 3734 } 3735 } 3736 3737 const attr_t selected_highlight_attr = A_REVERSE; 3738 const attr_t pc_highlight_attr = COLOR_PAIR(WhiteOnBlue); 3739 3740 StreamString strm; 3741 3742 InstructionList &insts = m_disassembly_sp->GetInstructionList(); 3743 Address pc_address; 3744 3745 if (frame_sp) 3746 pc_address = frame_sp->GetFrameCodeAddress(); 3747 const uint32_t pc_idx = 3748 pc_address.IsValid() 3749 ? insts.GetIndexOfInstructionAtAddress(pc_address) 3750 : UINT32_MAX; 3751 if (set_selected_line_to_pc) { 3752 m_selected_line = pc_idx; 3753 } 3754 3755 const uint32_t non_visible_pc_offset = (num_visible_lines / 5); 3756 if (static_cast<size_t>(m_first_visible_line) >= num_disassembly_lines) 3757 m_first_visible_line = 0; 3758 3759 if (pc_idx < num_disassembly_lines) { 3760 if (pc_idx < static_cast<uint32_t>(m_first_visible_line) || 3761 pc_idx >= m_first_visible_line + num_visible_lines) 3762 m_first_visible_line = pc_idx - non_visible_pc_offset; 3763 } 3764 3765 for (size_t i = 0; i < num_visible_lines; ++i) { 3766 const uint32_t inst_idx = m_first_visible_line + i; 3767 Instruction *inst = insts.GetInstructionAtIndex(inst_idx).get(); 3768 if (!inst) 3769 break; 3770 3771 const int line_y = m_min_y + i; 3772 window.MoveCursor(1, line_y); 3773 const bool is_pc_line = frame_sp && inst_idx == pc_idx; 3774 const bool line_is_selected = m_selected_line == inst_idx; 3775 // Highlight the line as the PC line first, then if the selected line 3776 // isn't the same as the PC line, highlight it differently 3777 attr_t highlight_attr = 0; 3778 attr_t bp_attr = 0; 3779 if (is_pc_line) 3780 highlight_attr = pc_highlight_attr; 3781 else if (line_is_selected) 3782 highlight_attr = selected_highlight_attr; 3783 3784 if (bp_file_addrs.find(inst->GetAddress().GetFileAddress()) != 3785 bp_file_addrs.end()) 3786 bp_attr = COLOR_PAIR(BlackOnWhite); 3787 3788 if (bp_attr) 3789 window.AttributeOn(bp_attr); 3790 3791 window.Printf(" 0x%16.16llx ", 3792 static_cast<unsigned long long>( 3793 inst->GetAddress().GetLoadAddress(target))); 3794 3795 if (bp_attr) 3796 window.AttributeOff(bp_attr); 3797 3798 window.PutChar(ACS_VLINE); 3799 // Mark the line with the PC with a diamond 3800 if (is_pc_line) 3801 window.PutChar(ACS_DIAMOND); 3802 else 3803 window.PutChar(' '); 3804 3805 if (highlight_attr) 3806 window.AttributeOn(highlight_attr); 3807 3808 const char *mnemonic = inst->GetMnemonic(&exe_ctx); 3809 const char *operands = inst->GetOperands(&exe_ctx); 3810 const char *comment = inst->GetComment(&exe_ctx); 3811 3812 if (mnemonic != nullptr && mnemonic[0] == '\0') 3813 mnemonic = nullptr; 3814 if (operands != nullptr && operands[0] == '\0') 3815 operands = nullptr; 3816 if (comment != nullptr && comment[0] == '\0') 3817 comment = nullptr; 3818 3819 strm.Clear(); 3820 3821 if (mnemonic != nullptr && operands != nullptr && comment != nullptr) 3822 strm.Printf("%-8s %-25s ; %s", mnemonic, operands, comment); 3823 else if (mnemonic != nullptr && operands != nullptr) 3824 strm.Printf("%-8s %s", mnemonic, operands); 3825 else if (mnemonic != nullptr) 3826 strm.Printf("%s", mnemonic); 3827 3828 int right_pad = 1; 3829 window.PutCStringTruncated( 3830 right_pad, 3831 strm.GetString().substr(m_first_visible_column).data()); 3832 3833 if (is_pc_line && frame_sp && 3834 frame_sp->GetConcreteFrameIndex() == 0) { 3835 StopInfoSP stop_info_sp; 3836 if (thread) 3837 stop_info_sp = thread->GetStopInfo(); 3838 if (stop_info_sp) { 3839 const char *stop_description = stop_info_sp->GetDescription(); 3840 if (stop_description && stop_description[0]) { 3841 size_t stop_description_len = strlen(stop_description); 3842 int desc_x = window_width - stop_description_len - 16; 3843 if (desc_x - window.GetCursorX() > 0) 3844 window.Printf("%*s", desc_x - window.GetCursorX(), ""); 3845 window.MoveCursor(window_width - stop_description_len - 15, 3846 line_y); 3847 window.PrintfTruncated(1, "<<< Thread %u: %s ", 3848 thread->GetIndexID(), stop_description); 3849 } 3850 } else { 3851 window.Printf("%*s", window_width - window.GetCursorX() - 1, ""); 3852 } 3853 } 3854 if (highlight_attr) 3855 window.AttributeOff(highlight_attr); 3856 } 3857 } 3858 } 3859 return true; // Drawing handled 3860 } 3861 3862 size_t GetNumLines() { 3863 size_t num_lines = GetNumSourceLines(); 3864 if (num_lines == 0) 3865 num_lines = GetNumDisassemblyLines(); 3866 return num_lines; 3867 } 3868 3869 size_t GetNumSourceLines() const { 3870 if (m_file_sp) 3871 return m_file_sp->GetNumLines(); 3872 return 0; 3873 } 3874 3875 size_t GetNumDisassemblyLines() const { 3876 if (m_disassembly_sp) 3877 return m_disassembly_sp->GetInstructionList().GetSize(); 3878 return 0; 3879 } 3880 3881 HandleCharResult WindowDelegateHandleChar(Window &window, int c) override { 3882 const uint32_t num_visible_lines = NumVisibleLines(); 3883 const size_t num_lines = GetNumLines(); 3884 3885 switch (c) { 3886 case ',': 3887 case KEY_PPAGE: 3888 // Page up key 3889 if (static_cast<uint32_t>(m_first_visible_line) > num_visible_lines) 3890 m_first_visible_line -= num_visible_lines; 3891 else 3892 m_first_visible_line = 0; 3893 m_selected_line = m_first_visible_line; 3894 return eKeyHandled; 3895 3896 case '.': 3897 case KEY_NPAGE: 3898 // Page down key 3899 { 3900 if (m_first_visible_line + num_visible_lines < num_lines) 3901 m_first_visible_line += num_visible_lines; 3902 else if (num_lines < num_visible_lines) 3903 m_first_visible_line = 0; 3904 else 3905 m_first_visible_line = num_lines - num_visible_lines; 3906 m_selected_line = m_first_visible_line; 3907 } 3908 return eKeyHandled; 3909 3910 case KEY_UP: 3911 if (m_selected_line > 0) { 3912 m_selected_line--; 3913 if (static_cast<size_t>(m_first_visible_line) > m_selected_line) 3914 m_first_visible_line = m_selected_line; 3915 } 3916 return eKeyHandled; 3917 3918 case KEY_DOWN: 3919 if (m_selected_line + 1 < num_lines) { 3920 m_selected_line++; 3921 if (m_first_visible_line + num_visible_lines < m_selected_line) 3922 m_first_visible_line++; 3923 } 3924 return eKeyHandled; 3925 3926 case KEY_LEFT: 3927 if (m_first_visible_column > 0) 3928 --m_first_visible_column; 3929 return eKeyHandled; 3930 3931 case KEY_RIGHT: 3932 ++m_first_visible_column; 3933 return eKeyHandled; 3934 3935 case '\r': 3936 case '\n': 3937 case KEY_ENTER: 3938 // Set a breakpoint and run to the line using a one shot breakpoint 3939 if (GetNumSourceLines() > 0) { 3940 ExecutionContext exe_ctx = 3941 m_debugger.GetCommandInterpreter().GetExecutionContext(); 3942 if (exe_ctx.HasProcessScope() && exe_ctx.GetProcessRef().IsAlive()) { 3943 BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint( 3944 nullptr, // Don't limit the breakpoint to certain modules 3945 m_file_sp->GetFileSpec(), // Source file 3946 m_selected_line + 3947 1, // Source line number (m_selected_line is zero based) 3948 0, // Unspecified column. 3949 0, // No offset 3950 eLazyBoolCalculate, // Check inlines using global setting 3951 eLazyBoolCalculate, // Skip prologue using global setting, 3952 false, // internal 3953 false, // request_hardware 3954 eLazyBoolCalculate); // move_to_nearest_code 3955 // Make breakpoint one shot 3956 bp_sp->GetOptions()->SetOneShot(true); 3957 exe_ctx.GetProcessRef().Resume(); 3958 } 3959 } else if (m_selected_line < GetNumDisassemblyLines()) { 3960 const Instruction *inst = m_disassembly_sp->GetInstructionList() 3961 .GetInstructionAtIndex(m_selected_line) 3962 .get(); 3963 ExecutionContext exe_ctx = 3964 m_debugger.GetCommandInterpreter().GetExecutionContext(); 3965 if (exe_ctx.HasTargetScope()) { 3966 Address addr = inst->GetAddress(); 3967 BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint( 3968 addr, // lldb_private::Address 3969 false, // internal 3970 false); // request_hardware 3971 // Make breakpoint one shot 3972 bp_sp->GetOptions()->SetOneShot(true); 3973 exe_ctx.GetProcessRef().Resume(); 3974 } 3975 } 3976 return eKeyHandled; 3977 3978 case 'b': // 'b' == toggle breakpoint on currently selected line 3979 ToggleBreakpointOnSelectedLine(); 3980 return eKeyHandled; 3981 3982 case 'D': // 'D' == detach and keep stopped 3983 { 3984 ExecutionContext exe_ctx = 3985 m_debugger.GetCommandInterpreter().GetExecutionContext(); 3986 if (exe_ctx.HasProcessScope()) 3987 exe_ctx.GetProcessRef().Detach(true); 3988 } 3989 return eKeyHandled; 3990 3991 case 'c': 3992 // 'c' == continue 3993 { 3994 ExecutionContext exe_ctx = 3995 m_debugger.GetCommandInterpreter().GetExecutionContext(); 3996 if (exe_ctx.HasProcessScope()) 3997 exe_ctx.GetProcessRef().Resume(); 3998 } 3999 return eKeyHandled; 4000 4001 case 'f': 4002 // 'f' == step out (finish) 4003 { 4004 ExecutionContext exe_ctx = 4005 m_debugger.GetCommandInterpreter().GetExecutionContext(); 4006 if (exe_ctx.HasThreadScope() && 4007 StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) { 4008 exe_ctx.GetThreadRef().StepOut(); 4009 } 4010 } 4011 return eKeyHandled; 4012 4013 case 'n': // 'n' == step over 4014 case 'N': // 'N' == step over instruction 4015 { 4016 ExecutionContext exe_ctx = 4017 m_debugger.GetCommandInterpreter().GetExecutionContext(); 4018 if (exe_ctx.HasThreadScope() && 4019 StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) { 4020 bool source_step = (c == 'n'); 4021 exe_ctx.GetThreadRef().StepOver(source_step); 4022 } 4023 } 4024 return eKeyHandled; 4025 4026 case 's': // 's' == step into 4027 case 'S': // 'S' == step into instruction 4028 { 4029 ExecutionContext exe_ctx = 4030 m_debugger.GetCommandInterpreter().GetExecutionContext(); 4031 if (exe_ctx.HasThreadScope() && 4032 StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) { 4033 bool source_step = (c == 's'); 4034 exe_ctx.GetThreadRef().StepIn(source_step); 4035 } 4036 } 4037 return eKeyHandled; 4038 4039 case 'u': // 'u' == frame up 4040 case 'd': // 'd' == frame down 4041 { 4042 ExecutionContext exe_ctx = 4043 m_debugger.GetCommandInterpreter().GetExecutionContext(); 4044 if (exe_ctx.HasThreadScope()) { 4045 Thread *thread = exe_ctx.GetThreadPtr(); 4046 uint32_t frame_idx = thread->GetSelectedFrameIndex(); 4047 if (frame_idx == UINT32_MAX) 4048 frame_idx = 0; 4049 if (c == 'u' && frame_idx + 1 < thread->GetStackFrameCount()) 4050 ++frame_idx; 4051 else if (c == 'd' && frame_idx > 0) 4052 --frame_idx; 4053 if (thread->SetSelectedFrameByIndex(frame_idx, true)) 4054 exe_ctx.SetFrameSP(thread->GetSelectedFrame()); 4055 } 4056 } 4057 return eKeyHandled; 4058 4059 case 'h': 4060 window.CreateHelpSubwindow(); 4061 return eKeyHandled; 4062 4063 default: 4064 break; 4065 } 4066 return eKeyNotHandled; 4067 } 4068 4069 void ToggleBreakpointOnSelectedLine() { 4070 ExecutionContext exe_ctx = 4071 m_debugger.GetCommandInterpreter().GetExecutionContext(); 4072 if (!exe_ctx.HasTargetScope()) 4073 return; 4074 if (GetNumSourceLines() > 0) { 4075 // Source file breakpoint. 4076 BreakpointList &bp_list = exe_ctx.GetTargetRef().GetBreakpointList(); 4077 const size_t num_bps = bp_list.GetSize(); 4078 for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) { 4079 BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx); 4080 const size_t num_bps_locs = bp_sp->GetNumLocations(); 4081 for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) { 4082 BreakpointLocationSP bp_loc_sp = 4083 bp_sp->GetLocationAtIndex(bp_loc_idx); 4084 LineEntry bp_loc_line_entry; 4085 if (bp_loc_sp->GetAddress().CalculateSymbolContextLineEntry( 4086 bp_loc_line_entry)) { 4087 if (m_file_sp->GetFileSpec() == bp_loc_line_entry.file && 4088 m_selected_line + 1 == bp_loc_line_entry.line) { 4089 bool removed = 4090 exe_ctx.GetTargetRef().RemoveBreakpointByID(bp_sp->GetID()); 4091 assert(removed); 4092 UNUSED_IF_ASSERT_DISABLED(removed); 4093 return; // Existing breakpoint removed. 4094 } 4095 } 4096 } 4097 } 4098 // No breakpoint found on the location, add it. 4099 BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint( 4100 nullptr, // Don't limit the breakpoint to certain modules 4101 m_file_sp->GetFileSpec(), // Source file 4102 m_selected_line + 4103 1, // Source line number (m_selected_line is zero based) 4104 0, // No column specified. 4105 0, // No offset 4106 eLazyBoolCalculate, // Check inlines using global setting 4107 eLazyBoolCalculate, // Skip prologue using global setting, 4108 false, // internal 4109 false, // request_hardware 4110 eLazyBoolCalculate); // move_to_nearest_code 4111 } else { 4112 // Disassembly breakpoint. 4113 assert(GetNumDisassemblyLines() > 0); 4114 assert(m_selected_line < GetNumDisassemblyLines()); 4115 const Instruction *inst = m_disassembly_sp->GetInstructionList() 4116 .GetInstructionAtIndex(m_selected_line) 4117 .get(); 4118 Address addr = inst->GetAddress(); 4119 // Try to find it. 4120 BreakpointList &bp_list = exe_ctx.GetTargetRef().GetBreakpointList(); 4121 const size_t num_bps = bp_list.GetSize(); 4122 for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) { 4123 BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx); 4124 const size_t num_bps_locs = bp_sp->GetNumLocations(); 4125 for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) { 4126 BreakpointLocationSP bp_loc_sp = 4127 bp_sp->GetLocationAtIndex(bp_loc_idx); 4128 LineEntry bp_loc_line_entry; 4129 const lldb::addr_t file_addr = 4130 bp_loc_sp->GetAddress().GetFileAddress(); 4131 if (file_addr == addr.GetFileAddress()) { 4132 bool removed = 4133 exe_ctx.GetTargetRef().RemoveBreakpointByID(bp_sp->GetID()); 4134 assert(removed); 4135 UNUSED_IF_ASSERT_DISABLED(removed); 4136 return; // Existing breakpoint removed. 4137 } 4138 } 4139 } 4140 // No breakpoint found on the address, add it. 4141 BreakpointSP bp_sp = 4142 exe_ctx.GetTargetRef().CreateBreakpoint(addr, // lldb_private::Address 4143 false, // internal 4144 false); // request_hardware 4145 } 4146 } 4147 4148 protected: 4149 typedef std::set<uint32_t> BreakpointLines; 4150 typedef std::set<lldb::addr_t> BreakpointAddrs; 4151 4152 Debugger &m_debugger; 4153 SymbolContext m_sc; 4154 SourceManager::FileSP m_file_sp; 4155 SymbolContextScope *m_disassembly_scope; 4156 lldb::DisassemblerSP m_disassembly_sp; 4157 AddressRange m_disassembly_range; 4158 StreamString m_title; 4159 lldb::user_id_t m_tid; 4160 int m_line_width; 4161 uint32_t m_selected_line; // The selected line 4162 uint32_t m_pc_line; // The line with the PC 4163 uint32_t m_stop_id; 4164 uint32_t m_frame_idx; 4165 int m_first_visible_line; 4166 int m_first_visible_column; 4167 int m_min_x; 4168 int m_min_y; 4169 int m_max_x; 4170 int m_max_y; 4171 }; 4172 4173 DisplayOptions ValueObjectListDelegate::g_options = {true}; 4174 4175 IOHandlerCursesGUI::IOHandlerCursesGUI(Debugger &debugger) 4176 : IOHandler(debugger, IOHandler::Type::Curses) {} 4177 4178 void IOHandlerCursesGUI::Activate() { 4179 IOHandler::Activate(); 4180 if (!m_app_ap) { 4181 m_app_ap = std::make_unique<Application>(GetInputFILE(), GetOutputFILE()); 4182 4183 // This is both a window and a menu delegate 4184 std::shared_ptr<ApplicationDelegate> app_delegate_sp( 4185 new ApplicationDelegate(*m_app_ap, m_debugger)); 4186 4187 MenuDelegateSP app_menu_delegate_sp = 4188 std::static_pointer_cast<MenuDelegate>(app_delegate_sp); 4189 MenuSP lldb_menu_sp( 4190 new Menu("LLDB", "F1", KEY_F(1), ApplicationDelegate::eMenuID_LLDB)); 4191 MenuSP exit_menuitem_sp( 4192 new Menu("Exit", nullptr, 'x', ApplicationDelegate::eMenuID_LLDBExit)); 4193 exit_menuitem_sp->SetCannedResult(MenuActionResult::Quit); 4194 lldb_menu_sp->AddSubmenu(MenuSP(new Menu( 4195 "About LLDB", nullptr, 'a', ApplicationDelegate::eMenuID_LLDBAbout))); 4196 lldb_menu_sp->AddSubmenu(MenuSP(new Menu(Menu::Type::Separator))); 4197 lldb_menu_sp->AddSubmenu(exit_menuitem_sp); 4198 4199 MenuSP target_menu_sp(new Menu("Target", "F2", KEY_F(2), 4200 ApplicationDelegate::eMenuID_Target)); 4201 target_menu_sp->AddSubmenu(MenuSP(new Menu( 4202 "Create", nullptr, 'c', ApplicationDelegate::eMenuID_TargetCreate))); 4203 target_menu_sp->AddSubmenu(MenuSP(new Menu( 4204 "Delete", nullptr, 'd', ApplicationDelegate::eMenuID_TargetDelete))); 4205 4206 MenuSP process_menu_sp(new Menu("Process", "F3", KEY_F(3), 4207 ApplicationDelegate::eMenuID_Process)); 4208 process_menu_sp->AddSubmenu(MenuSP(new Menu( 4209 "Attach", nullptr, 'a', ApplicationDelegate::eMenuID_ProcessAttach))); 4210 process_menu_sp->AddSubmenu( 4211 MenuSP(new Menu("Detach and resume", nullptr, 'd', 4212 ApplicationDelegate::eMenuID_ProcessDetachResume))); 4213 process_menu_sp->AddSubmenu( 4214 MenuSP(new Menu("Detach suspended", nullptr, 's', 4215 ApplicationDelegate::eMenuID_ProcessDetachSuspended))); 4216 process_menu_sp->AddSubmenu(MenuSP(new Menu( 4217 "Launch", nullptr, 'l', ApplicationDelegate::eMenuID_ProcessLaunch))); 4218 process_menu_sp->AddSubmenu(MenuSP(new Menu(Menu::Type::Separator))); 4219 process_menu_sp->AddSubmenu( 4220 MenuSP(new Menu("Continue", nullptr, 'c', 4221 ApplicationDelegate::eMenuID_ProcessContinue))); 4222 process_menu_sp->AddSubmenu(MenuSP(new Menu( 4223 "Halt", nullptr, 'h', ApplicationDelegate::eMenuID_ProcessHalt))); 4224 process_menu_sp->AddSubmenu(MenuSP(new Menu( 4225 "Kill", nullptr, 'k', ApplicationDelegate::eMenuID_ProcessKill))); 4226 4227 MenuSP thread_menu_sp(new Menu("Thread", "F4", KEY_F(4), 4228 ApplicationDelegate::eMenuID_Thread)); 4229 thread_menu_sp->AddSubmenu(MenuSP(new Menu( 4230 "Step In", nullptr, 'i', ApplicationDelegate::eMenuID_ThreadStepIn))); 4231 thread_menu_sp->AddSubmenu( 4232 MenuSP(new Menu("Step Over", nullptr, 'v', 4233 ApplicationDelegate::eMenuID_ThreadStepOver))); 4234 thread_menu_sp->AddSubmenu(MenuSP(new Menu( 4235 "Step Out", nullptr, 'o', ApplicationDelegate::eMenuID_ThreadStepOut))); 4236 4237 MenuSP view_menu_sp( 4238 new Menu("View", "F5", KEY_F(5), ApplicationDelegate::eMenuID_View)); 4239 view_menu_sp->AddSubmenu( 4240 MenuSP(new Menu("Backtrace", nullptr, 'b', 4241 ApplicationDelegate::eMenuID_ViewBacktrace))); 4242 view_menu_sp->AddSubmenu( 4243 MenuSP(new Menu("Registers", nullptr, 'r', 4244 ApplicationDelegate::eMenuID_ViewRegisters))); 4245 view_menu_sp->AddSubmenu(MenuSP(new Menu( 4246 "Source", nullptr, 's', ApplicationDelegate::eMenuID_ViewSource))); 4247 view_menu_sp->AddSubmenu( 4248 MenuSP(new Menu("Variables", nullptr, 'v', 4249 ApplicationDelegate::eMenuID_ViewVariables))); 4250 4251 MenuSP help_menu_sp( 4252 new Menu("Help", "F6", KEY_F(6), ApplicationDelegate::eMenuID_Help)); 4253 help_menu_sp->AddSubmenu(MenuSP(new Menu( 4254 "GUI Help", nullptr, 'g', ApplicationDelegate::eMenuID_HelpGUIHelp))); 4255 4256 m_app_ap->Initialize(); 4257 WindowSP &main_window_sp = m_app_ap->GetMainWindow(); 4258 4259 MenuSP menubar_sp(new Menu(Menu::Type::Bar)); 4260 menubar_sp->AddSubmenu(lldb_menu_sp); 4261 menubar_sp->AddSubmenu(target_menu_sp); 4262 menubar_sp->AddSubmenu(process_menu_sp); 4263 menubar_sp->AddSubmenu(thread_menu_sp); 4264 menubar_sp->AddSubmenu(view_menu_sp); 4265 menubar_sp->AddSubmenu(help_menu_sp); 4266 menubar_sp->SetDelegate(app_menu_delegate_sp); 4267 4268 Rect content_bounds = main_window_sp->GetFrame(); 4269 Rect menubar_bounds = content_bounds.MakeMenuBar(); 4270 Rect status_bounds = content_bounds.MakeStatusBar(); 4271 Rect source_bounds; 4272 Rect variables_bounds; 4273 Rect threads_bounds; 4274 Rect source_variables_bounds; 4275 content_bounds.VerticalSplitPercentage(0.80, source_variables_bounds, 4276 threads_bounds); 4277 source_variables_bounds.HorizontalSplitPercentage(0.70, source_bounds, 4278 variables_bounds); 4279 4280 WindowSP menubar_window_sp = 4281 main_window_sp->CreateSubWindow("Menubar", menubar_bounds, false); 4282 // Let the menubar get keys if the active window doesn't handle the keys 4283 // that are typed so it can respond to menubar key presses. 4284 menubar_window_sp->SetCanBeActive( 4285 false); // Don't let the menubar become the active window 4286 menubar_window_sp->SetDelegate(menubar_sp); 4287 4288 WindowSP source_window_sp( 4289 main_window_sp->CreateSubWindow("Source", source_bounds, true)); 4290 WindowSP variables_window_sp( 4291 main_window_sp->CreateSubWindow("Variables", variables_bounds, false)); 4292 WindowSP threads_window_sp( 4293 main_window_sp->CreateSubWindow("Threads", threads_bounds, false)); 4294 WindowSP status_window_sp( 4295 main_window_sp->CreateSubWindow("Status", status_bounds, false)); 4296 status_window_sp->SetCanBeActive( 4297 false); // Don't let the status bar become the active window 4298 main_window_sp->SetDelegate( 4299 std::static_pointer_cast<WindowDelegate>(app_delegate_sp)); 4300 source_window_sp->SetDelegate( 4301 WindowDelegateSP(new SourceFileWindowDelegate(m_debugger))); 4302 variables_window_sp->SetDelegate( 4303 WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger))); 4304 TreeDelegateSP thread_delegate_sp(new ThreadsTreeDelegate(m_debugger)); 4305 threads_window_sp->SetDelegate(WindowDelegateSP( 4306 new TreeWindowDelegate(m_debugger, thread_delegate_sp))); 4307 status_window_sp->SetDelegate( 4308 WindowDelegateSP(new StatusBarWindowDelegate(m_debugger))); 4309 4310 // Show the main help window once the first time the curses GUI is launched 4311 static bool g_showed_help = false; 4312 if (!g_showed_help) { 4313 g_showed_help = true; 4314 main_window_sp->CreateHelpSubwindow(); 4315 } 4316 4317 // All colors with black background. 4318 init_pair(1, COLOR_BLACK, COLOR_BLACK); 4319 init_pair(2, COLOR_RED, COLOR_BLACK); 4320 init_pair(3, COLOR_GREEN, COLOR_BLACK); 4321 init_pair(4, COLOR_YELLOW, COLOR_BLACK); 4322 init_pair(5, COLOR_BLUE, COLOR_BLACK); 4323 init_pair(6, COLOR_MAGENTA, COLOR_BLACK); 4324 init_pair(7, COLOR_CYAN, COLOR_BLACK); 4325 init_pair(8, COLOR_WHITE, COLOR_BLACK); 4326 // All colors with blue background. 4327 init_pair(9, COLOR_BLACK, COLOR_BLUE); 4328 init_pair(10, COLOR_RED, COLOR_BLUE); 4329 init_pair(11, COLOR_GREEN, COLOR_BLUE); 4330 init_pair(12, COLOR_YELLOW, COLOR_BLUE); 4331 init_pair(13, COLOR_BLUE, COLOR_BLUE); 4332 init_pair(14, COLOR_MAGENTA, COLOR_BLUE); 4333 init_pair(15, COLOR_CYAN, COLOR_BLUE); 4334 init_pair(16, COLOR_WHITE, COLOR_BLUE); 4335 // These must match the order in the color indexes enum. 4336 init_pair(17, COLOR_BLACK, COLOR_WHITE); 4337 init_pair(18, COLOR_MAGENTA, COLOR_WHITE); 4338 static_assert(LastColorPairIndex == 18, "Color indexes do not match."); 4339 } 4340 } 4341 4342 void IOHandlerCursesGUI::Deactivate() { m_app_ap->Terminate(); } 4343 4344 void IOHandlerCursesGUI::Run() { 4345 m_app_ap->Run(m_debugger); 4346 SetIsDone(true); 4347 } 4348 4349 IOHandlerCursesGUI::~IOHandlerCursesGUI() = default; 4350 4351 void IOHandlerCursesGUI::Cancel() {} 4352 4353 bool IOHandlerCursesGUI::Interrupt() { return false; } 4354 4355 void IOHandlerCursesGUI::GotEOF() {} 4356 4357 void IOHandlerCursesGUI::TerminalSizeChanged() { 4358 m_app_ap->TerminalSizeChanged(); 4359 } 4360 4361 #endif // LLDB_ENABLE_CURSES 4362