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