/src/binutils-gdb/opcodes/avr-dis.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* Disassemble AVR instructions. |
2 | | Copyright (C) 1999-2025 Free Software Foundation, Inc. |
3 | | |
4 | | Contributed by Denis Chertykov <denisc@overta.ru> |
5 | | |
6 | | This file is part of libopcodes. |
7 | | |
8 | | This library is free software; you can redistribute it and/or modify |
9 | | it under the terms of the GNU General Public License as published by |
10 | | the Free Software Foundation; either version 3, or (at your option) |
11 | | any later version. |
12 | | |
13 | | It is distributed in the hope that it will be useful, but WITHOUT |
14 | | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
15 | | or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public |
16 | | License for more details. |
17 | | |
18 | | You should have received a copy of the GNU General Public License |
19 | | along with this program; if not, write to the Free Software |
20 | | Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, |
21 | | MA 02110-1301, USA. */ |
22 | | |
23 | | #include "sysdep.h" |
24 | | #include <assert.h> |
25 | | #include "disassemble.h" |
26 | | #include "opintl.h" |
27 | | #include "libiberty.h" |
28 | | #include <stdint.h> |
29 | | |
30 | | struct avr_opcodes_s |
31 | | { |
32 | | char *name; |
33 | | char *constraints; |
34 | | char *opcode; |
35 | | int insn_size; /* In words. */ |
36 | | int isa; |
37 | | unsigned int bin_opcode; |
38 | | }; |
39 | | |
40 | | #define AVR_INSN(NAME, CONSTR, OPCODE, SIZE, ISA, BIN) \ |
41 | | {#NAME, CONSTR, OPCODE, SIZE, ISA, BIN}, |
42 | | |
43 | | const struct avr_opcodes_s avr_opcodes[] = |
44 | | { |
45 | | #include "opcode/avr.h" |
46 | | {NULL, NULL, NULL, 0, 0, 0} |
47 | | }; |
48 | | |
49 | | static const char * comment_start = "0x"; |
50 | | |
51 | | static int |
52 | | avr_operand (unsigned int insn, |
53 | | unsigned int insn2, |
54 | | unsigned int pc, |
55 | | int constraint, |
56 | | char * opcode_str, |
57 | | char * buf, |
58 | | char * comment, |
59 | | enum disassembler_style * style, |
60 | | int regs, |
61 | | int * sym, |
62 | | bfd_vma * sym_addr, |
63 | | disassemble_info * info) |
64 | 136k | { |
65 | 136k | int ok = 1; |
66 | 136k | *sym = 0; |
67 | | |
68 | 136k | switch (constraint) |
69 | 136k | { |
70 | | /* Any register operand. */ |
71 | 40.0k | case 'r': |
72 | 40.0k | if (regs) |
73 | 11.1k | insn = (insn & 0xf) | ((insn & 0x0200) >> 5); /* Source register. */ |
74 | 28.8k | else |
75 | 28.8k | insn = (insn & 0x01f0) >> 4; /* Destination register. */ |
76 | | |
77 | 40.0k | sprintf (buf, "r%d", insn); |
78 | 40.0k | *style = dis_style_register; |
79 | 40.0k | break; |
80 | | |
81 | 29.3k | case 'd': |
82 | 29.3k | if (regs) |
83 | 548 | sprintf (buf, "r%d", 16 + (insn & 0xf)); |
84 | 28.8k | else |
85 | 28.8k | sprintf (buf, "r%d", 16 + ((insn & 0xf0) >> 4)); |
86 | 29.3k | *style = dis_style_register; |
87 | 29.3k | break; |
88 | | |
89 | 549 | case 'w': |
90 | 549 | sprintf (buf, "r%d", 24 + ((insn & 0x30) >> 3)); |
91 | 549 | *style = dis_style_register; |
92 | 549 | break; |
93 | | |
94 | 1.95k | case 'a': |
95 | 1.95k | if (regs) |
96 | 977 | sprintf (buf, "r%d", 16 + (insn & 7)); |
97 | 977 | else |
98 | 977 | sprintf (buf, "r%d", 16 + ((insn >> 4) & 7)); |
99 | 1.95k | *style = dis_style_register; |
100 | 1.95k | break; |
101 | | |
102 | 1.81k | case 'v': |
103 | 1.81k | if (regs) |
104 | 909 | sprintf (buf, "r%d", (insn & 0xf) * 2); |
105 | 909 | else |
106 | 909 | sprintf (buf, "r%d", ((insn & 0xf0) >> 3)); |
107 | 1.81k | *style = dis_style_register; |
108 | 1.81k | break; |
109 | | |
110 | 4.13k | case 'e': |
111 | 4.13k | { |
112 | 4.13k | char *xyz; |
113 | | |
114 | 4.13k | switch (insn & 0x100f) |
115 | 4.13k | { |
116 | 512 | case 0x0000: xyz = "Z"; break; |
117 | 894 | case 0x1001: xyz = "Z+"; break; |
118 | 541 | case 0x1002: xyz = "-Z"; break; |
119 | 98 | case 0x0008: xyz = "Y"; break; |
120 | 118 | case 0x1009: xyz = "Y+"; break; |
121 | 133 | case 0x100a: xyz = "-Y"; break; |
122 | 258 | case 0x100c: xyz = "X"; break; |
123 | 191 | case 0x100d: xyz = "X+"; break; |
124 | 336 | case 0x100e: xyz = "-X"; break; |
125 | 1.05k | default: xyz = "??"; ok = 0; |
126 | 4.13k | } |
127 | 4.13k | strcpy (buf, xyz); |
128 | | |
129 | 4.13k | if (AVR_UNDEF_P (insn)) |
130 | 358 | sprintf (comment, _("undefined")); |
131 | 4.13k | } |
132 | 0 | *style = dis_style_register; |
133 | 4.13k | break; |
134 | | |
135 | 962 | case 'z': |
136 | 962 | *buf++ = 'Z'; |
137 | | |
138 | | /* Check for post-increment. */ |
139 | 962 | char *s; |
140 | 15.6k | for (s = opcode_str; *s; ++s) |
141 | 15.3k | { |
142 | 15.3k | if (*s == '+') |
143 | 697 | { |
144 | 697 | if (insn & (1 << (15 - (s - opcode_str)))) |
145 | 184 | *buf++ = '+'; |
146 | 697 | break; |
147 | 697 | } |
148 | 15.3k | } |
149 | | |
150 | 962 | *buf = '\0'; |
151 | 962 | if (AVR_UNDEF_P (insn)) |
152 | 37 | sprintf (comment, _("undefined")); |
153 | 962 | *style = dis_style_register; |
154 | 962 | break; |
155 | | |
156 | 6.78k | case 'b': |
157 | 6.78k | { |
158 | 6.78k | unsigned int x; |
159 | | |
160 | 6.78k | x = (insn & 7); |
161 | 6.78k | x |= (insn >> 7) & (3 << 3); |
162 | 6.78k | x |= (insn >> 8) & (1 << 5); |
163 | | |
164 | 6.78k | if (insn & 0x8) |
165 | 3.07k | *buf++ = 'Y'; |
166 | 3.71k | else |
167 | 3.71k | *buf++ = 'Z'; |
168 | 6.78k | sprintf (buf, "+%d", x); |
169 | 6.78k | sprintf (comment, "0x%02x", x); |
170 | 6.78k | *style = dis_style_register; |
171 | 6.78k | } |
172 | 6.78k | break; |
173 | | |
174 | 231 | case 'h': |
175 | 231 | *sym = 1; |
176 | 231 | *sym_addr = ((((insn & 1) | ((insn & 0x1f0) >> 3)) << 16) | insn2) * 2; |
177 | | /* See PR binutils/2454. Ideally we would like to display the hex |
178 | | value of the address only once, but this would mean recoding |
179 | | objdump_print_address() which would affect many targets. */ |
180 | 231 | sprintf (buf, "%#lx", (unsigned long) *sym_addr); |
181 | 231 | strcpy (comment, comment_start); |
182 | 231 | info->insn_info_valid = 1; |
183 | 231 | info->insn_type = dis_jsr; |
184 | 231 | info->target = *sym_addr; |
185 | 231 | *style = dis_style_address; |
186 | 231 | break; |
187 | | |
188 | 7.85k | case 'L': |
189 | 7.85k | { |
190 | 7.85k | int rel_addr = (((insn & 0xfff) ^ 0x800) - 0x800) * 2; |
191 | 7.85k | sprintf (buf, ".%+-8d", rel_addr); |
192 | 7.85k | *sym = 1; |
193 | 7.85k | *sym_addr = pc + 2 + rel_addr; |
194 | 7.85k | strcpy (comment, comment_start); |
195 | 7.85k | info->insn_info_valid = 1; |
196 | 7.85k | info->insn_type = dis_branch; |
197 | 7.85k | info->target = *sym_addr; |
198 | 7.85k | *style = dis_style_address_offset; |
199 | 7.85k | } |
200 | 7.85k | break; |
201 | | |
202 | 4.10k | case 'l': |
203 | 4.10k | { |
204 | 4.10k | int rel_addr = ((((insn >> 3) & 0x7f) ^ 0x40) - 0x40) * 2; |
205 | | |
206 | 4.10k | sprintf (buf, ".%+-8d", rel_addr); |
207 | 4.10k | *sym = 1; |
208 | 4.10k | *sym_addr = pc + 2 + rel_addr; |
209 | 4.10k | strcpy (comment, comment_start); |
210 | 4.10k | info->insn_info_valid = 1; |
211 | 4.10k | info->insn_type = dis_condbranch; |
212 | 4.10k | info->target = *sym_addr; |
213 | 4.10k | *style = dis_style_address_offset; |
214 | 4.10k | } |
215 | 4.10k | break; |
216 | | |
217 | 1.00k | case 'i': |
218 | 1.00k | { |
219 | 1.00k | unsigned int val = insn2 | 0x800000; |
220 | 1.00k | *sym = 1; |
221 | 1.00k | *sym_addr = val; |
222 | 1.00k | sprintf (buf, "0x%04X", insn2); |
223 | 1.00k | strcpy (comment, comment_start); |
224 | 1.00k | *style = dis_style_immediate; |
225 | 1.00k | } |
226 | 1.00k | break; |
227 | | |
228 | 949 | case 'j': |
229 | 949 | { |
230 | 949 | unsigned int val = ((insn & 0xf) | ((insn & 0x600) >> 5) |
231 | 949 | | ((insn & 0x100) >> 2)); |
232 | 949 | if ((insn & 0x100) == 0) |
233 | 574 | val |= 0x80; |
234 | 949 | *sym = 1; |
235 | 949 | *sym_addr = val | 0x800000; |
236 | 949 | sprintf (buf, "0x%02x", val); |
237 | 949 | strcpy (comment, comment_start); |
238 | 949 | *style = dis_style_immediate; |
239 | 949 | } |
240 | 949 | break; |
241 | | |
242 | 27.3k | case 'M': |
243 | 27.3k | sprintf (buf, "0x%02X", ((insn & 0xf00) >> 4) | (insn & 0xf)); |
244 | 27.3k | sprintf (comment, "%d", ((insn & 0xf00) >> 4) | (insn & 0xf)); |
245 | 27.3k | *style = dis_style_immediate; |
246 | 27.3k | break; |
247 | | |
248 | 0 | case 'n': |
249 | 0 | sprintf (buf, "??"); |
250 | | /* xgettext:c-format */ |
251 | 0 | opcodes_error_handler (_("internal disassembler error")); |
252 | 0 | ok = 0; |
253 | 0 | *style = dis_style_immediate; |
254 | 0 | break; |
255 | | |
256 | 549 | case 'K': |
257 | 549 | { |
258 | 549 | unsigned int x; |
259 | | |
260 | 549 | x = (insn & 0xf) | ((insn >> 2) & 0x30); |
261 | 549 | sprintf (buf, "0x%02x", x); |
262 | 549 | sprintf (comment, "%d", x); |
263 | 549 | *style = dis_style_immediate; |
264 | 549 | } |
265 | 549 | break; |
266 | | |
267 | 4.04k | case 's': |
268 | 4.04k | sprintf (buf, "%d", insn & 7); |
269 | 4.04k | *style = dis_style_immediate; |
270 | 4.04k | break; |
271 | | |
272 | 0 | case 'S': |
273 | 0 | sprintf (buf, "%d", (insn >> 4) & 7); |
274 | 0 | *style = dis_style_immediate; |
275 | 0 | break; |
276 | | |
277 | 2.91k | case 'P': |
278 | 2.91k | { |
279 | 2.91k | unsigned int x; |
280 | | |
281 | 2.91k | x = (insn & 0xf); |
282 | 2.91k | x |= (insn >> 5) & 0x30; |
283 | 2.91k | sprintf (buf, "0x%02x", x); |
284 | 2.91k | sprintf (comment, "%d", x); |
285 | 2.91k | *style = dis_style_address; |
286 | 2.91k | } |
287 | 2.91k | break; |
288 | | |
289 | 1.47k | case 'p': |
290 | 1.47k | { |
291 | 1.47k | unsigned int x; |
292 | | |
293 | 1.47k | x = (insn >> 3) & 0x1f; |
294 | 1.47k | sprintf (buf, "0x%02x", x); |
295 | 1.47k | sprintf (comment, "%d", x); |
296 | 1.47k | *style = dis_style_address; |
297 | 1.47k | } |
298 | 1.47k | break; |
299 | | |
300 | 129 | case 'E': |
301 | 129 | sprintf (buf, "%d", (insn >> 4) & 15); |
302 | 129 | *style = dis_style_immediate; |
303 | 129 | break; |
304 | | |
305 | 0 | case '?': |
306 | 0 | *buf = '\0'; |
307 | 0 | break; |
308 | | |
309 | 0 | default: |
310 | 0 | sprintf (buf, "??"); |
311 | | /* xgettext:c-format */ |
312 | 0 | opcodes_error_handler (_("unknown constraint `%c'"), constraint); |
313 | 0 | ok = 0; |
314 | 136k | } |
315 | | |
316 | 136k | return ok; |
317 | 136k | } |
318 | | |
319 | | /* Read the opcode from ADDR. Return 0 in success and save opcode |
320 | | in *INSN, otherwise, return -1. */ |
321 | | |
322 | | static int |
323 | | avrdis_opcode (bfd_vma addr, disassemble_info *info, uint16_t *insn) |
324 | 102k | { |
325 | 102k | bfd_byte buffer[2]; |
326 | 102k | int status; |
327 | | |
328 | 102k | status = info->read_memory_func (addr, buffer, 2, info); |
329 | | |
330 | 102k | if (status == 0) |
331 | 101k | { |
332 | 101k | *insn = bfd_getl16 (buffer); |
333 | 101k | return 0; |
334 | 101k | } |
335 | | |
336 | 130 | info->memory_error_func (status, addr, info); |
337 | 130 | return -1; |
338 | 102k | } |
339 | | |
340 | | |
341 | | int |
342 | | print_insn_avr (bfd_vma addr, disassemble_info *info) |
343 | 100k | { |
344 | 100k | uint16_t insn, insn2; |
345 | 100k | const struct avr_opcodes_s *opcode; |
346 | 100k | static unsigned int *maskptr; |
347 | 100k | void *stream = info->stream; |
348 | 100k | fprintf_styled_ftype prin = info->fprintf_styled_func; |
349 | 100k | static unsigned int *avr_bin_masks; |
350 | 100k | static int initialized; |
351 | 100k | int cmd_len = 2; |
352 | 100k | int ok = 0; |
353 | 100k | char op1[20], op2[20], comment1[40], comment2[40]; |
354 | 100k | enum disassembler_style style_op1, style_op2; |
355 | 100k | int sym_op1 = 0, sym_op2 = 0; |
356 | 100k | bfd_vma sym_addr1, sym_addr2; |
357 | | |
358 | | /* Clear instruction information field. */ |
359 | 100k | info->insn_info_valid = 0; |
360 | 100k | info->branch_delay_insns = 0; |
361 | 100k | info->data_size = 0; |
362 | 100k | info->insn_type = dis_noninsn; |
363 | 100k | info->target = 0; |
364 | 100k | info->target2 = 0; |
365 | | |
366 | 100k | if (!initialized) |
367 | 1 | { |
368 | 1 | unsigned int nopcodes; |
369 | | |
370 | | /* PR 4045: Try to avoid duplicating the 0x prefix that |
371 | | objdump_print_addr() will put on addresses when there |
372 | | is no symbol table available. */ |
373 | 1 | if (info->symtab_size == 0) |
374 | 1 | comment_start = " "; |
375 | | |
376 | 1 | nopcodes = sizeof (avr_opcodes) / sizeof (struct avr_opcodes_s); |
377 | | |
378 | 1 | avr_bin_masks = xmalloc (nopcodes * sizeof (unsigned int)); |
379 | | |
380 | 1 | for (opcode = avr_opcodes, maskptr = avr_bin_masks; |
381 | 126 | opcode->name; |
382 | 125 | opcode++, maskptr++) |
383 | 125 | { |
384 | 125 | char * s; |
385 | 125 | unsigned int bin = 0; |
386 | 125 | unsigned int mask = 0; |
387 | | |
388 | 2.12k | for (s = opcode->opcode; *s; ++s) |
389 | 2.00k | { |
390 | 2.00k | bin <<= 1; |
391 | 2.00k | mask <<= 1; |
392 | 2.00k | bin |= (*s == '1'); |
393 | 2.00k | mask |= (*s == '1' || *s == '0'); |
394 | 2.00k | } |
395 | 125 | assert (s - opcode->opcode == 16); |
396 | 125 | assert (opcode->bin_opcode == bin); |
397 | 125 | *maskptr = mask; |
398 | 125 | } |
399 | | |
400 | 1 | initialized = 1; |
401 | 1 | } |
402 | | |
403 | 100k | if (avrdis_opcode (addr, info, &insn) != 0) |
404 | 125 | return -1; |
405 | | |
406 | 100k | for (opcode = avr_opcodes, maskptr = avr_bin_masks; |
407 | 7.42M | opcode->name; |
408 | 7.32M | opcode++, maskptr++) |
409 | 7.40M | { |
410 | 7.40M | if ((opcode->isa == AVR_ISA_TINY) && (info->mach != bfd_mach_avrtiny)) |
411 | 46.4k | continue; |
412 | 7.36M | if ((insn & *maskptr) == opcode->bin_opcode) |
413 | 85.9k | break; |
414 | 7.36M | } |
415 | | |
416 | | /* Special case: disassemble `ldd r,b+0' as `ld r,b', and |
417 | | `std b+0,r' as `st b,r' (next entry in the table). */ |
418 | | |
419 | 100k | if (AVR_DISP0_P (insn)) |
420 | 610 | opcode++; |
421 | | |
422 | 100k | op1[0] = 0; |
423 | 100k | op2[0] = 0; |
424 | 100k | comment1[0] = 0; |
425 | 100k | comment2[0] = 0; |
426 | 100k | style_op1 = dis_style_text; |
427 | 100k | style_op2 = dis_style_text; |
428 | | |
429 | 100k | if (opcode->name) |
430 | 85.9k | { |
431 | 85.9k | char *constraints = opcode->constraints; |
432 | 85.9k | char *opcode_str = opcode->opcode; |
433 | | |
434 | 85.9k | insn2 = 0; |
435 | 85.9k | ok = 1; |
436 | | |
437 | 85.9k | if (opcode->insn_size > 1) |
438 | 1.23k | { |
439 | 1.23k | if (avrdis_opcode (addr + 2, info, &insn2) != 0) |
440 | 5 | return -1; |
441 | 1.23k | cmd_len = 4; |
442 | 1.23k | } |
443 | | |
444 | 85.9k | if (*constraints && *constraints != '?') |
445 | 74.8k | { |
446 | 74.8k | int regs = REGISTER_P (*constraints); |
447 | | |
448 | 74.8k | ok = avr_operand (insn, insn2, addr, *constraints, opcode_str, op1, |
449 | 74.8k | comment1, &style_op1, 0, &sym_op1, &sym_addr1, |
450 | 74.8k | info); |
451 | | |
452 | 74.8k | if (ok && *(++constraints) == ',') |
453 | 61.3k | ok = avr_operand (insn, insn2, addr, *(++constraints), opcode_str, |
454 | 61.3k | op2, *comment1 ? comment2 : comment1, |
455 | 61.3k | &style_op2, regs, &sym_op2, &sym_addr2, |
456 | 61.3k | info); |
457 | 74.8k | } |
458 | 85.9k | } |
459 | | |
460 | 100k | if (!ok) |
461 | 15.8k | { |
462 | | /* Unknown opcode, or invalid combination of operands. */ |
463 | 15.8k | sprintf (op1, "0x%04x", insn); |
464 | 15.8k | op2[0] = 0; |
465 | 15.8k | sprintf (comment1, "????"); |
466 | 15.8k | comment2[0] = 0; |
467 | 15.8k | } |
468 | | |
469 | 100k | (*prin) (stream, ok ? dis_style_mnemonic : dis_style_assembler_directive, |
470 | 100k | "%s", ok ? opcode->name : ".word"); |
471 | | |
472 | 100k | if (*op1) |
473 | 89.6k | (*prin) (stream, style_op1, "\t%s", op1); |
474 | | |
475 | 100k | if (*op2) |
476 | 61.2k | { |
477 | 61.2k | (*prin) (stream, dis_style_text, ", "); |
478 | 61.2k | (*prin) (stream, style_op2, "%s", op2); |
479 | 61.2k | } |
480 | | |
481 | 100k | if (*comment1) |
482 | 69.4k | (*prin) (stream, dis_style_comment_start, "\t; %s", comment1); |
483 | | |
484 | 100k | if (sym_op1) |
485 | 12.7k | info->print_address_func (sym_addr1, info); |
486 | | |
487 | 100k | if (*comment2) |
488 | 0 | (*prin) (stream, dis_style_comment_start, " %s", comment2); |
489 | | |
490 | 100k | if (sym_op2) |
491 | 1.40k | info->print_address_func (sym_addr2, info); |
492 | | |
493 | 100k | return cmd_len; |
494 | 100k | } |