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 = 0; 1501 int row_idx = 0; 1502 int x = 1; 1503 int y = 1; 1504 bool might_have_children; 1505 bool expanded = false; 1506 bool calculated_children = false; 1507 std::vector<Row> children; 1508 1509 Row(const ValueObjectSP &v, Row *p) 1510 : value(v, lldb::eDynamicDontRunTarget, true), parent(p), 1511 might_have_children(v ? v->MightHaveChildren() : false) {} 1512 1513 size_t GetDepth() const { 1514 if (parent) 1515 return 1 + parent->GetDepth(); 1516 return 0; 1517 } 1518 1519 void Expand() { expanded = true; } 1520 1521 std::vector<Row> &GetChildren() { 1522 ProcessSP process_sp = value.GetProcessSP(); 1523 auto stop_id = process_sp->GetStopID(); 1524 if (process_sp && stop_id != children_stop_id) { 1525 children_stop_id = stop_id; 1526 calculated_children = false; 1527 } 1528 if (!calculated_children) { 1529 children.clear(); 1530 calculated_children = true; 1531 ValueObjectSP valobj = value.GetSP(); 1532 if (valobj) { 1533 const size_t num_children = valobj->GetNumChildren(); 1534 for (size_t i = 0; i < num_children; ++i) { 1535 children.push_back(Row(valobj->GetChildAtIndex(i, true), this)); 1536 } 1537 } 1538 } 1539 return children; 1540 } 1541 1542 void Unexpand() { 1543 expanded = false; 1544 calculated_children = false; 1545 children.clear(); 1546 } 1547 1548 void DrawTree(Window &window) { 1549 if (parent) 1550 parent->DrawTreeForChild(window, this, 0); 1551 1552 if (might_have_children) { 1553 // It we can get UTF8 characters to work we should try to use the 1554 // "symbol" UTF8 string below 1555 // const char *symbol = ""; 1556 // if (row.expanded) 1557 // symbol = "\xe2\x96\xbd "; 1558 // else 1559 // symbol = "\xe2\x96\xb7 "; 1560 // window.PutCString (symbol); 1561 1562 // The ACS_DARROW and ACS_RARROW don't look very nice they are just a 'v' 1563 // or '>' character... 1564 // if (expanded) 1565 // window.PutChar (ACS_DARROW); 1566 // else 1567 // window.PutChar (ACS_RARROW); 1568 // Since we can't find any good looking right arrow/down arrow symbols, 1569 // just use a diamond... 1570 window.PutChar(ACS_DIAMOND); 1571 window.PutChar(ACS_HLINE); 1572 } 1573 } 1574 1575 void DrawTreeForChild(Window &window, Row *child, uint32_t reverse_depth) { 1576 if (parent) 1577 parent->DrawTreeForChild(window, this, reverse_depth + 1); 1578 1579 if (&GetChildren().back() == child) { 1580 // Last child 1581 if (reverse_depth == 0) { 1582 window.PutChar(ACS_LLCORNER); 1583 window.PutChar(ACS_HLINE); 1584 } else { 1585 window.PutChar(' '); 1586 window.PutChar(' '); 1587 } 1588 } else { 1589 if (reverse_depth == 0) { 1590 window.PutChar(ACS_LTEE); 1591 window.PutChar(ACS_HLINE); 1592 } else { 1593 window.PutChar(ACS_VLINE); 1594 window.PutChar(' '); 1595 } 1596 } 1597 } 1598 }; 1599 1600 struct DisplayOptions { 1601 bool show_types; 1602 }; 1603 1604 class TreeItem; 1605 1606 class TreeDelegate { 1607 public: 1608 TreeDelegate() = default; 1609 virtual ~TreeDelegate() = default; 1610 1611 virtual void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) = 0; 1612 virtual void TreeDelegateGenerateChildren(TreeItem &item) = 0; 1613 virtual bool TreeDelegateItemSelected( 1614 TreeItem &item) = 0; // Return true if we need to update views 1615 }; 1616 1617 typedef std::shared_ptr<TreeDelegate> TreeDelegateSP; 1618 1619 class TreeItem { 1620 public: 1621 TreeItem(TreeItem *parent, TreeDelegate &delegate, bool might_have_children) 1622 : m_parent(parent), m_delegate(delegate), m_user_data(nullptr), 1623 m_identifier(0), m_row_idx(-1), m_children(), 1624 m_might_have_children(might_have_children), m_is_expanded(false) {} 1625 1626 TreeItem &operator=(const TreeItem &rhs) { 1627 if (this != &rhs) { 1628 m_parent = rhs.m_parent; 1629 m_delegate = rhs.m_delegate; 1630 m_user_data = rhs.m_user_data; 1631 m_identifier = rhs.m_identifier; 1632 m_row_idx = rhs.m_row_idx; 1633 m_children = rhs.m_children; 1634 m_might_have_children = rhs.m_might_have_children; 1635 m_is_expanded = rhs.m_is_expanded; 1636 } 1637 return *this; 1638 } 1639 1640 TreeItem(const TreeItem &) = default; 1641 1642 size_t GetDepth() const { 1643 if (m_parent) 1644 return 1 + m_parent->GetDepth(); 1645 return 0; 1646 } 1647 1648 int GetRowIndex() const { return m_row_idx; } 1649 1650 void ClearChildren() { m_children.clear(); } 1651 1652 void Resize(size_t n, const TreeItem &t) { m_children.resize(n, t); } 1653 1654 TreeItem &operator[](size_t i) { return m_children[i]; } 1655 1656 void SetRowIndex(int row_idx) { m_row_idx = row_idx; } 1657 1658 size_t GetNumChildren() { 1659 m_delegate.TreeDelegateGenerateChildren(*this); 1660 return m_children.size(); 1661 } 1662 1663 void ItemWasSelected() { m_delegate.TreeDelegateItemSelected(*this); } 1664 1665 void CalculateRowIndexes(int &row_idx) { 1666 SetRowIndex(row_idx); 1667 ++row_idx; 1668 1669 const bool expanded = IsExpanded(); 1670 1671 // The root item must calculate its children, or we must calculate the 1672 // number of children if the item is expanded 1673 if (m_parent == nullptr || expanded) 1674 GetNumChildren(); 1675 1676 for (auto &item : m_children) { 1677 if (expanded) 1678 item.CalculateRowIndexes(row_idx); 1679 else 1680 item.SetRowIndex(-1); 1681 } 1682 } 1683 1684 TreeItem *GetParent() { return m_parent; } 1685 1686 bool IsExpanded() const { return m_is_expanded; } 1687 1688 void Expand() { m_is_expanded = true; } 1689 1690 void Unexpand() { m_is_expanded = false; } 1691 1692 bool Draw(Window &window, const int first_visible_row, 1693 const uint32_t selected_row_idx, int &row_idx, int &num_rows_left) { 1694 if (num_rows_left <= 0) 1695 return false; 1696 1697 if (m_row_idx >= first_visible_row) { 1698 window.MoveCursor(2, row_idx + 1); 1699 1700 if (m_parent) 1701 m_parent->DrawTreeForChild(window, this, 0); 1702 1703 if (m_might_have_children) { 1704 // It we can get UTF8 characters to work we should try to use the 1705 // "symbol" UTF8 string below 1706 // const char *symbol = ""; 1707 // if (row.expanded) 1708 // symbol = "\xe2\x96\xbd "; 1709 // else 1710 // symbol = "\xe2\x96\xb7 "; 1711 // window.PutCString (symbol); 1712 1713 // The ACS_DARROW and ACS_RARROW don't look very nice they are just a 1714 // 'v' or '>' character... 1715 // if (expanded) 1716 // window.PutChar (ACS_DARROW); 1717 // else 1718 // window.PutChar (ACS_RARROW); 1719 // Since we can't find any good looking right arrow/down arrow symbols, 1720 // just use a diamond... 1721 window.PutChar(ACS_DIAMOND); 1722 window.PutChar(ACS_HLINE); 1723 } 1724 bool highlight = (selected_row_idx == static_cast<size_t>(m_row_idx)) && 1725 window.IsActive(); 1726 1727 if (highlight) 1728 window.AttributeOn(A_REVERSE); 1729 1730 m_delegate.TreeDelegateDrawTreeItem(*this, window); 1731 1732 if (highlight) 1733 window.AttributeOff(A_REVERSE); 1734 ++row_idx; 1735 --num_rows_left; 1736 } 1737 1738 if (num_rows_left <= 0) 1739 return false; // We are done drawing... 1740 1741 if (IsExpanded()) { 1742 for (auto &item : m_children) { 1743 // If we displayed all the rows and item.Draw() returns false we are 1744 // done drawing and can exit this for loop 1745 if (!item.Draw(window, first_visible_row, selected_row_idx, row_idx, 1746 num_rows_left)) 1747 break; 1748 } 1749 } 1750 return num_rows_left >= 0; // Return true if not done drawing yet 1751 } 1752 1753 void DrawTreeForChild(Window &window, TreeItem *child, 1754 uint32_t reverse_depth) { 1755 if (m_parent) 1756 m_parent->DrawTreeForChild(window, this, reverse_depth + 1); 1757 1758 if (&m_children.back() == child) { 1759 // Last child 1760 if (reverse_depth == 0) { 1761 window.PutChar(ACS_LLCORNER); 1762 window.PutChar(ACS_HLINE); 1763 } else { 1764 window.PutChar(' '); 1765 window.PutChar(' '); 1766 } 1767 } else { 1768 if (reverse_depth == 0) { 1769 window.PutChar(ACS_LTEE); 1770 window.PutChar(ACS_HLINE); 1771 } else { 1772 window.PutChar(ACS_VLINE); 1773 window.PutChar(' '); 1774 } 1775 } 1776 } 1777 1778 TreeItem *GetItemForRowIndex(uint32_t row_idx) { 1779 if (static_cast<uint32_t>(m_row_idx) == row_idx) 1780 return this; 1781 if (m_children.empty()) 1782 return nullptr; 1783 if (IsExpanded()) { 1784 for (auto &item : m_children) { 1785 TreeItem *selected_item_ptr = item.GetItemForRowIndex(row_idx); 1786 if (selected_item_ptr) 1787 return selected_item_ptr; 1788 } 1789 } 1790 return nullptr; 1791 } 1792 1793 void *GetUserData() const { return m_user_data; } 1794 1795 void SetUserData(void *user_data) { m_user_data = user_data; } 1796 1797 uint64_t GetIdentifier() const { return m_identifier; } 1798 1799 void SetIdentifier(uint64_t identifier) { m_identifier = identifier; } 1800 1801 void SetMightHaveChildren(bool b) { m_might_have_children = b; } 1802 1803 protected: 1804 TreeItem *m_parent; 1805 TreeDelegate &m_delegate; 1806 void *m_user_data; 1807 uint64_t m_identifier; 1808 int m_row_idx; // Zero based visible row index, -1 if not visible or for the 1809 // root item 1810 std::vector<TreeItem> m_children; 1811 bool m_might_have_children; 1812 bool m_is_expanded; 1813 }; 1814 1815 class TreeWindowDelegate : public WindowDelegate { 1816 public: 1817 TreeWindowDelegate(Debugger &debugger, const TreeDelegateSP &delegate_sp) 1818 : m_debugger(debugger), m_delegate_sp(delegate_sp), 1819 m_root(nullptr, *delegate_sp, true), m_selected_item(nullptr), 1820 m_num_rows(0), m_selected_row_idx(0), m_first_visible_row(0), 1821 m_min_x(0), m_min_y(0), m_max_x(0), m_max_y(0) {} 1822 1823 int NumVisibleRows() const { return m_max_y - m_min_y; } 1824 1825 bool WindowDelegateDraw(Window &window, bool force) override { 1826 ExecutionContext exe_ctx( 1827 m_debugger.GetCommandInterpreter().GetExecutionContext()); 1828 Process *process = exe_ctx.GetProcessPtr(); 1829 1830 bool display_content = false; 1831 if (process) { 1832 StateType state = process->GetState(); 1833 if (StateIsStoppedState(state, true)) { 1834 // We are stopped, so it is ok to 1835 display_content = true; 1836 } else if (StateIsRunningState(state)) { 1837 return true; // Don't do any updating when we are running 1838 } 1839 } 1840 1841 m_min_x = 2; 1842 m_min_y = 1; 1843 m_max_x = window.GetWidth() - 1; 1844 m_max_y = window.GetHeight() - 1; 1845 1846 window.Erase(); 1847 window.DrawTitleBox(window.GetName()); 1848 1849 if (display_content) { 1850 const int num_visible_rows = NumVisibleRows(); 1851 m_num_rows = 0; 1852 m_root.CalculateRowIndexes(m_num_rows); 1853 1854 // If we unexpanded while having something selected our total number of 1855 // rows is less than the num visible rows, then make sure we show all the 1856 // rows by setting the first visible row accordingly. 1857 if (m_first_visible_row > 0 && m_num_rows < num_visible_rows) 1858 m_first_visible_row = 0; 1859 1860 // Make sure the selected row is always visible 1861 if (m_selected_row_idx < m_first_visible_row) 1862 m_first_visible_row = m_selected_row_idx; 1863 else if (m_first_visible_row + num_visible_rows <= m_selected_row_idx) 1864 m_first_visible_row = m_selected_row_idx - num_visible_rows + 1; 1865 1866 int row_idx = 0; 1867 int num_rows_left = num_visible_rows; 1868 m_root.Draw(window, m_first_visible_row, m_selected_row_idx, row_idx, 1869 num_rows_left); 1870 // Get the selected row 1871 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); 1872 } else { 1873 m_selected_item = nullptr; 1874 } 1875 1876 return true; // Drawing handled 1877 } 1878 1879 const char *WindowDelegateGetHelpText() override { 1880 return "Thread window keyboard shortcuts:"; 1881 } 1882 1883 KeyHelp *WindowDelegateGetKeyHelp() override { 1884 static curses::KeyHelp g_source_view_key_help[] = { 1885 {KEY_UP, "Select previous item"}, 1886 {KEY_DOWN, "Select next item"}, 1887 {KEY_RIGHT, "Expand the selected item"}, 1888 {KEY_LEFT, 1889 "Unexpand the selected item or select parent if not expanded"}, 1890 {KEY_PPAGE, "Page up"}, 1891 {KEY_NPAGE, "Page down"}, 1892 {'h', "Show help dialog"}, 1893 {' ', "Toggle item expansion"}, 1894 {',', "Page up"}, 1895 {'.', "Page down"}, 1896 {'\0', nullptr}}; 1897 return g_source_view_key_help; 1898 } 1899 1900 HandleCharResult WindowDelegateHandleChar(Window &window, int c) override { 1901 switch (c) { 1902 case ',': 1903 case KEY_PPAGE: 1904 // Page up key 1905 if (m_first_visible_row > 0) { 1906 if (m_first_visible_row > m_max_y) 1907 m_first_visible_row -= m_max_y; 1908 else 1909 m_first_visible_row = 0; 1910 m_selected_row_idx = m_first_visible_row; 1911 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); 1912 if (m_selected_item) 1913 m_selected_item->ItemWasSelected(); 1914 } 1915 return eKeyHandled; 1916 1917 case '.': 1918 case KEY_NPAGE: 1919 // Page down key 1920 if (m_num_rows > m_max_y) { 1921 if (m_first_visible_row + m_max_y < m_num_rows) { 1922 m_first_visible_row += m_max_y; 1923 m_selected_row_idx = m_first_visible_row; 1924 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); 1925 if (m_selected_item) 1926 m_selected_item->ItemWasSelected(); 1927 } 1928 } 1929 return eKeyHandled; 1930 1931 case KEY_UP: 1932 if (m_selected_row_idx > 0) { 1933 --m_selected_row_idx; 1934 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); 1935 if (m_selected_item) 1936 m_selected_item->ItemWasSelected(); 1937 } 1938 return eKeyHandled; 1939 1940 case KEY_DOWN: 1941 if (m_selected_row_idx + 1 < m_num_rows) { 1942 ++m_selected_row_idx; 1943 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); 1944 if (m_selected_item) 1945 m_selected_item->ItemWasSelected(); 1946 } 1947 return eKeyHandled; 1948 1949 case KEY_RIGHT: 1950 if (m_selected_item) { 1951 if (!m_selected_item->IsExpanded()) 1952 m_selected_item->Expand(); 1953 } 1954 return eKeyHandled; 1955 1956 case KEY_LEFT: 1957 if (m_selected_item) { 1958 if (m_selected_item->IsExpanded()) 1959 m_selected_item->Unexpand(); 1960 else if (m_selected_item->GetParent()) { 1961 m_selected_row_idx = m_selected_item->GetParent()->GetRowIndex(); 1962 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); 1963 if (m_selected_item) 1964 m_selected_item->ItemWasSelected(); 1965 } 1966 } 1967 return eKeyHandled; 1968 1969 case ' ': 1970 // Toggle expansion state when SPACE is pressed 1971 if (m_selected_item) { 1972 if (m_selected_item->IsExpanded()) 1973 m_selected_item->Unexpand(); 1974 else 1975 m_selected_item->Expand(); 1976 } 1977 return eKeyHandled; 1978 1979 case 'h': 1980 window.CreateHelpSubwindow(); 1981 return eKeyHandled; 1982 1983 default: 1984 break; 1985 } 1986 return eKeyNotHandled; 1987 } 1988 1989 protected: 1990 Debugger &m_debugger; 1991 TreeDelegateSP m_delegate_sp; 1992 TreeItem m_root; 1993 TreeItem *m_selected_item; 1994 int m_num_rows; 1995 int m_selected_row_idx; 1996 int m_first_visible_row; 1997 int m_min_x; 1998 int m_min_y; 1999 int m_max_x; 2000 int m_max_y; 2001 }; 2002 2003 class FrameTreeDelegate : public TreeDelegate { 2004 public: 2005 FrameTreeDelegate() : TreeDelegate() { 2006 FormatEntity::Parse( 2007 "frame #${frame.index}: {${function.name}${function.pc-offset}}}", 2008 m_format); 2009 } 2010 2011 ~FrameTreeDelegate() override = default; 2012 2013 void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override { 2014 Thread *thread = (Thread *)item.GetUserData(); 2015 if (thread) { 2016 const uint64_t frame_idx = item.GetIdentifier(); 2017 StackFrameSP frame_sp = thread->GetStackFrameAtIndex(frame_idx); 2018 if (frame_sp) { 2019 StreamString strm; 2020 const SymbolContext &sc = 2021 frame_sp->GetSymbolContext(eSymbolContextEverything); 2022 ExecutionContext exe_ctx(frame_sp); 2023 if (FormatEntity::Format(m_format, strm, &sc, &exe_ctx, nullptr, 2024 nullptr, false, false)) { 2025 int right_pad = 1; 2026 window.PutCStringTruncated(right_pad, strm.GetString().str().c_str()); 2027 } 2028 } 2029 } 2030 } 2031 2032 void TreeDelegateGenerateChildren(TreeItem &item) override { 2033 // No children for frames yet... 2034 } 2035 2036 bool TreeDelegateItemSelected(TreeItem &item) override { 2037 Thread *thread = (Thread *)item.GetUserData(); 2038 if (thread) { 2039 thread->GetProcess()->GetThreadList().SetSelectedThreadByID( 2040 thread->GetID()); 2041 const uint64_t frame_idx = item.GetIdentifier(); 2042 thread->SetSelectedFrameByIndex(frame_idx); 2043 return true; 2044 } 2045 return false; 2046 } 2047 2048 protected: 2049 FormatEntity::Entry m_format; 2050 }; 2051 2052 class ThreadTreeDelegate : public TreeDelegate { 2053 public: 2054 ThreadTreeDelegate(Debugger &debugger) 2055 : TreeDelegate(), m_debugger(debugger), m_tid(LLDB_INVALID_THREAD_ID), 2056 m_stop_id(UINT32_MAX) { 2057 FormatEntity::Parse("thread #${thread.index}: tid = ${thread.id}{, stop " 2058 "reason = ${thread.stop-reason}}", 2059 m_format); 2060 } 2061 2062 ~ThreadTreeDelegate() override = default; 2063 2064 ProcessSP GetProcess() { 2065 return m_debugger.GetCommandInterpreter() 2066 .GetExecutionContext() 2067 .GetProcessSP(); 2068 } 2069 2070 ThreadSP GetThread(const TreeItem &item) { 2071 ProcessSP process_sp = GetProcess(); 2072 if (process_sp) 2073 return process_sp->GetThreadList().FindThreadByID(item.GetIdentifier()); 2074 return ThreadSP(); 2075 } 2076 2077 void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override { 2078 ThreadSP thread_sp = GetThread(item); 2079 if (thread_sp) { 2080 StreamString strm; 2081 ExecutionContext exe_ctx(thread_sp); 2082 if (FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, nullptr, 2083 nullptr, false, false)) { 2084 int right_pad = 1; 2085 window.PutCStringTruncated(right_pad, strm.GetString().str().c_str()); 2086 } 2087 } 2088 } 2089 2090 void TreeDelegateGenerateChildren(TreeItem &item) override { 2091 ProcessSP process_sp = GetProcess(); 2092 if (process_sp && process_sp->IsAlive()) { 2093 StateType state = process_sp->GetState(); 2094 if (StateIsStoppedState(state, true)) { 2095 ThreadSP thread_sp = GetThread(item); 2096 if (thread_sp) { 2097 if (m_stop_id == process_sp->GetStopID() && 2098 thread_sp->GetID() == m_tid) 2099 return; // Children are already up to date 2100 if (!m_frame_delegate_sp) { 2101 // Always expand the thread item the first time we show it 2102 m_frame_delegate_sp = std::make_shared<FrameTreeDelegate>(); 2103 } 2104 2105 m_stop_id = process_sp->GetStopID(); 2106 m_tid = thread_sp->GetID(); 2107 2108 TreeItem t(&item, *m_frame_delegate_sp, false); 2109 size_t num_frames = thread_sp->GetStackFrameCount(); 2110 item.Resize(num_frames, t); 2111 for (size_t i = 0; i < num_frames; ++i) { 2112 item[i].SetUserData(thread_sp.get()); 2113 item[i].SetIdentifier(i); 2114 } 2115 } 2116 return; 2117 } 2118 } 2119 item.ClearChildren(); 2120 } 2121 2122 bool TreeDelegateItemSelected(TreeItem &item) override { 2123 ProcessSP process_sp = GetProcess(); 2124 if (process_sp && process_sp->IsAlive()) { 2125 StateType state = process_sp->GetState(); 2126 if (StateIsStoppedState(state, true)) { 2127 ThreadSP thread_sp = GetThread(item); 2128 if (thread_sp) { 2129 ThreadList &thread_list = thread_sp->GetProcess()->GetThreadList(); 2130 std::lock_guard<std::recursive_mutex> guard(thread_list.GetMutex()); 2131 ThreadSP selected_thread_sp = thread_list.GetSelectedThread(); 2132 if (selected_thread_sp->GetID() != thread_sp->GetID()) { 2133 thread_list.SetSelectedThreadByID(thread_sp->GetID()); 2134 return true; 2135 } 2136 } 2137 } 2138 } 2139 return false; 2140 } 2141 2142 protected: 2143 Debugger &m_debugger; 2144 std::shared_ptr<FrameTreeDelegate> m_frame_delegate_sp; 2145 lldb::user_id_t m_tid; 2146 uint32_t m_stop_id; 2147 FormatEntity::Entry m_format; 2148 }; 2149 2150 class ThreadsTreeDelegate : public TreeDelegate { 2151 public: 2152 ThreadsTreeDelegate(Debugger &debugger) 2153 : TreeDelegate(), m_thread_delegate_sp(), m_debugger(debugger), 2154 m_stop_id(UINT32_MAX) { 2155 FormatEntity::Parse("process ${process.id}{, name = ${process.name}}", 2156 m_format); 2157 } 2158 2159 ~ThreadsTreeDelegate() override = default; 2160 2161 ProcessSP GetProcess() { 2162 return m_debugger.GetCommandInterpreter() 2163 .GetExecutionContext() 2164 .GetProcessSP(); 2165 } 2166 2167 void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override { 2168 ProcessSP process_sp = GetProcess(); 2169 if (process_sp && process_sp->IsAlive()) { 2170 StreamString strm; 2171 ExecutionContext exe_ctx(process_sp); 2172 if (FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, nullptr, 2173 nullptr, false, false)) { 2174 int right_pad = 1; 2175 window.PutCStringTruncated(right_pad, strm.GetString().str().c_str()); 2176 } 2177 } 2178 } 2179 2180 void TreeDelegateGenerateChildren(TreeItem &item) override { 2181 ProcessSP process_sp = GetProcess(); 2182 if (process_sp && process_sp->IsAlive()) { 2183 StateType state = process_sp->GetState(); 2184 if (StateIsStoppedState(state, true)) { 2185 const uint32_t stop_id = process_sp->GetStopID(); 2186 if (m_stop_id == stop_id) 2187 return; // Children are already up to date 2188 2189 m_stop_id = stop_id; 2190 2191 if (!m_thread_delegate_sp) { 2192 // Always expand the thread item the first time we show it 2193 // item.Expand(); 2194 m_thread_delegate_sp = 2195 std::make_shared<ThreadTreeDelegate>(m_debugger); 2196 } 2197 2198 TreeItem t(&item, *m_thread_delegate_sp, false); 2199 ThreadList &threads = process_sp->GetThreadList(); 2200 std::lock_guard<std::recursive_mutex> guard(threads.GetMutex()); 2201 size_t num_threads = threads.GetSize(); 2202 item.Resize(num_threads, t); 2203 for (size_t i = 0; i < num_threads; ++i) { 2204 item[i].SetIdentifier(threads.GetThreadAtIndex(i)->GetID()); 2205 item[i].SetMightHaveChildren(true); 2206 } 2207 return; 2208 } 2209 } 2210 item.ClearChildren(); 2211 } 2212 2213 bool TreeDelegateItemSelected(TreeItem &item) override { return false; } 2214 2215 protected: 2216 std::shared_ptr<ThreadTreeDelegate> m_thread_delegate_sp; 2217 Debugger &m_debugger; 2218 uint32_t m_stop_id; 2219 FormatEntity::Entry m_format; 2220 }; 2221 2222 class ValueObjectListDelegate : public WindowDelegate { 2223 public: 2224 ValueObjectListDelegate() 2225 : m_rows(), m_selected_row(nullptr), m_selected_row_idx(0), 2226 m_first_visible_row(0), m_num_rows(0), m_max_x(0), m_max_y(0) {} 2227 2228 ValueObjectListDelegate(ValueObjectList &valobj_list) 2229 : m_rows(), m_selected_row(nullptr), m_selected_row_idx(0), 2230 m_first_visible_row(0), m_num_rows(0), m_max_x(0), m_max_y(0) { 2231 SetValues(valobj_list); 2232 } 2233 2234 ~ValueObjectListDelegate() override = default; 2235 2236 void SetValues(ValueObjectList &valobj_list) { 2237 m_selected_row = nullptr; 2238 m_selected_row_idx = 0; 2239 m_first_visible_row = 0; 2240 m_num_rows = 0; 2241 m_rows.clear(); 2242 for (auto &valobj_sp : valobj_list.GetObjects()) 2243 m_rows.push_back(Row(valobj_sp, nullptr)); 2244 } 2245 2246 bool WindowDelegateDraw(Window &window, bool force) override { 2247 m_num_rows = 0; 2248 m_min_x = 2; 2249 m_min_y = 1; 2250 m_max_x = window.GetWidth() - 1; 2251 m_max_y = window.GetHeight() - 1; 2252 2253 window.Erase(); 2254 window.DrawTitleBox(window.GetName()); 2255 2256 const int num_visible_rows = NumVisibleRows(); 2257 const int num_rows = CalculateTotalNumberRows(m_rows); 2258 2259 // If we unexpanded while having something selected our total number of 2260 // rows is less than the num visible rows, then make sure we show all the 2261 // rows by setting the first visible row accordingly. 2262 if (m_first_visible_row > 0 && num_rows < num_visible_rows) 2263 m_first_visible_row = 0; 2264 2265 // Make sure the selected row is always visible 2266 if (m_selected_row_idx < m_first_visible_row) 2267 m_first_visible_row = m_selected_row_idx; 2268 else if (m_first_visible_row + num_visible_rows <= m_selected_row_idx) 2269 m_first_visible_row = m_selected_row_idx - num_visible_rows + 1; 2270 2271 DisplayRows(window, m_rows, g_options); 2272 2273 // Get the selected row 2274 m_selected_row = GetRowForRowIndex(m_selected_row_idx); 2275 // Keep the cursor on the selected row so the highlight and the cursor are 2276 // always on the same line 2277 if (m_selected_row) 2278 window.MoveCursor(m_selected_row->x, m_selected_row->y); 2279 2280 return true; // Drawing handled 2281 } 2282 2283 KeyHelp *WindowDelegateGetKeyHelp() override { 2284 static curses::KeyHelp g_source_view_key_help[] = { 2285 {KEY_UP, "Select previous item"}, 2286 {KEY_DOWN, "Select next item"}, 2287 {KEY_RIGHT, "Expand selected item"}, 2288 {KEY_LEFT, "Unexpand selected item or select parent if not expanded"}, 2289 {KEY_PPAGE, "Page up"}, 2290 {KEY_NPAGE, "Page down"}, 2291 {'A', "Format as annotated address"}, 2292 {'b', "Format as binary"}, 2293 {'B', "Format as hex bytes with ASCII"}, 2294 {'c', "Format as character"}, 2295 {'d', "Format as a signed integer"}, 2296 {'D', "Format selected value using the default format for the type"}, 2297 {'f', "Format as float"}, 2298 {'h', "Show help dialog"}, 2299 {'i', "Format as instructions"}, 2300 {'o', "Format as octal"}, 2301 {'p', "Format as pointer"}, 2302 {'s', "Format as C string"}, 2303 {'t', "Toggle showing/hiding type names"}, 2304 {'u', "Format as an unsigned integer"}, 2305 {'x', "Format as hex"}, 2306 {'X', "Format as uppercase hex"}, 2307 {' ', "Toggle item expansion"}, 2308 {',', "Page up"}, 2309 {'.', "Page down"}, 2310 {'\0', nullptr}}; 2311 return g_source_view_key_help; 2312 } 2313 2314 HandleCharResult WindowDelegateHandleChar(Window &window, int c) override { 2315 switch (c) { 2316 case 'x': 2317 case 'X': 2318 case 'o': 2319 case 's': 2320 case 'u': 2321 case 'd': 2322 case 'D': 2323 case 'i': 2324 case 'A': 2325 case 'p': 2326 case 'c': 2327 case 'b': 2328 case 'B': 2329 case 'f': 2330 // Change the format for the currently selected item 2331 if (m_selected_row) { 2332 auto valobj_sp = m_selected_row->value.GetSP(); 2333 if (valobj_sp) 2334 valobj_sp->SetFormat(FormatForChar(c)); 2335 } 2336 return eKeyHandled; 2337 2338 case 't': 2339 // Toggle showing type names 2340 g_options.show_types = !g_options.show_types; 2341 return eKeyHandled; 2342 2343 case ',': 2344 case KEY_PPAGE: 2345 // Page up key 2346 if (m_first_visible_row > 0) { 2347 if (static_cast<int>(m_first_visible_row) > m_max_y) 2348 m_first_visible_row -= m_max_y; 2349 else 2350 m_first_visible_row = 0; 2351 m_selected_row_idx = m_first_visible_row; 2352 } 2353 return eKeyHandled; 2354 2355 case '.': 2356 case KEY_NPAGE: 2357 // Page down key 2358 if (m_num_rows > static_cast<size_t>(m_max_y)) { 2359 if (m_first_visible_row + m_max_y < m_num_rows) { 2360 m_first_visible_row += m_max_y; 2361 m_selected_row_idx = m_first_visible_row; 2362 } 2363 } 2364 return eKeyHandled; 2365 2366 case KEY_UP: 2367 if (m_selected_row_idx > 0) 2368 --m_selected_row_idx; 2369 return eKeyHandled; 2370 2371 case KEY_DOWN: 2372 if (m_selected_row_idx + 1 < m_num_rows) 2373 ++m_selected_row_idx; 2374 return eKeyHandled; 2375 2376 case KEY_RIGHT: 2377 if (m_selected_row) { 2378 if (!m_selected_row->expanded) 2379 m_selected_row->Expand(); 2380 } 2381 return eKeyHandled; 2382 2383 case KEY_LEFT: 2384 if (m_selected_row) { 2385 if (m_selected_row->expanded) 2386 m_selected_row->Unexpand(); 2387 else if (m_selected_row->parent) 2388 m_selected_row_idx = m_selected_row->parent->row_idx; 2389 } 2390 return eKeyHandled; 2391 2392 case ' ': 2393 // Toggle expansion state when SPACE is pressed 2394 if (m_selected_row) { 2395 if (m_selected_row->expanded) 2396 m_selected_row->Unexpand(); 2397 else 2398 m_selected_row->Expand(); 2399 } 2400 return eKeyHandled; 2401 2402 case 'h': 2403 window.CreateHelpSubwindow(); 2404 return eKeyHandled; 2405 2406 default: 2407 break; 2408 } 2409 return eKeyNotHandled; 2410 } 2411 2412 protected: 2413 std::vector<Row> m_rows; 2414 Row *m_selected_row; 2415 uint32_t m_selected_row_idx; 2416 uint32_t m_first_visible_row; 2417 uint32_t m_num_rows; 2418 int m_min_x; 2419 int m_min_y; 2420 int m_max_x; 2421 int m_max_y; 2422 2423 static Format FormatForChar(int c) { 2424 switch (c) { 2425 case 'x': 2426 return eFormatHex; 2427 case 'X': 2428 return eFormatHexUppercase; 2429 case 'o': 2430 return eFormatOctal; 2431 case 's': 2432 return eFormatCString; 2433 case 'u': 2434 return eFormatUnsigned; 2435 case 'd': 2436 return eFormatDecimal; 2437 case 'D': 2438 return eFormatDefault; 2439 case 'i': 2440 return eFormatInstruction; 2441 case 'A': 2442 return eFormatAddressInfo; 2443 case 'p': 2444 return eFormatPointer; 2445 case 'c': 2446 return eFormatChar; 2447 case 'b': 2448 return eFormatBinary; 2449 case 'B': 2450 return eFormatBytesWithASCII; 2451 case 'f': 2452 return eFormatFloat; 2453 } 2454 return eFormatDefault; 2455 } 2456 2457 bool DisplayRowObject(Window &window, Row &row, DisplayOptions &options, 2458 bool highlight, bool last_child) { 2459 ValueObject *valobj = row.value.GetSP().get(); 2460 2461 if (valobj == nullptr) 2462 return false; 2463 2464 const char *type_name = 2465 options.show_types ? valobj->GetTypeName().GetCString() : nullptr; 2466 const char *name = valobj->GetName().GetCString(); 2467 const char *value = valobj->GetValueAsCString(); 2468 const char *summary = valobj->GetSummaryAsCString(); 2469 2470 window.MoveCursor(row.x, row.y); 2471 2472 row.DrawTree(window); 2473 2474 if (highlight) 2475 window.AttributeOn(A_REVERSE); 2476 2477 if (type_name && type_name[0]) 2478 window.PrintfTruncated(1, "(%s) ", type_name); 2479 2480 if (name && name[0]) 2481 window.PutCStringTruncated(1, name); 2482 2483 attr_t changd_attr = 0; 2484 if (valobj->GetValueDidChange()) 2485 changd_attr = COLOR_PAIR(RedOnBlack) | A_BOLD; 2486 2487 if (value && value[0]) { 2488 window.PutCStringTruncated(1, " = "); 2489 if (changd_attr) 2490 window.AttributeOn(changd_attr); 2491 window.PutCStringTruncated(1, value); 2492 if (changd_attr) 2493 window.AttributeOff(changd_attr); 2494 } 2495 2496 if (summary && summary[0]) { 2497 window.PutCStringTruncated(1, " "); 2498 if (changd_attr) 2499 window.AttributeOn(changd_attr); 2500 window.PutCStringTruncated(1, summary); 2501 if (changd_attr) 2502 window.AttributeOff(changd_attr); 2503 } 2504 2505 if (highlight) 2506 window.AttributeOff(A_REVERSE); 2507 2508 return true; 2509 } 2510 2511 void DisplayRows(Window &window, std::vector<Row> &rows, 2512 DisplayOptions &options) { 2513 // > 0x25B7 2514 // \/ 0x25BD 2515 2516 bool window_is_active = window.IsActive(); 2517 for (auto &row : rows) { 2518 const bool last_child = row.parent && &rows[rows.size() - 1] == &row; 2519 // Save the row index in each Row structure 2520 row.row_idx = m_num_rows; 2521 if ((m_num_rows >= m_first_visible_row) && 2522 ((m_num_rows - m_first_visible_row) < 2523 static_cast<size_t>(NumVisibleRows()))) { 2524 row.x = m_min_x; 2525 row.y = m_num_rows - m_first_visible_row + 1; 2526 if (DisplayRowObject(window, row, options, 2527 window_is_active && 2528 m_num_rows == m_selected_row_idx, 2529 last_child)) { 2530 ++m_num_rows; 2531 } else { 2532 row.x = 0; 2533 row.y = 0; 2534 } 2535 } else { 2536 row.x = 0; 2537 row.y = 0; 2538 ++m_num_rows; 2539 } 2540 2541 auto &children = row.GetChildren(); 2542 if (row.expanded && !children.empty()) { 2543 DisplayRows(window, children, options); 2544 } 2545 } 2546 } 2547 2548 int CalculateTotalNumberRows(std::vector<Row> &rows) { 2549 int row_count = 0; 2550 for (auto &row : rows) { 2551 ++row_count; 2552 if (row.expanded) 2553 row_count += CalculateTotalNumberRows(row.GetChildren()); 2554 } 2555 return row_count; 2556 } 2557 2558 static Row *GetRowForRowIndexImpl(std::vector<Row> &rows, size_t &row_index) { 2559 for (auto &row : rows) { 2560 if (row_index == 0) 2561 return &row; 2562 else { 2563 --row_index; 2564 auto &children = row.GetChildren(); 2565 if (row.expanded && !children.empty()) { 2566 Row *result = GetRowForRowIndexImpl(children, row_index); 2567 if (result) 2568 return result; 2569 } 2570 } 2571 } 2572 return nullptr; 2573 } 2574 2575 Row *GetRowForRowIndex(size_t row_index) { 2576 return GetRowForRowIndexImpl(m_rows, row_index); 2577 } 2578 2579 int NumVisibleRows() const { return m_max_y - m_min_y; } 2580 2581 static DisplayOptions g_options; 2582 }; 2583 2584 class FrameVariablesWindowDelegate : public ValueObjectListDelegate { 2585 public: 2586 FrameVariablesWindowDelegate(Debugger &debugger) 2587 : ValueObjectListDelegate(), m_debugger(debugger), 2588 m_frame_block(nullptr) {} 2589 2590 ~FrameVariablesWindowDelegate() override = default; 2591 2592 const char *WindowDelegateGetHelpText() override { 2593 return "Frame variable window keyboard shortcuts:"; 2594 } 2595 2596 bool WindowDelegateDraw(Window &window, bool force) override { 2597 ExecutionContext exe_ctx( 2598 m_debugger.GetCommandInterpreter().GetExecutionContext()); 2599 Process *process = exe_ctx.GetProcessPtr(); 2600 Block *frame_block = nullptr; 2601 StackFrame *frame = nullptr; 2602 2603 if (process) { 2604 StateType state = process->GetState(); 2605 if (StateIsStoppedState(state, true)) { 2606 frame = exe_ctx.GetFramePtr(); 2607 if (frame) 2608 frame_block = frame->GetFrameBlock(); 2609 } else if (StateIsRunningState(state)) { 2610 return true; // Don't do any updating when we are running 2611 } 2612 } 2613 2614 ValueObjectList local_values; 2615 if (frame_block) { 2616 // Only update the variables if they have changed 2617 if (m_frame_block != frame_block) { 2618 m_frame_block = frame_block; 2619 2620 VariableList *locals = frame->GetVariableList(true); 2621 if (locals) { 2622 const DynamicValueType use_dynamic = eDynamicDontRunTarget; 2623 for (const VariableSP &local_sp : *locals) { 2624 ValueObjectSP value_sp = 2625 frame->GetValueObjectForFrameVariable(local_sp, use_dynamic); 2626 if (value_sp) { 2627 ValueObjectSP synthetic_value_sp = value_sp->GetSyntheticValue(); 2628 if (synthetic_value_sp) 2629 local_values.Append(synthetic_value_sp); 2630 else 2631 local_values.Append(value_sp); 2632 } 2633 } 2634 // Update the values 2635 SetValues(local_values); 2636 } 2637 } 2638 } else { 2639 m_frame_block = nullptr; 2640 // Update the values with an empty list if there is no frame 2641 SetValues(local_values); 2642 } 2643 2644 return ValueObjectListDelegate::WindowDelegateDraw(window, force); 2645 } 2646 2647 protected: 2648 Debugger &m_debugger; 2649 Block *m_frame_block; 2650 }; 2651 2652 class RegistersWindowDelegate : public ValueObjectListDelegate { 2653 public: 2654 RegistersWindowDelegate(Debugger &debugger) 2655 : ValueObjectListDelegate(), m_debugger(debugger) {} 2656 2657 ~RegistersWindowDelegate() override = default; 2658 2659 const char *WindowDelegateGetHelpText() override { 2660 return "Register window keyboard shortcuts:"; 2661 } 2662 2663 bool WindowDelegateDraw(Window &window, bool force) override { 2664 ExecutionContext exe_ctx( 2665 m_debugger.GetCommandInterpreter().GetExecutionContext()); 2666 StackFrame *frame = exe_ctx.GetFramePtr(); 2667 2668 ValueObjectList value_list; 2669 if (frame) { 2670 if (frame->GetStackID() != m_stack_id) { 2671 m_stack_id = frame->GetStackID(); 2672 RegisterContextSP reg_ctx(frame->GetRegisterContext()); 2673 if (reg_ctx) { 2674 const uint32_t num_sets = reg_ctx->GetRegisterSetCount(); 2675 for (uint32_t set_idx = 0; set_idx < num_sets; ++set_idx) { 2676 value_list.Append( 2677 ValueObjectRegisterSet::Create(frame, reg_ctx, set_idx)); 2678 } 2679 } 2680 SetValues(value_list); 2681 } 2682 } else { 2683 Process *process = exe_ctx.GetProcessPtr(); 2684 if (process && process->IsAlive()) 2685 return true; // Don't do any updating if we are running 2686 else { 2687 // Update the values with an empty list if there is no process or the 2688 // process isn't alive anymore 2689 SetValues(value_list); 2690 } 2691 } 2692 return ValueObjectListDelegate::WindowDelegateDraw(window, force); 2693 } 2694 2695 protected: 2696 Debugger &m_debugger; 2697 StackID m_stack_id; 2698 }; 2699 2700 static const char *CursesKeyToCString(int ch) { 2701 static char g_desc[32]; 2702 if (ch >= KEY_F0 && ch < KEY_F0 + 64) { 2703 snprintf(g_desc, sizeof(g_desc), "F%u", ch - KEY_F0); 2704 return g_desc; 2705 } 2706 switch (ch) { 2707 case KEY_DOWN: 2708 return "down"; 2709 case KEY_UP: 2710 return "up"; 2711 case KEY_LEFT: 2712 return "left"; 2713 case KEY_RIGHT: 2714 return "right"; 2715 case KEY_HOME: 2716 return "home"; 2717 case KEY_BACKSPACE: 2718 return "backspace"; 2719 case KEY_DL: 2720 return "delete-line"; 2721 case KEY_IL: 2722 return "insert-line"; 2723 case KEY_DC: 2724 return "delete-char"; 2725 case KEY_IC: 2726 return "insert-char"; 2727 case KEY_CLEAR: 2728 return "clear"; 2729 case KEY_EOS: 2730 return "clear-to-eos"; 2731 case KEY_EOL: 2732 return "clear-to-eol"; 2733 case KEY_SF: 2734 return "scroll-forward"; 2735 case KEY_SR: 2736 return "scroll-backward"; 2737 case KEY_NPAGE: 2738 return "page-down"; 2739 case KEY_PPAGE: 2740 return "page-up"; 2741 case KEY_STAB: 2742 return "set-tab"; 2743 case KEY_CTAB: 2744 return "clear-tab"; 2745 case KEY_CATAB: 2746 return "clear-all-tabs"; 2747 case KEY_ENTER: 2748 return "enter"; 2749 case KEY_PRINT: 2750 return "print"; 2751 case KEY_LL: 2752 return "lower-left key"; 2753 case KEY_A1: 2754 return "upper left of keypad"; 2755 case KEY_A3: 2756 return "upper right of keypad"; 2757 case KEY_B2: 2758 return "center of keypad"; 2759 case KEY_C1: 2760 return "lower left of keypad"; 2761 case KEY_C3: 2762 return "lower right of keypad"; 2763 case KEY_BTAB: 2764 return "back-tab key"; 2765 case KEY_BEG: 2766 return "begin key"; 2767 case KEY_CANCEL: 2768 return "cancel key"; 2769 case KEY_CLOSE: 2770 return "close key"; 2771 case KEY_COMMAND: 2772 return "command key"; 2773 case KEY_COPY: 2774 return "copy key"; 2775 case KEY_CREATE: 2776 return "create key"; 2777 case KEY_END: 2778 return "end key"; 2779 case KEY_EXIT: 2780 return "exit key"; 2781 case KEY_FIND: 2782 return "find key"; 2783 case KEY_HELP: 2784 return "help key"; 2785 case KEY_MARK: 2786 return "mark key"; 2787 case KEY_MESSAGE: 2788 return "message key"; 2789 case KEY_MOVE: 2790 return "move key"; 2791 case KEY_NEXT: 2792 return "next key"; 2793 case KEY_OPEN: 2794 return "open key"; 2795 case KEY_OPTIONS: 2796 return "options key"; 2797 case KEY_PREVIOUS: 2798 return "previous key"; 2799 case KEY_REDO: 2800 return "redo key"; 2801 case KEY_REFERENCE: 2802 return "reference key"; 2803 case KEY_REFRESH: 2804 return "refresh key"; 2805 case KEY_REPLACE: 2806 return "replace key"; 2807 case KEY_RESTART: 2808 return "restart key"; 2809 case KEY_RESUME: 2810 return "resume key"; 2811 case KEY_SAVE: 2812 return "save key"; 2813 case KEY_SBEG: 2814 return "shifted begin key"; 2815 case KEY_SCANCEL: 2816 return "shifted cancel key"; 2817 case KEY_SCOMMAND: 2818 return "shifted command key"; 2819 case KEY_SCOPY: 2820 return "shifted copy key"; 2821 case KEY_SCREATE: 2822 return "shifted create key"; 2823 case KEY_SDC: 2824 return "shifted delete-character key"; 2825 case KEY_SDL: 2826 return "shifted delete-line key"; 2827 case KEY_SELECT: 2828 return "select key"; 2829 case KEY_SEND: 2830 return "shifted end key"; 2831 case KEY_SEOL: 2832 return "shifted clear-to-end-of-line key"; 2833 case KEY_SEXIT: 2834 return "shifted exit key"; 2835 case KEY_SFIND: 2836 return "shifted find key"; 2837 case KEY_SHELP: 2838 return "shifted help key"; 2839 case KEY_SHOME: 2840 return "shifted home key"; 2841 case KEY_SIC: 2842 return "shifted insert-character key"; 2843 case KEY_SLEFT: 2844 return "shifted left-arrow key"; 2845 case KEY_SMESSAGE: 2846 return "shifted message key"; 2847 case KEY_SMOVE: 2848 return "shifted move key"; 2849 case KEY_SNEXT: 2850 return "shifted next key"; 2851 case KEY_SOPTIONS: 2852 return "shifted options key"; 2853 case KEY_SPREVIOUS: 2854 return "shifted previous key"; 2855 case KEY_SPRINT: 2856 return "shifted print key"; 2857 case KEY_SREDO: 2858 return "shifted redo key"; 2859 case KEY_SREPLACE: 2860 return "shifted replace key"; 2861 case KEY_SRIGHT: 2862 return "shifted right-arrow key"; 2863 case KEY_SRSUME: 2864 return "shifted resume key"; 2865 case KEY_SSAVE: 2866 return "shifted save key"; 2867 case KEY_SSUSPEND: 2868 return "shifted suspend key"; 2869 case KEY_SUNDO: 2870 return "shifted undo key"; 2871 case KEY_SUSPEND: 2872 return "suspend key"; 2873 case KEY_UNDO: 2874 return "undo key"; 2875 case KEY_MOUSE: 2876 return "Mouse event has occurred"; 2877 case KEY_RESIZE: 2878 return "Terminal resize event"; 2879 #ifdef KEY_EVENT 2880 case KEY_EVENT: 2881 return "We were interrupted by an event"; 2882 #endif 2883 case KEY_RETURN: 2884 return "return"; 2885 case ' ': 2886 return "space"; 2887 case '\t': 2888 return "tab"; 2889 case KEY_ESCAPE: 2890 return "escape"; 2891 default: 2892 if (llvm::isPrint(ch)) 2893 snprintf(g_desc, sizeof(g_desc), "%c", ch); 2894 else 2895 snprintf(g_desc, sizeof(g_desc), "\\x%2.2x", ch); 2896 return g_desc; 2897 } 2898 return nullptr; 2899 } 2900 2901 HelpDialogDelegate::HelpDialogDelegate(const char *text, 2902 KeyHelp *key_help_array) 2903 : m_text(), m_first_visible_line(0) { 2904 if (text && text[0]) { 2905 m_text.SplitIntoLines(text); 2906 m_text.AppendString(""); 2907 } 2908 if (key_help_array) { 2909 for (KeyHelp *key = key_help_array; key->ch; ++key) { 2910 StreamString key_description; 2911 key_description.Printf("%10s - %s", CursesKeyToCString(key->ch), 2912 key->description); 2913 m_text.AppendString(key_description.GetString()); 2914 } 2915 } 2916 } 2917 2918 HelpDialogDelegate::~HelpDialogDelegate() = default; 2919 2920 bool HelpDialogDelegate::WindowDelegateDraw(Window &window, bool force) { 2921 window.Erase(); 2922 const int window_height = window.GetHeight(); 2923 int x = 2; 2924 int y = 1; 2925 const int min_y = y; 2926 const int max_y = window_height - 1 - y; 2927 const size_t num_visible_lines = max_y - min_y + 1; 2928 const size_t num_lines = m_text.GetSize(); 2929 const char *bottom_message; 2930 if (num_lines <= num_visible_lines) 2931 bottom_message = "Press any key to exit"; 2932 else 2933 bottom_message = "Use arrows to scroll, any other key to exit"; 2934 window.DrawTitleBox(window.GetName(), bottom_message); 2935 while (y <= max_y) { 2936 window.MoveCursor(x, y); 2937 window.PutCStringTruncated( 2938 1, m_text.GetStringAtIndex(m_first_visible_line + y - min_y)); 2939 ++y; 2940 } 2941 return true; 2942 } 2943 2944 HandleCharResult HelpDialogDelegate::WindowDelegateHandleChar(Window &window, 2945 int key) { 2946 bool done = false; 2947 const size_t num_lines = m_text.GetSize(); 2948 const size_t num_visible_lines = window.GetHeight() - 2; 2949 2950 if (num_lines <= num_visible_lines) { 2951 done = true; 2952 // If we have all lines visible and don't need scrolling, then any key 2953 // press will cause us to exit 2954 } else { 2955 switch (key) { 2956 case KEY_UP: 2957 if (m_first_visible_line > 0) 2958 --m_first_visible_line; 2959 break; 2960 2961 case KEY_DOWN: 2962 if (m_first_visible_line + num_visible_lines < num_lines) 2963 ++m_first_visible_line; 2964 break; 2965 2966 case KEY_PPAGE: 2967 case ',': 2968 if (m_first_visible_line > 0) { 2969 if (static_cast<size_t>(m_first_visible_line) >= num_visible_lines) 2970 m_first_visible_line -= num_visible_lines; 2971 else 2972 m_first_visible_line = 0; 2973 } 2974 break; 2975 2976 case KEY_NPAGE: 2977 case '.': 2978 if (m_first_visible_line + num_visible_lines < num_lines) { 2979 m_first_visible_line += num_visible_lines; 2980 if (static_cast<size_t>(m_first_visible_line) > num_lines) 2981 m_first_visible_line = num_lines - num_visible_lines; 2982 } 2983 break; 2984 2985 default: 2986 done = true; 2987 break; 2988 } 2989 } 2990 if (done) 2991 window.GetParent()->RemoveSubWindow(&window); 2992 return eKeyHandled; 2993 } 2994 2995 class ApplicationDelegate : public WindowDelegate, public MenuDelegate { 2996 public: 2997 enum { 2998 eMenuID_LLDB = 1, 2999 eMenuID_LLDBAbout, 3000 eMenuID_LLDBExit, 3001 3002 eMenuID_Target, 3003 eMenuID_TargetCreate, 3004 eMenuID_TargetDelete, 3005 3006 eMenuID_Process, 3007 eMenuID_ProcessAttach, 3008 eMenuID_ProcessDetachResume, 3009 eMenuID_ProcessDetachSuspended, 3010 eMenuID_ProcessLaunch, 3011 eMenuID_ProcessContinue, 3012 eMenuID_ProcessHalt, 3013 eMenuID_ProcessKill, 3014 3015 eMenuID_Thread, 3016 eMenuID_ThreadStepIn, 3017 eMenuID_ThreadStepOver, 3018 eMenuID_ThreadStepOut, 3019 3020 eMenuID_View, 3021 eMenuID_ViewBacktrace, 3022 eMenuID_ViewRegisters, 3023 eMenuID_ViewSource, 3024 eMenuID_ViewVariables, 3025 3026 eMenuID_Help, 3027 eMenuID_HelpGUIHelp 3028 }; 3029 3030 ApplicationDelegate(Application &app, Debugger &debugger) 3031 : WindowDelegate(), MenuDelegate(), m_app(app), m_debugger(debugger) {} 3032 3033 ~ApplicationDelegate() override = default; 3034 3035 bool WindowDelegateDraw(Window &window, bool force) override { 3036 return false; // Drawing not handled, let standard window drawing happen 3037 } 3038 3039 HandleCharResult WindowDelegateHandleChar(Window &window, int key) override { 3040 switch (key) { 3041 case '\t': 3042 window.SelectNextWindowAsActive(); 3043 return eKeyHandled; 3044 3045 case KEY_BTAB: 3046 window.SelectPreviousWindowAsActive(); 3047 return eKeyHandled; 3048 3049 case 'h': 3050 window.CreateHelpSubwindow(); 3051 return eKeyHandled; 3052 3053 case KEY_ESCAPE: 3054 return eQuitApplication; 3055 3056 default: 3057 break; 3058 } 3059 return eKeyNotHandled; 3060 } 3061 3062 const char *WindowDelegateGetHelpText() override { 3063 return "Welcome to the LLDB curses GUI.\n\n" 3064 "Press the TAB key to change the selected view.\n" 3065 "Each view has its own keyboard shortcuts, press 'h' to open a " 3066 "dialog to display them.\n\n" 3067 "Common key bindings for all views:"; 3068 } 3069 3070 KeyHelp *WindowDelegateGetKeyHelp() override { 3071 static curses::KeyHelp g_source_view_key_help[] = { 3072 {'\t', "Select next view"}, 3073 {KEY_BTAB, "Select previous view"}, 3074 {'h', "Show help dialog with view specific key bindings"}, 3075 {',', "Page up"}, 3076 {'.', "Page down"}, 3077 {KEY_UP, "Select previous"}, 3078 {KEY_DOWN, "Select next"}, 3079 {KEY_LEFT, "Unexpand or select parent"}, 3080 {KEY_RIGHT, "Expand"}, 3081 {KEY_PPAGE, "Page up"}, 3082 {KEY_NPAGE, "Page down"}, 3083 {'\0', nullptr}}; 3084 return g_source_view_key_help; 3085 } 3086 3087 MenuActionResult MenuDelegateAction(Menu &menu) override { 3088 switch (menu.GetIdentifier()) { 3089 case eMenuID_ThreadStepIn: { 3090 ExecutionContext exe_ctx = 3091 m_debugger.GetCommandInterpreter().GetExecutionContext(); 3092 if (exe_ctx.HasThreadScope()) { 3093 Process *process = exe_ctx.GetProcessPtr(); 3094 if (process && process->IsAlive() && 3095 StateIsStoppedState(process->GetState(), true)) 3096 exe_ctx.GetThreadRef().StepIn(true); 3097 } 3098 } 3099 return MenuActionResult::Handled; 3100 3101 case eMenuID_ThreadStepOut: { 3102 ExecutionContext exe_ctx = 3103 m_debugger.GetCommandInterpreter().GetExecutionContext(); 3104 if (exe_ctx.HasThreadScope()) { 3105 Process *process = exe_ctx.GetProcessPtr(); 3106 if (process && process->IsAlive() && 3107 StateIsStoppedState(process->GetState(), true)) 3108 exe_ctx.GetThreadRef().StepOut(); 3109 } 3110 } 3111 return MenuActionResult::Handled; 3112 3113 case eMenuID_ThreadStepOver: { 3114 ExecutionContext exe_ctx = 3115 m_debugger.GetCommandInterpreter().GetExecutionContext(); 3116 if (exe_ctx.HasThreadScope()) { 3117 Process *process = exe_ctx.GetProcessPtr(); 3118 if (process && process->IsAlive() && 3119 StateIsStoppedState(process->GetState(), true)) 3120 exe_ctx.GetThreadRef().StepOver(true); 3121 } 3122 } 3123 return MenuActionResult::Handled; 3124 3125 case eMenuID_ProcessContinue: { 3126 ExecutionContext exe_ctx = 3127 m_debugger.GetCommandInterpreter().GetExecutionContext(); 3128 if (exe_ctx.HasProcessScope()) { 3129 Process *process = exe_ctx.GetProcessPtr(); 3130 if (process && process->IsAlive() && 3131 StateIsStoppedState(process->GetState(), true)) 3132 process->Resume(); 3133 } 3134 } 3135 return MenuActionResult::Handled; 3136 3137 case eMenuID_ProcessKill: { 3138 ExecutionContext exe_ctx = 3139 m_debugger.GetCommandInterpreter().GetExecutionContext(); 3140 if (exe_ctx.HasProcessScope()) { 3141 Process *process = exe_ctx.GetProcessPtr(); 3142 if (process && process->IsAlive()) 3143 process->Destroy(false); 3144 } 3145 } 3146 return MenuActionResult::Handled; 3147 3148 case eMenuID_ProcessHalt: { 3149 ExecutionContext exe_ctx = 3150 m_debugger.GetCommandInterpreter().GetExecutionContext(); 3151 if (exe_ctx.HasProcessScope()) { 3152 Process *process = exe_ctx.GetProcessPtr(); 3153 if (process && process->IsAlive()) 3154 process->Halt(); 3155 } 3156 } 3157 return MenuActionResult::Handled; 3158 3159 case eMenuID_ProcessDetachResume: 3160 case eMenuID_ProcessDetachSuspended: { 3161 ExecutionContext exe_ctx = 3162 m_debugger.GetCommandInterpreter().GetExecutionContext(); 3163 if (exe_ctx.HasProcessScope()) { 3164 Process *process = exe_ctx.GetProcessPtr(); 3165 if (process && process->IsAlive()) 3166 process->Detach(menu.GetIdentifier() == 3167 eMenuID_ProcessDetachSuspended); 3168 } 3169 } 3170 return MenuActionResult::Handled; 3171 3172 case eMenuID_Process: { 3173 // Populate the menu with all of the threads if the process is stopped 3174 // when the Process menu gets selected and is about to display its 3175 // submenu. 3176 Menus &submenus = menu.GetSubmenus(); 3177 ExecutionContext exe_ctx = 3178 m_debugger.GetCommandInterpreter().GetExecutionContext(); 3179 Process *process = exe_ctx.GetProcessPtr(); 3180 if (process && process->IsAlive() && 3181 StateIsStoppedState(process->GetState(), true)) { 3182 if (submenus.size() == 7) 3183 menu.AddSubmenu(MenuSP(new Menu(Menu::Type::Separator))); 3184 else if (submenus.size() > 8) 3185 submenus.erase(submenus.begin() + 8, submenus.end()); 3186 3187 ThreadList &threads = process->GetThreadList(); 3188 std::lock_guard<std::recursive_mutex> guard(threads.GetMutex()); 3189 size_t num_threads = threads.GetSize(); 3190 for (size_t i = 0; i < num_threads; ++i) { 3191 ThreadSP thread_sp = threads.GetThreadAtIndex(i); 3192 char menu_char = '\0'; 3193 if (i < 9) 3194 menu_char = '1' + i; 3195 StreamString thread_menu_title; 3196 thread_menu_title.Printf("Thread %u", thread_sp->GetIndexID()); 3197 const char *thread_name = thread_sp->GetName(); 3198 if (thread_name && thread_name[0]) 3199 thread_menu_title.Printf(" %s", thread_name); 3200 else { 3201 const char *queue_name = thread_sp->GetQueueName(); 3202 if (queue_name && queue_name[0]) 3203 thread_menu_title.Printf(" %s", queue_name); 3204 } 3205 menu.AddSubmenu( 3206 MenuSP(new Menu(thread_menu_title.GetString().str().c_str(), 3207 nullptr, menu_char, thread_sp->GetID()))); 3208 } 3209 } else if (submenus.size() > 7) { 3210 // Remove the separator and any other thread submenu items that were 3211 // previously added 3212 submenus.erase(submenus.begin() + 7, submenus.end()); 3213 } 3214 // Since we are adding and removing items we need to recalculate the name 3215 // lengths 3216 menu.RecalculateNameLengths(); 3217 } 3218 return MenuActionResult::Handled; 3219 3220 case eMenuID_ViewVariables: { 3221 WindowSP main_window_sp = m_app.GetMainWindow(); 3222 WindowSP source_window_sp = main_window_sp->FindSubWindow("Source"); 3223 WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables"); 3224 WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers"); 3225 const Rect source_bounds = source_window_sp->GetBounds(); 3226 3227 if (variables_window_sp) { 3228 const Rect variables_bounds = variables_window_sp->GetBounds(); 3229 3230 main_window_sp->RemoveSubWindow(variables_window_sp.get()); 3231 3232 if (registers_window_sp) { 3233 // We have a registers window, so give all the area back to the 3234 // registers window 3235 Rect registers_bounds = variables_bounds; 3236 registers_bounds.size.width = source_bounds.size.width; 3237 registers_window_sp->SetBounds(registers_bounds); 3238 } else { 3239 // We have no registers window showing so give the bottom area back 3240 // to the source view 3241 source_window_sp->Resize(source_bounds.size.width, 3242 source_bounds.size.height + 3243 variables_bounds.size.height); 3244 } 3245 } else { 3246 Rect new_variables_rect; 3247 if (registers_window_sp) { 3248 // We have a registers window so split the area of the registers 3249 // window into two columns where the left hand side will be the 3250 // variables and the right hand side will be the registers 3251 const Rect variables_bounds = registers_window_sp->GetBounds(); 3252 Rect new_registers_rect; 3253 variables_bounds.VerticalSplitPercentage(0.50, new_variables_rect, 3254 new_registers_rect); 3255 registers_window_sp->SetBounds(new_registers_rect); 3256 } else { 3257 // No registers window, grab the bottom part of the source window 3258 Rect new_source_rect; 3259 source_bounds.HorizontalSplitPercentage(0.70, new_source_rect, 3260 new_variables_rect); 3261 source_window_sp->SetBounds(new_source_rect); 3262 } 3263 WindowSP new_window_sp = main_window_sp->CreateSubWindow( 3264 "Variables", new_variables_rect, false); 3265 new_window_sp->SetDelegate( 3266 WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger))); 3267 } 3268 touchwin(stdscr); 3269 } 3270 return MenuActionResult::Handled; 3271 3272 case eMenuID_ViewRegisters: { 3273 WindowSP main_window_sp = m_app.GetMainWindow(); 3274 WindowSP source_window_sp = main_window_sp->FindSubWindow("Source"); 3275 WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables"); 3276 WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers"); 3277 const Rect source_bounds = source_window_sp->GetBounds(); 3278 3279 if (registers_window_sp) { 3280 if (variables_window_sp) { 3281 const Rect variables_bounds = variables_window_sp->GetBounds(); 3282 3283 // We have a variables window, so give all the area back to the 3284 // variables window 3285 variables_window_sp->Resize(variables_bounds.size.width + 3286 registers_window_sp->GetWidth(), 3287 variables_bounds.size.height); 3288 } else { 3289 // We have no variables window showing so give the bottom area back 3290 // to the source view 3291 source_window_sp->Resize(source_bounds.size.width, 3292 source_bounds.size.height + 3293 registers_window_sp->GetHeight()); 3294 } 3295 main_window_sp->RemoveSubWindow(registers_window_sp.get()); 3296 } else { 3297 Rect new_regs_rect; 3298 if (variables_window_sp) { 3299 // We have a variables window, split it into two columns where the 3300 // left hand side will be the variables and the right hand side will 3301 // be the registers 3302 const Rect variables_bounds = variables_window_sp->GetBounds(); 3303 Rect new_vars_rect; 3304 variables_bounds.VerticalSplitPercentage(0.50, new_vars_rect, 3305 new_regs_rect); 3306 variables_window_sp->SetBounds(new_vars_rect); 3307 } else { 3308 // No variables window, grab the bottom part of the source window 3309 Rect new_source_rect; 3310 source_bounds.HorizontalSplitPercentage(0.70, new_source_rect, 3311 new_regs_rect); 3312 source_window_sp->SetBounds(new_source_rect); 3313 } 3314 WindowSP new_window_sp = 3315 main_window_sp->CreateSubWindow("Registers", new_regs_rect, false); 3316 new_window_sp->SetDelegate( 3317 WindowDelegateSP(new RegistersWindowDelegate(m_debugger))); 3318 } 3319 touchwin(stdscr); 3320 } 3321 return MenuActionResult::Handled; 3322 3323 case eMenuID_HelpGUIHelp: 3324 m_app.GetMainWindow()->CreateHelpSubwindow(); 3325 return MenuActionResult::Handled; 3326 3327 default: 3328 break; 3329 } 3330 3331 return MenuActionResult::NotHandled; 3332 } 3333 3334 protected: 3335 Application &m_app; 3336 Debugger &m_debugger; 3337 }; 3338 3339 class StatusBarWindowDelegate : public WindowDelegate { 3340 public: 3341 StatusBarWindowDelegate(Debugger &debugger) : m_debugger(debugger) { 3342 FormatEntity::Parse("Thread: ${thread.id%tid}", m_format); 3343 } 3344 3345 ~StatusBarWindowDelegate() override = default; 3346 3347 bool WindowDelegateDraw(Window &window, bool force) override { 3348 ExecutionContext exe_ctx = 3349 m_debugger.GetCommandInterpreter().GetExecutionContext(); 3350 Process *process = exe_ctx.GetProcessPtr(); 3351 Thread *thread = exe_ctx.GetThreadPtr(); 3352 StackFrame *frame = exe_ctx.GetFramePtr(); 3353 window.Erase(); 3354 window.SetBackground(BlackOnWhite); 3355 window.MoveCursor(0, 0); 3356 if (process) { 3357 const StateType state = process->GetState(); 3358 window.Printf("Process: %5" PRIu64 " %10s", process->GetID(), 3359 StateAsCString(state)); 3360 3361 if (StateIsStoppedState(state, true)) { 3362 StreamString strm; 3363 if (thread && FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, 3364 nullptr, nullptr, false, false)) { 3365 window.MoveCursor(40, 0); 3366 window.PutCStringTruncated(1, strm.GetString().str().c_str()); 3367 } 3368 3369 window.MoveCursor(60, 0); 3370 if (frame) 3371 window.Printf("Frame: %3u PC = 0x%16.16" PRIx64, 3372 frame->GetFrameIndex(), 3373 frame->GetFrameCodeAddress().GetOpcodeLoadAddress( 3374 exe_ctx.GetTargetPtr())); 3375 } else if (state == eStateExited) { 3376 const char *exit_desc = process->GetExitDescription(); 3377 const int exit_status = process->GetExitStatus(); 3378 if (exit_desc && exit_desc[0]) 3379 window.Printf(" with status = %i (%s)", exit_status, exit_desc); 3380 else 3381 window.Printf(" with status = %i", exit_status); 3382 } 3383 } 3384 return true; 3385 } 3386 3387 protected: 3388 Debugger &m_debugger; 3389 FormatEntity::Entry m_format; 3390 }; 3391 3392 class SourceFileWindowDelegate : public WindowDelegate { 3393 public: 3394 SourceFileWindowDelegate(Debugger &debugger) 3395 : WindowDelegate(), m_debugger(debugger), m_sc(), m_file_sp(), 3396 m_disassembly_scope(nullptr), m_disassembly_sp(), m_disassembly_range(), 3397 m_title(), m_line_width(4), m_selected_line(0), m_pc_line(0), 3398 m_stop_id(0), m_frame_idx(UINT32_MAX), m_first_visible_line(0), 3399 m_first_visible_column(0), m_min_x(0), m_min_y(0), m_max_x(0), 3400 m_max_y(0) {} 3401 3402 ~SourceFileWindowDelegate() override = default; 3403 3404 void Update(const SymbolContext &sc) { m_sc = sc; } 3405 3406 uint32_t NumVisibleLines() const { return m_max_y - m_min_y; } 3407 3408 const char *WindowDelegateGetHelpText() override { 3409 return "Source/Disassembly window keyboard shortcuts:"; 3410 } 3411 3412 KeyHelp *WindowDelegateGetKeyHelp() override { 3413 static curses::KeyHelp g_source_view_key_help[] = { 3414 {KEY_RETURN, "Run to selected line with one shot breakpoint"}, 3415 {KEY_UP, "Select previous source line"}, 3416 {KEY_DOWN, "Select next source line"}, 3417 {KEY_LEFT, "Scroll to the left"}, 3418 {KEY_RIGHT, "Scroll to the right"}, 3419 {KEY_PPAGE, "Page up"}, 3420 {KEY_NPAGE, "Page down"}, 3421 {'b', "Set breakpoint on selected source/disassembly line"}, 3422 {'c', "Continue process"}, 3423 {'D', "Detach with process suspended"}, 3424 {'h', "Show help dialog"}, 3425 {'n', "Step over (source line)"}, 3426 {'N', "Step over (single instruction)"}, 3427 {'f', "Step out (finish)"}, 3428 {'s', "Step in (source line)"}, 3429 {'S', "Step in (single instruction)"}, 3430 {'u', "Frame up"}, 3431 {'d', "Frame down"}, 3432 {',', "Page up"}, 3433 {'.', "Page down"}, 3434 {'\0', nullptr}}; 3435 return g_source_view_key_help; 3436 } 3437 3438 bool WindowDelegateDraw(Window &window, bool force) override { 3439 ExecutionContext exe_ctx = 3440 m_debugger.GetCommandInterpreter().GetExecutionContext(); 3441 Process *process = exe_ctx.GetProcessPtr(); 3442 Thread *thread = nullptr; 3443 3444 bool update_location = false; 3445 if (process) { 3446 StateType state = process->GetState(); 3447 if (StateIsStoppedState(state, true)) { 3448 // We are stopped, so it is ok to 3449 update_location = true; 3450 } 3451 } 3452 3453 m_min_x = 1; 3454 m_min_y = 2; 3455 m_max_x = window.GetMaxX() - 1; 3456 m_max_y = window.GetMaxY() - 1; 3457 3458 const uint32_t num_visible_lines = NumVisibleLines(); 3459 StackFrameSP frame_sp; 3460 bool set_selected_line_to_pc = false; 3461 3462 if (update_location) { 3463 const bool process_alive = process ? process->IsAlive() : false; 3464 bool thread_changed = false; 3465 if (process_alive) { 3466 thread = exe_ctx.GetThreadPtr(); 3467 if (thread) { 3468 frame_sp = thread->GetSelectedFrame(); 3469 auto tid = thread->GetID(); 3470 thread_changed = tid != m_tid; 3471 m_tid = tid; 3472 } else { 3473 if (m_tid != LLDB_INVALID_THREAD_ID) { 3474 thread_changed = true; 3475 m_tid = LLDB_INVALID_THREAD_ID; 3476 } 3477 } 3478 } 3479 const uint32_t stop_id = process ? process->GetStopID() : 0; 3480 const bool stop_id_changed = stop_id != m_stop_id; 3481 bool frame_changed = false; 3482 m_stop_id = stop_id; 3483 m_title.Clear(); 3484 if (frame_sp) { 3485 m_sc = frame_sp->GetSymbolContext(eSymbolContextEverything); 3486 if (m_sc.module_sp) { 3487 m_title.Printf( 3488 "%s", m_sc.module_sp->GetFileSpec().GetFilename().GetCString()); 3489 ConstString func_name = m_sc.GetFunctionName(); 3490 if (func_name) 3491 m_title.Printf("`%s", func_name.GetCString()); 3492 } 3493 const uint32_t frame_idx = frame_sp->GetFrameIndex(); 3494 frame_changed = frame_idx != m_frame_idx; 3495 m_frame_idx = frame_idx; 3496 } else { 3497 m_sc.Clear(true); 3498 frame_changed = m_frame_idx != UINT32_MAX; 3499 m_frame_idx = UINT32_MAX; 3500 } 3501 3502 const bool context_changed = 3503 thread_changed || frame_changed || stop_id_changed; 3504 3505 if (process_alive) { 3506 if (m_sc.line_entry.IsValid()) { 3507 m_pc_line = m_sc.line_entry.line; 3508 if (m_pc_line != UINT32_MAX) 3509 --m_pc_line; // Convert to zero based line number... 3510 // Update the selected line if the stop ID changed... 3511 if (context_changed) 3512 m_selected_line = m_pc_line; 3513 3514 if (m_file_sp && m_file_sp->GetFileSpec() == m_sc.line_entry.file) { 3515 // Same file, nothing to do, we should either have the lines or not 3516 // (source file missing) 3517 if (m_selected_line >= static_cast<size_t>(m_first_visible_line)) { 3518 if (m_selected_line >= m_first_visible_line + num_visible_lines) 3519 m_first_visible_line = m_selected_line - 10; 3520 } else { 3521 if (m_selected_line > 10) 3522 m_first_visible_line = m_selected_line - 10; 3523 else 3524 m_first_visible_line = 0; 3525 } 3526 } else { 3527 // File changed, set selected line to the line with the PC 3528 m_selected_line = m_pc_line; 3529 m_file_sp = 3530 m_debugger.GetSourceManager().GetFile(m_sc.line_entry.file); 3531 if (m_file_sp) { 3532 const size_t num_lines = m_file_sp->GetNumLines(); 3533 m_line_width = 1; 3534 for (size_t n = num_lines; n >= 10; n = n / 10) 3535 ++m_line_width; 3536 3537 if (num_lines < num_visible_lines || 3538 m_selected_line < num_visible_lines) 3539 m_first_visible_line = 0; 3540 else 3541 m_first_visible_line = m_selected_line - 10; 3542 } 3543 } 3544 } else { 3545 m_file_sp.reset(); 3546 } 3547 3548 if (!m_file_sp || m_file_sp->GetNumLines() == 0) { 3549 // Show disassembly 3550 bool prefer_file_cache = false; 3551 if (m_sc.function) { 3552 if (m_disassembly_scope != m_sc.function) { 3553 m_disassembly_scope = m_sc.function; 3554 m_disassembly_sp = m_sc.function->GetInstructions( 3555 exe_ctx, nullptr, prefer_file_cache); 3556 if (m_disassembly_sp) { 3557 set_selected_line_to_pc = true; 3558 m_disassembly_range = m_sc.function->GetAddressRange(); 3559 } else { 3560 m_disassembly_range.Clear(); 3561 } 3562 } else { 3563 set_selected_line_to_pc = context_changed; 3564 } 3565 } else if (m_sc.symbol) { 3566 if (m_disassembly_scope != m_sc.symbol) { 3567 m_disassembly_scope = m_sc.symbol; 3568 m_disassembly_sp = m_sc.symbol->GetInstructions( 3569 exe_ctx, nullptr, prefer_file_cache); 3570 if (m_disassembly_sp) { 3571 set_selected_line_to_pc = true; 3572 m_disassembly_range.GetBaseAddress() = 3573 m_sc.symbol->GetAddress(); 3574 m_disassembly_range.SetByteSize(m_sc.symbol->GetByteSize()); 3575 } else { 3576 m_disassembly_range.Clear(); 3577 } 3578 } else { 3579 set_selected_line_to_pc = context_changed; 3580 } 3581 } 3582 } 3583 } else { 3584 m_pc_line = UINT32_MAX; 3585 } 3586 } 3587 3588 const int window_width = window.GetWidth(); 3589 window.Erase(); 3590 window.DrawTitleBox("Sources"); 3591 if (!m_title.GetString().empty()) { 3592 window.AttributeOn(A_REVERSE); 3593 window.MoveCursor(1, 1); 3594 window.PutChar(' '); 3595 window.PutCStringTruncated(1, m_title.GetString().str().c_str()); 3596 int x = window.GetCursorX(); 3597 if (x < window_width - 1) { 3598 window.Printf("%*s", window_width - x - 1, ""); 3599 } 3600 window.AttributeOff(A_REVERSE); 3601 } 3602 3603 Target *target = exe_ctx.GetTargetPtr(); 3604 const size_t num_source_lines = GetNumSourceLines(); 3605 if (num_source_lines > 0) { 3606 // Display source 3607 BreakpointLines bp_lines; 3608 if (target) { 3609 BreakpointList &bp_list = target->GetBreakpointList(); 3610 const size_t num_bps = bp_list.GetSize(); 3611 for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) { 3612 BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx); 3613 const size_t num_bps_locs = bp_sp->GetNumLocations(); 3614 for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) { 3615 BreakpointLocationSP bp_loc_sp = 3616 bp_sp->GetLocationAtIndex(bp_loc_idx); 3617 LineEntry bp_loc_line_entry; 3618 if (bp_loc_sp->GetAddress().CalculateSymbolContextLineEntry( 3619 bp_loc_line_entry)) { 3620 if (m_file_sp->GetFileSpec() == bp_loc_line_entry.file) { 3621 bp_lines.insert(bp_loc_line_entry.line); 3622 } 3623 } 3624 } 3625 } 3626 } 3627 3628 const attr_t selected_highlight_attr = A_REVERSE; 3629 const attr_t pc_highlight_attr = COLOR_PAIR(BlackOnBlue); 3630 3631 for (size_t i = 0; i < num_visible_lines; ++i) { 3632 const uint32_t curr_line = m_first_visible_line + i; 3633 if (curr_line < num_source_lines) { 3634 const int line_y = m_min_y + i; 3635 window.MoveCursor(1, line_y); 3636 const bool is_pc_line = curr_line == m_pc_line; 3637 const bool line_is_selected = m_selected_line == curr_line; 3638 // Highlight the line as the PC line first, then if the selected line 3639 // isn't the same as the PC line, highlight it differently 3640 attr_t highlight_attr = 0; 3641 attr_t bp_attr = 0; 3642 if (is_pc_line) 3643 highlight_attr = pc_highlight_attr; 3644 else if (line_is_selected) 3645 highlight_attr = selected_highlight_attr; 3646 3647 if (bp_lines.find(curr_line + 1) != bp_lines.end()) 3648 bp_attr = COLOR_PAIR(BlackOnWhite); 3649 3650 if (bp_attr) 3651 window.AttributeOn(bp_attr); 3652 3653 window.Printf(" %*u ", m_line_width, curr_line + 1); 3654 3655 if (bp_attr) 3656 window.AttributeOff(bp_attr); 3657 3658 window.PutChar(ACS_VLINE); 3659 // Mark the line with the PC with a diamond 3660 if (is_pc_line) 3661 window.PutChar(ACS_DIAMOND); 3662 else 3663 window.PutChar(' '); 3664 3665 if (highlight_attr) 3666 window.AttributeOn(highlight_attr); 3667 3668 StreamString lineStream; 3669 m_file_sp->DisplaySourceLines(curr_line + 1, {}, 0, 0, &lineStream); 3670 StringRef line = lineStream.GetString(); 3671 if (line.endswith("\n")) 3672 line = line.drop_back(); 3673 bool wasWritten = window.OutputColoredStringTruncated( 3674 1, line, m_first_visible_column, line_is_selected); 3675 if (line_is_selected && !wasWritten) { 3676 // Draw an empty space to show the selected line if empty, 3677 // or draw '<' if nothing is visible because of scrolling too much 3678 // to the right. 3679 window.PutCStringTruncated( 3680 1, line.empty() && m_first_visible_column == 0 ? " " : "<"); 3681 } 3682 3683 if (is_pc_line && frame_sp && 3684 frame_sp->GetConcreteFrameIndex() == 0) { 3685 StopInfoSP stop_info_sp; 3686 if (thread) 3687 stop_info_sp = thread->GetStopInfo(); 3688 if (stop_info_sp) { 3689 const char *stop_description = stop_info_sp->GetDescription(); 3690 if (stop_description && stop_description[0]) { 3691 size_t stop_description_len = strlen(stop_description); 3692 int desc_x = window_width - stop_description_len - 16; 3693 if (desc_x - window.GetCursorX() > 0) 3694 window.Printf("%*s", desc_x - window.GetCursorX(), ""); 3695 window.MoveCursor(window_width - stop_description_len - 16, 3696 line_y); 3697 const attr_t stop_reason_attr = COLOR_PAIR(WhiteOnBlue); 3698 window.AttributeOn(stop_reason_attr); 3699 window.PrintfTruncated(1, " <<< Thread %u: %s ", 3700 thread->GetIndexID(), stop_description); 3701 window.AttributeOff(stop_reason_attr); 3702 } 3703 } else { 3704 window.Printf("%*s", window_width - window.GetCursorX() - 1, ""); 3705 } 3706 } 3707 if (highlight_attr) 3708 window.AttributeOff(highlight_attr); 3709 } else { 3710 break; 3711 } 3712 } 3713 } else { 3714 size_t num_disassembly_lines = GetNumDisassemblyLines(); 3715 if (num_disassembly_lines > 0) { 3716 // Display disassembly 3717 BreakpointAddrs bp_file_addrs; 3718 Target *target = exe_ctx.GetTargetPtr(); 3719 if (target) { 3720 BreakpointList &bp_list = target->GetBreakpointList(); 3721 const size_t num_bps = bp_list.GetSize(); 3722 for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) { 3723 BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx); 3724 const size_t num_bps_locs = bp_sp->GetNumLocations(); 3725 for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; 3726 ++bp_loc_idx) { 3727 BreakpointLocationSP bp_loc_sp = 3728 bp_sp->GetLocationAtIndex(bp_loc_idx); 3729 LineEntry bp_loc_line_entry; 3730 const lldb::addr_t file_addr = 3731 bp_loc_sp->GetAddress().GetFileAddress(); 3732 if (file_addr != LLDB_INVALID_ADDRESS) { 3733 if (m_disassembly_range.ContainsFileAddress(file_addr)) 3734 bp_file_addrs.insert(file_addr); 3735 } 3736 } 3737 } 3738 } 3739 3740 const attr_t selected_highlight_attr = A_REVERSE; 3741 const attr_t pc_highlight_attr = COLOR_PAIR(WhiteOnBlue); 3742 3743 StreamString strm; 3744 3745 InstructionList &insts = m_disassembly_sp->GetInstructionList(); 3746 Address pc_address; 3747 3748 if (frame_sp) 3749 pc_address = frame_sp->GetFrameCodeAddress(); 3750 const uint32_t pc_idx = 3751 pc_address.IsValid() 3752 ? insts.GetIndexOfInstructionAtAddress(pc_address) 3753 : UINT32_MAX; 3754 if (set_selected_line_to_pc) { 3755 m_selected_line = pc_idx; 3756 } 3757 3758 const uint32_t non_visible_pc_offset = (num_visible_lines / 5); 3759 if (static_cast<size_t>(m_first_visible_line) >= num_disassembly_lines) 3760 m_first_visible_line = 0; 3761 3762 if (pc_idx < num_disassembly_lines) { 3763 if (pc_idx < static_cast<uint32_t>(m_first_visible_line) || 3764 pc_idx >= m_first_visible_line + num_visible_lines) 3765 m_first_visible_line = pc_idx - non_visible_pc_offset; 3766 } 3767 3768 for (size_t i = 0; i < num_visible_lines; ++i) { 3769 const uint32_t inst_idx = m_first_visible_line + i; 3770 Instruction *inst = insts.GetInstructionAtIndex(inst_idx).get(); 3771 if (!inst) 3772 break; 3773 3774 const int line_y = m_min_y + i; 3775 window.MoveCursor(1, line_y); 3776 const bool is_pc_line = frame_sp && inst_idx == pc_idx; 3777 const bool line_is_selected = m_selected_line == inst_idx; 3778 // Highlight the line as the PC line first, then if the selected line 3779 // isn't the same as the PC line, highlight it differently 3780 attr_t highlight_attr = 0; 3781 attr_t bp_attr = 0; 3782 if (is_pc_line) 3783 highlight_attr = pc_highlight_attr; 3784 else if (line_is_selected) 3785 highlight_attr = selected_highlight_attr; 3786 3787 if (bp_file_addrs.find(inst->GetAddress().GetFileAddress()) != 3788 bp_file_addrs.end()) 3789 bp_attr = COLOR_PAIR(BlackOnWhite); 3790 3791 if (bp_attr) 3792 window.AttributeOn(bp_attr); 3793 3794 window.Printf(" 0x%16.16llx ", 3795 static_cast<unsigned long long>( 3796 inst->GetAddress().GetLoadAddress(target))); 3797 3798 if (bp_attr) 3799 window.AttributeOff(bp_attr); 3800 3801 window.PutChar(ACS_VLINE); 3802 // Mark the line with the PC with a diamond 3803 if (is_pc_line) 3804 window.PutChar(ACS_DIAMOND); 3805 else 3806 window.PutChar(' '); 3807 3808 if (highlight_attr) 3809 window.AttributeOn(highlight_attr); 3810 3811 const char *mnemonic = inst->GetMnemonic(&exe_ctx); 3812 const char *operands = inst->GetOperands(&exe_ctx); 3813 const char *comment = inst->GetComment(&exe_ctx); 3814 3815 if (mnemonic != nullptr && mnemonic[0] == '\0') 3816 mnemonic = nullptr; 3817 if (operands != nullptr && operands[0] == '\0') 3818 operands = nullptr; 3819 if (comment != nullptr && comment[0] == '\0') 3820 comment = nullptr; 3821 3822 strm.Clear(); 3823 3824 if (mnemonic != nullptr && operands != nullptr && comment != nullptr) 3825 strm.Printf("%-8s %-25s ; %s", mnemonic, operands, comment); 3826 else if (mnemonic != nullptr && operands != nullptr) 3827 strm.Printf("%-8s %s", mnemonic, operands); 3828 else if (mnemonic != nullptr) 3829 strm.Printf("%s", mnemonic); 3830 3831 int right_pad = 1; 3832 window.PutCStringTruncated( 3833 right_pad, 3834 strm.GetString().substr(m_first_visible_column).data()); 3835 3836 if (is_pc_line && frame_sp && 3837 frame_sp->GetConcreteFrameIndex() == 0) { 3838 StopInfoSP stop_info_sp; 3839 if (thread) 3840 stop_info_sp = thread->GetStopInfo(); 3841 if (stop_info_sp) { 3842 const char *stop_description = stop_info_sp->GetDescription(); 3843 if (stop_description && stop_description[0]) { 3844 size_t stop_description_len = strlen(stop_description); 3845 int desc_x = window_width - stop_description_len - 16; 3846 if (desc_x - window.GetCursorX() > 0) 3847 window.Printf("%*s", desc_x - window.GetCursorX(), ""); 3848 window.MoveCursor(window_width - stop_description_len - 15, 3849 line_y); 3850 window.PrintfTruncated(1, "<<< Thread %u: %s ", 3851 thread->GetIndexID(), stop_description); 3852 } 3853 } else { 3854 window.Printf("%*s", window_width - window.GetCursorX() - 1, ""); 3855 } 3856 } 3857 if (highlight_attr) 3858 window.AttributeOff(highlight_attr); 3859 } 3860 } 3861 } 3862 return true; // Drawing handled 3863 } 3864 3865 size_t GetNumLines() { 3866 size_t num_lines = GetNumSourceLines(); 3867 if (num_lines == 0) 3868 num_lines = GetNumDisassemblyLines(); 3869 return num_lines; 3870 } 3871 3872 size_t GetNumSourceLines() const { 3873 if (m_file_sp) 3874 return m_file_sp->GetNumLines(); 3875 return 0; 3876 } 3877 3878 size_t GetNumDisassemblyLines() const { 3879 if (m_disassembly_sp) 3880 return m_disassembly_sp->GetInstructionList().GetSize(); 3881 return 0; 3882 } 3883 3884 HandleCharResult WindowDelegateHandleChar(Window &window, int c) override { 3885 const uint32_t num_visible_lines = NumVisibleLines(); 3886 const size_t num_lines = GetNumLines(); 3887 3888 switch (c) { 3889 case ',': 3890 case KEY_PPAGE: 3891 // Page up key 3892 if (static_cast<uint32_t>(m_first_visible_line) > num_visible_lines) 3893 m_first_visible_line -= num_visible_lines; 3894 else 3895 m_first_visible_line = 0; 3896 m_selected_line = m_first_visible_line; 3897 return eKeyHandled; 3898 3899 case '.': 3900 case KEY_NPAGE: 3901 // Page down key 3902 { 3903 if (m_first_visible_line + num_visible_lines < num_lines) 3904 m_first_visible_line += num_visible_lines; 3905 else if (num_lines < num_visible_lines) 3906 m_first_visible_line = 0; 3907 else 3908 m_first_visible_line = num_lines - num_visible_lines; 3909 m_selected_line = m_first_visible_line; 3910 } 3911 return eKeyHandled; 3912 3913 case KEY_UP: 3914 if (m_selected_line > 0) { 3915 m_selected_line--; 3916 if (static_cast<size_t>(m_first_visible_line) > m_selected_line) 3917 m_first_visible_line = m_selected_line; 3918 } 3919 return eKeyHandled; 3920 3921 case KEY_DOWN: 3922 if (m_selected_line + 1 < num_lines) { 3923 m_selected_line++; 3924 if (m_first_visible_line + num_visible_lines < m_selected_line) 3925 m_first_visible_line++; 3926 } 3927 return eKeyHandled; 3928 3929 case KEY_LEFT: 3930 if (m_first_visible_column > 0) 3931 --m_first_visible_column; 3932 return eKeyHandled; 3933 3934 case KEY_RIGHT: 3935 ++m_first_visible_column; 3936 return eKeyHandled; 3937 3938 case '\r': 3939 case '\n': 3940 case KEY_ENTER: 3941 // Set a breakpoint and run to the line using a one shot breakpoint 3942 if (GetNumSourceLines() > 0) { 3943 ExecutionContext exe_ctx = 3944 m_debugger.GetCommandInterpreter().GetExecutionContext(); 3945 if (exe_ctx.HasProcessScope() && exe_ctx.GetProcessRef().IsAlive()) { 3946 BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint( 3947 nullptr, // Don't limit the breakpoint to certain modules 3948 m_file_sp->GetFileSpec(), // Source file 3949 m_selected_line + 3950 1, // Source line number (m_selected_line is zero based) 3951 0, // Unspecified column. 3952 0, // No offset 3953 eLazyBoolCalculate, // Check inlines using global setting 3954 eLazyBoolCalculate, // Skip prologue using global setting, 3955 false, // internal 3956 false, // request_hardware 3957 eLazyBoolCalculate); // move_to_nearest_code 3958 // Make breakpoint one shot 3959 bp_sp->GetOptions()->SetOneShot(true); 3960 exe_ctx.GetProcessRef().Resume(); 3961 } 3962 } else if (m_selected_line < GetNumDisassemblyLines()) { 3963 const Instruction *inst = m_disassembly_sp->GetInstructionList() 3964 .GetInstructionAtIndex(m_selected_line) 3965 .get(); 3966 ExecutionContext exe_ctx = 3967 m_debugger.GetCommandInterpreter().GetExecutionContext(); 3968 if (exe_ctx.HasTargetScope()) { 3969 Address addr = inst->GetAddress(); 3970 BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint( 3971 addr, // lldb_private::Address 3972 false, // internal 3973 false); // request_hardware 3974 // Make breakpoint one shot 3975 bp_sp->GetOptions()->SetOneShot(true); 3976 exe_ctx.GetProcessRef().Resume(); 3977 } 3978 } 3979 return eKeyHandled; 3980 3981 case 'b': // 'b' == toggle breakpoint on currently selected line 3982 ToggleBreakpointOnSelectedLine(); 3983 return eKeyHandled; 3984 3985 case 'D': // 'D' == detach and keep stopped 3986 { 3987 ExecutionContext exe_ctx = 3988 m_debugger.GetCommandInterpreter().GetExecutionContext(); 3989 if (exe_ctx.HasProcessScope()) 3990 exe_ctx.GetProcessRef().Detach(true); 3991 } 3992 return eKeyHandled; 3993 3994 case 'c': 3995 // 'c' == continue 3996 { 3997 ExecutionContext exe_ctx = 3998 m_debugger.GetCommandInterpreter().GetExecutionContext(); 3999 if (exe_ctx.HasProcessScope()) 4000 exe_ctx.GetProcessRef().Resume(); 4001 } 4002 return eKeyHandled; 4003 4004 case 'f': 4005 // 'f' == step out (finish) 4006 { 4007 ExecutionContext exe_ctx = 4008 m_debugger.GetCommandInterpreter().GetExecutionContext(); 4009 if (exe_ctx.HasThreadScope() && 4010 StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) { 4011 exe_ctx.GetThreadRef().StepOut(); 4012 } 4013 } 4014 return eKeyHandled; 4015 4016 case 'n': // 'n' == step over 4017 case 'N': // 'N' == step over instruction 4018 { 4019 ExecutionContext exe_ctx = 4020 m_debugger.GetCommandInterpreter().GetExecutionContext(); 4021 if (exe_ctx.HasThreadScope() && 4022 StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) { 4023 bool source_step = (c == 'n'); 4024 exe_ctx.GetThreadRef().StepOver(source_step); 4025 } 4026 } 4027 return eKeyHandled; 4028 4029 case 's': // 's' == step into 4030 case 'S': // 'S' == step into instruction 4031 { 4032 ExecutionContext exe_ctx = 4033 m_debugger.GetCommandInterpreter().GetExecutionContext(); 4034 if (exe_ctx.HasThreadScope() && 4035 StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) { 4036 bool source_step = (c == 's'); 4037 exe_ctx.GetThreadRef().StepIn(source_step); 4038 } 4039 } 4040 return eKeyHandled; 4041 4042 case 'u': // 'u' == frame up 4043 case 'd': // 'd' == frame down 4044 { 4045 ExecutionContext exe_ctx = 4046 m_debugger.GetCommandInterpreter().GetExecutionContext(); 4047 if (exe_ctx.HasThreadScope()) { 4048 Thread *thread = exe_ctx.GetThreadPtr(); 4049 uint32_t frame_idx = thread->GetSelectedFrameIndex(); 4050 if (frame_idx == UINT32_MAX) 4051 frame_idx = 0; 4052 if (c == 'u' && frame_idx + 1 < thread->GetStackFrameCount()) 4053 ++frame_idx; 4054 else if (c == 'd' && frame_idx > 0) 4055 --frame_idx; 4056 if (thread->SetSelectedFrameByIndex(frame_idx, true)) 4057 exe_ctx.SetFrameSP(thread->GetSelectedFrame()); 4058 } 4059 } 4060 return eKeyHandled; 4061 4062 case 'h': 4063 window.CreateHelpSubwindow(); 4064 return eKeyHandled; 4065 4066 default: 4067 break; 4068 } 4069 return eKeyNotHandled; 4070 } 4071 4072 void ToggleBreakpointOnSelectedLine() { 4073 ExecutionContext exe_ctx = 4074 m_debugger.GetCommandInterpreter().GetExecutionContext(); 4075 if (!exe_ctx.HasTargetScope()) 4076 return; 4077 if (GetNumSourceLines() > 0) { 4078 // Source file breakpoint. 4079 BreakpointList &bp_list = exe_ctx.GetTargetRef().GetBreakpointList(); 4080 const size_t num_bps = bp_list.GetSize(); 4081 for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) { 4082 BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx); 4083 const size_t num_bps_locs = bp_sp->GetNumLocations(); 4084 for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) { 4085 BreakpointLocationSP bp_loc_sp = 4086 bp_sp->GetLocationAtIndex(bp_loc_idx); 4087 LineEntry bp_loc_line_entry; 4088 if (bp_loc_sp->GetAddress().CalculateSymbolContextLineEntry( 4089 bp_loc_line_entry)) { 4090 if (m_file_sp->GetFileSpec() == bp_loc_line_entry.file && 4091 m_selected_line + 1 == bp_loc_line_entry.line) { 4092 bool removed = 4093 exe_ctx.GetTargetRef().RemoveBreakpointByID(bp_sp->GetID()); 4094 assert(removed); 4095 UNUSED_IF_ASSERT_DISABLED(removed); 4096 return; // Existing breakpoint removed. 4097 } 4098 } 4099 } 4100 } 4101 // No breakpoint found on the location, add it. 4102 BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint( 4103 nullptr, // Don't limit the breakpoint to certain modules 4104 m_file_sp->GetFileSpec(), // Source file 4105 m_selected_line + 4106 1, // Source line number (m_selected_line is zero based) 4107 0, // No column specified. 4108 0, // No offset 4109 eLazyBoolCalculate, // Check inlines using global setting 4110 eLazyBoolCalculate, // Skip prologue using global setting, 4111 false, // internal 4112 false, // request_hardware 4113 eLazyBoolCalculate); // move_to_nearest_code 4114 } else { 4115 // Disassembly breakpoint. 4116 assert(GetNumDisassemblyLines() > 0); 4117 assert(m_selected_line < GetNumDisassemblyLines()); 4118 const Instruction *inst = m_disassembly_sp->GetInstructionList() 4119 .GetInstructionAtIndex(m_selected_line) 4120 .get(); 4121 Address addr = inst->GetAddress(); 4122 // Try to find it. 4123 BreakpointList &bp_list = exe_ctx.GetTargetRef().GetBreakpointList(); 4124 const size_t num_bps = bp_list.GetSize(); 4125 for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) { 4126 BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx); 4127 const size_t num_bps_locs = bp_sp->GetNumLocations(); 4128 for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) { 4129 BreakpointLocationSP bp_loc_sp = 4130 bp_sp->GetLocationAtIndex(bp_loc_idx); 4131 LineEntry bp_loc_line_entry; 4132 const lldb::addr_t file_addr = 4133 bp_loc_sp->GetAddress().GetFileAddress(); 4134 if (file_addr == addr.GetFileAddress()) { 4135 bool removed = 4136 exe_ctx.GetTargetRef().RemoveBreakpointByID(bp_sp->GetID()); 4137 assert(removed); 4138 UNUSED_IF_ASSERT_DISABLED(removed); 4139 return; // Existing breakpoint removed. 4140 } 4141 } 4142 } 4143 // No breakpoint found on the address, add it. 4144 BreakpointSP bp_sp = 4145 exe_ctx.GetTargetRef().CreateBreakpoint(addr, // lldb_private::Address 4146 false, // internal 4147 false); // request_hardware 4148 } 4149 } 4150 4151 protected: 4152 typedef std::set<uint32_t> BreakpointLines; 4153 typedef std::set<lldb::addr_t> BreakpointAddrs; 4154 4155 Debugger &m_debugger; 4156 SymbolContext m_sc; 4157 SourceManager::FileSP m_file_sp; 4158 SymbolContextScope *m_disassembly_scope; 4159 lldb::DisassemblerSP m_disassembly_sp; 4160 AddressRange m_disassembly_range; 4161 StreamString m_title; 4162 lldb::user_id_t m_tid; 4163 int m_line_width; 4164 uint32_t m_selected_line; // The selected line 4165 uint32_t m_pc_line; // The line with the PC 4166 uint32_t m_stop_id; 4167 uint32_t m_frame_idx; 4168 int m_first_visible_line; 4169 int m_first_visible_column; 4170 int m_min_x; 4171 int m_min_y; 4172 int m_max_x; 4173 int m_max_y; 4174 }; 4175 4176 DisplayOptions ValueObjectListDelegate::g_options = {true}; 4177 4178 IOHandlerCursesGUI::IOHandlerCursesGUI(Debugger &debugger) 4179 : IOHandler(debugger, IOHandler::Type::Curses) {} 4180 4181 void IOHandlerCursesGUI::Activate() { 4182 IOHandler::Activate(); 4183 if (!m_app_ap) { 4184 m_app_ap = std::make_unique<Application>(GetInputFILE(), GetOutputFILE()); 4185 4186 // This is both a window and a menu delegate 4187 std::shared_ptr<ApplicationDelegate> app_delegate_sp( 4188 new ApplicationDelegate(*m_app_ap, m_debugger)); 4189 4190 MenuDelegateSP app_menu_delegate_sp = 4191 std::static_pointer_cast<MenuDelegate>(app_delegate_sp); 4192 MenuSP lldb_menu_sp( 4193 new Menu("LLDB", "F1", KEY_F(1), ApplicationDelegate::eMenuID_LLDB)); 4194 MenuSP exit_menuitem_sp( 4195 new Menu("Exit", nullptr, 'x', ApplicationDelegate::eMenuID_LLDBExit)); 4196 exit_menuitem_sp->SetCannedResult(MenuActionResult::Quit); 4197 lldb_menu_sp->AddSubmenu(MenuSP(new Menu( 4198 "About LLDB", nullptr, 'a', ApplicationDelegate::eMenuID_LLDBAbout))); 4199 lldb_menu_sp->AddSubmenu(MenuSP(new Menu(Menu::Type::Separator))); 4200 lldb_menu_sp->AddSubmenu(exit_menuitem_sp); 4201 4202 MenuSP target_menu_sp(new Menu("Target", "F2", KEY_F(2), 4203 ApplicationDelegate::eMenuID_Target)); 4204 target_menu_sp->AddSubmenu(MenuSP(new Menu( 4205 "Create", nullptr, 'c', ApplicationDelegate::eMenuID_TargetCreate))); 4206 target_menu_sp->AddSubmenu(MenuSP(new Menu( 4207 "Delete", nullptr, 'd', ApplicationDelegate::eMenuID_TargetDelete))); 4208 4209 MenuSP process_menu_sp(new Menu("Process", "F3", KEY_F(3), 4210 ApplicationDelegate::eMenuID_Process)); 4211 process_menu_sp->AddSubmenu(MenuSP(new Menu( 4212 "Attach", nullptr, 'a', ApplicationDelegate::eMenuID_ProcessAttach))); 4213 process_menu_sp->AddSubmenu( 4214 MenuSP(new Menu("Detach and resume", nullptr, 'd', 4215 ApplicationDelegate::eMenuID_ProcessDetachResume))); 4216 process_menu_sp->AddSubmenu( 4217 MenuSP(new Menu("Detach suspended", nullptr, 's', 4218 ApplicationDelegate::eMenuID_ProcessDetachSuspended))); 4219 process_menu_sp->AddSubmenu(MenuSP(new Menu( 4220 "Launch", nullptr, 'l', ApplicationDelegate::eMenuID_ProcessLaunch))); 4221 process_menu_sp->AddSubmenu(MenuSP(new Menu(Menu::Type::Separator))); 4222 process_menu_sp->AddSubmenu( 4223 MenuSP(new Menu("Continue", nullptr, 'c', 4224 ApplicationDelegate::eMenuID_ProcessContinue))); 4225 process_menu_sp->AddSubmenu(MenuSP(new Menu( 4226 "Halt", nullptr, 'h', ApplicationDelegate::eMenuID_ProcessHalt))); 4227 process_menu_sp->AddSubmenu(MenuSP(new Menu( 4228 "Kill", nullptr, 'k', ApplicationDelegate::eMenuID_ProcessKill))); 4229 4230 MenuSP thread_menu_sp(new Menu("Thread", "F4", KEY_F(4), 4231 ApplicationDelegate::eMenuID_Thread)); 4232 thread_menu_sp->AddSubmenu(MenuSP(new Menu( 4233 "Step In", nullptr, 'i', ApplicationDelegate::eMenuID_ThreadStepIn))); 4234 thread_menu_sp->AddSubmenu( 4235 MenuSP(new Menu("Step Over", nullptr, 'v', 4236 ApplicationDelegate::eMenuID_ThreadStepOver))); 4237 thread_menu_sp->AddSubmenu(MenuSP(new Menu( 4238 "Step Out", nullptr, 'o', ApplicationDelegate::eMenuID_ThreadStepOut))); 4239 4240 MenuSP view_menu_sp( 4241 new Menu("View", "F5", KEY_F(5), ApplicationDelegate::eMenuID_View)); 4242 view_menu_sp->AddSubmenu( 4243 MenuSP(new Menu("Backtrace", nullptr, 'b', 4244 ApplicationDelegate::eMenuID_ViewBacktrace))); 4245 view_menu_sp->AddSubmenu( 4246 MenuSP(new Menu("Registers", nullptr, 'r', 4247 ApplicationDelegate::eMenuID_ViewRegisters))); 4248 view_menu_sp->AddSubmenu(MenuSP(new Menu( 4249 "Source", nullptr, 's', ApplicationDelegate::eMenuID_ViewSource))); 4250 view_menu_sp->AddSubmenu( 4251 MenuSP(new Menu("Variables", nullptr, 'v', 4252 ApplicationDelegate::eMenuID_ViewVariables))); 4253 4254 MenuSP help_menu_sp( 4255 new Menu("Help", "F6", KEY_F(6), ApplicationDelegate::eMenuID_Help)); 4256 help_menu_sp->AddSubmenu(MenuSP(new Menu( 4257 "GUI Help", nullptr, 'g', ApplicationDelegate::eMenuID_HelpGUIHelp))); 4258 4259 m_app_ap->Initialize(); 4260 WindowSP &main_window_sp = m_app_ap->GetMainWindow(); 4261 4262 MenuSP menubar_sp(new Menu(Menu::Type::Bar)); 4263 menubar_sp->AddSubmenu(lldb_menu_sp); 4264 menubar_sp->AddSubmenu(target_menu_sp); 4265 menubar_sp->AddSubmenu(process_menu_sp); 4266 menubar_sp->AddSubmenu(thread_menu_sp); 4267 menubar_sp->AddSubmenu(view_menu_sp); 4268 menubar_sp->AddSubmenu(help_menu_sp); 4269 menubar_sp->SetDelegate(app_menu_delegate_sp); 4270 4271 Rect content_bounds = main_window_sp->GetFrame(); 4272 Rect menubar_bounds = content_bounds.MakeMenuBar(); 4273 Rect status_bounds = content_bounds.MakeStatusBar(); 4274 Rect source_bounds; 4275 Rect variables_bounds; 4276 Rect threads_bounds; 4277 Rect source_variables_bounds; 4278 content_bounds.VerticalSplitPercentage(0.80, source_variables_bounds, 4279 threads_bounds); 4280 source_variables_bounds.HorizontalSplitPercentage(0.70, source_bounds, 4281 variables_bounds); 4282 4283 WindowSP menubar_window_sp = 4284 main_window_sp->CreateSubWindow("Menubar", menubar_bounds, false); 4285 // Let the menubar get keys if the active window doesn't handle the keys 4286 // that are typed so it can respond to menubar key presses. 4287 menubar_window_sp->SetCanBeActive( 4288 false); // Don't let the menubar become the active window 4289 menubar_window_sp->SetDelegate(menubar_sp); 4290 4291 WindowSP source_window_sp( 4292 main_window_sp->CreateSubWindow("Source", source_bounds, true)); 4293 WindowSP variables_window_sp( 4294 main_window_sp->CreateSubWindow("Variables", variables_bounds, false)); 4295 WindowSP threads_window_sp( 4296 main_window_sp->CreateSubWindow("Threads", threads_bounds, false)); 4297 WindowSP status_window_sp( 4298 main_window_sp->CreateSubWindow("Status", status_bounds, false)); 4299 status_window_sp->SetCanBeActive( 4300 false); // Don't let the status bar become the active window 4301 main_window_sp->SetDelegate( 4302 std::static_pointer_cast<WindowDelegate>(app_delegate_sp)); 4303 source_window_sp->SetDelegate( 4304 WindowDelegateSP(new SourceFileWindowDelegate(m_debugger))); 4305 variables_window_sp->SetDelegate( 4306 WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger))); 4307 TreeDelegateSP thread_delegate_sp(new ThreadsTreeDelegate(m_debugger)); 4308 threads_window_sp->SetDelegate(WindowDelegateSP( 4309 new TreeWindowDelegate(m_debugger, thread_delegate_sp))); 4310 status_window_sp->SetDelegate( 4311 WindowDelegateSP(new StatusBarWindowDelegate(m_debugger))); 4312 4313 // Show the main help window once the first time the curses GUI is launched 4314 static bool g_showed_help = false; 4315 if (!g_showed_help) { 4316 g_showed_help = true; 4317 main_window_sp->CreateHelpSubwindow(); 4318 } 4319 4320 // All colors with black background. 4321 init_pair(1, COLOR_BLACK, COLOR_BLACK); 4322 init_pair(2, COLOR_RED, COLOR_BLACK); 4323 init_pair(3, COLOR_GREEN, COLOR_BLACK); 4324 init_pair(4, COLOR_YELLOW, COLOR_BLACK); 4325 init_pair(5, COLOR_BLUE, COLOR_BLACK); 4326 init_pair(6, COLOR_MAGENTA, COLOR_BLACK); 4327 init_pair(7, COLOR_CYAN, COLOR_BLACK); 4328 init_pair(8, COLOR_WHITE, COLOR_BLACK); 4329 // All colors with blue background. 4330 init_pair(9, COLOR_BLACK, COLOR_BLUE); 4331 init_pair(10, COLOR_RED, COLOR_BLUE); 4332 init_pair(11, COLOR_GREEN, COLOR_BLUE); 4333 init_pair(12, COLOR_YELLOW, COLOR_BLUE); 4334 init_pair(13, COLOR_BLUE, COLOR_BLUE); 4335 init_pair(14, COLOR_MAGENTA, COLOR_BLUE); 4336 init_pair(15, COLOR_CYAN, COLOR_BLUE); 4337 init_pair(16, COLOR_WHITE, COLOR_BLUE); 4338 // These must match the order in the color indexes enum. 4339 init_pair(17, COLOR_BLACK, COLOR_WHITE); 4340 init_pair(18, COLOR_MAGENTA, COLOR_WHITE); 4341 static_assert(LastColorPairIndex == 18, "Color indexes do not match."); 4342 } 4343 } 4344 4345 void IOHandlerCursesGUI::Deactivate() { m_app_ap->Terminate(); } 4346 4347 void IOHandlerCursesGUI::Run() { 4348 m_app_ap->Run(m_debugger); 4349 SetIsDone(true); 4350 } 4351 4352 IOHandlerCursesGUI::~IOHandlerCursesGUI() = default; 4353 4354 void IOHandlerCursesGUI::Cancel() {} 4355 4356 bool IOHandlerCursesGUI::Interrupt() { return false; } 4357 4358 void IOHandlerCursesGUI::GotEOF() {} 4359 4360 void IOHandlerCursesGUI::TerminalSizeChanged() { 4361 m_app_ap->TerminalSizeChanged(); 4362 } 4363 4364 #endif // LLDB_ENABLE_CURSES 4365