rizin/librz/arch/isa/avr/avr_esil.c
wargio d47ceedbd3 Merge rz_asm and rz_analysis into one library but keep deprecated apis.
The tms320c64x has been merged into tms320.
2024-03-07 18:38:49 +08:00

1642 lines
44 KiB
C

// SPDX-FileCopyrightText: 2011-2019 pancake <pancake@nopcode.org>
// SPDX-FileCopyrightText: 2011-2019 Roc Valles <vallesroc@gmail.com>
// SPDX-FileCopyrightText: 2011-2019 condret <condr3t@protonmail.com>
// SPDX-FileCopyrightText: 2011-2019 killabyte <killabytenow@gmail.com>
// SPDX-FileCopyrightText: 2022 deroad <wargio@libero.it>
// SPDX-License-Identifier: LGPL-3.0-only
#include "avr_esil.h"
#include <rz_crypto.h>
/** \file avr_esil.c
* Converts AVR instructions into ESIL statements
*
* references:
* - http://www.atmel.com/images/atmel-0856-avr-instruction-set-manual.pdf
* - https://en.wikipedia.org/wiki/Atmel_AVR_instruction_set
*/
typedef struct _cpu_const_tag {
const char *const key;
ut8 type;
ut32 value;
ut8 size;
} CPU_CONST;
#define CPU_CONST_NONE 0
#define CPU_CONST_PARAM 1
#define CPU_CONST_REG 2
typedef struct _cpu_model_tag {
const char *const model;
int pc;
char *inherit;
struct _cpu_model_tag *inherit_cpu_p;
CPU_CONST *consts[10];
} CPU_MODEL;
typedef void (*inst_handler_t)(RzAnalysis *analysis, RzAnalysisOp *op, const ut8 *buf, int len, int *fail, CPU_MODEL *cpu);
typedef struct _opcodes_tag_ {
const char *const name;
int mask;
int selector;
inst_handler_t handler;
int cycles;
int size;
ut64 type;
} OPCODE_DESC;
static OPCODE_DESC *avr_op_analyze(RzAnalysis *analysis, RzAnalysisOp *op, ut64 addr, const ut8 *buf, int len, CPU_MODEL *cpu);
#define CPU_MODEL_DECL(model, pc, consts) \
{ \
model, \
pc, \
consts \
}
#define MASK(bits) ((bits) == 32 ? 0xffffffff : (~((~((ut32)0)) << (bits))))
#define CPU_PC_MASK(cpu) MASK((cpu)->pc)
#define CPU_PC_SIZE(cpu) ((((cpu)->pc) >> 3) + ((((cpu)->pc) & 0x07) ? 1 : 0))
#define INST_HANDLER(OPCODE_NAME) static void _inst__##OPCODE_NAME(RzAnalysis *analysis, RzAnalysisOp *op, const ut8 *buf, int len, int *fail, CPU_MODEL *cpu)
#define INST_DECL(OP, M, SL, C, SZ, T) \
{ \
#OP, (M), (SL), _inst__##OP, (C), (SZ), RZ_ANALYSIS_OP_TYPE_##T \
}
#define INST_LAST \
{ "unknown", 0, 0, (void *)0, 2, 1, RZ_ANALYSIS_OP_TYPE_UNK }
#define INST_CALL(OPCODE_NAME) _inst__##OPCODE_NAME(analysis, op, buf, len, fail, cpu)
#define INST_INVALID \
{ \
*fail = 1; \
return; \
}
#define INST_ASSERT(x) \
{ \
if (!(x)) { \
INST_INVALID; \
} \
}
#define ESIL_A(e, ...) rz_strbuf_appendf(&op->esil, e, ##__VA_ARGS__)
#define STR_BEGINS(in, s) rz_str_ncasecmp(in, s, strlen(s))
// Following IO definitions are valid for:
// ATmega8
// ATmega88
CPU_CONST cpu_reg_common[] = {
{ "spl", CPU_CONST_REG, 0x3d, sizeof(ut8) },
{ "sph", CPU_CONST_REG, 0x3e, sizeof(ut8) },
{ "sreg", CPU_CONST_REG, 0x3f, sizeof(ut8) },
{ "spmcsr", CPU_CONST_REG, 0x37, sizeof(ut8) },
{ NULL, 0, 0, 0 },
};
CPU_CONST cpu_memsize_common[] = {
{ "eeprom_size", CPU_CONST_PARAM, 512, sizeof(ut32) },
{ "io_size", CPU_CONST_PARAM, 0x40, sizeof(ut32) },
{ "sram_start", CPU_CONST_PARAM, 0x60, sizeof(ut32) },
{ "sram_size", CPU_CONST_PARAM, 1024, sizeof(ut32) },
{ NULL, 0, 0, 0 },
};
CPU_CONST cpu_memsize_m640_m1280m_m1281_m2560_m2561[] = {
{ "eeprom_size", CPU_CONST_PARAM, 512, sizeof(ut32) },
{ "io_size", CPU_CONST_PARAM, 0x1ff, sizeof(ut32) },
{ "sram_start", CPU_CONST_PARAM, 0x200, sizeof(ut32) },
{ "sram_size", CPU_CONST_PARAM, 0x2000, sizeof(ut32) },
{ NULL, 0, 0, 0 },
};
CPU_CONST cpu_memsize_xmega128a4u[] = {
{ "eeprom_size", CPU_CONST_PARAM, 0x800, sizeof(ut32) },
{ "io_size", CPU_CONST_PARAM, 0x1000, sizeof(ut32) },
{ "sram_start", CPU_CONST_PARAM, 0x800, sizeof(ut32) },
{ "sram_size", CPU_CONST_PARAM, 0x2000, sizeof(ut32) },
{ NULL, 0, 0, 0 },
};
CPU_CONST cpu_pagesize_5_bits[] = {
{ "page_size", CPU_CONST_PARAM, 5, sizeof(ut8) },
{ NULL, 0, 0, 0 },
};
CPU_CONST cpu_pagesize_7_bits[] = {
{ "page_size", CPU_CONST_PARAM, 7, sizeof(ut8) },
{ NULL, 0, 0, 0 },
};
CPU_MODEL cpu_models[] = {
{
.model = "ATmega640",
.pc = 15,
.consts = {
cpu_reg_common,
cpu_memsize_m640_m1280m_m1281_m2560_m2561,
cpu_pagesize_7_bits,
NULL },
},
{ .model = "ATxmega128a4u", .pc = 17, .consts = { cpu_reg_common, cpu_memsize_xmega128a4u, cpu_pagesize_7_bits, NULL } },
{ .model = "ATmega1280", .pc = 16, .inherit = "ATmega640" },
{ .model = "ATmega1281", .pc = 16, .inherit = "ATmega640" },
{ .model = "ATmega2560", .pc = 17, .inherit = "ATmega640" },
{ .model = "ATmega2561", .pc = 17, .inherit = "ATmega640" },
{ .model = "ATmega88", .pc = 8, .inherit = "ATmega8" },
// CPU_MODEL_DECL ("ATmega168", 13, 512, 512),
// last model is the default AVR - ATmega8 forever!
{ .model = "ATmega8", .pc = 13, .consts = { cpu_reg_common, cpu_memsize_common, cpu_pagesize_5_bits, NULL } },
};
static CPU_MODEL *get_cpu_model(char *model);
static CPU_MODEL *__get_cpu_model_recursive(char *model) {
if (!model) {
return &cpu_models[0];
}
CPU_MODEL *cpu = NULL;
for (cpu = cpu_models; cpu < cpu_models + ((sizeof(cpu_models) / sizeof(CPU_MODEL))) - 1; cpu++) {
if (!rz_str_casecmp(model, cpu->model)) {
break;
}
}
// fix inheritance tree
if (cpu->inherit && !cpu->inherit_cpu_p) {
cpu->inherit_cpu_p = get_cpu_model(cpu->inherit);
if (!cpu->inherit_cpu_p) {
RZ_LOG_ERROR("Cannot inherit from unknown CPU model '%s'.\n", cpu->inherit);
}
}
return cpu;
}
static CPU_MODEL *get_cpu_model(char *model) {
if (!model) {
return &cpu_models[0];
}
static CPU_MODEL *cpu = NULL;
// cached value?
if (cpu && !rz_str_casecmp(model, cpu->model)) {
return cpu;
}
// do the real search
cpu = __get_cpu_model_recursive(model);
return cpu;
}
static ut32 const_get_value(CPU_CONST *c) {
return c ? MASK(c->size * 8) & c->value : 0;
}
static CPU_CONST *const_by_name(CPU_MODEL *cpu, int type, char *c) {
CPU_CONST **clist, *citem;
for (clist = cpu->consts; *clist; clist++) {
for (citem = *clist; citem->key; citem++) {
if (!strcmp(c, citem->key) && (type == CPU_CONST_NONE || type == citem->type)) {
return citem;
}
}
}
if (cpu->inherit_cpu_p) {
return const_by_name(cpu->inherit_cpu_p, type, c);
}
RZ_LOG_ERROR("Cannot find const key[%s].\n", c);
return NULL;
}
static int __esil_pop_argument(RzAnalysisEsil *esil, ut64 *v) {
char *t = rz_analysis_esil_pop(esil);
if (!t || !rz_analysis_esil_get_parm(esil, t, v)) {
free(t);
return false;
}
free(t);
return true;
}
static CPU_CONST *const_by_value(CPU_MODEL *cpu, int type, ut32 v) {
CPU_CONST **clist, *citem;
for (clist = cpu->consts; *clist; clist++) {
for (citem = *clist; citem && citem->key; citem++) {
if (citem->value == (MASK(citem->size * 8) & v) && (type == CPU_CONST_NONE || type == citem->type)) {
return citem;
}
}
}
if (cpu->inherit_cpu_p) {
return const_by_value(cpu->inherit_cpu_p, type, v);
}
return NULL;
}
static RzStrBuf *__generic_io_dest(ut8 port, int write, CPU_MODEL *cpu) {
RzStrBuf *r = rz_strbuf_new("");
CPU_CONST *c = const_by_value(cpu, CPU_CONST_REG, port);
if (c != NULL) {
rz_strbuf_set(r, c->key);
if (write) {
rz_strbuf_append(r, ",=");
}
} else {
rz_strbuf_setf(r, "_io,%d,+,%s[1]", port, write ? "=" : "");
}
return r;
}
static void __generic_ld_st(RzAnalysisOp *op, char *mem, char ireg, int use_ramp, int prepostdec, int offset, int st) {
if (ireg) {
// preincrement index register
if (prepostdec < 0) {
ESIL_A("1,%c,-,%c,=,", ireg, ireg);
}
// set register index address
ESIL_A("%c,", ireg);
// add offset
if (offset != 0) {
ESIL_A("%d,+,", offset);
}
} else {
ESIL_A("%d,", offset);
}
if (use_ramp) {
ESIL_A("16,ramp%c,<<,+,", ireg ? ireg : 'd');
}
// set SRAM base address
ESIL_A("_%s,+,", mem);
// read/write from SRAM
ESIL_A("%s[1],", st ? "=" : "");
// postincrement index register
if (ireg && prepostdec > 0) {
ESIL_A("1,%c,+,%c,=,", ireg, ireg);
}
}
static void __generic_pop(RzAnalysisOp *op, int sz) {
if (sz > 1) {
ESIL_A("1,sp,+,_ram,+,"); // calc SRAM(sp+1)
ESIL_A("[%d],", sz); // read value
ESIL_A("%d,sp,+=,", sz); // sp += item_size
} else {
ESIL_A("1,sp,+=," // increment stack pointer
"sp,_ram,+,[1],"); // load SRAM[sp]
}
}
static void __generic_push(RzAnalysisOp *op, int sz) {
ESIL_A("sp,_ram,+,"); // calc pointer SRAM(sp)
if (sz > 1) {
ESIL_A("-%d,+,", sz - 1); // dec SP by 'sz'
}
ESIL_A("=[%d],", sz); // store value in stack
ESIL_A("-%d,sp,+=,", sz); // decrement stack pointer
}
INST_HANDLER(adc) { // ADC Rd, Rr
// ROL Rd
if (len < 2) {
return;
}
const ut32 d = ((buf[0] >> 4) & 0xf) | ((buf[1] & 1) << 4);
const ut32 r = (buf[0] & 0xf) | ((buf[1] & 2) << 3);
ESIL_A("r%d,cf,+,r%d,+=,", r, d); // Rd + Rr + C
ESIL_A("$z,zf,:=,");
ESIL_A("3,$c,hf,:=,");
ESIL_A("7,$c,cf,:=,");
ESIL_A("7,$o,vf,:=,");
ESIL_A("0x80,r%d,&,!,!,nf,:=", d);
}
INST_HANDLER(add) { // ADD Rd, Rr
// LSL Rd
if (len < 2) {
return;
}
const ut32 d = ((buf[0] >> 4) & 0xf) | ((buf[1] & 1) << 4);
const ut32 r = (buf[0] & 0xf) | ((buf[1] & 2) << 3);
ESIL_A("r%d,r%d,+=,", r, d); // Rd + Rr
ESIL_A("$z,zf,:=,");
ESIL_A("3,$c,hf,:=,");
ESIL_A("7,$c,cf,:=,");
ESIL_A("7,$o,vf,:=,");
ESIL_A("0x80,r%d,&,!,!,nf,:=,", d);
}
INST_HANDLER(adiw) { // ADIW Rd+1:Rd, K
if (len < 1) {
return;
}
const ut32 d = ((buf[0] & 0x30) >> 3) + 24;
const ut32 k = (buf[0] & 0x0f) | ((buf[0] >> 2) & 0x30);
ESIL_A("7,r%d,>>,", d + 1); // remember previous highest bit
ESIL_A("8,%d,8,r%d,<<,r%d,|,+,DUP,r%d,=,>>,r%d,=,", k, d + 1, d, d, d + 1); // Rd+1_Rd + k
// FLAGS:
ESIL_A("DUP,!,7,r%d,>>,&,vf,:=,", d + 1); // V
ESIL_A("r%d,0x80,&,!,!,nf,:=,", d + 1); // N
ESIL_A("8,r%d,<<,r%d,|,!,zf,:=,", d + 1, d); // Z
ESIL_A("7,r%d,>>,!,&,cf,:=,", d + 1); // C
ESIL_A("vf,nf,^,sf,:="); // S
}
INST_HANDLER(and) { // AND Rd, Rr
// TST Rd
if (len < 2) {
return;
}
const ut32 d = ((buf[0] >> 4) & 0xf) | ((buf[1] & 1) << 4);
const ut32 r = (buf[0] & 0xf) | ((buf[1] & 2) << 3);
ESIL_A("r%d,r%d,&=,$z,zf,:=,r%d,0x80,&,!,!,nf,:=,0,vf,:=,nf,sf,:=,", r, d, d);
}
INST_HANDLER(andi) { // ANDI Rd, K
// CBR Rd, K (= ANDI Rd, 1-K)
if (len < 2) {
return;
}
const ut32 d = ((buf[0] >> 4) & 0xf) + 16;
const ut32 k = ((buf[1] & 0x0f) << 4) | (buf[0] & 0x0f);
ESIL_A("%d,r%d,&=,$z,zf,:=,r%d,0x80,&,!,!,nf,:=,0,vf,:=,nf,sf,:=,", k, d, d);
}
INST_HANDLER(asr) { // ASR Rd
if (len < 2) {
return;
}
int d = ((buf[0] >> 4) & 0xf) | ((buf[1] & 1) << 4);
ESIL_A("r%d,0x1,&,cf,:=,0x1,r%d,>>,r%d,0x80,&,|,", d, d, d);
// 0: R=(Rd >> 1) | Rd7
ESIL_A("$z,zf,:=,"); // Z
ESIL_A("r%d,0x80,&,!,!,nf,:=,", d); // N
ESIL_A("nf,cf,^,vf,:=,"); // V
ESIL_A("nf,vf,^,sf,:=,"); // S
}
INST_HANDLER(bclr) { // BCLR s
// CLC
// CLH
// CLI
// CLN
// CLR
// CLS
// CLT
// CLV
// CLZ
if (len < 1) {
return;
}
int s = (buf[0] >> 4) & 0x7;
ESIL_A("0xff,%d,1,<<,^,sreg,&=,", s);
}
INST_HANDLER(bld) { // BLD Rd, b
if (len < 2) {
return;
}
int d = ((buf[1] & 0x01) << 4) | ((buf[0] >> 4) & 0xf);
int b = buf[0] & 0x7;
ESIL_A("r%d,%d,1,<<,0xff,^,&,", d, b); // Rd/b = 0
ESIL_A("%d,tf,<<,|,r%d,=,", b, d); // Rd/b |= T<<b
}
INST_HANDLER(brbx) { // BRBC s, k
// BRBS s, k
// BRBC/S 0: BRCC BRCS
// BRSH BRLO
// BRBC/S 1: BREQ BRNE
// BRBC/S 2: BRPL BRMI
// BRBC/S 3: BRVC BRVS
// BRBC/S 4: BRGE BRLT
// BRBC/S 5: BRHC BRHS
// BRBC/S 6: BRTC BRTS
// BRBC/S 7: BRID BRIE
if (len < 2) {
return;
}
int s = buf[0] & 0x7;
ut64 jump = op->addr + ((((buf[1] & 0x03) << 6) | ((buf[0] & 0xf8) >> 2)) | (buf[1] & 0x2 ? ~((int)0x7f) : 0)) + 2;
ESIL_A("%d,1,<<,sreg,&,", s); // SREG(s)
ESIL_A(buf[1] & 0x4
? "!," // BRBC => branch if cleared
: "!,!,"); // BRBS => branch if set
ESIL_A("?{,%" PFMT64d ",pc,=,},", jump); // ?true => jmp
}
INST_HANDLER(break) { // BREAK
ESIL_A("BREAK");
}
INST_HANDLER(bset) { // BSET s
// SEC
// SEH
// SEI
// SEN
// SER
// SES
// SET
// SEV
// SEZ
if (len < 1) {
return;
}
int s = (buf[0] >> 4) & 0x7;
ESIL_A("%d,1,<<,sreg,|=,", s);
}
INST_HANDLER(bst) { // BST Rd, b
if (len < 2) {
return;
}
ESIL_A("r%d,%d,1,<<,&,!,!,tf,=,", // tf = Rd/b
((buf[1] & 1) << 4) | ((buf[0] >> 4) & 0xf), // r
buf[0] & 0x7); // b
}
INST_HANDLER(call) { // CALL k
if (len < 4) {
return;
}
ut64 jump = (buf[2] << 1) | (buf[3] << 9) | (buf[1] & 0x01) << 23 | (buf[0] & 0x01) << 17 | (buf[0] & 0xf0) << 14;
ESIL_A("pc,"); // esil is already pointing to
// next instruction (@ret)
__generic_push(op, CPU_PC_SIZE(cpu)); // push @ret in stack
ESIL_A("%" PFMT64d ",pc,=,", jump); // jump!
}
INST_HANDLER(cbi) { // CBI A, b
if (len < 1) {
return;
}
int a = (buf[0] >> 3) & 0x1f;
int b = buf[0] & 0x07;
RzStrBuf *io_port;
// read port a and clear bit b
io_port = __generic_io_dest(a, 0, cpu);
ESIL_A("0xff,%d,1,<<,^,%s,&,", b, rz_strbuf_get(io_port));
rz_strbuf_free(io_port);
// write result to port a
io_port = __generic_io_dest(a, 1, cpu);
ESIL_A("%s,", rz_strbuf_get(io_port));
rz_strbuf_free(io_port);
}
INST_HANDLER(com) { // COM Rd
if (len < 2) {
return;
}
int r = ((buf[0] >> 4) & 0x0f) | ((buf[1] & 1) << 4);
ESIL_A("r%d,0xff,-,r%d,=,$z,zf,:=,0,cf,:=,0,vf,:=,r%d,0x80,&,!,!,nf,:=,vf,nf,^,sf,:=", r, r, r);
// Rd = 0xFF-Rd
}
INST_HANDLER(cp) { // CP Rd, Rr
if (len < 2) {
return;
}
const ut32 r = (buf[0] & 0x0f) | ((buf[1] << 3) & 0x10);
const ut32 d = ((buf[0] >> 4) & 0x0f) | ((buf[1] << 4) & 0x10);
ESIL_A("r%d,r%d,-,0x80,&,!,!,nf,:=,", r, d);
ESIL_A("r%d,r%d,==,", r, d);
ESIL_A("$z,zf,:=,");
ESIL_A("3,$b,hf,:=,");
ESIL_A("8,$b,cf,:=,");
ESIL_A("7,$o,vf,:=,");
ESIL_A("vf,nf,^,sf,:=");
}
INST_HANDLER(cpc) { // CPC Rd, Rr
if (len < 2) {
return;
}
const ut32 r = (buf[0] & 0x0f) | ((buf[1] << 3) & 0x10);
const ut32 d = ((buf[0] >> 4) & 0x0f) | ((buf[1] << 4) & 0x10);
ESIL_A("cf,r%d,+,DUP,r%d,-,0x80,&,!,!,nf,:=,", r, d); // Rd - Rr - C
ESIL_A("r%d,==,", d);
ESIL_A("$z,zf,:=,");
ESIL_A("3,$b,hf,:=,");
ESIL_A("8,$b,cf,:=,");
ESIL_A("7,$o,vf,:=,");
ESIL_A("vf,nf,^,sf,:=");
}
INST_HANDLER(cpi) { // CPI Rd, K
if (len < 2) {
return;
}
const ut32 d = ((buf[0] >> 4) & 0xf) + 16;
const ut32 k = (buf[0] & 0xf) | ((buf[1] & 0xf) << 4);
ESIL_A("%d,r%d,-,0x80,&,!,!,nf,:=,", k, d); // Rd - k
ESIL_A("%d,r%d,==,", k, d);
ESIL_A("$z,zf,:=,");
ESIL_A("3,$b,hf,:=,");
ESIL_A("8,$b,cf,:=,");
ESIL_A("7,$o,vf,:=,");
ESIL_A("vf,nf,^,sf,:=");
}
INST_HANDLER(cpse) { // CPSE Rd, Rr
if (len < 2) {
return;
}
int r = (buf[0] & 0xf) | ((buf[1] & 0x2) << 3);
int d = ((buf[0] >> 4) & 0xf) | ((buf[1] & 0x1) << 4);
ESIL_A("r%d,r%d,^,!,", r, d); // Rr == Rd
ESIL_A("?{,%" PFMT64d ",pc,=,},", op->jump); // ?true => jmp
}
INST_HANDLER(dec) { // DEC Rd
if (len < 2) {
return;
}
const ut32 d = ((buf[0] >> 4) & 0xf) | ((buf[1] & 0x1) << 4);
ESIL_A("0x1,r%d,-=,", d); // Rd--
// FLAGS:
ESIL_A("7,$o,vf,:=,"); // V
ESIL_A("r%d,0x80,&,!,!,nf,:=,", d); // N
ESIL_A("$z,zf,:=,"); // Z
ESIL_A("vf,nf,^,sf,:=,"); // S
}
INST_HANDLER(des) { // DES k
int round = (buf[0] >> 4);
ESIL_A("%d,des", round);
}
INST_HANDLER(eijmp) { // EIJMP
ESIL_A("1,z,16,eind,<<,+,<<,pc,=,");
}
INST_HANDLER(eicall) { // EICALL
// push pc in stack
ESIL_A("pc,"); // esil is already pointing to
// next instruction (@ret)
__generic_push(op, CPU_PC_SIZE(cpu)); // push @ret in stack
// do a standard EIJMP
INST_CALL(eijmp);
}
INST_HANDLER(elpm) { // ELPM
// ELPM Rd
// ELPM Rd, Z+
if (len < 2) {
return;
}
int d = ((buf[1] & 0xfe) == 0x90)
? ((buf[1] & 1) << 4) | ((buf[0] >> 4) & 0xf) // Rd
: 0; // R0
ESIL_A("16,rampz,<<,z,+,_prog,+,[1],"); // read RAMPZ:Z
ESIL_A("r%d,=,", d); // Rd = [1]
if ((buf[1] & 0xfe) == 0x90 && (buf[0] & 0xf) == 0x7) {
ESIL_A("16,1,z,+,DUP,z,=,>>,1,&,rampz,+=,"); // ++(rampz:z)
}
}
INST_HANDLER(eor) { // EOR Rd, Rr
// CLR Rd
if (len < 2) {
return;
}
const ut32 d = ((buf[0] >> 4) & 0xf) | ((buf[1] & 1) << 4);
const ut32 r = (buf[0] & 0xf) | ((buf[1] & 2) << 3);
ESIL_A("r%d,r%d,^=,$z,zf,:=,0,vf,:=,r%d,0x80,&,!,!,nf,:=,nf,sf,:=", r, d, d);
// 0: Rd ^= Rr
}
INST_HANDLER(fmul) { // FMUL Rd, Rr
if (len < 1) {
return;
}
const ut32 d = ((buf[0] >> 4) & 0x7) + 16;
const ut32 r = (buf[0] & 0x7) + 16;
ESIL_A("8,");
ESIL_A("0xffff,1,r%d,r%d,*,<<,&,DUP,r0,=,>>,r1,=,", r, d); // 0: r1_r0 = (rd * rr) << 1
ESIL_A("8,r1,<<,r0,|,DUP,0x8000,&,!,!,cf,:=,"); // C = R/15
ESIL_A("!,zf,:="); // Z = !R
}
INST_HANDLER(fmuls) { // FMULS Rd, Rr
if (len < 1) {
return;
}
const ut32 d = ((buf[0] >> 4) & 0x7) + 16;
const ut32 r = (buf[0] & 0x7) + 16;
ESIL_A("8,1,");
ESIL_A("r%d,DUP,0x80,&,?{,0xff00,|,},", d); // sign extension Rd
ESIL_A("r%d,DUP,0x80,&,?{,0xff00,|,},", r); // sign extension Rr
ESIL_A("*,<<,DUP,r0,=,>>,r1,=,"); // 0: (Rd*Rr)<<1
ESIL_A("8,r1,<<,r0,|,DUP,0x8000,&,!,!,cf,:=,"); // C = R/16
ESIL_A("!,zf,:="); // Z = !R
}
INST_HANDLER(fmulsu) { // FMULSU Rd, Rr
if (len < 1) {
return;
}
const ut32 d = ((buf[0] >> 4) & 0x7) + 16;
const ut32 r = (buf[0] & 0x7) + 16;
ESIL_A("8,1,");
ESIL_A("r%d,DUP,0x80,&,?{,0xff00,|,},", d); // sign extension Rd
ESIL_A("r%d,*,<<,DUP,r0,=,>>,r1,=,", r); // 0: (Rd*Rr)<<1
ESIL_A("8,r1,<<,r0,|,DUP,0x8000,&,!,!,cf,:=,"); // C = R/16
ESIL_A("!,zf,:="); // Z = !R
}
INST_HANDLER(ijmp) { // IJMP k
// read z for calculating jump address on runtime
ESIL_A("1,z,<<,pc,=,"); // jump!
}
INST_HANDLER(icall) { // ICALL k
// push pc in stack
ESIL_A("pc,"); // esil is already pointing to
// next instruction (@ret)
__generic_push(op, CPU_PC_SIZE(cpu)); // push @ret in stack
// do a standard IJMP
INST_CALL(ijmp);
}
INST_HANDLER(in) { // IN Rd, A
if (len < 2) {
return;
}
int r = ((buf[0] >> 4) & 0x0f) | ((buf[1] & 0x01) << 4);
int a = (buf[0] & 0x0f) | ((buf[1] & 0x6) << 3);
RzStrBuf *io_src = __generic_io_dest(a, 0, cpu);
ESIL_A("%s,r%d,=,", rz_strbuf_get(io_src), r);
rz_strbuf_free(io_src);
}
INST_HANDLER(inc) { // INC Rd
if (len < 2) {
return;
}
const ut32 d = ((buf[0] >> 4) & 0xf) | ((buf[1] & 0x1) << 4);
ESIL_A("1,r%d,+=,", d); // Rd++
// FLAGS:
ESIL_A("7,$o,vf,:=,"); // V
ESIL_A("r%d,0x80,&,!,!,nf,:=,", d); // N
ESIL_A("$z,zf,:=,"); // Z
ESIL_A("vf,nf,^,sf,:=,"); // S
}
INST_HANDLER(jmp) { // JMP k
if (len < 4) {
return;
}
ut64 jump = (buf[2] << 1) | (buf[3] << 9) | (buf[1] & 0x01) << 23 | (buf[0] & 0x01) << 17 | (buf[0] & 0xf0) << 14;
ESIL_A("%" PFMT64d ",pc,=,", jump); // jump!
}
INST_HANDLER(lac) { // LAC Z, Rd
if (len < 2) {
return;
}
int d = ((buf[0] >> 4) & 0xf) | ((buf[1] & 0x1) << 4);
// read memory from RAMPZ:Z
__generic_ld_st(op, "ram", 'z', 1, 0, 0, 0); // 0: Read (RAMPZ:Z)
ESIL_A("r%d,0xff,^,&,", d); // 0: (Z) & ~Rd
ESIL_A("DUP,r%d,=,", d); // Rd = [0]
__generic_ld_st(op, "ram", 'z', 1, 0, 0, 1); // Store in RAM
}
INST_HANDLER(las) { // LAS Z, Rd
if (len < 2) {
return;
}
int d = ((buf[0] >> 4) & 0xf) | ((buf[1] & 0x1) << 4);
// read memory from RAMPZ:Z
__generic_ld_st(op, "ram", 'z', 1, 0, 0, 0); // 0: Read (RAMPZ:Z)
ESIL_A("r%d,|,", d); // 0: (Z) | Rd
ESIL_A("DUP,r%d,=,", d); // Rd = [0]
__generic_ld_st(op, "ram", 'z', 1, 0, 0, 1); // Store in RAM
}
INST_HANDLER(lat) { // LAT Z, Rd
if (len < 2) {
return;
}
int d = ((buf[0] >> 4) & 0xf) | ((buf[1] & 0x1) << 4);
// read memory from RAMPZ:Z
__generic_ld_st(op, "ram", 'z', 1, 0, 0, 0); // 0: Read (RAMPZ:Z)
ESIL_A("r%d,^,", d); // 0: (Z) ^ Rd
ESIL_A("DUP,r%d,=,", d); // Rd = [0]
__generic_ld_st(op, "ram", 'z', 1, 0, 0, 1); // Store in RAM
}
INST_HANDLER(ld) { // LD Rd, X
// LD Rd, X+
// LD Rd, -X
if (len < 2) {
return;
}
// read memory
__generic_ld_st(
op, "ram",
'x', // use index register X
0, // no use RAMP* registers
(buf[0] & 0xf) == 0xe
? -1 // pre decremented
: (buf[0] & 0xf) == 0xd
? 1 // post incremented
: 0, // no increment
0, // offset always 0
0); // load operation (!st)
// load register
ESIL_A("r%d,=,", ((buf[1] & 1) << 4) | ((buf[0] >> 4) & 0xf));
}
INST_HANDLER(ldd) { // LD Rd, Y LD Rd, Z
// LD Rd, Y+ LD Rd, Z+
// LD Rd, -Y LD Rd, -Z
// LD Rd, Y+q LD Rd, Z+q
if (len < 2) {
return;
}
// calculate offset (this value only has sense in some opcodes,
// but we are optimistic and we calculate it always)
int offset = (buf[1] & 0x20) | ((buf[1] & 0xc) << 1) | (buf[0] & 0x7);
// read memory
__generic_ld_st(
op, "ram",
buf[0] & 0x8 ? 'y' : 'z', // index register Y/Z
0, // no use RAMP* registers
!(buf[1] & 0x10)
? 0 // no increment
: buf[0] & 0x1
? 1 // post incremented
: -1, // pre decremented
!(buf[1] & 0x10) ? offset : 0, // offset or not offset
0); // load operation (!st)
// load register
ESIL_A("r%d,=,", ((buf[1] & 1) << 4) | ((buf[0] >> 4) & 0xf));
}
INST_HANDLER(ldi) { // LDI Rd, K
if (len < 2) {
return;
}
int k = (buf[0] & 0xf) + ((buf[1] & 0xf) << 4);
int d = ((buf[0] >> 4) & 0xf) + 16;
ESIL_A("0x%x,r%d,=,", k, d);
}
INST_HANDLER(lds) { // LDS Rd, k
if (len < 4) {
return;
}
int d = ((buf[0] >> 4) & 0xf) | ((buf[1] & 0x1) << 4);
int k = (buf[3] << 8) | buf[2];
// load value from RAMPD:k
__generic_ld_st(op, "ram", 0, 1, 0, k, 0);
ESIL_A("r%d,=,", d);
}
INST_HANDLER(sts) { // STS k, Rr
if (len < 4) {
return;
}
int r = ((buf[0] >> 4) & 0xf) | ((buf[1] & 0x1) << 4);
int k = (buf[3] << 8) | buf[2];
ESIL_A("r%d,", r);
__generic_ld_st(op, "ram", 0, 1, 0, k, 1);
}
INST_HANDLER(lpm) { // LPM
// LPM Rd, Z
// LPM Rd, Z+
if (len < 2) {
return;
}
ut16 ins = (((ut16)buf[1]) << 8) | ((ut16)buf[0]);
// read program memory
__generic_ld_st(
op, "prog",
'z', // index register Y/Z
1, // use RAMP* registers
(ins & 0xfe0f) == 0x9005
? 1 // post incremented
: 0, // no increment
0, // not offset
0); // load operation (!st)
// load register
ESIL_A("r%d,=,",
(ins == 0x95c8)
? 0 // LPM (r0)
: ((buf[0] >> 4) & 0xf) // LPM Rd
| ((buf[1] & 0x1) << 4));
}
INST_HANDLER(lsr) { // LSR Rd
if (len < 2) {
return;
}
const ut32 d = ((buf[0] >> 4) & 0xf) | ((buf[1] & 1) << 4);
ESIL_A("r%d,0x1,&,cf,:=,", d); // C = Rd0
ESIL_A("1,r%d,>>=,", d); // 0: R=(Rd >> 1)
ESIL_A("$z,zf,:=,"); // Z
ESIL_A("0,nf,:=,"); // N
ESIL_A("cf,vf,:=,"); // V
ESIL_A("cf,sf,:=,"); // S
}
INST_HANDLER(mov) { // MOV Rd, Rr
if (len < 2) {
return;
}
const ut32 d = ((buf[1] << 4) & 0x10) | ((buf[0] >> 4) & 0x0f);
const ut32 r = ((buf[1] << 3) & 0x10) | (buf[0] & 0x0f);
ESIL_A("r%d,r%d,=,", r, d);
}
INST_HANDLER(movw) { // MOVW Rd+1:Rd, Rr+1:Rr
if (len < 1) {
return;
}
const ut32 d = (buf[0] & 0xf0) >> 3;
const ut32 r = (buf[0] & 0x0f) << 1;
ESIL_A("r%d,r%d,=,r%d,r%d,=,", r, d, r + 1, d + 1);
}
INST_HANDLER(mul) { // MUL Rd, Rr
if (len < 2) {
return;
}
const ut32 d = ((buf[1] << 4) & 0x10) | ((buf[0] >> 4) & 0x0f);
const ut32 r = ((buf[1] << 3) & 0x10) | (buf[0] & 0x0f);
ESIL_A("8,r%d,r%d,*,DUP,r0,=,>>,r1,=,", r, d); // 0: r1_r0 = rd * rr
ESIL_A("8,r1,<<,r0,|,DUP,0x8000,&,!,!,cf,:=,"); // C = R/15
ESIL_A("!,zf,:="); // Z = !R
}
INST_HANDLER(muls) { // MULS Rd, Rr
if (len < 1) {
return;
}
const ut32 d = (buf[0] >> 4 & 0x0f) + 16;
const ut32 r = (buf[0] & 0x0f) + 16;
ESIL_A("8,");
ESIL_A("r%d,DUP,0x80,&,?{,0xff00,|,},", d); // sign extension Rd
ESIL_A("r%d,DUP,0x80,&,?{,0xff00,|,},", r); // sign extension Rr
ESIL_A("*,DUP,r0,=,>>,r1,=,"); // 0: (Rd*Rr)
ESIL_A("8,r1,<<,r0,|,DUP,0x8000,&,!,!,cf,:=,"); // C = R/16
ESIL_A("!,zf,:="); // Z = !R
}
INST_HANDLER(mulsu) { // MULSU Rd, Rr
if (len < 1) {
return;
}
const ut32 d = (buf[0] >> 4 & 0x07) + 16;
const ut32 r = (buf[0] & 0x07) + 16;
ESIL_A("8,");
ESIL_A("r%d,DUP,0x80,&,?{,0xff00,|,},", d); // sign extension Rd
ESIL_A("r%d,*,DUP,r0,=,>>,r1,=,", r); // 0: (Rd*Rr)
ESIL_A("8,r1,<<,r0,|,DUP,0x8000,&,!,!,cf,:=,"); // C = R/16
ESIL_A("!,zf,:="); // Z = !R
}
INST_HANDLER(neg) { // NEG Rd
if (len < 2) {
return;
}
int d = ((buf[0] >> 4) & 0xf) | ((buf[1] & 1) << 4);
ESIL_A("r%d,0x00,-,0xff,&,", d); // 0: (0-Rd)
ESIL_A("DUP,r%d,0xff,^,|,0x08,&,!,!,hf,=,", d); // H
ESIL_A("DUP,0x80,-,!,vf,=,"); // V
ESIL_A("DUP,0x80,&,!,!,nf,=,"); // N
ESIL_A("DUP,!,zf,=,"); // Z
ESIL_A("DUP,!,!,cf,=,"); // C
ESIL_A("vf,nf,^,sf,=,"); // S
ESIL_A("r%d,=,", d); // Rd = result
}
INST_HANDLER(nop) { // NOP
ESIL_A(",,");
}
INST_HANDLER(or) { // OR Rd, Rr
if (len < 2) {
return;
}
int d = ((buf[0] >> 4) & 0xf) | ((buf[1] & 1) << 4);
int r = (buf[0] & 0xf) | ((buf[1] & 2) << 3);
ESIL_A("r%d,r%d,|=,", r, d); // 0: (Rd | Rr)
ESIL_A("$z,zf,:=,"); // Z
ESIL_A("r%d,&,!,!,nf,:=,", d); // N
ESIL_A("0,vf,:=,"); // V
ESIL_A("nf,sf,:="); // S
}
INST_HANDLER(ori) { // ORI Rd, K
// SBR Rd, K
if (len < 2) {
return;
}
const ut32 d = ((buf[0] >> 4) & 0xf) + 16;
const ut32 k = (buf[0] & 0xf) | ((buf[1] & 0xf) << 4);
ESIL_A("%d,r%d,|=,", k, d); // 0: (Rd | k)
ESIL_A("$z,zf,:=,"); // Z
ESIL_A("r%d,0x80,&,!,!,nf,:=,", d); // N
ESIL_A("0,vf,:=,"); // V
ESIL_A("nf,sf,:="); // S
}
INST_HANDLER(out) { // OUT A, Rr
if (len < 2) {
return;
}
int r = ((buf[0] >> 4) & 0x0f) | ((buf[1] & 0x01) << 4);
int a = (buf[0] & 0x0f) | ((buf[1] & 0x6) << 3);
RzStrBuf *io_dst = __generic_io_dest(a, 1, cpu);
ESIL_A("r%d,%s,", r, rz_strbuf_get(io_dst));
rz_strbuf_free(io_dst);
}
INST_HANDLER(pop) { // POP Rd
if (len < 2) {
return;
}
int d = ((buf[1] & 0x1) << 4) | ((buf[0] >> 4) & 0xf);
__generic_pop(op, 1);
ESIL_A("r%d,=,", d); // store in Rd
}
INST_HANDLER(push) { // PUSH Rr
if (len < 2) {
return;
}
int r = ((buf[1] & 0x1) << 4) | ((buf[0] >> 4) & 0xf);
ESIL_A("r%d,", r); // load Rr
__generic_push(op, 1); // push it into stack
}
INST_HANDLER(rcall) { // RCALL k
if (len < 2) {
return;
}
// target address
ut64 jump = op->addr + ((((((buf[1] & 0xf) << 8) | buf[0]) << 1) | (((buf[1] & 0x8) ? ~((int)0x1fff) : 0))) + 2);
// esil
ESIL_A("pc,"); // esil already points to next
// instruction (@ret)
__generic_push(op, CPU_PC_SIZE(cpu)); // push @ret addr
ESIL_A("%" PFMT64d ",pc,=,", jump); // jump!
}
INST_HANDLER(ret) { // RET
// esil
__generic_pop(op, CPU_PC_SIZE(cpu));
ESIL_A("pc,=,"); // jump!
}
INST_HANDLER(reti) { // RETI
// first perform a standard 'ret'
INST_CALL(ret);
// RETI: The I-bit is cleared by hardware after an interrupt
// has occurred, and is set by the RETI instruction to enable
// subsequent interrupts
ESIL_A("1,if,=,");
}
INST_HANDLER(rjmp) { // RJMP k
st32 loc = (((((buf[1] & 0xf) << 9) | (buf[0] << 1))) | (buf[1] & 0x8 ? ~(0x1fff) : 0)) + 2;
ut64 jump = op->addr + loc;
ESIL_A("%" PFMT64d ",pc,=,", jump);
}
INST_HANDLER(ror) { // ROR Rd
const ut32 d = ((buf[0] >> 4) & 0x0f) | ((buf[1] << 4) & 0x10);
ESIL_A("cf,nf,:=,"); // N
ESIL_A("r%d,0x1,&,", d); // C
ESIL_A("1,r%d,>>,7,cf,<<,|,r%d,=,cf,:=,", d, d); // 0: (Rd>>1) | (cf<<7)
ESIL_A("$z,zf,:=,"); // Z
ESIL_A("nf,cf,^,vf,:=,"); // V
ESIL_A("vf,nf,^,sf,:="); // S
}
INST_HANDLER(sbc) { // SBC Rd, Rr
if (len < 2) {
return;
}
const ut32 r = (buf[0] & 0x0f) | ((buf[1] & 0x2) << 3);
const ut32 d = ((buf[0] >> 4) & 0xf) | ((buf[1] & 0x1) << 4);
ESIL_A("cf,r%d,+,r%d,-=,", r, d); // 0: (Rd-Rr-C)
ESIL_A("$z,zf,:=,");
ESIL_A("3,$b,hf,:=,");
ESIL_A("8,$b,cf,:=,");
ESIL_A("7,$o,vf,:=,");
ESIL_A("0x80,r%d,&,!,!,nf,:=,", d);
ESIL_A("vf,nf,^,sf,:=");
}
INST_HANDLER(sbci) { // SBCI Rd, k
if (len < 2) {
return;
}
const ut32 d = ((buf[0] >> 4) & 0xf) + 16;
const ut32 k = ((buf[1] & 0xf) << 4) | (buf[0] & 0xf);
ESIL_A("cf,%d,+,r%d,-=,", k, d); // 0: (Rd-k-C)
ESIL_A("$z,zf,:=,");
ESIL_A("3,$b,hf,:=,");
ESIL_A("8,$b,cf,:=,");
ESIL_A("7,$o,vf,:=,");
ESIL_A("0x80,r%d,&,!,!,nf,:=,", d);
ESIL_A("vf,nf,^,sf,:=");
}
INST_HANDLER(sub) { // SUB Rd, Rr
if (len < 2) {
return;
}
const ut32 d = ((buf[0] >> 4) & 0xf) | ((buf[1] & 1) << 4);
const ut32 r = (buf[0] & 0xf) | ((buf[1] & 2) << 3);
ESIL_A("r%d,r%d,-=,", r, d); // 0: (Rd-k)
ESIL_A("$z,zf,:=,");
ESIL_A("3,$b,hf,:=,");
ESIL_A("8,$b,cf,:=,");
ESIL_A("7,$o,vf,:=,");
ESIL_A("0x80,r%d,&,!,!,nf,:=,", d);
ESIL_A("vf,nf,^,sf,:=");
}
INST_HANDLER(subi) { // SUBI Rd, k
if (len < 2) {
return;
}
const ut32 d = ((buf[0] >> 4) & 0xf) + 16;
const ut32 k = ((buf[1] & 0xf) << 4) | (buf[0] & 0xf);
ESIL_A("%d,r%d,-=,", k, d); // 0: (Rd-k)
ESIL_A("$z,zf,:=,");
ESIL_A("3,$b,hf,:=,");
ESIL_A("8,$b,cf,:=,");
ESIL_A("7,$o,vf,:=,");
ESIL_A("0x80,r%d,&,!,!,nf,:=,", d);
ESIL_A("vf,nf,^,sf,:=");
}
INST_HANDLER(sbi) { // SBI A, b
if (len < 1) {
return;
}
int a = (buf[0] >> 3) & 0x1f;
int b = buf[0] & 0x07;
RzStrBuf *io_port;
// read port a and clear bit b
io_port = __generic_io_dest(a, 0, cpu);
ESIL_A("0xff,%d,1,<<,|,%s,&,", b, rz_strbuf_get(io_port));
rz_strbuf_free(io_port);
// write result to port a
io_port = __generic_io_dest(a, 1, cpu);
ESIL_A("%s,", rz_strbuf_get(io_port));
rz_strbuf_free(io_port);
}
INST_HANDLER(sbix) { // SBIC A, b
// SBIS A, b
if (len < 2) {
return;
}
int a = (buf[0] >> 3) & 0x1f;
int b = buf[0] & 0x07;
RzStrBuf *io_port;
// read port a and clear bit b
io_port = __generic_io_dest(a, 0, cpu);
ESIL_A("%d,1,<<,%s,&,", b, rz_strbuf_get(io_port)); // IO(A,b)
ESIL_A((buf[1] & 0xe) == 0xc
? "!," // SBIC => branch if 0
: "!,!,"); // SBIS => branch if 1
ESIL_A("?{,%" PFMT64d ",pc,=,},", op->jump); // ?true => jmp
rz_strbuf_free(io_port);
}
INST_HANDLER(sbiw) { // SBIW Rd+1:Rd, K
if (len < 1) {
return;
}
int d = ((buf[0] & 0x30) >> 3) + 24;
int k = (buf[0] & 0xf) | ((buf[0] >> 2) & 0x30);
ESIL_A("7,r%d,>>,", d + 1); // remember previous highest bit
ESIL_A("8,%d,8,r%d,<<,r%d,|,-,DUP,r%d,=,>>,r%d,=,", k, d + 1, d, d, d + 1); // 0(Rd+1_Rd - k)
ESIL_A("$z,zf,:=,");
ESIL_A("DUP,!,7,r%d,>>,&,cf,:=,", d + 1); // C
ESIL_A("r%d,0x80,&,!,!,nf,:=,", d + 1); // N
ESIL_A("7,r%d,>>,!,&,vf,:=,", d + 1); // V
ESIL_A("vf,nf,^,sf,:="); // S
}
INST_HANDLER(sbrx) { // SBRC Rr, b
// SBRS Rr, b
if (len < 2) {
return;
}
int b = buf[0] & 0x7;
int r = ((buf[0] >> 4) & 0xf) | ((buf[1] & 0x01) << 4);
ESIL_A("%d,1,<<,r%d,&,", b, r); // Rr(b)
ESIL_A((buf[1] & 0xe) == 0xc
? "!," // SBRC => branch if cleared
: "!,!,"); // SBRS => branch if set
ESIL_A("?{,%" PFMT64d ",pc,=,},", op->jump); // ?true => jmp
}
INST_HANDLER(sleep) { // SLEEP
ESIL_A("BREAK");
}
INST_HANDLER(spm) { // SPM Z+
ut64 spmcsr;
// read SPM Control Register (SPMCR)
rz_analysis_esil_reg_read(analysis->esil, "spmcsr", &spmcsr, NULL);
// clear SPMCSR
ESIL_A("0x7c,spmcsr,&=,");
// decide action depending on the old value of SPMCSR
switch (spmcsr & 0x7f) {
case 0x03: // PAGE ERASE
// invoke SPM_CLEAR_PAGE (erases target page writing
// the 0xff value
ESIL_A("16,rampz,<<,z,+,"); // push target address
ESIL_A("SPM_PAGE_ERASE,"); // do magic
break;
case 0x01: // FILL TEMPORARY BUFFER
ESIL_A("r1,r0,"); // push data
ESIL_A("z,"); // push target address
ESIL_A("SPM_PAGE_FILL,"); // do magic
break;
case 0x05: // WRITE PAGE
ESIL_A("16,rampz,<<,z,+,"); // push target address
ESIL_A("SPM_PAGE_WRITE,"); // do magic
break;
default:
RZ_LOG_DEBUG("SPM: I dont know what to do with SPMCSR %02" PFMT64x ".\n", spmcsr);
break;
}
}
INST_HANDLER(st) { // ST X, Rr
// ST X+, Rr
// ST -X, Rr
if (len < 2) {
return;
}
// load register
ESIL_A("r%d,", ((buf[1] & 1) << 4) | ((buf[0] >> 4) & 0xf));
// write in memory
__generic_ld_st(
op, "ram",
'x', // use index register X
0, // no use RAMP* registers
(buf[0] & 0xf) == 0xe
? -1 // pre decremented
: (buf[0] & 0xf) == 0xd
? 1 // post increment
: 0, // no increment
0, // offset always 0
1); // store operation (st)
}
INST_HANDLER(std) { // ST Y, Rr ST Z, Rr
// ST Y+, Rr ST Z+, Rr
// ST -Y, Rr ST -Z, Rr
// ST Y+q, Rr ST Z+q, Rr
if (len < 2) {
return;
}
// load register
ESIL_A("r%d,", ((buf[1] & 1) << 4) | ((buf[0] >> 4) & 0xf));
// write in memory
__generic_ld_st(
op, "ram",
buf[0] & 0x8 ? 'y' : 'z', // index register Y/Z
0, // no use RAMP* registers
!(buf[1] & 0x10)
? 0 // no increment
: buf[0] & 0x1
? 1 // post incremented
: -1, // pre decremented
!(buf[1] & 0x10)
? (buf[1] & 0x20) // offset
| ((buf[1] & 0xc) << 1) | (buf[0] & 0x7)
: 0, // no offset
1); // load operation (!st)
}
INST_HANDLER(swap) { // SWAP Rd
if (len < 2) {
return;
}
int d = ((buf[1] & 0x1) << 4) | ((buf[0] >> 4) & 0xf);
ESIL_A("4,r%d,>>,0x0f,&,", d); // (Rd >> 4) & 0xf
ESIL_A("4,r%d,<<,0xf0,&,", d); // (Rd >> 4) & 0xf
ESIL_A("|,"); // S[0] | S[1]
ESIL_A("r%d,=,", d); // Rd = result
}
OPCODE_DESC opcodes[] = {
// op mask select cycles size type
INST_DECL(break, 0xffff, 0x9698, 1, 2, TRAP), // BREAK
INST_DECL(eicall, 0xffff, 0x9519, 0, 2, UCALL), // EICALL
INST_DECL(eijmp, 0xffff, 0x9419, 0, 2, UJMP), // EIJMP
INST_DECL(icall, 0xffff, 0x9509, 0, 2, UCALL), // ICALL
INST_DECL(ijmp, 0xffff, 0x9409, 0, 2, UJMP), // IJMP
INST_DECL(lpm, 0xffff, 0x95c8, 3, 2, LOAD), // LPM
INST_DECL(nop, 0xffff, 0x0000, 1, 2, NOP), // NOP
INST_DECL(ret, 0xffff, 0x9508, 4, 2, RET), // RET
INST_DECL(reti, 0xffff, 0x9518, 4, 2, RET), // RETI
INST_DECL(sleep, 0xffff, 0x9588, 1, 2, NOP), // SLEEP
INST_DECL(spm, 0xffff, 0x95e8, 1, 2, TRAP), // SPM ...
INST_DECL(bclr, 0xff8f, 0x9488, 1, 2, MOV), // BCLR s
INST_DECL(bset, 0xff8f, 0x9408, 1, 2, MOV), // BSET s
INST_DECL(fmul, 0xff88, 0x0308, 2, 2, MUL), // FMUL Rd, Rr
INST_DECL(fmuls, 0xff88, 0x0380, 2, 2, MUL), // FMULS Rd, Rr
INST_DECL(fmulsu, 0xff88, 0x0388, 2, 2, MUL), // FMULSU Rd, Rr
INST_DECL(mulsu, 0xff88, 0x0300, 2, 2, AND), // MUL Rd, Rr
INST_DECL(des, 0xff0f, 0x940b, 0, 2, CRYPTO), // DES k
INST_DECL(adiw, 0xff00, 0x9600, 2, 2, ADD), // ADIW Rd+1:Rd, K
INST_DECL(sbiw, 0xff00, 0x9700, 2, 2, SUB), // SBIW Rd+1:Rd, K
INST_DECL(cbi, 0xff00, 0x9800, 1, 2, IO), // CBI A, K
INST_DECL(sbi, 0xff00, 0x9a00, 1, 2, IO), // SBI A, K
INST_DECL(movw, 0xff00, 0x0100, 1, 2, MOV), // MOVW Rd+1:Rd, Rr+1:Rr
INST_DECL(muls, 0xff00, 0x0200, 2, 2, AND), // MUL Rd, Rr
INST_DECL(asr, 0xfe0f, 0x9405, 1, 2, SAR), // ASR Rd
INST_DECL(com, 0xfe0f, 0x9400, 1, 2, NOT), // COM Rd
INST_DECL(dec, 0xfe0f, 0x940a, 1, 2, SUB), // DEC Rd
INST_DECL(elpm, 0xfe0f, 0x9006, 0, 2, LOAD), // ELPM Rd, Z
INST_DECL(elpm, 0xfe0f, 0x9007, 0, 2, LOAD), // ELPM Rd, Z+
INST_DECL(inc, 0xfe0f, 0x9403, 1, 2, ADD), // INC Rd
INST_DECL(lac, 0xfe0f, 0x9206, 2, 2, LOAD), // LAC Z, Rd
INST_DECL(las, 0xfe0f, 0x9205, 2, 2, LOAD), // LAS Z, Rd
INST_DECL(lat, 0xfe0f, 0x9207, 2, 2, LOAD), // LAT Z, Rd
INST_DECL(ld, 0xfe0f, 0x900c, 0, 2, LOAD), // LD Rd, X
INST_DECL(ld, 0xfe0f, 0x900d, 0, 2, LOAD), // LD Rd, X+
INST_DECL(ld, 0xfe0f, 0x900e, 0, 2, LOAD), // LD Rd, -X
INST_DECL(lds, 0xfe0f, 0x9000, 0, 4, LOAD), // LDS Rd, k
INST_DECL(sts, 0xfe0f, 0x9200, 2, 4, STORE), // STS k, Rr
INST_DECL(lpm, 0xfe0f, 0x9004, 3, 2, LOAD), // LPM Rd, Z
INST_DECL(lpm, 0xfe0f, 0x9005, 3, 2, LOAD), // LPM Rd, Z+
INST_DECL(lsr, 0xfe0f, 0x9406, 1, 2, SHR), // LSR Rd
INST_DECL(neg, 0xfe0f, 0x9401, 2, 2, SUB), // NEG Rd
INST_DECL(pop, 0xfe0f, 0x900f, 2, 2, POP), // POP Rd
INST_DECL(push, 0xfe0f, 0x920f, 0, 2, PUSH), // PUSH Rr
INST_DECL(ror, 0xfe0f, 0x9407, 1, 2, SAR), // ROR Rd
INST_DECL(st, 0xfe0f, 0x920c, 2, 2, STORE), // ST X, Rr
INST_DECL(st, 0xfe0f, 0x920d, 0, 2, STORE), // ST X+, Rr
INST_DECL(st, 0xfe0f, 0x920e, 0, 2, STORE), // ST -X, Rr
INST_DECL(swap, 0xfe0f, 0x9402, 1, 2, SAR), // SWAP Rd
INST_DECL(call, 0xfe0e, 0x940e, 0, 4, CALL), // CALL k
INST_DECL(jmp, 0xfe0e, 0x940c, 2, 4, JMP), // JMP k
INST_DECL(bld, 0xfe08, 0xf800, 1, 2, MOV), // BLD Rd, b
INST_DECL(bst, 0xfe08, 0xfa00, 1, 2, MOV), // BST Rd, b
INST_DECL(sbix, 0xff00, 0x9900, 2, 2, CJMP), // SBIC A, b
INST_DECL(sbix, 0xff00, 0x9b00, 2, 2, CJMP), // SBIS A, b
INST_DECL(sbrx, 0xfe08, 0xfc00, 2, 2, CJMP), // SBRC Rr, b
INST_DECL(sbrx, 0xfe08, 0xfe00, 2, 2, CJMP), // SBRS Rr, b
INST_DECL(ldd, 0xfe07, 0x9001, 0, 2, LOAD), // LD Rd, Y/Z+
INST_DECL(ldd, 0xfe07, 0x9002, 0, 2, LOAD), // LD Rd, -Y/Z
INST_DECL(std, 0xfe07, 0x9201, 0, 2, STORE), // ST Y/Z+, Rr
INST_DECL(std, 0xfe07, 0x9202, 0, 2, STORE), // ST -Y/Z, Rr
INST_DECL(adc, 0xfc00, 0x1c00, 1, 2, ADD), // ADC Rd, Rr
INST_DECL(add, 0xfc00, 0x0c00, 1, 2, ADD), // ADD Rd, Rr
INST_DECL(and, 0xfc00, 0x2000, 1, 2, AND), // AND Rd, Rr
INST_DECL(brbx, 0xfc00, 0xf000, 0, 2, CJMP), // BRBS s, k
INST_DECL(brbx, 0xfc00, 0xf400, 0, 2, CJMP), // BRBC s, k
INST_DECL(cp, 0xfc00, 0x1400, 1, 2, CMP), // CP Rd, Rr
INST_DECL(cpc, 0xfc00, 0x0400, 1, 2, CMP), // CPC Rd, Rr
INST_DECL(cpse, 0xfc00, 0x1000, 0, 2, CJMP), // CPSE Rd, Rr
INST_DECL(eor, 0xfc00, 0x2400, 1, 2, XOR), // EOR Rd, Rr
INST_DECL(mov, 0xfc00, 0x2c00, 1, 2, MOV), // MOV Rd, Rr
INST_DECL(mul, 0xfc00, 0x9c00, 2, 2, AND), // MUL Rd, Rr
INST_DECL(or, 0xfc00, 0x2800, 1, 2, OR), // OR Rd, Rr
INST_DECL(sbc, 0xfc00, 0x0800, 1, 2, SUB), // SBC Rd, Rr
INST_DECL(sub, 0xfc00, 0x1800, 1, 2, SUB), // SUB Rd, Rr
INST_DECL(in, 0xf800, 0xb000, 1, 2, IO), // IN Rd, A
INST_DECL(out, 0xf800, 0xb800, 1, 2, IO), // OUT A, Rr
INST_DECL(andi, 0xf000, 0x7000, 1, 2, AND), // ANDI Rd, K
INST_DECL(cpi, 0xf000, 0x3000, 1, 2, CMP), // CPI Rd, K
INST_DECL(ldi, 0xf000, 0xe000, 1, 2, LOAD), // LDI Rd, K
INST_DECL(ori, 0xf000, 0x6000, 1, 2, OR), // ORI Rd, K
INST_DECL(rcall, 0xf000, 0xd000, 0, 2, CALL), // RCALL k
INST_DECL(rjmp, 0xf000, 0xc000, 2, 2, JMP), // RJMP k
INST_DECL(sbci, 0xf000, 0x4000, 1, 2, SUB), // SBC Rd, Rr
INST_DECL(subi, 0xf000, 0x5000, 1, 2, SUB), // SUBI Rd, Rr
INST_DECL(ldd, 0xd200, 0x8000, 0, 2, LOAD), // LD Rd, Y/Z+q
INST_DECL(std, 0xd200, 0x8200, 0, 2, STORE), // ST Y/Z+q, Rr
// INST_DECL(lds16, 0xf800, 0xa000, 1, 2, LOAD), // LDS Rd, k
INST_LAST
};
static OPCODE_DESC *avr_op_analyze(RzAnalysis *analysis, RzAnalysisOp *op, ut64 addr, const ut8 *buf, int len, CPU_MODEL *cpu) {
OPCODE_DESC *opcode_desc;
if (len < 2) {
return NULL;
}
ut16 ins = (buf[1] << 8) | buf[0];
int fail;
char *t;
// process opcode
for (opcode_desc = opcodes; opcode_desc->handler; opcode_desc++) {
if ((ins & opcode_desc->mask) == opcode_desc->selector) {
fail = 0;
// start void esil expression
rz_strbuf_set(&op->esil, "");
// handle opcode
opcode_desc->handler(analysis, op, buf, len, &fail, cpu);
if (fail) {
break;
} else if (opcode_desc->cycles <= 0) {
opcode_desc->cycles = 2;
}
// remove trailing coma (COMETE LA COMA)
t = rz_strbuf_get(&op->esil);
if (t && strlen(t) > 1) {
t += strlen(t) - 1;
if (*t == ',') {
*t = '\0';
}
}
return opcode_desc;
}
}
return NULL;
}
static bool avr_custom_des(RzAnalysisEsil *esil) {
if (!esil || !esil->analysis || !esil->analysis->reg) {
return false;
}
ut64 arg;
if (!__esil_pop_argument(esil, &arg)) {
return false;
}
int round = arg;
if (round < 0 || round > 15) {
return false;
}
ut64 decrypt;
rz_analysis_esil_reg_read(esil, "hf", &decrypt, NULL);
if (decrypt) {
round = 15 - round;
}
ut8 regs[0x10];
static const char *reg_names[] = {
"r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7",
"r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15"
};
for (size_t i = 0; i < sizeof(regs); i++) {
ut64 v = 0;
rz_analysis_esil_reg_read(esil, reg_names[i], &v, NULL);
regs[i] = v;
}
// Atmel's "AVR Instruction Set Manual" unfortunately is very ambiguous
// regarding the details of this instruction and leaves the most interesting
// questions open, especially what intermediate results are stored and how.
// The below implementation has been developed based on observing the exact
// results in the Simulator in Atmel/Microchip Studio emulating ATxmega128A1.
// Things may seem very strange (especially hi/lo swapping), but it is all
// intended to get the right behavior!
ut32 buf_hi = rz_read_at_le32(regs, 0);
ut32 buf_lo = rz_read_at_le32(regs, 4);
ut32 key_orig_hi = rz_read_at_le32(regs, 8);
ut32 key_orig_lo = rz_read_at_le32(regs, 0xc);
ut32 key_lo = key_orig_lo;
ut32 key_hi = key_orig_hi;
rz_des_permute_key(&key_lo, &key_hi);
int i = round;
if (!decrypt) {
rz_des_shift_key(i, false, &key_lo, &key_hi);
}
ut32 round_key_lo, round_key_hi;
rz_des_pc2(&round_key_lo, &round_key_hi, key_lo, key_hi);
if (decrypt) {
rz_des_shift_key(i, true, &key_lo, &key_hi);
}
rz_des_permute_block0(&buf_lo, &buf_hi);
rz_des_round(&buf_lo, &buf_hi, &round_key_lo, &round_key_hi);
if (arg < 15) {
rz_des_permute_block1(&buf_lo, &buf_hi);
} else {
rz_des_permute_block1(&buf_hi, &buf_lo);
buf_lo ^= buf_hi;
buf_hi ^= buf_lo;
buf_lo ^= buf_hi;
}
rz_des_permute_key_inv(&key_lo, &key_hi); // un-permute so the rz_des_permute_key() in the next round will restore it
key_lo |= key_orig_hi & 0x01010101; // restore the parity bits that got lost in PC-1
key_hi |= key_orig_lo & 0x01010101;
rz_write_at_le32(regs, buf_hi, 0);
rz_write_at_le32(regs, buf_lo, 4);
rz_write_at_le32(regs, key_lo, 8);
rz_write_at_le32(regs, key_hi, 0xc);
for (size_t i = 0; i < sizeof(regs); i++) {
ut64 v = regs[i];
rz_analysis_esil_reg_write(esil, reg_names[i], v);
}
return true;
}
// ESIL operation SPM_PAGE_ERASE
static bool avr_custom_spm_page_erase(RzAnalysisEsil *esil) {
CPU_MODEL *cpu;
ut8 c;
ut64 addr, page_size_bits, i;
// sanity check
if (!esil || !esil->analysis || !esil->analysis->reg) {
return false;
}
// get target address
if (!__esil_pop_argument(esil, &addr)) {
return false;
}
// get details about current MCU and fix input address
cpu = get_cpu_model(esil->analysis->cpu);
page_size_bits = const_get_value(const_by_name(cpu, CPU_CONST_PARAM, "page_size"));
// align base address to page_size_bits
addr &= ~(MASK(page_size_bits));
// perform erase
// RZ_LOG_DEBUG("SPM_PAGE_ERASE %ld bytes @ 0x%08" PFMT64x ".\n", page_size, addr);
c = 0xff;
for (i = 0; i < (1ULL << page_size_bits); i++) {
rz_analysis_esil_mem_write(
esil, (addr + i) & CPU_PC_MASK(cpu), &c, 1);
}
return true;
}
// ESIL operation SPM_PAGE_FILL
static bool avr_custom_spm_page_fill(RzAnalysisEsil *esil) {
CPU_MODEL *cpu;
ut64 addr, page_size_bits, i;
ut8 r0, r1;
// sanity check
if (!esil || !esil->analysis || !esil->analysis->reg) {
return false;
}
// get target address, r0, r1
if (!__esil_pop_argument(esil, &addr)) {
return false;
}
if (!__esil_pop_argument(esil, &i)) {
return false;
}
r0 = i;
if (!__esil_pop_argument(esil, &i)) {
return false;
}
r1 = i;
// get details about current MCU and fix input address
cpu = get_cpu_model(esil->analysis->cpu);
page_size_bits = const_get_value(const_by_name(cpu, CPU_CONST_PARAM, "page_size"));
// align and crop base address
addr &= (MASK(page_size_bits) ^ 1);
// perform write to temporary page
// RZ_LOG_DEBUG("SPM_PAGE_FILL bytes (%02x, %02x) @ 0x%08" PFMT64x ".\n", r1, r0, addr);
rz_analysis_esil_mem_write(esil, addr++, &r0, 1);
rz_analysis_esil_mem_write(esil, addr++, &r1, 1);
return true;
}
// ESIL operation SPM_PAGE_WRITE
static bool avr_custom_spm_page_write(RzAnalysisEsil *esil) {
CPU_MODEL *cpu;
char *t = NULL;
ut64 addr, page_size_bits, tmp_page;
// sanity check
if (!esil || !esil->analysis || !esil->analysis->reg) {
return false;
}
// get target address
if (!__esil_pop_argument(esil, &addr)) {
return false;
}
// get details about current MCU and fix input address and base address
// of the internal temporary page
cpu = get_cpu_model(esil->analysis->cpu);
page_size_bits = const_get_value(const_by_name(cpu, CPU_CONST_PARAM, "page_size"));
rz_analysis_esil_reg_read(esil, "_page", &tmp_page, NULL);
// align base address to page_size_bits
addr &= (~(MASK(page_size_bits)) & CPU_PC_MASK(cpu));
// perform writing
// RZ_LOG_DEBUG("SPM_PAGE_WRITE %ld bytes @ 0x%08" PFMT64x ".\n", page_size, addr);
if (!(t = malloc(1 << page_size_bits))) {
RZ_LOG_ERROR("Cannot alloc a buffer for copying the temporary page.\n");
return false;
}
rz_analysis_esil_mem_read(esil, tmp_page, (ut8 *)t, 1 << page_size_bits);
rz_analysis_esil_mem_write(esil, addr, (ut8 *)t, 1 << page_size_bits);
return true;
}
static int esil_avr_hook_reg_write(RzAnalysisEsil *esil, const char *name, ut64 *val) {
CPU_MODEL *cpu;
if (!esil || !esil->analysis) {
return 0;
}
// select cpu info
cpu = get_cpu_model(esil->analysis->cpu);
// crop registers and force certain values
if (!strcmp(name, "pc")) {
*val &= CPU_PC_MASK(cpu);
} else if (!strcmp(name, "pcl")) {
if (cpu->pc < 8) {
*val &= MASK(8);
}
} else if (!strcmp(name, "pch")) {
*val = cpu->pc > 8
? *val & MASK(cpu->pc - 8)
: 0;
}
return 0;
}
RZ_IPI int rz_avr_esil_init(RzAnalysisEsil *esil) {
if (!esil) {
return false;
}
rz_analysis_esil_set_op(esil, "des", avr_custom_des, 0, 0, RZ_ANALYSIS_ESIL_OP_TYPE_CUSTOM); // better meta info plz
rz_analysis_esil_set_op(esil, "SPM_PAGE_ERASE", avr_custom_spm_page_erase, 0, 0, RZ_ANALYSIS_ESIL_OP_TYPE_CUSTOM);
rz_analysis_esil_set_op(esil, "SPM_PAGE_FILL", avr_custom_spm_page_fill, 0, 0, RZ_ANALYSIS_ESIL_OP_TYPE_CUSTOM);
rz_analysis_esil_set_op(esil, "SPM_PAGE_WRITE", avr_custom_spm_page_write, 0, 0, RZ_ANALYSIS_ESIL_OP_TYPE_CUSTOM);
esil->cb.hook_reg_write = esil_avr_hook_reg_write;
return true;
}
RZ_IPI int rz_avr_esil_fini(RzAnalysisEsil *esil) {
return true;
}
RZ_IPI void rz_avr_esil_opcode(RzAnalysis *analysis, RzAnalysisOp *op, ut64 addr, const ut8 *buf, int len) {
// select cpu info
CPU_MODEL *cpu = get_cpu_model(analysis->cpu);
avr_op_analyze(analysis, op, addr, buf, len, cpu);
}