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