mirror of
https://github.com/avrdudes/avrdude.git
synced 2026-07-20 00:19:22 +03:00
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.
1373 lines
43 KiB
C
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;
|
|
}
|