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