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