[gdb/tui] Fix segfault in tui_find_disassembly_address

PR29040 describes a FAIL for test-case gdb.threads/next-fork-other-thread.exp
and target board unix/-m32.

The FAIL happens due to the test executable running into an assert, which is
caused by a forked child segfaulting, like so:
...
 Program terminated with signal SIGSEGV, Segmentation fault.
 #0  0x00000000 in ?? ()
...

I tried to reproduce the segfault with exec next-fork-other-thread-fork, using
TUI layout asm.

I set a breakpoint at fork and ran to the breakpoint, and somewhere during the
following session I ran into a gdb segfault here in
tui_find_disassembly_address:
...
	  /* Disassemble forward.  */
	  next_addr = tui_disassemble (gdbarch, asm_lines, new_low, max_lines);
	  last_addr = asm_lines.back ().addr;
...
due to asm_lines being empty after the call to tui_disassemble, while
asm_lines.back () assumes that it's not empty.

I have not been able to reproduce that segfault in that original setting, I'm
not sure of the exact scenario (though looking back it probably involved
"set detach-on-fork off").

What likely happened is that I managed to reproduce PR29040, and TUI (attempted
to) display the disassembly for address 0, which led to the gdb segfault.

When gdb_print_insn encounters an insn it cannot print because it can't read
the memory, it throws a MEMORY_ERROR that is caught by tui_disassemble.

The specific bit that causes the gdb segfault is that if gdb_print_insn throws
a MEMORY_ERROR for the first insn in tui_disassemble, it returns an empty
asm_lines.

FWIW, I did manage to reproduce the gdb segfault as follows:
...
$ gdb -q \
    -iex "set pagination off" \
    /usr/bin/rustc \
    -ex "set breakpoint pending on" \
    -ex "b dl_main" \
    -ex run \
    -ex "up 4" \
    -ex "layout asm" \
    -ex "print \$pc"
  ...
<TUI>
  ...
$1 = (void (*)()) 0x1
(gdb)
...
Now press <up>, and the segfault triggers.

Fix the segfault by handling asm_lines.empty () results of tui_disassemble in
tui_find_disassembly_address.

I've written a unit test that exercises this scenario.

Tested on x86_64-linux.

Reviewed-by: Kevin Buettner <kevinb@redhat.com>

PR tui/30823
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=30823
This commit is contained in:
Tom de Vries
2023-09-28 20:17:33 +02:00
parent 4ef9b04fa6
commit 72535eb14b

View File

@@ -41,6 +41,8 @@
#include "objfiles.h" #include "objfiles.h"
#include "cli/cli-style.h" #include "cli/cli-style.h"
#include "tui/tui-location.h" #include "tui/tui-location.h"
#include "gdbsupport/selftest.h"
#include "inferior.h"
#include "gdb_curses.h" #include "gdb_curses.h"
@@ -203,6 +205,8 @@ tui_find_disassembly_address (struct gdbarch *gdbarch, CORE_ADDR pc, int from)
instruction fails to disassemble we will take the address of the instruction fails to disassemble we will take the address of the
previous instruction that did disassemble as the result. */ previous instruction that did disassemble as the result. */
tui_disassemble (gdbarch, asm_lines, pc, max_lines + 1); tui_disassemble (gdbarch, asm_lines, pc, max_lines + 1);
if (asm_lines.empty ())
return pc;
new_low = asm_lines.back ().addr; new_low = asm_lines.back ().addr;
} }
else else
@@ -244,6 +248,8 @@ tui_find_disassembly_address (struct gdbarch *gdbarch, CORE_ADDR pc, int from)
/* Disassemble forward. */ /* Disassemble forward. */
next_addr = tui_disassemble (gdbarch, asm_lines, new_low, max_lines); next_addr = tui_disassemble (gdbarch, asm_lines, new_low, max_lines);
if (asm_lines.empty ())
break;
last_addr = asm_lines.back ().addr; last_addr = asm_lines.back ().addr;
/* If disassembling from the current value of NEW_LOW reached PC /* If disassembling from the current value of NEW_LOW reached PC
@@ -522,3 +528,36 @@ tui_disasm_window::display_start_addr (struct gdbarch **gdbarch_p,
*gdbarch_p = m_gdbarch; *gdbarch_p = m_gdbarch;
*addr_p = m_start_line_or_addr.u.addr; *addr_p = m_start_line_or_addr.u.addr;
} }
#if GDB_SELF_TEST
namespace selftests {
namespace tui {
namespace disasm {
static void
run_tests ()
{
if (current_inferior () != nullptr)
{
struct gdbarch *gdbarch = current_inferior ()->gdbarch;
/* Check that tui_find_disassembly_address robustly handles the case of
being passed a PC for which gdb_print_insn throws a MEMORY_ERROR. */
SELF_CHECK (tui_find_disassembly_address (gdbarch, 0, 1) == 0);
SELF_CHECK (tui_find_disassembly_address (gdbarch, 0, -1) == 0);
}
}
} /* namespace disasm */
} /* namespace tui */
} /* namespace selftests */
#endif /* GDB_SELF_TEST */
void _initialize_tui_disasm ();
void
_initialize_tui_disasm ()
{
#if GDB_SELF_TEST
selftests::register_test ("tui-disasm", selftests::tui::disasm::run_tests);
#endif
}