/* Disassemble AVR instructions. Copyright 1999, 2000, 2002, 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc. Contributed by Denis Chertykov This file is part of libopcodes. 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 #include "sysdep.h" #include "dis-asm.h" #include "opintl.h" #include "libiberty.h" struct avr_opcodes_s { char *name; char *constraints; char *opcode; int insn_size; /* In words. */ int isa; unsigned int bin_opcode; }; #define AVR_INSN(NAME, CONSTR, OPCODE, SIZE, ISA, BIN) \ {#NAME, CONSTR, OPCODE, SIZE, ISA, BIN}, const struct avr_opcodes_s avr_opcodes[] = { #include "opcode/avr.h" {NULL, NULL, NULL, 0, 0, 0} }; static const char * comment_start = "0x"; static int avr_operand (unsigned int insn, unsigned int insn2, unsigned int pc, int constraint, char *buf, char *comment, int regs, int *sym, bfd_vma *sym_addr) { int ok = 1; *sym = 0; switch (constraint) { /* Any register operand. */ case 'r': if (regs) insn = (insn & 0xf) | ((insn & 0x0200) >> 5); /* Source register. */ else insn = (insn & 0x01f0) >> 4; /* Destination register. */ sprintf (buf, "r%d", insn); break; case 'd': if (regs) sprintf (buf, "r%d", 16 + (insn & 0xf)); else sprintf (buf, "r%d", 16 + ((insn & 0xf0) >> 4)); break; case 'w': sprintf (buf, "r%d", 24 + ((insn & 0x30) >> 3)); break; case 'a': if (regs) sprintf (buf, "r%d", 16 + (insn & 7)); else sprintf (buf, "r%d", 16 + ((insn >> 4) & 7)); break; case 'v': if (regs) sprintf (buf, "r%d", (insn & 0xf) * 2); else sprintf (buf, "r%d", ((insn & 0xf0) >> 3)); break; case 'e': { char *xyz; switch (insn & 0x100f) { case 0x0000: xyz = "Z"; break; case 0x1001: xyz = "Z+"; break; case 0x1002: xyz = "-Z"; break; case 0x0008: xyz = "Y"; break; case 0x1009: xyz = "Y+"; break; case 0x100a: xyz = "-Y"; break; case 0x100c: xyz = "X"; break; case 0x100d: xyz = "X+"; break; case 0x100e: xyz = "-X"; break; default: xyz = "??"; ok = 0; } strcpy (buf, xyz); if (AVR_UNDEF_P (insn)) sprintf (comment, _("undefined")); } break; case 'z': *buf++ = 'Z'; if (insn & 0x1) *buf++ = '+'; *buf = '\0'; if (AVR_UNDEF_P (insn)) sprintf (comment, _("undefined")); break; case 'b': { unsigned int x; x = (insn & 7); x |= (insn >> 7) & (3 << 3); x |= (insn >> 8) & (1 << 5); if (insn & 0x8) *buf++ = 'Y'; else *buf++ = 'Z'; sprintf (buf, "+%d", x); sprintf (comment, "0x%02x", x); } break; case 'h': *sym = 1; *sym_addr = ((((insn & 1) | ((insn & 0x1f0) >> 3)) << 16) | insn2) * 2; /* See PR binutils/2454. Ideally we would like to display the hex value of the address only once, but this would mean recoding objdump_print_address() which would affect many targets. */ sprintf (buf, "%#lx", (unsigned long) *sym_addr); strcpy (comment, comment_start); break; case 'L': { int rel_addr = (((insn & 0xfff) ^ 0x800) - 0x800) * 2; sprintf (buf, ".%+-8d", rel_addr); *sym = 1; *sym_addr = pc + 2 + rel_addr; strcpy (comment, comment_start); } break; case 'l': { int rel_addr = ((((insn >> 3) & 0x7f) ^ 0x40) - 0x40) * 2; sprintf (buf, ".%+-8d", rel_addr); *sym = 1; *sym_addr = pc + 2 + rel_addr; strcpy (comment, comment_start); } break; case 'i': sprintf (buf, "0x%04X", insn2); break; case 'M': sprintf (buf, "0x%02X", ((insn & 0xf00) >> 4) | (insn & 0xf)); sprintf (comment, "%d", ((insn & 0xf00) >> 4) | (insn & 0xf)); break; case 'n': sprintf (buf, "??"); fprintf (stderr, _("Internal disassembler error")); ok = 0; break; case 'K': { unsigned int x; x = (insn & 0xf) | ((insn >> 2) & 0x30); sprintf (buf, "0x%02x", x); sprintf (comment, "%d", x); } break; case 's': sprintf (buf, "%d", insn & 7); break; case 'S': sprintf (buf, "%d", (insn >> 4) & 7); break; case 'P': { unsigned int x; x = (insn & 0xf); x |= (insn >> 5) & 0x30; sprintf (buf, "0x%02x", x); sprintf (comment, "%d", x); } break; case 'p': { unsigned int x; x = (insn >> 3) & 0x1f; sprintf (buf, "0x%02x", x); sprintf (comment, "%d", x); } break; case '?': *buf = '\0'; break; default: sprintf (buf, "??"); fprintf (stderr, _("unknown constraint `%c'"), constraint); ok = 0; } return ok; } static unsigned short avrdis_opcode (bfd_vma addr, disassemble_info *info) { bfd_byte buffer[2]; int status; status = info->read_memory_func (addr, buffer, 2, info); if (status == 0) return bfd_getl16 (buffer); info->memory_error_func (status, addr, info); return -1; } int print_insn_avr (bfd_vma addr, disassemble_info *info) { unsigned int insn, insn2; const struct avr_opcodes_s *opcode; static unsigned int *maskptr; void *stream = info->stream; fprintf_ftype prin = info->fprintf_func; static unsigned int *avr_bin_masks; static int initialized; int cmd_len = 2; int ok = 0; char op1[20], op2[20], comment1[40], comment2[40]; int sym_op1 = 0, sym_op2 = 0; bfd_vma sym_addr1, sym_addr2; if (!initialized) { unsigned int nopcodes; /* PR 4045: Try to avoid duplicating the 0x prefix that objdump_print_addr() will put on addresses when there is no symbol table available. */ if (info->symtab_size == 0) comment_start = " "; nopcodes = sizeof (avr_opcodes) / sizeof (struct avr_opcodes_s); avr_bin_masks = xmalloc (nopcodes * sizeof (unsigned int)); for (opcode = avr_opcodes, maskptr = avr_bin_masks; opcode->name; opcode++, maskptr++) { char * s; unsigned int bin = 0; unsigned int mask = 0; for (s = opcode->opcode; *s; ++s) { bin <<= 1; mask <<= 1; bin |= (*s == '1'); mask |= (*s == '1' || *s == '0'); } assert (s - opcode->opcode == 16); assert (opcode->bin_opcode == bin); *maskptr = mask; } initialized = 1; } insn = avrdis_opcode (addr, info); for (opcode = avr_opcodes, maskptr = avr_bin_masks; opcode->name; opcode++, maskptr++) if ((insn & *maskptr) == opcode->bin_opcode) break; /* Special case: disassemble `ldd r,b+0' as `ld r,b', and `std b+0,r' as `st b,r' (next entry in the table). */ if (AVR_DISP0_P (insn)) opcode++; op1[0] = 0; op2[0] = 0; comment1[0] = 0; comment2[0] = 0; if (opcode->name) { char *op = opcode->constraints; insn2 = 0; ok = 1; if (opcode->insn_size > 1) { insn2 = avrdis_opcode (addr + 2, info); cmd_len = 4; } if (*op && *op != '?') { int regs = REGISTER_P (*op); ok = avr_operand (insn, insn2, addr, *op, op1, comment1, 0, &sym_op1, &sym_addr1); if (ok && *(++op) == ',') ok = avr_operand (insn, insn2, addr, *(++op), op2, *comment1 ? comment2 : comment1, regs, &sym_op2, &sym_addr2); } } if (!ok) { /* Unknown opcode, or invalid combination of operands. */ sprintf (op1, "0x%04x", insn); op2[0] = 0; sprintf (comment1, "????"); comment2[0] = 0; } (*prin) (stream, "%s", ok ? opcode->name : ".word"); if (*op1) (*prin) (stream, "\t%s", op1); if (*op2) (*prin) (stream, ", %s", op2); if (*comment1) (*prin) (stream, "\t; %s", comment1); if (sym_op1) info->print_address_func (sym_addr1, info); if (*comment2) (*prin) (stream, " %s", comment2); if (sym_op2) info->print_address_func (sym_addr2, info); return cmd_len; }