arch/tms320: named instruction IDs (TMS320C55_INS_*) from the disassembler

Gives the C55x / C55x+ disassembler a Capstone-style per-instruction
identifier and threads it through to analysis, so the analyzers dispatch
on named operations instead of raw opcode bytes.

What lands
==========

* librz/arch/isa/tms320/tms320c55x_insn.{c,h} -- a shared TMS320C55InsID enum
  (TMS320C55_INS_AADD, TMS320C55_INS_B, TMS320C55_INS_CALL, ...) covering
  C55x and C55x+, plus tms320c55x_insn_name(), tms320c55x_insn_id_from_syntax()
  and tms320c55x_insn_optype(). The prefix is TMS320C55_ / tms320c55x_
  rather than TMS320_ / tms320_ because the other TMS320 families (C54x,
  C64x, C28x) have substantially different instruction sets; the enum,
  the file and these helpers are C55x/C55x+ specific.

* insn_head_t gains an .id field; every head entry in c55x/table.h is
  tagged with its TMS320C55InsID. The disassembler resolves the decoded
  instruction ID from the emitted mnemonic (so multi-form leading bytes
  such as 0x95 -> intr/trap, 0x48 -> ret/reti/rpt, 0x50 -> sftl/popboth
  resolve to the exact instruction, which a static byte->head map cannot).
  tms320_dasm_t gains an insn_id field and tms320_dasm_insn_id() accessor.

* tms320_c55x_insn_id_decode() / tms320_c55x_plus_insn_id_decode() let the
  analyzers resolve the named ID for a byte sequence via a cached decoder
  instance.

* The C55x analyzer's dispatch is rewritten from switch(opcode_byte) to
  switch(TMS320C55InsID). Both analyzers set op->id to the named ID, the
  same way the Capstone-based plugins (e.g. c64x) populate op->id.

* The C55x+ analyzer keeps its byte-level dispatch for control flow,
  stack deltas and operands, but takes the final arithmetic/logical/move/
  multiply/stack type from tms320c55x_insn_optype(op->id). A leading byte on
  C55x+ encodes several instructions (selected by operand bits), so the
  byte switch alone cannot tell ADD from SUB, AND from OR/XOR, or AMOV
  from ASUB; the decoded id can.

Bugs fixed
==========

Driving dispatch / typing from the decoded id fixes a number of latent
mis-classifications the raw-byte switch had masked, verified against the
TI dis55 reference disassembler and the C55x+ documentation:

  - 0x50 0x66 (psh Tx) was typed SHL; now a stack push (corrects
    sym._main's computed stackframe in the rel.stripped.coff test).
  - 0x48 0x05 (reti) was typed REP; now RET.
  - 0x95 0x0F / 0x8F (intr vs trap) distinguished by the decoder.
  - C55x+ 0x7B: byte1 bit7 selects LD (mov) vs add/sub, and within
    add/sub byte2 bit7 selects sub; was always typed add/mov by nibble.
    (TI SWPU104 Table 7-2, opcode 01111011.)
  - C55x+ 0xD2 mar(XDAa op k24): address-register modify, now LEA like
    AADD / AMOV; was typed SUB. (Table 7-2, opcode 11010010.)
  - 57 further C55x+ arith/logical/move/stack contradictions found by
    cross-referencing a Motorola Droid (Wrigley3G) C55x+ baseband dump
    against the decoder are resolved by the optype override.

* DELAY is a memory-delay MOVE per TI SWPU104 sec.6.7.1 (Memory Delay,
  grouped under Move Operations): it copies Smem to Smem+1. Both
  analyzers now mark it as a memory access (width 2, write) and drop the
  spurious FAMILY_CPU (CPU is already the default family).

Tests
=====

New checks assert op->id carries the right TMS320C55_INS_* value on each
CPU, plus regression tests for every byte/decoded-id mismatch fixed above
(c55x: 0x50/0x48/0x95/0xb6; c55x+: 0x7b/0xd2). The c55x opcode-
classification and stackframe expectations are updated to the corrected
output.

Cross-reference
===============

  TI SPRU374    'TMS320C55x DSP Mnemonic Instruction Set Reference Guide'
  TI SWPU086    'TMS320C55x+ DSP Algebraic Instruction Set Reference Guide'
  TI SWPU104    'TMS320C55x+ DSP Mnemonic Instruction Set Reference Guide'
This commit is contained in:
Anton Kochkov
2026-05-28 17:44:54 +08:00
committed by NOT XVilka
parent 6d57743bb6
commit 13ebcbc2c7
10 changed files with 1123 additions and 393 deletions
+262 -381
View File
@@ -9,6 +9,8 @@
#include <rz_analysis.h>
#include "c55x_analysis.h"
#include "../tms320c55x_insn.h"
#include "../tms320_dasm.h"
/**
* \file c55x_analysis.c
@@ -265,169 +267,94 @@ int tms320_c55x_op_byte(RzAnalysis *analysis, RzAnalysisOp *op, ut64 addr,
op_byte &= ~0x01;
}
switch (op_byte) {
/* ---- 0x00: RPTCC k8, cond (3-byte conditional repeat) -------- */
case 0x00:
/* Resolve the named instruction ID from the disassembler (single
* source of truth) and dispatch on it, rather than on the raw opcode
* byte. The disassembler runs the operand mask lists, so multi-form
* leading bytes resolve to the exact mnemonic decoded. Second-byte
* refinements and register-field extraction still read buf[]/op_byte
* directly where the mnemonic alone is insufficient (e.g. picking the
* displacement width for the several B / CALL encodings). */
const ut16 id = tms320c55x_insn_id_decode(buf, len);
op->id = id;
switch (id) {
/* ---- RPTCC k8, cond (3-byte conditional repeat) -------------- */
case TMS320C55_INS_RPTCC:
op->type = RZ_ANALYSIS_OP_TYPE_REP;
op->fail = addr + sz;
break;
/* ---- 0x02: RETCC cond (conditional return) ------------------- */
case 0x02:
/* ---- RETCC cond (conditional return) ------------------------- */
case TMS320C55_INS_RETCC:
set_cret(op);
break;
/* ---- 0x04: BCC k8, cond (8-bit signed relative cond branch) -- */
case 0x04:
if (sz >= 2) {
/* ---- BCC: 8-bit or 16-bit signed relative conditional branch.
* 0x04 / 0x60-0x66 carry an 8-bit displacement at byte 1;
* 0x6F carries a 16-bit BE displacement. ------------------- */
case TMS320C55_INS_BCC:
if (op_byte == 0x6f) {
if (sz >= 4) {
const ut32 disp = rz_read_at_be16(buf, 1);
set_cjmp(op, addr + sz + sign_extend(disp, 16));
}
} else if (sz >= 2) {
set_cjmp(op, addr + sz + sign_extend(buf[1], 8));
}
break;
/* ---- 0x06: B L16 (16-bit relative unconditional) ------------- */
case 0x06:
if (sz >= 3) {
const ut32 disp = rz_read_at_be16(buf, 1);
set_jmp(op, addr + sz + sign_extend(disp, 16));
}
break;
/* ---- 0x08: CALL L16 (16-bit relative call) ------------------- */
case 0x08:
if (sz >= 3) {
const ut32 disp = rz_read_at_be16(buf, 1);
set_call(op, addr + sz + sign_extend(disp, 16));
}
break;
/* ---- 0x0C: RPT k16 ------------------------------------------- */
case 0x0c:
op->type = RZ_ANALYSIS_OP_TYPE_REP;
break;
/* ---- 0x0E: RPTB pmad (block-repeat) -------------------------- */
case 0x0e:
op->type = RZ_ANALYSIS_OP_TYPE_REP;
break;
/* ---- 0x20: NOP (1 byte) -------------------------------------- */
case 0x20:
op->type = RZ_ANALYSIS_OP_TYPE_NOP;
break;
/* ---- 0x21: || nop (parallel marker -- NOT a return) ---------- */
case 0x21:
op->type = RZ_ANALYSIS_OP_TYPE_NOP;
break;
/* ---- 0x38: PSH (single-word push) ---------------------------- */
case 0x38:
set_push(op, 1);
break;
/* ---- 0x3A: POP ----------------------------------------------- */
case 0x3a:
set_pop(op, -1);
break;
/* ---- 0x48: 2-byte multi-form: RPT CSR / RPTADD / RPTSUB / RET / RETI
* differentiated by bits 0-2 of buf[1] (per the table.h
* INSN_MASK(8,3,value) where bit 8 of the 16-bit BE
* instruction is bit 0 of byte 1). The mapping:
* buf[1] & 0x07 == 0 -> RPT CSR
* buf[1] & 0x07 == 1 -> RPTADD CSR, TAx
* buf[1] & 0x07 == 2 -> RPTADD CSR, K4
* buf[1] & 0x07 == 3 -> RPTSUB CSR, K4
* buf[1] & 0x07 == 4 -> RET
* buf[1] & 0x07 == 5 -> RETI
*/
case 0x48:
if (sz >= 2) {
switch (buf[1] & 0x07) {
case 4: /* RET */
case 5: /* RETI */
set_ret(op);
break;
case 0: /* RPT CSR */
case 1: /* RPTADD CSR, TAx */
case 2: /* RPTADD CSR, K4 */
case 3: /* RPTSUB CSR, K4 */
op->type = RZ_ANALYSIS_OP_TYPE_REP;
break;
default:
op->type = RZ_ANALYSIS_OP_TYPE_NOP;
break;
/* ---- B: unconditional branch. Encodings:
* 0x06 16-bit relative, 0x4A 8-bit relative,
* 0x6A 24-bit absolute, 0x91 indirect via ACx. ------------- */
case TMS320C55_INS_B:
if (op_byte == 0x91) {
op->type = RZ_ANALYSIS_OP_TYPE_UJMP;
op->eob = true;
op->fail = addr + sz;
if (sz >= 2) {
set_ireg(op, c55x_acc_names[buf[1] & 0x03]);
}
} else if (op_byte == 0x6a || op_byte == 0x6b) {
if (sz >= 4) {
set_jmp(op, rz_read_at_be24(buf, 1));
}
} else if (op_byte == 0x4a) {
if (sz >= 2) {
set_jmp(op, addr + sz + sign_extend(buf[1], 8));
}
} else { /* 0x06 */
if (sz >= 3) {
const ut32 disp = rz_read_at_be16(buf, 1);
set_jmp(op, addr + sz + sign_extend(disp, 16));
}
}
break;
/* ---- 0x49-0x4F miscellaneous 2-byte forms (most aren't control
* flow). 0x4A is the 2-byte short B; everything else stays
* NULL or is handled below. */
case 0x4a: /* B k8 (8-bit signed relative B) -- 2 bytes */
if (sz >= 2) {
set_jmp(op, addr + sz + sign_extend(buf[1], 8));
}
break;
case 0x4c: /* RPT k8 (3-byte) */
op->type = RZ_ANALYSIS_OP_TYPE_REP;
break;
case 0x4e:
/* AADD K8, SP -- address arithmetic (frame setup).
*
* Semantic: SP = SP + K8 (signed).
*
* rizin's stack-effect convention is that op->stackptr is
* the amount by which SP *decreases* (i.e. the amount the
* stack frame grows). On c55x the stack grows downward, so
* K8 < 0 -> SP decreases -> frame grows -> stackptr = -K8
* K8 > 0 -> SP increases -> frame shrinks -> stackptr = -K8
* which is just stackptr = -K8 in both cases. See
* rz_analysis_op_apply_sp_effect() in librz/arch/op.c. */
op->type = RZ_ANALYSIS_OP_TYPE_ADD;
if (sz >= 2) {
const st8 k8 = (st8)buf[1];
set_imm(op, k8);
set_dst_reg(op, "sp");
set_disp(op, k8);
/* ---- CALL: 0x08 16-bit relative, 0x6C 24-bit absolute,
* 0x92 indirect via ACx. ----------------------------------- */
case TMS320C55_INS_CALL:
if (op_byte == 0x92 || op_byte == 0x93) {
op->type = RZ_ANALYSIS_OP_TYPE_UCALL;
op->fail = addr + sz;
op->stackop = RZ_ANALYSIS_STACK_INC;
op->stackptr = -k8;
op->stackptr = 2;
if (sz >= 2) {
set_ireg(op, c55x_acc_names[buf[1] & 0x03]);
}
} else if (op_byte == 0x6c) {
if (sz >= 4) {
set_call(op, rz_read_at_be24(buf, 1));
}
} else { /* 0x08 */
if (sz >= 3) {
const ut32 disp = rz_read_at_be16(buf, 1);
set_call(op, addr + sz + sign_extend(disp, 16));
}
}
break;
/* ---- 0x60-0x66: BCC variants (8-bit signed relative cond branch).
* Multiple variants encode different condition register
* interpretations but all are conditional jumps with
* 8-bit signed displacement at byte 1. */
case 0x60:
case 0x61:
case 0x62:
case 0x63:
case 0x64:
case 0x65:
case 0x66:
if (sz >= 2) {
set_cjmp(op, addr + sz + sign_extend(buf[1], 8));
}
break;
/* ---- 0x6A / 0x6B: B P24 (24-bit absolute unconditional) ------ */
case 0x6a:
case 0x6b:
if (sz >= 4) {
set_jmp(op, rz_read_at_be24(buf, 1));
}
break;
/* ---- 0x6C: CALL P24 (24-bit absolute call) ------------------- */
case 0x6c:
if (sz >= 4) {
set_call(op, rz_read_at_be24(buf, 1));
}
break;
/* ---- 0x6E: CALLCC P24, cond (conditional 24-bit call) -------- */
case 0x6e:
/* ---- CALLCC P24, cond (conditional 24-bit call) -------------- */
case TMS320C55_INS_CALLCC:
if (sz >= 4) {
/* Format: 6E hh ll cond -- disp is BE16 at offset 1. */
const ut32 disp = rz_read_at_be16(buf, 1);
@@ -435,297 +362,251 @@ int tms320_c55x_op_byte(RzAnalysis *analysis, RzAnalysisOp *op, ut64 addr,
}
break;
/* ---- 0x6F: BCC P24, cond -- alternate cond branch ------------- */
case 0x6f:
if (sz >= 4) {
const ut32 disp = rz_read_at_be16(buf, 1);
set_cjmp(op, addr + sz + sign_extend(disp, 16));
/* ---- RPT / RPTB / RPTADD / RPTSUB (block/single repeat) ------ */
case TMS320C55_INS_RPT:
case TMS320C55_INS_RPTB:
case TMS320C55_INS_RPTADD:
case TMS320C55_INS_RPTSUB:
case TMS320C55_INS_RPTBLOCAL:
op->type = RZ_ANALYSIS_OP_TYPE_REP;
break;
/* ---- RET / RETI: both are returns with -2 stack effect. The
* disassembler resolves the 0x48 family (and any other return
* encodings) to the exact mnemonic, so no second-byte logic is
* needed here. ------------------------------------------------ */
case TMS320C55_INS_RET:
case TMS320C55_INS_RETI:
set_ret(op);
break;
/* ---- NOP ----------------------------------------------------- */
case TMS320C55_INS_NOP:
op->type = RZ_ANALYSIS_OP_TYPE_NOP;
break;
/* ---- PSH variants: 0x38 single push; 0xB5 PSH Smem,
* 0xB7 PSH dbl(Smem), 0xE4 PSH dbl(Lmem). ------------------ */
case TMS320C55_INS_PSH:
if (op_byte == 0x38) {
set_push(op, 1);
} else {
set_push(op, (op_byte == 0xb7 || op_byte == 0xe4) ? 2 : 1);
set_dst_reg(op, "sp");
set_dir(op, RZ_ANALYSIS_OP_DIR_WRITE);
}
break;
/* ---- 0x91: B ACx (indirect register branch) ------------------ */
case 0x91:
op->type = RZ_ANALYSIS_OP_TYPE_UJMP;
op->eob = true;
op->fail = addr + sz;
if (sz >= 2) {
set_ireg(op, c55x_acc_names[buf[1] & 0x03]);
/* ---- POP variants: 0x3A single pop; 0xB8/0xB9 POP dbl(Smem),
* 0xBB POP Smem. ------------------------------------------- */
case TMS320C55_INS_POP:
if (op_byte == 0x3a) {
set_pop(op, -1);
} else {
set_pop(op, (op_byte == 0xbb) ? -1 : -2);
set_dst_reg(op, "sp");
set_dir(op, RZ_ANALYSIS_OP_DIR_READ);
}
break;
/* ---- 0x92 / 0x93: CALL ACx (indirect register call) ---------- */
case 0x92:
case 0x93:
op->type = RZ_ANALYSIS_OP_TYPE_UCALL;
op->fail = addr + sz;
op->stackop = RZ_ANALYSIS_STACK_INC;
op->stackptr = 2;
if (sz >= 2) {
set_ireg(op, c55x_acc_names[buf[1] & 0x03]);
/* ---- PSHBOTH / POPBOTH (dual register stack ops) ------------- */
case TMS320C55_INS_PSHBOTH:
set_push(op, 2);
set_dst_reg(op, "sp");
set_dir(op, RZ_ANALYSIS_OP_DIR_WRITE);
break;
case TMS320C55_INS_POPBOTH:
set_pop(op, -2);
set_dst_reg(op, "sp");
set_dir(op, RZ_ANALYSIS_OP_DIR_READ);
break;
/* ---- AADD: address-arithmetic add. 0x4E is `AADD K8, SP`
* (frame setup); 0x14 is `AADD k16, AC/AR`. --------------- */
case TMS320C55_INS_AADD:
if (op_byte == 0x4e) {
/* SP = SP + K8 (signed). rizin's convention: op->stackptr is
* the amount SP *decreases* (frame growth). On C55x the stack
* grows down, so stackptr = -K8 regardless of sign. See
* rz_analysis_op_apply_sp_effect() in librz/arch/op.c. */
op->type = RZ_ANALYSIS_OP_TYPE_ADD;
if (sz >= 2) {
const st8 k8 = (st8)buf[1];
set_imm(op, k8);
set_dst_reg(op, "sp");
set_disp(op, k8);
op->stackop = RZ_ANALYSIS_STACK_INC;
op->stackptr = -k8;
}
} else { /* 0x14: AADD k16, AC/AR */
op->type = RZ_ANALYSIS_OP_TYPE_LEA;
if (sz >= 3) {
const ut8 src_idx = (buf[1] >> 4) & 0x0f;
const ut8 dst_idx = (buf[2] >> 4) & 0x0f;
set_dst_reg(op, c55x_gpr_names[dst_idx]);
set_ireg(op, c55x_gpr_names[src_idx]);
}
}
break;
/* ---- 0x94: RESET --------------------------------------------- */
case 0x94:
/* ---- RESET --------------------------------------------------- */
case TMS320C55_INS_RESET:
op->type = RZ_ANALYSIS_OP_TYPE_TRAP;
op->family = RZ_ANALYSIS_OP_FAMILY_CPU;
break;
/* ---- 0x95: INTR #k5 or TRAP #k5 (bit 7 of buf[1] selects) ---- */
case 0x95:
/* ---- INTR #k5 / TRAP #k5: the disassembler distinguishes the two
* (bit 7 of buf[1]); use the resolved ID for the type and read
* the 5-bit immediate from buf[1]. ------------------------- */
case TMS320C55_INS_INTR:
case TMS320C55_INS_TRAP:
op->type = (id == TMS320C55_INS_TRAP) ? RZ_ANALYSIS_OP_TYPE_TRAP : RZ_ANALYSIS_OP_TYPE_SWI;
if (sz >= 2) {
const ut8 k5 = buf[1] & 0x1f;
if (buf[1] & 0x80) {
op->type = RZ_ANALYSIS_OP_TYPE_TRAP;
} else {
op->type = RZ_ANALYSIS_OP_TYPE_SWI;
}
set_imm(op, k5);
op->family = RZ_ANALYSIS_OP_FAMILY_CPU;
set_imm(op, buf[1] & 0x1f);
}
op->family = RZ_ANALYSIS_OP_FAMILY_CPU;
break;
/* ---- 0x96 / 0x97 / 0x9E / 0x9F: XCC (predicated execute) ----- */
case 0x96:
case 0x97:
case 0x9e:
case 0x9f:
/* ---- XCC / XCCPART (predicated execute) ---------------------- */
case TMS320C55_INS_XCC:
case TMS320C55_INS_XCCPART:
op->type = RZ_ANALYSIS_OP_TYPE_CMP;
op->family = RZ_ANALYSIS_OP_FAMILY_CPU;
break;
/* ---- PSH/POP variants ---------------------------------------- */
case 0xb5:
case 0xb7:
case 0xe4:
/* PSH variants: 0xb5 PSH Smem, 0xb7 PSH dbl(Smem),
* 0xe4 PSH dbl(Lmem). All decrement SP. */
set_push(op, (op_byte == 0xb7 || op_byte == 0xe4) ? 2 : 1);
set_dst_reg(op, "sp");
set_dir(op, RZ_ANALYSIS_OP_DIR_WRITE);
break;
case 0xb8:
case 0xb9:
case 0xbb:
/* POP variants: 0xb8 / 0xb9 POP dbl(Smem),
* 0xbb POP Smem. All increment SP. */
set_pop(op, (op_byte == 0xbb) ? -1 : -2);
set_dst_reg(op, "sp");
set_dir(op, RZ_ANALYSIS_OP_DIR_READ);
break;
/* ---- Common arithmetic/logical/move bytes ------------------- */
case 0x10:
case 0x18:
case 0x28:
case 0x72:
case 0x7d:
case 0xd9:
case 0xf4:
/* ---- Logical AND family -------------------------------------- */
case TMS320C55_INS_AND:
case TMS320C55_INS_BAND:
case TMS320C55_INS_BTST:
case TMS320C55_INS_BTSTSET:
op->type = RZ_ANALYSIS_OP_TYPE_AND;
break;
case 0x1a:
case 0x2a:
case 0x73:
case 0x7e:
case 0xda:
case 0xf5:
/* ---- Logical OR ---------------------------------------------- */
case TMS320C55_INS_OR:
op->type = RZ_ANALYSIS_OP_TYPE_OR;
break;
case 0x1c:
case 0x2c:
case 0x74:
case 0x7f:
case 0xdb:
case 0xf6:
/* ---- Logical XOR --------------------------------------------- */
case TMS320C55_INS_XOR:
op->type = RZ_ANALYSIS_OP_TYPE_XOR;
break;
case 0x12:
case 0xf0:
case 0xf1:
/* ---- Compare family ------------------------------------------ */
case TMS320C55_INS_CMP:
case TMS320C55_INS_CMPAND:
case TMS320C55_INS_CMPOR:
case TMS320C55_INS_MAX:
case TMS320C55_INS_MIN:
op->type = RZ_ANALYSIS_OP_TYPE_CMP;
break;
case 0x14:
/* AADD k16, AC/AR -- address-arithmetic add.
* Encoding: 14 <src_high_nib>0 <dst_high_nib>0.
* Both register fields are 4 bits in the high nibble of
* buf[1] / buf[2]; see c55x_gpr_names[] for the map. */
op->type = RZ_ANALYSIS_OP_TYPE_LEA;
if (sz >= 3) {
const ut8 src_idx = (buf[1] >> 4) & 0x0f;
const ut8 dst_idx = (buf[2] >> 4) & 0x0f;
set_dst_reg(op, c55x_gpr_names[dst_idx]);
set_ireg(op, c55x_gpr_names[src_idx]);
/* ---- MOV family ---------------------------------------------- */
case TMS320C55_INS_MOV:
/* 0xA0-0xAF: MOV Smem, REG (LOAD); 0xC0-0xCF: MOV REG, Smem
* (STORE). Both extract the register from the low nibble and,
* for the SP-relative form (bit 0 of buf[1] clear), set a
* *sp(#disp) operand. Other MOV-encoding bytes set only type. */
op->type = RZ_ANALYSIS_OP_TYPE_MOV;
if (op_byte >= 0xa0 && op_byte <= 0xaf) {
set_dst_reg(op, c55x_gpr_names[op_byte & 0x0f]);
set_dir(op, RZ_ANALYSIS_OP_DIR_READ);
set_mem_width(op, 2);
if (sz >= 2 && !(buf[1] & 0x01)) {
set_ireg(op, "sp");
set_disp(op, buf[1] >> 1);
}
} else if (op_byte >= 0xc0 && op_byte <= 0xcf) {
set_dst_reg(op, c55x_gpr_names[op_byte & 0x0f]);
set_dir(op, RZ_ANALYSIS_OP_DIR_WRITE);
set_mem_width(op, 2);
if (sz >= 2 && !(buf[1] & 0x01)) {
set_ireg(op, "sp");
set_disp(op, buf[1] >> 1);
}
}
break;
case 0x16:
case 0x22:
case 0x3c:
case 0x3e:
case 0x44:
case 0x52:
case 0x75:
case 0x78:
/* MOV-family bytes that don't have a uniform low-nibble
* register encoding -- type only. */
/* ---- Bit set/clear (treated as MOV-like field writes) -------- */
case TMS320C55_INS_BCLR:
case TMS320C55_INS_BSET:
case TMS320C55_INS_BFXTR:
op->type = RZ_ANALYSIS_OP_TYPE_MOV;
break;
case 0xa0:
case 0xa1:
case 0xa2:
case 0xa3:
case 0xa4:
case 0xa5:
case 0xa6:
case 0xa7:
case 0xa8:
case 0xa9:
case 0xaa:
case 0xab:
case 0xac:
case 0xad:
case 0xae:
case 0xaf:
/* MOV Smem, REG -- LOAD-family. Low nibble of the leading
* byte selects the destination register; for the SP-relative
* common case (bit 0 of buf[1] clear), the source memory
* operand is `*sp(#disp)` where disp = buf[1]>>1. */
/* ---- DELAY: memory-delay move. Per TI SWPU104 sec.6.7.1 (Memory
* Delay, grouped under "Move Operations"), delay(Smem) copies the
* content of Smem to the next-higher address Smem+1 -- a one-word
* memory-to-memory data shift used to implement delay lines in
* filters. It is a data MOV (one data read + one data write), not
* a CPU/system-control instruction, so it gets the default
* family rather than FAMILY_CPU. ----------------------------- */
case TMS320C55_INS_DELAY:
op->type = RZ_ANALYSIS_OP_TYPE_MOV;
set_dst_reg(op, c55x_gpr_names[op_byte & 0x0f]);
set_dir(op, RZ_ANALYSIS_OP_DIR_READ);
set_mem_width(op, 2);
if (sz >= 2 && !(buf[1] & 0x01)) {
set_ireg(op, "sp");
set_disp(op, buf[1] >> 1);
}
break;
case 0xc0:
case 0xc1:
case 0xc2:
case 0xc3:
case 0xc4:
case 0xc5:
case 0xc6:
case 0xc7:
case 0xc8:
case 0xc9:
case 0xca:
case 0xcb:
case 0xcc:
case 0xcd:
case 0xce:
case 0xcf:
/* MOV REG, Smem -- STORE-family. Low nibble of the leading
* byte selects the source register; for the SP-relative
* common case, the destination memory operand is
* `*sp(#disp)` where disp = buf[1]>>1. */
op->type = RZ_ANALYSIS_OP_TYPE_MOV;
set_dst_reg(op, c55x_gpr_names[op_byte & 0x0f]);
set_dir(op, RZ_ANALYSIS_OP_DIR_WRITE);
set_mem_width(op, 2);
if (sz >= 2 && !(buf[1] & 0x01)) {
set_ireg(op, "sp");
set_disp(op, buf[1] >> 1);
}
break;
case 0x24:
case 0x40:
case 0x5a:
case 0x70:
case 0x7a:
case 0x7b:
case 0x81:
case 0xd6:
/* ---- Add family ---------------------------------------------- */
case TMS320C55_INS_ADD:
case TMS320C55_INS_ADDSUBCC:
op->type = RZ_ANALYSIS_OP_TYPE_ADD;
break;
case 0x26:
case 0x42:
case 0x71:
case 0x7c:
case 0xd7:
case 0xd8:
/* ---- Subtract / negate --------------------------------------- */
case TMS320C55_INS_SUB:
case TMS320C55_INS_NEG:
op->type = RZ_ANALYSIS_OP_TYPE_SUB;
break;
case 0x2e: /* MAX */
case 0x30: /* MIN */
op->type = RZ_ANALYSIS_OP_TYPE_CMP;
break;
case 0x32: /* ABS -- see comment in c55x_plus analyzer */
op->type = RZ_ANALYSIS_OP_TYPE_NULL;
break;
case 0x34: /* NEG */
op->type = RZ_ANALYSIS_OP_TYPE_SUB;
break;
case 0x36: /* NOT */
/* ---- Bitwise NOT --------------------------------------------- */
case TMS320C55_INS_NOT:
op->type = RZ_ANALYSIS_OP_TYPE_NOT;
break;
case 0x46: /* BCLR */
case 0xec: /* BSET */
op->type = RZ_ANALYSIS_OP_TYPE_MOV;
break;
case 0x50:
case 0x5c: /* SFTL */
/* ---- Shifts -------------------------------------------------- */
case TMS320C55_INS_SFTL:
op->type = RZ_ANALYSIS_OP_TYPE_SHL;
break;
case 0x54: /* ADDV */
op->type = RZ_ANALYSIS_OP_TYPE_ADD;
break;
case 0x56:
case 0x83: /* MAC */
case 0x58:
case 0x82:
case 0xfd: /* MPY */
case 0x1e:
case 0x79: /* MPYK */
case 0x84: /* MAS */
case 0x86:
case 0x87:
case 0xd1:
case 0xd3: /* MPYM */
case 0xd0: /* MACMZ */
case 0xd2:
case 0xd4: /* MACM */
case 0xd5: /* MASM */
case 0xf8: /* MPYMK */
/* ---- Multiply / MAC family ----------------------------------- */
case TMS320C55_INS_MAC:
case TMS320C55_INS_MACM:
case TMS320C55_INS_MASM:
case TMS320C55_INS_MAS:
case TMS320C55_INS_MPY:
case TMS320C55_INS_MPYK:
case TMS320C55_INS_MPYM:
case TMS320C55_INS_MPYMK:
case TMS320C55_INS_SQDST:
case TMS320C55_INS_LMSF:
op->type = RZ_ANALYSIS_OP_TYPE_MUL;
break;
case 0x5e: /* SWAP */
/* ---- SWAP (register exchange) -------------------------------- */
case TMS320C55_INS_SWAP:
op->type = RZ_ANALYSIS_OP_TYPE_XCHG;
break;
case 0x76: /* BFXTR */
op->type = RZ_ANALYSIS_OP_TYPE_MOV;
break;
case 0x77: /* AMOV */
case 0x85:
case 0xb4: /* AMAR */
/* ---- Address-register moves (LEA-like) ----------------------- */
case TMS320C55_INS_AMOV:
case TMS320C55_INS_AMAR:
case TMS320C55_INS_FIRSADD:
op->type = RZ_ANALYSIS_OP_TYPE_LEA;
break;
case 0xb6: /* DELAY */
op->type = RZ_ANALYSIS_OP_TYPE_MOV;
op->family = RZ_ANALYSIS_OP_FAMILY_CPU;
break;
case 0xdc:
case 0xe0: /* BTST */
op->type = RZ_ANALYSIS_OP_TYPE_AND;
break;
case 0xe3: /* BTSTSET */
op->type = RZ_ANALYSIS_OP_TYPE_AND;
break;
case 0xde: /* ADDSUBCC */
op->type = RZ_ANALYSIS_OP_TYPE_ADD;
break;
case 0xf2:
case 0xf3: /* BAND */
op->type = RZ_ANALYSIS_OP_TYPE_AND;
/* ---- ABS: leave NULL (no precise rizin type; see C55x+ note) - */
case TMS320C55_INS_ABS:
op->type = RZ_ANALYSIS_OP_TYPE_NULL;
break;
default:
/* Unknown leading byte. Mark as NULL so the analyzer can
* keep walking but won't merge it into a basic block. The
* size=1 default in c55x_op_size() means we advance one
* byte and try the next one -- useful for parallel-prefixed
* instructions where the leading 0x01/0x03/0x05/... bytes
* are themselves the parallel marker. */
/* Unknown / untyped instruction. Mark as NULL so the analyzer
* keeps walking but won't merge it into a basic block. The
* size=1 default in c55x_op_size() means we advance one byte
* and re-sync -- useful for parallel-prefixed instructions
* whose leading 0x01/0x03/0x05/... bytes are the parallel
* marker. */
break;
}
File diff suppressed because it is too large Load Diff
@@ -10,6 +10,8 @@
#include "c55plus_analysis.h"
#include "ins.h"
#include "../tms320c55x_insn.h"
#include "../tms320_dasm.h"
/**
* \file c55plus_analysis.c
@@ -205,6 +207,14 @@ int tms320_c55x_plus_op(RzAnalysis *analysis, RzAnalysisOp *op, ut64 addr,
op->size = ins_len;
op->type = RZ_ANALYSIS_OP_TYPE_NULL;
/* Surface the named instruction ID (Capstone-style op->id), resolved
* from the C55x+ token decoder's decoded mnemonic. The type-level
* dispatch below stays byte-driven because C55x+ packs many distinct
* instructions behind each leading byte (disambiguated by operand
* bits the analyzer already inspects); the ID gives consumers the
* exact mnemonic without re-deriving it. */
op->id = tms320c55x_plus_insn_id_decode(buf, len);
switch (buf[0]) {
/* ---- 0x00 family: NOP_16 / IDLE / RETI / to_word --------------- */
case 0x00:
@@ -380,10 +390,16 @@ int tms320_c55x_plus_op(RzAnalysis *analysis, RzAnalysisOp *op, ut64 addr,
op->type = RZ_ANALYSIS_OP_TYPE_MOV;
break;
/* ---- 0x60: DELAY --------------------------------------------- */
/* ---- 0x60: DELAY -- memory-delay move (TI SWPU104 sec.6.7.1,
* "Memory Delay", grouped under Move Operations). delay(Smem)
* copies the word at Smem to the next-higher address Smem+1 (a
* one-word memory-to-memory data shift used to build delay lines
* in filters): one data read, one data write. It is a data MOV,
* not a CPU/system-control op, so no FAMILY_CPU. -------------- */
case 0x60:
op->type = RZ_ANALYSIS_OP_TYPE_MOV;
op->family = RZ_ANALYSIS_OP_FAMILY_CPU;
set_mem_width(op, 2);
set_dir(op, RZ_ANALYSIS_OP_DIR_WRITE);
break;
/* ---- 0x61: PSH dbl(mem) -------------------------------------- */
@@ -497,14 +513,31 @@ int tms320_c55x_plus_op(RzAnalysis *analysis, RzAnalysisOp *op, ut64 addr,
op->type = RZ_ANALYSIS_OP_TYPE_MOV;
break;
/* ---- 0x7B: ADD #k7, ACx (b1 0x00-0x0F) or MOV #k8, Tx (0xB0-0xBF) */
/* ---- 0x7B: Ra = Ra +/- k4 | Ra <</>> #1 | Ra = k4 -------------
* Per TI SWPU104 Table 7-2 (opcode 01111011):
* byte1 bit7 (0x80) set -> LD (Ra = k4) -> MOV
* byte1 bit7 (0x80) clear -> arithmetic/shift on Ra, where
* byte2 bit4 (0x10) set -> shift by 1 (SFTA) -> SHL
* byte2 bit4 (0x10) clr -> add/sub k4, with
* byte2 bit7 (0x80) set -> SUB else ADD
* The k4 immediate is the low nibble of byte2. */
case 0x7b:
if (ins_len >= 3) {
set_imm(op, buf[2]);
if ((buf[1] & 0xf0) == 0xb0) {
if (buf[1] & 0x80) {
/* LD Ra, #k4 -- load immediate */
op->type = RZ_ANALYSIS_OP_TYPE_MOV;
set_imm(op, buf[2] & 0x0f);
} else if (buf[2] & 0x10) {
/* SFTA Ra, #1 -- arithmetic shift by one */
op->type = (buf[2] & 0x80) ? RZ_ANALYSIS_OP_TYPE_SHR : RZ_ANALYSIS_OP_TYPE_SHL;
} else if (buf[2] & 0x80) {
/* SUB Ra, #k4 */
op->type = RZ_ANALYSIS_OP_TYPE_SUB;
set_imm(op, buf[2] & 0x0f);
} else {
/* ADD Ra, #k4 */
op->type = RZ_ANALYSIS_OP_TYPE_ADD;
set_imm(op, buf[2] & 0x0f);
}
} else {
op->type = RZ_ANALYSIS_OP_TYPE_MOV;
@@ -652,8 +685,18 @@ int tms320_c55x_plus_op(RzAnalysis *analysis, RzAnalysisOp *op, ut64 addr,
case 0xcb: op->type = RZ_ANALYSIS_OP_TYPE_MUL; break;
case 0xce: op->type = RZ_ANALYSIS_OP_TYPE_MUL; break; /* SQDST */
case 0xd1: op->type = RZ_ANALYSIS_OP_TYPE_MOV; break; /* COPY */
case 0xd2: op->type = RZ_ANALYSIS_OP_TYPE_SUB; break;
case 0xd1:
op->type = RZ_ANALYSIS_OP_TYPE_MOV;
break; /* COPY */
/* ---- 0xD2: mar(XDAa op k24) -- modify extended address register.
* Per TI SWPU104 Table 7-2 (opcode 11010010), byte1 bits 6-5 select
* 00 -> mar(XDAa - k24) (ASUB)
* 01 -> mar(XDAa + k24) (AMOV/AADD with +)
* 10 -> mar(XDAa = k24) (AMOV load)
* All are address-register arithmetic/loads, classified LEA (the
* same family as 0x14 AADD and 0x77 AMOV). The earlier code typed
* this SUB, which mis-classified the load and add forms. */
case 0xd2: op->type = RZ_ANALYSIS_OP_TYPE_LEA; break;
case 0xd4:
op->type = RZ_ANALYSIS_OP_TYPE_CMP;
break; /* MAXDIFF */
@@ -802,5 +845,20 @@ int tms320_c55x_plus_op(RzAnalysis *analysis, RzAnalysisOp *op, ut64 addr,
break;
}
/* The byte-level switch above handles control flow (targets, fail
* paths), stack deltas and operand fields, but it cannot always tell
* apart instructions that share a leading byte but differ by operand
* bits (e.g. ADD vs SUB, AND vs OR vs XOR, AMOV vs ASUB, PSH vs POP).
* The decoder already resolved the exact mnemonic, so for the
* arithmetic / logical / move / multiply / stack families take the
* authoritative type from the instruction id. Control flow, repeats,
* XCC and the like map to NULL here and keep the type the byte switch
* assigned. Stack-effect bookkeeping (stackop/stackptr) set above is
* preserved. */
const int id_type = tms320c55x_insn_optype((TMS320C55InsID)op->id);
if (id_type != RZ_ANALYSIS_OP_TYPE_NULL) {
op->type = id_type;
}
return op->size;
}
+60
View File
@@ -11,6 +11,7 @@
/* private headers */
#include "tms320_dasm.h"
#include "tms320c55x_insn.h"
#include "c55x_plus/c55plus.h"
@@ -1168,6 +1169,20 @@ int tms320_dasm(tms320_dasm_t *dasm, const ut8 *stream, int len) {
if (dasm->status & TMS320_S_INVAL) {
strcpy(dasm->syntax, "invalid"), dasm->length = 1;
dasm->insn_id = TMS320C55_INS_INVALID;
} else {
/* Resolve the instruction ID from the decoded mnemonic. The
* syntax string is the disassembler's ground truth: it reflects
* the exact sub-instruction selected by the operand mask lists,
* which a static leading-byte->head mapping cannot capture (e.g.
* 0x95 0x8F decodes to TRAP, not INTR; 0x48 0x05 to RETI, not
* RPT). Falls back to the matched table head's id for C55x when
* the mnemonic isn't in the table (should not normally happen). */
dasm->insn_id = tms320c55x_insn_id_from_syntax(dasm->syntax);
if (dasm->insn_id == TMS320C55_INS_INVALID &&
tms320_f_get_cpu(dasm) != TMS320_F_CPU_C55X_PLUS && dasm->head) {
dasm->insn_id = dasm->head->id;
}
}
return full_insn_size(dasm);
@@ -1176,6 +1191,51 @@ int tms320_dasm(tms320_dasm_t *dasm, const ut8 *stream, int len) {
// insn_head_t c55x_list[]
#include "c55x/table.h"
/* Resolve the C55x instruction ID for a byte sequence by running the
* disassembler and mapping its decoded mnemonic to a TMS320C55InsID. This
* is the accurate path: unlike a static leading-byte table, it honours
* the per-instruction mask lists, so multi-form heads (e.g. 0x50, which
* decodes to either POPBOTH or SFTL depending on operand bits) resolve
* to the mnemonic actually decoded.
*
* A single cached dasm instance is used to avoid per-call init/fini. The
* cache is process-wide and the disassembler is stateless across calls
* (init_dasm() resets it each time), so this is safe for the analyzer's
* single-threaded use. */
ut16 tms320c55x_insn_id_decode(const ut8 *buf, int len) {
static tms320_dasm_t dasm;
static int initialized = 0;
if (!initialized) {
tms320_dasm_init(&dasm);
tms320_f_set_cpu(&dasm, TMS320_F_CPU_C55X);
initialized = 1;
}
if (!buf || len < 1) {
return TMS320C55_INS_INVALID;
}
tms320_dasm(&dasm, buf, len);
return dasm.insn_id;
}
/* C55x+ counterpart of tms320c55x_insn_id_decode(): run the C55x+
* token decoder over the byte sequence and return the TMS320C55InsID of the
* decoded mnemonic. Uses a separate cached dasm instance pinned to the
* C55x+ feature set. */
ut16 tms320c55x_plus_insn_id_decode(const ut8 *buf, int len) {
static tms320_dasm_t dasm;
static int initialized = 0;
if (!initialized) {
tms320_dasm_init(&dasm);
tms320_f_set_cpu(&dasm, TMS320_F_CPU_C55X_PLUS);
initialized = 1;
}
if (!buf || len < 1) {
return TMS320C55_INS_INVALID;
}
tms320_dasm(&dasm, buf, len);
return dasm.insn_id;
}
int tms320_dasm_init(tms320_dasm_t *dasm) {
int i = 0;
+13
View File
@@ -104,6 +104,7 @@ struct tms320_instruction_flag {
struct tms320_instruction_head {
ut8 byte;
ut8 size;
ut16 id; ///< TMS320C55InsID for this leading-byte entry (0 = unset/invalid)
insn_item_t insn;
};
@@ -124,6 +125,7 @@ typedef struct {
#define TMS320_S_INVAL 0x01
ut8 status;
ut8 length;
ut16 insn_id; ///< TMS320C55InsID of the decoded instruction (0 if unknown)
char syntax[1024];
#define def_field(name, size) \
@@ -245,6 +247,17 @@ typedef struct {
extern int tms320_dasm(tms320_dasm_t *, const ut8 *, int);
/** Resolve the C55x instruction ID for a byte sequence by disassembling
* it and mapping the decoded mnemonic to a TMS320C55InsID. Honours the
* per-instruction operand masks (so multi-form leading bytes resolve to
* the mnemonic actually decoded). Returns 0 when the bytes don't decode
* to a known instruction. */
extern ut16 tms320c55x_insn_id_decode(const ut8 *buf, int len);
/** C55x+ counterpart of tms320c55x_insn_id_decode(). Resolves the
* TMS320C55InsID by running the C55x+ token decoder over the bytes. */
extern ut16 tms320c55x_plus_insn_id_decode(const ut8 *buf, int len);
extern int tms320_dasm_init(tms320_dasm_t *);
extern int tms320_dasm_fini(tms320_dasm_t *);
+270
View File
@@ -0,0 +1,270 @@
// SPDX-FileCopyrightText: 2026 RizinOrg <[email protected]>
// SPDX-License-Identifier: LGPL-3.0-only
#include <string.h>
#include <rz_analysis.h>
#include "tms320c55x_insn.h"
/* Mnemonic strings indexed by TMS320C55InsID. Keep in sync with the enum
* in tms320c55x_insn.h. */
static const char *const tms320c55x_insn_names[] = {
[TMS320C55_INS_INVALID] = "invalid",
[TMS320C55_INS_AADD] = "aadd",
[TMS320C55_INS_ABDST] = "abdst",
[TMS320C55_INS_ABS] = "abs",
[TMS320C55_INS_ADD] = "add",
[TMS320C55_INS_ADDSUB] = "addsub",
[TMS320C55_INS_ADDSUB2CC] = "addsub2cc",
[TMS320C55_INS_ADDSUBCC] = "addsubcc",
[TMS320C55_INS_AMAR] = "amar",
[TMS320C55_INS_AMOV] = "amov",
[TMS320C55_INS_AND] = "and",
[TMS320C55_INS_ASUB] = "asub",
[TMS320C55_INS_B] = "b",
[TMS320C55_INS_BAND] = "band",
[TMS320C55_INS_BCC] = "bcc",
[TMS320C55_INS_BCLR] = "bclr",
[TMS320C55_INS_BCNT] = "bcnt",
[TMS320C55_INS_BFXPA] = "bfxpa",
[TMS320C55_INS_BFXTR] = "bfxtr",
[TMS320C55_INS_BNOT] = "bnot",
[TMS320C55_INS_BSET] = "bset",
[TMS320C55_INS_BTST] = "btst",
[TMS320C55_INS_BTSTCLR] = "btstclr",
[TMS320C55_INS_BTSTNOT] = "btstnot",
[TMS320C55_INS_BTSTP] = "btstp",
[TMS320C55_INS_BTSTSET] = "btstset",
[TMS320C55_INS_CALL] = "call",
[TMS320C55_INS_CALLCC] = "callcc",
[TMS320C55_INS_CIRC] = "circ",
[TMS320C55_INS_CMP] = "cmp",
[TMS320C55_INS_CMPAND] = "cmpand",
[TMS320C55_INS_CMPOR] = "cmpor",
[TMS320C55_INS_DELAY] = "delay",
[TMS320C55_INS_DMAXDIFF] = "dmaxdiff",
[TMS320C55_INS_DMINDIFF] = "dmindiff",
[TMS320C55_INS_EXP] = "exp",
[TMS320C55_INS_FIRSADD] = "firsadd",
[TMS320C55_INS_FIRSSUB] = "firssub",
[TMS320C55_INS_IDLE] = "idle",
[TMS320C55_INS_INTR] = "intr",
[TMS320C55_INS_LMS] = "lms",
[TMS320C55_INS_LMSF] = "lmsf",
[TMS320C55_INS_MAC] = "mac",
[TMS320C55_INS_MACK] = "mack",
[TMS320C55_INS_MACM] = "macm",
[TMS320C55_INS_MACMK] = "macmk",
[TMS320C55_INS_MANT] = "mant",
[TMS320C55_INS_MAS] = "mas",
[TMS320C55_INS_MASM] = "masm",
[TMS320C55_INS_MAX] = "max",
[TMS320C55_INS_MAXDIFF] = "maxdiff",
[TMS320C55_INS_MIN] = "min",
[TMS320C55_INS_MINDIFF] = "mindiff",
[TMS320C55_INS_MOV] = "mov",
[TMS320C55_INS_MPY] = "mpy",
[TMS320C55_INS_MPYK] = "mpyk",
[TMS320C55_INS_MPYM] = "mpym",
[TMS320C55_INS_MPYMK] = "mpymk",
[TMS320C55_INS_NEG] = "neg",
[TMS320C55_INS_NOP] = "nop",
[TMS320C55_INS_NOT] = "not",
[TMS320C55_INS_OR] = "or",
[TMS320C55_INS_POP] = "pop",
[TMS320C55_INS_POPBOTH] = "popboth",
[TMS320C55_INS_PSH] = "psh",
[TMS320C55_INS_PSHBOTH] = "pshboth",
[TMS320C55_INS_RESET] = "reset",
[TMS320C55_INS_RET] = "ret",
[TMS320C55_INS_RETCC] = "retcc",
[TMS320C55_INS_RETI] = "reti",
[TMS320C55_INS_ROL] = "rol",
[TMS320C55_INS_ROR] = "ror",
[TMS320C55_INS_ROUND] = "round",
[TMS320C55_INS_RPT] = "rpt",
[TMS320C55_INS_RPTADD] = "rptadd",
[TMS320C55_INS_RPTB] = "rptb",
[TMS320C55_INS_RPTBLOCAL] = "rptblocal",
[TMS320C55_INS_RPTCC] = "rptcc",
[TMS320C55_INS_RPTSUB] = "rptsub",
[TMS320C55_INS_SAT] = "sat",
[TMS320C55_INS_SFTCC] = "sftcc",
[TMS320C55_INS_SFTL] = "sftl",
[TMS320C55_INS_SFTS] = "sfts",
[TMS320C55_INS_SFTSC] = "sftsc",
[TMS320C55_INS_SIM_TRIG] = "sim_trig",
[TMS320C55_INS_SQA] = "sqa",
[TMS320C55_INS_SQAM] = "sqam",
[TMS320C55_INS_SQDST] = "sqdst",
[TMS320C55_INS_SQR] = "sqr",
[TMS320C55_INS_SQRM] = "sqrm",
[TMS320C55_INS_SQS] = "sqs",
[TMS320C55_INS_SQSM] = "sqsm",
[TMS320C55_INS_SUB] = "sub",
[TMS320C55_INS_SUBADD] = "subadd",
[TMS320C55_INS_SUBC] = "subc",
[TMS320C55_INS_SWAP] = "swap",
[TMS320C55_INS_TRAP] = "trap",
[TMS320C55_INS_XCC] = "xcc",
[TMS320C55_INS_XCCPART] = "xccpart",
[TMS320C55_INS_XOR] = "xor",
};
RZ_API const char *tms320c55x_insn_name(TMS320C55InsID id) {
if ((size_t)id < (sizeof(tms320c55x_insn_names) / sizeof(tms320c55x_insn_names[0])) && tms320c55x_insn_names[id]) {
return tms320c55x_insn_names[id];
}
return "invalid";
}
RZ_API TMS320C55InsID tms320c55x_insn_id_from_syntax(const char *syntax) {
if (!syntax) {
return TMS320C55_INS_INVALID;
}
/* Skip leading spaces and an optional C55x+ parallel marker. */
const char *p = syntax;
while (*p == ' ' || *p == '\t') {
p++;
}
if (p[0] == '|' && p[1] == '|') {
p += 2;
while (*p == ' ' || *p == '\t') {
p++;
}
}
/* Extract the leading mnemonic token (lowercase letters, digits, _). */
char tok[32];
size_t n = 0;
while (n < sizeof(tok) - 1 && ((p[n] >= 'a' && p[n] <= 'z') || (p[n] >= '0' && p[n] <= '9') || p[n] == '_')) {
tok[n] = p[n];
n++;
}
tok[n] = '\0';
if (n == 0) {
return TMS320C55_INS_INVALID;
}
for (size_t i = 1; i < (sizeof(tms320c55x_insn_names) / sizeof(tms320c55x_insn_names[0])); i++) {
if (tms320c55x_insn_names[i] && !strcmp(tms320c55x_insn_names[i], tok)) {
return (TMS320C55InsID)i;
}
}
return TMS320C55_INS_INVALID;
}
RZ_API int tms320c55x_insn_optype(TMS320C55InsID id) {
switch (id) {
case TMS320C55_INS_ADD:
case TMS320C55_INS_AADD:
case TMS320C55_INS_ADDSUB:
case TMS320C55_INS_ADDSUBCC:
case TMS320C55_INS_ADDSUB2CC:
case TMS320C55_INS_SUBADD:
return RZ_ANALYSIS_OP_TYPE_ADD;
case TMS320C55_INS_SUB:
case TMS320C55_INS_SUBC:
case TMS320C55_INS_NEG:
return RZ_ANALYSIS_OP_TYPE_SUB;
case TMS320C55_INS_AND:
case TMS320C55_INS_BAND:
case TMS320C55_INS_BTST:
case TMS320C55_INS_BTSTCLR:
case TMS320C55_INS_BTSTNOT:
case TMS320C55_INS_BTSTP:
case TMS320C55_INS_BTSTSET:
return RZ_ANALYSIS_OP_TYPE_AND;
case TMS320C55_INS_OR:
case TMS320C55_INS_CMPOR:
return RZ_ANALYSIS_OP_TYPE_OR;
case TMS320C55_INS_XOR:
return RZ_ANALYSIS_OP_TYPE_XOR;
case TMS320C55_INS_NOT:
case TMS320C55_INS_BNOT:
return RZ_ANALYSIS_OP_TYPE_NOT;
case TMS320C55_INS_MOV:
case TMS320C55_INS_DELAY:
case TMS320C55_INS_BFXTR:
case TMS320C55_INS_BFXPA:
case TMS320C55_INS_BSET:
case TMS320C55_INS_BCLR:
case TMS320C55_INS_ROUND:
case TMS320C55_INS_EXP:
case TMS320C55_INS_MANT:
case TMS320C55_INS_BCNT:
case TMS320C55_INS_CIRC:
return RZ_ANALYSIS_OP_TYPE_MOV;
case TMS320C55_INS_MPY:
case TMS320C55_INS_MPYK:
case TMS320C55_INS_MPYM:
case TMS320C55_INS_MPYMK:
case TMS320C55_INS_MAC:
case TMS320C55_INS_MACK:
case TMS320C55_INS_MACM:
case TMS320C55_INS_MACMK:
case TMS320C55_INS_MAS:
case TMS320C55_INS_MASM:
case TMS320C55_INS_SQA:
case TMS320C55_INS_SQAM:
case TMS320C55_INS_SQR:
case TMS320C55_INS_SQRM:
case TMS320C55_INS_SQS:
case TMS320C55_INS_SQSM:
case TMS320C55_INS_SQDST:
case TMS320C55_INS_LMS:
case TMS320C55_INS_LMSF:
case TMS320C55_INS_FIRSADD:
case TMS320C55_INS_FIRSSUB:
return RZ_ANALYSIS_OP_TYPE_MUL;
case TMS320C55_INS_SFTL:
case TMS320C55_INS_SFTS:
case TMS320C55_INS_SFTSC:
case TMS320C55_INS_SFTCC:
return RZ_ANALYSIS_OP_TYPE_SHL;
case TMS320C55_INS_ROL:
return RZ_ANALYSIS_OP_TYPE_ROL;
case TMS320C55_INS_ROR:
return RZ_ANALYSIS_OP_TYPE_ROR;
case TMS320C55_INS_CMP:
case TMS320C55_INS_CMPAND:
case TMS320C55_INS_MAX:
case TMS320C55_INS_MIN:
case TMS320C55_INS_MAXDIFF:
case TMS320C55_INS_MINDIFF:
case TMS320C55_INS_DMAXDIFF:
case TMS320C55_INS_DMINDIFF:
return RZ_ANALYSIS_OP_TYPE_CMP;
case TMS320C55_INS_AMOV:
case TMS320C55_INS_AMAR:
case TMS320C55_INS_ASUB:
return RZ_ANALYSIS_OP_TYPE_LEA;
case TMS320C55_INS_SWAP:
return RZ_ANALYSIS_OP_TYPE_XCHG;
case TMS320C55_INS_NOP:
case TMS320C55_INS_IDLE:
return RZ_ANALYSIS_OP_TYPE_NOP;
case TMS320C55_INS_PSH:
return RZ_ANALYSIS_OP_TYPE_PUSH;
case TMS320C55_INS_POP:
return RZ_ANALYSIS_OP_TYPE_POP;
/* PSHBOTH / POPBOTH push or pop a register *pair* in one op; the
* byte-level classifier types them UPUSH / UPOP (multi-register
* stack ops), which is more specific than PUSH / POP. Returning NULL
* here preserves that. */
case TMS320C55_INS_RET:
case TMS320C55_INS_RETI:
return RZ_ANALYSIS_OP_TYPE_RET;
case TMS320C55_INS_RETCC:
return RZ_ANALYSIS_OP_TYPE_CRET;
case TMS320C55_INS_INTR:
return RZ_ANALYSIS_OP_TYPE_SWI;
case TMS320C55_INS_TRAP:
case TMS320C55_INS_RESET:
return RZ_ANALYSIS_OP_TYPE_TRAP;
default:
/* Control flow (B/BCC/CALL/CALLCC/...), repeats (RPT*), XCC,
* SAT, ABS, SIM_TRIG and anything else: leave to the byte-level
* classifier, which sets jump/call targets, stack deltas and
* operand fields. */
return RZ_ANALYSIS_OP_TYPE_NULL;
}
}
+150
View File
@@ -0,0 +1,150 @@
// SPDX-FileCopyrightText: 2026 RizinOrg <[email protected]>
// SPDX-License-Identifier: LGPL-3.0-only
#ifndef RZ_TMS320C55X_INSN_H
#define RZ_TMS320C55X_INSN_H
#include <rz_types.h>
/**
* \file tms320c55x_insn.h
*
* Named instruction identifiers for the TMS320 C55x / C55x+ families,
* in the same spirit as Capstone's per-architecture instruction enums
* (e.g. ARM_INS_BL, X86_INS_MOV). Rather than dispatching analysis on
* raw opcode bytes, the disassembler tags each decoded instruction with
* one of these IDs and the analyzers switch on the ID.
*
* The prefix is TMS320C55_ (not just TMS320_) because the other TMS320
* families -- C54x, C64x, C28x, ... -- have substantially different
* instruction sets; this enum is specific to the C55x / C55x+ ISA. The
* namespace is shared between C55x and C55x+ because those two share the
* bulk of their mnemonics; instructions that exist only on one of them
* (e.g. SWAP / SIM_TRIG on C55x+) are still part of the single enum.
*
* The mnemonic set was extracted from the C55x decoder opcode table
* (librz/arch/isa/tms320/c55x/table.h) and the C55x+ token decoder, and
* cross-referenced with TI SPRU374 (C55x) and SWPU086 / SWPU104 (C55x+).
*/
typedef enum {
TMS320C55_INS_INVALID = 0,
TMS320C55_INS_AADD,
TMS320C55_INS_ABDST,
TMS320C55_INS_ABS,
TMS320C55_INS_ADD,
TMS320C55_INS_ADDSUB,
TMS320C55_INS_ADDSUB2CC,
TMS320C55_INS_ADDSUBCC,
TMS320C55_INS_AMAR,
TMS320C55_INS_AMOV,
TMS320C55_INS_AND,
TMS320C55_INS_ASUB,
TMS320C55_INS_B,
TMS320C55_INS_BAND,
TMS320C55_INS_BCC,
TMS320C55_INS_BCLR,
TMS320C55_INS_BCNT,
TMS320C55_INS_BFXPA,
TMS320C55_INS_BFXTR,
TMS320C55_INS_BNOT,
TMS320C55_INS_BSET,
TMS320C55_INS_BTST,
TMS320C55_INS_BTSTCLR,
TMS320C55_INS_BTSTNOT,
TMS320C55_INS_BTSTP,
TMS320C55_INS_BTSTSET,
TMS320C55_INS_CALL,
TMS320C55_INS_CALLCC,
TMS320C55_INS_CIRC,
TMS320C55_INS_CMP,
TMS320C55_INS_CMPAND,
TMS320C55_INS_CMPOR,
TMS320C55_INS_DELAY,
TMS320C55_INS_DMAXDIFF,
TMS320C55_INS_DMINDIFF,
TMS320C55_INS_EXP,
TMS320C55_INS_FIRSADD,
TMS320C55_INS_FIRSSUB,
TMS320C55_INS_IDLE,
TMS320C55_INS_INTR,
TMS320C55_INS_LMS,
TMS320C55_INS_LMSF,
TMS320C55_INS_MAC,
TMS320C55_INS_MACK,
TMS320C55_INS_MACM,
TMS320C55_INS_MACMK,
TMS320C55_INS_MANT,
TMS320C55_INS_MAS,
TMS320C55_INS_MASM,
TMS320C55_INS_MAX,
TMS320C55_INS_MAXDIFF,
TMS320C55_INS_MIN,
TMS320C55_INS_MINDIFF,
TMS320C55_INS_MOV,
TMS320C55_INS_MPY,
TMS320C55_INS_MPYK,
TMS320C55_INS_MPYM,
TMS320C55_INS_MPYMK,
TMS320C55_INS_NEG,
TMS320C55_INS_NOP,
TMS320C55_INS_NOT,
TMS320C55_INS_OR,
TMS320C55_INS_POP,
TMS320C55_INS_POPBOTH,
TMS320C55_INS_PSH,
TMS320C55_INS_PSHBOTH,
TMS320C55_INS_RESET,
TMS320C55_INS_RET,
TMS320C55_INS_RETCC,
TMS320C55_INS_RETI,
TMS320C55_INS_ROL,
TMS320C55_INS_ROR,
TMS320C55_INS_ROUND,
TMS320C55_INS_RPT,
TMS320C55_INS_RPTADD,
TMS320C55_INS_RPTB,
TMS320C55_INS_RPTBLOCAL,
TMS320C55_INS_RPTCC,
TMS320C55_INS_RPTSUB,
TMS320C55_INS_SAT,
TMS320C55_INS_SFTCC,
TMS320C55_INS_SFTL,
TMS320C55_INS_SFTS,
TMS320C55_INS_SFTSC,
TMS320C55_INS_SIM_TRIG,
TMS320C55_INS_SQA,
TMS320C55_INS_SQAM,
TMS320C55_INS_SQDST,
TMS320C55_INS_SQR,
TMS320C55_INS_SQRM,
TMS320C55_INS_SQS,
TMS320C55_INS_SQSM,
TMS320C55_INS_SUB,
TMS320C55_INS_SUBADD,
TMS320C55_INS_SUBC,
TMS320C55_INS_SWAP,
TMS320C55_INS_TRAP,
TMS320C55_INS_XCC,
TMS320C55_INS_XCCPART,
TMS320C55_INS_XOR,
} TMS320C55InsID;
/** Human-readable mnemonic for a TMS320C55InsID (never NULL). */
RZ_API const char *tms320c55x_insn_name(TMS320C55InsID id);
/** Resolve the leading mnemonic of a decoded syntax string to its
* TMS320C55InsID. Parses the first whitespace-delimited token, skipping a
* leading C55x+ parallel marker ("|| ") if present. Returns
* TMS320C55_INS_INVALID when no mnemonic matches. */
RZ_API TMS320C55InsID tms320c55x_insn_id_from_syntax(const char *syntax);
/** Map a TMS320C55InsID to the closest RzAnalysisOp type
* (RZ_ANALYSIS_OP_TYPE_*), returned as int to avoid pulling rz_analysis.h
* into this header. Returns RZ_ANALYSIS_OP_TYPE_NULL (0) for instructions
* with no clean scalar type (control flow, repeats, and forms whose type
* the analyzer derives from operands). This is the authoritative type for
* the arithmetic / logical / move / stack families, where a leading-byte
* switch cannot tell e.g. ADD from SUB or AND from XOR. */
RZ_API int tms320c55x_insn_optype(TMS320C55InsID id);
#endif /* RZ_TMS320C55X_INSN_H */
+1
View File
@@ -323,6 +323,7 @@ arch_isa_sources = [
'isa/tms320/c55x_plus/ins.c',
'isa/tms320/c64x/c64x.c',
'isa/tms320/tms320_dasm.c',
'isa/tms320/tms320c55x_insn.c',
'isa/v810/v810_disas.c',
'isa/v810/v810_il.c',
'isa/v850/v850_disas.c',
+68 -1
View File
@@ -244,7 +244,7 @@ type: push
type: pop
type: upush
type: lea
type: sub
type: lea
type: null
type: sub
type: cmp
@@ -511,3 +511,70 @@ stackframe: 6
stackframe: 0
EOF
RUN
NAME=c55x+ analysis: named instruction id (op->id = TMS320C55_INS_*)
CMDS=<<EOF
e asm.arch=tms320
e analysis.cpu=c55x+
# op->id carries the named TMS320C55InsID resolved by the C55x+ token
# decoder (positions in tms320_insn.h): AADD=1, INTR=39, NOP=59, POP=62,
# PSH=64, RET=67, RETI=69, TRAP=96.
wx 21 @ 0
ao 1 @ 0~id
wx 0e00 @ 0
ao 1 @ 0~id
wx 0f00 @ 0
ao 1 @ 0~id
wx 0310 @ 0
ao 1 @ 0~id
wx 0350 @ 0
ao 1 @ 0~id
wx 00c0 @ 0
ao 1 @ 0~id
wx 20 @ 0
ao 1 @ 0~id
wx 0c10 @ 0
ao 1 @ 0~id
EOF
EXPECT=<<EOF
id: 67
id: 64
id: 62
id: 39
id: 96
id: 69
id: 59
id: 1
EOF
RUN
NAME=c55x+ analysis: regression - decoded-id type override
CMDS=<<EOF
e asm.arch=tms320
e analysis.cpu=c55x+
# These leading bytes encode multiple instructions disambiguated by
# operand bits; the decoded TMS320C55_INS_* id drives the final type.
# 0x7B byte1 bit7 set -> LD (mov); clear -> add/sub by byte2 bit7
# (TI SWPU104 Table 7-2, opcode 01111011).
wx 7b8000 @ 0
ao 1 @ 0~^type
wx 7b3000 @ 0
ao 1 @ 0~^type
wx 7b3081 @ 0
ao 1 @ 0~^type
# 0xD2 mar(XDAa op k24): address-register modify, classified lea like
# AADD/AMOV (TI SWPU104 Table 7-2, opcode 11010010). amov and asub forms
# both resolve to lea.
wx d280000000 @ 0
ao 1 @ 0~^type
wx d200000000 @ 0
ao 1 @ 0~^type
EOF
EXPECT=<<EOF
type: mov
type: add
type: sub
type: lea
type: lea
EOF
RUN
+91 -4
View File
@@ -188,7 +188,7 @@ wx c000 @ 0
ao 1 @ 0~^type
wx c500 @ 0
ao 1 @ 0~^type
wx 7a01 @ 0
wx 7a010000 @ 0
ao 1 @ 0~^type
wx 7b00 @ 0
ao 1 @ 0~^type
@@ -295,7 +295,7 @@ type: cret
type: cjmp
type: jmp
type: call
type: null
type: ccall
type: add
type: sub
type: mov
@@ -351,7 +351,7 @@ type: sub
type: cmp
type: cmp
type: xchg
type: mov
type: null
type: lea
type: lea
type: call
@@ -548,7 +548,7 @@ cpu c55x
int main(int argc, char **argv, char **envp);
0
3
stackframe: 14
stackframe: 15
call-refs: 0x000002bb C 0x00000364 C
EOF
RUN
@@ -696,3 +696,90 @@ args: 1
locals: 0
EOF
RUN
NAME=c55x analysis: named instruction id (op->id = TMS320C55_INS_*)
CMDS=<<EOF
e asm.arch=tms320
e analysis.cpu=c55x
# The disassembler tags each instruction with a named TMS320C55InsID, which
# the analyzer surfaces on op->id (Capstone-style). These values are the
# positions in the TMS320C55_INS_* enum (see tms320_insn.h): B=12, CALL=26,
# RET=67, NOP=59, PSH=64, POP=62, AND=10, MOV=53, INTR=39, TRAP=96.
wx 060020 @ 0
ao 1 @ 0~id
wx 080020 @ 0
ao 1 @ 0~id
wx 4884 @ 0
ao 1 @ 0~id
wx 20 @ 0
ao 1 @ 0~id
wx 3800 @ 0
ao 1 @ 0~id
wx 3a00 @ 0
ao 1 @ 0~id
wx 1010 @ 0
ao 1 @ 0~id
wx a000 @ 0
ao 1 @ 0~id
wx 950f @ 0
ao 1 @ 0~id
wx 958f @ 0
ao 1 @ 0~id
EOF
EXPECT=<<EOF
id: 12
id: 26
id: 67
id: 59
id: 64
id: 62
id: 10
id: 53
id: 39
id: 96
EOF
RUN
NAME=c55x analysis: regression - byte-vs-decoded id classification
CMDS=<<EOF
e asm.arch=tms320
e analysis.cpu=c55x
# These cases were mis-classified by the earlier raw-opcode-byte switch
# and are fixed by dispatching on the decoded TMS320C55_INS_* id.
#
# 0x50 is a multi-form leading byte: with operand 0x66 it is `psh t2`
# (a stack push), not the `sftl` form the bare byte 0x50 maps to. The
# old byte switch typed it SHL; it must be a stack push (stackptr=1).
wx 5066 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^stackptr
# 0x48 0x05 decodes to `reti`, a return. The old switch typed the whole
# 0x48 family REP; reti must be RET.
wx 4805 @ 0
ao 1 @ 0~^type
# 0x95 with bit7 of byte1 clear is INTR (swi); set is TRAP. The decoder
# distinguishes them; both carry the 5-bit immediate in val.
wx 950f @ 0
ao 1 @ 0~^type
ao 1 @ 0~^val
wx 958f @ 0
ao 1 @ 0~^type
ao 1 @ 0~^val
# DELAY (0xb6) is a memory-delay MOVE per TI SWPU104 6.7.1: it accesses
# memory (refptr=2), it is not a CPU-control op.
wx b600 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^refptr
EOF
EXPECT=<<EOF
type: push
stackptr: 1
type: ret
type: swi
val: 0x0000000f
type: trap
val: 0x0000000f
type: mov
refptr: 2
EOF
RUN