448b8ca865
Check for null before dereferencing an operand pointer. Normally this situation should never arise, but could happen if a "partial" instruction is encountered at the end of a file or section. opcodes/ * s12z-dis.c (print_insn_s12z): Do not dereference an operand if it is null.
391 lines
8.1 KiB
C
391 lines
8.1 KiB
C
/* 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"
|
|
|
|
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;
|
|
(*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)
|
|
(*info->fprintf_func) (info->stream, fmt,
|
|
registers[mo->regs[0]].name);
|
|
used_reg = 1;
|
|
|
|
if (mo->n_regs > used_reg)
|
|
{
|
|
(*info->fprintf_func) (info->stream, ",%s",
|
|
registers[mo->regs[used_reg]].name);
|
|
}
|
|
|
|
(*info->fprintf_func) (info->stream, "%c",
|
|
mo->indirect ? ']' : ')');
|
|
}
|
|
break;
|
|
};
|
|
}
|
|
|
|
static const char shift_size_table[] = {
|
|
'b', 'w', 'p', 'l'
|
|
};
|
|
|
|
int
|
|
print_insn_s12z (bfd_vma memaddr, struct disassemble_info* info)
|
|
{
|
|
int o;
|
|
enum operator 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;
|
|
}
|
|
(*mra.info->fprintf_func) (mra.info->stream, "%c",
|
|
shift_size_table[operands[o]->osize]);
|
|
}
|
|
}
|
|
}
|
|
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;
|
|
}
|