blob: 5930ab4ef6698b42d2ffd2f5ccd90c8f9270d0e6 [file] [log] [blame]
/* s12z-dis.c -- Freescale S12Z disassembly
Copyright (C) 2018-2019 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 "bfd_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, mra->info);
return -1;
}
return 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";
if (!info->symbol_at_address_func (addr + base, info))
{
(*info->fprintf_func) (info->stream, fmt, addr);
}
else
{
asymbol *sym = NULL;
int j;
for (j = 0; j < info->symtab_size; ++j)
{
sym = info->symtab[j];
if (bfd_asymbol_value (sym) == addr + base)
{
break;
}
}
if (j < info->symtab_size)
(*info->fprintf_func) (info->stream, "%s", bfd_asymbol_name (sym));
else
(*info->fprintf_func) (info->stream, fmt, addr);
}
}
/* 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;
}