| /* s12z-dis.c -- Freescale S12Z disassembly |
| Copyright (C) 2018-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 <stdint.h> |
| #include <stdbool.h> |
| #include <assert.h> |
| |
| #include "opcode/s12z.h" |
| #include "bfd.h" |
| #include "dis-asm.h" |
| #include "disassemble.h" |
| #include "s12z-opc.h" |
| #include "opintl.h" |
| |
| struct mem_read_abstraction |
| { |
| struct mem_read_abstraction_base base; |
| bfd_vma memaddr; |
| struct disassemble_info* info; |
| }; |
| |
| static void |
| advance (struct mem_read_abstraction_base *b) |
| { |
| struct mem_read_abstraction *mra = (struct mem_read_abstraction *) b; |
| mra->memaddr ++; |
| } |
| |
| static bfd_vma |
| posn (struct mem_read_abstraction_base *b) |
| { |
| struct mem_read_abstraction *mra = (struct mem_read_abstraction *) b; |
| return mra->memaddr; |
| } |
| |
| static int |
| abstract_read_memory (struct mem_read_abstraction_base *b, |
| int offset, |
| size_t n, bfd_byte *bytes) |
| { |
| struct mem_read_abstraction *mra = (struct mem_read_abstraction *) b; |
| |
| int status = (*mra->info->read_memory_func) (mra->memaddr + offset, |
| bytes, n, mra->info); |
| if (status != 0) |
| (*mra->info->memory_error_func) (status, mra->memaddr + offset, |
| mra->info); |
| return status != 0 ? -1 : 0; |
| } |
| |
| /* Start of disassembly file. */ |
| const struct reg registers[S12Z_N_REGISTERS] = |
| { |
| {"d2", 2}, |
| {"d3", 2}, |
| {"d4", 2}, |
| {"d5", 2}, |
| |
| {"d0", 1}, |
| {"d1", 1}, |
| |
| {"d6", 4}, |
| {"d7", 4}, |
| |
| {"x", 3}, |
| {"y", 3}, |
| {"s", 3}, |
| {"p", 3}, |
| {"cch", 1}, |
| {"ccl", 1}, |
| {"ccw", 2} |
| }; |
| |
| static const char *mnemonics[] = |
| { |
| "!!invalid!!", |
| "psh", |
| "pul", |
| "tbne", "tbeq", "tbpl", "tbmi", "tbgt", "tble", |
| "dbne", "dbeq", "dbpl", "dbmi", "dbgt", "dble", |
| "sex", |
| "exg", |
| "lsl", "lsr", |
| "asl", "asr", |
| "rol", "ror", |
| "bfins", "bfext", |
| |
| "trap", |
| |
| "ld", |
| "st", |
| "cmp", |
| |
| "stop", |
| "wai", |
| "sys", |
| |
| "minu", |
| "mins", |
| "maxu", |
| "maxs", |
| |
| "abs", |
| "adc", |
| "bit", |
| "sbc", |
| "rti", |
| "clb", |
| "eor", |
| |
| "sat", |
| |
| "nop", |
| "bgnd", |
| "brclr", |
| "brset", |
| "rts", |
| "lea", |
| "mov", |
| |
| "bra", |
| "bsr", |
| "bhi", |
| "bls", |
| "bcc", |
| "bcs", |
| "bne", |
| "beq", |
| "bvc", |
| "bvs", |
| "bpl", |
| "bmi", |
| "bge", |
| "blt", |
| "bgt", |
| "ble", |
| "inc", |
| "clr", |
| "dec", |
| |
| "add", |
| "sub", |
| "and", |
| "or", |
| |
| "tfr", |
| "jmp", |
| "jsr", |
| "com", |
| "andcc", |
| "neg", |
| "orcc", |
| "bclr", |
| "bset", |
| "btgl", |
| "swi", |
| |
| "mulu", |
| "divu", |
| "modu", |
| "macu", |
| "qmulu", |
| |
| "muls", |
| "divs", |
| "mods", |
| "macs", |
| "qmuls", |
| |
| NULL |
| }; |
| |
| |
| static void |
| operand_separator (struct disassemble_info *info) |
| { |
| if ((info->flags & 0x2)) |
| (*info->fprintf_func) (info->stream, ","); |
| |
| (*info->fprintf_func) (info->stream, " "); |
| |
| info->flags |= 0x2; |
| } |
| |
| /* Render the symbol name whose value is ADDR + BASE or the adddress itself if |
| there is no symbol. If BASE is non zero, then the a PC relative adddress is |
| assumend (ie BASE is the value in the PC. */ |
| static void |
| decode_possible_symbol (bfd_vma addr, bfd_vma base, |
| struct disassemble_info *info, bool relative) |
| { |
| const char *fmt = relative ? "*%+" BFD_VMA_FMT "d" : "%" BFD_VMA_FMT "d"; |
| asymbol *sym = info->symbol_at_address_func (addr + base, info); |
| |
| if (!sym) |
| (*info->fprintf_func) (info->stream, fmt, addr); |
| else |
| (*info->fprintf_func) (info->stream, "%s", bfd_asymbol_name (sym)); |
| } |
| |
| |
| /* Emit the disassembled text for OPR */ |
| static void |
| opr_emit_disassembly (const struct operand *opr, |
| struct disassemble_info *info) |
| { |
| operand_separator (info); |
| |
| switch (opr->cl) |
| { |
| case OPND_CL_IMMEDIATE: |
| (*info->fprintf_func) (info->stream, "#%d", |
| ((struct immediate_operand *) opr)->value); |
| break; |
| case OPND_CL_REGISTER: |
| { |
| int r = ((struct register_operand*) opr)->reg; |
| |
| if (r < 0 || r >= S12Z_N_REGISTERS) |
| (*info->fprintf_func) (info->stream, _("<illegal reg num>")); |
| else |
| (*info->fprintf_func) (info->stream, "%s", registers[r].name); |
| } |
| break; |
| case OPND_CL_REGISTER_ALL16: |
| (*info->fprintf_func) (info->stream, "%s", "ALL16b"); |
| break; |
| case OPND_CL_REGISTER_ALL: |
| (*info->fprintf_func) (info->stream, "%s", "ALL"); |
| break; |
| case OPND_CL_BIT_FIELD: |
| (*info->fprintf_func) (info->stream, "#%d:%d", |
| ((struct bitfield_operand*)opr)->width, |
| ((struct bitfield_operand*)opr)->offset); |
| break; |
| case OPND_CL_SIMPLE_MEMORY: |
| { |
| struct simple_memory_operand *mo = |
| (struct simple_memory_operand *) opr; |
| decode_possible_symbol (mo->addr, mo->base, info, mo->relative); |
| } |
| break; |
| case OPND_CL_MEMORY: |
| { |
| int used_reg = 0; |
| struct memory_operand *mo = (struct memory_operand *) opr; |
| (*info->fprintf_func) (info->stream, "%c", mo->indirect ? '[' : '('); |
| |
| const char *fmt; |
| assert (mo->mutation == OPND_RM_NONE || mo->n_regs == 1); |
| switch (mo->mutation) |
| { |
| case OPND_RM_PRE_DEC: |
| fmt = "-%s"; |
| break; |
| case OPND_RM_PRE_INC: |
| fmt = "+%s"; |
| break; |
| case OPND_RM_POST_DEC: |
| fmt = "%s-"; |
| break; |
| case OPND_RM_POST_INC: |
| fmt = "%s+"; |
| break; |
| case OPND_RM_NONE: |
| default: |
| if (mo->n_regs < 2) |
| (*info->fprintf_func) (info->stream, (mo->n_regs == 0) ? "%d" : "%d,", mo->base_offset); |
| fmt = "%s"; |
| break; |
| } |
| if (mo->n_regs > 0) |
| { |
| int r = mo->regs[0]; |
| |
| if (r < 0 || r >= S12Z_N_REGISTERS) |
| (*info->fprintf_func) (info->stream, fmt, _("<illegal reg num>")); |
| else |
| (*info->fprintf_func) (info->stream, fmt, registers[r].name); |
| } |
| used_reg = 1; |
| |
| if (mo->n_regs > used_reg) |
| { |
| int r = mo->regs[used_reg]; |
| |
| if (r < 0 || r >= S12Z_N_REGISTERS) |
| (*info->fprintf_func) (info->stream, _("<illegal reg num>")); |
| else |
| (*info->fprintf_func) (info->stream, ",%s", |
| registers[r].name); |
| } |
| |
| (*info->fprintf_func) (info->stream, "%c", |
| mo->indirect ? ']' : ')'); |
| } |
| break; |
| }; |
| } |
| |
| #define S12Z_N_SIZES 4 |
| static const char shift_size_table[S12Z_N_SIZES] = |
| { |
| 'b', 'w', 'p', 'l' |
| }; |
| |
| int |
| print_insn_s12z (bfd_vma memaddr, struct disassemble_info* info) |
| { |
| int o; |
| enum optr operator = OP_INVALID; |
| int n_operands = 0; |
| |
| /* The longest instruction in S12Z can have 6 operands. |
| (Most have 3 or less. Only PSH and PUL have so many. */ |
| struct operand *operands[6]; |
| |
| struct mem_read_abstraction mra; |
| mra.base.read = (void *) abstract_read_memory ; |
| mra.base.advance = advance ; |
| mra.base.posn = posn; |
| mra.memaddr = memaddr; |
| mra.info = info; |
| |
| short osize = -1; |
| int n_bytes = |
| decode_s12z (&operator, &osize, &n_operands, operands, |
| (struct mem_read_abstraction_base *) &mra); |
| |
| (info->fprintf_func) (info->stream, "%s", mnemonics[(long)operator]); |
| |
| /* Ship out size sufficies for those instructions which |
| need them. */ |
| if (osize == -1) |
| { |
| bool suffix = false; |
| |
| for (o = 0; o < n_operands; ++o) |
| { |
| if (operands[o] && operands[o]->osize != -1) |
| { |
| if (!suffix) |
| { |
| (*mra.info->fprintf_func) (mra.info->stream, "%c", '.'); |
| suffix = true; |
| } |
| |
| osize = operands[o]->osize; |
| |
| if (osize < 0 || osize >= S12Z_N_SIZES) |
| (*mra.info->fprintf_func) (mra.info->stream, _("<bad>")); |
| else |
| (*mra.info->fprintf_func) (mra.info->stream, "%c", |
| shift_size_table[osize]); |
| } |
| } |
| } |
| else |
| { |
| if (osize < 0 || osize >= S12Z_N_SIZES) |
| (*mra.info->fprintf_func) (mra.info->stream, _(".<bad>")); |
| else |
| (*mra.info->fprintf_func) (mra.info->stream, ".%c", |
| shift_size_table[osize]); |
| } |
| |
| /* Ship out the operands. */ |
| for (o = 0; o < n_operands; ++o) |
| { |
| if (operands[o]) |
| opr_emit_disassembly (operands[o], mra.info); |
| free (operands[o]); |
| } |
| |
| return n_bytes; |
| } |