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

@ -1,3 +1,23 @@
2017-03-28 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".
2017-03-28 Anton Kolesov <anton.kolesov@synopsys.com>
* arc-tdep (maintenance_print_arc_list): New variable.

View File

@ -101,6 +101,9 @@ show disassembler-options
The default value is the empty string. Currently, the only supported
targets are ARM, PowerPC and S/390.
maint print arc arc-instruction address
Print internal disassembler information about instruction at a given address.
*** Changes in GDB 7.12
* GDB and GDBserver now build with a C++ compiler by default.

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,

View File

@ -123,4 +123,29 @@ arc_mach_is_arcv2 (struct gdbarch *gdbarch)
return gdbarch_bfd_arch_info (gdbarch)->mach == bfd_mach_arc_arcv2;
}
/* Function to access ARC disassembler. Underlying opcodes disassembler will
print an instruction into stream specified in the INFO, so if it is
undesired, then this stream should be set to some invisible stream, but it
can't be set to an actual NULL value - that would cause a crash. */
int arc_delayed_print_insn (bfd_vma addr, struct disassemble_info *info);
/* Return properly initialized disassemble_info for ARC disassembler - it will
not print disassembled instructions to stderr. */
struct disassemble_info arc_disassemble_info (struct gdbarch *gdbarch);
/* Get branch/jump target address for the INSN. Note that this function
returns branch target and doesn't evaluate if this branch is taken or not.
For the indirect jumps value depends in register state, hence can change.
It is an error to call this function for a non-branch instruction. */
CORE_ADDR arc_insn_get_branch_target (const struct arc_instruction &insn);
/* Get address of next instruction after INSN, assuming linear execution (no
taken branches). If instruction has a delay slot, then returned value will
point at the instruction in delay slot. That is - "address of instruction +
instruction length with LIMM". */
CORE_ADDR arc_insn_get_linear_next_pc (const struct arc_instruction &insn);
#endif /* ARC_TDEP_H */

View File

@ -1,3 +1,7 @@
2017-03-28 Anton Kolesov <anton.kolesov@synopsys.com>
* gdb.texinfo (Synopsys ARC): Add "maint print arc arc-instruction".
2017-03-22 Yao Qi <yao.qi@linaro.org>
* python.texi (Inferiors In Python): Remove @code from Python.

View File

@ -22105,6 +22105,10 @@ messages.
@kindex show debug arc
Show the level of ARC specific debugging in operation.
@item maint print arc arc-instruction @var{address}
@kindex maint print arc arc-instruction
Print internal disassembler information about instruction at a given address.
@end table
@node ARM

View File

@ -1,3 +1,8 @@
2017-03-28 Anton Kolesov <anton.kolesov@synopsys.com>
* gdb.arch/arc-decode-insn.S: New file.
* gdb.arch/arc-decode-insn.exp: Likewise.
2017-03-21 Ivo Raisr <ivo.raisr@oracle.com>
PR tdep/20928

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,132 @@
# This testcase is part of GDB, the GNU debugger.
# Copyright 2017 Free Software Foundation, Inc.
# 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/>.
# These tests provides certain degree of testing for arc_insn functions,
# however it is not a comprehensive testsuite that would go through all
# possible ARC instructions - instead this particular test is focused on branch
# instructions and whether branch targets are evaluated properly. Most of the
# non-branch aspects of instruction decoder are used during prologue analysis,
# so are indirictly tested there.
# To maintain separation of test data and test logic, all of the information
# about instructions, like if it has delay slot, condition code, branch target
# address, is all specified in the test assembly file as a symbols, while this
# test case reads those symbols to learn which values are right, then compares
# values coming from decoder with those found in symbols. More information
# about requirements to actual test cases can be found in corresponding
# assembly file of this test case (arc-decode-insn.S).
if {![istarget "arc*-*-*"]} then {
verbose "Skipping ARC decoder test."
return
}
standard_testfile .S
if { [prepare_for_testing "failed to prepare" $testfile $srcfile] } {
return -1
}
if ![runto_main] {
fail "Can't run to main"
return 0
}
# Helper function that reads properties of instruction from the ELF file via
# its symbols and then confirms that decoder output aligns to the expected
# values.
proc test_branch_insn { test_name } {
# Make messages for failed cases more clear, by using hex in them.
set pc [get_hexadecimal_valueof &${test_name}_start -1]
# Calculate instruction length, based on ${test_name}_end symbol.
set end_pc [get_hexadecimal_valueof &${test_name}_end -1]
set length [expr $end_pc - $pc]
set target_address [get_hexadecimal_valueof &${test_name}_target -1]
# Figure out if there is a delay slot, using symbol
# ${test_name}_has_delay_slot. Note that it should be read via &,
# otherwise it would try to print value at the address specified in
# ${test_name}_has_delay_slot, while a symbol value itself is required.
if { 0 == [get_integer_valueof &${test_name}_has_delay_slot 0] } {
set has_delay_slot 0
} else {
set has_delay_slot 1
}
set cc [get_hexadecimal_valueof &${test_name}_cc 0]
# Can't use {} to create a list of items, because variables will not be
# evaluated inside the {}.
gdb_test_sequence "mt print arc arc-instruction $pc" "" [list \
"length_with_limm = $length" \
"cc = $cc" \
"is_control_flow = 1" \
"has_delay_slot = $has_delay_slot" \
"branch_target = $target_address"]
}
set branch_test_list { }
# Add items in the same groups as they can be enabled/disabled in assembly
# file.
lappend branch_test_list \
j_c j_blink j_limm j_u6 j_s12 j_d_c j_d_blink j_d_u6
lappend branch_test_list \
jcc_c jcc_blink jcc_limm jcc_u6 jcc_d_c jcc_d_blink jcc_d_u6 \
jcc_eq_s_blink jcc_ne_s_blink
lappend branch_test_list \
jl_c jl_limm jl_u6 jl_s12 jl_d_c jl_d_u6 jl_d_s12 jl_s_b jl_s_d_b
lappend branch_test_list \
jlcc_c jlcc_limm jlcc_u6 jlcc_d_c jlcc_d_u6
lappend branch_test_list \
b_s25 b_d_s25 b_s_s10
lappend branch_test_list \
bbit0_nt_b_c_s9 bbit0_d_nt_b_c_s9 bbit0_t_b_c_s9 bbit0_d_t_b_c_s9 \
bbit0_nt_b_u6_s9 bbit0_d_nt_b_u6_s9 bbit0_t_b_u6_s9 bbit0_d_t_b_u6_s9 \
bbit0_nt_b_limm_s9 bbit0_t_b_limm_s9 bbit0_nt_limm_c_s9 bbit0_t_limm_c_s9 \
bbit0_nt_limm_u6_s9 bbit0_t_limm_u6_s9 \
bbit1_nt_b_c_s9 bbit1_d_nt_b_c_s9 bbit1_t_b_c_s9 bbit1_d_t_b_c_s9 \
bbit1_nt_b_u6_s9 bbit1_d_nt_b_u6_s9 bbit1_t_b_u6_s9 bbit1_d_t_b_u6_s9 \
bbit1_nt_b_limm_s9 bbit1_t_b_limm_s9 bbit1_nt_limm_c_s9 bbit1_t_limm_c_s9 \
bbit1_nt_limm_u6_s9 bbit1_t_limm_u6_s9
lappend branch_test_list \
bcc_s21 bcc_d_s21 \
beq_s_s10 bne_s_s10 bgt_s_s7 bge_s_s7 blt_s_s7 ble_s_s7 bhi_s_s7 bhs_s_s7 \
blo_s_s7 bls_s_s7
lappend branch_test_list \
bi_c bih_c
lappend branch_test_list \
bl_s25 bl_d_s25 bl_s_s13 \
blcc_s21 blcc_d_s21
lappend branch_test_list \
breq_nt_b_c_s9 breq_d_nt_b_c_s9 breq_t_b_c_s9 breq_d_t_b_c_s9 \
breq_nt_b_u6_s9 breq_d_nt_b_u6_s9 breq_t_b_u6_s9 breq_d_t_b_u6_s9 \
breq_nt_b_limm_s9 breq_t_b_limm_s9 breq_nt_limm_c_s9 breq_t_limm_c_s9 \
breq_nt_limm_u6_s9 breq_t_limm_u6_s9
# lappend branch_test_list jli_s_u10
lappend branch_test_list leave_s
lappend branch_test_list lpcc_u7
runto start_branch_tests
foreach test $branch_test_list {
test_branch_insn $test
}