aboutsummaryrefslogtreecommitdiff
path: root/opcodes/sh64-dis.c
diff options
context:
space:
mode:
Diffstat (limited to 'opcodes/sh64-dis.c')
-rw-r--r--opcodes/sh64-dis.c659
1 files changed, 659 insertions, 0 deletions
diff --git a/opcodes/sh64-dis.c b/opcodes/sh64-dis.c
new file mode 100644
index 0000000..2c76067
--- /dev/null
+++ b/opcodes/sh64-dis.c
@@ -0,0 +1,659 @@
+/* Disassemble SH64 instructions.
+ Copyright (C) 2000, 2001 Free Software Foundation, Inc.
+
+ This program 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 2 of the License, or
+ (at your option) any later version.
+
+ This program 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
+
+#include <stdio.h>
+
+#include "dis-asm.h"
+#include "sysdep.h"
+#include "sh64-opc.h"
+#include "libiberty.h"
+
+/* We need to refer to the ELF header structure. */
+#include "elf-bfd.h"
+#include "elf/sh.h"
+
+#define ELF_MODE32_CODE_LABEL_P(SYM) \
+ (((elf_symbol_type *) (SYM))->internal_elf_sym.st_other & STO_SH5_ISA32)
+
+#define SAVED_MOVI_R(INFO) \
+ (((struct sh64_disassemble_info *) ((INFO)->private_data))->address_reg)
+
+#define SAVED_MOVI_IMM(INFO) \
+ (((struct sh64_disassemble_info *) ((INFO)->private_data))->built_address)
+
+struct sh64_disassemble_info
+ {
+ /* When we see a MOVI, we save the register and the value, and merge a
+ subsequent SHORI and display the address, if there is one. */
+ unsigned int address_reg;
+ bfd_signed_vma built_address;
+
+ /* This is the range decriptor for the current address. It is kept
+ around for the next call. */
+ sh64_elf_crange crange;
+ };
+
+/* Each item in the table is a mask to indicate which bits to be set
+ to determine an instruction's operator.
+ The index is as same as the instruction in the opcode table.
+ Note that some archs have this as a field in the opcode table. */
+static unsigned long *shmedia_opcode_mask_table;
+
+static void initialize_shmedia_opcode_mask_table PARAMS ((void));
+static int print_insn_shmedia PARAMS ((bfd_vma, disassemble_info *));
+static int print_insn_sh64x
+ PARAMS ((bfd_vma, disassemble_info *,
+ int (*) PARAMS ((bfd_vma, struct disassemble_info *)),
+ enum bfd_endian));
+static const char *creg_name PARAMS ((int));
+static boolean init_sh64_disasm_info PARAMS ((struct disassemble_info *));
+static enum sh64_elf_cr_type sh64_get_contents_type_disasm
+ PARAMS ((bfd_vma, struct disassemble_info *));
+
+/* Initialize the SH64 opcode mask table for each instruction in SHmedia
+ mode. */
+
+static void
+initialize_shmedia_opcode_mask_table ()
+{
+ int n_opc;
+ int n;
+
+ /* Calculate number of opcodes. */
+ for (n_opc = 0; shmedia_table[n_opc].name != NULL; n_opc++)
+ ;
+
+ shmedia_opcode_mask_table
+ = xmalloc (sizeof (shmedia_opcode_mask_table[0]) * n_opc);
+
+ for (n = 0; n < n_opc; n++)
+ {
+ int i;
+
+ unsigned long mask = 0;
+
+ for (i = 0; shmedia_table[n].arg[i] != A_NONE; i++)
+ {
+ int offset = shmedia_table[n].nibbles[i];
+ int length;
+
+ switch (shmedia_table[n].arg[i])
+ {
+ case A_GREG_M:
+ case A_GREG_N:
+ case A_GREG_D:
+ case A_CREG_K:
+ case A_CREG_J:
+ case A_FREG_G:
+ case A_FREG_H:
+ case A_FREG_F:
+ case A_DREG_G:
+ case A_DREG_H:
+ case A_DREG_F:
+ case A_FMREG_G:
+ case A_FMREG_H:
+ case A_FMREG_F:
+ case A_FPREG_G:
+ case A_FPREG_H:
+ case A_FPREG_F:
+ case A_FVREG_G:
+ case A_FVREG_H:
+ case A_FVREG_F:
+ case A_REUSE_PREV:
+ length = 6;
+ break;
+
+ case A_TREG_A:
+ case A_TREG_B:
+ length = 3;
+ break;
+
+ case A_IMMM:
+ abort ();
+ break;
+
+ case A_IMMU5:
+ length = 5;
+ break;
+
+ case A_IMMS6:
+ case A_IMMU6:
+ case A_IMMS6BY32:
+ length = 6;
+ break;
+
+ case A_IMMS10:
+ case A_IMMS10BY1:
+ case A_IMMS10BY2:
+ case A_IMMS10BY4:
+ case A_IMMS10BY8:
+ length = 10;
+ break;
+
+ case A_IMMU16:
+ case A_IMMS16:
+ case A_PCIMMS16BY4:
+ case A_PCIMMS16BY4_PT:
+ length = 16;
+ break;
+
+ default:
+ abort ();
+ length = 0;
+ break;
+ }
+
+ if (length != 0)
+ mask |= (0xffffffff >> (32 - length)) << offset;
+ }
+ shmedia_opcode_mask_table[n] = 0xffffffff & ~mask;
+ }
+}
+
+/* Get a predefined control-register-name, or return NULL. */
+
+const char *
+creg_name (cregno)
+ int cregno;
+{
+ const shmedia_creg_info *cregp;
+
+ /* If control register usage is common enough, change this to search a
+ hash-table. */
+ for (cregp = shmedia_creg_table; cregp->name != NULL; cregp++)
+ {
+ if (cregp->cregno == cregno)
+ return cregp->name;
+ }
+
+ return NULL;
+}
+
+/* Main function to disassemble SHmedia instructions. */
+
+static int
+print_insn_shmedia (memaddr, info)
+ bfd_vma memaddr;
+ struct disassemble_info *info;
+{
+ fprintf_ftype fprintf_fn = info->fprintf_func;
+ void *stream = info->stream;
+
+ unsigned char insn[4];
+ unsigned long instruction;
+ int status;
+ int n;
+ const shmedia_opcode_info *op;
+ int i;
+ unsigned int r = 0;
+ long imm = 0;
+ bfd_vma disp_pc_addr;
+
+ status = info->read_memory_func (memaddr, insn, 4, info);
+
+ /* If we can't read four bytes, something is wrong. Display any data we
+ can get as .byte:s. */
+ if (status != 0)
+ {
+ int i;
+
+ for (i = 0; i < 3; i++)
+ {
+ status = info->read_memory_func (memaddr + i, insn, 1, info);
+ if (status != 0)
+ break;
+ (*fprintf_fn) (stream, "%s0x%02x",
+ i == 0 ? ".byte " : ", ",
+ insn[0]);
+ }
+
+ return i ? i : -1;
+ }
+
+ /* Rearrange the bytes to make up an instruction. */
+ if (info->endian == BFD_ENDIAN_LITTLE)
+ instruction = bfd_getl32 (insn);
+ else
+ instruction = bfd_getb32 (insn);
+
+ /* FIXME: Searching could be implemented using a hash on relevant
+ fields. */
+ for (n = 0, op = shmedia_table;
+ op->name != NULL
+ && ((instruction & shmedia_opcode_mask_table[n]) != op->opcode_base);
+ n++, op++)
+ ;
+
+ /* FIXME: We should also check register number constraints. */
+ if (op->name == NULL)
+ {
+ fprintf_fn (stream, ".long 0x%08x", instruction);
+ return 4;
+ }
+
+ fprintf_fn (stream, "%s\t", op->name);
+
+ for (i = 0; i < 3 && op->arg[i] != A_NONE; i++)
+ {
+ unsigned long temp = instruction >> op->nibbles[i];
+ int by_number = 0;
+
+ if (i > 0 && op->arg[i] != A_REUSE_PREV)
+ fprintf_fn (stream, ",");
+
+ switch (op->arg[i])
+ {
+ case A_REUSE_PREV:
+ continue;
+
+ case A_GREG_M:
+ case A_GREG_N:
+ case A_GREG_D:
+ r = temp & 0x3f;
+ fprintf_fn (stream, "r%d", r);
+ break;
+
+ case A_FVREG_F:
+ case A_FVREG_G:
+ case A_FVREG_H:
+ r = temp & 0x3f;
+ fprintf_fn (stream, "fv%d", r);
+ break;
+
+ case A_FPREG_F:
+ case A_FPREG_G:
+ case A_FPREG_H:
+ r = temp & 0x3f;
+ fprintf_fn (stream, "fp%d", r);
+ break;
+
+ case A_FMREG_F:
+ case A_FMREG_G:
+ case A_FMREG_H:
+ r = temp & 0x3f;
+ fprintf_fn (stream, "mtrx%d", r);
+ break;
+
+ case A_CREG_K:
+ case A_CREG_J:
+ {
+ const char *name;
+ r = temp & 0x3f;
+
+ name = creg_name (r);
+
+ if (name != NULL)
+ fprintf_fn (stream, "%s", name);
+ else
+ fprintf_fn (stream, "cr%d", r);
+ }
+ break;
+
+ case A_FREG_G:
+ case A_FREG_H:
+ case A_FREG_F:
+ r = temp & 0x3f;
+ fprintf_fn (stream, "fr%d", r);
+ break;
+
+ case A_DREG_G:
+ case A_DREG_H:
+ case A_DREG_F:
+ r = temp & 0x3f;
+ fprintf_fn (stream, "dr%d", r);
+ break;
+
+ case A_TREG_A:
+ case A_TREG_B:
+ r = temp & 0x7;
+ fprintf_fn (stream, "tr%d", r);
+ break;
+
+ /* A signed 6-bit number. */
+ case A_IMMS6:
+ imm = temp & 0x3f;
+ if (imm & (unsigned long) 0x20)
+ imm |= ~(unsigned long) 0x3f;
+ fprintf_fn (stream, "%d", imm);
+ break;
+
+ /* A signed 6-bit number, multiplied by 32 when used. */
+ case A_IMMS6BY32:
+ imm = temp & 0x3f;
+ if (imm & (unsigned long) 0x20)
+ imm |= ~(unsigned long) 0x3f;
+ fprintf_fn (stream, "%d", imm * 32);
+ break;
+
+ /* A signed 10-bit number, multiplied by 8 when used. */
+ case A_IMMS10BY8:
+ by_number++;
+ /* Fall through. */
+
+ /* A signed 10-bit number, multiplied by 4 when used. */
+ case A_IMMS10BY4:
+ by_number++;
+ /* Fall through. */
+
+ /* A signed 10-bit number, multiplied by 2 when used. */
+ case A_IMMS10BY2:
+ by_number++;
+ /* Fall through. */
+
+ /* A signed 10-bit number. */
+ case A_IMMS10:
+ case A_IMMS10BY1:
+ imm = temp & 0x3ff;
+ if (imm & (unsigned long) 0x200)
+ imm |= ~(unsigned long) 0x3ff;
+ imm <<= by_number;
+ fprintf_fn (stream, "%d", imm);
+ break;
+
+ /* A signed 16-bit number. */
+ case A_IMMS16:
+ imm = temp & 0xffff;
+ if (imm & (unsigned long) 0x8000)
+ imm |= ~((unsigned long) 0xffff);
+ fprintf_fn (stream, "%d", imm);
+ break;
+
+ /* A PC-relative signed 16-bit number, multiplied by 4 when
+ used. */
+ case A_PCIMMS16BY4:
+ imm = temp & 0xffff; /* 16 bits */
+ if (imm & (unsigned long) 0x8000)
+ imm |= ~(unsigned long) 0xffff;
+ imm <<= 2;
+ disp_pc_addr = (bfd_vma) imm + memaddr;
+ (*info->print_address_func) (disp_pc_addr, info);
+ break;
+
+ /* An unsigned 5-bit number. */
+ case A_IMMU5:
+ imm = temp & 0x1f;
+ fprintf_fn (stream, "%d", imm);
+ break;
+
+ /* An unsigned 6-bit number. */
+ case A_IMMU6:
+ imm = temp & 0x3f;
+ fprintf_fn (stream, "%d", imm);
+ break;
+
+ /* An unsigned 16-bit number. */
+ case A_IMMU16:
+ imm = temp & 0xffff;
+ fprintf_fn (stream, "%d", imm);
+ break;
+
+ default:
+ abort ();
+ break;
+ }
+ }
+
+ /* FIXME: Looks like 32-bit values only are handled.
+ FIXME: PC-relative numbers aren't handled correctly. */
+ if (op->opcode_base == (unsigned long) SHMEDIA_SHORI_OPC
+ && SAVED_MOVI_R (info) == r)
+ {
+ asection *section = info->section;
+
+ /* Most callers do not set the section field correctly yet. Revert
+ to getting the section from symbols, if any. */
+ if (section == NULL
+ && info->symbols != NULL
+ && bfd_asymbol_flavour (info->symbols[0]) == bfd_target_elf_flavour
+ && ! bfd_is_und_section (bfd_get_section (info->symbols[0]))
+ && ! bfd_is_abs_section (bfd_get_section (info->symbols[0])))
+ section = bfd_get_section (info->symbols[0]);
+
+ /* Only guess addresses when the contents of this section is fully
+ relocated. Otherwise, the value will be zero or perhaps even
+ bogus. */
+ if (section == NULL
+ || section->owner == NULL
+ || elf_elfheader (section->owner)->e_type == ET_EXEC)
+ {
+ bfd_signed_vma shori_addr;
+
+ shori_addr = SAVED_MOVI_IMM (info) << 16;
+ shori_addr |= imm;
+
+ fprintf_fn (stream, "\t! 0x");
+ (*info->print_address_func) (shori_addr, info);
+ }
+ }
+
+ if (op->opcode_base == SHMEDIA_MOVI_OPC)
+ {
+ SAVED_MOVI_IMM (info) = imm;
+ SAVED_MOVI_R (info) = r;
+ }
+ else
+ {
+ SAVED_MOVI_IMM (info) = 0;
+ SAVED_MOVI_R (info) = 255;
+ }
+
+ return 4;
+}
+
+/* Check the type of contents about to be disassembled. This is like
+ sh64_get_contents_type (which may be called from here), except that it
+ takes the same arguments as print_insn_* and does what can be done if
+ no section is available. */
+
+static enum sh64_elf_cr_type
+sh64_get_contents_type_disasm (memaddr, info)
+ bfd_vma memaddr;
+ struct disassemble_info *info;
+{
+ struct sh64_disassemble_info *sh64_infop = info->private_data;
+
+ /* Perhaps we have a region from a previous probe and it still counts
+ for this address? */
+ if (sh64_infop->crange.cr_type != CRT_NONE
+ && memaddr >= sh64_infop->crange.cr_addr
+ && memaddr < sh64_infop->crange.cr_addr + sh64_infop->crange.cr_size)
+ return sh64_infop->crange.cr_type;
+
+ /* If we have a section, try and use it. */
+ if (info->section
+ && bfd_get_flavour (info->section->owner) == bfd_target_elf_flavour)
+ {
+ enum sh64_elf_cr_type cr_type
+ = sh64_get_contents_type (info->section, memaddr,
+ &sh64_infop->crange);
+
+ if (cr_type != CRT_NONE)
+ return cr_type;
+ }
+
+ /* If we have symbols, we can try and get at a section from *that*. */
+ if (info->symbols != NULL
+ && bfd_asymbol_flavour (info->symbols[0]) == bfd_target_elf_flavour
+ && ! bfd_is_und_section (bfd_get_section (info->symbols[0]))
+ && ! bfd_is_abs_section (bfd_get_section (info->symbols[0])))
+ {
+ enum sh64_elf_cr_type cr_type
+ = sh64_get_contents_type (bfd_get_section (info->symbols[0]),
+ memaddr, &sh64_infop->crange);
+
+ if (cr_type != CRT_NONE)
+ return cr_type;
+ }
+
+ /* We can make a reasonable guess based on the st_other field of a
+ symbol; for a BranchTarget this is marked as STO_SH5_ISA32 and then
+ it's most probably code there. */
+ if (info->symbols
+ && bfd_asymbol_flavour (info->symbols[0]) == bfd_target_elf_flavour
+ && elf_symbol_from (bfd_asymbol_bfd (info->symbols[0]),
+ info->symbols[0])->internal_elf_sym.st_other
+ == STO_SH5_ISA32)
+ return CRT_SH5_ISA32;
+
+ /* If all else fails, guess this is code and guess on the low bit set. */
+ return (memaddr & 1) == 1 ? CRT_SH5_ISA32 : CRT_SH5_ISA16;
+}
+
+/* Initialize static and dynamic disassembly state. */
+
+static boolean
+init_sh64_disasm_info (info)
+ struct disassemble_info *info;
+{
+ struct sh64_disassemble_info *sh64_infop
+ = calloc (sizeof (*sh64_infop), 1);
+
+ if (sh64_infop == NULL)
+ return false;
+
+ info->private_data = sh64_infop;
+
+ SAVED_MOVI_IMM (info) = 0;
+ SAVED_MOVI_R (info) = 255;
+
+ if (shmedia_opcode_mask_table == NULL)
+ initialize_shmedia_opcode_mask_table ();
+
+ return true;
+}
+
+/* Main entry to disassemble SHmedia instructions, given an endian set in
+ INFO. Note that the simulator uses this as the main entry and does not
+ use any of the functions further below. */
+
+int
+print_insn_sh64x_media (memaddr, info)
+ bfd_vma memaddr;
+ struct disassemble_info *info;
+{
+ if (info->private_data == NULL && ! init_sh64_disasm_info (info))
+ return -1;
+
+ /* Make reasonable output. */
+ info->bytes_per_line = 4;
+ info->bytes_per_chunk = 4;
+
+ return print_insn_shmedia (memaddr, info);
+}
+
+/* Main entry to disassemble SHcompact or SHmedia insns. */
+
+static int
+print_insn_sh64x (memaddr, info, pfun_compact, endian)
+ bfd_vma memaddr;
+ struct disassemble_info *info;
+ int (*pfun_compact) PARAMS ((bfd_vma, struct disassemble_info *));
+ enum bfd_endian endian;
+{
+ enum sh64_elf_cr_type cr_type;
+
+ if (info->private_data == NULL && ! init_sh64_disasm_info (info))
+ return -1;
+
+ cr_type = sh64_get_contents_type_disasm (memaddr, info);
+ if (cr_type != CRT_SH5_ISA16)
+ {
+ int length = 4 - (memaddr % 4);
+ info->display_endian = endian;
+
+ /* Only disassemble on four-byte boundaries. Addresses that are not
+ a multiple of four can happen after a data region. */
+ if (cr_type == CRT_SH5_ISA32 && length == 4)
+ return print_insn_sh64x_media (memaddr, info);
+
+ /* We get CRT_DATA *only* for data regions in a mixed-contents
+ section. For sections with data only, we get indication of one
+ of the ISA:s. You may think that we shouldn't disassemble
+ section with only data if we can figure that out. However, the
+ disassembly function is by default not called for data-only
+ sections, so if the user explicitly specified disassembly of a
+ data section, that's what we should do. */
+ if (cr_type == CRT_DATA || length != 4)
+ {
+ int status;
+ unsigned char data[4];
+ struct sh64_disassemble_info *sh64_infop = info->private_data;
+
+ if (length == 4
+ && sh64_infop->crange.cr_type != CRT_NONE
+ && memaddr >= sh64_infop->crange.cr_addr
+ && memaddr < (sh64_infop->crange.cr_addr
+ + sh64_infop->crange.cr_size))
+ length
+ = (sh64_infop->crange.cr_addr
+ + sh64_infop->crange.cr_size - memaddr);
+
+ status
+ = (*info->read_memory_func) (memaddr, data,
+ length >= 4 ? 4 : length, info);
+
+ if (status == 0 && length >= 4)
+ {
+ (*info->fprintf_func) (info->stream, ".long 0x%08lx",
+ endian == BFD_ENDIAN_BIG
+ ? (long) (bfd_getb32 (data))
+ : (long) (bfd_getl32 (data)));
+ return 4;
+ }
+ else
+ {
+ int i;
+
+ for (i = 0; i < length; i++)
+ {
+ status = info->read_memory_func (memaddr + i, data, 1, info);
+ if (status != 0)
+ break;
+ (*info->fprintf_func) (info->stream, "%s0x%02x",
+ i == 0 ? ".byte " : ", ",
+ data[0]);
+ }
+
+ return i ? i : -1;
+ }
+ }
+ }
+
+ return (*pfun_compact) (memaddr, info);
+}
+
+/* Main entry to disassemble SHcompact or SHmedia insns, big endian. */
+
+int
+print_insn_sh64 (memaddr, info)
+ bfd_vma memaddr;
+ struct disassemble_info *info;
+{
+ return
+ print_insn_sh64x (memaddr, info, print_insn_sh, BFD_ENDIAN_BIG);
+}
+
+/* Main entry to disassemble SHcompact or SHmedia insns, little endian. */
+
+int
+print_insn_sh64l (memaddr, info)
+ bfd_vma memaddr;
+ struct disassemble_info *info;
+{
+ return
+ print_insn_sh64x (memaddr, info, print_insn_shl, BFD_ENDIAN_LITTLE);
+}