1 /* TUI display source/assembly window.
2
3 Copyright (C) 1998-2024 Free Software Foundation, Inc.
4
5 Contributed by Hewlett-Packard Company.
6
7 This file is part of GDB.
8
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 3 of the License, or
12 (at your option) any later version.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program. If not, see <http://www.gnu.org/licenses/>. */
21
22 #include <ctype.h>
23 #include "symtab.h"
24 #include "frame.h"
25 #include "breakpoint.h"
26 #include "value.h"
27 #include "source.h"
28 #include "objfiles.h"
29 #include "filenames.h"
30 #include "gdbsupport/gdb-safe-ctype.h"
31
32 #include "tui/tui.h"
33 #include "tui/tui-data.h"
34 #include "tui/tui-io.h"
35 #include "tui/tui-status.h"
36 #include "tui/tui-win.h"
37 #include "tui/tui-wingeneral.h"
38 #include "tui/tui-winsource.h"
39 #include "tui/tui-source.h"
40 #include "tui/tui-disasm.h"
41 #include "tui/tui-location.h"
42 #include "gdb_curses.h"
43
44 /* ncurses returns -1, but BSD segfaults; the code assumes ncurses */
45 #define tui_getmaxx(w) ((w) ? getmaxx (w) : -1)
46 #define tui_getmaxy(w) ((w) ? getmaxy (w) : -1)
47
48 /* Function to display the "main" routine. */
49 void
tui_display_main()50 tui_display_main ()
51 {
52 auto adapter = tui_source_windows ();
53 if (adapter.begin () != adapter.end ())
54 {
55 struct gdbarch *gdbarch;
56 CORE_ADDR addr;
57
58 tui_get_begin_asm_address (&gdbarch, &addr);
59 if (addr != (CORE_ADDR) 0)
60 {
61 struct symtab *s;
62
63 tui_update_source_windows_with_addr (gdbarch, addr);
64 s = find_pc_line_symtab (addr);
65 tui_location.set_location (s);
66 }
67 }
68 }
69
70 /* See tui-winsource.h. */
71
72 std::string
tui_copy_source_line(const char ** ptr,int * length)73 tui_copy_source_line (const char **ptr, int *length)
74 {
75 const char *lineptr = *ptr;
76
77 /* Init the line with the line number. */
78 std::string result;
79
80 int column = 0;
81 char c;
82 do
83 {
84 int skip_bytes;
85
86 c = *lineptr;
87 if (c == '\033' && skip_ansi_escape (lineptr, &skip_bytes))
88 {
89 /* We always have to preserve escapes. */
90 result.append (lineptr, lineptr + skip_bytes);
91 lineptr += skip_bytes;
92 continue;
93 }
94 if (c == '\0')
95 break;
96
97 ++lineptr;
98 ++column;
99
100 auto process_tab = [&] ()
101 {
102 int max_tab_len = tui_tab_width;
103
104 --column;
105 for (int j = column % max_tab_len;
106 j < max_tab_len;
107 column++, j++)
108 result.push_back (' ');
109 };
110
111 if (c == '\n' || c == '\r' || c == '\0')
112 {
113 /* Nothing. */
114 }
115 else if (c == '\t')
116 process_tab ();
117 else if (ISCNTRL (c))
118 {
119 result.push_back ('^');
120 result.push_back (c + 0100);
121 ++column;
122 }
123 else if (c == 0177)
124 {
125 result.push_back ('^');
126 result.push_back ('?');
127 ++column;
128 }
129 else
130 result.push_back (c);
131 }
132 while (c != '\0' && c != '\n' && c != '\r');
133
134 if (c == '\r' && *lineptr == '\n')
135 ++lineptr;
136 *ptr = lineptr;
137
138 if (length != nullptr)
139 *length = column;
140
141 return result;
142 }
143
144 void
style_changed()145 tui_source_window_base::style_changed ()
146 {
147 if (tui_active && is_visible ())
148 refill ();
149 }
150
151 /* Function to display source in the source window. This function
152 initializes the horizontal scroll to 0. */
153 void
update_source_window(struct gdbarch * gdbarch,const struct symtab_and_line & sal)154 tui_source_window_base::update_source_window
155 (struct gdbarch *gdbarch,
156 const struct symtab_and_line &sal)
157 {
158 m_horizontal_offset = 0;
159 update_source_window_as_is (gdbarch, sal);
160 }
161
162
163 /* Function to display source in the source/asm window. This function
164 shows the source as specified by the horizontal offset. */
165 void
update_source_window_as_is(struct gdbarch * gdbarch,const struct symtab_and_line & sal)166 tui_source_window_base::update_source_window_as_is
167 (struct gdbarch *gdbarch,
168 const struct symtab_and_line &sal)
169 {
170 bool ret = set_contents (gdbarch, sal);
171
172 if (!ret)
173 erase_source_content ();
174 else
175 {
176 validate_scroll_offsets ();
177 update_breakpoint_info (nullptr, false);
178 update_exec_info (false);
179 show_source_content ();
180 }
181 }
182
183
184 /* See tui-winsource.h. */
185 void
update_source_window_with_addr(struct gdbarch * gdbarch,CORE_ADDR addr)186 tui_source_window_base::update_source_window_with_addr (struct gdbarch *gdbarch,
187 CORE_ADDR addr)
188 {
189 struct symtab_and_line sal {};
190 if (addr != 0)
191 sal = find_pc_line (addr, 0);
192
193 update_source_window (gdbarch, sal);
194 }
195
196 /* Function to ensure that the source and/or disassembly windows
197 reflect the input address. */
198 void
tui_update_source_windows_with_addr(struct gdbarch * gdbarch,CORE_ADDR addr)199 tui_update_source_windows_with_addr (struct gdbarch *gdbarch, CORE_ADDR addr)
200 {
201 struct symtab_and_line sal {};
202 if (addr != 0)
203 sal = find_pc_line (addr, 0);
204
205 for (struct tui_source_window_base *win_info : tui_source_windows ())
206 win_info->update_source_window (gdbarch, sal);
207 }
208
209 /* Function to ensure that the source and/or disassembly windows
210 reflect the symtab and line. */
211 void
tui_update_source_windows_with_line(struct symtab_and_line sal)212 tui_update_source_windows_with_line (struct symtab_and_line sal)
213 {
214 struct gdbarch *gdbarch = nullptr;
215 if (sal.symtab != nullptr)
216 {
217 find_line_pc (sal.symtab, sal.line, &sal.pc);
218 gdbarch = sal.symtab->compunit ()->objfile ()->arch ();
219 }
220
221 for (struct tui_source_window_base *win_info : tui_source_windows ())
222 win_info->update_source_window (gdbarch, sal);
223 }
224
225 void
do_erase_source_content(const char * str)226 tui_source_window_base::do_erase_source_content (const char *str)
227 {
228 m_content.clear ();
229 if (handle != nullptr)
230 center_string (str);
231 }
232
233 /* See tui-winsource.h. */
234
235 void
puts_to_pad_with_skip(const char * string,int skip)236 tui_source_window_base::puts_to_pad_with_skip (const char *string, int skip)
237 {
238 gdb_assert (m_pad.get () != nullptr);
239 WINDOW *w = m_pad.get ();
240
241 while (skip > 0)
242 {
243 const char *next = strpbrk (string, "\033");
244
245 /* Print the plain text prefix. */
246 size_t n_chars = next == nullptr ? strlen (string) : next - string;
247 if (n_chars > 0)
248 {
249 if (skip > 0)
250 {
251 if (skip < n_chars)
252 {
253 string += skip;
254 n_chars -= skip;
255 skip = 0;
256 }
257 else
258 {
259 skip -= n_chars;
260 string += n_chars;
261 n_chars = 0;
262 }
263 }
264
265 if (n_chars > 0)
266 {
267 std::string copy (string, n_chars);
268 tui_puts (copy.c_str (), w);
269 }
270 }
271
272 /* We finished. */
273 if (next == nullptr)
274 break;
275
276 gdb_assert (*next == '\033');
277
278 int n_read;
279 if (skip_ansi_escape (next, &n_read))
280 {
281 std::string copy (next, n_read);
282 tui_puts (copy.c_str (), w);
283 next += n_read;
284 }
285 else
286 gdb_assert_not_reached ("unhandled escape");
287
288 string = next;
289 }
290
291 if (*string != '\0')
292 tui_puts (string, w);
293 }
294
295 /* Redraw the complete line of a source or disassembly window. */
296 void
show_source_line(int lineno)297 tui_source_window_base::show_source_line (int lineno)
298 {
299 struct tui_source_element *line;
300
301 line = &m_content[lineno];
302 if (line->is_exec_point)
303 tui_set_reverse_mode (m_pad.get (), true);
304
305 wmove (m_pad.get (), lineno, 0);
306 puts_to_pad_with_skip (line->line.c_str (), m_pad_offset);
307
308 if (line->is_exec_point)
309 tui_set_reverse_mode (m_pad.get (), false);
310 }
311
312 /* See tui-winsource.h. */
313
314 void
refresh_window()315 tui_source_window_base::refresh_window ()
316 {
317 TUI_SCOPED_DEBUG_START_END ("window `%s`", name ());
318
319 /* tui_win_info::refresh_window would draw the empty background window to
320 the screen, potentially creating a flicker. */
321 wnoutrefresh (handle.get ());
322
323 int pad_width = tui_getmaxx (m_pad.get ());
324 int left_margin = this->left_margin ();
325 int view_width = this->view_width ();
326 int content_width = m_max_length;
327 int pad_x = m_horizontal_offset - m_pad_offset;
328
329 tui_debug_printf ("pad_width = %d, left_margin = %d, view_width = %d",
330 pad_width, left_margin, view_width);
331 tui_debug_printf ("content_width = %d, pad_x = %d, m_horizontal_offset = %d",
332 content_width, pad_x, m_horizontal_offset);
333 tui_debug_printf ("m_pad_offset = %d", m_pad_offset);
334
335 gdb_assert (m_pad_offset >= 0);
336 gdb_assert (m_horizontal_offset + view_width
337 <= std::max (content_width, view_width));
338 gdb_assert (pad_x >= 0);
339 gdb_assert (m_horizontal_offset >= 0);
340
341 /* This function can be called before the pad has been allocated, this
342 should only occur during the initial startup. In this case the first
343 condition in the following asserts will not be true, but the nullptr
344 check will. */
345 gdb_assert (pad_width > 0 || m_pad.get () == nullptr);
346 gdb_assert (pad_x + view_width <= pad_width || m_pad.get () == nullptr);
347
348 int sminrow = y + box_width ();
349 int smincol = x + box_width () + left_margin;
350 int smaxrow = sminrow + m_content.size () - 1;
351 int smaxcol = smincol + view_width - 1;
352 if (m_pad.get ())
353 prefresh (m_pad.get (), 0, pad_x, sminrow, smincol, smaxrow, smaxcol);
354 }
355
356 void
show_source_content()357 tui_source_window_base::show_source_content ()
358 {
359 TUI_SCOPED_DEBUG_START_END ("window `%s`", name ());
360
361 gdb_assert (!m_content.empty ());
362
363 /* The pad should be at least as wide as the window, but ideally, as wide
364 as the content, however, for some very wide content this might not be
365 possible. */
366 int required_pad_width = std::max (m_max_length, width);
367 int required_pad_height = m_content.size ();
368
369 /* If the required pad width is wider than the previously requested pad
370 width, then we might want to grow the pad. */
371 if (required_pad_width > m_pad_requested_width
372 || required_pad_height > tui_getmaxy (m_pad.get ()))
373 {
374 /* The current pad width. */
375 int pad_width = m_pad == nullptr ? 0 : tui_getmaxx (m_pad.get ());
376
377 gdb_assert (pad_width <= m_pad_requested_width);
378
379 /* If the current pad width is smaller than the previously requested
380 pad width, then this means we previously failed to allocate a
381 bigger pad. There's no point asking again, so we'll just make so
382 with the pad we currently have. */
383 if (pad_width == m_pad_requested_width
384 || required_pad_height > tui_getmaxy (m_pad.get ()))
385 {
386 pad_width = required_pad_width;
387
388 do
389 {
390 /* Try to allocate a new pad. */
391 m_pad.reset (newpad (required_pad_height, pad_width));
392
393 if (m_pad == nullptr)
394 {
395 int reduced_width = std::max (pad_width / 2, width);
396 if (reduced_width == pad_width)
397 error (_("failed to setup source window"));
398 pad_width = reduced_width;
399 }
400 }
401 while (m_pad == nullptr);
402 }
403
404 m_pad_requested_width = required_pad_width;
405 tui_debug_printf ("requested width %d, allocated width %d",
406 required_pad_width, tui_getmaxx (m_pad.get ()));
407 }
408
409 gdb_assert (m_pad != nullptr);
410 werase (m_pad.get ());
411 for (int lineno = 0; lineno < m_content.size (); lineno++)
412 show_source_line (lineno);
413
414 if (can_box ())
415 {
416 /* Calling check_and_display_highlight_if_needed will call refresh_window
417 (so long as the current window can be boxed), which will ensure that
418 the newly loaded window content is copied to the screen. */
419 check_and_display_highlight_if_needed ();
420 }
421 else
422 refresh_window ();
423 }
424
tui_source_window_base()425 tui_source_window_base::tui_source_window_base ()
426 {
427 m_start_line_or_addr.loa = LOA_ADDRESS;
428 m_start_line_or_addr.u.addr = 0;
429
430 gdb::observers::styling_changed.attach
431 (std::bind (&tui_source_window::style_changed, this),
432 m_observable, "tui-winsource");
433 }
434
~tui_source_window_base()435 tui_source_window_base::~tui_source_window_base ()
436 {
437 gdb::observers::styling_changed.detach (m_observable);
438 }
439
440 /* See tui-data.h. */
441
442 void
update_tab_width()443 tui_source_window_base::update_tab_width ()
444 {
445 werase (handle.get ());
446 rerender ();
447 }
448
449 void
rerender()450 tui_source_window_base::rerender ()
451 {
452 TUI_SCOPED_DEBUG_START_END ("window `%s`", name ());
453
454 if (!m_content.empty ())
455 {
456 struct symtab_and_line cursal
457 = get_current_source_symtab_and_line ();
458
459 if (m_start_line_or_addr.loa == LOA_LINE)
460 cursal.line = m_start_line_or_addr.u.line_no;
461 else
462 cursal.pc = m_start_line_or_addr.u.addr;
463 update_source_window (m_gdbarch, cursal);
464 }
465 else if (deprecated_safe_get_selected_frame () != NULL)
466 {
467 struct symtab_and_line cursal
468 = get_current_source_symtab_and_line ();
469 frame_info_ptr frame = deprecated_safe_get_selected_frame ();
470 struct gdbarch *gdbarch = get_frame_arch (frame);
471
472 struct symtab *s = find_pc_line_symtab (get_frame_pc (frame));
473 if (this != TUI_SRC_WIN)
474 find_line_pc (s, cursal.line, &cursal.pc);
475
476 /* This centering code is copied from tui_source_window::maybe_update.
477 It would be nice to do centering more often, and do it in just one
478 location. But since this is a regression fix, handle this
479 conservatively for now. */
480 int start_line = (cursal.line - ((height - box_size ()) / 2)) + 1;
481 if (start_line <= 0)
482 start_line = 1;
483 cursal.line = start_line;
484
485 update_source_window (gdbarch, cursal);
486 }
487 else
488 {
489 CORE_ADDR addr;
490 struct gdbarch *gdbarch;
491 tui_get_begin_asm_address (&gdbarch, &addr);
492 if (addr == 0)
493 erase_source_content ();
494 else
495 update_source_window_with_addr (gdbarch, addr);
496 }
497 }
498
499 /* See tui-data.h. */
500
501 void
refill()502 tui_source_window_base::refill ()
503 {
504 symtab_and_line sal {};
505
506 if (this == TUI_SRC_WIN)
507 {
508 sal = get_current_source_symtab_and_line ();
509 if (sal.symtab == NULL)
510 {
511 frame_info_ptr fi = deprecated_safe_get_selected_frame ();
512 if (fi != nullptr)
513 sal = find_pc_line (get_frame_pc (fi), 0);
514 }
515 }
516
517 if (sal.pspace == nullptr)
518 sal.pspace = current_program_space;
519
520 if (m_start_line_or_addr.loa == LOA_LINE)
521 sal.line = m_start_line_or_addr.u.line_no;
522 else
523 sal.pc = m_start_line_or_addr.u.addr;
524
525 update_source_window_as_is (m_gdbarch, sal);
526 }
527
528 /* See tui-winsource.h. */
529
530 bool
validate_scroll_offsets()531 tui_source_window_base::validate_scroll_offsets ()
532 {
533 TUI_SCOPED_DEBUG_START_END ("window `%s`", name ());
534
535 int original_pad_offset = m_pad_offset;
536
537 if (m_horizontal_offset < 0)
538 m_horizontal_offset = 0;
539
540 int content_width = m_max_length;
541 int pad_width = tui_getmaxx (m_pad.get ());
542 int view_width = this->view_width ();
543
544 tui_debug_printf ("pad_width = %d, view_width = %d, content_width = %d",
545 pad_width, view_width, content_width);
546 tui_debug_printf ("original_pad_offset = %d, m_horizontal_offset = %d",
547 original_pad_offset, m_horizontal_offset);
548
549 if (m_horizontal_offset + view_width > content_width)
550 m_horizontal_offset = std::max (content_width - view_width, 0);
551
552 if ((m_horizontal_offset + view_width) > (m_pad_offset + pad_width))
553 {
554 m_pad_offset = std::min (m_horizontal_offset, content_width - pad_width);
555 m_pad_offset = std::max (m_pad_offset, 0);
556 }
557 else if (m_horizontal_offset < m_pad_offset)
558 m_pad_offset = std::max (m_horizontal_offset + view_width - pad_width, 0);
559
560 gdb_assert (m_pad_offset >= 0);
561 return (original_pad_offset != m_pad_offset);
562 }
563
564 /* Scroll the source forward or backward horizontally. */
565
566 void
do_scroll_horizontal(int num_to_scroll)567 tui_source_window_base::do_scroll_horizontal (int num_to_scroll)
568 {
569 if (!m_content.empty ())
570 {
571 m_horizontal_offset += num_to_scroll;
572
573 if (validate_scroll_offsets ())
574 show_source_content ();
575
576 refresh_window ();
577 }
578 }
579
580
581 /* Set or clear the is_exec_point flag in the line whose line is
582 line_no. */
583
584 void
set_is_exec_point_at(struct tui_line_or_address l)585 tui_source_window_base::set_is_exec_point_at (struct tui_line_or_address l)
586 {
587 bool changed = false;
588 int i;
589
590 i = 0;
591 while (i < m_content.size ())
592 {
593 bool new_state;
594 struct tui_line_or_address content_loa =
595 m_content[i].line_or_addr;
596
597 if (content_loa.loa == l.loa
598 && ((l.loa == LOA_LINE && content_loa.u.line_no == l.u.line_no)
599 || (l.loa == LOA_ADDRESS && content_loa.u.addr == l.u.addr)))
600 new_state = true;
601 else
602 new_state = false;
603 if (new_state != m_content[i].is_exec_point)
604 {
605 changed = true;
606 m_content[i].is_exec_point = new_state;
607 }
608 i++;
609 }
610 if (changed)
611 refill ();
612 }
613
614 /* See tui-winsource.h. */
615
616 void
tui_update_all_breakpoint_info(struct breakpoint * being_deleted)617 tui_update_all_breakpoint_info (struct breakpoint *being_deleted)
618 {
619 for (tui_source_window_base *win : tui_source_windows ())
620 {
621 if (win->update_breakpoint_info (being_deleted, false))
622 win->update_exec_info ();
623 }
624 }
625
626
627 /* Scan the source window and the breakpoints to update the break_mode
628 information for each line.
629
630 Returns true if something changed and the execution window must be
631 refreshed. */
632
633 bool
update_breakpoint_info(struct breakpoint * being_deleted,bool current_only)634 tui_source_window_base::update_breakpoint_info
635 (struct breakpoint *being_deleted, bool current_only)
636 {
637 int i;
638 bool need_refresh = false;
639
640 for (i = 0; i < m_content.size (); i++)
641 {
642 struct tui_source_element *line;
643
644 line = &m_content[i];
645 if (current_only && !line->is_exec_point)
646 continue;
647
648 /* Scan each breakpoint to see if the current line has something to
649 do with it. Identify enable/disabled breakpoints as well as
650 those that we already hit. */
651 tui_bp_flags mode = 0;
652 for (breakpoint &bp : all_breakpoints ())
653 {
654 if (&bp == being_deleted)
655 continue;
656
657 for (bp_location &loc : bp.locations ())
658 {
659 if (location_matches_p (&loc, i))
660 {
661 if (bp.enable_state == bp_disabled)
662 mode |= TUI_BP_DISABLED;
663 else
664 mode |= TUI_BP_ENABLED;
665 if (bp.hit_count)
666 mode |= TUI_BP_HIT;
667 if (bp.first_loc ().cond)
668 mode |= TUI_BP_CONDITIONAL;
669 if (bp.type == bp_hardware_breakpoint)
670 mode |= TUI_BP_HARDWARE;
671 }
672 }
673 }
674
675 if (line->break_mode != mode)
676 {
677 line->break_mode = mode;
678 need_refresh = true;
679 }
680 }
681 return need_refresh;
682 }
683
684 /* See tui-winsource.h. */
685
686 void
update_exec_info(bool refresh_p)687 tui_source_window_base::update_exec_info (bool refresh_p)
688 {
689 update_breakpoint_info (nullptr, true);
690 for (int i = 0; i < m_content.size (); i++)
691 {
692 struct tui_source_element *src_element = &m_content[i];
693 /* Add 1 for '\0'. */
694 char element[TUI_EXECINFO_SIZE + 1];
695 /* Initialize all but last element. */
696 char space = tui_left_margin_verbose ? '_' : ' ';
697 memset (element, space, TUI_EXECINFO_SIZE);
698 /* Initialize last element. */
699 element[TUI_EXECINFO_SIZE] = '\0';
700
701 /* Now update the exec info content based upon the state
702 of each line as indicated by the source content. */
703 tui_bp_flags mode = src_element->break_mode;
704 if (mode & TUI_BP_HIT)
705 element[TUI_BP_HIT_POS] = (mode & TUI_BP_HARDWARE) ? 'H' : 'B';
706 else if (mode & (TUI_BP_ENABLED | TUI_BP_DISABLED))
707 element[TUI_BP_HIT_POS] = (mode & TUI_BP_HARDWARE) ? 'h' : 'b';
708
709 if (mode & TUI_BP_ENABLED)
710 element[TUI_BP_BREAK_POS] = '+';
711 else if (mode & TUI_BP_DISABLED)
712 element[TUI_BP_BREAK_POS] = '-';
713
714 if (src_element->is_exec_point)
715 element[TUI_EXEC_POS] = '>';
716
717 mvwaddstr (handle.get (), i + box_width (), box_width (), element);
718
719 show_line_number (i);
720 }
721 if (refresh_p)
722 refresh_window ();
723 }
724