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