[gdb/tui] Make assert in tui_find_disassembly_address more strict
[binutils-gdb.git] / gdb / tui / tui-disasm.c
1 /* Disassembly display.
2
3 Copyright (C) 1998-2023 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 "defs.h"
23 #include "arch-utils.h"
24 #include "symtab.h"
25 #include "breakpoint.h"
26 #include "frame.h"
27 #include "value.h"
28 #include "source.h"
29 #include "disasm.h"
30 #include "tui/tui.h"
31 #include "tui/tui-command.h"
32 #include "tui/tui-data.h"
33 #include "tui/tui-win.h"
34 #include "tui/tui-layout.h"
35 #include "tui/tui-winsource.h"
36 #include "tui/tui-stack.h"
37 #include "tui/tui-file.h"
38 #include "tui/tui-disasm.h"
39 #include "tui/tui-source.h"
40 #include "progspace.h"
41 #include "objfiles.h"
42 #include "cli/cli-style.h"
43 #include "tui/tui-location.h"
44 #include "gdbsupport/selftest.h"
45 #include "inferior.h"
46
47 #include "gdb_curses.h"
48
49 struct tui_asm_line
50 {
51 CORE_ADDR addr;
52 std::string addr_string;
53 size_t addr_size;
54 std::string insn;
55 };
56
57 /* Helper function to find the number of characters in STR, skipping
58 any ANSI escape sequences. */
59 static size_t
60 len_without_escapes (const std::string &str)
61 {
62 size_t len = 0;
63 const char *ptr = str.c_str ();
64 char c;
65
66 while ((c = *ptr) != '\0')
67 {
68 if (c == '\033')
69 {
70 ui_file_style style;
71 size_t n_read;
72 if (style.parse (ptr, &n_read))
73 ptr += n_read;
74 else
75 {
76 /* Shouldn't happen, but just skip the ESC if it somehow
77 does. */
78 ++ptr;
79 }
80 }
81 else
82 {
83 ++len;
84 ++ptr;
85 }
86 }
87 return len;
88 }
89
90 /* Function to disassemble up to COUNT instructions starting from address
91 PC into the ASM_LINES vector (which will be emptied of any previous
92 contents). Return the address of the COUNT'th instruction after pc.
93 When ADDR_SIZE is non-null then place the maximum size of an address and
94 label into the value pointed to by ADDR_SIZE, and set the addr_size
95 field on each item in ASM_LINES, otherwise the addr_size fields within
96 ASM_LINES are undefined.
97
98 It is worth noting that ASM_LINES might not have COUNT entries when this
99 function returns. If the disassembly is truncated for some other
100 reason, for example, we hit invalid memory, then ASM_LINES can have
101 fewer entries than requested. */
102 static CORE_ADDR
103 tui_disassemble (struct gdbarch *gdbarch,
104 std::vector<tui_asm_line> &asm_lines,
105 CORE_ADDR pc, int count,
106 size_t *addr_size = nullptr)
107 {
108 bool term_out = source_styling && gdb_stdout->can_emit_style_escape ();
109 string_file gdb_dis_out (term_out);
110
111 /* Must start with an empty list. */
112 asm_lines.clear ();
113
114 /* Now construct each line. */
115 for (int i = 0; i < count; ++i)
116 {
117 tui_asm_line tal;
118 CORE_ADDR orig_pc = pc;
119
120 try
121 {
122 pc = pc + gdb_print_insn (gdbarch, pc, &gdb_dis_out, NULL);
123 }
124 catch (const gdb_exception_error &except)
125 {
126 /* If PC points to an invalid address then we'll catch a
127 MEMORY_ERROR here, this should stop the disassembly, but
128 otherwise is fine. */
129 if (except.error != MEMORY_ERROR)
130 throw;
131 return pc;
132 }
133
134 /* Capture the disassembled instruction. */
135 tal.insn = gdb_dis_out.release ();
136
137 /* And capture the address the instruction is at. */
138 tal.addr = orig_pc;
139 print_address (gdbarch, orig_pc, &gdb_dis_out);
140 tal.addr_string = gdb_dis_out.release ();
141
142 if (addr_size != nullptr)
143 {
144 size_t new_size;
145
146 if (term_out)
147 new_size = len_without_escapes (tal.addr_string);
148 else
149 new_size = tal.addr_string.size ();
150 *addr_size = std::max (*addr_size, new_size);
151 tal.addr_size = new_size;
152 }
153
154 asm_lines.push_back (std::move (tal));
155 }
156 return pc;
157 }
158
159 /* Look backward from ADDR for an address from which we can start
160 disassembling, this needs to be something we can be reasonably
161 confident will fall on an instruction boundary. We use msymbol
162 addresses, or the start of a section. */
163
164 static CORE_ADDR
165 tui_find_backward_disassembly_start_address (CORE_ADDR addr)
166 {
167 struct bound_minimal_symbol msym, msym_prev;
168
169 msym = lookup_minimal_symbol_by_pc_section (addr - 1, nullptr,
170 lookup_msym_prefer::TEXT,
171 &msym_prev);
172 if (msym.minsym != nullptr)
173 return msym.value_address ();
174 else if (msym_prev.minsym != nullptr)
175 return msym_prev.value_address ();
176
177 /* Find the section that ADDR is in, and look for the start of the
178 section. */
179 struct obj_section *section = find_pc_section (addr);
180 if (section != NULL)
181 return section->addr ();
182
183 return addr;
184 }
185
186 /* Find the disassembly address that corresponds to FROM lines above
187 or below the PC. Variable sized instructions are taken into
188 account by the algorithm. */
189 static CORE_ADDR
190 tui_find_disassembly_address (struct gdbarch *gdbarch, CORE_ADDR pc, int from)
191 {
192 CORE_ADDR new_low;
193 int max_lines;
194
195 max_lines = (from > 0) ? from : - from;
196 if (max_lines == 0)
197 return pc;
198
199 std::vector<tui_asm_line> asm_lines;
200
201 new_low = pc;
202 if (from > 0)
203 {
204 /* Always disassemble 1 extra instruction here, then if the last
205 instruction fails to disassemble we will take the address of the
206 previous instruction that did disassemble as the result. */
207 tui_disassemble (gdbarch, asm_lines, pc, max_lines + 1);
208 if (asm_lines.empty ())
209 return pc;
210 new_low = asm_lines.back ().addr;
211 }
212 else
213 {
214 /* In order to disassemble backwards we need to find a suitable
215 address to start disassembling from and then work forward until we
216 re-find the address we're currently at. We can then figure out
217 which address will be at the top of the TUI window after our
218 backward scroll. During our backward disassemble we need to be
219 able to distinguish between the case where the last address we
220 _can_ disassemble is ADDR, and the case where the disassembly
221 just happens to stop at ADDR, for this reason we increase
222 MAX_LINES by one. */
223 max_lines++;
224
225 /* When we disassemble a series of instructions this will hold the
226 address of the last instruction disassembled. */
227 CORE_ADDR last_addr;
228
229 /* And this will hold the address of the next instruction that would
230 have been disassembled. */
231 CORE_ADDR next_addr;
232
233 /* As we search backward if we find an address that looks like a
234 promising starting point then we record it in this structure. If
235 the next address we try is not a suitable starting point then we
236 will fall back to the address held here. */
237 gdb::optional<CORE_ADDR> possible_new_low;
238
239 /* The previous value of NEW_LOW so we know if the new value is
240 different or not. */
241 CORE_ADDR prev_low;
242
243 do
244 {
245 /* Find an address from which we can start disassembling. */
246 prev_low = new_low;
247 new_low = tui_find_backward_disassembly_start_address (new_low);
248
249 /* Disassemble forward. */
250 next_addr = tui_disassemble (gdbarch, asm_lines, new_low, max_lines);
251 if (asm_lines.empty ())
252 break;
253 last_addr = asm_lines.back ().addr;
254
255 /* If disassembling from the current value of NEW_LOW reached PC
256 (or went past it) then this would do as a starting point if we
257 can't find anything better, so remember it. */
258 if (last_addr >= pc && new_low != prev_low
259 && asm_lines.size () >= max_lines)
260 possible_new_low.emplace (new_low);
261
262 /* Continue searching until we find a value of NEW_LOW from which
263 disassembling MAX_LINES instructions doesn't reach PC. We
264 know this means we can find the required number of previous
265 instructions then. */
266 }
267 while ((last_addr > pc
268 || (last_addr == pc && asm_lines.size () < max_lines))
269 && new_low != prev_low);
270
271 /* If we failed to disassemble the required number of lines, try to fall
272 back to a previous possible start address in POSSIBLE_NEW_LOW. */
273 if (asm_lines.size () < max_lines)
274 {
275 if (!possible_new_low.has_value ())
276 return new_low;
277
278 /* Take the best possible match we have. */
279 new_low = *possible_new_low;
280 next_addr = tui_disassemble (gdbarch, asm_lines, new_low, max_lines);
281 last_addr = asm_lines.back ().addr;
282 }
283
284 /* The following walk forward assumes that ASM_LINES contains exactly
285 MAX_LINES entries. */
286 gdb_assert (asm_lines.size () == max_lines);
287
288 /* Scan forward disassembling one instruction at a time until
289 the last visible instruction of the window matches the pc.
290 We keep the disassembled instructions in the 'lines' window
291 and shift it downward (increasing its addresses). */
292 int pos = max_lines - 1;
293 if (last_addr < pc)
294 do
295 {
296 pos++;
297 if (pos >= max_lines)
298 pos = 0;
299
300 CORE_ADDR old_next_addr = next_addr;
301 std::vector<tui_asm_line> single_asm_line;
302 next_addr = tui_disassemble (gdbarch, single_asm_line,
303 next_addr, 1);
304 /* If there are some problems while disassembling exit. */
305 if (next_addr <= old_next_addr)
306 return pc;
307 gdb_assert (single_asm_line.size () == 1);
308 asm_lines[pos] = single_asm_line[0];
309 } while (next_addr <= pc);
310 pos++;
311 if (pos >= max_lines)
312 pos = 0;
313 new_low = asm_lines[pos].addr;
314
315 /* When scrolling backward the addresses should move backward, or at
316 the very least stay the same if we are at the first address that
317 can be disassembled. */
318 gdb_assert (new_low <= pc);
319 }
320 return new_low;
321 }
322
323 /* Function to set the disassembly window's content. */
324 bool
325 tui_disasm_window::set_contents (struct gdbarch *arch,
326 const struct symtab_and_line &sal)
327 {
328 int i;
329 int max_lines;
330 CORE_ADDR cur_pc;
331 int tab_len = tui_tab_width;
332 int insn_pos;
333
334 CORE_ADDR pc = sal.pc;
335 if (pc == 0)
336 return false;
337
338 m_gdbarch = arch;
339 m_start_line_or_addr.loa = LOA_ADDRESS;
340 m_start_line_or_addr.u.addr = pc;
341 cur_pc = tui_location.addr ();
342
343 /* Window size, excluding highlight box. */
344 max_lines = height - 2;
345
346 /* Get temporary table that will hold all strings (addr & insn). */
347 std::vector<tui_asm_line> asm_lines;
348 size_t addr_size = 0;
349 tui_disassemble (m_gdbarch, asm_lines, pc, max_lines, &addr_size);
350
351 /* Align instructions to the same column. */
352 insn_pos = (1 + (addr_size / tab_len)) * tab_len;
353
354 /* Now construct each line. */
355 m_content.resize (max_lines);
356 m_max_length = -1;
357 for (i = 0; i < max_lines; i++)
358 {
359 tui_source_element *src = &m_content[i];
360
361 std::string line;
362 CORE_ADDR addr;
363
364 if (i < asm_lines.size ())
365 {
366 line
367 = (asm_lines[i].addr_string
368 + n_spaces (insn_pos - asm_lines[i].addr_size)
369 + asm_lines[i].insn);
370 addr = asm_lines[i].addr;
371 }
372 else
373 {
374 line = "";
375 addr = 0;
376 }
377
378 const char *ptr = line.c_str ();
379 int line_len;
380 src->line = tui_copy_source_line (&ptr, &line_len);
381 m_max_length = std::max (m_max_length, line_len);
382
383 src->line_or_addr.loa = LOA_ADDRESS;
384 src->line_or_addr.u.addr = addr;
385 src->is_exec_point = (addr == cur_pc && line.size () > 0);
386 }
387 return true;
388 }
389
390
391 void
392 tui_get_begin_asm_address (struct gdbarch **gdbarch_p, CORE_ADDR *addr_p)
393 {
394 struct gdbarch *gdbarch = get_current_arch ();
395 CORE_ADDR addr = 0;
396
397 if (tui_location.addr () == 0)
398 {
399 if (have_full_symbols () || have_partial_symbols ())
400 {
401 set_default_source_symtab_and_line ();
402 struct symtab_and_line sal = get_current_source_symtab_and_line ();
403
404 if (sal.symtab != nullptr)
405 find_line_pc (sal.symtab, sal.line, &addr);
406 }
407
408 if (addr == 0)
409 {
410 struct bound_minimal_symbol main_symbol
411 = lookup_minimal_symbol (main_name (), nullptr, nullptr);
412 if (main_symbol.minsym != nullptr)
413 addr = main_symbol.value_address ();
414 }
415 }
416 else /* The target is executing. */
417 {
418 gdbarch = tui_location.gdbarch ();
419 addr = tui_location.addr ();
420 }
421
422 *gdbarch_p = gdbarch;
423 *addr_p = addr;
424 }
425
426 /* Determine what the low address will be to display in the TUI's
427 disassembly window. This may or may not be the same as the low
428 address input. */
429 CORE_ADDR
430 tui_get_low_disassembly_address (struct gdbarch *gdbarch,
431 CORE_ADDR low, CORE_ADDR pc)
432 {
433 int pos;
434
435 /* Determine where to start the disassembly so that the pc is about
436 in the middle of the viewport. */
437 if (TUI_DISASM_WIN != NULL)
438 pos = TUI_DISASM_WIN->height;
439 else if (TUI_CMD_WIN == NULL)
440 pos = tui_term_height () / 2 - 2;
441 else
442 pos = tui_term_height () - TUI_CMD_WIN->height - 2;
443 pos = (pos - 2) / 2;
444
445 pc = tui_find_disassembly_address (gdbarch, pc, -pos);
446
447 if (pc < low)
448 pc = low;
449 return pc;
450 }
451
452 /* Scroll the disassembly forward or backward vertically. */
453 void
454 tui_disasm_window::do_scroll_vertical (int num_to_scroll)
455 {
456 if (!m_content.empty ())
457 {
458 CORE_ADDR pc;
459
460 pc = m_start_line_or_addr.u.addr;
461
462 symtab_and_line sal {};
463 sal.pspace = current_program_space;
464 sal.pc = tui_find_disassembly_address (m_gdbarch, pc, num_to_scroll);
465 update_source_window_as_is (m_gdbarch, sal);
466 }
467 }
468
469 bool
470 tui_disasm_window::location_matches_p (struct bp_location *loc, int line_no)
471 {
472 return (m_content[line_no].line_or_addr.loa == LOA_ADDRESS
473 && m_content[line_no].line_or_addr.u.addr == loc->address);
474 }
475
476 bool
477 tui_disasm_window::addr_is_displayed (CORE_ADDR addr) const
478 {
479 if (m_content.size () < SCROLL_THRESHOLD)
480 return false;
481
482 for (size_t i = 0; i < m_content.size () - SCROLL_THRESHOLD; ++i)
483 {
484 if (m_content[i].line_or_addr.loa == LOA_ADDRESS
485 && m_content[i].line_or_addr.u.addr == addr)
486 return true;
487 }
488
489 return false;
490 }
491
492 void
493 tui_disasm_window::maybe_update (frame_info_ptr fi, symtab_and_line sal)
494 {
495 CORE_ADDR low;
496
497 struct gdbarch *frame_arch = get_frame_arch (fi);
498
499 if (find_pc_partial_function (sal.pc, NULL, &low, NULL) == 0)
500 {
501 /* There is no symbol available for current PC. There is no
502 safe way how to "disassemble backwards". */
503 low = sal.pc;
504 }
505 else
506 low = tui_get_low_disassembly_address (frame_arch, low, sal.pc);
507
508 struct tui_line_or_address a;
509
510 a.loa = LOA_ADDRESS;
511 a.u.addr = low;
512 if (!addr_is_displayed (sal.pc))
513 {
514 sal.pc = low;
515 update_source_window (frame_arch, sal);
516 }
517 else
518 {
519 a.u.addr = sal.pc;
520 set_is_exec_point_at (a);
521 }
522 }
523
524 void
525 tui_disasm_window::display_start_addr (struct gdbarch **gdbarch_p,
526 CORE_ADDR *addr_p)
527 {
528 *gdbarch_p = m_gdbarch;
529 *addr_p = m_start_line_or_addr.u.addr;
530 }
531
532 #if GDB_SELF_TEST
533 namespace selftests {
534 namespace tui {
535 namespace disasm {
536
537 static void
538 run_tests ()
539 {
540 if (current_inferior () != nullptr)
541 {
542 gdbarch *gdbarch = current_inferior ()->arch ();
543
544 /* Check that tui_find_disassembly_address robustly handles the case of
545 being passed a PC for which gdb_print_insn throws a MEMORY_ERROR. */
546 SELF_CHECK (tui_find_disassembly_address (gdbarch, 0, 1) == 0);
547 SELF_CHECK (tui_find_disassembly_address (gdbarch, 0, -1) == 0);
548 }
549 }
550
551 } /* namespace disasm */
552 } /* namespace tui */
553 } /* namespace selftests */
554 #endif /* GDB_SELF_TEST */
555
556 void _initialize_tui_disasm ();
557 void
558 _initialize_tui_disasm ()
559 {
560 #if GDB_SELF_TEST
561 selftests::register_test ("tui-disasm", selftests::tui::disasm::run_tests);
562 #endif
563 }