Files
avrdude/src/disasm.c
stefanrueger 158a2a79f1 No longer use register names in I/O space for ldi operands
When disassembling AVRDUDE uses known labels for ldi operations that
initialise a register pair, eg,

      ldi     r30, lo8(nvm.ctrla)  ; 0xcb = 203
      ldi     r31, hi8(nvm.ctrla)  ; 0x01 = 1
      out     cpu.ccp, r24
      st      Z, r18

This is meant to illustrate a possible intention of the code. It is not
unusual to see a base register loaded, for example the USARTC0 register
base, into the Y or Z register pair and read write with displacement from
specific USARTC0 register bytes using the ldd and std opcodes.

However, sometimes these labels are misleading when, eg, the register pair
is just initialised as a counter. In particular that can easily happen for
I/O register addresses for which there are fewer use cases to put them
into a register pair as the in/out opcodes are short and no savings would
be made using ldd/std. For example,

      ldi     r28, lo8(gpio.gpior9) ; 0x09 = 9
      ldi     r29, hi8(gpio.gpior9) ; 0x00 = 0

is most likely used as a 16-bit counter initialised with 9. This would
better be disassembled as

      ldi     r28, 0x09            ; 9
      ldi     r29, 0x00            ; 0

This commit does so by removing the use of registers in I/O space as
symbolic ldi operands when loading a register pair.
2025-11-10 01:26:16 +01:00

1373 lines
43 KiB
C

/*
* avrdude - A Downloader/Uploader for AVR device programmers
* Copyright (C) 2007 Johannes Bauer <JohannesBauer@gmx.de>
* Copyright (C) 2024 by Stefan Rueger <stefan.rueger@urclocks.com>
*
* 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, see <http://www.gnu.org/licenses/>.
*/
/*
* This disassembly code originates from the avrdisas disassembler written in
* 2007 by Johannes Bauer. It has been rewritten by Stefan Rueger to
* - Enable disassembly of small memory chunks in AVRDUDE's terminal
* - Drive disassembly from the avr_opcodes[] table alone
* - Generate a compilable source
* - Find symbolic values for ldi constants that initialise register pais
*
* Like the ship of Theseus there is little of the avrdisas orginal code that
* has remained, but it is fair to say that without it the AVRDUDE disasm
* command would not have happened.
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include "avrdude.h"
#include "libavrdude.h"
#define ALLSUBTYPES "BWLQOCAS"
enum {
TYPE_BYTE = 1, // 'B': 1 byte
TYPE_WORD, // 'W': 2 bytes
TYPE_LONG, // 'L': 4 bytes
TYPE_QUAD, // 'Q': 8 bytes
TYPE_OCTA, // 'O': 16 bytes
TYPE_CHAR, // 'C': 1 byte (printed as unterminated .ascii string)
TYPE_ASTRING, // 'A': Autoaligned nul-terminated .asciz string
TYPE_STRING, // 'S': Nul-terminated .asciz string
};
#define buf2op16(i) buf2uint16((const unsigned char *) (buf + (i)))
#define buf2op32(i) buf2uint32((const unsigned char *) (buf + (i)))
static void zap_symbols() {
if(cx->dis_symbols) {
for(int i = 0; i < cx->dis_symbolN; i++) {
mmt_free(cx->dis_symbols[i].comment);
mmt_free(cx->dis_symbols[i].name);
}
mmt_free(cx->dis_symbols);
cx->dis_symbols = NULL;
}
cx->dis_symbolN = 0;
}
static int type_order(int type) {
switch(type) {
case 'I':
return '1';
case 'M':
return '2';
case 'L':
return '3';
case 'P':
return '4';
default:
return type;
}
}
static int symbol_sort(const void *v1, const void *v2) {
const Dis_symbol *p1 = v1, *p2 = v2;
int diff;
if((diff = type_order(p1->type) - type_order(p2->type)))
return diff;
return p1->address - p2->address;
}
static int symbol_stable_qsort(const void *v1, const void *v2) {
int diff = symbol_sort(v1, v2);
if(diff)
return diff;
return (char *) v1 - (char *) v2; // Keep original order if same keys (stable sort)
}
static char *cleanup(char *str) {
for(char *s = str; *s; s++)
*s = *s == '.' || isalnum(*s & 0xff)? *s: '_';
return str;
}
// Max list length of the type on one line (approx 110 chars with label and op-codes)
static int nmult(int type) {
switch(type) {
default:
case TYPE_BYTE: return 12; // 12 bytes: 113 chars
case TYPE_WORD: return 8; // 16 bytes: 109 chars
case TYPE_LONG: return 5; // 20 bytes: 109 chars
case TYPE_QUAD: return 3; // 24 bytes: 113 chars
case TYPE_OCTA: return 1; // 16 bytes: 81 chars
}
}
// How many items can a line print given the array has got already done printed
static int maxmult(Dis_symbol *s, int done) {
int max = nmult(s->subtype);
return s->count-done < max? s->count-done: max;
}
// Width of memory a subtype covers
static int subtype_width(int subtype) {
switch(subtype) {
default:
case TYPE_BYTE: return 1;
case TYPE_WORD: return 2;
case TYPE_LONG: return 4;
case TYPE_QUAD: return 8;
case TYPE_OCTA: return 16;
}
}
// Width of memory a symbol covers
static int symbol_width(Dis_symbol *s) {
return s->count*subtype_width(s->subtype);
}
static Dis_symbol *find_symbol(int type, int address) {
Dis_symbol key, *s = cx->dis_symbols, *found;
key.type = type;
key.address = address;
if(!(found = bsearch(&key, s, cx->dis_symbolN, sizeof(Dis_symbol), symbol_sort)))
return NULL;
// Determine m as first matching symbol that has smallest width
int k = found - s, m = k, w, width = symbol_width(s + k);
for(int i = k - 1; i >= 0 && symbol_sort(s + i, s + k) == 0; i--)
if((w = symbol_width(s + i)) <= width) // Want first entry of those with same min width
m = i, width = w;
for(int i = k + 1; i < cx->dis_symbolN && symbol_sort(s + i, s + k) == 0; i++)
if((w = symbol_width(s + i)) < width) // < is deliberate, see above
m = i, width = w;
return s + m;
}
static void add_symbol(int addr, int type, int sub, int count, const char *name, const char *com) {
int N = cx->dis_symbolN++;
if(N%1024 == 0)
cx->dis_symbols = (Dis_symbol *) mmt_realloc(cx->dis_symbols, sizeof(Dis_symbol)*(N + 1024));
cx->dis_symbols[N].address = addr;
cx->dis_symbols[N].type = type;
cx->dis_symbols[N].subtype = sub;
cx->dis_symbols[N].count = count;
cx->dis_symbols[N].used = 0;
cx->dis_symbols[N].printed = 0;
cx->dis_symbols[N].name = name? cleanup(str_rtrim(mmt_strdup(str_ltrim(name)))): NULL;
cx->dis_symbols[N].comment = com? str_rtrim(mmt_strdup(str_ltrim(com))): NULL;
}
/*
* Tokenising of a tagfile line returning (argc, argv); parsing ends when
* - A token starts with a comment character #
* - The comment field after the name of the symbol is encountered
* - The end of the string is encountered
*
* Argv is allocated once, so the caller only needs to mmt_free argv.
* On error NULL is returned (when input line was too long).
*
*/
static int tagfile_tokenize(char *s, int *argcp, const char ***argvp) {
size_t slen;
int n, nargs;
const char **argv;
char *buf, *q, *r;
// Upper estimate of the number of arguments
for(nargs = 0, q = s; *q; nargs++) {
while(*q && !isspace((unsigned char) *q))
q++;
while(*q && isspace((unsigned char) *q))
q++;
}
slen = q - s;
// Limit input line to some 186 Megabytes as max nargs is (slen+1)/2
if(slen > 2*((INT_MAX - 2*sizeof(char *))/(sizeof(char *) + 3)))
return 0;
// Allocate once for pointers and contents, so caller only needs to mmt_free(argv)
argv = mmt_malloc((nargs + 2)*sizeof(char *) + slen + nargs);
buf = (char *) (argv + nargs + 1);
for(n = 0, r = s; *r;) {
q = str_nexttok(r, " \t\n\r\v\f", &r);
size_t len = strlen(q);
if(*q == '#') { // Inline comment: ignore rest of line
r = q + len;
break;
}
strcpy(buf, q);
if(*buf) // Don't record empty arguments
argv[n++] = buf;
if(n > 1 && n == (str_eq(argv[1], "L")? 3: 5)) { // Stop parsing after symbol name
if(*r)
argv[n] = r; // Comment, if any
break;
}
buf += len + 1;
}
*argcp = n;
*argvp = argv;
return 1;
}
#define Return(fmt, ...) do { \
pmsg_error("tagfile line %d " fmt, lineno, __VA_ARGS__); msg_error("\n"); \
return -1; \
} while(0)
static int tagfile_readline(char *line, int lineno, const char *const *isrnames, int ni) {
int type, subtype, vn, address, count, argc = 0;
const char *errptr, **argv = NULL;
if(!tagfile_tokenize(line, &argc, &argv))
Return("%s", "is too long");
if(argc == 0)
return 0;
if(argc < 3)
Return("%s", "needs at least address, symbol type (L/P/M) and name");
address = str_int(argv[0], STR_INT32, &errptr);
if(errptr)
Return("address %s: %s", argv[0], errptr);
if(strlen(argv[1]) != 1 || !strchr("LPM", *argv[1]))
Return("%s", "2nd argument must be L, P or M");
type = *argv[1];
if(type == 'L') {
const char *name = argv[2];
if(str_starts(name, "__vector_") && looks_like_number(name + 9))
if((vn = strtol(name + 9, NULL, 0)) > 0 && vn < ni) // Don't replace __vectors_0
name = str_lc((char *) str_ccprintf("__vector_%s", isrnames[vn]));
add_symbol(address, 'L', TYPE_BYTE, 1, name, argv[3]);
return 0;
}
if(argc < 5 || strlen(argv[2]) != 1 || !strchr(ALLSUBTYPES, *argv[2]))
Return("needs to be <address> %c [%s] <count> <name>", type, type == 'M'? "BW": ALLSUBTYPES);
switch(*argv[2]) {
default:
subtype = TYPE_BYTE;
break;
case 'C':
subtype = TYPE_CHAR;
break;
case 'W':
subtype = TYPE_WORD;
break;
case 'L':
subtype = TYPE_LONG;
break;
case 'Q':
subtype = TYPE_QUAD;
break;
case 'O':
subtype = TYPE_OCTA;
break;
case 'A':
subtype = TYPE_ASTRING;
break;
case 'S':
subtype = TYPE_STRING;
}
if(type == 'M' && subtype != TYPE_BYTE && subtype != TYPE_WORD) {
pmsg_error("memory label type can only be B(yte) or W(ord)");
return -1;
}
count = str_int(argv[3], STR_INT32, &errptr);
if(errptr)
Return("count %s: %s\n", argv[3], errptr);
if(count < 1)
Return("tagfile line %d has invalid count %d", lineno, count);
add_symbol(address, type, subtype, count, argv[4], argv[5]);
mmt_free(argv);
return 0;
}
// Allocate, copy, append a suffix (H, L, 0...8 or nothing), cleanup name and return
static char *regname(const char *pre, const char *reg, int suf) {
char *ret =
suf <= -1? mmt_sprintf("%s%s", pre, reg):
suf == 'h' || suf == 'l'? mmt_sprintf("%s%s%c", pre, reg, suf): mmt_sprintf("%s%s%d", pre, reg, suf);
return cleanup(ret);
}
// Return the basename of a register, ie, the part after the first . (if any)
static const char *regbase(const char *reg) {
const char *ret = strchr(reg, '.');
return ret? ret + 1: reg;
}
// Return the basename of rf[i].reg if that's unique amongst the nr register entries
static const char *shortrname(const Register_file *rf, int nr, int i) {
const char *f = rf[i].reg, *s = regbase(f);
if(f != s)
for(int k = 0; k < nr; k++)
if(k != i && str_eq(s, regbase(rf[k].reg)))
return f;
return s;
}
static void add_register(int io_off, int addr, const char *name, int suffix) {
add_symbol(io_off + addr, 'M', TYPE_BYTE, 1, regname(io_off? "mem.": "", name, suffix), NULL);
if(addr < 0x40 && io_off) // Only keep I/O addresses separate if mem addresses have an offset
add_symbol(addr, 'I', TYPE_BYTE, 1, regname(io_off? "io.": "", name, suffix), NULL);
}
// Initialise cx->dis_symbols from part register file
static void init_regfile(const AVRPART *p) {
AVRMEM *mem;
int nr = 0, io_off = cx->dis_io_offset;
const Register_file *rf = avr_locate_register_file(p, &nr);
if((mem = avr_locate_sram(p)) && mem->size > 1 && mem->offset <= 0x200) {
add_symbol(mem->offset, 'M', TYPE_BYTE, mem->size, "sram.start", NULL);
add_symbol(mem->offset + mem->size - 1, 'M', TYPE_BYTE, 1, "sram.end", NULL);
}
if(rf) {
for(int i = 0; i < nr; i++) {
const char *rname = io_off? shortrname(rf, nr, i): rf[i].reg;
if(rf[i].size == 1) {
add_register(io_off, rf[i].addr, rname, -1);
} else if(rf[i].size == 2) {
add_register(io_off, rf[i].addr, rname, 'l');
add_register(io_off, rf[i].addr + 1, rname, 'h');
} else if(rf[i].size > 2) {
for(int k = 0; k < rf[i].size; k++)
add_register(io_off, rf[i].addr + k, rname, k);
}
}
qsort(cx->dis_symbols, cx->dis_symbolN, sizeof(Dis_symbol), symbol_stable_qsort);
}
}
int disasm_init_tagfile(const AVRPART *p, const char *fname) {
FILE *inf = fopen(fname, "r");
int ni = 0, lineno = 1;
const char *errstr;
const char *const *isrnames = avr_locate_isrtable(p, &ni);
if(!inf) {
pmsg_ext_error("cannot open tagfile %s: %s\n", fname, strerror(errno));
return -1;
}
zap_symbols();
init_regfile(p);
for(char *buffer; (buffer = str_fgets(inf, &errstr)); mmt_free(buffer))
if(tagfile_readline(buffer, lineno++, isrnames, ni) < 0)
goto error;
if(errstr) {
pmsg_error("read error in tag file %s: %s\n", fname, errstr);
goto error;
}
fclose(inf);
qsort(cx->dis_symbols, cx->dis_symbolN, sizeof(Dis_symbol), symbol_stable_qsort);
return 0;
error:
fclose(inf);
return -1;
}
static const char *resolve_address(int type, int address) {
Dis_symbol *s = find_symbol(type == 'I' && !cx->dis_io_offset? 'M': type, address);
if(s && s->name)
s->used = 1;
return s? s->name: NULL;
}
// Increase cycle number by 1 if it's a 3 byte PC
static const char *cycles(int mnemo) {
if(mnemo < 0)
return "---";
const char *ret = avr_opcodes[mnemo].clock[cx->dis_cycle_index];
// A plus sign after the cycle number means add one for 3-byte PC
if(*ret && ret[1] == '+')
return str_ccprintf("%c", cx->dis_flashsz > 128*1024? *ret + 1: *ret);
return ret;
}
static const char *get_label_name(int destination, const char **commentp) {
Dis_symbol *s = find_symbol('L', destination);
if(s && s->name) {
if(commentp)
*commentp = s->comment;
s->printed = 1; // Will be printed in pass 2
return s->name;
}
for(int i = 0; i < cx->dis_labelN; i++)
if(cx->dis_labels[i].addr == destination)
return str_ccprintf("%s%d", cx->dis_labels[i].is_func? "Subroutine": "Label", cx->dis_labels[i].labelno);
return NULL;
}
// Wrap around flash
static int disasm_wrap(int addr) {
if(cx->dis_flashsz2)
addr &= cx->dis_flashsz2 - 1;
return addr;
}
#define disasm_out(...) do { \
if(cx->dis_pass != 2) \
break; \
if(cx->dis_para > 0) \
term_out("\n"); \
cx->dis_para = 0; \
term_out(__VA_ARGS__); \
} while(0)
#define LINE_N 256
typedef struct {
char label[LINE_N], code[LINE_N], comment[LINE_N];
} Dis_line;
// Opcode starts in codecol()
static int codecol() {
int ret = 0;
if(cx->dis_opts.addresses)
ret += 3 + cx->dis_addrwidth;
if(cx->dis_opts.sreg_flags)
ret += 9;
if(cx->dis_opts.cycles)
ret += 4;
if(cx->dis_opts.opcode_bytes)
ret += 12;
return (ret? ret + 1: 2);
}
// Comments start in commentcol() + 1
static int commentcol() {
return codecol() + cx->dis_codewidth;
}
enum {
jumpable = 1, // Any opcode address (not P data, not middle of 32-bit opcode)
callable = 2, // Address has been used as target of call/rcall etc
};
// Xable is jumpable or callable
static void set_address(int address, int xable) {
if(cx->dis_jcaddr && address >= cx->dis_start && address <= cx->dis_end) {
int n = sizeof(int)*8, idx = (address - cx->dis_start)/2*2;
cx->dis_jcaddr[idx/n] |= (xable << (idx%n));
}
}
static int is_address(int address, int xable) {
if(!cx->dis_jcaddr || address < cx->dis_start || address > cx->dis_end)
return 0;
int n = sizeof(int)*8, idx = (address - cx->dis_start)/2*2;
return !!(cx->dis_jcaddr[idx/n] & (xable << (idx%n)));
}
// Format output for a label list r referenced by mnemonic m
static void output_references(const char *m, char *r) {
disasm_out("; %c%s from ", toupper(*m & 0xff), m + 1);
for(char *s = r;;) {
char *c = strchr(s + 1, ',');
if(c && c - r > 80 && s > r) {
*s = 0;
disasm_out("%s\n; %*s ", r, (int) strlen(m), "");
r = (char *) str_ltrim(s + 1);
s = r;
} else if(c && c - r > 70) {
*c = 0;
disasm_out("%s\n; %*s ", r, (int) strlen(m), "");
r = (char *) str_ltrim(c + 1);
s = r;
} else if(c) {
s = c;
} else {
disasm_out("%s\n", r);
break;
}
}
}
// Unified printing of a line
static void lineout(const char *code, const char *comment,
int mnemo, int oplen, const char *buf, int pos, int addr, int showlabel) {
Dis_jumpcall *jc = cx->dis_jumpcalls;
int here = disasm_wrap(pos + addr);
if(cx->dis_opts.labels && showlabel) {
int match = 0, first = -1;
const char *comment = NULL, *name;
Dis_symbol *s;
for(int i = 0; i < cx->dis_jumpcallN; i++)
if(jc[i].to == here)
if(!match++)
first = i;
if(cx->dis_pass == 2 && match) {
cx->dis_para++;
char *reflist = mmt_malloc(match*(3 + 64)), *r = reflist; // Worst case length
int mne = jc[first].mnemo, one_mne = 1;
for(int i = first; i < cx->dis_jumpcallN && jc[i].to == here; i++) {
if(mne != jc[i].mnemo) { // More than one mnemonic reference this line
one_mne = 0;
if(cx->dis_opts.comments)
output_references(avr_opcodes[mne].opcode, reflist);
mne = jc[i].mnemo;
r = reflist;
*r = 0;
}
strcpy(r, str_ccprintf(&", L%0*x"[2*(r == reflist)], cx->dis_addrwidth, jc[i].from));
r += strlen(r);
}
name = get_label_name(here, &comment);
if(!comment && strlen(reflist) + commentcol() < 70 && one_mne) { // Refs line with label
const char *mnestr = avr_opcodes[mne].opcode;
if(cx->dis_opts.comments)
disasm_out("%-*s ; %s\n", commentcol(), str_ccprintf("%s:", name),
str_ccprintf("%c%s from %s", toupper(*mnestr & 0xff), mnestr + 1, reflist));
else
disasm_out("%s:\n", name);
} else {
if(cx->dis_opts.comments)
output_references(avr_opcodes[mne].opcode, reflist);
if(!comment || !*comment || !cx->dis_opts.comments)
disasm_out("%s:\n", name);
else
disasm_out("%-*s ; %s\n", commentcol(), str_ccprintf("%s:", name), comment);
}
cx->dis_para = -1;
mmt_free(reflist);
} else if(match) { // Register potential L label in pass 1 as to be printed
(void) get_label_name(here, &comment);
} else if(!match && cx->dis_opts.unused_labels && (s = find_symbol('L', here))) {
s->printed = 1;
if(!s->comment || !*s->comment || !cx->dis_opts.comments)
disasm_out("%s:\n", s->name);
else
disasm_out("%-*s ; %s\n", commentcol(), str_ccprintf("%s:", s->name), s->comment);
}
}
if(cx->dis_opts.addresses)
disasm_out("L%0*x: ", cx->dis_addrwidth, here);
if(cx->dis_opts.sreg_flags)
disasm_out("%s ", mnemo < 0? "--------": avr_opcodes[mnemo].flags);
if(cx->dis_opts.cycles)
disasm_out("%3s ", cycles(mnemo));
if(cx->dis_opts.opcode_bytes)
for(int i = 0; i < 4; i++)
disasm_out(i < oplen? "%02x ": " ", buf[pos + i] & 0xff);
disasm_out(codecol() > 2? " ": " ");
if(!comment || !*comment || !cx->dis_opts.comments)
disasm_out("%s\n", code);
else
disasm_out("%-*s ; %s\n", cx->dis_codewidth, code, comment);
if(mnemo == MNEMO_ret || mnemo == MNEMO_u_ret || mnemo == MNEMO_reti || mnemo == MNEMO_u_reti)
cx->dis_para++;
}
// Process 1- to 16-byte numbers
static int process_num(const char *buf, int buflen, int subtype, int mult, int pad, int pos, int offset) {
int i, dx, n = subtype_width(subtype);
char code[1024], *cp = code;
size_t rem = sizeof code, len;
while(mult > 1 && buflen - pos < n*mult)
mult--;
if(buflen - pos < n)
n = buflen - pos;
while(n & (n - 1)) // Round down to next power of 2
n &= n - 1;
snprintf(cp, rem, ".%s ", n==1? "byte": n==2? "word": n==4? "long": n==8? "quad": "octa");
len = strlen(cp), rem -= len, cp += len;
for(dx = pos, i = 0; i < mult; i++, dx += n) {
snprintf(cp, rem, "%c 0x%s", i? ',': ' ',
n == 1? str_ccprintf("%02x", buf[dx] & 0xff):
n == 2? str_ccprintf("%04x", buf2op16(dx)):
n == 4? str_ccprintf("%08x", buf2op32(dx)):
n == 8? str_ccprintf("%08x%08x", buf2op32(dx+4), buf2op32(dx)):
str_ccprintf("%08x%08x%08x%08x", buf2op32(dx+12), buf2op32(dx+8), buf2op32(dx+4), buf2op32(dx))
);
len = strlen(cp), rem -= len, cp += len;
}
while(pad && i++ < nmult(subtype))
snprintf(cp, rem, "%*s", 4+n*2, ""), len = strlen(cp), rem -= len, cp += len;
char *comment = mmt_malloc(n*mult+1), c;
for(i = 0; i < n*mult; i++)
c = buf[pos+i], comment[i] = (c & 0x80) || c <= 32 || c == 0x7f? '_': c;
lineout(code, comment, -1, n*mult, buf, pos, offset, 0);
return n*mult;
}
static int process_fill0xff(const char *buf, int buflen, int nbytes, int pos, int offset) {
cx->dis_para++;
lineout(str_ccprintf(".fill %d, 2, 0xffff", nbytes/2), NULL, -1, nbytes, buf, pos, offset, 1);
return nbytes/2*2;
}
// Output quoted string
static int process_string(const char *buf, int buflen, int pos, int offset) {
char *code, *out;
int i = pos;
while(i < buflen && buf[i])
i++;
if(i == buflen) { // Ran out of buffer: string not terminated
char *str = mmt_malloc(i - pos + 1);
memcpy(str, buf + pos, i - pos);
str[i - pos] = 0;
out = cfg_escape(str);
mmt_free(str);
code = mmt_sprintf(".ascii %s", out);
} else { // Nul terminated string
out = cfg_escape(buf + pos);
code = mmt_sprintf(".asciz %s", out);
i++;
}
lineout(code, NULL, -1, i - pos, buf, pos, offset, 0);
mmt_free(out);
mmt_free(code);
return i - pos;
}
// Output quoted character array
static int process_chars(const char *buf, int buflen, int nbytes, int pos, int offset) {
char *code, *out;
// Shorten character array if out of space
if(buflen - pos < nbytes)
nbytes = buflen - pos;
out = cfg_escapen(buf + pos, nbytes);
code = mmt_sprintf(".ascii %s", out);
lineout(code, NULL, -1, nbytes, buf, pos, offset, 0);
mmt_free(out);
mmt_free(code);
return nbytes;
}
// Returns number of bytes of PGM data at this position, printing them in pass 2
static int process_data(const char *buf, int buflen, int pos, int offset) {
int ret = 0;
Dis_symbol *s = find_symbol('P', disasm_wrap(pos + offset));
if(!s) {
if(pos + 1 >= buflen)
return 0;
if(!(s = find_symbol('P', disasm_wrap(pos + offset + 1)))) { // No PGM label, check for fill block
int k = 0;
if((buf[pos] & 0xff) == 0xff && (buf[pos + 1] & 0xff) == 0xff)
for(k = pos + 2; k < buflen; k++)
if((buf[k] & 0xff) != 0xff || find_symbol('P', disasm_wrap(k + offset)))
break;
k &= ~1;
return !k || k - pos < 4? 0: process_fill0xff(buf, buflen, k - pos, pos, offset);
}
// Found PGM label at odd address, print byte before label and continue
process_num(buf, buflen, TYPE_BYTE, 1, 0, pos, offset);
ret = 1;
}
if(s->name) {
cx->dis_para++;
s->printed = 1; // Will be printed in pass 2
if(!s->comment || !*s->comment || !cx->dis_opts.comments)
disasm_out("%s:\n", s->name);
else
disasm_out("%-*s ; %s\n", commentcol(), str_ccprintf("%s:", s->name), s->comment);
}
switch(s->subtype) {
case TYPE_BYTE:
case TYPE_WORD:
case TYPE_LONG:
case TYPE_QUAD:
case TYPE_OCTA:
for(int i = 0; i < s->count && pos + ret < buflen; i += maxmult(s, i))
ret += process_num(buf, buflen, s->subtype, maxmult(s, i), !!i, pos + ret, offset);
break;
case TYPE_CHAR:
ret += process_chars(buf, buflen, s->count, pos + ret, offset);
break;
case TYPE_ASTRING:
case TYPE_STRING:
for(int i = 0; i < s->count && pos + ret < buflen; i++)
ret += process_string(buf, buflen, pos + ret, offset);
}
if(s->subtype == TYPE_ASTRING) { // Autoaligned string
if(ret%2) {
if(buf[pos + ret])
pmsg_warning("autoalignment expects 0x00 padding but got 0x%02x\n", buf[pos + ret] & 0xff);
lineout(str_ccprintf(".byte 0x%02x", buf[pos + ret] & 0xff),
"String autoalignment", -1, 1, buf, pos + ret, offset, 0);
ret++;
}
}
return ret;
}
static void emit_used_symbols() {
Dis_symbol *s = cx->dis_symbols;
int len, maxlen = 0;
for(int i = 0; i < cx->dis_symbolN; i++)
if(s[i].used && !s[i].printed && s[i].name)
if((len = strlen(s[i].name)) > maxlen)
maxlen = len;
for(int i = 0; i < cx->dis_symbolN; i++)
if(s[i].used && !s[i].printed && s[i].name) {
const char *equ = str_ccprintf(".equ %s,%*s 0x%02x", s[i].name,
(int) (maxlen - strlen(s[i].name)), "", s[i].address);
if(!s[i].comment || !*s[i].comment || !cx->dis_opts.comments)
disasm_out("%*s%s\n", codecol(), "", equ);
else
disasm_out("%*s%-*s ; %s\n", codecol(), "", cx->dis_codewidth, equ, s[i].comment);
}
}
void disasm_zap_jumpcalls() {
mmt_free(cx->dis_jumpcalls); cx->dis_jumpcalls = NULL; cx->dis_jumpcallN = 0;
mmt_free(cx->dis_labels); cx->dis_labels = NULL; cx->dis_labelN = 0;
}
static void register_jumpcall(int from, int to, int mnemo, int is_func) {
if(cx->dis_opts.labels) {
Dis_jumpcall *jc = cx->dis_jumpcalls;
int N = cx->dis_jumpcallN;
// Already entered this jumpcall?
for(int i = 0; i < N; i++)
if(jc[i].from == from && jc[i].to == to && jc[i].mnemo == mnemo)
return;
if(N%1024 == 0)
jc = mmt_realloc(jc, sizeof(Dis_jumpcall)*(N + 1024));
jc[N].from = from;
jc[N].to = to;
jc[N].mnemo = mnemo;
if(is_func)
set_address(to, callable);
cx->dis_jumpcalls = jc;
cx->dis_jumpcallN++;
}
}
static int jumpcall_sort(const void *v1, const void *v2) {
const Dis_jumpcall *p1 = v1, *p2 = v2;
int diff;
if((diff = p1->to - p2->to))
return diff;
if((diff = p1->mnemo - p2->mnemo))
return diff;
return p1->from - p2->from;
}
static void set_labels(void) {
int dest = -1, cur_no[2] = { 0, 0 }, j = 0;
qsort(cx->dis_jumpcalls, cx->dis_jumpcallN, sizeof(Dis_jumpcall), jumpcall_sort);
cx->dis_labels = mmt_malloc(cx->dis_jumpcallN*sizeof*cx->dis_labels);
for(int i = 0; i < cx->dis_jumpcallN; i++) {
if(is_address(cx->dis_jumpcalls[i].to, jumpable) && dest != cx->dis_jumpcalls[i].to) {
int is_func = is_address(dest = cx->dis_jumpcalls[i].to, callable);
cx->dis_labels[j].addr = dest;
cx->dis_labels[j].is_func = is_func;
cx->dis_labels[j++].labelno = cur_no[is_func]++;
}
}
cx->dis_labelN = j;
}
#define Ra (regs['a'])
#define Rd (regs['d'])
#define Rr (regs['r'])
#define Rk (regs['k'])
#define RK (regs['K'])
#define Rs (regs['s'])
#define RA (regs['A'])
#define Rb (regs['b'])
#define Rq (regs['q'])
#define Na (bits['a'])
#define Nd (bits['d'])
#define Nr (bits['r'])
#define Nk (bits['k'])
#define NK (bits['K'])
#define Ns (bits['s'])
#define NA (bits['A'])
#define Nb (bits['b'])
#define Nq (bits['q'])
static char *add_comment(Dis_line *line, const char *comment) {
int len = strlen(line->comment), rem = LINE_N - len - 1;
char *p = line->comment + len;
if(len && *comment && rem > 2)
strcpy(p, ", "), p += 2, rem -= 2;
strncpy(p, comment, rem);
p[rem] = 0;
return p + strlen(p);
}
static const char *regstyle(int n, int regword) {
if(regword && !cx->dis_opts.avrgcc_style)
return str_ccprintf("%d:%d", n + 1, n);
return str_ccprintf("%d", n);
}
// Return the number of bits set in Number
unsigned bitcount(unsigned n) {
unsigned ret;
// A la Kernighan (and Richie): iteratively clear the least significant bit set
for(ret = 0; n; ret++)
n &= n - 1;
return ret;
}
typedef struct {
int from, is_func, is_lpm, preop, postop, zwd;
} Op_context;
static const char *get_ldi_name(int op1, int op2, Op_context *oxp) {
Dis_symbol *s;
char buf[2];
int ra = ldi_Rd(op1), rb = ldi_Rd(op2);
if((ra ^ rb) == 1) { // Two successive ldi opcodes initialise a register pair
buf[ra & 1] = ldi_K(op1);
buf[rb & 1] = ldi_K(op2);
int addr = buf2op16(0); // Address of register pair
// Assume address width is 2 if ldi acts on Z and is followed by e/icall within 5 opcodes
int awidth = (ra | 1) == 31 && oxp->zwd == 2? 2: 1;
for(const char *c = awidth == 2 || oxp->is_lpm? "LP": "MLP"; *c; c++)
if((s = find_symbol(*c, addr*awidth)))
break;
if(s && s->name) { // Label matches the address loaded into register pair
// Don't use register names in I/O space as ldi label names
if(s->type != 'M' || (uint16_t) addr >= 64 + cx->dis_io_offset) {
s->used = 1;
return str_ccprintf("%s%s(%s)", awidth == 2? "pm_": "", ra & 1? "hi8": "lo8", s->name);
}
}
if((ra | 1) == 31 && oxp->is_lpm && addr >= cx->dis_start && addr < cx->dis_end) {
if(cx->dis_pass == 1)
register_jumpcall(oxp->from, addr, MNEMO_ldi, 0);
const char *name = get_label_name(addr, NULL);
if(name && cx->dis_opts.labels && is_address(addr, jumpable))
return str_ccprintf("%s(%s)", ra & 1? "hi8": "lo8", name);
} else if(awidth == 2 && 2*addr >= cx->dis_start && 2*addr < cx->dis_end) {
if(cx->dis_pass == 1)
register_jumpcall(oxp->from, 2*addr, MNEMO_ldi, oxp->is_func);
const char *name = get_label_name(2*addr, NULL);
if(name && cx->dis_opts.labels && is_address(2*addr, jumpable))
return str_ccprintf("pm_%s(%s)", ra & 1? "hi8": "lo8", name);
}
}
return NULL;
}
static const char *get_ldi_context(Op_context *oxp, int opcode) {
const char *ret;
if(oxp->preop >= 0 && (ret = get_ldi_name(opcode, oxp->preop, oxp)))
return ret;
if(oxp->postop >= 0 && (ret = get_ldi_name(opcode, oxp->postop, oxp)))
return ret;
return NULL;
}
// Avr-gcc complains over relative jumps/branches that wrap around memory in large parts
static int avr_gcc_ok(int addr, int target, int offset) {
if(cx->dis_flashsz > 0 && cx->dis_flashsz <= 8192)
return 1;
return addr + offset + 2 == target; // Not wrapping around memory: OK!
}
static int show_target_symbol(int is_relative, int addr, int target, int offset) {
if(!cx->dis_opts.labels)
return 0;
// Prefer jmp .+0 over rjmp label
if(is_relative && target == disasm_wrap(addr + 2))
return 0;
if(!is_address(target, jumpable))
return 0;
if(!is_relative)
return 1;
return avr_gcc_ok(addr, target, offset);
}
static void disassemble(const char *buf, int addr, int opcode, AVR_mnemo mnemo, Op_context *oxp, Dis_line *line) {
memset(line, 0, sizeof *line);
if(mnemo < 0) {
add_comment(line, "Invalid opcode");
snprintf(line->code, LINE_N, ".word 0x%02x%02x", buf[1] & 0xff, buf[0] & 0xff);
return;
}
const AVR_opcode *oc = avr_opcodes + mnemo;
const char *lsym = NULL;
if(op16_is_mnemo(opcode, MNEMO_ldi) && (lsym = get_ldi_context(oxp, opcode))) {
mnemo = MNEMO_ldi; // Could have been ser
oc = avr_opcodes + mnemo;
}
int regs[128] = { 0 }, bits[128] = { 0 };
unsigned bmask = 0x8000;
for(const char *p = oc->bits; *p && bmask; p++) {
if(*p == ' ')
continue;
bits[*p & 0x7f]++;
regs[*p & 0x7f] <<= 1;
regs[*p & 0x7f] |= !!(opcode & bmask);
bmask >>= 1;
}
// Treat 32 bit opcodes
if(oc->nwords == 2) {
bits['k'] += 16;
regs['k'] <<= 16;
regs['k'] |= buf2op16(2);
}
// Some sanity checks for things the code relies on
if(NA && NA != 5 && NA != 6)
pmsg_warning("unexpected number of A bits in avr_opcodes table for OP_ID(%s)\n", oc->idname);
if(Na && Na != 7)
pmsg_warning("unexpected number of a bits in avr_opcodes table for OP_ID(%s)\n", oc->idname);
if(Nb && Nb != 3)
pmsg_warning("unexpected number of b bits in avr_opcodes table for OP_ID(%s)\n", oc->idname);
if(Nk && Nk != 7 && Nk != 12 && Nk != 16 && Nk != 22)
pmsg_warning("unexpected number of k bits in avr_opcodes table for OP_ID(%s)\n", oc->idname);
if(NK && NK != 4 && NK != 6 && NK != 8)
pmsg_warning("unexpected number of bits in avr_opcodes table for OP_ID(%s)\n", oc->idname);
if(Nq && Nq != 6)
pmsg_warning("unexpected number of q bits in avr_opcodes table for OP_ID(%s)\n", oc->idname);
if(Nd && (Nd < 2 || Nd > 5))
pmsg_warning("unexpected number of Rd bits in avr_opcodes table for OP_ID(%s)\n", oc->idname);
if(Nr && (Nr < 3 || Nr > 5))
pmsg_warning("unexpected number of Rr bits in avr_opcodes table for OP_ID(%s)\n", oc->idname);
if(Ns && Ns != 3)
pmsg_warning("unexpected number of s bits in avr_opcodes table for OP_ID(%s)\n", oc->idname);
// Cbr r17, 0x06 is marginally easier to read than andi r17, 0xf9
if(mnemo == MNEMO_andi && bitcount(RK) >= 4) {
RK = ~RK & 0xff;
mnemo = MNEMO_cbr;
oc = avr_opcodes + mnemo;
}
// Apply register formula
int regword = 0;
switch(oc->type & OTY_REG_MASK) {
case OTY_REVN: // Even registers r0, r2, ..., r30
Rd *= 2, Rr *= 2;
regword = 1; // movw
break;
case OTY_RUPP: // Upper registers only r16, ..., r31
Rd += 16, Rr += 16;
break;
case OTY_RW24: // r24, r26, r28, r30 only
Rd = 2*Rd + 24;
regword = 1; // adiw, sbiw
break;
}
int awd = cx->dis_addrwidth, swd = cx->dis_sramwidth;
int target = 0, offset = 0, is_jumpcall = 0, is_relative = 0;
int is_function = !!(oc->type & OTY_EXTERNAL); // Call/rcall affects stack memory
const char *name, *ksym = NULL, *asym = NULL, *rsym = NA? resolve_address('I', RA): NULL;
if(Na) {
/*
* Address is limited to 0x40...0xbf for the reduced-core (TPI part)
* ADDR[7:0] ← (/INST[8], INST[8], INST[10], INST[9], INST[3], INST[2], INST[1], INST[0])
* ADDR[7:0] ← (/a[4], a[4], a[6], a[5], a[3], a[2], a[1], a[0])
*/
Ra = (Ra & 0xf) | ((Ra >> 1) & 0x30) | ((Ra & 0x10) << 2) | (((Ra & 0x10) ^ 0x10) << 3);
asym = resolve_address('M', Ra);
}
switch(Nk) {
case 0:
break;
case 7: // Branches
offset = (int8_t) (Rk << 1); // Sign-extend and multiply by 2
target = disasm_wrap(addr + offset + 2);
if(cx->dis_pass == 1 && offset && avr_gcc_ok(addr, target, offset))
register_jumpcall(addr, target, mnemo, 0);
is_jumpcall = 1;
is_relative = 1;
break; // rjmp/rcall
case 12:
offset = (int16_t) (Rk << 4) >> 3; // Sign extend and multiply by 2
target = disasm_wrap(addr + offset + 2);
if(cx->dis_pass == 1 && offset && avr_gcc_ok(addr, target, offset))
register_jumpcall(addr, target, mnemo, is_function);
is_jumpcall = 1;
is_relative = 1;
break;
case 16: // lds/sts
ksym = resolve_address('M', Rk);
break;
case 22:
if(cx->dis_flashsz && 2*Rk > cx->dis_flashsz)
add_comment(line, str_ccprintf("Warning: destination outside flash [0, 0x%0*x]", awd, cx->dis_flashsz - 1));
target = 2*Rk; // disasm_wrap(2*Rk);
if(cx->dis_pass == 1)
register_jumpcall(addr, target, mnemo, is_function);
is_jumpcall = 1;
break;
}
snprintf(line->code, LINE_N, "%-7s ", oc->opcode);
char *lc = line->code + strlen(line->code);
#define add_operand(lc, ...) snprintf((lc), LINE_N - ((lc) - line->code), __VA_ARGS__)
// Check for opcodes with undefined results
switch(oc->type & OTY_WARN_MASK) {
case OTY_XWRN:
if(Rd == 26 || Rd == 27 || Rr == 26 || Rr == 27)
add_comment(line, "Warning: the result of this operation is undefined");
break;
case OTY_YWRN:
if(Rd == 28 || Rd == 29 || Rr == 28 || Rr == 29)
add_comment(line, "Warning: the result of this operation is undefined");
break;
case OTY_ZWRN:
if(Rd == 30 || Rd == 31 || Rr == 30 || Rr == 31)
add_comment(line, "Warning: the result of this operation is undefined");
break;
}
for(const char *o = oc->operands; *o && lc - line->code < LINE_N - 1; o++) {
switch(*o) {
case 'R':
*lc++ = 'r', *lc = 0;
break;
default:
*lc++ = *o, *lc = 0;
break;
case 'A':
if(rsym)
add_operand(lc, "%s", rsym);
else
add_operand(lc, "0x%02x", RA);
break;
case 'a':
if(asym)
add_operand(lc, "%s", asym);
else
add_operand(lc, "0x%02x", Ra);
break;
case 'k':
if(is_jumpcall) {
if(show_target_symbol(is_relative, addr, target, offset) && (name = get_label_name(target, NULL))) {
add_operand(lc, "%s", name);
if(cx->dis_opts.addresses)
add_comment(line, str_ccprintf("L%0*x", awd, target));
} else {
Dis_symbol *s = find_symbol('L', target);
if(is_relative) {
add_operand(lc, ".%+d", offset);
if(cx->dis_opts.comments && cx->dis_opts.addresses && s && s->name && *s->name)
add_comment(line, str_ccprintf("L%0*x (%s)", awd, target, s->name));
else if(cx->dis_opts.comments && cx->dis_opts.addresses)
add_comment(line, str_ccprintf("L%0*x", awd, target));
else if(cx->dis_opts.comments && s && s->name && *s->name)
add_comment(line, str_ccprintf("%s", s->name));
} else {
add_operand(lc, "0x%0*x", awd, target);
if(cx->dis_opts.comments && s && s->name && *s->name)
add_comment(line, str_ccprintf("%s", s->name));
}
}
} else {
if(ksym)
add_operand(lc, "%s", ksym);
else
add_operand(lc, "0x%0*x", swd, Rk);
}
break;
case 'b':
add_operand(lc, "%d", Rb);
add_comment(line, str_ccprintf("Bit %d = 0x%02x", Rb, 1 << Rb));
break;
case 's':
add_operand(lc, "%d", Rs);
break;
case 'd':
add_operand(lc, "%s", regstyle(Rd, regword));
break;
case 'r':
add_operand(lc, "%s", regstyle(Rr, regword));
break;
case 'K':
if(NK == 4)
add_operand(lc, "%d", RK);
else {
if(mnemo == MNEMO_ldi && lsym) {
add_operand(lc, "%s", lsym);
add_comment(line, str_ccprintf("0x%02x = %d", RK, RK));
} else {
add_operand(lc, "0x%02x", RK);
add_comment(line, str_ccprintf("%d", RK));
}
}
break;
case 'q':
add_operand(lc, "%d", Rq);
break;
}
lc += strlen(lc);
}
if(cx->dis_opts.op_names)
add_comment(line, avr_opcodes[mnemo].description);
if(cx->dis_opts.op_explanations)
add_comment(line, avr_opcodes[mnemo].operation);
// Trim trailing spaces
while(--lc >= line->code && *lc == ' ')
*lc = 0;
}
// Is there a label called main and it is used as a destination by disasm()?
static int have_own_main() {
int mainaddr = -1;
Dis_symbol *s = cx->dis_symbols;
for(int i = 0; i < cx->dis_symbolN; i++)
if(s[i].type == 'L' && s[i].name && str_eq(s[i].name, "main"))
mainaddr = s[i].address;
if(mainaddr >= 0)
for(int i = 0; i < cx->dis_jumpcallN; i++)
if(cx->dis_jumpcalls[i].to == mainaddr)
return 1;
return 0;
}
static void set_context(Op_context *oxp, const char *buf, int pos, int buflen, int addr, int leadin, int leadout) {
// Compute initial context structure: the opcode before and the following one
oxp->from = disasm_wrap(pos + addr);
oxp->is_func = 0; // Next Z-opcode ahead is an icall/eicall
oxp->is_lpm = 0; // Next Z-opcode ahead is a lpm/elpm
oxp->preop = pos + leadin > 1? buf2op16(pos - 2): -1;
oxp->postop = -1;
oxp->zwd = 0; // 2: next Z-opcode ahead uses Z as word addr, 1: as byte addr
int k = 0, op16, i = pos + op_width(buf2op16(pos));
if(i < buflen + leadout - 1) {
AVR_mnemo z = 0;
oxp->postop = op16 = buf2op16(i);
// Check whether there is an opcode ahead that uses the Z register
for(k = 0, i += op_width(op16); k < 6 && i < buflen + leadout - 1; k++, i += op_width(op16)) {
if(op16_is_mnemo(op16, MNEMO_rjmp) || op16_is_mnemo(op16, MNEMO_jmp) ||
op16_is_mnemo(op16, MNEMO_ret) || op16_is_mnemo(op16, MNEMO_reti) ||
op16_is_mnemo(op16, MNEMO_u_ret) || op16_is_mnemo(op16, MNEMO_u_reti) ||
(oxp->zwd = z_width((op16 = buf2op16(i)), &z))) {
break;
}
}
if(oxp->zwd == 2)
oxp->is_func = z == MNEMO_icall || z == MNEMO_eicall || z == MNEMO_u_icall || z == MNEMO_u_eicall;
else
oxp->is_lpm = z >= MNEMO_lpm_0 && z <= MNEMO_elpm_zp;
}
}
/*
* Disassemble buflen bytes at buf which corresponds to address addr
*
* - Caller is responsible that buflen does not split an opcode
* - Before(!) the location buf there are leadin bytes available (0-2)
* - After the location buf+readlen there are leadout bytes available (0-16)
*/
int disasm(const char *buf, int buflen, int addr, int leadin, int leadout) {
int pos, opcode, mnemo, oplen;
Dis_line line = { 0 };
Op_context ox = { 0 };
for(int i = 0; i < cx->dis_symbolN; i++) // Clear used/printed state of symbols
cx->dis_symbols[i].used = cx->dis_symbols[i].printed = 0;
cx->dis_start = addr, cx->dis_end = addr + buflen - 1;
// Two bits in int array per word address indicate whether addr is jumpable/callable
cx->dis_jcaddr = mmt_malloc(((buflen + 7)/8 + sizeof(int)-1)/sizeof(int)*sizeof(int));
set_address(0, jumpable); // Mark reset as potential rjmp destination
// Make two passes: the first gathers labels, the second outputs the assembler code
for(cx->dis_pass = 1; cx->dis_pass < 3; cx->dis_pass++) {
if(cx->dis_pass == 2) {
cx->dis_para = 0;
set_labels();
if(cx->dis_opts.avrgcc_style)
emit_used_symbols();
if(cx->dis_opts.gcc_source) {
cx->dis_para++;
disasm_out("%*s.text\n%s", codecol(), "", have_own_main()? "": "main:\n");
cx->dis_para = -1;
}
}
for(pos = 0; pos < buflen; pos += oplen) {
// Check if this is actually code or maybe only data from tagfile
if((oplen = process_data(buf, buflen, pos, addr))) {
cx->dis_para++;
continue;
}
if(pos & 1) { // Last of PGM data items left off at odd address
oplen = process_num(buf, buflen, TYPE_BYTE, 1, 0, pos, addr);
continue;
}
opcode = buf2op16(pos);
mnemo = opcode_mnemo(opcode, cx->dis_opts.avrlevel);
oplen = mnemo < 0? 2: 2*avr_opcodes[mnemo].nwords;
if(op16_is_mnemo(opcode, MNEMO_ldi))
set_context(&ox, buf, pos, buflen, addr, leadin, leadout);
disassemble(buf + pos, disasm_wrap(pos + addr), opcode, mnemo, &ox, &line);
lineout(line.code, line.comment, mnemo, oplen, buf, pos, addr, 1);
if(cx->dis_pass == 1) // Mark this position as potential jump/call destination
set_address(pos + cx->dis_start, jumpable);
}
}
mmt_free(cx->dis_jcaddr);
cx->dis_jcaddr = NULL;
return 0;
}
// Should be called once per terminal session
int disasm_init(const AVRPART *p) {
AVRMEM *mem;
// Sanity check (problems only occur if avr_opcodes was changed)
for(size_t i = 0; i < sizeof avr_opcodes/sizeof *avr_opcodes; i++)
if(avr_opcodes[i].mnemo != (AVR_mnemo) i) {
msg_error("avr_opcodes[] table broken (this should never happen)\n");
return -1;
}
cx->dis_flashsz = 0; // Flash size
cx->dis_flashsz2 = 0; // Flash size rounded up to next power of two
cx->dis_addrwidth = 4; // Number of hex digits needed for flash addresses
cx->dis_sramwidth = 4; // Number of hex digits needed for sram addresses
cx->dis_codewidth = 28; // Width of the code column (eg, ldi r17, 0x32)
if((mem = avr_locate_flash(p)) && mem->size > 1) {
int nbits = intlog2(mem->size - 1) + 1;
cx->dis_flashsz = mem->size;
cx->dis_flashsz2 = 1 << nbits;
cx->dis_addrwidth = (nbits + 3)/4;
}
if((mem = avr_locate_sram(p)) && mem->size > 1) {
int size = mem->size;
if(mem->offset > 0 && mem->offset <= 0x200)
size += mem->offset;
cx->dis_sramwidth = (intlog2(size - 1) + 1 + 3)/4;
}
cx->dis_cycle_index = avr_get_cycle_index(p);
cx->dis_io_offset = (mem = avr_locate_io(p))? mem->offset: 0;
init_regfile(p);
return 0;
}