binutils-gdb/opcodes/microblaze-dis.c
Neal Frager 2d1777b530 opcodes: microblaze: Fix bit masking bug
There is currently a bug in the bit masking for the barrel shift
instructions because the bit mask is not including all of the
register bits which must be zero.  With this patch, the disassembler
can be sure that the 32-bit value is indeed a barrel shift instruction
and not a data value in memory.

This fix can be verified by assembling and disassembling the following:

	.text
	.long 0x65005f5f

With this patch, the bug is fixed, and the objdump will know that
0x65005f5f is not a barrel shift instruction.

Signed-off-by: Neal Frager <neal.frager@amd.com>
Signed-off-by: Michael J. Eager <eager@eagercon.com>
2023-10-19 17:59:06 -07:00

592 lines
15 KiB
C

/* Disassemble Xilinx microblaze instructions.
Copyright (C) 2009-2023 Free Software Foundation, Inc.
This file is part of the GNU opcodes library.
This library 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, or (at your option)
any later version.
It 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 file; see the file COPYING. If not, write to the
Free Software Foundation, 51 Franklin Street - Fifth Floor, Boston,
MA 02110-1301, USA. */
#include "sysdep.h"
#define STATIC_TABLE
#define DEFINE_TABLE
#include "disassemble.h"
#include <strings.h>
#include "microblaze-opc.h"
#include "microblaze-dis.h"
#define get_field_rd(buf, instr) get_field (buf, instr, RD_MASK, RD_LOW)
#define get_field_r1(buf, instr) get_field (buf, instr, RA_MASK, RA_LOW)
#define get_field_r2(buf, instr) get_field (buf, instr, RB_MASK, RB_LOW)
#define get_int_field_imm(instr) ((instr & IMM_MASK) >> IMM_LOW)
#define get_int_field_r1(instr) ((instr & RA_MASK) >> RA_LOW)
#define NUM_STRBUFS 4
#define STRBUF_SIZE 25
struct string_buf
{
unsigned int which;
char str[NUM_STRBUFS][STRBUF_SIZE];
};
static inline char *
strbuf (struct string_buf *buf)
{
#ifdef ENABLE_CHECKING
if (buf->which >= NUM_STRBUFS)
abort ();
#endif
return buf->str[buf->which++];
}
static char *
get_field (struct string_buf *buf, long instr, long mask, unsigned short low)
{
char *p = strbuf (buf);
sprintf (p, "%s%d", register_prefix, (int)((instr & mask) >> low));
return p;
}
static char *
get_field_imm (struct string_buf *buf, long instr)
{
char *p = strbuf (buf);
sprintf (p, "%d", (short)((instr & IMM_MASK) >> IMM_LOW));
return p;
}
static char *
get_field_imm5 (struct string_buf *buf, long instr)
{
char *p = strbuf (buf);
sprintf (p, "%d", (short)((instr & IMM5_MASK) >> IMM_LOW));
return p;
}
static char *
get_field_imm5_mbar (struct string_buf *buf, long instr)
{
char *p = strbuf (buf);
sprintf (p, "%d", (short)((instr & IMM5_MBAR_MASK) >> IMM_MBAR));
return p;
}
static char *
get_field_immw (struct string_buf *buf, long instr)
{
char *p = strbuf (buf);
if (instr & 0x00004000)
sprintf (p, "%d", (short)(((instr & IMM5_WIDTH_MASK)
>> IMM_WIDTH_LOW))); /* bsefi */
else
sprintf (p, "%d", (short)(((instr & IMM5_WIDTH_MASK) >>
IMM_WIDTH_LOW) - ((instr & IMM5_MASK) >>
IMM_LOW) + 1)); /* bsifi */
return p;
}
static char *
get_field_rfsl (struct string_buf *buf, long instr)
{
char *p = strbuf (buf);
sprintf (p, "%s%d", fsl_register_prefix,
(short)((instr & RFSL_MASK) >> IMM_LOW));
return p;
}
static char *
get_field_imm15 (struct string_buf *buf, long instr)
{
char *p = strbuf (buf);
sprintf (p, "%d", (short)((instr & IMM15_MASK) >> IMM_LOW));
return p;
}
static char *
get_field_special (struct string_buf *buf, long instr,
const struct op_code_struct *op)
{
char *p = strbuf (buf);
char *spr;
switch ((((instr & IMM_MASK) >> IMM_LOW) ^ op->immval_mask))
{
case REG_MSR_MASK :
spr = "msr";
break;
case REG_PC_MASK :
spr = "pc";
break;
case REG_EAR_MASK :
spr = "ear";
break;
case REG_ESR_MASK :
spr = "esr";
break;
case REG_FSR_MASK :
spr = "fsr";
break;
case REG_BTR_MASK :
spr = "btr";
break;
case REG_EDR_MASK :
spr = "edr";
break;
case REG_PID_MASK :
spr = "pid";
break;
case REG_ZPR_MASK :
spr = "zpr";
break;
case REG_TLBX_MASK :
spr = "tlbx";
break;
case REG_TLBLO_MASK :
spr = "tlblo";
break;
case REG_TLBHI_MASK :
spr = "tlbhi";
break;
case REG_TLBSX_MASK :
spr = "tlbsx";
break;
case REG_SHR_MASK :
spr = "shr";
break;
case REG_SLR_MASK :
spr = "slr";
break;
default :
if (((((instr & IMM_MASK) >> IMM_LOW) ^ op->immval_mask) & 0xE000)
== REG_PVR_MASK)
{
sprintf (p, "%spvr%d", register_prefix,
(unsigned short)(((instr & IMM_MASK) >> IMM_LOW)
^ op->immval_mask) ^ REG_PVR_MASK);
return p;
}
else
spr = "pc";
break;
}
sprintf (p, "%s%s", register_prefix, spr);
return p;
}
static unsigned long
read_insn_microblaze (bfd_vma memaddr,
struct disassemble_info *info,
const struct op_code_struct **opr)
{
unsigned char ibytes[4];
int status;
const struct op_code_struct *op;
unsigned long inst;
status = info->read_memory_func (memaddr, ibytes, 4, info);
if (status != 0)
{
info->memory_error_func (status, memaddr, info);
return 0;
}
if (info->endian == BFD_ENDIAN_BIG)
inst = (((unsigned) ibytes[0] << 24) | (ibytes[1] << 16)
| (ibytes[2] << 8) | ibytes[3]);
else if (info->endian == BFD_ENDIAN_LITTLE)
inst = (((unsigned) ibytes[3] << 24) | (ibytes[2] << 16)
| (ibytes[1] << 8) | ibytes[0]);
else
abort ();
/* Just a linear search of the table. */
for (op = microblaze_opcodes; op->name != 0; op ++)
if (op->bit_sequence == (inst & op->opcode_mask))
break;
*opr = op;
return inst;
}
int
print_insn_microblaze (bfd_vma memaddr, struct disassemble_info * info)
{
fprintf_ftype print_func = info->fprintf_func;
void *stream = info->stream;
unsigned long inst, prev_inst;
const struct op_code_struct *op, *pop;
int immval = 0;
bool immfound = false;
static bfd_vma prev_insn_addr = -1; /* Init the prev insn addr. */
static int prev_insn_vma = -1; /* Init the prev insn vma. */
int curr_insn_vma = info->buffer_vma;
struct string_buf buf;
buf.which = 0;
info->bytes_per_chunk = 4;
inst = read_insn_microblaze (memaddr, info, &op);
if (inst == 0)
return -1;
if (prev_insn_vma == curr_insn_vma)
{
if (memaddr-(info->bytes_per_chunk) == prev_insn_addr)
{
prev_inst = read_insn_microblaze (prev_insn_addr, info, &pop);
if (prev_inst == 0)
return -1;
if (pop->instr == imm)
{
immval = (get_int_field_imm (prev_inst) << 16) & 0xffff0000;
immfound = true;
}
else
{
immval = 0;
immfound = false;
}
}
}
/* Make curr insn as prev insn. */
prev_insn_addr = memaddr;
prev_insn_vma = curr_insn_vma;
if (op->name == NULL)
print_func (stream, ".long 0x%04x", (unsigned int) inst);
else
{
print_func (stream, "%s", op->name);
switch (op->inst_type)
{
case INST_TYPE_RD_R1_R2:
print_func (stream, "\t%s, %s, %s", get_field_rd (&buf, inst),
get_field_r1 (&buf, inst), get_field_r2 (&buf, inst));
break;
case INST_TYPE_RD_R1_IMM:
print_func (stream, "\t%s, %s, %s", get_field_rd (&buf, inst),
get_field_r1 (&buf, inst), get_field_imm (&buf, inst));
if (info->print_address_func && get_int_field_r1 (inst) == 0
&& info->symbol_at_address_func)
{
if (immfound)
immval |= (get_int_field_imm (inst) & 0x0000ffff);
else
{
immval = get_int_field_imm (inst);
if (immval & 0x8000)
immval |= 0xFFFF0000;
}
if (immval > 0 && info->symbol_at_address_func (immval, info))
{
print_func (stream, "\t// ");
info->print_address_func (immval, info);
}
}
break;
case INST_TYPE_RD_R1_IMM5:
print_func (stream, "\t%s, %s, %s", get_field_rd (&buf, inst),
get_field_r1 (&buf, inst), get_field_imm5 (&buf, inst));
break;
case INST_TYPE_RD_RFSL:
print_func (stream, "\t%s, %s", get_field_rd (&buf, inst),
get_field_rfsl (&buf, inst));
break;
case INST_TYPE_R1_RFSL:
print_func (stream, "\t%s, %s", get_field_r1 (&buf, inst),
get_field_rfsl (&buf, inst));
break;
case INST_TYPE_RD_SPECIAL:
print_func (stream, "\t%s, %s", get_field_rd (&buf, inst),
get_field_special (&buf, inst, op));
break;
case INST_TYPE_SPECIAL_R1:
print_func (stream, "\t%s, %s", get_field_special (&buf, inst, op),
get_field_r1 (&buf, inst));
break;
case INST_TYPE_RD_R1:
print_func (stream, "\t%s, %s", get_field_rd (&buf, inst),
get_field_r1 (&buf, inst));
break;
case INST_TYPE_R1_R2:
print_func (stream, "\t%s, %s", get_field_r1 (&buf, inst),
get_field_r2 (&buf, inst));
break;
case INST_TYPE_R1_IMM:
print_func (stream, "\t%s, %s", get_field_r1 (&buf, inst),
get_field_imm (&buf, inst));
/* The non-pc relative instructions are returns, which shouldn't
have a label printed. */
if (info->print_address_func && op->inst_offset_type == INST_PC_OFFSET
&& info->symbol_at_address_func)
{
if (immfound)
immval |= (get_int_field_imm (inst) & 0x0000ffff);
else
{
immval = get_int_field_imm (inst);
if (immval & 0x8000)
immval |= 0xFFFF0000;
}
immval += memaddr;
if (immval > 0 && info->symbol_at_address_func (immval, info))
{
print_func (stream, "\t// ");
info->print_address_func (immval, info);
}
else
{
print_func (stream, "\t\t// ");
print_func (stream, "%x", immval);
}
}
break;
case INST_TYPE_RD_IMM:
print_func (stream, "\t%s, %s", get_field_rd (&buf, inst),
get_field_imm (&buf, inst));
if (info->print_address_func && info->symbol_at_address_func)
{
if (immfound)
immval |= (get_int_field_imm (inst) & 0x0000ffff);
else
{
immval = get_int_field_imm (inst);
if (immval & 0x8000)
immval |= 0xFFFF0000;
}
if (op->inst_offset_type == INST_PC_OFFSET)
immval += (int) memaddr;
if (info->symbol_at_address_func (immval, info))
{
print_func (stream, "\t// ");
info->print_address_func (immval, info);
}
}
break;
case INST_TYPE_IMM:
print_func (stream, "\t%s", get_field_imm (&buf, inst));
if (info->print_address_func && info->symbol_at_address_func
&& op->instr != imm)
{
if (immfound)
immval |= (get_int_field_imm (inst) & 0x0000ffff);
else
{
immval = get_int_field_imm (inst);
if (immval & 0x8000)
immval |= 0xFFFF0000;
}
if (op->inst_offset_type == INST_PC_OFFSET)
immval += (int) memaddr;
if (immval > 0 && info->symbol_at_address_func (immval, info))
{
print_func (stream, "\t// ");
info->print_address_func (immval, info);
}
else if (op->inst_offset_type == INST_PC_OFFSET)
{
print_func (stream, "\t\t// ");
print_func (stream, "%x", immval);
}
}
break;
case INST_TYPE_RD_R2:
print_func (stream, "\t%s, %s", get_field_rd (&buf, inst),
get_field_r2 (&buf, inst));
break;
case INST_TYPE_R2:
print_func (stream, "\t%s", get_field_r2 (&buf, inst));
break;
case INST_TYPE_R1:
print_func (stream, "\t%s", get_field_r1 (&buf, inst));
break;
case INST_TYPE_R1_R2_SPECIAL:
print_func (stream, "\t%s, %s", get_field_r1 (&buf, inst),
get_field_r2 (&buf, inst));
break;
case INST_TYPE_RD_IMM15:
print_func (stream, "\t%s, %s", get_field_rd (&buf, inst),
get_field_imm15 (&buf, inst));
break;
/* For mbar insn. */
case INST_TYPE_IMM5:
print_func (stream, "\t%s", get_field_imm5_mbar (&buf, inst));
break;
/* For mbar 16 or sleep insn. */
case INST_TYPE_NONE:
break;
/* For bit field insns. */
case INST_TYPE_RD_R1_IMMW_IMMS:
print_func (stream, "\t%s, %s, %s, %s",
get_field_rd (&buf, inst),
get_field_r1 (&buf, inst),
get_field_immw (&buf, inst),
get_field_imm5 (&buf, inst));
break;
/* For tuqula instruction */
case INST_TYPE_RD:
print_func (stream, "\t%s", get_field_rd (&buf, inst));
break;
case INST_TYPE_RFSL:
print_func (stream, "\t%s", get_field_rfsl (&buf, inst));
break;
default:
/* If the disassembler lags the instruction set. */
print_func (stream, "\tundecoded operands, inst is 0x%04x",
(unsigned int) inst);
break;
}
}
/* Say how many bytes we consumed. */
return 4;
}
enum microblaze_instr
get_insn_microblaze (long inst,
bool *isunsignedimm,
enum microblaze_instr_type *insn_type,
short *delay_slots)
{
const struct op_code_struct *op;
*isunsignedimm = false;
/* Just a linear search of the table. */
for (op = microblaze_opcodes; op->name != 0; op ++)
if (op->bit_sequence == (inst & op->opcode_mask))
break;
if (op->name == 0)
return invalid_inst;
else
{
*isunsignedimm = (op->inst_type == INST_TYPE_RD_R1_UNSIGNED_IMM);
*insn_type = op->instr_type;
*delay_slots = op->delay_slots;
return op->instr;
}
}
enum microblaze_instr
microblaze_decode_insn (long insn, int *rd, int *ra, int *rb, int *immed)
{
enum microblaze_instr op;
bool t1;
enum microblaze_instr_type t2;
short t3;
op = get_insn_microblaze (insn, &t1, &t2, &t3);
*rd = (insn & RD_MASK) >> RD_LOW;
*ra = (insn & RA_MASK) >> RA_LOW;
*rb = (insn & RB_MASK) >> RB_LOW;
t3 = (insn & IMM_MASK) >> IMM_LOW;
*immed = (int) t3;
return (op);
}
unsigned long
microblaze_get_target_address (long inst, bool immfound, int immval,
long pcval, long r1val, long r2val,
bool *targetvalid,
bool *unconditionalbranch)
{
const struct op_code_struct *op;
long targetaddr = 0;
*unconditionalbranch = false;
/* Just a linear search of the table. */
for (op = microblaze_opcodes; op->name != 0; op ++)
if (op->bit_sequence == (inst & op->opcode_mask))
break;
if (op->name == 0)
{
*targetvalid = false;
}
else if (op->instr_type == branch_inst)
{
switch (op->inst_type)
{
case INST_TYPE_R2:
*unconditionalbranch = true;
/* Fall through. */
case INST_TYPE_RD_R2:
case INST_TYPE_R1_R2:
targetaddr = r2val;
*targetvalid = true;
if (op->inst_offset_type == INST_PC_OFFSET)
targetaddr += pcval;
break;
case INST_TYPE_IMM:
*unconditionalbranch = true;
/* Fall through. */
case INST_TYPE_RD_IMM:
case INST_TYPE_R1_IMM:
if (immfound)
{
targetaddr = (immval << 16) & 0xffff0000;
targetaddr |= (get_int_field_imm (inst) & 0x0000ffff);
}
else
{
targetaddr = get_int_field_imm (inst);
if (targetaddr & 0x8000)
targetaddr |= 0xFFFF0000;
}
if (op->inst_offset_type == INST_PC_OFFSET)
targetaddr += pcval;
*targetvalid = true;
break;
default:
*targetvalid = false;
break;
}
}
else if (op->instr_type == return_inst)
{
if (immfound)
{
targetaddr = (immval << 16) & 0xffff0000;
targetaddr |= (get_int_field_imm (inst) & 0x0000ffff);
}
else
{
targetaddr = get_int_field_imm (inst);
if (targetaddr & 0x8000)
targetaddr |= 0xFFFF0000;
}
targetaddr += r1val;
*targetvalid = true;
}
else
*targetvalid = false;
return targetaddr;
}