mirror of
https://sourceware.org/git/binutils-gdb.git
synced 2024-12-21 04:42:53 +08:00
8b39b1e7ab
This commit started from an observation I made while working on some other disassembler patches, that is, that the function gdb_buffered_insn_length, is broken ... sort of. I noticed that the gdb_buffered_insn_length function doesn't set up the application data field if the disassemble_info structure. Further, I noticed that some architectures, for example, ARM, require that the application_data field be set, see gdb_print_insn_arm in arm-tdep.c. And so, if we ever use gdb_buffered_insn_length for ARM, then GDB will likely crash. Which is why I said only "sort of" broken. Right now we don't use gdb_buffered_insn_length with ARM, so maybe it isn't broken yet? Anyway to prove to myself that there was a problem here I extended the disassembler self tests in disasm-selftests.c to include a test of gdb_buffered_insn_length. As I run the test for all architectures, I do indeed see GDB crash for ARM. To fix this we need gdb_buffered_insn_length to create a disassembler that inherits from gdb_disassemble_info, but we also need this new disassembler to not print anything. And so, I introduce a new gdb_non_printing_disassembler class, this is a disassembler that doesn't print anything to the output stream. I then observed that both ARC and S12Z also create non-printing disassemblers, but these are slightly different. While the disassembler in gdb_non_printing_disassembler reads the instruction from a buffer, the ARC and S12Z disassemblers read from target memory using target_read_code. And so, I further split gdb_non_printing_disassembler into two sub-classes, gdb_non_printing_memory_disassembler and gdb_non_printing_buffer_disassembler. The new selftests now pass, but otherwise, there should be no user visible changes after this commit.
346 lines
9.3 KiB
C
346 lines
9.3 KiB
C
/* Self tests for disassembler for GDB, the GNU debugger.
|
|
|
|
Copyright (C) 2017-2022 Free Software Foundation, Inc.
|
|
|
|
This file is part of GDB.
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
|
|
|
#include "defs.h"
|
|
#include "disasm.h"
|
|
#include "gdbsupport/selftest.h"
|
|
#include "selftest-arch.h"
|
|
#include "gdbarch.h"
|
|
|
|
namespace selftests {
|
|
|
|
/* Return a pointer to a buffer containing an instruction that can be
|
|
disassembled for architecture GDBARCH. *LEN will be set to the length
|
|
of the returned buffer.
|
|
|
|
If there's no known instruction to disassemble for GDBARCH (because we
|
|
haven't figured on out, not because no instructions exist) then nullptr
|
|
is returned, and *LEN is set to 0. */
|
|
|
|
static const gdb_byte *
|
|
get_test_insn (struct gdbarch *gdbarch, size_t *len)
|
|
{
|
|
*len = 0;
|
|
const gdb_byte *insn = nullptr;
|
|
|
|
switch (gdbarch_bfd_arch_info (gdbarch)->arch)
|
|
{
|
|
case bfd_arch_bfin:
|
|
/* M3.L = 0xe117 */
|
|
static const gdb_byte bfin_insn[] = {0x17, 0xe1, 0xff, 0xff};
|
|
|
|
insn = bfin_insn;
|
|
*len = sizeof (bfin_insn);
|
|
break;
|
|
case bfd_arch_arm:
|
|
/* mov r0, #0 */
|
|
static const gdb_byte arm_insn[] = {0x0, 0x0, 0xa0, 0xe3};
|
|
|
|
insn = arm_insn;
|
|
*len = sizeof (arm_insn);
|
|
break;
|
|
case bfd_arch_ia64:
|
|
/* We get:
|
|
internal-error: gdbarch_sw_breakpoint_from_kind:
|
|
Assertion `gdbarch->sw_breakpoint_from_kind != NULL' failed. */
|
|
return insn;
|
|
case bfd_arch_mep:
|
|
/* Disassembles as '*unknown*' insn, then len self-check fails. */
|
|
return insn;
|
|
case bfd_arch_mips:
|
|
if (gdbarch_bfd_arch_info (gdbarch)->mach == bfd_mach_mips16)
|
|
/* Disassembles insn, but len self-check fails. */
|
|
return insn;
|
|
goto generic_case;
|
|
case bfd_arch_tic6x:
|
|
/* Disassembles as '<undefined instruction 0x56454314>' insn, but len
|
|
self-check passes, so let's allow it. */
|
|
goto generic_case;
|
|
case bfd_arch_xtensa:
|
|
/* Disassembles insn, but len self-check fails. */
|
|
return insn;
|
|
case bfd_arch_or1k:
|
|
/* Disassembles as '*unknown*' insn, but len self-check passes, so let's
|
|
allow it. */
|
|
goto generic_case;
|
|
case bfd_arch_s390:
|
|
/* nopr %r7 */
|
|
static const gdb_byte s390_insn[] = {0x07, 0x07};
|
|
|
|
insn = s390_insn;
|
|
*len = sizeof (s390_insn);
|
|
break;
|
|
case bfd_arch_xstormy16:
|
|
/* nop */
|
|
static const gdb_byte xstormy16_insn[] = {0x0, 0x0};
|
|
|
|
insn = xstormy16_insn;
|
|
*len = sizeof (xstormy16_insn);
|
|
break;
|
|
case bfd_arch_nios2:
|
|
case bfd_arch_score:
|
|
case bfd_arch_riscv:
|
|
/* nios2, riscv, and score need to know the current instruction
|
|
to select breakpoint instruction. Give the breakpoint
|
|
instruction kind explicitly. */
|
|
{
|
|
int bplen;
|
|
insn = gdbarch_sw_breakpoint_from_kind (gdbarch, 4, &bplen);
|
|
*len = bplen;
|
|
}
|
|
break;
|
|
case bfd_arch_arc:
|
|
/* PR 21003 */
|
|
if (gdbarch_bfd_arch_info (gdbarch)->mach == bfd_mach_arc_arc601)
|
|
return insn;
|
|
goto generic_case;
|
|
case bfd_arch_z80:
|
|
{
|
|
int bplen;
|
|
insn = gdbarch_sw_breakpoint_from_kind (gdbarch, 0x0008, &bplen);
|
|
*len = bplen;
|
|
}
|
|
break;
|
|
case bfd_arch_i386:
|
|
{
|
|
const struct bfd_arch_info *info = gdbarch_bfd_arch_info (gdbarch);
|
|
/* The disassembly tests will fail on x86-linux because
|
|
opcodes rejects an attempt to disassemble for an arch with
|
|
a 64-bit address size when bfd_vma is 32-bit. */
|
|
if (info->bits_per_address > sizeof (bfd_vma) * CHAR_BIT)
|
|
return insn;
|
|
}
|
|
/* fall through */
|
|
default:
|
|
generic_case:
|
|
{
|
|
/* Test disassemble breakpoint instruction. */
|
|
CORE_ADDR pc = 0;
|
|
int kind;
|
|
int bplen;
|
|
|
|
struct gdbarch_info info;
|
|
info.bfd_arch_info = gdbarch_bfd_arch_info (gdbarch);
|
|
|
|
enum gdb_osabi it;
|
|
bool found = false;
|
|
for (it = GDB_OSABI_UNKNOWN; it != GDB_OSABI_INVALID;
|
|
it = static_cast<enum gdb_osabi>(static_cast<int>(it) + 1))
|
|
{
|
|
if (it == GDB_OSABI_UNKNOWN)
|
|
continue;
|
|
|
|
info.osabi = it;
|
|
|
|
if (it != GDB_OSABI_NONE)
|
|
{
|
|
if (!has_gdb_osabi_handler (info))
|
|
/* Unsupported. Skip to prevent warnings like:
|
|
A handler for the OS ABI <x> is not built into this
|
|
configuration of GDB. Attempting to continue with the
|
|
default <y> settings. */
|
|
continue;
|
|
}
|
|
|
|
gdbarch = gdbarch_find_by_info (info);
|
|
SELF_CHECK (gdbarch != NULL);
|
|
|
|
try
|
|
{
|
|
kind = gdbarch_breakpoint_kind_from_pc (gdbarch, &pc);
|
|
insn = gdbarch_sw_breakpoint_from_kind (gdbarch, kind, &bplen);
|
|
}
|
|
catch (...)
|
|
{
|
|
continue;
|
|
}
|
|
found = true;
|
|
break;
|
|
}
|
|
|
|
/* Assert that we have found an instruction to disassemble. */
|
|
SELF_CHECK (found);
|
|
|
|
*len = bplen;
|
|
break;
|
|
}
|
|
}
|
|
SELF_CHECK (*len > 0);
|
|
|
|
return insn;
|
|
}
|
|
|
|
/* Test disassembly of one instruction. */
|
|
|
|
static void
|
|
print_one_insn_test (struct gdbarch *gdbarch)
|
|
{
|
|
size_t len;
|
|
const gdb_byte *insn = get_test_insn (gdbarch, &len);
|
|
|
|
if (insn == nullptr)
|
|
return;
|
|
|
|
/* Test gdb_disassembler for a given gdbarch by reading data from a
|
|
pre-allocated buffer. If you want to see the disassembled
|
|
instruction printed to gdb_stdout, use maint selftest -verbose. */
|
|
|
|
class gdb_disassembler_test : public gdb_disassembler
|
|
{
|
|
public:
|
|
|
|
explicit gdb_disassembler_test (struct gdbarch *gdbarch,
|
|
const gdb_byte *insn,
|
|
size_t len)
|
|
: gdb_disassembler (gdbarch,
|
|
(run_verbose () ? gdb_stdlog : &null_stream),
|
|
gdb_disassembler_test::read_memory),
|
|
m_insn (insn), m_len (len)
|
|
{
|
|
}
|
|
|
|
int
|
|
print_insn (CORE_ADDR memaddr)
|
|
{
|
|
int len = gdb_disassembler::print_insn (memaddr);
|
|
|
|
if (run_verbose ())
|
|
debug_printf ("\n");
|
|
|
|
return len;
|
|
}
|
|
|
|
private:
|
|
/* A buffer contain one instruction. */
|
|
const gdb_byte *m_insn;
|
|
|
|
/* Length of the buffer. */
|
|
size_t m_len;
|
|
|
|
static int read_memory (bfd_vma memaddr, gdb_byte *myaddr,
|
|
unsigned int len, struct disassemble_info *info)
|
|
{
|
|
gdb_disassembler_test *self
|
|
= static_cast<gdb_disassembler_test *>(info->application_data);
|
|
|
|
/* The disassembler in opcodes may read more data than one
|
|
instruction. Supply infinite consecutive copies
|
|
of the same instruction. */
|
|
for (size_t i = 0; i < len; i++)
|
|
myaddr[i] = self->m_insn[(memaddr + i) % self->m_len];
|
|
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
gdb_disassembler_test di (gdbarch, insn, len);
|
|
|
|
SELF_CHECK (di.print_insn (0) == len);
|
|
}
|
|
|
|
/* Test the gdb_buffered_insn_length function. */
|
|
|
|
static void
|
|
buffered_insn_length_test (struct gdbarch *gdbarch)
|
|
{
|
|
size_t buf_len;
|
|
const gdb_byte *insn = get_test_insn (gdbarch, &buf_len);
|
|
|
|
if (insn == nullptr)
|
|
return;
|
|
|
|
/* The tic6x architecture is VLIW. Disassembling requires that the
|
|
entire instruction bundle be available. However, the buffer we got
|
|
back from get_test_insn only contains a single instruction, which is
|
|
just part of an instruction bundle. As a result, the disassemble will
|
|
fail. To avoid this, skip tic6x tests now. */
|
|
if (gdbarch_bfd_arch_info (gdbarch)->arch == bfd_arch_tic6x)
|
|
return;
|
|
|
|
CORE_ADDR insn_address = 0;
|
|
int calculated_len = gdb_buffered_insn_length (gdbarch, insn, buf_len,
|
|
insn_address);
|
|
|
|
SELF_CHECK (calculated_len == buf_len);
|
|
}
|
|
|
|
/* Test disassembly on memory error. */
|
|
|
|
static void
|
|
memory_error_test (struct gdbarch *gdbarch)
|
|
{
|
|
class gdb_disassembler_test : public gdb_disassembler
|
|
{
|
|
public:
|
|
gdb_disassembler_test (struct gdbarch *gdbarch)
|
|
: gdb_disassembler (gdbarch, &null_stream,
|
|
gdb_disassembler_test::read_memory)
|
|
{
|
|
}
|
|
|
|
static int read_memory (bfd_vma memaddr, gdb_byte *myaddr,
|
|
unsigned int len,
|
|
struct disassemble_info *info)
|
|
{
|
|
/* Always return an error. */
|
|
return -1;
|
|
}
|
|
};
|
|
|
|
if (gdbarch_bfd_arch_info (gdbarch)->arch == bfd_arch_i386)
|
|
{
|
|
const struct bfd_arch_info *info = gdbarch_bfd_arch_info (gdbarch);
|
|
/* This test will fail on x86-linux because opcodes rejects an
|
|
attempt to disassemble for an arch with a 64-bit address size
|
|
when bfd_vma is 32-bit. */
|
|
if (info->bits_per_address > sizeof (bfd_vma) * CHAR_BIT)
|
|
return;
|
|
}
|
|
|
|
gdb_disassembler_test di (gdbarch);
|
|
bool saw_memory_error = false;
|
|
|
|
try
|
|
{
|
|
di.print_insn (0);
|
|
}
|
|
catch (const gdb_exception_error &ex)
|
|
{
|
|
if (ex.error == MEMORY_ERROR)
|
|
saw_memory_error = true;
|
|
}
|
|
|
|
/* Expect MEMORY_ERROR. */
|
|
SELF_CHECK (saw_memory_error);
|
|
}
|
|
|
|
} // namespace selftests
|
|
|
|
void _initialize_disasm_selftests ();
|
|
void
|
|
_initialize_disasm_selftests ()
|
|
{
|
|
selftests::register_test_foreach_arch ("print_one_insn",
|
|
selftests::print_one_insn_test);
|
|
selftests::register_test_foreach_arch ("memory_error",
|
|
selftests::memory_error_test);
|
|
selftests::register_test_foreach_arch ("buffered_insn_length",
|
|
selftests::buffered_insn_length_test);
|
|
}
|