/* C-SKY disassembler.
Copyright (C) 1988-2024 Free Software Foundation, Inc.
Contributed by C-SKY Microsystems and Mentor Graphics.
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; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston,
MA 02110-1301, USA. */
struct csky_dis_info
{
/* Mem to disassemble. */
bfd_vma mem;
/* Disassemble info. */
disassemble_info *info;
/* Opcode information. */
struct csky_opcode_info const *opinfo;
uint64_t isa;
/* The value of operand to show. */
int value;
/* Whether to look up/print a symbol name. */
int need_output_symbol;
} dis_info;
enum sym_type last_type;
int last_map_sym = 1;
bfd_vma last_map_addr = 0;
int using_abi = 0;
/* Only for objdump tool. */
#define INIT_MACH_FLAG 0xffffffff
#define BINARY_MACH_FLAG 0x0
static unsigned int mach_flag = INIT_MACH_FLAG;
static void
print_insn_data (bfd_vma pc ATTRIBUTE_UNUSED,
struct disassemble_info *info,
long given)
{
switch (info->bytes_per_chunk)
{
case 1:
info->fprintf_func (info->stream, ".byte\t0x%02lx", given);
break;
case 2:
info->fprintf_func (info->stream, ".short\t0x%04lx", given);
break;
case 4:
info->fprintf_func (info->stream, ".long\t0x%08lx", given);
break;
default:
abort ();
}
}
static int
csky_get_mask (struct csky_opcode_info const *pinfo)
{
int i = 0;
int mask = 0;
/* List type. */
if (pinfo->operand_num == -1)
mask |= csky_get_operand_mask (&pinfo->oprnd.oprnds[i]);
else
for (; i < pinfo->operand_num; i++)
mask |= csky_get_operand_mask (&pinfo->oprnd.oprnds[i]);
mask = ~mask;
return mask;
}
static unsigned int
csky_chars_to_number (unsigned char * buf, int n)
{
int i;
unsigned int val = 0;
if (dis_info.info->endian == BFD_ENDIAN_BIG)
for (i = 0; i < n; i++)
val = val << 8 | buf[i];
else
for (i = n - 1; i >= 0; i--)
val = val << 8 | buf[i];
return val;
}
static struct csky_opcode const *g_opcodeP;
static struct csky_opcode const *
csky_find_inst_info (struct csky_opcode_info const **pinfo,
CSKY_INST_TYPE inst, int length)
{
int i;
unsigned int mask;
struct csky_opcode const *p;
p = g_opcodeP;
while (p->mnemonic)
{
if (!(p->isa_flag16 & dis_info.isa)
&& !(p->isa_flag32 & dis_info.isa))
{
p++;
continue;
}
/* Get the opcode mask. */
for (i = 0; i < OP_TABLE_NUM; i++)
if (length == 2)
{
mask = csky_get_mask (&p->op16[i]);
if (mask != 0 && (inst & mask) == p->op16[i].opcode)
{
*pinfo = &p->op16[i];
g_opcodeP = p;
return p;
}
}
else if (length == 4)
{
mask = csky_get_mask (&p->op32[i]);
if (mask != 0
&& ((unsigned long)(inst & mask)
== (unsigned long)p->op32[i].opcode))
{
*pinfo = &p->op32[i];
g_opcodeP = p;
return p;
}
}
p++;
}
return NULL;
}
static bool
is_extern_symbol (struct disassemble_info *info, int addr)
{
unsigned int rel_count = 0;
if (info->section == NULL)
return 0;
if ((info->section->flags & SEC_RELOC) != 0) /* Fit .o file. */
{
struct reloc_cache_entry *pt = info->section->relocation;
for (; rel_count < info->section->reloc_count; rel_count++, pt++)
if ((long unsigned int)addr == pt->address)
return true;
return false;
}
return false;
}
/* Suppress printing of mapping symbols emitted by the assembler to mark
the beginning of code and data sequences. */
/* Get general register name. */
static const char *
get_gr_name (int regno)
{
return csky_get_general_reg_name (mach_flag, regno, using_abi);
}
/* Get control register name. */
static const char *
get_cr_name (unsigned int regno, int bank)
{
return csky_get_control_reg_name (mach_flag, bank, regno, using_abi);
}
static int
csky_output_operand (char *str, struct operand const *oprnd,
CSKY_INST_TYPE inst, int reloc ATTRIBUTE_UNUSED)
{
int ret = 0;;
int bit = 0;
int result = 0;
bfd_vma value;
int mask = oprnd->mask;
int max = 0;
char buf[128];
/* Get operand value with mask. */
value = inst & mask;
for (; mask; mask >>= 1, value >>=1)
if (mask & 0x1)
{
result |= ((value & 0x1) << bit);
max |= (1 << bit);
bit++;
}
value = result;
/* Here is general instructions that have no reloc. */
switch (oprnd->type)
{
case OPRND_TYPE_CTRLREG:
if (IS_CSKY_V1(mach_flag) && ((value & 0x1f) == 0x1f))
return -1;
strcat (str, get_cr_name((value & 0x1f), (value >> 5)));
break;
case OPRND_TYPE_DUMMY_REG:
mask = dis_info.opinfo->oprnd.oprnds[0].mask;
value = inst & mask;
for (; mask; mask >>= 1, value >>=1)
if (mask & 0x1)
{
result |= ((value & 0x1) << bit);
bit++;
}
value = result;
strcat (str, get_gr_name (value));
break;
case OPRND_TYPE_GREG0_7:
case OPRND_TYPE_GREG0_15:
case OPRND_TYPE_GREG16_31:
case OPRND_TYPE_REGnsplr:
case OPRND_TYPE_AREG:
strcat (str, get_gr_name (value));
break;
case OPRND_TYPE_CPREG:
sprintf (buf, "cpr%d", (int)value);
strcat (str, buf);
break;
case OPRND_TYPE_FREG:
sprintf (buf, "fr%d", (int)value);
strcat (str, buf);
break;
case OPRND_TYPE_VREG:
dis_info.value = value;
sprintf (buf, "vr%d", (int)value);
strcat (str, buf);
break;
case OPRND_TYPE_CPCREG:
sprintf (buf, "cpcr%d", (int)value);
strcat (str, buf);
break;
case OPRND_TYPE_CPIDX:
sprintf (buf, "cp%d", (int)value);
strcat (str, buf);
break;
case OPRND_TYPE_IMM2b_JMPIX:
value = (value + 2) << 3;
sprintf (buf, "%d", (int)value);
strcat (str, buf);
break;
case OPRND_TYPE_IMM_LDST:
case OPRND_TYPE_IMM_FLDST:
value <<= oprnd->shift;
sprintf (buf, "0x%x", (unsigned int)value);
strcat (str, buf);
break;
case OPRND_TYPE_IMM7b_LS2:
case OPRND_TYPE_IMM8b_LS2:
sprintf (buf, "%d", (int)(value << 2));
strcat (str, buf);
ret = 0;
break;
case OPRND_TYPE_IMM5b_BMASKI:
if ((value != 0) && (value > 31 || value < 8))
{
ret = -1;
break;
}
sprintf (buf, "%d", (int)value);
strcat (str, buf);
ret = 0;
break;
case OPRND_TYPE_IMM5b_1_31:
if (value > 31 || value < 1)
{
ret = -1;
break;
}
sprintf (buf, "%d", (int)value);
strcat (str, buf);
ret = 0;
break;
case OPRND_TYPE_IMM5b_7_31:
if (value > 31 || value < 7)
{
ret = -1;
break;
}
sprintf (buf, "%d", (int)value);
strcat (str, buf);
ret = 0;
break;
case OPRND_TYPE_IMM5b_VSH:
{
char num[128];
value = ((value & 0x1) << 4) | (value >> 1);
sprintf (num, "%d", (int)value);
strcat (str, num);
ret = 0;
break;
}
case OPRND_TYPE_MSB2SIZE:
case OPRND_TYPE_LSB2SIZE:
{
static int size;
if (oprnd->type == OPRND_TYPE_MSB2SIZE)
size = value;
else
{
str[strlen (str) - 2] = '\0';
sprintf (buf, "%d, %d", (int)(size + value), (int)value);
strcat (str, buf);
}
break;
}
case OPRND_TYPE_IMM1b:
case OPRND_TYPE_IMM2b:
case OPRND_TYPE_IMM4b:
case OPRND_TYPE_IMM5b:
case OPRND_TYPE_IMM5b_LS:
case OPRND_TYPE_IMM7b:
case OPRND_TYPE_IMM8b:
case OPRND_TYPE_IMM12b:
case OPRND_TYPE_IMM15b:
case OPRND_TYPE_IMM16b:
case OPRND_TYPE_IMM16b_MOVIH:
case OPRND_TYPE_IMM16b_ORI:
sprintf (buf, "%d", (int)value);
strcat (str, buf);
ret = 0;
break;
case OPRND_TYPE_OFF8b:
case OPRND_TYPE_OFF16b:
{
unsigned char ibytes[4];
int shift = oprnd->shift;
int status;
unsigned int mem_val;
dis_info.info->stop_vma = 0;
value = ((dis_info.mem + (value << shift)
+ ((IS_CSKY_V1 (mach_flag)) ? 2 : 0))
& 0xfffffffc);
status = dis_info.info->read_memory_func (value, ibytes, 4,
dis_info.info);
if (status != 0)
{
dis_info.info->memory_error_func (status, dis_info.mem,
dis_info.info);
return -1;
}
mem_val = csky_chars_to_number (ibytes, 4);
/* Remove [] around literal value to match ABI syntax. */
sprintf (buf, "0x%X", mem_val);
strcat (str, buf);
/* For jmpi/jsri, we'll try to get a symbol for the target. */
if (dis_info.info->print_address_func && mem_val != 0)
{
dis_info.value = mem_val;
dis_info.need_output_symbol = 1;
}
else
{
sprintf (buf, "\t// from address pool at 0x%x",
(unsigned int)value);
strcat (str, buf);
}
break;
}
case OPRND_TYPE_BLOOP_OFF4b:
case OPRND_TYPE_BLOOP_OFF12b:
case OPRND_TYPE_OFF11b:
case OPRND_TYPE_OFF16b_LSL1:
case OPRND_TYPE_IMM_OFF18b:
case OPRND_TYPE_OFF26b:
{
int shift = oprnd->shift;
if (value & ((max >> 1) + 1))
value |= ~max;
if (is_extern_symbol (dis_info.info, dis_info.mem))
value = 0;
else if (IS_CSKY_V1 (mach_flag))
value = dis_info.mem + 2 + (value << shift);
else
value = dis_info.mem + (value << shift);
dis_info.need_output_symbol = 1;
dis_info.value= value;
sprintf (buf, "0x%x", (unsigned int)value);
strcat (str, buf);
break;
}
case OPRND_TYPE_CONSTANT:
case OPRND_TYPE_FCONSTANT:
{
int shift = oprnd->shift;
bfd_byte ibytes[8];
int status;
bfd_vma addr;
int nbytes;
strcat (str, buf);
break;
}
case OPRND_TYPE_ELRW_CONSTANT:
{
int shift = oprnd->shift;
char ibytes[4];
int status;
bfd_vma addr;
dis_info.info->stop_vma = 0;
value = 0x80 + ((~value) & 0x7f);
value = value << shift;
addr = (dis_info.mem + value) & 0xfffffffc;
status = dis_info.info->read_memory_func (addr, (bfd_byte *)ibytes,
4, dis_info.info);
if (status != 0)
/* Address out of bounds. -> lrw rx, [pc, 0ffset]. */
sprintf (buf, "[pc, %d]\t// from address pool at %x", (int) value,
(unsigned int)addr);
else
{
dis_info.value = addr;
value = csky_chars_to_number ((unsigned char *)ibytes, 4);
dis_info.need_output_symbol = 1;
sprintf (buf, "0x%x", (unsigned int)value);
}
strcat (str, buf);
break;
}
case OPRND_TYPE_SFLOAT:
case OPRND_TYPE_DFLOAT:
{
/* This is for fmovis/fmovid, which have an internal 13-bit
encoding that they convert to single/double precision
(respectively). We'll convert the 13-bit encoding to an IEEE
double and then to host double format to print it.
Sign bit: bit 20.
4-bit exponent: bits 19:16, biased by 11.
8-bit mantissa: split between 24:21 and 7:4. */
uint64_t imm4;
uint64_t imm8;
uint64_t dbnum;
unsigned char valbytes[8];
double fvalue;
/* 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)
{
bfd_vma addr;
int n;
int last_sym = -1;
enum sym_type type = CUR_TEXT;
if (memaddr <= last_map_addr)
last_map_sym = -1;
/* Start scanning at the start of the function, or wherever
we finished last time. */
n = 0;
if (n < last_map_sym)
n = last_map_sym;
/* Scan up to the location being disassembled. */
for (; n < info->symtab_size; n++)
{
addr = bfd_asymbol_value (info->symtab[n]);
if (addr > memaddr)
break;
if ((info->section == NULL
|| info->section == info->symtab[n]->section)
&& get_sym_code_type (info, n, &type))
last_sym = n;
}
last_map_sym = last_sym;
last_type = type;
is_data = (last_type == CUR_DATA);
if (is_data)
{
size = 4 - ( memaddr & 3);
for (n = last_sym + 1; n < info->symtab_size; n++)
{
addr = bfd_asymbol_value (info->symtab[n]);
if (addr > memaddr)
{
if (addr - memaddr < size)
size = addr - memaddr;
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 = (memaddr & 1) ? 1 : 2;
}
}
info->bytes_per_line = 4;
if (is_data)
{
int i;
/* Size was already set above. */
info->bytes_per_chunk = size;
printer = print_insn_data;
status = info->read_memory_func (memaddr, (bfd_byte *) buf, size, info);
given = 0;
if (info->endian == BFD_ENDIAN_LITTLE)
for (i = size - 1; i >= 0; i--)
given = buf[i] | (given << 8);
else
for (i = 0; i < (int) size; i++)
given = buf[i] | (given << 8);