radare2/libr/anal/p/anal_8051.c
2015-10-31 01:59:01 +01:00

562 lines
15 KiB
C

/* radare - LGPL - Copyright 2013 - pancake */
/* Copyright 2015 - dkreuter */
#include <string.h>
#include <r_types.h>
#include <r_lib.h>
#include <r_asm.h>
#include <r_anal.h>
#include <8051_disas.h>
#define IRAM 0x10000
static ut8 bitindex[] = {
// bit 'i' can be found in (ram[bitindex[i>>3]] >> (i&7)) & 1
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, // 0x00
0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, // 0x40
0x80, 0x88, 0x90, 0x98, 0xA0, 0xA8, 0xB0, 0xB8, // 0x80
0x00, 0x00, 0xD0, 0x00, 0xE0, 0x00, 0xF0, 0x00 // 0xC0
};
#define emit(frag) r_strbuf_appendf(&op->esil, frag)
#define emitf(...) r_strbuf_appendf(&op->esil, __VA_ARGS__)
#define j(frag) emitf(frag, 1 & buf[0], buf[1], buf[2]);
#define h(frag) emitf(frag, 7 & buf[0], buf[1], buf[2]);
#define k(frag) emitf(frag, bitindex[buf[1]>>3], buf[1] & 7, buf[2]);
// on 8051 the stack grows upward and lsb is pushed first meaning
// that on little-endian esil vms =[2] works as indended
#define PUSH1 "1,sp,+=,sp,=[1]"
#define POP1 "sp,[1],1,sp,-=,"
#define PUSH2 "1,sp,+=,sp,=[2],1,sp,+="
#define POP2 "1,sp,-=,sp,[2],1,sp,-=,"
#define CALL(skipbytes) skipbytes",pc,+," PUSH2
#define JMP(skipbytes) skipbytes",+,pc,+="
#define CJMP(target, skipbytes) "?{," ESX_##target "" JMP(skipbytes) ",}"
#define BIT_R "%2$d,%1$d,[1],>>,1,&,"
#define IRAM_BASE "0x10000"
#define XRAM_BASE "0x10100"
#define ES_IB1 IRAM_BASE ",%2$d,+,"
#define ES_IB2 IRAM_BASE ",%3$d,+,"
#define ES_R0I "r%1$d,"
#define ES_AI "A,"
#define ES_R0 "r%1$d,"
#define ES_R1 "r%2$d,"
#define ES_A "A,"
#define ES_L1 "%2$d,"
#define ES_L2 "%3$d,"
#define ES_C "C,"
// signed char variant
#define ESX_L1 "%2$hhd,"
#define ESX_L2 "%2$hhd,"
#define ACC_IB1 "[1],"
#define ACC_IB2 "[1],"
#define ACC_R0I "[1],"
#define ACC_AI "[1],"
#define ACC_R0 ""
#define ACC_R1 ""
#define ACC_A ""
#define ACC_L1 ""
#define ACC_L2 ""
#define ACC_C ""
#define XR(subject) ES_##subject ACC_##subject
#define XW(subject) ES_##subject "=" ACC_##subject
#define XI(subject, operation) ES_##subject operation "=" ACC_##subject
#define TEMPLATE_4(base, format, src4, arg1, arg2) \
case base + 0x4: \
h (format(0, src4, arg1, arg2)) break; \
\
case base + 0x5: \
h (format(1, IB1, arg1, arg2)) break; \
\
case base + 0x6: \
case base + 0x7: \
j (format(0, R0I, arg1, arg2)) break; \
\
case base + 0x8: case base + 0x9: \
case base + 0xA: case base + 0xB: \
case base + 0xC: case base + 0xD: \
case base + 0xE: case base + 0xF: \
h (format(0, R0, arg1, arg2)) break;
#define OP_GROUP_INPLACE_LHS_4(base, lhs, op) TEMPLATE_4(base, OP_GROUP_INPLACE_LHS_4_FMT, L1, lhs, op)
#define OP_GROUP_INPLACE_LHS_4_FMT(databyte, rhs, lhs, op) XR(rhs) XI(lhs, op)
#define OP_GROUP_UNARY_4(base, op) TEMPLATE_4(base, OP_GROUP_UNARY_4_FMT, A, op, XXX)
#define OP_GROUP_UNARY_4_FMT(databyte, lhs, op, xxx) XI(lhs, op)
////////
static void analop_esil(RAnal *a, RAnalOp *op, ut64 addr, const ut8 *buf, const char *buf_asm) {
r_strbuf_init (&op->esil);
r_strbuf_set (&op->esil, "");
switch (buf[0]) {
// Irregulars sorted by lower nibble
case 0x00: /* nop */ emit(","); break;
case 0x10: /* jbc */
k(BIT_R "&,?{,%2$d,1,<<,255,^,%1$d,&=[1],%3$hhd,3,+,pc,+=,}"); break;
case 0x20: /* jb */
k(BIT_R "&,?{,%3$hhd,3,+,pc,+=,}"); break;
case 0x30: /* jnb */
k(BIT_R "&,!,?{,%3$hhd,3,+,pc,+=,}"); break;
case 0x40: /* jc */ emitf("C,!,?{,%hhd,2,+,pc,+=,}", buf[1]); break;
case 0x50: /* jnc */ emitf("C,""?{,%hhd,2,+,pc,+=,}", buf[1]); break;
case 0x60: /* jz */ emitf("A,!,?{,%hhd,2,+,pc,+=,}", buf[1]); break;
case 0x70: /* jnz */ emitf("A,""?{,%hhd,2,+,pc,+=,}", buf[1]); break;
case 0x80: /* sjmp */ j(ESX_L1 JMP("2")); break;
case 0x90: /* mov */ emitf("%d,dptr,=", (buf[1]<<8) + buf[2]); break;
case 0xA0: /* orl */ k(BIT_R "C,|="); break;
case 0xB0: /* anl */ k(BIT_R "C,&="); break;
case 0xC0: /* push */ h(XR(IB1) PUSH1); break;
case 0xD0: /* pop */ h(POP1 XW(IB1)); break;
case 0xE0: /* movx */ /* TODO */ break;
case 0xF0: /* movx */ /* TODO */ break;
case 0x11: case 0x31: case 0x51: case 0x71:
case 0x91: case 0xB1: case 0xD1: case 0xF1:
emit(CALL("2")); // fall through
case 0x01: case 0x21: case 0x41: case 0x61:
case 0x81: case 0xA1: case 0xC1: case 0xE1:
emitf("0x%x,pc,=", (addr & 0xF800) | ((((unsigned short)buf[0])<<3) & 0x0700) | buf[1]); break;
case 0x02: /* ljmp */ emitf( "%d,pc,=", (unsigned int)((buf[1]<<8)+buf[2])); break;
case 0x12: /* lcall */ emitf(CALL("3")",%d,pc,=", (unsigned int)((buf[1]<<8)+buf[2])); break;
case 0x22: /* ret */ emitf(POP2 "pc,="); break;
case 0x32: /* reti */ /* TODO */ break;
case 0x72: /* orl */ /* TODO */ break;
case 0x82: /* anl */ /* TODO */ break;
case 0x92: /* mov */ /* TODO */ break;
case 0xA2: /* mov */ /* TODO */ break;
case 0xB2: /* cpl */ k("%2$d,1,<<,%1$d,^=[1]"); break;
case 0xC2: /* clr */ /* TODO */ break;
case 0x03: /* rr */ emit("1,A,0x101,*,>>,A,="); break;
case 0x13: /* rrc */ /* TODO */ break;
case 0x23: /* rl */ emit("7,A,0x101,*,>>,A,="); break;
case 0x33: /* rlc */ /* TODO */ break;
case 0x73: /* jmp */ emit("dptr,A,+,pc,="); break;
case 0x83: /* movc */ emit("A,dptr,+,[1],A,="); break;
case 0x93: /* movc */ emit("A,pc,+,[1],A,="); break;
case 0xA3: /* inc */ h(XI(IB1, "++")); break;
case 0xB3: /* cpl */ emit("1," XI(C, "^")); break;
case 0xC3: /* clr */ emit("0,C,="); break;
// Regulars sorted by upper nibble
OP_GROUP_UNARY_4(0x00, "++")
OP_GROUP_UNARY_4(0x10, "--")
OP_GROUP_INPLACE_LHS_4(0x20, A, "+")
case 0x34:
h (XR(L1) "C,+," XI(A, "+")) break;
case 0x35:
h (XR(IB1) "C,+," XI(A, "+")) break;
case 0x36: case 0x37:
j (XR(R0I) "C,+," XI(A, "+")) break;
case 0x38: case 0x39:
case 0x3A: case 0x3B:
case 0x3C: case 0x3D:
case 0x3E: case 0x3F:
h (XR(R0) "C,+," XI(A, "+")) break;
OP_GROUP_INPLACE_LHS_4(0x40, A, "|")
OP_GROUP_INPLACE_LHS_4(0x50, A, "&")
OP_GROUP_INPLACE_LHS_4(0x60, A, "^")
case 0x74:
h (XR(L1) XW(A)) break;
case 0x75:
h (XR(L2) XW(IB1)) break;
case 0x76: case 0x77:
j (XR(L1) XW(R0I)) break;
case 0x78: case 0x79:
case 0x7A: case 0x7B:
case 0x7C: case 0x7D:
case 0x7E: case 0x7F:
h (XR(L1) XW(R0)) break;
case 0x84:
/* div */ emit("B,!,OV,=,0,A,B,A,/=,A,B,*,-,-,B,=,0,C,="); break;
case 0x85:
/* mov */ h(IRAM_BASE ",%2$d,+,[1]," IRAM_BASE ",%2$d,+,=[1]"); break;
case 0x86: case 0x87:
j (XR(R0I) XW(IB1)) break;
case 0x88: case 0x89:
case 0x8A: case 0x8B:
case 0x8C: case 0x8D:
case 0x8E: case 0x8F:
h (XR(R0) XW(IB1)) break;
OP_GROUP_INPLACE_LHS_4(0x90, A, ".")
case 0xA4:
/* mul */ emit("8,A,B,*,DUP,>>,DUP,!,!,OV,=,B,=,A,=,0,C,="); break;
case 0xA5: /* ??? */ emit("0,TRAP"); break;
case 0xA6: case 0xA7:
j (XR(IB1) XW(R0I)) break;
case 0xA8: case 0xA9:
case 0xAA: case 0xAB:
case 0xAC: case 0xAD:
case 0xAE: case 0xAF:
h (XR(IB1) XW(R0)) break;
case 0xB4:
h (XR(L1) XR(A) "!=,?{,%3$hhd,2,+pc,+=,}") break;
case 0xB5:
h (XR(IB1) XR(A) "!=,?{,%3$hhd,2,+pc,+=,}") break;
case 0xB6: case 0xB7:
j (XR(L1) XR(R0I) "!=,?{,%3$hhd,2,+pc,+=,}") break;
case 0xB8: case 0xB9:
case 0xBA: case 0xBB:
case 0xBC: case 0xBD:
case 0xBE: case 0xBF:
h (XR(L1) XR(R0) "!=,?{,%3$hhd,2,+pc,+=,}") break;
case 0xC4:
/* swap */ emit("4,A,0x101,*,>>,A,="); break;
case 0xC5:
/* xch */ /* TODO */ break;
case 0xC6: case 0xC7:
/* xch */ /* TODO */ break;
case 0xC8: case 0xC9:
case 0xCA: case 0xCB:
case 0xCC: case 0xCD:
case 0xCE: case 0xCF:
/* xch */ h (XR(A) XR(R0) XW(A) "," XW(R0)); break;
case 0xD2:
/* setb */ /* TODO */ break;
case 0xD3:
/* setb */ /* TODO */ break;
case 0xD4:
/* da */ emit("A,--="); break;
case 0xD5:
/* djnz */ h(XI(R0I, "--") "," XR(R0I) CJMP(L2, "2")); break;
case 0xD6:
/* xchd */ /* TODO */ break;
case 0xD7:
/* xchd */ /* TODO */ break;
case 0xD8: case 0xD9:
case 0xDA: case 0xDB:
case 0xDC: case 0xDD:
case 0xDE: case 0xDF:
/* djnz */ h(XI(R0, "--") "," XR(R0) CJMP(L1, "2")); break;
case 0xE2: case 0xE3:
/* movx */ j(XRAM_BASE "r%0$d,+,[1]," XW(A)); break;
case 0xE4:
/* clr */ emit("0,A,="); break;
case 0xE5:
/* mov */ h (XR(IB1) XW(A)) break;
case 0xE6: case 0xE7:
/* mov */ j (XR(R0I) XW(A)) break;
case 0xE8: case 0xE9:
case 0xEA: case 0xEB:
case 0xEC: case 0xED:
case 0xEE: case 0xEF:
/* mov */ h (XR(R0) XW(A)) break;
case 0xF2: case 0xF3:
/* movx */ j(XR(A) XRAM_BASE "r%0$d,+,=[1]");
case 0xF4:
/* cpl */ h ("255" XI(A, "^")) break;
case 0xF5:
/* mov */ h (XR(A) XW(IB1)) break;
case 0xF6: case 0xF7:
/* mov */ j (XR(A) XW(R0I)) break;
case 0xF8: case 0xF9:
case 0xFA: case 0xFB:
case 0xFC: case 0xFD:
case 0xFE: case 0xFF:
/* mov */ h (XR(A) XW(R0)) break;
default: break;
}
}
static struct r_i8015_reg {
const char *name;
ut8 offset; // offset into memory, where the value is held
ut8 resetvalue; // value the register takes in case of a reset
ut8 num_bytes; // no more than sizeof(ut64)
ut8 banked : 1;
ut8 isdptr : 1;
} registers[] = {
// keep these sorted
{"acc", 0xE0, 0x00, 1, 0},
{"b", 0xF0, 0x00, 1, 0},
{"dph", 0x83, 0x00, 1, 0},
{"dpl", 0x82, 0x00, 1, 0},
{"dptr", 0x00, 0x00, 2, 0, 1},
{"ie", 0xA8, 0x00, 1, 0},
{"ip", 0xB8, 0x00, 1, 0},
{"p0", 0x80, 0xFF, 1, 0},
{"p1", 0x90, 0xFF, 1, 0},
{"p2", 0xA0, 0xFF, 1, 0},
{"p3", 0xB0, 0xFF, 1, 0},
{"pcon", 0x87, 0x00, 1, 0},
{"psw", 0xD0, 0x00, 1, 0},
{"r0", 0x00, 0x00, 1, 1},
{"r1", 0x01, 0x00, 1, 1},
{"r2", 0x02, 0x00, 1, 1},
{"r3", 0x03, 0x00, 1, 1},
{"r4", 0x04, 0x00, 1, 1},
{"r5", 0x05, 0x00, 1, 1},
{"r6", 0x06, 0x00, 1, 1},
{"r7", 0x07, 0x00, 1, 1},
{"sbuf", 0x99, 0x00, 1, 0},
{"scon", 0x98, 0x00, 1, 0},
{"sp", 0x81, 0x07, 1, 0},
{"tcon", 0x88, 0x00, 1, 0},
{"th0", 0x8C, 0x00, 1, 0},
{"th1", 0x8D, 0x00, 1, 0},
{"tl0", 0x8A, 0x00, 1, 0},
{"tl1", 0x8B, 0x00, 1, 0},
{"tmod", 0x89, 0x00, 1, 0}
};
static int i8051_hook_reg_read(RAnalEsil *, const char *, ut64 *, int *);
static int i8051_reg_compare(const void *name, const void *reg) {
return strcmp((const char*)name, ((struct r_i8015_reg*)reg)->name);
}
static struct r_i8015_reg *i8051_reg_find(const char *name) {
return (struct r_i8015_reg *) bsearch(
name,
registers,
sizeof(registers)/sizeof(registers[0]),
sizeof(registers[0]),
i8051_reg_compare);
}
static int i8051_reg_get_offset(RAnalEsil *esil, struct r_i8015_reg *ri) {
ut8 offset = ri->offset;
if (ri->banked) {
ut64 psw = 0LL;
i8051_hook_reg_read(esil, "psw", &psw, NULL);
offset += psw & 0x18;
}
return offset;
}
// dkreuter: It would be nice if we could attach hooks to RRegItems directly.
// That way we could avoid doing a string lookup on register names
// as r_reg_get already does this. Also, the anal esil callbacks
// approach interferes with r_reg_arena_swap.
#define ocbs ((struct r_i8051_user*)esil->cb.user)->cbs
struct r_i8051_user {
RAnalEsilCallbacks cbs;
};
static int i8051_hook_reg_read(RAnalEsil *esil, const char *name, ut64 *res, int *size) {
int ret = 0;
ut64 val = 0LL;
struct r_i8015_reg *ri;
RAnalEsilCallbacks cbs = esil->cb;
if ((ri = i8051_reg_find (name))) {
ut8 offset = i8051_reg_get_offset(esil, ri);
ret = r_anal_esil_mem_read (esil, IRAM + offset, (ut8*)res, ri->num_bytes);
}
esil->cb = ocbs;
if (!ret && ocbs.hook_reg_read) {
ret = ocbs.hook_reg_read (esil, name, res, NULL);
}
if (!ret && ocbs.reg_read) {
ret = ocbs.reg_read (esil, name, &val, NULL);
}
esil->cb = cbs;
return ret;
}
static int i8051_hook_reg_write(RAnalEsil *esil, const char *name, ut64 val) {
int ret = 0;
struct r_i8015_reg *ri;
RAnalEsilCallbacks cbs = esil->cb;
if ((ri = i8051_reg_find (name))) {
ut8 offset = i8051_reg_get_offset(esil, ri);
ret = r_anal_esil_mem_write (esil, IRAM + offset, (ut8*)&val, ri->num_bytes);
}
esil->cb = ocbs;
if (!ret && ocbs.hook_reg_write) {
ret = ocbs.hook_reg_write (esil, name, val);
}
esil->cb = cbs;
return ret;
}
static int esil_i8051_init (RAnalEsil *esil) {
if (esil->cb.user)
return true;
esil->cb.user = R_NEW0(struct r_i8051_user);
ocbs = esil->cb;
esil->cb.hook_reg_read = i8051_hook_reg_read;
esil->cb.hook_reg_write = i8051_hook_reg_write;
return true;
}
static int esil_i8051_fini (RAnalEsil *esil) {
esil->cb = ocbs;
R_FREE(esil->cb.user);
return true;
}
static int set_reg_profile(RAnal *anal) {
const char *p =
"=PC pc\n"
"=SP sp\n"
"gpr r0 .8 0 0\n"
"gpr r1 .8 1 0\n"
"gpr r2 .8 2 0\n"
"gpr r3 .8 3 0\n"
"gpr r4 .8 4 0\n"
"gpr r5 .8 5 0\n"
"gpr r6 .8 6 0\n"
"gpr r7 .8 7 0\n"
"gpr A .8 8 0\n"
"gpr B .8 9 0\n"
"gpr sp .8 10 0\n"
"gpr pc .16 12 0\n"
"gpr dptr .16 14 0\n"
"gpr C .1 16 0\n"
"gpr OV .1 17 0\n";
return r_reg_set_profile_string (anal->reg, p);
}
// TODO: Cleanup the code, remove unneeded data copies
static int i8051_op(RAnal *anal, RAnalOp *op, ut64 addr, const ut8 *buf, int len) {
char *tmp = NULL;
char buf_asm[64];
op->delay = 0;
r_8051_op o = r_8051_decode (buf, len);
memset(buf_asm, 0, sizeof (buf_asm));
if (!o.name) return 0; // invalid instruction
tmp = r_8051_disasm (o, addr, buf_asm, sizeof (buf_asm));
if (tmp) {
if (strlen (tmp) < sizeof (buf_asm)) {
strncpy (buf_asm, tmp, strlen (tmp));
} else {
eprintf ("8051 analysis: too big opcode!\n");
free (tmp);
op->size = -1;
return -1;
}
free (tmp);
}
if (!strncmp (buf_asm, "push", 4)) {
op->type = R_ANAL_OP_TYPE_UPUSH;
op->ptr = 0;
op->stackop = R_ANAL_STACK_INC;
op->stackptr = 1;
} else
if (!strncmp (buf_asm, "pop", 3)) {
op->type = R_ANAL_OP_TYPE_POP;
op->ptr = 0;
op->stackop = R_ANAL_STACK_INC;
op->stackptr = -1;
} else
if (!strncmp (buf_asm, "ret", 3)) {
op->type = R_ANAL_OP_TYPE_RET;
op->stackop = R_ANAL_STACK_INC;
op->stackptr = -2;
} else
if (!strncmp (buf_asm, "nop", 3)) {
op->type = R_ANAL_OP_TYPE_NOP;
} else
if (!strncmp (buf_asm, "inv", 3)) {
op->type = R_ANAL_OP_TYPE_ILL;
} else
if ((!strncmp (buf_asm, "inc", 3)) ||
(!strncmp (buf_asm, "add", 3))) {
op->type = R_ANAL_OP_TYPE_ADD;
} else
if ((!strncmp (buf_asm, "dec", 3)) ||
(!strncmp (buf_asm, "sub", 3))) {
op->type = R_ANAL_OP_TYPE_SUB;
} else
if (!strncmp (buf_asm, "mov", 3)) {
op->type = R_ANAL_OP_TYPE_MOV;
} else
if (*buf_asm && !strncmp (buf_asm+1, "call", 4)) {
op->type = R_ANAL_OP_TYPE_CALL;
op->jump = o.addr;
op->fail = addr+o.length;
} else
/* CJNE, DJNZ, JC, JNC, JZ, JB, JNB, LJMP, SJMP */
if (buf_asm[0]=='j' || (buf_asm[0] && buf_asm[1] == 'j'))
{
op->type = R_ANAL_OP_TYPE_JMP;
if (o.operand == OFFSET)
op->jump = o.addr+addr+o.length;
else
op->jump = o.addr;
op->fail = addr+o.length;
}
if (anal->decode) {
ut8 copy[3] = {0, 0, 0};
memcpy (copy, buf, len >= 3 ? 3 : len);
analop_esil (anal, op, addr, copy, buf_asm);
}
return op->size = o.length;
}
struct r_anal_plugin_t r_anal_plugin_8051 = {
.name = "8051",
.arch = "8051",
.bits = 8|16,
.desc = "8051 CPU code analysis plugin",
.license = "LGPL3",
.op = &i8051_op,
.set_reg_profile = set_reg_profile,
.esil_init = esil_i8051_init,
.esil_fini = esil_i8051_fini
};
#ifndef CORELIB
struct r_lib_struct_t radare_plugin = {
.type = R_LIB_TYPE_ANAL,
.data = &r_anal_plugin_8051,
.version = R2_VERSION
};
#endif