arc: Add disassembler helper

Add disassembler helper for GDB, that uses opcodes structure arc_instruction
and adds convenience functions to handle instruction operands.  This interface
solves at least those problems with arc_instruction:

  * Some instructions, like "push_s", have implicit operands which are not
    directly present in arc_instruction.
  * Operands of particular meaning, like branch/jump targets, have various
    locations and meaning depending on type of branch/target.
  * Access to operand value is abstracted into a separate function, so callee
    code shouldn't bother if operand value is an immediate value or in a
    register.

Testcases included in this commit are fairly limited - they test exclusively
branch instructions, something that will be used in software single stepping.
Most of the other parts of this disassembler helper are tested during prologue
analysis testing.

gdb/ChangeLog:

yyyy-mm-dd  Anton Kolesov  <anton.kolesov@synopsys.com>

	* configure.tgt: Add arc-insn.o.
	* arc-tdep.c (arc_delayed_print_insn): Make non-static.
	(dump_arc_instruction_command): New function.
	(arc_fprintf_disasm): Likewise.
	(arc_disassemble_info): Likewise.
	(arc_insn_get_operand_value): Likewise.
	(arc_insn_get_operand_value_signed): Likewise.
	(arc_insn_get_memory_base_reg): Likewise.
	(arc_insn_get_memory_offset): Likewise.
	(arc_insn_get_branch_target): Likewise.
	(arc_insn_dump): Likewise.
	(arc_insn_get_linear_next_pc): Likewise.
	* arc-tdep.h (arc_delayed_print_insn): Add function declaration.
	(arc_disassemble_info): Likewise.
	(arc_insn_get_branch_target): Likewise.
	(arc_insn_get_linear_next_pc): Likewise.
	* NEWS: Mention new "maint print arc arc-instruction".

gdb/doc/ChangeLog:

yyyy-mm-dd  Anton Kolesov  <anton.kolesov@synopsys.com>

	* gdb.texinfo (Synopsys ARC): Add "maint print arc arc-instruction".

gdb/testsuite/ChangeLog:

yyyy-mm-dd  Anton Kolesov  <anton.kolesov@synopsys.com>

	* gdb.arch/arc-decode-insn.S: New file.
	* gdb.arch/arc-decode-insn.exp: Likewise.
This commit is contained in:
Anton Kolesov
2017-02-10 14:12:06 +03:00
parent 3be78afded
commit eea787570f
9 changed files with 1509 additions and 1 deletions

View File

@@ -32,6 +32,7 @@
/* ARC header files. */
#include "opcode/arc.h"
#include "opcodes/arc-dis.h"
#include "arc-tdep.h"
/* Standard headers. */
@@ -116,6 +117,269 @@ static const char *const core_arcompact_register_names[] = {
"lp_count", "reserved", "limm", "pcl",
};
/* Returns an unsigned value of OPERAND_NUM in instruction INSN.
For relative branch instructions returned value is an offset, not an actual
branch target. */
static ULONGEST
arc_insn_get_operand_value (const struct arc_instruction &insn,
unsigned int operand_num)
{
switch (insn.operands[operand_num].kind)
{
case ARC_OPERAND_KIND_LIMM:
gdb_assert (insn.limm_p);
return insn.limm_value;
case ARC_OPERAND_KIND_SHIMM:
return insn.operands[operand_num].value;
default:
/* Value in instruction is a register number. */
struct regcache *regcache = get_current_regcache ();
ULONGEST value;
regcache_cooked_read_unsigned (regcache,
insn.operands[operand_num].value,
&value);
return value;
}
}
/* Like arc_insn_get_operand_value, but returns a signed value. */
static LONGEST
arc_insn_get_operand_value_signed (const struct arc_instruction &insn,
unsigned int operand_num)
{
switch (insn.operands[operand_num].kind)
{
case ARC_OPERAND_KIND_LIMM:
gdb_assert (insn.limm_p);
/* Convert unsigned raw value to signed one. This assumes 2's
complement arithmetic, but so is the LONG_MIN value from generic
defs.h and that assumption is true for ARC. */
gdb_static_assert (sizeof (insn.limm_value) == sizeof (int));
return (((LONGEST) insn.limm_value) ^ INT_MIN) - INT_MIN;
case ARC_OPERAND_KIND_SHIMM:
/* Sign conversion has been done by binutils. */
return insn.operands[operand_num].value;
default:
/* Value in instruction is a register number. */
struct regcache *regcache = get_current_regcache ();
LONGEST value;
regcache_cooked_read_signed (regcache,
insn.operands[operand_num].value,
&value);
return value;
}
}
/* Get register with base address of memory operation. */
int
arc_insn_get_memory_base_reg (const struct arc_instruction &insn)
{
/* POP_S and PUSH_S have SP as an implicit argument in a disassembler. */
if (insn.insn_class == PUSH || insn.insn_class == POP)
return ARC_SP_REGNUM;
gdb_assert (insn.insn_class == LOAD || insn.insn_class == STORE);
/* Other instructions all have at least two operands: operand 0 is data,
operand 1 is address. Operand 2 is offset from address. However, see
comment to arc_instruction.operands - in some cases, third operand may be
missing, namely if it is 0. */
gdb_assert (insn.operands_count >= 2);
return insn.operands[1].value;
}
/* Get offset of a memory operation INSN. */
CORE_ADDR
arc_insn_get_memory_offset (const struct arc_instruction &insn)
{
/* POP_S and PUSH_S have offset as an implicit argument in a
disassembler. */
if (insn.insn_class == POP)
return 4;
else if (insn.insn_class == PUSH)
return -4;
gdb_assert (insn.insn_class == LOAD || insn.insn_class == STORE);
/* Other instructions all have at least two operands: operand 0 is data,
operand 1 is address. Operand 2 is offset from address. However, see
comment to arc_instruction.operands - in some cases, third operand may be
missing, namely if it is 0. */
if (insn.operands_count < 3)
return 0;
CORE_ADDR value = arc_insn_get_operand_value (insn, 2);
/* Handle scaling. */
if (insn.writeback_mode == ARC_WRITEBACK_AS)
{
/* Byte data size is not valid for AS. Halfword means shift by 1 bit.
Word and double word means shift by 2 bits. */
gdb_assert (insn.data_size_mode != ARC_SCALING_B);
if (insn.data_size_mode == ARC_SCALING_H)
value <<= 1;
else
value <<= 2;
}
return value;
}
/* Functions are sorted in the order as they are used in the
_initialize_arc_tdep (), which uses the same order as gdbarch.h. Static
functions are defined before the first invocation. */
CORE_ADDR
arc_insn_get_branch_target (const struct arc_instruction &insn)
{
gdb_assert (insn.is_control_flow);
/* BI [c]: PC = nextPC + (c << 2). */
if (insn.insn_class == BI)
{
ULONGEST reg_value = arc_insn_get_operand_value (insn, 0);
return arc_insn_get_linear_next_pc (insn) + (reg_value << 2);
}
/* BIH [c]: PC = nextPC + (c << 1). */
else if (insn.insn_class == BIH)
{
ULONGEST reg_value = arc_insn_get_operand_value (insn, 0);
return arc_insn_get_linear_next_pc (insn) + (reg_value << 1);
}
/* JLI and EI. */
/* JLI and EI depend on optional AUX registers. Not supported right now. */
else if (insn.insn_class == JLI)
{
fprintf_unfiltered (gdb_stderr,
"JLI_S instruction is not supported by the GDB.");
return 0;
}
else if (insn.insn_class == EI)
{
fprintf_unfiltered (gdb_stderr,
"EI_S instruction is not supported by the GDB.");
return 0;
}
/* LEAVE_S: PC = BLINK. */
else if (insn.insn_class == LEAVE)
{
struct regcache *regcache = get_current_regcache ();
ULONGEST value;
regcache_cooked_read_unsigned (regcache, ARC_BLINK_REGNUM, &value);
return value;
}
/* BBIT0/1, BRcc: PC = currentPC + operand. */
else if (insn.insn_class == BBIT0 || insn.insn_class == BBIT1
|| insn.insn_class == BRCC)
{
/* Most instructions has branch target as their sole argument. However
conditional brcc/bbit has it as a third operand. */
CORE_ADDR pcrel_addr = arc_insn_get_operand_value (insn, 2);
/* Offset is relative to the 4-byte aligned address of the current
instruction, hence last two bits should be truncated. */
return pcrel_addr + align_down (insn.address, 4);
}
/* B, Bcc, BL, BLcc, LP, LPcc: PC = currentPC + operand. */
else if (insn.insn_class == BRANCH || insn.insn_class == LOOP)
{
CORE_ADDR pcrel_addr = arc_insn_get_operand_value (insn, 0);
/* Offset is relative to the 4-byte aligned address of the current
instruction, hence last two bits should be truncated. */
return pcrel_addr + align_down (insn.address, 4);
}
/* J, Jcc, JL, JLcc: PC = operand. */
else if (insn.insn_class == JUMP)
{
/* All jumps are single-operand. */
return arc_insn_get_operand_value (insn, 0);
}
/* This is some new and unknown instruction. */
gdb_assert_not_reached ("Unknown branch instruction.");
}
/* Dump INSN into gdb_stdlog. */
void
arc_insn_dump (const struct arc_instruction &insn)
{
struct gdbarch *gdbarch = target_gdbarch ();
arc_print ("Dumping arc_instruction at %s\n",
paddress (gdbarch, insn.address));
arc_print ("\tlength = %u\n", insn.length);
if (!insn.valid)
{
arc_print ("\tThis is not a valid ARC instruction.\n");
return;
}
arc_print ("\tlength_with_limm = %u\n", insn.length + (insn.limm_p ? 4 : 0));
arc_print ("\tcc = 0x%x\n", insn.condition_code);
arc_print ("\tinsn_class = %u\n", insn.insn_class);
arc_print ("\tis_control_flow = %i\n", insn.is_control_flow);
arc_print ("\thas_delay_slot = %i\n", insn.has_delay_slot);
CORE_ADDR next_pc = arc_insn_get_linear_next_pc (insn);
arc_print ("\tlinear_next_pc = %s\n", paddress (gdbarch, next_pc));
if (insn.is_control_flow)
{
CORE_ADDR t = arc_insn_get_branch_target (insn);
arc_print ("\tbranch_target = %s\n", paddress (gdbarch, t));
}
arc_print ("\tlimm_p = %i\n", insn.limm_p);
if (insn.limm_p)
arc_print ("\tlimm_value = 0x%08x\n", insn.limm_value);
if (insn.insn_class == STORE || insn.insn_class == LOAD
|| insn.insn_class == PUSH || insn.insn_class == POP)
{
arc_print ("\twriteback_mode = %u\n", insn.writeback_mode);
arc_print ("\tdata_size_mode = %u\n", insn.data_size_mode);
arc_print ("\tmemory_base_register = %s\n",
gdbarch_register_name (gdbarch,
arc_insn_get_memory_base_reg (insn)));
/* get_memory_offset returns an unsigned CORE_ADDR, but treat it as a
LONGEST for a nicer representation. */
arc_print ("\taddr_offset = %s\n",
plongest (arc_insn_get_memory_offset (insn)));
}
arc_print ("\toperands_count = %u\n", insn.operands_count);
for (unsigned int i = 0; i < insn.operands_count; ++i)
{
int is_reg = (insn.operands[i].kind == ARC_OPERAND_KIND_REG);
arc_print ("\toperand[%u] = {\n", i);
arc_print ("\t\tis_reg = %i\n", is_reg);
if (is_reg)
arc_print ("\t\tregister = %s\n",
gdbarch_register_name (gdbarch, insn.operands[i].value));
/* Don't know if this value is signed or not, so print both
representations. This tends to look quite ugly, especially for big
numbers. */
arc_print ("\t\tunsigned value = %s\n",
pulongest (arc_insn_get_operand_value (insn, i)));
arc_print ("\t\tsigned value = %s\n",
plongest (arc_insn_get_operand_value_signed (insn, i)));
arc_print ("\t}\n");
}
}
CORE_ADDR
arc_insn_get_linear_next_pc (const struct arc_instruction &insn)
{
/* In ARC long immediate is always 4 bytes. */
return (insn.address + insn.length + (insn.limm_p ? 4 : 0));
}
/* Implement the "write_pc" gdbarch method.
In ARC PC register is a normal register so in most cases setting PC value
@@ -649,6 +913,30 @@ arc_frame_base_address (struct frame_info *this_frame, void **prologue_cache)
return (CORE_ADDR) get_frame_register_unsigned (this_frame, ARC_FP_REGNUM);
}
/* Copy of gdb_buffered_insn_length_fprintf from disasm.c. */
static int ATTRIBUTE_PRINTF (2, 3)
arc_fprintf_disasm (void *stream, const char *format, ...)
{
return 0;
}
struct disassemble_info
arc_disassemble_info (struct gdbarch *gdbarch)
{
struct disassemble_info di;
init_disassemble_info (&di, &null_stream, arc_fprintf_disasm);
di.arch = gdbarch_bfd_arch_info (gdbarch)->arch;
di.mach = gdbarch_bfd_arch_info (gdbarch)->mach;
di.endian = gdbarch_byte_order (gdbarch);
di.read_memory_func = [](bfd_vma memaddr, gdb_byte *myaddr,
unsigned int len, struct disassemble_info *info)
{
return target_read_code (memaddr, myaddr, len);
};
return di;
}
/* Implement the "skip_prologue" gdbarch method.
Skip the prologue for the function at PC. This is done by checking from
@@ -701,7 +989,7 @@ arc_skip_prologue (struct gdbarch *gdbarch, CORE_ADDR pc)
that will not print, or `stream` should be different from standard
gdb_stdlog. */
static int
int
arc_delayed_print_insn (bfd_vma addr, struct disassemble_info *info)
{
int (*print_insn) (bfd_vma, struct disassemble_info *);
@@ -1320,6 +1608,26 @@ maintenance_print_arc_command (char *args, int from_tty)
cmd_show_list (maintenance_print_arc_list, from_tty, "");
}
/* This command accepts single argument - address of instruction to
disassemble. */
static void
dump_arc_instruction_command (char *args, int from_tty)
{
struct value *val;
if (args != NULL && strlen (args) > 0)
val = evaluate_expression (parse_expression (args).get ());
else
val = access_value_history (0);
record_latest_value (val);
CORE_ADDR address = value_as_address (val);
struct arc_instruction insn;
struct disassemble_info di = arc_disassemble_info (target_gdbarch ());
arc_insn_decode (address, &di, arc_delayed_print_insn, &insn);
arc_insn_dump (insn);
}
/* Suppress warning from -Wmissing-prototypes. */
extern initialize_file_ftype _initialize_arc_tdep;
@@ -1340,6 +1648,11 @@ _initialize_arc_tdep (void)
&maintenance_print_arc_list, "maintenance print arc ", 0,
&maintenanceprintlist);
add_cmd ("arc-instruction", class_maintenance,
dump_arc_instruction_command,
_("Dump arc_instruction structure for specified address."),
&maintenance_print_arc_list);
/* Debug internals for ARC GDB. */
add_setshow_zinteger_cmd ("arc", class_maintenance,
&arc_debug,