2408 lines
69 KiB
C
2408 lines
69 KiB
C
/* aarch64-dis.c -- AArch64 disassembler.
|
||
Copyright 2009-2013 Free Software Foundation, Inc.
|
||
Contributed by ARM Ltd.
|
||
|
||
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 program; see the file COPYING3. If not,
|
||
see <http://www.gnu.org/licenses/>. */
|
||
|
||
#include "sysdep.h"
|
||
#include "bfd_stdint.h"
|
||
#include "dis-asm.h"
|
||
#include "libiberty.h"
|
||
#include "opintl.h"
|
||
#include "aarch64-dis.h"
|
||
#include "elf-bfd.h"
|
||
|
||
#define ERR_OK 0
|
||
#define ERR_UND -1
|
||
#define ERR_UNP -3
|
||
#define ERR_NYI -5
|
||
|
||
#define INSNLEN 4
|
||
|
||
/* Cached mapping symbol state. */
|
||
enum map_type
|
||
{
|
||
MAP_INSN,
|
||
MAP_DATA
|
||
};
|
||
|
||
static enum map_type last_type;
|
||
static int last_mapping_sym = -1;
|
||
static bfd_vma last_mapping_addr = 0;
|
||
|
||
/* Other options */
|
||
static int no_aliases = 0; /* If set disassemble as most general inst. */
|
||
|
||
|
||
static void
|
||
set_default_aarch64_dis_options (struct disassemble_info *info ATTRIBUTE_UNUSED)
|
||
{
|
||
}
|
||
|
||
static void
|
||
parse_aarch64_dis_option (const char *option, unsigned int len ATTRIBUTE_UNUSED)
|
||
{
|
||
/* Try to match options that are simple flags */
|
||
if (CONST_STRNEQ (option, "no-aliases"))
|
||
{
|
||
no_aliases = 1;
|
||
return;
|
||
}
|
||
|
||
if (CONST_STRNEQ (option, "aliases"))
|
||
{
|
||
no_aliases = 0;
|
||
return;
|
||
}
|
||
|
||
#ifdef DEBUG_AARCH64
|
||
if (CONST_STRNEQ (option, "debug_dump"))
|
||
{
|
||
debug_dump = 1;
|
||
return;
|
||
}
|
||
#endif /* DEBUG_AARCH64 */
|
||
|
||
/* Invalid option. */
|
||
fprintf (stderr, _("Unrecognised disassembler option: %s\n"), option);
|
||
}
|
||
|
||
static void
|
||
parse_aarch64_dis_options (const char *options)
|
||
{
|
||
const char *option_end;
|
||
|
||
if (options == NULL)
|
||
return;
|
||
|
||
while (*options != '\0')
|
||
{
|
||
/* Skip empty options. */
|
||
if (*options == ',')
|
||
{
|
||
options++;
|
||
continue;
|
||
}
|
||
|
||
/* We know that *options is neither NUL or a comma. */
|
||
option_end = options + 1;
|
||
while (*option_end != ',' && *option_end != '\0')
|
||
option_end++;
|
||
|
||
parse_aarch64_dis_option (options, option_end - options);
|
||
|
||
/* Go on to the next one. If option_end points to a comma, it
|
||
will be skipped above. */
|
||
options = option_end;
|
||
}
|
||
}
|
||
|
||
/* Functions doing the instruction disassembling. */
|
||
|
||
/* The unnamed arguments consist of the number of fields and information about
|
||
these fields where the VALUE will be extracted from CODE and returned.
|
||
MASK can be zero or the base mask of the opcode.
|
||
|
||
N.B. the fields are required to be in such an order than the most signficant
|
||
field for VALUE comes the first, e.g. the <index> in
|
||
SQDMLAL <Va><d>, <Vb><n>, <Vm>.<Ts>[<index>]
|
||
is encoded in H:L:M in some cases, the fields H:L:M should be passed in
|
||
the order of H, L, M. */
|
||
|
||
static inline aarch64_insn
|
||
extract_fields (aarch64_insn code, aarch64_insn mask, ...)
|
||
{
|
||
uint32_t num;
|
||
const aarch64_field *field;
|
||
enum aarch64_field_kind kind;
|
||
va_list va;
|
||
|
||
va_start (va, mask);
|
||
num = va_arg (va, uint32_t);
|
||
assert (num <= 5);
|
||
aarch64_insn value = 0x0;
|
||
while (num--)
|
||
{
|
||
kind = va_arg (va, enum aarch64_field_kind);
|
||
field = &fields[kind];
|
||
value <<= field->width;
|
||
value |= extract_field (kind, code, mask);
|
||
}
|
||
return value;
|
||
}
|
||
|
||
/* Sign-extend bit I of VALUE. */
|
||
static inline int32_t
|
||
sign_extend (aarch64_insn value, unsigned i)
|
||
{
|
||
uint32_t ret = value;
|
||
|
||
assert (i < 32);
|
||
if ((value >> i) & 0x1)
|
||
{
|
||
uint32_t val = (uint32_t)(-1) << i;
|
||
ret = ret | val;
|
||
}
|
||
return (int32_t) ret;
|
||
}
|
||
|
||
/* N.B. the following inline helpfer functions create a dependency on the
|
||
order of operand qualifier enumerators. */
|
||
|
||
/* Given VALUE, return qualifier for a general purpose register. */
|
||
static inline enum aarch64_opnd_qualifier
|
||
get_greg_qualifier_from_value (aarch64_insn value)
|
||
{
|
||
enum aarch64_opnd_qualifier qualifier = AARCH64_OPND_QLF_W + value;
|
||
assert (value <= 0x1
|
||
&& aarch64_get_qualifier_standard_value (qualifier) == value);
|
||
return qualifier;
|
||
}
|
||
|
||
/* Given VALUE, return qualifier for a vector register. */
|
||
static inline enum aarch64_opnd_qualifier
|
||
get_vreg_qualifier_from_value (aarch64_insn value)
|
||
{
|
||
enum aarch64_opnd_qualifier qualifier = AARCH64_OPND_QLF_V_8B + value;
|
||
|
||
assert (value <= 0x8
|
||
&& aarch64_get_qualifier_standard_value (qualifier) == value);
|
||
return qualifier;
|
||
}
|
||
|
||
/* Given VALUE, return qualifier for an FP or AdvSIMD scalar register. */
|
||
static inline enum aarch64_opnd_qualifier
|
||
get_sreg_qualifier_from_value (aarch64_insn value)
|
||
{
|
||
enum aarch64_opnd_qualifier qualifier = AARCH64_OPND_QLF_S_B + value;
|
||
|
||
assert (value <= 0x4
|
||
&& aarch64_get_qualifier_standard_value (qualifier) == value);
|
||
return qualifier;
|
||
}
|
||
|
||
/* Given the instruction in *INST which is probably half way through the
|
||
decoding and our caller wants to know the expected qualifier for operand
|
||
I. Return such a qualifier if we can establish it; otherwise return
|
||
AARCH64_OPND_QLF_NIL. */
|
||
|
||
static aarch64_opnd_qualifier_t
|
||
get_expected_qualifier (const aarch64_inst *inst, int i)
|
||
{
|
||
aarch64_opnd_qualifier_seq_t qualifiers;
|
||
/* Should not be called if the qualifier is known. */
|
||
assert (inst->operands[i].qualifier == AARCH64_OPND_QLF_NIL);
|
||
if (aarch64_find_best_match (inst, inst->opcode->qualifiers_list,
|
||
i, qualifiers))
|
||
return qualifiers[i];
|
||
else
|
||
return AARCH64_OPND_QLF_NIL;
|
||
}
|
||
|
||
/* Operand extractors. */
|
||
|
||
int
|
||
aarch64_ext_regno (const aarch64_operand *self, aarch64_opnd_info *info,
|
||
const aarch64_insn code,
|
||
const aarch64_inst *inst ATTRIBUTE_UNUSED)
|
||
{
|
||
info->reg.regno = extract_field (self->fields[0], code, 0);
|
||
return 1;
|
||
}
|
||
|
||
/* e.g. IC <ic_op>{, <Xt>}. */
|
||
int
|
||
aarch64_ext_regrt_sysins (const aarch64_operand *self, aarch64_opnd_info *info,
|
||
const aarch64_insn code,
|
||
const aarch64_inst *inst ATTRIBUTE_UNUSED)
|
||
{
|
||
info->reg.regno = extract_field (self->fields[0], code, 0);
|
||
assert (info->idx == 1
|
||
&& (aarch64_get_operand_class (inst->operands[0].type)
|
||
== AARCH64_OPND_CLASS_SYSTEM));
|
||
/* This will make the constraint checking happy and more importantly will
|
||
help the disassembler determine whether this operand is optional or
|
||
not. */
|
||
info->present = inst->operands[0].sysins_op->has_xt;
|
||
|
||
return 1;
|
||
}
|
||
|
||
/* e.g. SQDMLAL <Va><d>, <Vb><n>, <Vm>.<Ts>[<index>]. */
|
||
int
|
||
aarch64_ext_reglane (const aarch64_operand *self, aarch64_opnd_info *info,
|
||
const aarch64_insn code,
|
||
const aarch64_inst *inst ATTRIBUTE_UNUSED)
|
||
{
|
||
/* regno */
|
||
info->reglane.regno = extract_field (self->fields[0], code,
|
||
inst->opcode->mask);
|
||
|
||
/* Index and/or type. */
|
||
if (inst->opcode->iclass == asisdone
|
||
|| inst->opcode->iclass == asimdins)
|
||
{
|
||
if (info->type == AARCH64_OPND_En
|
||
&& inst->opcode->operands[0] == AARCH64_OPND_Ed)
|
||
{
|
||
unsigned shift;
|
||
/* index2 for e.g. INS <Vd>.<Ts>[<index1>], <Vn>.<Ts>[<index2>]. */
|
||
assert (info->idx == 1); /* Vn */
|
||
aarch64_insn value = extract_field (FLD_imm4, code, 0);
|
||
/* Depend on AARCH64_OPND_Ed to determine the qualifier. */
|
||
info->qualifier = get_expected_qualifier (inst, info->idx);
|
||
shift = get_logsz (aarch64_get_qualifier_esize (info->qualifier));
|
||
info->reglane.index = value >> shift;
|
||
}
|
||
else
|
||
{
|
||
/* index and type for e.g. DUP <V><d>, <Vn>.<T>[<index>].
|
||
imm5<3:0> <V>
|
||
0000 RESERVED
|
||
xxx1 B
|
||
xx10 H
|
||
x100 S
|
||
1000 D */
|
||
int pos = -1;
|
||
aarch64_insn value = extract_field (FLD_imm5, code, 0);
|
||
while (++pos <= 3 && (value & 0x1) == 0)
|
||
value >>= 1;
|
||
if (pos > 3)
|
||
return 0;
|
||
info->qualifier = get_sreg_qualifier_from_value (pos);
|
||
info->reglane.index = (unsigned) (value >> 1);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
/* Index only for e.g. SQDMLAL <Va><d>, <Vb><n>, <Vm>.<Ts>[<index>]
|
||
or SQDMLAL <Va><d>, <Vb><n>, <Vm>.<Ts>[<index>]. */
|
||
|
||
/* Need information in other operand(s) to help decoding. */
|
||
info->qualifier = get_expected_qualifier (inst, info->idx);
|
||
switch (info->qualifier)
|
||
{
|
||
case AARCH64_OPND_QLF_S_H:
|
||
/* h:l:m */
|
||
info->reglane.index = extract_fields (code, 0, 3, FLD_H, FLD_L,
|
||
FLD_M);
|
||
info->reglane.regno &= 0xf;
|
||
break;
|
||
case AARCH64_OPND_QLF_S_S:
|
||
/* h:l */
|
||
info->reglane.index = extract_fields (code, 0, 2, FLD_H, FLD_L);
|
||
break;
|
||
case AARCH64_OPND_QLF_S_D:
|
||
/* H */
|
||
info->reglane.index = extract_field (FLD_H, code, 0);
|
||
break;
|
||
default:
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
return 1;
|
||
}
|
||
|
||
int
|
||
aarch64_ext_reglist (const aarch64_operand *self, aarch64_opnd_info *info,
|
||
const aarch64_insn code,
|
||
const aarch64_inst *inst ATTRIBUTE_UNUSED)
|
||
{
|
||
/* R */
|
||
info->reglist.first_regno = extract_field (self->fields[0], code, 0);
|
||
/* len */
|
||
info->reglist.num_regs = extract_field (FLD_len, code, 0) + 1;
|
||
return 1;
|
||
}
|
||
|
||
/* Decode Rt and opcode fields of Vt in AdvSIMD load/store instructions. */
|
||
int
|
||
aarch64_ext_ldst_reglist (const aarch64_operand *self ATTRIBUTE_UNUSED,
|
||
aarch64_opnd_info *info, const aarch64_insn code,
|
||
const aarch64_inst *inst)
|
||
{
|
||
aarch64_insn value;
|
||
/* Number of elements in each structure to be loaded/stored. */
|
||
unsigned expected_num = get_opcode_dependent_value (inst->opcode);
|
||
|
||
struct
|
||
{
|
||
unsigned is_reserved;
|
||
unsigned num_regs;
|
||
unsigned num_elements;
|
||
} data [] =
|
||
{ {0, 4, 4},
|
||
{1, 4, 4},
|
||
{0, 4, 1},
|
||
{0, 4, 2},
|
||
{0, 3, 3},
|
||
{1, 3, 3},
|
||
{0, 3, 1},
|
||
{0, 1, 1},
|
||
{0, 2, 2},
|
||
{1, 2, 2},
|
||
{0, 2, 1},
|
||
};
|
||
|
||
/* Rt */
|
||
info->reglist.first_regno = extract_field (FLD_Rt, code, 0);
|
||
/* opcode */
|
||
value = extract_field (FLD_opcode, code, 0);
|
||
if (expected_num != data[value].num_elements || data[value].is_reserved)
|
||
return 0;
|
||
info->reglist.num_regs = data[value].num_regs;
|
||
|
||
return 1;
|
||
}
|
||
|
||
/* Decode Rt and S fields of Vt in AdvSIMD load single structure to all
|
||
lanes instructions. */
|
||
int
|
||
aarch64_ext_ldst_reglist_r (const aarch64_operand *self ATTRIBUTE_UNUSED,
|
||
aarch64_opnd_info *info, const aarch64_insn code,
|
||
const aarch64_inst *inst)
|
||
{
|
||
aarch64_insn value;
|
||
|
||
/* Rt */
|
||
info->reglist.first_regno = extract_field (FLD_Rt, code, 0);
|
||
/* S */
|
||
value = extract_field (FLD_S, code, 0);
|
||
|
||
/* Number of registers is equal to the number of elements in
|
||
each structure to be loaded/stored. */
|
||
info->reglist.num_regs = get_opcode_dependent_value (inst->opcode);
|
||
assert (info->reglist.num_regs >= 1 && info->reglist.num_regs <= 4);
|
||
|
||
/* Except when it is LD1R. */
|
||
if (info->reglist.num_regs == 1 && value == (aarch64_insn) 1)
|
||
info->reglist.num_regs = 2;
|
||
|
||
return 1;
|
||
}
|
||
|
||
/* Decode Q, opcode<2:1>, S, size and Rt fields of Vt in AdvSIMD
|
||
load/store single element instructions. */
|
||
int
|
||
aarch64_ext_ldst_elemlist (const aarch64_operand *self ATTRIBUTE_UNUSED,
|
||
aarch64_opnd_info *info, const aarch64_insn code,
|
||
const aarch64_inst *inst ATTRIBUTE_UNUSED)
|
||
{
|
||
aarch64_field field = {0, 0};
|
||
aarch64_insn QSsize; /* fields Q:S:size. */
|
||
aarch64_insn opcodeh2; /* opcode<2:1> */
|
||
|
||
/* Rt */
|
||
info->reglist.first_regno = extract_field (FLD_Rt, code, 0);
|
||
|
||
/* Decode the index, opcode<2:1> and size. */
|
||
gen_sub_field (FLD_asisdlso_opcode, 1, 2, &field);
|
||
opcodeh2 = extract_field_2 (&field, code, 0);
|
||
QSsize = extract_fields (code, 0, 3, FLD_Q, FLD_S, FLD_vldst_size);
|
||
switch (opcodeh2)
|
||
{
|
||
case 0x0:
|
||
info->qualifier = AARCH64_OPND_QLF_S_B;
|
||
/* Index encoded in "Q:S:size". */
|
||
info->reglist.index = QSsize;
|
||
break;
|
||
case 0x1:
|
||
info->qualifier = AARCH64_OPND_QLF_S_H;
|
||
/* Index encoded in "Q:S:size<1>". */
|
||
info->reglist.index = QSsize >> 1;
|
||
break;
|
||
case 0x2:
|
||
if ((QSsize & 0x1) == 0)
|
||
{
|
||
info->qualifier = AARCH64_OPND_QLF_S_S;
|
||
/* Index encoded in "Q:S". */
|
||
info->reglist.index = QSsize >> 2;
|
||
}
|
||
else
|
||
{
|
||
info->qualifier = AARCH64_OPND_QLF_S_D;
|
||
/* Index encoded in "Q". */
|
||
info->reglist.index = QSsize >> 3;
|
||
if (extract_field (FLD_S, code, 0))
|
||
/* UND */
|
||
return 0;
|
||
}
|
||
break;
|
||
default:
|
||
return 0;
|
||
}
|
||
|
||
info->reglist.has_index = 1;
|
||
info->reglist.num_regs = 0;
|
||
/* Number of registers is equal to the number of elements in
|
||
each structure to be loaded/stored. */
|
||
info->reglist.num_regs = get_opcode_dependent_value (inst->opcode);
|
||
assert (info->reglist.num_regs >= 1 && info->reglist.num_regs <= 4);
|
||
|
||
return 1;
|
||
}
|
||
|
||
/* Decode fields immh:immb and/or Q for e.g.
|
||
SSHR <Vd>.<T>, <Vn>.<T>, #<shift>
|
||
or SSHR <V><d>, <V><n>, #<shift>. */
|
||
|
||
int
|
||
aarch64_ext_advsimd_imm_shift (const aarch64_operand *self ATTRIBUTE_UNUSED,
|
||
aarch64_opnd_info *info, const aarch64_insn code,
|
||
const aarch64_inst *inst)
|
||
{
|
||
int pos;
|
||
aarch64_insn Q, imm, immh;
|
||
enum aarch64_insn_class iclass = inst->opcode->iclass;
|
||
|
||
immh = extract_field (FLD_immh, code, 0);
|
||
if (immh == 0)
|
||
return 0;
|
||
imm = extract_fields (code, 0, 2, FLD_immh, FLD_immb);
|
||
pos = 4;
|
||
/* Get highest set bit in immh. */
|
||
while (--pos >= 0 && (immh & 0x8) == 0)
|
||
immh <<= 1;
|
||
|
||
assert ((iclass == asimdshf || iclass == asisdshf)
|
||
&& (info->type == AARCH64_OPND_IMM_VLSR
|
||
|| info->type == AARCH64_OPND_IMM_VLSL));
|
||
|
||
if (iclass == asimdshf)
|
||
{
|
||
Q = extract_field (FLD_Q, code, 0);
|
||
/* immh Q <T>
|
||
0000 x SEE AdvSIMD modified immediate
|
||
0001 0 8B
|
||
0001 1 16B
|
||
001x 0 4H
|
||
001x 1 8H
|
||
01xx 0 2S
|
||
01xx 1 4S
|
||
1xxx 0 RESERVED
|
||
1xxx 1 2D */
|
||
info->qualifier =
|
||
get_vreg_qualifier_from_value ((pos << 1) | (int) Q);
|
||
}
|
||
else
|
||
info->qualifier = get_sreg_qualifier_from_value (pos);
|
||
|
||
if (info->type == AARCH64_OPND_IMM_VLSR)
|
||
/* immh <shift>
|
||
0000 SEE AdvSIMD modified immediate
|
||
0001 (16-UInt(immh:immb))
|
||
001x (32-UInt(immh:immb))
|
||
01xx (64-UInt(immh:immb))
|
||
1xxx (128-UInt(immh:immb)) */
|
||
info->imm.value = (16 << pos) - imm;
|
||
else
|
||
/* immh:immb
|
||
immh <shift>
|
||
0000 SEE AdvSIMD modified immediate
|
||
0001 (UInt(immh:immb)-8)
|
||
001x (UInt(immh:immb)-16)
|
||
01xx (UInt(immh:immb)-32)
|
||
1xxx (UInt(immh:immb)-64) */
|
||
info->imm.value = imm - (8 << pos);
|
||
|
||
return 1;
|
||
}
|
||
|
||
/* Decode shift immediate for e.g. sshr (imm). */
|
||
int
|
||
aarch64_ext_shll_imm (const aarch64_operand *self ATTRIBUTE_UNUSED,
|
||
aarch64_opnd_info *info, const aarch64_insn code,
|
||
const aarch64_inst *inst ATTRIBUTE_UNUSED)
|
||
{
|
||
int64_t imm;
|
||
aarch64_insn val;
|
||
val = extract_field (FLD_size, code, 0);
|
||
switch (val)
|
||
{
|
||
case 0: imm = 8; break;
|
||
case 1: imm = 16; break;
|
||
case 2: imm = 32; break;
|
||
default: return 0;
|
||
}
|
||
info->imm.value = imm;
|
||
return 1;
|
||
}
|
||
|
||
/* Decode imm for e.g. BFM <Wd>, <Wn>, #<immr>, #<imms>.
|
||
value in the field(s) will be extracted as unsigned immediate value. */
|
||
int
|
||
aarch64_ext_imm (const aarch64_operand *self, aarch64_opnd_info *info,
|
||
const aarch64_insn code,
|
||
const aarch64_inst *inst ATTRIBUTE_UNUSED)
|
||
{
|
||
int64_t imm;
|
||
/* Maximum of two fields to extract. */
|
||
assert (self->fields[2] == FLD_NIL);
|
||
|
||
if (self->fields[1] == FLD_NIL)
|
||
imm = extract_field (self->fields[0], code, 0);
|
||
else
|
||
/* e.g. TBZ b5:b40. */
|
||
imm = extract_fields (code, 0, 2, self->fields[0], self->fields[1]);
|
||
|
||
if (info->type == AARCH64_OPND_FPIMM)
|
||
info->imm.is_fp = 1;
|
||
|
||
if (operand_need_sign_extension (self))
|
||
imm = sign_extend (imm, get_operand_fields_width (self) - 1);
|
||
|
||
if (operand_need_shift_by_two (self))
|
||
imm <<= 2;
|
||
|
||
if (info->type == AARCH64_OPND_ADDR_ADRP)
|
||
imm <<= 12;
|
||
|
||
info->imm.value = imm;
|
||
return 1;
|
||
}
|
||
|
||
/* Decode imm and its shifter for e.g. MOVZ <Wd>, #<imm16>{, LSL #<shift>}. */
|
||
int
|
||
aarch64_ext_imm_half (const aarch64_operand *self, aarch64_opnd_info *info,
|
||
const aarch64_insn code,
|
||
const aarch64_inst *inst ATTRIBUTE_UNUSED)
|
||
{
|
||
aarch64_ext_imm (self, info, code, inst);
|
||
info->shifter.kind = AARCH64_MOD_LSL;
|
||
info->shifter.amount = extract_field (FLD_hw, code, 0) << 4;
|
||
return 1;
|
||
}
|
||
|
||
/* Decode cmode and "a:b:c:d:e:f:g:h" for e.g.
|
||
MOVI <Vd>.<T>, #<imm8> {, LSL #<amount>}. */
|
||
int
|
||
aarch64_ext_advsimd_imm_modified (const aarch64_operand *self ATTRIBUTE_UNUSED,
|
||
aarch64_opnd_info *info,
|
||
const aarch64_insn code,
|
||
const aarch64_inst *inst ATTRIBUTE_UNUSED)
|
||
{
|
||
uint64_t imm;
|
||
enum aarch64_opnd_qualifier opnd0_qualifier = inst->operands[0].qualifier;
|
||
aarch64_field field = {0, 0};
|
||
|
||
assert (info->idx == 1);
|
||
|
||
if (info->type == AARCH64_OPND_SIMD_FPIMM)
|
||
info->imm.is_fp = 1;
|
||
|
||
/* a:b:c:d:e:f:g:h */
|
||
imm = extract_fields (code, 0, 2, FLD_abc, FLD_defgh);
|
||
if (!info->imm.is_fp && aarch64_get_qualifier_esize (opnd0_qualifier) == 8)
|
||
{
|
||
/* Either MOVI <Dd>, #<imm>
|
||
or MOVI <Vd>.2D, #<imm>.
|
||
<imm> is a 64-bit immediate
|
||
'aaaaaaaabbbbbbbbccccccccddddddddeeeeeeeeffffffffgggggggghhhhhhhh',
|
||
encoded in "a:b:c:d:e:f:g:h". */
|
||
int i;
|
||
unsigned abcdefgh = imm;
|
||
for (imm = 0ull, i = 0; i < 8; i++)
|
||
if (((abcdefgh >> i) & 0x1) != 0)
|
||
imm |= 0xffull << (8 * i);
|
||
}
|
||
info->imm.value = imm;
|
||
|
||
/* cmode */
|
||
info->qualifier = get_expected_qualifier (inst, info->idx);
|
||
switch (info->qualifier)
|
||
{
|
||
case AARCH64_OPND_QLF_NIL:
|
||
/* no shift */
|
||
info->shifter.kind = AARCH64_MOD_NONE;
|
||
return 1;
|
||
case AARCH64_OPND_QLF_LSL:
|
||
/* shift zeros */
|
||
info->shifter.kind = AARCH64_MOD_LSL;
|
||
switch (aarch64_get_qualifier_esize (opnd0_qualifier))
|
||
{
|
||
case 4: gen_sub_field (FLD_cmode, 1, 2, &field); break; /* per word */
|
||
case 2: gen_sub_field (FLD_cmode, 1, 1, &field); break; /* per half */
|
||
case 1: gen_sub_field (FLD_cmode, 1, 0, &field); break; /* per byte */
|
||
default: assert (0); return 0;
|
||
}
|
||
/* 00: 0; 01: 8; 10:16; 11:24. */
|
||
info->shifter.amount = extract_field_2 (&field, code, 0) << 3;
|
||
break;
|
||
case AARCH64_OPND_QLF_MSL:
|
||
/* shift ones */
|
||
info->shifter.kind = AARCH64_MOD_MSL;
|
||
gen_sub_field (FLD_cmode, 0, 1, &field); /* per word */
|
||
info->shifter.amount = extract_field_2 (&field, code, 0) ? 16 : 8;
|
||
break;
|
||
default:
|
||
assert (0);
|
||
return 0;
|
||
}
|
||
|
||
return 1;
|
||
}
|
||
|
||
/* Decode scale for e.g. SCVTF <Dd>, <Wn>, #<fbits>. */
|
||
int
|
||
aarch64_ext_fbits (const aarch64_operand *self ATTRIBUTE_UNUSED,
|
||
aarch64_opnd_info *info, const aarch64_insn code,
|
||
const aarch64_inst *inst ATTRIBUTE_UNUSED)
|
||
{
|
||
info->imm.value = 64- extract_field (FLD_scale, code, 0);
|
||
return 1;
|
||
}
|
||
|
||
/* Decode arithmetic immediate for e.g.
|
||
SUBS <Wd>, <Wn|WSP>, #<imm> {, <shift>}. */
|
||
int
|
||
aarch64_ext_aimm (const aarch64_operand *self ATTRIBUTE_UNUSED,
|
||
aarch64_opnd_info *info, const aarch64_insn code,
|
||
const aarch64_inst *inst ATTRIBUTE_UNUSED)
|
||
{
|
||
aarch64_insn value;
|
||
|
||
info->shifter.kind = AARCH64_MOD_LSL;
|
||
/* shift */
|
||
value = extract_field (FLD_shift, code, 0);
|
||
if (value >= 2)
|
||
return 0;
|
||
info->shifter.amount = value ? 12 : 0;
|
||
/* imm12 (unsigned) */
|
||
info->imm.value = extract_field (FLD_imm12, code, 0);
|
||
|
||
return 1;
|
||
}
|
||
|
||
/* Decode logical immediate for e.g. ORR <Wd|WSP>, <Wn>, #<imm>. */
|
||
|
||
int
|
||
aarch64_ext_limm (const aarch64_operand *self ATTRIBUTE_UNUSED,
|
||
aarch64_opnd_info *info, const aarch64_insn code,
|
||
const aarch64_inst *inst ATTRIBUTE_UNUSED)
|
||
{
|
||
uint64_t imm, mask;
|
||
uint32_t sf;
|
||
uint32_t N, R, S;
|
||
unsigned simd_size;
|
||
aarch64_insn value;
|
||
|
||
value = extract_fields (code, 0, 3, FLD_N, FLD_immr, FLD_imms);
|
||
assert (inst->operands[0].qualifier == AARCH64_OPND_QLF_W
|
||
|| inst->operands[0].qualifier == AARCH64_OPND_QLF_X);
|
||
sf = aarch64_get_qualifier_esize (inst->operands[0].qualifier) != 4;
|
||
|
||
/* value is N:immr:imms. */
|
||
S = value & 0x3f;
|
||
R = (value >> 6) & 0x3f;
|
||
N = (value >> 12) & 0x1;
|
||
|
||
if (sf == 0 && N == 1)
|
||
return 0;
|
||
|
||
/* The immediate value is S+1 bits to 1, left rotated by SIMDsize - R
|
||
(in other words, right rotated by R), then replicated. */
|
||
if (N != 0)
|
||
{
|
||
simd_size = 64;
|
||
mask = 0xffffffffffffffffull;
|
||
}
|
||
else
|
||
{
|
||
switch (S)
|
||
{
|
||
case 0x00 ... 0x1f: /* 0xxxxx */ simd_size = 32; break;
|
||
case 0x20 ... 0x2f: /* 10xxxx */ simd_size = 16; S &= 0xf; break;
|
||
case 0x30 ... 0x37: /* 110xxx */ simd_size = 8; S &= 0x7; break;
|
||
case 0x38 ... 0x3b: /* 1110xx */ simd_size = 4; S &= 0x3; break;
|
||
case 0x3c ... 0x3d: /* 11110x */ simd_size = 2; S &= 0x1; break;
|
||
default: return 0;
|
||
}
|
||
mask = (1ull << simd_size) - 1;
|
||
/* Top bits are IGNORED. */
|
||
R &= simd_size - 1;
|
||
}
|
||
/* NOTE: if S = simd_size - 1 we get 0xf..f which is rejected. */
|
||
if (S == simd_size - 1)
|
||
return 0;
|
||
/* S+1 consecutive bits to 1. */
|
||
/* NOTE: S can't be 63 due to detection above. */
|
||
imm = (1ull << (S + 1)) - 1;
|
||
/* Rotate to the left by simd_size - R. */
|
||
if (R != 0)
|
||
imm = ((imm << (simd_size - R)) & mask) | (imm >> R);
|
||
/* Replicate the value according to SIMD size. */
|
||
switch (simd_size)
|
||
{
|
||
case 2: imm = (imm << 2) | imm;
|
||
case 4: imm = (imm << 4) | imm;
|
||
case 8: imm = (imm << 8) | imm;
|
||
case 16: imm = (imm << 16) | imm;
|
||
case 32: imm = (imm << 32) | imm;
|
||
case 64: break;
|
||
default: assert (0); return 0;
|
||
}
|
||
|
||
info->imm.value = sf ? imm : imm & 0xffffffff;
|
||
|
||
return 1;
|
||
}
|
||
|
||
/* Decode Ft for e.g. STR <Qt>, [<Xn|SP>, <R><m>{, <extend> {<amount>}}]
|
||
or LDP <Qt1>, <Qt2>, [<Xn|SP>], #<imm>. */
|
||
int
|
||
aarch64_ext_ft (const aarch64_operand *self ATTRIBUTE_UNUSED,
|
||
aarch64_opnd_info *info,
|
||
const aarch64_insn code, const aarch64_inst *inst)
|
||
{
|
||
aarch64_insn value;
|
||
|
||
/* Rt */
|
||
info->reg.regno = extract_field (FLD_Rt, code, 0);
|
||
|
||
/* size */
|
||
value = extract_field (FLD_ldst_size, code, 0);
|
||
if (inst->opcode->iclass == ldstpair_indexed
|
||
|| inst->opcode->iclass == ldstnapair_offs
|
||
|| inst->opcode->iclass == ldstpair_off
|
||
|| inst->opcode->iclass == loadlit)
|
||
{
|
||
enum aarch64_opnd_qualifier qualifier;
|
||
switch (value)
|
||
{
|
||
case 0: qualifier = AARCH64_OPND_QLF_S_S; break;
|
||
case 1: qualifier = AARCH64_OPND_QLF_S_D; break;
|
||
case 2: qualifier = AARCH64_OPND_QLF_S_Q; break;
|
||
default: return 0;
|
||
}
|
||
info->qualifier = qualifier;
|
||
}
|
||
else
|
||
{
|
||
/* opc1:size */
|
||
value = extract_fields (code, 0, 2, FLD_opc1, FLD_ldst_size);
|
||
if (value > 0x4)
|
||
return 0;
|
||
info->qualifier = get_sreg_qualifier_from_value (value);
|
||
}
|
||
|
||
return 1;
|
||
}
|
||
|
||
/* Decode the address operand for e.g. STXRB <Ws>, <Wt>, [<Xn|SP>{,#0}]. */
|
||
int
|
||
aarch64_ext_addr_simple (const aarch64_operand *self ATTRIBUTE_UNUSED,
|
||
aarch64_opnd_info *info,
|
||
aarch64_insn code,
|
||
const aarch64_inst *inst ATTRIBUTE_UNUSED)
|
||
{
|
||
/* Rn */
|
||
info->addr.base_regno = extract_field (FLD_Rn, code, 0);
|
||
return 1;
|
||
}
|
||
|
||
/* Decode the address operand for e.g.
|
||
STR <Qt>, [<Xn|SP>, <R><m>{, <extend> {<amount>}}]. */
|
||
int
|
||
aarch64_ext_addr_regoff (const aarch64_operand *self ATTRIBUTE_UNUSED,
|
||
aarch64_opnd_info *info,
|
||
aarch64_insn code, const aarch64_inst *inst)
|
||
{
|
||
aarch64_insn S, value;
|
||
|
||
/* Rn */
|
||
info->addr.base_regno = extract_field (FLD_Rn, code, 0);
|
||
/* Rm */
|
||
info->addr.offset.regno = extract_field (FLD_Rm, code, 0);
|
||
/* option */
|
||
value = extract_field (FLD_option, code, 0);
|
||
info->shifter.kind =
|
||
aarch64_get_operand_modifier_from_value (value, TRUE /* extend_p */);
|
||
/* Fix-up the shifter kind; although the table-driven approach is
|
||
efficient, it is slightly inflexible, thus needing this fix-up. */
|
||
if (info->shifter.kind == AARCH64_MOD_UXTX)
|
||
info->shifter.kind = AARCH64_MOD_LSL;
|
||
/* S */
|
||
S = extract_field (FLD_S, code, 0);
|
||
if (S == 0)
|
||
{
|
||
info->shifter.amount = 0;
|
||
info->shifter.amount_present = 0;
|
||
}
|
||
else
|
||
{
|
||
int size;
|
||
/* Need information in other operand(s) to help achieve the decoding
|
||
from 'S' field. */
|
||
info->qualifier = get_expected_qualifier (inst, info->idx);
|
||
/* Get the size of the data element that is accessed, which may be
|
||
different from that of the source register size, e.g. in strb/ldrb. */
|
||
size = aarch64_get_qualifier_esize (info->qualifier);
|
||
info->shifter.amount = get_logsz (size);
|
||
info->shifter.amount_present = 1;
|
||
}
|
||
|
||
return 1;
|
||
}
|
||
|
||
/* Decode the address operand for e.g. LDRSW <Xt>, [<Xn|SP>], #<simm>. */
|
||
int
|
||
aarch64_ext_addr_simm (const aarch64_operand *self, aarch64_opnd_info *info,
|
||
aarch64_insn code, const aarch64_inst *inst)
|
||
{
|
||
aarch64_insn imm;
|
||
info->qualifier = get_expected_qualifier (inst, info->idx);
|
||
|
||
/* Rn */
|
||
info->addr.base_regno = extract_field (FLD_Rn, code, 0);
|
||
/* simm (imm9 or imm7) */
|
||
imm = extract_field (self->fields[0], code, 0);
|
||
info->addr.offset.imm = sign_extend (imm, fields[self->fields[0]].width - 1);
|
||
if (self->fields[0] == FLD_imm7)
|
||
/* scaled immediate in ld/st pair instructions. */
|
||
info->addr.offset.imm *= aarch64_get_qualifier_esize (info->qualifier);
|
||
/* qualifier */
|
||
if (inst->opcode->iclass == ldst_unscaled
|
||
|| inst->opcode->iclass == ldstnapair_offs
|
||
|| inst->opcode->iclass == ldstpair_off
|
||
|| inst->opcode->iclass == ldst_unpriv)
|
||
info->addr.writeback = 0;
|
||
else
|
||
{
|
||
/* pre/post- index */
|
||
info->addr.writeback = 1;
|
||
if (extract_field (self->fields[1], code, 0) == 1)
|
||
info->addr.preind = 1;
|
||
else
|
||
info->addr.postind = 1;
|
||
}
|
||
|
||
return 1;
|
||
}
|
||
|
||
/* Decode the address operand for e.g. LDRSW <Xt>, [<Xn|SP>{, #<simm>}]. */
|
||
int
|
||
aarch64_ext_addr_uimm12 (const aarch64_operand *self, aarch64_opnd_info *info,
|
||
aarch64_insn code,
|
||
const aarch64_inst *inst ATTRIBUTE_UNUSED)
|
||
{
|
||
int shift;
|
||
info->qualifier = get_expected_qualifier (inst, info->idx);
|
||
shift = get_logsz (aarch64_get_qualifier_esize (info->qualifier));
|
||
/* Rn */
|
||
info->addr.base_regno = extract_field (self->fields[0], code, 0);
|
||
/* uimm12 */
|
||
info->addr.offset.imm = extract_field (self->fields[1], code, 0) << shift;
|
||
return 1;
|
||
}
|
||
|
||
/* Decode the address operand for e.g.
|
||
LD1 {<Vt>.<T>, <Vt2>.<T>, <Vt3>.<T>}, [<Xn|SP>], <Xm|#<amount>>. */
|
||
int
|
||
aarch64_ext_simd_addr_post (const aarch64_operand *self ATTRIBUTE_UNUSED,
|
||
aarch64_opnd_info *info,
|
||
aarch64_insn code, const aarch64_inst *inst)
|
||
{
|
||
/* The opcode dependent area stores the number of elements in
|
||
each structure to be loaded/stored. */
|
||
int is_ld1r = get_opcode_dependent_value (inst->opcode) == 1;
|
||
|
||
/* Rn */
|
||
info->addr.base_regno = extract_field (FLD_Rn, code, 0);
|
||
/* Rm | #<amount> */
|
||
info->addr.offset.regno = extract_field (FLD_Rm, code, 0);
|
||
if (info->addr.offset.regno == 31)
|
||
{
|
||
if (inst->opcode->operands[0] == AARCH64_OPND_LVt_AL)
|
||
/* Special handling of loading single structure to all lane. */
|
||
info->addr.offset.imm = (is_ld1r ? 1
|
||
: inst->operands[0].reglist.num_regs)
|
||
* aarch64_get_qualifier_esize (inst->operands[0].qualifier);
|
||
else
|
||
info->addr.offset.imm = inst->operands[0].reglist.num_regs
|
||
* aarch64_get_qualifier_esize (inst->operands[0].qualifier)
|
||
* aarch64_get_qualifier_nelem (inst->operands[0].qualifier);
|
||
}
|
||
else
|
||
info->addr.offset.is_reg = 1;
|
||
info->addr.writeback = 1;
|
||
|
||
return 1;
|
||
}
|
||
|
||
/* Decode the condition operand for e.g. CSEL <Xd>, <Xn>, <Xm>, <cond>. */
|
||
int
|
||
aarch64_ext_cond (const aarch64_operand *self ATTRIBUTE_UNUSED,
|
||
aarch64_opnd_info *info,
|
||
aarch64_insn code, const aarch64_inst *inst ATTRIBUTE_UNUSED)
|
||
{
|
||
aarch64_insn value;
|
||
/* cond */
|
||
value = extract_field (FLD_cond, code, 0);
|
||
info->cond = get_cond_from_value (value);
|
||
return 1;
|
||
}
|
||
|
||
/* Decode the system register operand for e.g. MRS <Xt>, <systemreg>. */
|
||
int
|
||
aarch64_ext_sysreg (const aarch64_operand *self ATTRIBUTE_UNUSED,
|
||
aarch64_opnd_info *info,
|
||
aarch64_insn code,
|
||
const aarch64_inst *inst ATTRIBUTE_UNUSED)
|
||
{
|
||
/* op0:op1:CRn:CRm:op2 */
|
||
info->sysreg = extract_fields (code, 0, 5, FLD_op0, FLD_op1, FLD_CRn,
|
||
FLD_CRm, FLD_op2);
|
||
return 1;
|
||
}
|
||
|
||
/* Decode the PSTATE field operand for e.g. MSR <pstatefield>, #<imm>. */
|
||
int
|
||
aarch64_ext_pstatefield (const aarch64_operand *self ATTRIBUTE_UNUSED,
|
||
aarch64_opnd_info *info, aarch64_insn code,
|
||
const aarch64_inst *inst ATTRIBUTE_UNUSED)
|
||
{
|
||
int i;
|
||
/* op1:op2 */
|
||
info->pstatefield = extract_fields (code, 0, 2, FLD_op1, FLD_op2);
|
||
for (i = 0; aarch64_pstatefields[i].name != NULL; ++i)
|
||
if (aarch64_pstatefields[i].value == (aarch64_insn)info->pstatefield)
|
||
return 1;
|
||
/* Reserved value in <pstatefield>. */
|
||
return 0;
|
||
}
|
||
|
||
/* Decode the system instruction op operand for e.g. AT <at_op>, <Xt>. */
|
||
int
|
||
aarch64_ext_sysins_op (const aarch64_operand *self ATTRIBUTE_UNUSED,
|
||
aarch64_opnd_info *info,
|
||
aarch64_insn code,
|
||
const aarch64_inst *inst ATTRIBUTE_UNUSED)
|
||
{
|
||
int i;
|
||
aarch64_insn value;
|
||
const aarch64_sys_ins_reg *sysins_ops;
|
||
/* op0:op1:CRn:CRm:op2 */
|
||
value = extract_fields (code, 0, 5,
|
||
FLD_op0, FLD_op1, FLD_CRn,
|
||
FLD_CRm, FLD_op2);
|
||
|
||
switch (info->type)
|
||
{
|
||
case AARCH64_OPND_SYSREG_AT: sysins_ops = aarch64_sys_regs_at; break;
|
||
case AARCH64_OPND_SYSREG_DC: sysins_ops = aarch64_sys_regs_dc; break;
|
||
case AARCH64_OPND_SYSREG_IC: sysins_ops = aarch64_sys_regs_ic; break;
|
||
case AARCH64_OPND_SYSREG_TLBI: sysins_ops = aarch64_sys_regs_tlbi; break;
|
||
default: assert (0); return 0;
|
||
}
|
||
|
||
for (i = 0; sysins_ops[i].template != NULL; ++i)
|
||
if (sysins_ops[i].value == value)
|
||
{
|
||
info->sysins_op = sysins_ops + i;
|
||
DEBUG_TRACE ("%s found value: %x, has_xt: %d, i: %d.",
|
||
info->sysins_op->template,
|
||
(unsigned)info->sysins_op->value,
|
||
info->sysins_op->has_xt, i);
|
||
return 1;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
/* Decode the memory barrier option operand for e.g. DMB <option>|#<imm>. */
|
||
|
||
int
|
||
aarch64_ext_barrier (const aarch64_operand *self ATTRIBUTE_UNUSED,
|
||
aarch64_opnd_info *info,
|
||
aarch64_insn code,
|
||
const aarch64_inst *inst ATTRIBUTE_UNUSED)
|
||
{
|
||
/* CRm */
|
||
info->barrier = aarch64_barrier_options + extract_field (FLD_CRm, code, 0);
|
||
return 1;
|
||
}
|
||
|
||
/* Decode the prefetch operation option operand for e.g.
|
||
PRFM <prfop>, [<Xn|SP>{, #<pimm>}]. */
|
||
|
||
int
|
||
aarch64_ext_prfop (const aarch64_operand *self ATTRIBUTE_UNUSED,
|
||
aarch64_opnd_info *info,
|
||
aarch64_insn code, const aarch64_inst *inst ATTRIBUTE_UNUSED)
|
||
{
|
||
/* prfop in Rt */
|
||
info->prfop = aarch64_prfops + extract_field (FLD_Rt, code, 0);
|
||
return 1;
|
||
}
|
||
|
||
/* Decode the extended register operand for e.g.
|
||
STR <Qt>, [<Xn|SP>, <R><m>{, <extend> {<amount>}}]. */
|
||
int
|
||
aarch64_ext_reg_extended (const aarch64_operand *self ATTRIBUTE_UNUSED,
|
||
aarch64_opnd_info *info,
|
||
aarch64_insn code,
|
||
const aarch64_inst *inst ATTRIBUTE_UNUSED)
|
||
{
|
||
aarch64_insn value;
|
||
|
||
/* Rm */
|
||
info->reg.regno = extract_field (FLD_Rm, code, 0);
|
||
/* option */
|
||
value = extract_field (FLD_option, code, 0);
|
||
info->shifter.kind =
|
||
aarch64_get_operand_modifier_from_value (value, TRUE /* extend_p */);
|
||
/* imm3 */
|
||
info->shifter.amount = extract_field (FLD_imm3, code, 0);
|
||
|
||
/* This makes the constraint checking happy. */
|
||
info->shifter.operator_present = 1;
|
||
|
||
/* Assume inst->operands[0].qualifier has been resolved. */
|
||
assert (inst->operands[0].qualifier != AARCH64_OPND_QLF_NIL);
|
||
info->qualifier = AARCH64_OPND_QLF_W;
|
||
if (inst->operands[0].qualifier == AARCH64_OPND_QLF_X
|
||
&& (info->shifter.kind == AARCH64_MOD_UXTX
|
||
|| info->shifter.kind == AARCH64_MOD_SXTX))
|
||
info->qualifier = AARCH64_OPND_QLF_X;
|
||
|
||
return 1;
|
||
}
|
||
|
||
/* Decode the shifted register operand for e.g.
|
||
SUBS <Xd>, <Xn>, <Xm> {, <shift> #<amount>}. */
|
||
int
|
||
aarch64_ext_reg_shifted (const aarch64_operand *self ATTRIBUTE_UNUSED,
|
||
aarch64_opnd_info *info,
|
||
aarch64_insn code,
|
||
const aarch64_inst *inst ATTRIBUTE_UNUSED)
|
||
{
|
||
aarch64_insn value;
|
||
|
||
/* Rm */
|
||
info->reg.regno = extract_field (FLD_Rm, code, 0);
|
||
/* shift */
|
||
value = extract_field (FLD_shift, code, 0);
|
||
info->shifter.kind =
|
||
aarch64_get_operand_modifier_from_value (value, FALSE /* extend_p */);
|
||
if (info->shifter.kind == AARCH64_MOD_ROR
|
||
&& inst->opcode->iclass != log_shift)
|
||
/* ROR is not available for the shifted register operand in arithmetic
|
||
instructions. */
|
||
return 0;
|
||
/* imm6 */
|
||
info->shifter.amount = extract_field (FLD_imm6, code, 0);
|
||
|
||
/* This makes the constraint checking happy. */
|
||
info->shifter.operator_present = 1;
|
||
|
||
return 1;
|
||
}
|
||
|
||
/* Bitfields that are commonly used to encode certain operands' information
|
||
may be partially used as part of the base opcode in some instructions.
|
||
For example, the bit 1 of the field 'size' in
|
||
FCVTXN <Vb><d>, <Va><n>
|
||
is actually part of the base opcode, while only size<0> is available
|
||
for encoding the register type. Another example is the AdvSIMD
|
||
instruction ORR (register), in which the field 'size' is also used for
|
||
the base opcode, leaving only the field 'Q' available to encode the
|
||
vector register arrangement specifier '8B' or '16B'.
|
||
|
||
This function tries to deduce the qualifier from the value of partially
|
||
constrained field(s). Given the VALUE of such a field or fields, the
|
||
qualifiers CANDIDATES and the MASK (indicating which bits are valid for
|
||
operand encoding), the function returns the matching qualifier or
|
||
AARCH64_OPND_QLF_NIL if nothing matches.
|
||
|
||
N.B. CANDIDATES is a group of possible qualifiers that are valid for
|
||
one operand; it has a maximum of AARCH64_MAX_QLF_SEQ_NUM qualifiers and
|
||
may end with AARCH64_OPND_QLF_NIL. */
|
||
|
||
static enum aarch64_opnd_qualifier
|
||
get_qualifier_from_partial_encoding (aarch64_insn value,
|
||
const enum aarch64_opnd_qualifier* \
|
||
candidates,
|
||
aarch64_insn mask)
|
||
{
|
||
int i;
|
||
DEBUG_TRACE ("enter with value: %d, mask: %d", (int)value, (int)mask);
|
||
for (i = 0; i < AARCH64_MAX_QLF_SEQ_NUM; ++i)
|
||
{
|
||
aarch64_insn standard_value;
|
||
if (candidates[i] == AARCH64_OPND_QLF_NIL)
|
||
break;
|
||
standard_value = aarch64_get_qualifier_standard_value (candidates[i]);
|
||
if ((standard_value & mask) == (value & mask))
|
||
return candidates[i];
|
||
}
|
||
return AARCH64_OPND_QLF_NIL;
|
||
}
|
||
|
||
/* Given a list of qualifier sequences, return all possible valid qualifiers
|
||
for operand IDX in QUALIFIERS.
|
||
Assume QUALIFIERS is an array whose length is large enough. */
|
||
|
||
static void
|
||
get_operand_possible_qualifiers (int idx,
|
||
const aarch64_opnd_qualifier_seq_t *list,
|
||
enum aarch64_opnd_qualifier *qualifiers)
|
||
{
|
||
int i;
|
||
for (i = 0; i < AARCH64_MAX_QLF_SEQ_NUM; ++i)
|
||
if ((qualifiers[i] = list[i][idx]) == AARCH64_OPND_QLF_NIL)
|
||
break;
|
||
}
|
||
|
||
/* Decode the size Q field for e.g. SHADD.
|
||
We tag one operand with the qualifer according to the code;
|
||
whether the qualifier is valid for this opcode or not, it is the
|
||
duty of the semantic checking. */
|
||
|
||
static int
|
||
decode_sizeq (aarch64_inst *inst)
|
||
{
|
||
int idx;
|
||
enum aarch64_opnd_qualifier qualifier;
|
||
aarch64_insn code;
|
||
aarch64_insn value, mask;
|
||
enum aarch64_field_kind fld_sz;
|
||
enum aarch64_opnd_qualifier candidates[AARCH64_MAX_QLF_SEQ_NUM];
|
||
|
||
if (inst->opcode->iclass == asisdlse
|
||
|| inst->opcode->iclass == asisdlsep
|
||
|| inst->opcode->iclass == asisdlso
|
||
|| inst->opcode->iclass == asisdlsop)
|
||
fld_sz = FLD_vldst_size;
|
||
else
|
||
fld_sz = FLD_size;
|
||
|
||
code = inst->value;
|
||
value = extract_fields (code, inst->opcode->mask, 2, fld_sz, FLD_Q);
|
||
/* Obtain the info that which bits of fields Q and size are actually
|
||
available for operand encoding. Opcodes like FMAXNM and FMLA have
|
||
size[1] unavailable. */
|
||
mask = extract_fields (~inst->opcode->mask, 0, 2, fld_sz, FLD_Q);
|
||
|
||
/* The index of the operand we are going to tag a qualifier and the qualifer
|
||
itself are reasoned from the value of the size and Q fields and the
|
||
possible valid qualifier lists. */
|
||
idx = aarch64_select_operand_for_sizeq_field_coding (inst->opcode);
|
||
DEBUG_TRACE ("key idx: %d", idx);
|
||
|
||
/* For most related instruciton, size:Q are fully available for operand
|
||
encoding. */
|
||
if (mask == 0x7)
|
||
{
|
||
inst->operands[idx].qualifier = get_vreg_qualifier_from_value (value);
|
||
return 1;
|
||
}
|
||
|
||
get_operand_possible_qualifiers (idx, inst->opcode->qualifiers_list,
|
||
candidates);
|
||
#ifdef DEBUG_AARCH64
|
||
if (debug_dump)
|
||
{
|
||
int i;
|
||
for (i = 0; candidates[i] != AARCH64_OPND_QLF_NIL
|
||
&& i < AARCH64_MAX_QLF_SEQ_NUM; ++i)
|
||
DEBUG_TRACE ("qualifier %d: %s", i,
|
||
aarch64_get_qualifier_name(candidates[i]));
|
||
DEBUG_TRACE ("%d, %d", (int)value, (int)mask);
|
||
}
|
||
#endif /* DEBUG_AARCH64 */
|
||
|
||
qualifier = get_qualifier_from_partial_encoding (value, candidates, mask);
|
||
|
||
if (qualifier == AARCH64_OPND_QLF_NIL)
|
||
return 0;
|
||
|
||
inst->operands[idx].qualifier = qualifier;
|
||
return 1;
|
||
}
|
||
|
||
/* Decode size[0]:Q, i.e. bit 22 and bit 30, for
|
||
e.g. FCVTN<Q> <Vd>.<Tb>, <Vn>.<Ta>. */
|
||
|
||
static int
|
||
decode_asimd_fcvt (aarch64_inst *inst)
|
||
{
|
||
aarch64_field field = {0, 0};
|
||
aarch64_insn value;
|
||
enum aarch64_opnd_qualifier qualifier;
|
||
|
||
gen_sub_field (FLD_size, 0, 1, &field);
|
||
value = extract_field_2 (&field, inst->value, 0);
|
||
qualifier = value == 0 ? AARCH64_OPND_QLF_V_4S
|
||
: AARCH64_OPND_QLF_V_2D;
|
||
switch (inst->opcode->op)
|
||
{
|
||
case OP_FCVTN:
|
||
case OP_FCVTN2:
|
||
/* FCVTN<Q> <Vd>.<Tb>, <Vn>.<Ta>. */
|
||
inst->operands[1].qualifier = qualifier;
|
||
break;
|
||
case OP_FCVTL:
|
||
case OP_FCVTL2:
|
||
/* FCVTL<Q> <Vd>.<Ta>, <Vn>.<Tb>. */
|
||
inst->operands[0].qualifier = qualifier;
|
||
break;
|
||
default:
|
||
assert (0);
|
||
return 0;
|
||
}
|
||
|
||
return 1;
|
||
}
|
||
|
||
/* Decode size[0], i.e. bit 22, for
|
||
e.g. FCVTXN <Vb><d>, <Va><n>. */
|
||
|
||
static int
|
||
decode_asisd_fcvtxn (aarch64_inst *inst)
|
||
{
|
||
aarch64_field field = {0, 0};
|
||
gen_sub_field (FLD_size, 0, 1, &field);
|
||
if (!extract_field_2 (&field, inst->value, 0))
|
||
return 0;
|
||
inst->operands[0].qualifier = AARCH64_OPND_QLF_S_S;
|
||
return 1;
|
||
}
|
||
|
||
/* Decode the 'opc' field for e.g. FCVT <Dd>, <Sn>. */
|
||
static int
|
||
decode_fcvt (aarch64_inst *inst)
|
||
{
|
||
enum aarch64_opnd_qualifier qualifier;
|
||
aarch64_insn value;
|
||
const aarch64_field field = {15, 2};
|
||
|
||
/* opc dstsize */
|
||
value = extract_field_2 (&field, inst->value, 0);
|
||
switch (value)
|
||
{
|
||
case 0: qualifier = AARCH64_OPND_QLF_S_S; break;
|
||
case 1: qualifier = AARCH64_OPND_QLF_S_D; break;
|
||
case 3: qualifier = AARCH64_OPND_QLF_S_H; break;
|
||
default: return 0;
|
||
}
|
||
inst->operands[0].qualifier = qualifier;
|
||
|
||
return 1;
|
||
}
|
||
|
||
/* Do miscellaneous decodings that are not common enough to be driven by
|
||
flags. */
|
||
|
||
static int
|
||
do_misc_decoding (aarch64_inst *inst)
|
||
{
|
||
switch (inst->opcode->op)
|
||
{
|
||
case OP_FCVT:
|
||
return decode_fcvt (inst);
|
||
case OP_FCVTN:
|
||
case OP_FCVTN2:
|
||
case OP_FCVTL:
|
||
case OP_FCVTL2:
|
||
return decode_asimd_fcvt (inst);
|
||
case OP_FCVTXN_S:
|
||
return decode_asisd_fcvtxn (inst);
|
||
default:
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
/* Opcodes that have fields shared by multiple operands are usually flagged
|
||
with flags. In this function, we detect such flags, decode the related
|
||
field(s) and store the information in one of the related operands. The
|
||
'one' operand is not any operand but one of the operands that can
|
||
accommadate all the information that has been decoded. */
|
||
|
||
static int
|
||
do_special_decoding (aarch64_inst *inst)
|
||
{
|
||
int idx;
|
||
aarch64_insn value;
|
||
/* Condition for truly conditional executed instructions, e.g. b.cond. */
|
||
if (inst->opcode->flags & F_COND)
|
||
{
|
||
value = extract_field (FLD_cond2, inst->value, 0);
|
||
inst->cond = get_cond_from_value (value);
|
||
}
|
||
/* 'sf' field. */
|
||
if (inst->opcode->flags & F_SF)
|
||
{
|
||
idx = select_operand_for_sf_field_coding (inst->opcode);
|
||
value = extract_field (FLD_sf, inst->value, 0);
|
||
inst->operands[idx].qualifier = get_greg_qualifier_from_value (value);
|
||
if ((inst->opcode->flags & F_N)
|
||
&& extract_field (FLD_N, inst->value, 0) != value)
|
||
return 0;
|
||
}
|
||
/* size:Q fields. */
|
||
if (inst->opcode->flags & F_SIZEQ)
|
||
return decode_sizeq (inst);
|
||
|
||
if (inst->opcode->flags & F_FPTYPE)
|
||
{
|
||
idx = select_operand_for_fptype_field_coding (inst->opcode);
|
||
value = extract_field (FLD_type, inst->value, 0);
|
||
switch (value)
|
||
{
|
||
case 0: inst->operands[idx].qualifier = AARCH64_OPND_QLF_S_S; break;
|
||
case 1: inst->operands[idx].qualifier = AARCH64_OPND_QLF_S_D; break;
|
||
case 3: inst->operands[idx].qualifier = AARCH64_OPND_QLF_S_H; break;
|
||
default: return 0;
|
||
}
|
||
}
|
||
|
||
if (inst->opcode->flags & F_SSIZE)
|
||
{
|
||
/* N.B. some opcodes like FCMGT <V><d>, <V><n>, #0 have the size[1] as part
|
||
of the base opcode. */
|
||
aarch64_insn mask;
|
||
enum aarch64_opnd_qualifier candidates[AARCH64_MAX_QLF_SEQ_NUM];
|
||
idx = select_operand_for_scalar_size_field_coding (inst->opcode);
|
||
value = extract_field (FLD_size, inst->value, inst->opcode->mask);
|
||
mask = extract_field (FLD_size, ~inst->opcode->mask, 0);
|
||
/* For most related instruciton, the 'size' field is fully available for
|
||
operand encoding. */
|
||
if (mask == 0x3)
|
||
inst->operands[idx].qualifier = get_sreg_qualifier_from_value (value);
|
||
else
|
||
{
|
||
get_operand_possible_qualifiers (idx, inst->opcode->qualifiers_list,
|
||
candidates);
|
||
inst->operands[idx].qualifier
|
||
= get_qualifier_from_partial_encoding (value, candidates, mask);
|
||
}
|
||
}
|
||
|
||
if (inst->opcode->flags & F_T)
|
||
{
|
||
/* Num of consecutive '0's on the right side of imm5<3:0>. */
|
||
int num = 0;
|
||
unsigned val, Q;
|
||
assert (aarch64_get_operand_class (inst->opcode->operands[0])
|
||
== AARCH64_OPND_CLASS_SIMD_REG);
|
||
/* imm5<3:0> q <t>
|
||
0000 x reserved
|
||
xxx1 0 8b
|
||
xxx1 1 16b
|
||
xx10 0 4h
|
||
xx10 1 8h
|
||
x100 0 2s
|
||
x100 1 4s
|
||
1000 0 reserved
|
||
1000 1 2d */
|
||
val = extract_field (FLD_imm5, inst->value, 0);
|
||
while ((val & 0x1) == 0 && ++num <= 3)
|
||
val >>= 1;
|
||
if (num > 3)
|
||
return 0;
|
||
Q = (unsigned) extract_field (FLD_Q, inst->value, inst->opcode->mask);
|
||
inst->operands[0].qualifier =
|
||
get_vreg_qualifier_from_value ((num << 1) | Q);
|
||
}
|
||
|
||
if (inst->opcode->flags & F_GPRSIZE_IN_Q)
|
||
{
|
||
/* Use Rt to encode in the case of e.g.
|
||
STXP <Ws>, <Xt1>, <Xt2>, [<Xn|SP>{,#0}]. */
|
||
idx = aarch64_operand_index (inst->opcode->operands, AARCH64_OPND_Rt);
|
||
if (idx == -1)
|
||
{
|
||
/* Otherwise use the result operand, which has to be a integer
|
||
register. */
|
||
assert (aarch64_get_operand_class (inst->opcode->operands[0])
|
||
== AARCH64_OPND_CLASS_INT_REG);
|
||
idx = 0;
|
||
}
|
||
assert (idx == 0 || idx == 1);
|
||
value = extract_field (FLD_Q, inst->value, 0);
|
||
inst->operands[idx].qualifier = get_greg_qualifier_from_value (value);
|
||
}
|
||
|
||
if (inst->opcode->flags & F_LDS_SIZE)
|
||
{
|
||
aarch64_field field = {0, 0};
|
||
assert (aarch64_get_operand_class (inst->opcode->operands[0])
|
||
== AARCH64_OPND_CLASS_INT_REG);
|
||
gen_sub_field (FLD_opc, 0, 1, &field);
|
||
value = extract_field_2 (&field, inst->value, 0);
|
||
inst->operands[0].qualifier
|
||
= value ? AARCH64_OPND_QLF_W : AARCH64_OPND_QLF_X;
|
||
}
|
||
|
||
/* Miscellaneous decoding; done as the last step. */
|
||
if (inst->opcode->flags & F_MISC)
|
||
return do_misc_decoding (inst);
|
||
|
||
return 1;
|
||
}
|
||
|
||
/* Converters converting a real opcode instruction to its alias form. */
|
||
|
||
/* ROR <Wd>, <Ws>, #<shift>
|
||
is equivalent to:
|
||
EXTR <Wd>, <Ws>, <Ws>, #<shift>. */
|
||
static int
|
||
convert_extr_to_ror (aarch64_inst *inst)
|
||
{
|
||
if (inst->operands[1].reg.regno == inst->operands[2].reg.regno)
|
||
{
|
||
copy_operand_info (inst, 2, 3);
|
||
inst->operands[3].type = AARCH64_OPND_NIL;
|
||
return 1;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/* UXTL<Q> <Vd>.<Ta>, <Vn>.<Tb>
|
||
is equivalent to:
|
||
USHLL<Q> <Vd>.<Ta>, <Vn>.<Tb>, #0. */
|
||
static int
|
||
convert_shll_to_xtl (aarch64_inst *inst)
|
||
{
|
||
if (inst->operands[2].imm.value == 0)
|
||
{
|
||
inst->operands[2].type = AARCH64_OPND_NIL;
|
||
return 1;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/* Convert
|
||
UBFM <Xd>, <Xn>, #<shift>, #63.
|
||
to
|
||
LSR <Xd>, <Xn>, #<shift>. */
|
||
static int
|
||
convert_bfm_to_sr (aarch64_inst *inst)
|
||
{
|
||
int64_t imms, val;
|
||
|
||
imms = inst->operands[3].imm.value;
|
||
val = inst->operands[2].qualifier == AARCH64_OPND_QLF_imm_0_31 ? 31 : 63;
|
||
if (imms == val)
|
||
{
|
||
inst->operands[3].type = AARCH64_OPND_NIL;
|
||
return 1;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
/* Convert MOV to ORR. */
|
||
static int
|
||
convert_orr_to_mov (aarch64_inst *inst)
|
||
{
|
||
/* MOV <Vd>.<T>, <Vn>.<T>
|
||
is equivalent to:
|
||
ORR <Vd>.<T>, <Vn>.<T>, <Vn>.<T>. */
|
||
if (inst->operands[1].reg.regno == inst->operands[2].reg.regno)
|
||
{
|
||
inst->operands[2].type = AARCH64_OPND_NIL;
|
||
return 1;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/* When <imms> >= <immr>, the instruction written:
|
||
SBFX <Xd>, <Xn>, #<lsb>, #<width>
|
||
is equivalent to:
|
||
SBFM <Xd>, <Xn>, #<lsb>, #(<lsb>+<width>-1). */
|
||
|
||
static int
|
||
convert_bfm_to_bfx (aarch64_inst *inst)
|
||
{
|
||
int64_t immr, imms;
|
||
|
||
immr = inst->operands[2].imm.value;
|
||
imms = inst->operands[3].imm.value;
|
||
if (imms >= immr)
|
||
{
|
||
int64_t lsb = immr;
|
||
inst->operands[2].imm.value = lsb;
|
||
inst->operands[3].imm.value = imms + 1 - lsb;
|
||
/* The two opcodes have different qualifiers for
|
||
the immediate operands; reset to help the checking. */
|
||
reset_operand_qualifier (inst, 2);
|
||
reset_operand_qualifier (inst, 3);
|
||
return 1;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
/* When <imms> < <immr>, the instruction written:
|
||
SBFIZ <Xd>, <Xn>, #<lsb>, #<width>
|
||
is equivalent to:
|
||
SBFM <Xd>, <Xn>, #((64-<lsb>)&0x3f), #(<width>-1). */
|
||
|
||
static int
|
||
convert_bfm_to_bfi (aarch64_inst *inst)
|
||
{
|
||
int64_t immr, imms, val;
|
||
|
||
immr = inst->operands[2].imm.value;
|
||
imms = inst->operands[3].imm.value;
|
||
val = inst->operands[2].qualifier == AARCH64_OPND_QLF_imm_0_31 ? 32 : 64;
|
||
if (imms < immr)
|
||
{
|
||
inst->operands[2].imm.value = (val - immr) & (val - 1);
|
||
inst->operands[3].imm.value = imms + 1;
|
||
/* The two opcodes have different qualifiers for
|
||
the immediate operands; reset to help the checking. */
|
||
reset_operand_qualifier (inst, 2);
|
||
reset_operand_qualifier (inst, 3);
|
||
return 1;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
/* The instruction written:
|
||
LSL <Xd>, <Xn>, #<shift>
|
||
is equivalent to:
|
||
UBFM <Xd>, <Xn>, #((64-<shift>)&0x3f), #(63-<shift>). */
|
||
|
||
static int
|
||
convert_ubfm_to_lsl (aarch64_inst *inst)
|
||
{
|
||
int64_t immr = inst->operands[2].imm.value;
|
||
int64_t imms = inst->operands[3].imm.value;
|
||
int64_t val
|
||
= inst->operands[2].qualifier == AARCH64_OPND_QLF_imm_0_31 ? 31 : 63;
|
||
|
||
if ((immr == 0 && imms == val) || immr == imms + 1)
|
||
{
|
||
inst->operands[3].type = AARCH64_OPND_NIL;
|
||
inst->operands[2].imm.value = val - imms;
|
||
return 1;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
/* CINC <Wd>, <Wn>, <cond>
|
||
is equivalent to:
|
||
CSINC <Wd>, <Wn>, <Wn>, invert(<cond>). */
|
||
|
||
static int
|
||
convert_from_csel (aarch64_inst *inst)
|
||
{
|
||
if (inst->operands[1].reg.regno == inst->operands[2].reg.regno)
|
||
{
|
||
copy_operand_info (inst, 2, 3);
|
||
inst->operands[2].cond = get_inverted_cond (inst->operands[3].cond);
|
||
inst->operands[3].type = AARCH64_OPND_NIL;
|
||
return 1;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/* CSET <Wd>, <cond>
|
||
is equivalent to:
|
||
CSINC <Wd>, WZR, WZR, invert(<cond>). */
|
||
|
||
static int
|
||
convert_csinc_to_cset (aarch64_inst *inst)
|
||
{
|
||
if (inst->operands[1].reg.regno == 0x1f
|
||
&& inst->operands[2].reg.regno == 0x1f)
|
||
{
|
||
copy_operand_info (inst, 1, 3);
|
||
inst->operands[1].cond = get_inverted_cond (inst->operands[3].cond);
|
||
inst->operands[3].type = AARCH64_OPND_NIL;
|
||
inst->operands[2].type = AARCH64_OPND_NIL;
|
||
return 1;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/* MOV <Wd>, #<imm>
|
||
is equivalent to:
|
||
MOVZ <Wd>, #<imm16>, LSL #<shift>.
|
||
|
||
A disassembler may output ORR, MOVZ and MOVN as a MOV mnemonic, except when
|
||
ORR has an immediate that could be generated by a MOVZ or MOVN instruction,
|
||
or where a MOVN has an immediate that could be encoded by MOVZ, or where
|
||
MOVZ/MOVN #0 have a shift amount other than LSL #0, in which case the
|
||
machine-instruction mnemonic must be used. */
|
||
|
||
static int
|
||
convert_movewide_to_mov (aarch64_inst *inst)
|
||
{
|
||
uint64_t value = inst->operands[1].imm.value;
|
||
/* MOVZ/MOVN #0 have a shift amount other than LSL #0. */
|
||
if (value == 0 && inst->operands[1].shifter.amount != 0)
|
||
return 0;
|
||
inst->operands[1].type = AARCH64_OPND_IMM_MOV;
|
||
inst->operands[1].shifter.kind = AARCH64_MOD_NONE;
|
||
value <<= inst->operands[1].shifter.amount;
|
||
/* As an alias convertor, it has to be clear that the INST->OPCODE
|
||
is the opcode of the real instruction. */
|
||
if (inst->opcode->op == OP_MOVN)
|
||
{
|
||
int is32 = inst->operands[0].qualifier == AARCH64_OPND_QLF_W;
|
||
value = ~value;
|
||
/* A MOVN has an immediate that could be encoded by MOVZ. */
|
||
if (aarch64_wide_constant_p (value, is32, NULL) == TRUE)
|
||
return 0;
|
||
}
|
||
inst->operands[1].imm.value = value;
|
||
inst->operands[1].shifter.amount = 0;
|
||
return 1;
|
||
}
|
||
|
||
/* MOV <Wd>, #<imm>
|
||
is equivalent to:
|
||
ORR <Wd>, WZR, #<imm>.
|
||
|
||
A disassembler may output ORR, MOVZ and MOVN as a MOV mnemonic, except when
|
||
ORR has an immediate that could be generated by a MOVZ or MOVN instruction,
|
||
or where a MOVN has an immediate that could be encoded by MOVZ, or where
|
||
MOVZ/MOVN #0 have a shift amount other than LSL #0, in which case the
|
||
machine-instruction mnemonic must be used. */
|
||
|
||
static int
|
||
convert_movebitmask_to_mov (aarch64_inst *inst)
|
||
{
|
||
int is32;
|
||
uint64_t value;
|
||
|
||
/* Should have been assured by the base opcode value. */
|
||
assert (inst->operands[1].reg.regno == 0x1f);
|
||
copy_operand_info (inst, 1, 2);
|
||
is32 = inst->operands[0].qualifier == AARCH64_OPND_QLF_W;
|
||
inst->operands[1].type = AARCH64_OPND_IMM_MOV;
|
||
value = inst->operands[1].imm.value;
|
||
/* ORR has an immediate that could be generated by a MOVZ or MOVN
|
||
instruction. */
|
||
if (inst->operands[0].reg.regno != 0x1f
|
||
&& (aarch64_wide_constant_p (value, is32, NULL) == TRUE
|
||
|| aarch64_wide_constant_p (~value, is32, NULL) == TRUE))
|
||
return 0;
|
||
|
||
inst->operands[2].type = AARCH64_OPND_NIL;
|
||
return 1;
|
||
}
|
||
|
||
/* Some alias opcodes are disassembled by being converted from their real-form.
|
||
N.B. INST->OPCODE is the real opcode rather than the alias. */
|
||
|
||
static int
|
||
convert_to_alias (aarch64_inst *inst, const aarch64_opcode *alias)
|
||
{
|
||
switch (alias->op)
|
||
{
|
||
case OP_ASR_IMM:
|
||
case OP_LSR_IMM:
|
||
return convert_bfm_to_sr (inst);
|
||
case OP_LSL_IMM:
|
||
return convert_ubfm_to_lsl (inst);
|
||
case OP_CINC:
|
||
case OP_CINV:
|
||
case OP_CNEG:
|
||
return convert_from_csel (inst);
|
||
case OP_CSET:
|
||
case OP_CSETM:
|
||
return convert_csinc_to_cset (inst);
|
||
case OP_UBFX:
|
||
case OP_BFXIL:
|
||
case OP_SBFX:
|
||
return convert_bfm_to_bfx (inst);
|
||
case OP_SBFIZ:
|
||
case OP_BFI:
|
||
case OP_UBFIZ:
|
||
return convert_bfm_to_bfi (inst);
|
||
case OP_MOV_V:
|
||
return convert_orr_to_mov (inst);
|
||
case OP_MOV_IMM_WIDE:
|
||
case OP_MOV_IMM_WIDEN:
|
||
return convert_movewide_to_mov (inst);
|
||
case OP_MOV_IMM_LOG:
|
||
return convert_movebitmask_to_mov (inst);
|
||
case OP_ROR_IMM:
|
||
return convert_extr_to_ror (inst);
|
||
case OP_SXTL:
|
||
case OP_SXTL2:
|
||
case OP_UXTL:
|
||
case OP_UXTL2:
|
||
return convert_shll_to_xtl (inst);
|
||
default:
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
static int aarch64_opcode_decode (const aarch64_opcode *, const aarch64_insn,
|
||
aarch64_inst *, int);
|
||
|
||
/* Given the instruction information in *INST, check if the instruction has
|
||
any alias form that can be used to represent *INST. If the answer is yes,
|
||
update *INST to be in the form of the determined alias. */
|
||
|
||
/* In the opcode description table, the following flags are used in opcode
|
||
entries to help establish the relations between the real and alias opcodes:
|
||
|
||
F_ALIAS: opcode is an alias
|
||
F_HAS_ALIAS: opcode has alias(es)
|
||
F_P1
|
||
F_P2
|
||
F_P3: Disassembly preference priority 1-3 (the larger the
|
||
higher). If nothing is specified, it is the priority
|
||
0 by default, i.e. the lowest priority.
|
||
|
||
Although the relation between the machine and the alias instructions are not
|
||
explicitly described, it can be easily determined from the base opcode
|
||
values, masks and the flags F_ALIAS and F_HAS_ALIAS in their opcode
|
||
description entries:
|
||
|
||
The mask of an alias opcode must be equal to or a super-set (i.e. more
|
||
constrained) of that of the aliased opcode; so is the base opcode value.
|
||
|
||
if (opcode_has_alias (real) && alias_opcode_p (opcode)
|
||
&& (opcode->mask & real->mask) == real->mask
|
||
&& (real->mask & opcode->opcode) == (real->mask & real->opcode))
|
||
then OPCODE is an alias of, and only of, the REAL instruction
|
||
|
||
The alias relationship is forced flat-structured to keep related algorithm
|
||
simple; an opcode entry cannot be flagged with both F_ALIAS and F_HAS_ALIAS.
|
||
|
||
During the disassembling, the decoding decision tree (in
|
||
opcodes/aarch64-dis-2.c) always returns an machine instruction opcode entry;
|
||
if the decoding of such a machine instruction succeeds (and -Mno-aliases is
|
||
not specified), the disassembler will check whether there is any alias
|
||
instruction exists for this real instruction. If there is, the disassembler
|
||
will try to disassemble the 32-bit binary again using the alias's rule, or
|
||
try to convert the IR to the form of the alias. In the case of the multiple
|
||
aliases, the aliases are tried one by one from the highest priority
|
||
(currently the flag F_P3) to the lowest priority (no priority flag), and the
|
||
first succeeds first adopted.
|
||
|
||
You may ask why there is a need for the conversion of IR from one form to
|
||
another in handling certain aliases. This is because on one hand it avoids
|
||
adding more operand code to handle unusual encoding/decoding; on other
|
||
hand, during the disassembling, the conversion is an effective approach to
|
||
check the condition of an alias (as an alias may be adopted only if certain
|
||
conditions are met).
|
||
|
||
In order to speed up the alias opcode lookup, aarch64-gen has preprocessed
|
||
aarch64_opcode_table and generated aarch64_find_alias_opcode and
|
||
aarch64_find_next_alias_opcode (in opcodes/aarch64-dis-2.c) to help. */
|
||
|
||
static void
|
||
determine_disassembling_preference (struct aarch64_inst *inst)
|
||
{
|
||
const aarch64_opcode *opcode;
|
||
const aarch64_opcode *alias;
|
||
|
||
opcode = inst->opcode;
|
||
|
||
/* This opcode does not have an alias, so use itself. */
|
||
if (opcode_has_alias (opcode) == FALSE)
|
||
return;
|
||
|
||
alias = aarch64_find_alias_opcode (opcode);
|
||
assert (alias);
|
||
|
||
#ifdef DEBUG_AARCH64
|
||
if (debug_dump)
|
||
{
|
||
const aarch64_opcode *tmp = alias;
|
||
printf ("#### LIST orderd: ");
|
||
while (tmp)
|
||
{
|
||
printf ("%s, ", tmp->name);
|
||
tmp = aarch64_find_next_alias_opcode (tmp);
|
||
}
|
||
printf ("\n");
|
||
}
|
||
#endif /* DEBUG_AARCH64 */
|
||
|
||
for (; alias; alias = aarch64_find_next_alias_opcode (alias))
|
||
{
|
||
DEBUG_TRACE ("try %s", alias->name);
|
||
assert (alias_opcode_p (alias));
|
||
|
||
/* An alias can be a pseudo opcode which will never be used in the
|
||
disassembly, e.g. BIC logical immediate is such a pseudo opcode
|
||
aliasing AND. */
|
||
if (pseudo_opcode_p (alias))
|
||
{
|
||
DEBUG_TRACE ("skip pseudo %s", alias->name);
|
||
continue;
|
||
}
|
||
|
||
if ((inst->value & alias->mask) != alias->opcode)
|
||
{
|
||
DEBUG_TRACE ("skip %s as base opcode not match", alias->name);
|
||
continue;
|
||
}
|
||
/* No need to do any complicated transformation on operands, if the alias
|
||
opcode does not have any operand. */
|
||
if (aarch64_num_of_operands (alias) == 0 && alias->opcode == inst->value)
|
||
{
|
||
DEBUG_TRACE ("succeed with 0-operand opcode %s", alias->name);
|
||
aarch64_replace_opcode (inst, alias);
|
||
return;
|
||
}
|
||
if (alias->flags & F_CONV)
|
||
{
|
||
aarch64_inst copy;
|
||
memcpy (©, inst, sizeof (aarch64_inst));
|
||
/* ALIAS is the preference as long as the instruction can be
|
||
successfully converted to the form of ALIAS. */
|
||
if (convert_to_alias (©, alias) == 1)
|
||
{
|
||
aarch64_replace_opcode (©, alias);
|
||
assert (aarch64_match_operands_constraint (©, NULL));
|
||
DEBUG_TRACE ("succeed with %s via conversion", alias->name);
|
||
memcpy (inst, ©, sizeof (aarch64_inst));
|
||
return;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
/* Directly decode the alias opcode. */
|
||
aarch64_inst temp;
|
||
memset (&temp, '\0', sizeof (aarch64_inst));
|
||
if (aarch64_opcode_decode (alias, inst->value, &temp, 1) == 1)
|
||
{
|
||
DEBUG_TRACE ("succeed with %s via direct decoding", alias->name);
|
||
memcpy (inst, &temp, sizeof (aarch64_inst));
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Decode the CODE according to OPCODE; fill INST. Return 0 if the decoding
|
||
fails, which meanes that CODE is not an instruction of OPCODE; otherwise
|
||
return 1.
|
||
|
||
If OPCODE has alias(es) and NOALIASES_P is 0, an alias opcode may be
|
||
determined and used to disassemble CODE; this is done just before the
|
||
return. */
|
||
|
||
static int
|
||
aarch64_opcode_decode (const aarch64_opcode *opcode, const aarch64_insn code,
|
||
aarch64_inst *inst, int noaliases_p)
|
||
{
|
||
int i;
|
||
|
||
DEBUG_TRACE ("enter with %s", opcode->name);
|
||
|
||
assert (opcode && inst);
|
||
|
||
/* Check the base opcode. */
|
||
if ((code & opcode->mask) != (opcode->opcode & opcode->mask))
|
||
{
|
||
DEBUG_TRACE ("base opcode match FAIL");
|
||
goto decode_fail;
|
||
}
|
||
|
||
/* Clear inst. */
|
||
memset (inst, '\0', sizeof (aarch64_inst));
|
||
|
||
inst->opcode = opcode;
|
||
inst->value = code;
|
||
|
||
/* Assign operand codes and indexes. */
|
||
for (i = 0; i < AARCH64_MAX_OPND_NUM; ++i)
|
||
{
|
||
if (opcode->operands[i] == AARCH64_OPND_NIL)
|
||
break;
|
||
inst->operands[i].type = opcode->operands[i];
|
||
inst->operands[i].idx = i;
|
||
}
|
||
|
||
/* Call the opcode decoder indicated by flags. */
|
||
if (opcode_has_special_coder (opcode) && do_special_decoding (inst) == 0)
|
||
{
|
||
DEBUG_TRACE ("opcode flag-based decoder FAIL");
|
||
goto decode_fail;
|
||
}
|
||
|
||
/* Call operand decoders. */
|
||
for (i = 0; i < AARCH64_MAX_OPND_NUM; ++i)
|
||
{
|
||
const aarch64_operand *opnd;
|
||
enum aarch64_opnd type;
|
||
type = opcode->operands[i];
|
||
if (type == AARCH64_OPND_NIL)
|
||
break;
|
||
opnd = &aarch64_operands[type];
|
||
if (operand_has_extractor (opnd)
|
||
&& (! aarch64_extract_operand (opnd, &inst->operands[i], code, inst)))
|
||
{
|
||
DEBUG_TRACE ("operand decoder FAIL at operand %d", i);
|
||
goto decode_fail;
|
||
}
|
||
}
|
||
|
||
/* Match the qualifiers. */
|
||
if (aarch64_match_operands_constraint (inst, NULL) == 1)
|
||
{
|
||
/* Arriving here, the CODE has been determined as a valid instruction
|
||
of OPCODE and *INST has been filled with information of this OPCODE
|
||
instruction. Before the return, check if the instruction has any
|
||
alias and should be disassembled in the form of its alias instead.
|
||
If the answer is yes, *INST will be updated. */
|
||
if (!noaliases_p)
|
||
determine_disassembling_preference (inst);
|
||
DEBUG_TRACE ("SUCCESS");
|
||
return 1;
|
||
}
|
||
else
|
||
{
|
||
DEBUG_TRACE ("constraint matching FAIL");
|
||
}
|
||
|
||
decode_fail:
|
||
return 0;
|
||
}
|
||
|
||
/* This does some user-friendly fix-up to *INST. It is currently focus on
|
||
the adjustment of qualifiers to help the printed instruction
|
||
recognized/understood more easily. */
|
||
|
||
static void
|
||
user_friendly_fixup (aarch64_inst *inst)
|
||
{
|
||
switch (inst->opcode->iclass)
|
||
{
|
||
case testbranch:
|
||
/* TBNZ Xn|Wn, #uimm6, label
|
||
Test and Branch Not Zero: conditionally jumps to label if bit number
|
||
uimm6 in register Xn is not zero. The bit number implies the width of
|
||
the register, which may be written and should be disassembled as Wn if
|
||
uimm is less than 32. Limited to a branch offset range of +/- 32KiB.
|
||
*/
|
||
if (inst->operands[1].imm.value < 32)
|
||
inst->operands[0].qualifier = AARCH64_OPND_QLF_W;
|
||
break;
|
||
default: break;
|
||
}
|
||
}
|
||
|
||
/* Decode INSN and fill in *INST the instruction information. */
|
||
|
||
static int
|
||
disas_aarch64_insn (uint64_t pc ATTRIBUTE_UNUSED, uint32_t insn,
|
||
aarch64_inst *inst)
|
||
{
|
||
const aarch64_opcode *opcode = aarch64_opcode_lookup (insn);
|
||
|
||
#ifdef DEBUG_AARCH64
|
||
if (debug_dump)
|
||
{
|
||
const aarch64_opcode *tmp = opcode;
|
||
printf ("\n");
|
||
DEBUG_TRACE ("opcode lookup:");
|
||
while (tmp != NULL)
|
||
{
|
||
aarch64_verbose (" %s", tmp->name);
|
||
tmp = aarch64_find_next_opcode (tmp);
|
||
}
|
||
}
|
||
#endif /* DEBUG_AARCH64 */
|
||
|
||
/* A list of opcodes may have been found, as aarch64_opcode_lookup cannot
|
||
distinguish some opcodes, e.g. SSHR and MOVI, which almost share the same
|
||
opcode field and value, apart from the difference that one of them has an
|
||
extra field as part of the opcode, but such a field is used for operand
|
||
encoding in other opcode(s) ('immh' in the case of the example). */
|
||
while (opcode != NULL)
|
||
{
|
||
/* But only one opcode can be decoded successfully for, as the
|
||
decoding routine will check the constraint carefully. */
|
||
if (aarch64_opcode_decode (opcode, insn, inst, no_aliases) == 1)
|
||
return ERR_OK;
|
||
opcode = aarch64_find_next_opcode (opcode);
|
||
}
|
||
|
||
return ERR_UND;
|
||
}
|
||
|
||
/* Print operands. */
|
||
|
||
static void
|
||
print_operands (bfd_vma pc, const aarch64_opcode *opcode,
|
||
const aarch64_opnd_info *opnds, struct disassemble_info *info)
|
||
{
|
||
int i, pcrel_p, num_printed;
|
||
for (i = 0, num_printed = 0; i < AARCH64_MAX_OPND_NUM; ++i)
|
||
{
|
||
const size_t size = 128;
|
||
char str[size];
|
||
/* We regard the opcode operand info more, however we also look into
|
||
the inst->operands to support the disassembling of the optional
|
||
operand.
|
||
The two operand code should be the same in all cases, apart from
|
||
when the operand can be optional. */
|
||
if (opcode->operands[i] == AARCH64_OPND_NIL
|
||
|| opnds[i].type == AARCH64_OPND_NIL)
|
||
break;
|
||
|
||
/* Generate the operand string in STR. */
|
||
aarch64_print_operand (str, size, pc, opcode, opnds, i, &pcrel_p,
|
||
&info->target);
|
||
|
||
/* Print the delimiter (taking account of omitted operand(s)). */
|
||
if (str[0] != '\0')
|
||
(*info->fprintf_func) (info->stream, "%s",
|
||
num_printed++ == 0 ? "\t" : ", ");
|
||
|
||
/* Print the operand. */
|
||
if (pcrel_p)
|
||
(*info->print_address_func) (info->target, info);
|
||
else
|
||
(*info->fprintf_func) (info->stream, "%s", str);
|
||
}
|
||
}
|
||
|
||
/* Print the instruction mnemonic name. */
|
||
|
||
static void
|
||
print_mnemonic_name (const aarch64_inst *inst, struct disassemble_info *info)
|
||
{
|
||
if (inst->opcode->flags & F_COND)
|
||
{
|
||
/* For instructions that are truly conditionally executed, e.g. b.cond,
|
||
prepare the full mnemonic name with the corresponding condition
|
||
suffix. */
|
||
char name[8], *ptr;
|
||
size_t len;
|
||
|
||
ptr = strchr (inst->opcode->name, '.');
|
||
assert (ptr && inst->cond);
|
||
len = ptr - inst->opcode->name;
|
||
assert (len < 8);
|
||
strncpy (name, inst->opcode->name, len);
|
||
name [len] = '\0';
|
||
(*info->fprintf_func) (info->stream, "%s.%s", name, inst->cond->names[0]);
|
||
}
|
||
else
|
||
(*info->fprintf_func) (info->stream, "%s", inst->opcode->name);
|
||
}
|
||
|
||
/* Print the instruction according to *INST. */
|
||
|
||
static void
|
||
print_aarch64_insn (bfd_vma pc, const aarch64_inst *inst,
|
||
struct disassemble_info *info)
|
||
{
|
||
print_mnemonic_name (inst, info);
|
||
print_operands (pc, inst->opcode, inst->operands, info);
|
||
}
|
||
|
||
/* Entry-point of the instruction disassembler and printer. */
|
||
|
||
static void
|
||
print_insn_aarch64_word (bfd_vma pc,
|
||
uint32_t word,
|
||
struct disassemble_info *info)
|
||
{
|
||
static const char *err_msg[6] =
|
||
{
|
||
[ERR_OK] = "_",
|
||
[-ERR_UND] = "undefined",
|
||
[-ERR_UNP] = "unpredictable",
|
||
[-ERR_NYI] = "NYI"
|
||
};
|
||
|
||
int ret;
|
||
aarch64_inst inst;
|
||
|
||
info->insn_info_valid = 1;
|
||
info->branch_delay_insns = 0;
|
||
info->data_size = 0;
|
||
info->target = 0;
|
||
info->target2 = 0;
|
||
|
||
if (info->flags & INSN_HAS_RELOC)
|
||
/* If the instruction has a reloc associated with it, then
|
||
the offset field in the instruction will actually be the
|
||
addend for the reloc. (If we are using REL type relocs).
|
||
In such cases, we can ignore the pc when computing
|
||
addresses, since the addend is not currently pc-relative. */
|
||
pc = 0;
|
||
|
||
ret = disas_aarch64_insn (pc, word, &inst);
|
||
|
||
if (((word >> 21) & 0x3ff) == 1)
|
||
{
|
||
/* RESERVED for ALES. */
|
||
assert (ret != ERR_OK);
|
||
ret = ERR_NYI;
|
||
}
|
||
|
||
switch (ret)
|
||
{
|
||
case ERR_UND:
|
||
case ERR_UNP:
|
||
case ERR_NYI:
|
||
/* Handle undefined instructions. */
|
||
info->insn_type = dis_noninsn;
|
||
(*info->fprintf_func) (info->stream,".inst\t0x%08x ; %s",
|
||
word, err_msg[-ret]);
|
||
break;
|
||
case ERR_OK:
|
||
user_friendly_fixup (&inst);
|
||
print_aarch64_insn (pc, &inst, info);
|
||
break;
|
||
default:
|
||
abort ();
|
||
}
|
||
}
|
||
|
||
/* Disallow mapping symbols ($x, $d etc) from
|
||
being displayed in symbol relative addresses. */
|
||
|
||
bfd_boolean
|
||
aarch64_symbol_is_valid (asymbol * sym,
|
||
struct disassemble_info * info ATTRIBUTE_UNUSED)
|
||
{
|
||
const char * name;
|
||
|
||
if (sym == NULL)
|
||
return FALSE;
|
||
|
||
name = bfd_asymbol_name (sym);
|
||
|
||
return name
|
||
&& (name[0] != '$'
|
||
|| (name[1] != 'x' && name[1] != 'd')
|
||
|| (name[2] != '\0' && name[2] != '.'));
|
||
}
|
||
|
||
/* Print data bytes on INFO->STREAM. */
|
||
|
||
static void
|
||
print_insn_data (bfd_vma pc ATTRIBUTE_UNUSED,
|
||
uint32_t word,
|
||
struct disassemble_info *info)
|
||
{
|
||
switch (info->bytes_per_chunk)
|
||
{
|
||
case 1:
|
||
info->fprintf_func (info->stream, ".byte\t0x%02x", word);
|
||
break;
|
||
case 2:
|
||
info->fprintf_func (info->stream, ".short\t0x%04x", word);
|
||
break;
|
||
case 4:
|
||
info->fprintf_func (info->stream, ".word\t0x%08x", word);
|
||
break;
|
||
default:
|
||
abort ();
|
||
}
|
||
}
|
||
|
||
/* Try to infer the code or data type from a symbol.
|
||
Returns nonzero if *MAP_TYPE was set. */
|
||
|
||
static int
|
||
get_sym_code_type (struct disassemble_info *info, int n,
|
||
enum map_type *map_type)
|
||
{
|
||
elf_symbol_type *es;
|
||
unsigned int type;
|
||
const char *name;
|
||
|
||
es = *(elf_symbol_type **)(info->symtab + n);
|
||
type = ELF_ST_TYPE (es->internal_elf_sym.st_info);
|
||
|
||
/* If the symbol has function type then use that. */
|
||
if (type == STT_FUNC)
|
||
{
|
||
*map_type = MAP_INSN;
|
||
return TRUE;
|
||
}
|
||
|
||
/* Check for mapping symbols. */
|
||
name = bfd_asymbol_name(info->symtab[n]);
|
||
if (name[0] == '$'
|
||
&& (name[1] == 'x' || name[1] == 'd')
|
||
&& (name[2] == '\0' || name[2] == '.'))
|
||
{
|
||
*map_type = (name[1] == 'x' ? MAP_INSN : MAP_DATA);
|
||
return TRUE;
|
||
}
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
/* Entry-point of the AArch64 disassembler. */
|
||
|
||
int
|
||
print_insn_aarch64 (bfd_vma pc,
|
||
struct disassemble_info *info)
|
||
{
|
||
bfd_byte buffer[INSNLEN];
|
||
int status;
|
||
void (*printer) (bfd_vma, uint32_t, struct disassemble_info *);
|
||
bfd_boolean found = FALSE;
|
||
unsigned int size = 4;
|
||
unsigned long data;
|
||
|
||
if (info->disassembler_options)
|
||
{
|
||
set_default_aarch64_dis_options (info);
|
||
|
||
parse_aarch64_dis_options (info->disassembler_options);
|
||
|
||
/* To avoid repeated parsing of these options, we remove them here. */
|
||
info->disassembler_options = NULL;
|
||
}
|
||
|
||
/* Aarch64 instructions are always little-endian */
|
||
info->endian_code = BFD_ENDIAN_LITTLE;
|
||
|
||
/* First check the full symtab for a mapping symbol, even if there
|
||
are no usable non-mapping symbols for this address. */
|
||
if (info->symtab_size != 0
|
||
&& bfd_asymbol_flavour (*info->symtab) == bfd_target_elf_flavour)
|
||
{
|
||
enum map_type type = MAP_INSN;
|
||
int last_sym = -1;
|
||
bfd_vma addr;
|
||
int n;
|
||
|
||
if (pc <= last_mapping_addr)
|
||
last_mapping_sym = -1;
|
||
|
||
/* Start scanning at the start of the function, or wherever
|
||
we finished last time. */
|
||
n = info->symtab_pos + 1;
|
||
if (n < last_mapping_sym)
|
||
n = last_mapping_sym;
|
||
|
||
/* Scan up to the location being disassembled. */
|
||
for (; n < info->symtab_size; n++)
|
||
{
|
||
addr = bfd_asymbol_value (info->symtab[n]);
|
||
if (addr > pc)
|
||
break;
|
||
if ((info->section == NULL
|
||
|| info->section == info->symtab[n]->section)
|
||
&& get_sym_code_type (info, n, &type))
|
||
{
|
||
last_sym = n;
|
||
found = TRUE;
|
||
}
|
||
}
|
||
|
||
if (!found)
|
||
{
|
||
n = info->symtab_pos;
|
||
if (n < last_mapping_sym)
|
||
n = last_mapping_sym;
|
||
|
||
/* No mapping symbol found at this address. Look backwards
|
||
for a preceeding one. */
|
||
for (; n >= 0; n--)
|
||
{
|
||
if (get_sym_code_type (info, n, &type))
|
||
{
|
||
last_sym = n;
|
||
found = TRUE;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
last_mapping_sym = last_sym;
|
||
last_type = type;
|
||
|
||
/* Look a little bit ahead to see if we should print out
|
||
less than four bytes of data. If there's a symbol,
|
||
mapping or otherwise, after two bytes then don't
|
||
print more. */
|
||
if (last_type == MAP_DATA)
|
||
{
|
||
size = 4 - (pc & 3);
|
||
for (n = last_sym + 1; n < info->symtab_size; n++)
|
||
{
|
||
addr = bfd_asymbol_value (info->symtab[n]);
|
||
if (addr > pc)
|
||
{
|
||
if (addr - pc < size)
|
||
size = addr - pc;
|
||
break;
|
||
}
|
||
}
|
||
/* If the next symbol is after three bytes, we need to
|
||
print only part of the data, so that we can use either
|
||
.byte or .short. */
|
||
if (size == 3)
|
||
size = (pc & 1) ? 1 : 2;
|
||
}
|
||
}
|
||
|
||
if (last_type == MAP_DATA)
|
||
{
|
||
/* size was set above. */
|
||
info->bytes_per_chunk = size;
|
||
info->display_endian = info->endian;
|
||
printer = print_insn_data;
|
||
}
|
||
else
|
||
{
|
||
info->bytes_per_chunk = size = INSNLEN;
|
||
info->display_endian = info->endian_code;
|
||
printer = print_insn_aarch64_word;
|
||
}
|
||
|
||
status = (*info->read_memory_func) (pc, buffer, size, info);
|
||
if (status != 0)
|
||
{
|
||
(*info->memory_error_func) (status, pc, info);
|
||
return -1;
|
||
}
|
||
|
||
data = bfd_get_bits (buffer, size * 8,
|
||
info->display_endian == BFD_ENDIAN_BIG);
|
||
|
||
(*printer) (pc, data, info);
|
||
|
||
return size;
|
||
}
|
||
|
||
void
|
||
print_aarch64_disassembler_options (FILE *stream)
|
||
{
|
||
fprintf (stream, _("\n\
|
||
The following AARCH64 specific disassembler options are supported for use\n\
|
||
with the -M switch (multiple options should be separated by commas):\n"));
|
||
|
||
fprintf (stream, _("\n\
|
||
no-aliases Don't print instruction aliases.\n"));
|
||
|
||
fprintf (stream, _("\n\
|
||
aliases Do print instruction aliases.\n"));
|
||
|
||
#ifdef DEBUG_AARCH64
|
||
fprintf (stream, _("\n\
|
||
debug_dump Temp switch for debug trace.\n"));
|
||
#endif /* DEBUG_AARCH64 */
|
||
|
||
fprintf (stream, _("\n"));
|
||
}
|