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