From 628dac43e1f9ca77ed29d2229c0872661baf5354 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D9=85=D8=B5=D8=B7=D9=81=D9=8A=20=D9=85=D8=AD=D9=85=D9=88?= =?UTF-8?q?=D8=AF=20=D9=83=D9=85=D8=A7=D9=84=20=D8=A7=D9=84=D8=AF=D9=8A?= =?UTF-8?q?=D9=86?= <48567303+moste00@users.noreply.github.com> Date: Sat, 23 Mar 2024 00:26:57 +0200 Subject: [PATCH] Lift MSP430 machine language to RzIL: first 3 instructions and 1 addressing mode --- librz/arch/isa/msp430/msp430_il.c | 227 ++++++++++++++++++ librz/arch/isa/msp430/msp430_il.h | 50 ++++ .../isa/msp430/msp430_instruction_parse.inc | 209 ++++++++++++++++ librz/arch/meson.build | 1 + librz/arch/p/analysis/analysis_msp430.c | 37 ++- test/db/asm/msp430 | 3 + test/db/cmd/cmd_list | 2 +- 7 files changed, 527 insertions(+), 2 deletions(-) create mode 100644 librz/arch/isa/msp430/msp430_il.c create mode 100644 librz/arch/isa/msp430/msp430_il.h create mode 100644 librz/arch/isa/msp430/msp430_instruction_parse.inc diff --git a/librz/arch/isa/msp430/msp430_il.c b/librz/arch/isa/msp430/msp430_il.c new file mode 100644 index 00000000000..a8e1dc7d6f6 --- /dev/null +++ b/librz/arch/isa/msp430/msp430_il.c @@ -0,0 +1,227 @@ +// SPDX-FileCopyrightText: 2024 Mostafa Mahmoud +// SPDX-License-Identifier: LGPL-3.0-only + +#include + +// ``The 16-bit program counter (PC/R0) points to the next instruction to be executed.`` +// -- userguide, p. 44 +#define PC_SIZE 16 +// ``The low byte of a word is always an even address. The high byte is at the next odd address. +// For example, if a data word is located at address xxx4h, then the low byte of that data word +// is located at address xxx4h, and the high byte of that word is located at address xxx5h.`` +// -- userguide, p. 26 +#define IS_BIG_ENDIAN false +// implied by the width of the PC and other registers (which are used as pointers in the relevant addressing modes) +#define MEM_ADDR_SIZE 16U + +static const char *msp430_register_names[] = { + "pc", + "sp", + "sr", + "cg", + "r4", + "r5", + "r6", + "r7", + "r8", + "r9", + "r10", + "r11", + "r12", + "r13", + "r14", + "r15", + NULL +}; + +#include "rz_il/rz_il_opbuilder_begin.h" + +#define MSP430_GETR(r) VARG(msp430_register_names[r]) +#define MSP430_SETR(r, v) SETG(msp430_register_names[r], v) + +typedef struct { + RzILOpBitVector *first; + RzILOpBitVector *second; +} BVPair; + +RZ_OWN RzILOpEffect *rz_msp430_dummy() { + return NOP(); +} + +RZ_OWN RzILOpPure *get_destination(const Msp430Instruction *op) { + switch (op->dst_mode) { + case DST_REG: { + RzILOpBitVector *reg = MSP430_GETR(op->dst); + if (op->word_sized & WORD_SIZED_READ) { + return reg; + } + return CAST(8, IL_FALSE, reg); + } + default: NOT_IMPLEMENTED; // TODO : other addressing modes + } +} + +RZ_OWN BVPair get_destination_destructured(const Msp430Instruction *op) { + switch (op->dst_mode) { + case DST_REG: { + RzILOpBitVector *reg = MSP430_GETR(op->dst); + BVPair res; + res.first = CAST(8, IL_FALSE, reg); + res.second = CAST(8, IL_FALSE, SHIFTR0(DUP(reg), U8(8))); + return res; + } + default: { + BVPair dummy = { .first = U8(0), .second = U8(0) }; + return dummy; // TODO : other addressing modes + } + } +} + +RZ_OWN RzILOpEffect *set_destination(const Msp430Instruction *op, RzILOpBitVector *old_value, RzILOpBitVector *new_value) { + switch (op->dst_mode) { + case DST_REG: { + if (op->word_sized & WORD_SIZED_WRITE) { + return MSP430_SETR(op->dst, new_value); + } + // the general idea is: First we zero the lower byte through ANDing with 0xFF00 + // Then we assign the lower byte to the (byte-length) result through ORing with 0x00 + // the overall effect is that only the lower byte is assigned, which is what byte-sized operations do + return MSP430_SETR(op->dst, LOGOR(LOGAND(old_value, U16(0xFF00)), UNSIGNED(16, new_value))); + } + default: return NOP(); // TODO : other addressing modes + } +} + +RZ_OWN RzILOpBitVector *update_sr_nz_flags(RzILOpBitVector *new_value, RzILOpBitVector *old_sr_value) { + // the general idea is that we zero out the N and Z bits in the old SR value + // (by ANDing with a mask of all 1s except in those positions) + // and then we OR that resulting value with the new bits in the relevant positions and all 0s everywhere else + + // index of the N flag in the SR register: 2 + RzILOpBool *n_flag_value = MSB(new_value); + RzILOpBitVector *n_or_mask = SHIFTL0(BOOL_TO_BV(n_flag_value, 16), U8(2)); + + // index of the Z flag in the SR register: 1 + RzILOpBool *z_flag_value = IS_ZERO(DUP(new_value)); // must DUP new_value because it has been used before + RzILOpBitVector *z_or_mask = SHIFTL0(BOOL_TO_BV(z_flag_value, 16), U8(1)); + + RzILOpBitVector *and_mask = U16(~(1 << 2 | 1 << 1)); + RzILOpBitVector *or_mask = LOGOR(n_or_mask, z_or_mask); + + return LOGOR(LOGAND(old_sr_value, and_mask), or_mask); +} + +RZ_OWN RzILOpBitVector *update_sr_v_flag_rcc(RzILOpBitVector *old_value, RzILOpBool *old_carry, RzILOpBitVector *old_sr_value) { + // the idea is the same as set_nz_flags_from_result(...): we AND with a mask that zeroes out the bit we care about + // then we OR with a mask that have the bit we care about in the same position that we zeroed + RzILOpBool *v_flag_value = AND( + INV(MSB(old_value)), + old_carry); + + return LOGOR( + LOGAND(old_sr_value, U16(~(1 << 8))), /* zero out position 8, leave all else the same */ + SHIFTL0(BOOL_TO_BV(v_flag_value, 16), U8(8)) /* OR with the new value for the bit */ + ); +} + +RZ_OWN RzILOpBitVector *update_sr_c_flag(RzILOpBool *new_carry, RzILOpBitVector *old_sr_value) { + return LOGOR( + LOGAND(old_sr_value, U16(~1)), /* zero out the 0th position (i.e. the carry bit) */ + BOOL_TO_BV(new_carry, 16) /* OR with the new carry */ + ); +} + +RZ_OWN RzILOpEffect *rz_msp430_lift_rrc_instr(RzAnalysis *analysis, const Msp430Instruction *op) { + // rotation is just a shift with filling + return SEQ6( + /* 1- get the carry (to use later as the filling for the MSB of the operand) */ + SETL("old_sr", MSP430_GETR(MSP430_SR)), + SETL("carry", LSB(VARL("old_sr"))), + /* 2- get the operand (whether register, memory location, ...) */ + SETL("operand", get_destination(op)), + /* 3- Perform the actual Rotate Right through Carry operation. Do: + a- Shift the operand by 1 to the right and fill with carry */ + SETL("result", SHIFTR(VARL("carry"), VARL("operand"), U8(1))), + /* ... b- Set the operand to the value of the previous computation */ + set_destination(op, VARL("operand"), VARL("result")), + /* ... c- Finally set the flags: NZ as usual, v especially for RCC, and the carry flag from the discarded LSB */ + MSP430_SETR(MSP430_SR, update_sr_c_flag(LSB(VARL("operand")), update_sr_v_flag_rcc(VARL("operand"), VARL("carry"), update_sr_nz_flags(VARL("result"), VARL("old_sr")))))); +} + +RZ_OWN RzILOpEffect *rz_msp430_lift_sxt_instr(RzAnalysis *analysis, const Msp430Instruction *op) { + return set_destination(op, NULL, SIGNED(16, get_destination(op))); +} + +RZ_OWN RzILOpEffect *rz_msp430_lift_swpb_instr(RzAnalysis *analysis, const Msp430Instruction *op) { + // 1- get lower byte and upper byte of the operand + BVPair low_high = get_destination_destructured(op); + RzILOpBitVector *low_byte = low_high.first; + RzILOpBitVector *high_byte = low_high.second; + + // 2- append them in reverse order + RzILOpBitVector *result = APPEND(low_byte, high_byte); + + // 3- set them (flags aren't affected) + return set_destination(op, NULL, result); +} + +RzILOpEffect *rz_msp430_lift_todo(RzAnalysis *analysis, const Msp430Instruction *op) { + NOT_IMPLEMENTED; +} + +#include "rz_il/rz_il_opbuilder_end.h" + +static const MSP430InstructionLifter one_op_lifters[] = { + [MSP430_RRC] = rz_msp430_lift_rrc_instr, + [MSP430_SWPB] = rz_msp430_lift_swpb_instr, + [MSP430_RRA] = rz_msp430_lift_todo, // TODO + [MSP430_SXT] = rz_msp430_lift_sxt_instr, + [MSP430_PUSH] = rz_msp430_lift_todo, // TODO + [MSP430_CALL] = rz_msp430_lift_todo, // TODO + [MSP430_RETI] = rz_msp430_lift_todo, // TODO + [MSP430_UNUSED] = rz_msp430_lift_todo // TODO +}; +RZ_OWN RzILOpEffect *rz_msp430_lift_single_operand_instr(RZ_NONNULL RzAnalysis *analysis, const Msp430Instruction *op) { + return one_op_lifters[op->iopcode](analysis, op); +} + +RZ_OWN RzILOpEffect *rz_msp430_lift_double_operand_instr(RZ_NONNULL RzAnalysis *analysis, const Msp430Instruction *op) { + return rz_msp430_lift_todo(analysis, op); +} + +RZ_OWN RzILOpEffect *rz_msp430_lift_jump_instr(RZ_NONNULL RzAnalysis *analysis, const Msp430Instruction *op) { + return rz_msp430_lift_todo(analysis, op); +} + +RZ_OWN RZ_IPI RzILOpEffect *rz_msp430_lift_instr(RZ_NONNULL RzAnalysis *analysis, RZ_NONNULL const Msp430Instruction *op) { + rz_return_val_if_fail(analysis && op, NULL); + + switch (op->itype) { + case MSP430_ONEOP: { + return rz_msp430_lift_single_operand_instr(analysis, op); + } + case MSP430_TWOOP: { + return rz_msp430_lift_double_operand_instr(analysis, op); + } + case MSP430_JUMP: { + return rz_msp430_lift_jump_instr(analysis, op); + } + + // should never happen, op can't be an invalid instruction + default: + rz_warn_if_reached(); + return rz_msp430_dummy(); + } +} + +RZ_OWN RZ_IPI RzAnalysisILConfig *rz_msp430_il_config(RZ_NONNULL RzAnalysis *analysis) { + rz_return_val_if_fail(analysis, NULL); + + RzAnalysisILConfig *ilconf = rz_analysis_il_config_new(PC_SIZE, IS_BIG_ENDIAN, MEM_ADDR_SIZE); + + ilconf->reg_bindings = msp430_register_names; + + return ilconf; +} + +#include "msp430_instruction_parse.inc" diff --git a/librz/arch/isa/msp430/msp430_il.h b/librz/arch/isa/msp430/msp430_il.h new file mode 100644 index 00000000000..0951627868f --- /dev/null +++ b/librz/arch/isa/msp430/msp430_il.h @@ -0,0 +1,50 @@ +// SPDX-FileCopyrightText: 2024 Mostafa Mahmoud +// SPDX-License-Identifier: LGPL-3.0-only + +#ifndef MSP430_IL_H +#define MSP430_IL_H + +#include +#include + +#define WORD_SIZED_READ 1 +#define WORD_SIZED_WRITE 2 + +typedef enum { + SRC_REG, + SRC_INDX, + SRC_SYM, + SRC_ABS, + SRC_IND_REG, + SRC_IND_AUTOINC, + SRC_IMM +} Msp430SourceAddressingMode; + +typedef enum { + DST_REG, + DST_INDX, + DST_SYM, + DST_ABS +} Msp430DestinationAddressingMode; + +typedef struct { + ut8 itype; + ut8 iopcode; + ut8 word_sized; + + Msp430SourceAddressingMode src_mode; + ut32 src; + + Msp430DestinationAddressingMode dst_mode; + ut32 dst; +} Msp430Instruction; + +typedef RzILOpEffect *(*MSP430InstructionLifter)(RzAnalysis *analysis, const Msp430Instruction *op); + +RZ_OWN RZ_IPI RzILOpEffect *rz_msp430_lift_instr(RZ_NONNULL RzAnalysis *analysis, RZ_NONNULL const Msp430Instruction *op); + +RZ_OWN RZ_IPI RzAnalysisILConfig *rz_msp430_il_config(RZ_NONNULL RzAnalysis *analysis); + +void rz_msp430_instruction_from_command(RZ_OUT RZ_NONNULL Msp430Instruction *op, RZ_NONNULL struct msp430_cmd *cmd); + +#endif \ No newline at end of file diff --git a/librz/arch/isa/msp430/msp430_instruction_parse.inc b/librz/arch/isa/msp430/msp430_instruction_parse.inc new file mode 100644 index 00000000000..81297f3e640 --- /dev/null +++ b/librz/arch/isa/msp430/msp430_instruction_parse.inc @@ -0,0 +1,209 @@ +// SPDX-FileCopyrightText: 2024 Mostafa Mahmoud +// SPDX-License-Identifier: LGPL-3.0-only + +#include +#include +#include + +static ut8 parse_word_sizedness(ut8 opcode, char *instr_str); +static Msp430SourceAddressingMode parse_src(char *src_str, RZ_OUT ut32 *src, RZ_OUT int *dst_offset); +static Msp430DestinationAddressingMode parse_dst(char *dst_str, RZ_OUT ut32 *dst); + +void rz_msp430_instruction_from_command(RZ_OUT RZ_NONNULL Msp430Instruction *op, RZ_NONNULL struct msp430_cmd *cmd) { + op->itype = cmd->type; + op->iopcode = cmd->opcode; + op->word_sized = parse_word_sizedness(op->iopcode, cmd->instr); + + if (op->itype == MSP430_TWOOP) { + int dst_offset; + op->src_mode = parse_src(cmd->operands, &(op->src), &dst_offset); + op->dst_mode = parse_dst(cmd->operands + dst_offset, &(op->dst)); + } else if (op->itype == MSP430_ONEOP) { + op->dst_mode = parse_dst(cmd->operands, &(op->dst)); + } +} + +static ut8 parse_word_sizedness(cut8 opcode, char *instr_str) { + // the sign extend is special: it always reads a byte but writes a word + // this is the only reason there are 2 flags for the sizedness of a read and the sizedness of a write + if (opcode == MSP430_SXT) { + return WORD_SIZED_WRITE; + } + + char *curr = instr_str; + while (*curr != '\0' && *curr != '.') { + curr++; + } + + if (*curr == '\0') { + return WORD_SIZED_READ | WORD_SIZED_WRITE; + } + + rz_warn_if_fail(*(curr + 1) == 'b'); + return 0; // byte sized read and write +} + +static ut16 parse_4digit_hex(char *num); +static ut16 parse_immediate(char *imm); +static ut16 parse_reg(char *reg); +static ut16 parse_hex(char *num, int *end_offset); + +static Msp430SourceAddressingMode parse_src(char *src_str, RZ_OUT ut32 *src, RZ_OUT int *dst_offset) { + Msp430SourceAddressingMode m = 0; + ut32 s = 0; + + switch (src_str[0]) { + case '#': { + m = SRC_IMM; + s = parse_immediate(src_str + 1); + break; + } + case '@': { + s = parse_reg(src_str + 1); + // 0 1 2 3 + // @ r 9 + + char maybe_plus = (s < 10) ? src_str[3] : src_str[4]; + if (maybe_plus == '+') { + m = SRC_IND_AUTOINC; + } else { + m = SRC_IND_REG; + } + break; + } + case '&': { + m = SRC_ABS; + s = parse_4digit_hex(src_str + 1); + break; + } + case '0': { + m = SRC_SYM; + int end_offset = 0; + s = parse_hex(src_str, &end_offset); + if (*(src_str + end_offset) == '(') { + m = SRC_INDX; + s = s << 16; + s = s | parse_reg(src_str + end_offset + 1); + } + break; + } + case 'r': { + m = SRC_REG; + s = parse_reg(src_str); + break; + } + default: { + rz_warn_if_reached(); + break; + } + } + + char *curr = src_str; + // skip till the comma seperating the two operands + while (*curr != ',') { + curr++; + } + // skip the comma + curr++; + // skip any spaces after it + while (*curr == ' ') { + curr++; + } + *dst_offset = curr - src_str; + *src = s; + return m; +} + +static Msp430DestinationAddressingMode parse_dst(char *dst_str, RZ_OUT ut32 *dst) { + switch (dst_str[0]) { + case '&': { + *dst = parse_4digit_hex(dst_str + 1); + return DST_ABS; + } + case '0': { + int end_offset = 0; + *dst = parse_hex(dst_str, &end_offset); + if (*(dst_str + end_offset) == '(') { + ut32 d = *dst; + d = d << 16; + d = d | parse_reg(dst_str + end_offset + 1); + *dst = d; + return DST_INDX; + } + return DST_SYM; + } + case 'r': { + *dst = parse_reg(dst_str); + return DST_REG; + } + case 'p': { + rz_return_val_if_fail(dst_str[1] == 'c', -1); + *dst = 0; + return DST_REG; + } + default: { + rz_warn_if_reached(); + return -1; + } + } +} + +static ut16 hex_digit_to_num(char digit) { + // '0'..'9' are in the range of 48-57 + if (digit < 58) { + return digit - 48; + } + // 'A'..'F' are in the range of 65-70 + if (digit < 71) { + return digit - 65 + 10; + } + // the digit must be 'a'..'f', which are in the range of 97-102 + rz_warn_if_fail(digit < 103); + return digit - 97 + 10; +} + +static ut16 parse_4digit_hex(char *num) { + rz_return_val_if_fail(num[0] == '0' && num[1] == 'x', 0); + + ut16 hex_num = 0; + for (ut8 i = 0; i < 4; i++) { + char hex_digit = num[2 + i]; + hex_num = hex_num * 16 + hex_digit_to_num(hex_digit); + } + return hex_num; +} + +static ut16 parse_immediate(char *imm) { + int _; + if (imm[0] == '-') { + return -parse_hex(imm + 1, &_); + } + return parse_hex(imm, &_); +} + +static bool is_digit(char maybe_digit) { + return maybe_digit > 47 && maybe_digit < 58; +} + +static ut16 parse_reg(char *reg) { + rz_return_val_if_fail(reg[0] == 'r', 0); + char first_digit = reg[1]; + ut16 r = first_digit - 48; + char maybe_second_digit = reg[2]; + if (is_digit(maybe_second_digit)) { + r = r * 10 + maybe_second_digit - 48; + } + return r; +} + +static ut16 parse_hex(char *num, int *end_offset) { + rz_return_val_if_fail(num[0] == '0' && num[1] == 'x', 0); + + char *curr = num; + ut16 number = 0; + while (is_digit(*curr)) { + number = number * 16 + hex_digit_to_num(*curr); + curr++; + } + *end_offset = num - curr; + return number; +} diff --git a/librz/arch/meson.build b/librz/arch/meson.build index 5ac1fee5f14..f3d3a16c993 100644 --- a/librz/arch/meson.build +++ b/librz/arch/meson.build @@ -219,6 +219,7 @@ arch_isa_sources = [ 'isa/mcore/mcore.c', 'isa/mips/mips_assembler.c', 'isa/msp430/msp430_disas.c', + 'isa/msp430/msp430_il.c', 'isa/or1k/or1k_disas.c', 'isa/pic/pic_baseline.c', 'isa/pic/pic_midrange.c', diff --git a/librz/arch/p/analysis/analysis_msp430.c b/librz/arch/p/analysis/analysis_msp430.c index 9dff545e3e2..e89c73452cc 100644 --- a/librz/arch/p/analysis/analysis_msp430.c +++ b/librz/arch/p/analysis/analysis_msp430.c @@ -9,13 +9,13 @@ #include #include +#include static int msp430_op(RzAnalysis *analysis, RzAnalysisOp *op, ut64 addr, const ut8 *buf, int len, RzAnalysisOpMask mask) { int ret; struct msp430_cmd cmd; memset(&cmd, 0, sizeof(cmd)); - // op->id = ???; op->size = -1; op->nopcode = 1; op->type = RZ_ANALYSIS_OP_TYPE_UNK; @@ -87,9 +87,42 @@ static int msp430_op(RzAnalysis *analysis, RzAnalysisOp *op, ut64 addr, const ut op->type = RZ_ANALYSIS_OP_TYPE_UNK; } + if (mask & RZ_ANALYSIS_OP_MASK_IL) { + Msp430Instruction instruction; + // parse the instruction structure that the lifter expects from the command + rz_msp430_instruction_from_command(&instruction, &cmd); + RzILOpEffect *il_op = rz_msp430_lift_instr(analysis, &instruction); + op->il_op = il_op; + } + return ret; } +static char *get_reg_profile(RzAnalysis *analysis) { + const char *prof = + "=PC pc\n" + "=SP sp\n" + "=A0 r4\n" + "=A1 r5\n" + "gpr pc .16 0 0\n" + "gpr sp .16 2 0\n" + "gpr sr .16 4 0\n" + "gpr cg .16 6 0\n" + "gpr r4 .16 8 0\n" + "gpr r5 .16 10 0\n" + "gpr r6 .16 12 0\n" + "gpr r7 .16 14 0\n" + "gpr r8 .16 16 0\n" + "gpr r9 .16 18 0\n" + "gpr r10 .16 20 0\n" + "gpr r11 .16 22 0\n" + "gpr r12 .16 24 0\n" + "gpr r13 .16 26 0\n" + "gpr r14 .16 28 0\n" + "gpr r15 .16 30 0\n"; + return strdup(prof); +} + RzAnalysisPlugin rz_analysis_plugin_msp430 = { .name = "msp430", .desc = "TI MSP430 code analysis plugin", @@ -97,4 +130,6 @@ RzAnalysisPlugin rz_analysis_plugin_msp430 = { .arch = "msp430", .bits = 16, .op = msp430_op, + .il_config = rz_msp430_il_config, + .get_reg_profile = get_reg_profile }; diff --git a/test/db/asm/msp430 b/test/db/asm/msp430 index 3766232d717..61740f36b09 100644 --- a/test/db/asm/msp430 +++ b/test/db/asm/msp430 @@ -43,10 +43,13 @@ d "pop r11" 3b41 d "pop r4" 3441 d "push 1" 1312 d "ret" 3041 +d "rrc r4" 0410 0x0 (seq (set old_sr (var sr)) (set carry (lsb (var old_sr))) (set operand (var r4)) (set result (>> (var operand) (bv 8 0x1) (var carry))) (set r4 (var result)) (set sr (| (& (| (& (| (& (var old_sr) (bv 16 0xfff9)) (| (<< (ite (msb (var result)) (bv 16 0x1) (bv 16 0x0)) (bv 8 0x2) false) (<< (ite (is_zero (var result)) (bv 16 0x1) (bv 16 0x0)) (bv 8 0x1) false))) (bv 16 0xfeff)) (<< (ite (&& (! (msb (var operand))) (var carry)) (bv 16 0x1) (bv 16 0x0)) (bv 8 0x8) false)) (bv 16 0xfffe)) (ite (lsb (var operand)) (bv 16 0x1) (bv 16 0x0))))) d "sbc r11" 0b73 d "sbc.b r11" 4b73 d "setc" 12d3 d "setz" 22d3 +d "swpb r15" 8f10 0x0 (set r15 (append (cast 8 false (var r15)) (cast 8 false (>> (var r15) (bv 8 0x8) false)))) +d "sxt pc" 8011 0x0 (set pc (cast 16 (msb (cast 8 false (var pc))) (cast 8 false (var pc)))) d "tst 0x0003" 82930300 d "tst 0x3(r5)" 85930300 d "tst r10" 0a93 diff --git a/test/db/cmd/cmd_list b/test/db/cmd/cmd_list index 66436970289..d781dfdab0f 100644 --- a/test/db/cmd/cmd_list +++ b/test/db/cmd/cmd_list @@ -409,7 +409,7 @@ _dA__ 32 malbolge LGPL3 Malbolge Ternary VM (by condret) _dA__ 32 mcore LGPL3 Motorola MCORE disassembler _d___ 16 mcs96 LGPL3 condrets car adAe_ 16 32 64 mips BSD Capstone MIPS disassembler -_dA__ 16 msp430 LGPL3 msp430 disassembly plugin +_dA_I 16 msp430 LGPL3 msp430 disassembly plugin adA__ 16 32 64 null MIT no disassemble (by pancake) v1.0.0 _dA__ 32 or1k LGPL3 OpenRISC 1000 _dAe_ 8 pic LGPL3 PIC disassembler