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