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