| /* Disassemble D10V instructions. |
| Copyright (C) 1996-2021 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 program; if not, write to the Free Software |
| Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, |
| MA 02110-1301, USA. */ |
| |
| #include "sysdep.h" |
| #include <stdio.h> |
| #include "opcode/d10v.h" |
| #include "disassemble.h" |
| |
| /* The PC wraps at 18 bits, except for the segment number, |
| so use this mask to keep the parts we want. */ |
| #define PC_MASK 0x0303FFFF |
| |
| static void |
| print_operand (struct d10v_operand *oper, |
| unsigned long insn, |
| struct d10v_opcode *op, |
| bfd_vma memaddr, |
| struct disassemble_info *info) |
| { |
| int num, shift; |
| |
| if (oper->flags == OPERAND_ATMINUS) |
| { |
| (*info->fprintf_func) (info->stream, "@-"); |
| return; |
| } |
| if (oper->flags == OPERAND_MINUS) |
| { |
| (*info->fprintf_func) (info->stream, "-"); |
| return; |
| } |
| if (oper->flags == OPERAND_PLUS) |
| { |
| (*info->fprintf_func) (info->stream, "+"); |
| return; |
| } |
| if (oper->flags == OPERAND_ATSIGN) |
| { |
| (*info->fprintf_func) (info->stream, "@"); |
| return; |
| } |
| if (oper->flags == OPERAND_ATPAR) |
| { |
| (*info->fprintf_func) (info->stream, "@("); |
| return; |
| } |
| |
| shift = oper->shift; |
| |
| /* The LONG_L format shifts registers over by 15. */ |
| if (op->format == LONG_L && (oper->flags & OPERAND_REG)) |
| shift += 15; |
| |
| num = (insn >> shift) & (0x7FFFFFFF >> (31 - oper->bits)); |
| |
| if (oper->flags & OPERAND_REG) |
| { |
| int i; |
| int match = 0; |
| |
| num += (oper->flags |
| & (OPERAND_GPR | OPERAND_FFLAG | OPERAND_CFLAG | OPERAND_CONTROL)); |
| if (oper->flags & (OPERAND_ACC0 | OPERAND_ACC1)) |
| num += num ? OPERAND_ACC1 : OPERAND_ACC0; |
| for (i = 0; i < d10v_reg_name_cnt (); i++) |
| { |
| if (num == (d10v_predefined_registers[i].value & ~ OPERAND_SP)) |
| { |
| if (d10v_predefined_registers[i].pname) |
| (*info->fprintf_func) (info->stream, "%s", |
| d10v_predefined_registers[i].pname); |
| else |
| (*info->fprintf_func) (info->stream, "%s", |
| d10v_predefined_registers[i].name); |
| match = 1; |
| break; |
| } |
| } |
| if (match == 0) |
| { |
| /* This would only get executed if a register was not in the |
| register table. */ |
| if (oper->flags & (OPERAND_ACC0 | OPERAND_ACC1)) |
| (*info->fprintf_func) (info->stream, "a"); |
| else if (oper->flags & OPERAND_CONTROL) |
| (*info->fprintf_func) (info->stream, "cr"); |
| else if (oper->flags & OPERAND_REG) |
| (*info->fprintf_func) (info->stream, "r"); |
| (*info->fprintf_func) (info->stream, "%d", num & REGISTER_MASK); |
| } |
| } |
| else |
| { |
| /* Addresses are right-shifted by 2. */ |
| if (oper->flags & OPERAND_ADDR) |
| { |
| long max; |
| int neg = 0; |
| |
| max = (1 << (oper->bits - 1)); |
| if (num & max) |
| { |
| num = -num & ((1 << oper->bits) - 1); |
| neg = 1; |
| } |
| num = num << 2; |
| if (info->flags & INSN_HAS_RELOC) |
| (*info->print_address_func) (num & PC_MASK, info); |
| else |
| { |
| if (neg) |
| (*info->print_address_func) ((memaddr - num) & PC_MASK, info); |
| else |
| (*info->print_address_func) ((memaddr + num) & PC_MASK, info); |
| } |
| } |
| else |
| { |
| if (oper->flags & OPERAND_SIGNED) |
| { |
| int max = (1 << (oper->bits - 1)); |
| if (num & max) |
| { |
| num = -num & ((1 << oper->bits) - 1); |
| (*info->fprintf_func) (info->stream, "-"); |
| } |
| } |
| (*info->fprintf_func) (info->stream, "0x%x", num); |
| } |
| } |
| } |
| |
| static void |
| dis_long (unsigned long insn, |
| bfd_vma memaddr, |
| struct disassemble_info *info) |
| { |
| int i; |
| struct d10v_opcode *op = (struct d10v_opcode *) d10v_opcodes; |
| struct d10v_operand *oper; |
| int need_paren = 0; |
| int match = 0; |
| |
| while (op->name) |
| { |
| if ((op->format & LONG_OPCODE) |
| && ((op->mask & insn) == (unsigned long) op->opcode)) |
| { |
| match = 1; |
| (*info->fprintf_func) (info->stream, "%s\t", op->name); |
| |
| for (i = 0; op->operands[i]; i++) |
| { |
| oper = (struct d10v_operand *) &d10v_operands[op->operands[i]]; |
| if (oper->flags == OPERAND_ATPAR) |
| need_paren = 1; |
| print_operand (oper, insn, op, memaddr, info); |
| if (op->operands[i + 1] && oper->bits |
| && d10v_operands[op->operands[i + 1]].flags != OPERAND_PLUS |
| && d10v_operands[op->operands[i + 1]].flags != OPERAND_MINUS) |
| (*info->fprintf_func) (info->stream, ", "); |
| } |
| break; |
| } |
| op++; |
| } |
| |
| if (!match) |
| (*info->fprintf_func) (info->stream, ".long\t0x%08lx", insn); |
| |
| if (need_paren) |
| (*info->fprintf_func) (info->stream, ")"); |
| } |
| |
| static void |
| dis_2_short (unsigned long insn, |
| bfd_vma memaddr, |
| struct disassemble_info *info, |
| int order) |
| { |
| int i, j; |
| unsigned int ins[2]; |
| struct d10v_opcode *op; |
| int match, num_match = 0; |
| struct d10v_operand *oper; |
| int need_paren = 0; |
| |
| ins[0] = (insn & 0x3FFFFFFF) >> 15; |
| ins[1] = insn & 0x00007FFF; |
| |
| for (j = 0; j < 2; j++) |
| { |
| op = (struct d10v_opcode *) d10v_opcodes; |
| match = 0; |
| while (op->name) |
| { |
| if ((op->format & SHORT_OPCODE) |
| && ((((unsigned int) op->mask) & ins[j]) |
| == (unsigned int) op->opcode)) |
| { |
| (*info->fprintf_func) (info->stream, "%s\t", op->name); |
| for (i = 0; op->operands[i]; i++) |
| { |
| oper = (struct d10v_operand *) &d10v_operands[op->operands[i]]; |
| if (oper->flags == OPERAND_ATPAR) |
| need_paren = 1; |
| print_operand (oper, ins[j], op, memaddr, info); |
| if (op->operands[i + 1] && oper->bits |
| && d10v_operands[op->operands[i + 1]].flags != OPERAND_PLUS |
| && d10v_operands[op->operands[i + 1]].flags != OPERAND_MINUS) |
| (*info->fprintf_func) (info->stream, ", "); |
| } |
| match = 1; |
| num_match++; |
| break; |
| } |
| op++; |
| } |
| if (!match) |
| (*info->fprintf_func) (info->stream, "unknown"); |
| |
| switch (order) |
| { |
| case 0: |
| (*info->fprintf_func) (info->stream, "\t->\t"); |
| order = -1; |
| break; |
| case 1: |
| (*info->fprintf_func) (info->stream, "\t<-\t"); |
| order = -1; |
| break; |
| case 2: |
| (*info->fprintf_func) (info->stream, "\t||\t"); |
| order = -1; |
| break; |
| default: |
| break; |
| } |
| } |
| |
| if (num_match == 0) |
| (*info->fprintf_func) (info->stream, ".long\t0x%08lx", insn); |
| |
| if (need_paren) |
| (*info->fprintf_func) (info->stream, ")"); |
| } |
| |
| int |
| print_insn_d10v (bfd_vma memaddr, struct disassemble_info *info) |
| { |
| int status; |
| bfd_byte buffer[4]; |
| unsigned long insn; |
| |
| status = (*info->read_memory_func) (memaddr, buffer, 4, info); |
| if (status != 0) |
| { |
| (*info->memory_error_func) (status, memaddr, info); |
| return -1; |
| } |
| insn = bfd_getb32 (buffer); |
| |
| status = insn & FM11; |
| switch (status) |
| { |
| case 0: |
| dis_2_short (insn, memaddr, info, 2); |
| break; |
| case FM01: |
| dis_2_short (insn, memaddr, info, 0); |
| break; |
| case FM10: |
| dis_2_short (insn, memaddr, info, 1); |
| break; |
| case FM11: |
| dis_long (insn, memaddr, info); |
| break; |
| } |
| return 4; |
| } |