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