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