| /* Disassemble AVR instructions. |
| Copyright (C) 1999-2024 Free Software Foundation, Inc. |
| |
| Contributed by Denis Chertykov <denisc@overta.ru> |
| |
| 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 "sysdep.h" |
| #include <assert.h> |
| #include "disassemble.h" |
| #include "opintl.h" |
| #include "libiberty.h" |
| #include <stdint.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 * opcode_str, |
| char * buf, |
| char * comment, |
| enum disassembler_style * style, |
| int regs, |
| int * sym, |
| bfd_vma * sym_addr, |
| disassemble_info * info) |
| { |
| 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); |
| *style = dis_style_register; |
| break; |
| |
| case 'd': |
| if (regs) |
| sprintf (buf, "r%d", 16 + (insn & 0xf)); |
| else |
| sprintf (buf, "r%d", 16 + ((insn & 0xf0) >> 4)); |
| *style = dis_style_register; |
| break; |
| |
| case 'w': |
| sprintf (buf, "r%d", 24 + ((insn & 0x30) >> 3)); |
| *style = dis_style_register; |
| break; |
| |
| case 'a': |
| if (regs) |
| sprintf (buf, "r%d", 16 + (insn & 7)); |
| else |
| sprintf (buf, "r%d", 16 + ((insn >> 4) & 7)); |
| *style = dis_style_register; |
| break; |
| |
| case 'v': |
| if (regs) |
| sprintf (buf, "r%d", (insn & 0xf) * 2); |
| else |
| sprintf (buf, "r%d", ((insn & 0xf0) >> 3)); |
| *style = dis_style_register; |
| 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")); |
| } |
| *style = dis_style_register; |
| break; |
| |
| case 'z': |
| *buf++ = 'Z'; |
| |
| /* Check for post-increment. */ |
| char *s; |
| for (s = opcode_str; *s; ++s) |
| { |
| if (*s == '+') |
| { |
| if (insn & (1 << (15 - (s - opcode_str)))) |
| *buf++ = '+'; |
| break; |
| } |
| } |
| |
| *buf = '\0'; |
| if (AVR_UNDEF_P (insn)) |
| sprintf (comment, _("undefined")); |
| *style = dis_style_register; |
| 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); |
| *style = dis_style_register; |
| } |
| 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); |
| info->insn_info_valid = 1; |
| info->insn_type = dis_jsr; |
| info->target = *sym_addr; |
| *style = dis_style_address; |
| 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); |
| info->insn_info_valid = 1; |
| info->insn_type = dis_branch; |
| info->target = *sym_addr; |
| *style = dis_style_address_offset; |
| } |
| 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); |
| info->insn_info_valid = 1; |
| info->insn_type = dis_condbranch; |
| info->target = *sym_addr; |
| *style = dis_style_address_offset; |
| } |
| break; |
| |
| case 'i': |
| { |
| unsigned int val = insn2 | 0x800000; |
| *sym = 1; |
| *sym_addr = val; |
| sprintf (buf, "0x%04X", insn2); |
| strcpy (comment, comment_start); |
| *style = dis_style_immediate; |
| } |
| break; |
| |
| case 'j': |
| { |
| unsigned int val = ((insn & 0xf) | ((insn & 0x600) >> 5) |
| | ((insn & 0x100) >> 2)); |
| if ((insn & 0x100) == 0) |
| val |= 0x80; |
| *sym = 1; |
| *sym_addr = val | 0x800000; |
| sprintf (buf, "0x%02x", val); |
| strcpy (comment, comment_start); |
| *style = dis_style_immediate; |
| } |
| break; |
| |
| case 'M': |
| sprintf (buf, "0x%02X", ((insn & 0xf00) >> 4) | (insn & 0xf)); |
| sprintf (comment, "%d", ((insn & 0xf00) >> 4) | (insn & 0xf)); |
| *style = dis_style_immediate; |
| break; |
| |
| case 'n': |
| sprintf (buf, "??"); |
| /* xgettext:c-format */ |
| opcodes_error_handler (_("internal disassembler error")); |
| ok = 0; |
| *style = dis_style_immediate; |
| break; |
| |
| case 'K': |
| { |
| unsigned int x; |
| |
| x = (insn & 0xf) | ((insn >> 2) & 0x30); |
| sprintf (buf, "0x%02x", x); |
| sprintf (comment, "%d", x); |
| *style = dis_style_immediate; |
| } |
| break; |
| |
| case 's': |
| sprintf (buf, "%d", insn & 7); |
| *style = dis_style_immediate; |
| break; |
| |
| case 'S': |
| sprintf (buf, "%d", (insn >> 4) & 7); |
| *style = dis_style_immediate; |
| break; |
| |
| case 'P': |
| { |
| unsigned int x; |
| |
| x = (insn & 0xf); |
| x |= (insn >> 5) & 0x30; |
| sprintf (buf, "0x%02x", x); |
| sprintf (comment, "%d", x); |
| *style = dis_style_address; |
| } |
| break; |
| |
| case 'p': |
| { |
| unsigned int x; |
| |
| x = (insn >> 3) & 0x1f; |
| sprintf (buf, "0x%02x", x); |
| sprintf (comment, "%d", x); |
| *style = dis_style_address; |
| } |
| break; |
| |
| case 'E': |
| sprintf (buf, "%d", (insn >> 4) & 15); |
| *style = dis_style_immediate; |
| break; |
| |
| case '?': |
| *buf = '\0'; |
| break; |
| |
| default: |
| sprintf (buf, "??"); |
| /* xgettext:c-format */ |
| opcodes_error_handler (_("unknown constraint `%c'"), constraint); |
| ok = 0; |
| } |
| |
| return ok; |
| } |
| |
| /* Read the opcode from ADDR. Return 0 in success and save opcode |
| in *INSN, otherwise, return -1. */ |
| |
| static int |
| avrdis_opcode (bfd_vma addr, disassemble_info *info, uint16_t *insn) |
| { |
| bfd_byte buffer[2]; |
| int status; |
| |
| status = info->read_memory_func (addr, buffer, 2, info); |
| |
| if (status == 0) |
| { |
| *insn = bfd_getl16 (buffer); |
| return 0; |
| } |
| |
| info->memory_error_func (status, addr, info); |
| return -1; |
| } |
| |
| |
| int |
| print_insn_avr (bfd_vma addr, disassemble_info *info) |
| { |
| uint16_t insn, insn2; |
| const struct avr_opcodes_s *opcode; |
| static unsigned int *maskptr; |
| void *stream = info->stream; |
| fprintf_styled_ftype prin = info->fprintf_styled_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]; |
| enum disassembler_style style_op1, style_op2; |
| int sym_op1 = 0, sym_op2 = 0; |
| bfd_vma sym_addr1, sym_addr2; |
| |
| /* Clear instruction information field. */ |
| info->insn_info_valid = 0; |
| info->branch_delay_insns = 0; |
| info->data_size = 0; |
| info->insn_type = dis_noninsn; |
| info->target = 0; |
| info->target2 = 0; |
| |
| 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; |
| } |
| |
| if (avrdis_opcode (addr, info, &insn) != 0) |
| return -1; |
| |
| for (opcode = avr_opcodes, maskptr = avr_bin_masks; |
| opcode->name; |
| opcode++, maskptr++) |
| { |
| if ((opcode->isa == AVR_ISA_TINY) && (info->mach != bfd_mach_avrtiny)) |
| continue; |
| 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; |
| style_op1 = dis_style_text; |
| style_op2 = dis_style_text; |
| |
| if (opcode->name) |
| { |
| char *constraints = opcode->constraints; |
| char *opcode_str = opcode->opcode; |
| |
| insn2 = 0; |
| ok = 1; |
| |
| if (opcode->insn_size > 1) |
| { |
| if (avrdis_opcode (addr + 2, info, &insn2) != 0) |
| return -1; |
| cmd_len = 4; |
| } |
| |
| if (*constraints && *constraints != '?') |
| { |
| int regs = REGISTER_P (*constraints); |
| |
| ok = avr_operand (insn, insn2, addr, *constraints, opcode_str, op1, |
| comment1, &style_op1, 0, &sym_op1, &sym_addr1, |
| info); |
| |
| if (ok && *(++constraints) == ',') |
| ok = avr_operand (insn, insn2, addr, *(++constraints), opcode_str, |
| op2, *comment1 ? comment2 : comment1, |
| &style_op2, regs, &sym_op2, &sym_addr2, |
| info); |
| } |
| } |
| |
| if (!ok) |
| { |
| /* Unknown opcode, or invalid combination of operands. */ |
| sprintf (op1, "0x%04x", insn); |
| op2[0] = 0; |
| sprintf (comment1, "????"); |
| comment2[0] = 0; |
| } |
| |
| (*prin) (stream, ok ? dis_style_mnemonic : dis_style_assembler_directive, |
| "%s", ok ? opcode->name : ".word"); |
| |
| if (*op1) |
| (*prin) (stream, style_op1, "\t%s", op1); |
| |
| if (*op2) |
| { |
| (*prin) (stream, dis_style_text, ", "); |
| (*prin) (stream, style_op2, "%s", op2); |
| } |
| |
| if (*comment1) |
| (*prin) (stream, dis_style_comment_start, "\t; %s", comment1); |
| |
| if (sym_op1) |
| info->print_address_func (sym_addr1, info); |
| |
| if (*comment2) |
| (*prin) (stream, dis_style_comment_start, " %s", comment2); |
| |
| if (sym_op2) |
| info->print_address_func (sym_addr2, info); |
| |
| return cmd_len; |
| } |