diff --git a/librz/arch/isa/tms320/c2x/c2x.c b/librz/arch/isa/tms320/c2x/c2x.c new file mode 100644 index 0000000000..a0e161fc60 --- /dev/null +++ b/librz/arch/isa/tms320/c2x/c2x.c @@ -0,0 +1,620 @@ +// SPDX-FileCopyrightText: 2026 RizinOrg +// SPDX-License-Identifier: LGPL-3.0-only + +/** + * \file + * TMS320C2x (legacy single-accumulator fixed-point DSP, e.g. TMS320C25) + * disassembly + analysis, built on the shared C55 decode engine (c55_ir.[ch]). + * + * The C2x is a 16-bit word-addressed Harvard machine. Memory-reference + * instructions carry one addressing byte (the low 8 bits of the opcode word): + * bit 7 selects direct (0) or indirect (1) addressing. + * - Direct: bits 6-0 are a 7-bit data-page offset (dma); the effective + * address is (DP[8:0] << 7) | dma. + * - Indirect: bits 6-4 (M) select an auxiliary-register modification applied + * to the ARP-selected AR; bits 3-0 (N), when N>=8, reload ARP to + * N&7 (rendered ",arX"). M: 0=* 1=*- 2=*+ 4=*BR0- 5=*0- 6=*0+ + * 7=*BR0+. + * + * Opcode bit patterns are transcribed from the public TMS320C2x instruction set + * (matching MAME's tested TMS320x25 disassembler). Words are stored MSB-first, + * so the engine's per-word byte-swap (words_le) is NOT used. mask/match operate + * on the engine's 4-byte MSB-first "head" (opcode word in head bits 31:16); + * operand extractors index the c55_pack() word (opcode word in the low 16 bits + * for a 2-byte instruction, the leading word in bits 31:16 for a 4-byte one). + * + * Validation status: written against the encoding reference and the engine + * contracts; NOT yet round-tripped through a built rz-asm or cross-checked + * against a hardware/reference disassembler. The F8..FF branch tail in + * particular (B/CALL/BANZ/BBZ/BBNZ/BIOZ opcodes) is reconstructed and should be + * verified before merge. + */ + +#include "c2x.h" + +// bitfield helper (c55_field is private to the engine) + +static ut64 c2x_field(ut64 bits, ut8 lo, ut8 width) { + if (width >= 64) { + return bits >> lo; + } + return (bits >> lo) & (((ut64)1 << width) - 1); +} + +// operand extractors + +static const char *const c2x_indir_raw[8] = { + "*", "*-", "*+", "*?", "*br0-", "*0-", "*0+", "*br0+" +}; +static const C55AddrMode c2x_indir_amode[8] = { + C55_AM_INDIRECT, C55_AM_POSTDEC, C55_AM_POSTINC, C55_AM_INDIRECT /* reserved */, + C55_AM_BITREV_SUB, C55_AM_POSTSUB, C55_AM_POSTADD, C55_AM_BITREV +}; +static const char *const c2x_arx_raw[8] = { + "ar0", "ar1", "ar2", "ar3", "ar4", "ar5", "ar6", "ar7" +}; + +// Single data-memory operand: the byte at d->lo is the addressing byte. Direct +// addressing is represented structurally (C55_AM_DIRECT + 7-bit dma in disp) so +// the lifter can form its EA via c2x_ea(); indirect addressing is ARP-relative +// (no statically known AR), so it renders verbatim via raw and is opaque to IL. +RZ_IPI void c2x_x_mem(RZ_UNUSED const C55ArchDesc *a, ut64 bits, const C55OpDesc *d, C55Operand *out) { + ut8 b = (ut8)c2x_field(bits, d->lo, 8); + out->kind = C55_OP_MEM; + out->access = 16; + if (b & 0x80) { + ut8 m = (b >> 4) & 7; + out->amode = c2x_indir_amode[m]; + out->reg.cls = C55_RC_AR; // ARP-selected at run time; placeholder index + out->reg.num = 0; + out->raw = c2x_indir_raw[m]; + } else { + out->amode = C55_AM_DIRECT; + out->disp = (st32)(b & 0x7f); + } +} + +// next-ARP nibble (N field): N>=8 reloads ARP to N&7, rendered ",arX". +RZ_IPI void c2x_x_nextarp(RZ_UNUSED const C55ArchDesc *a, ut64 bits, const C55OpDesc *d, C55Operand *out) { + ut8 n = (ut8)c2x_field(bits, d->lo, 4); + if (n & 0x8) { + out->kind = C55_OP_REG; + out->reg.cls = C55_RC_AR; + out->reg.num = n & 7; + out->width = 16; + out->raw = c2x_arx_raw[n & 7]; + } else { + out->kind = C55_OP_NONE; + } +} + +// shift count (T 4-bit / S 3-bit), rendered as an immediate. +RZ_IPI void c2x_x_shift(RZ_UNUSED const C55ArchDesc *a, ut64 bits, const C55OpDesc *d, C55Operand *out) { + out->kind = C55_OP_IMM; + out->imm = c2x_field(bits, d->lo, d->width); + out->width = 16; +} + +// auxiliary-register number (R field) -> arX register operand. +RZ_IPI void c2x_x_reg(RZ_UNUSED const C55ArchDesc *a, ut64 bits, const C55OpDesc *d, C55Operand *out) { + out->kind = C55_OP_REG; + out->reg.cls = C55_RC_AR; + out->reg.num = (ut8)c2x_field(bits, d->lo, d->width); + out->width = 16; + out->raw = (out->reg.num < 8) ? c2x_arx_raw[out->reg.num] : NULL; +} + +// immediate (D 8-bit / K small / W 16-bit / MPYK 13-bit); param 1 => signed. +RZ_IPI void c2x_x_imm(RZ_UNUSED const C55ArchDesc *a, ut64 bits, const C55OpDesc *d, C55Operand *out) { + out->kind = C55_OP_IMM; + out->imm = c2x_field(bits, d->lo, d->width); + out->width = d->width; + out->imm_signed = (d->param == 1); +} + +// 16-bit absolute branch/call target carried in the trailing word. +RZ_IPI void c2x_x_branch(RZ_UNUSED const C55ArchDesc *a, ut64 bits, const C55OpDesc *d, C55Operand *out) { + out->kind = C55_OP_IMM; + out->imm = c2x_field(bits, d->lo, 16); + out->width = 16; + out->addr = true; + out->abs_target = true; +} + +// decode table + +#include "c2x_rowdefs.h" + +static const C55InsnDef c2x_table[] = { +#include "c2x_core_rows.inc" +}; + +#include "c2x_rowundefs.h" + +// register resolver + +static const C55RegInfo c2x_reg_acc = { "acc", "acc", 32 }; +static const C55RegInfo c2x_reg_t = { "t", "t", 16 }; +static const C55RegInfo c2x_reg_dp = { "dp", "dp", 16 }; +static const C55RegInfo c2x_reg_arp = { "arp", "arp", 16 }; +static const C55RegInfo c2x_reg_p = { "p", "p", 32 }; +static const C55RegInfo c2x_reg_pc = { "pc", "pc", 16 }; +static const C55RegInfo c2x_reg_ar[8] = { + { "ar0", "ar0", 16 }, { "ar1", "ar1", 16 }, { "ar2", "ar2", 16 }, { "ar3", "ar3", 16 }, + { "ar4", "ar4", 16 }, { "ar5", "ar5", 16 }, { "ar6", "ar6", 16 }, { "ar7", "ar7", 16 } +}; +static const C55RegInfo c2x_reg_st[2] = { { "st0", "st0", 16 }, { "st1", "st1", 16 } }; + +RZ_IPI const C55RegInfo *c2x_reg_info(C55RegClass cls, ut8 num, RZ_UNUSED C55SubReg sub) { + switch (cls) { + case C55_RC_AC: return &c2x_reg_acc; + case C55_RC_T: return &c2x_reg_t; + case C55_RC_DP: return &c2x_reg_dp; + case C55_RC_ARP: return &c2x_reg_arp; + case C55_RC_AR: return num < 8 ? &c2x_reg_ar[num] : NULL; + case C55_RC_ST: return num < 2 ? &c2x_reg_st[num] : NULL; + // SPECIAL num 0 is the product register P, 1 is PC. + case C55_RC_SPECIAL: return num == 0 ? &c2x_reg_p : &c2x_reg_pc; + default: return NULL; + } +} + +// id -> RzAnalysisOp type + +RZ_IPI ut32 c2x_op_type(ut16 id) { + switch (id) { + case C2X_INS_NOP: + return RZ_ANALYSIS_OP_TYPE_NOP; + case C2X_INS_ADD: + case C2X_INS_ADDH: + case C2X_INS_ADDS: + case C2X_INS_ADDT: + case C2X_INS_ADDC: + case C2X_INS_ADDK: + case C2X_INS_ADLK: + case C2X_INS_APAC: + case C2X_INS_ADRK: + return RZ_ANALYSIS_OP_TYPE_ADD; + case C2X_INS_SUB: + case C2X_INS_SUBH: + case C2X_INS_SUBS: + case C2X_INS_SUBT: + case C2X_INS_SUBC: + case C2X_INS_SUBB: + case C2X_INS_SUBK: + case C2X_INS_SBLK: + case C2X_INS_SPAC: + case C2X_INS_SBRK: + case C2X_INS_NEG: + return RZ_ANALYSIS_OP_TYPE_SUB; + case C2X_INS_AND: + case C2X_INS_ANDK: + return RZ_ANALYSIS_OP_TYPE_AND; + case C2X_INS_OR: + case C2X_INS_ORK: + return RZ_ANALYSIS_OP_TYPE_OR; + case C2X_INS_XOR: + case C2X_INS_XORK: + return RZ_ANALYSIS_OP_TYPE_XOR; + case C2X_INS_CMPL: + return RZ_ANALYSIS_OP_TYPE_NOT; + case C2X_INS_SFL: + case C2X_INS_ROL: + return RZ_ANALYSIS_OP_TYPE_SHL; + case C2X_INS_SFR: + case C2X_INS_ROR: + return RZ_ANALYSIS_OP_TYPE_SHR; + case C2X_INS_MPY: + case C2X_INS_MPYK: + case C2X_INS_MPYA: + case C2X_INS_MPYS: + case C2X_INS_MPYU: + case C2X_INS_SQRA: + case C2X_INS_SQRS: + case C2X_INS_PAC: + case C2X_INS_MAC: + case C2X_INS_MACD: + return RZ_ANALYSIS_OP_TYPE_MUL; + case C2X_INS_LAC: + case C2X_INS_LACT: + case C2X_INS_LACK: + case C2X_INS_ZAC: + case C2X_INS_ZALH: + case C2X_INS_ZALS: + case C2X_INS_ZALR: + case C2X_INS_LAR: + case C2X_INS_LARK: + case C2X_INS_LRLK: + case C2X_INS_LALK: + case C2X_INS_LDP: + case C2X_INS_LDPK: + case C2X_INS_LT: + case C2X_INS_LTA: + case C2X_INS_LTD: + case C2X_INS_LTP: + case C2X_INS_LTS: + case C2X_INS_LST: + case C2X_INS_LST1: + case C2X_INS_LPH: + case C2X_INS_DMOV: + case C2X_INS_PSHD: + case C2X_INS_PUSH: + case C2X_INS_ABS: + return RZ_ANALYSIS_OP_TYPE_MOV; + case C2X_INS_SACL: + case C2X_INS_SACH: + case C2X_INS_SAR: + case C2X_INS_SST: + case C2X_INS_SST1: + case C2X_INS_SPL: + case C2X_INS_SPH: + case C2X_INS_POPD: + case C2X_INS_POP: + case C2X_INS_TBLW: + return RZ_ANALYSIS_OP_TYPE_STORE; + case C2X_INS_TBLR: + return RZ_ANALYSIS_OP_TYPE_LOAD; + case C2X_INS_IN: + case C2X_INS_OUT: + return RZ_ANALYSIS_OP_TYPE_IO; + case C2X_INS_B: + return RZ_ANALYSIS_OP_TYPE_JMP; + case C2X_INS_BACC: + return RZ_ANALYSIS_OP_TYPE_UJMP; + case C2X_INS_BV: + case C2X_INS_BGZ: + case C2X_INS_BLEZ: + case C2X_INS_BLZ: + case C2X_INS_BGEZ: + case C2X_INS_BNZ: + case C2X_INS_BZ: + case C2X_INS_BNV: + case C2X_INS_BBZ: + case C2X_INS_BBNZ: + case C2X_INS_BIOZ: + case C2X_INS_BANZ: + case C2X_INS_BC: + case C2X_INS_BNC: + return RZ_ANALYSIS_OP_TYPE_CJMP; + case C2X_INS_CALL: + return RZ_ANALYSIS_OP_TYPE_CALL; + case C2X_INS_CALA: + return RZ_ANALYSIS_OP_TYPE_UCALL; + case C2X_INS_RET: + return RZ_ANALYSIS_OP_TYPE_RET; + case C2X_INS_TRAP: + return RZ_ANALYSIS_OP_TYPE_TRAP; + default: + return RZ_ANALYSIS_OP_TYPE_NULL; + } +} + +// id -> mnemonic (indexed by the C2X_INS_* id; unset ids read back "invalid") +static const char *const c2x_mnemonics[] = { + [C2X_INS_NOP] = "nop", + [C2X_INS_ADD] = "add", + [C2X_INS_ADDH] = "addh", + [C2X_INS_ADDS] = "adds", + [C2X_INS_ADDT] = "addt", + [C2X_INS_ADDC] = "addc", + [C2X_INS_SUB] = "sub", + [C2X_INS_SUBH] = "subh", + [C2X_INS_SUBS] = "subs", + [C2X_INS_SUBT] = "subt", + [C2X_INS_SUBC] = "subc", + [C2X_INS_SUBB] = "subb", + [C2X_INS_LAC] = "lac", + [C2X_INS_LACT] = "lact", + [C2X_INS_LACK] = "lack", + [C2X_INS_ZAC] = "zac", + [C2X_INS_ZALH] = "zalh", + [C2X_INS_ZALS] = "zals", + [C2X_INS_ZALR] = "zalr", + [C2X_INS_ADDK] = "addk", + [C2X_INS_SUBK] = "subk", + [C2X_INS_ABS] = "abs", + [C2X_INS_NEG] = "neg", + [C2X_INS_CMPL] = "cmpl", + [C2X_INS_SFL] = "sfl", + [C2X_INS_SFR] = "sfr", + [C2X_INS_ROL] = "rol", + [C2X_INS_ROR] = "ror", + [C2X_INS_NORM] = "norm", + [C2X_INS_SACL] = "sacl", + [C2X_INS_SACH] = "sach", + [C2X_INS_PAC] = "pac", + [C2X_INS_APAC] = "apac", + [C2X_INS_SPAC] = "spac", + [C2X_INS_LPH] = "lph", + [C2X_INS_SPL] = "spl", + [C2X_INS_SPH] = "sph", + [C2X_INS_LAR] = "lar", + [C2X_INS_SAR] = "sar", + [C2X_INS_LARK] = "lark", + [C2X_INS_LARP] = "larp", + [C2X_INS_MAR] = "mar", + [C2X_INS_LDP] = "ldp", + [C2X_INS_LDPK] = "ldpk", + [C2X_INS_ADRK] = "adrk", + [C2X_INS_SBRK] = "sbrk", + [C2X_INS_LT] = "lt", + [C2X_INS_LTA] = "lta", + [C2X_INS_LTD] = "ltd", + [C2X_INS_LTP] = "ltp", + [C2X_INS_LTS] = "lts", + [C2X_INS_MPY] = "mpy", + [C2X_INS_MPYK] = "mpyk", + [C2X_INS_MPYA] = "mpya", + [C2X_INS_MPYS] = "mpys", + [C2X_INS_MPYU] = "mpyu", + [C2X_INS_SQRA] = "sqra", + [C2X_INS_SQRS] = "sqrs", + [C2X_INS_MAC] = "mac", + [C2X_INS_MACD] = "macd", + [C2X_INS_AND] = "and", + [C2X_INS_OR] = "or", + [C2X_INS_XOR] = "xor", + [C2X_INS_ANDK] = "andk", + [C2X_INS_ORK] = "ork", + [C2X_INS_XORK] = "xork", + [C2X_INS_LST] = "lst", + [C2X_INS_LST1] = "lst1", + [C2X_INS_SST] = "sst", + [C2X_INS_SST1] = "sst1", + [C2X_INS_LALK] = "lalk", + [C2X_INS_ADLK] = "adlk", + [C2X_INS_SBLK] = "sblk", + [C2X_INS_LRLK] = "lrlk", + [C2X_INS_RPT] = "rpt", + [C2X_INS_RPTK] = "rptk", + [C2X_INS_DMOV] = "dmov", + [C2X_INS_PSHD] = "pshd", + [C2X_INS_POPD] = "popd", + [C2X_INS_PUSH] = "push", + [C2X_INS_POP] = "pop", + [C2X_INS_BITT] = "bitt", + [C2X_INS_BIT] = "bit", + [C2X_INS_TBLR] = "tblr", + [C2X_INS_TBLW] = "tblw", + [C2X_INS_BLKD] = "blkd", + [C2X_INS_BLKP] = "blkp", + [C2X_INS_IN] = "in", + [C2X_INS_OUT] = "out", + [C2X_INS_B] = "b", + [C2X_INS_BACC] = "bacc", + [C2X_INS_CALA] = "cala", + [C2X_INS_CALL] = "call", + [C2X_INS_RET] = "ret", + [C2X_INS_BANZ] = "banz", + [C2X_INS_BV] = "bv", + [C2X_INS_BGZ] = "bgz", + [C2X_INS_BLEZ] = "blez", + [C2X_INS_BLZ] = "blz", + [C2X_INS_BGEZ] = "bgez", + [C2X_INS_BNZ] = "bnz", + [C2X_INS_BZ] = "bz", + [C2X_INS_BNV] = "bnv", + [C2X_INS_BBZ] = "bbz", + [C2X_INS_BBNZ] = "bbnz", + [C2X_INS_BIOZ] = "bioz", + [C2X_INS_BC] = "bc", + [C2X_INS_BNC] = "bnc", + [C2X_INS_TRAP] = "trap", + [C2X_INS_IDLE] = "idle", + [C2X_INS_EINT] = "eint", + [C2X_INS_DINT] = "dint", + [C2X_INS_ROVM] = "rovm", + [C2X_INS_SOVM] = "sovm", + [C2X_INS_CNFD] = "cnfd", + [C2X_INS_CNFP] = "cnfp", + [C2X_INS_RSXM] = "rsxm", + [C2X_INS_SSXM] = "ssxm", + [C2X_INS_SPM] = "spm", + [C2X_INS_RXF] = "rxf", + [C2X_INS_SXF] = "sxf", + [C2X_INS_FORT] = "fort", + [C2X_INS_RC] = "rc", + [C2X_INS_SC] = "sc", + [C2X_INS_RTC] = "rtc", + [C2X_INS_STC] = "stc", + [C2X_INS_RFSM] = "rfsm", + [C2X_INS_SFSM] = "sfsm", + [C2X_INS_RHM] = "rhm", + [C2X_INS_SHM] = "shm", + [C2X_INS_RTXM] = "rtxm", + [C2X_INS_STXM] = "stxm", + [C2X_INS_CMPR] = "cmpr", + [C2X_INS_CONF] = "conf", +}; + +RZ_IPI const char *c2x_mnemonic(ut16 id) { + if (id < RZ_ARRAY_SIZE(c2x_mnemonics) && c2x_mnemonics[id]) { + return c2x_mnemonics[id]; + } + return "invalid"; +} + +// descriptor + analysis entry + +/** + * \brief C2x architecture descriptor for the shared C55 engine. + * + * Binds the C2x opcode table, operand extractors and consumers; the C5x + * superset reuses these for the instruction ids the two share. + */ +const C55ArchDesc c2x_arch_desc = { + .arch = C55_ARCH_C2X, + .cpu_name = "c2x", + .table = c2x_table, + .table_len = sizeof(c2x_table) / sizeof(c2x_table[0]), + .insn_len = NULL, // every row carries a fixed .len + .reg_info = c2x_reg_info, + .mnemonic = c2x_mnemonic, + .op_type = c2x_op_type, + .lift = NULL, + .mem = { .addr_unit_log2 = 0, .ptr_width = 16, .big_endian = true, .page_reg = "dp" }, + .ea = NULL, + .fill_dual = NULL, + .words_le = false, // C2x words are stored MSB-first; no per-word swap + .cond_exec_prefix = false, + .parallel_prefix = false, +}; + +// Populate the data-flow analysis fields from the decoded operands so variable +// and argument analysis (and data-xref tracking) can inspect the instruction: +// the source/destination access values (op->src/op->dst) for register and memory +// operands, and op->ptr for direct (data-page-relative) data accesses. Shared by +// the C2x and C5x analysis paths, which use the same operand representation. The +// register/memory roles follow the load/store direction the shared filler has +// already resolved (a load's register operand is the destination; a store's +// memory operand is the destination), with the accumulator left implicit. +RZ_IPI void c2x_fill_op_access(RzAnalysis *analysis, const C55ArchDesc *a, + const C55Insn *insn, RzAnalysisOp *op) { + if (!analysis || !a || !insn || !op) { + return; + } + RzReg *reg = rz_analysis_get_reg(analysis); + // Resolve the memory role from the analysis op type: a store writes its + // memory operand, a load / accumulator-move reads it; everything else + // (arithmetic, logic, ...) reads its operands into the implicit + // accumulator. The shared filler's load/store direction targets the C54x + // operand layout, so set the read/write direction here for the C2x/C5x + // memory forms. + const bool is_store = op->type == RZ_ANALYSIS_OP_TYPE_STORE; + const bool is_load = op->type == RZ_ANALYSIS_OP_TYPE_LOAD || + op->type == RZ_ANALYSIS_OP_TYPE_MOV; + bool has_mem = false; + size_t srci = 0; + for (ut8 i = 0; i < insn->n_ops; i++) { + const C55Operand *o = &insn->ops[i]; + if (o->kind == C55_OP_MEM) { + has_mem = true; + RzAnalysisValue *v = rz_analysis_value_new(); + if (!v) { + continue; + } + v->type = RZ_ANALYSIS_VAL_MEM; + v->memref = (o->access ? o->access : 16) / 8; + if (o->amode == C55_AM_DIRECT) { + // Direct addressing: the 7-bit field is the data offset within + // the current data page; expose it as the access pointer. + v->base = (ut64)(o->disp & 0x7f); + op->ptr = v->base; + op->ptrsize = v->memref; + } + // Indirect forms select the auxiliary register through ARP at run + // time, so no base register is known here; naming one would point + // every indirect access at the same AR. + if (is_store && !op->dst) { + op->dst = v; + } else if (srci < RZ_ARRAY_SIZE(op->src)) { + op->src[srci++] = v; + } else { + rz_analysis_value_free(v); + } + } else if (o->kind == C55_OP_REG && a->reg_info) { + const C55RegInfo *ri = a->reg_info(o->reg.cls, o->reg.num, o->reg.sub); + if (!ri) { + continue; + } + RzAnalysisValue *v = rz_analysis_value_new(); + if (!v) { + continue; + } + v->type = RZ_ANALYSIS_VAL_REG; + v->reg = rz_reg_get(reg, ri->name, RZ_REG_TYPE_ANY); + if (is_load && !op->dst) { + op->dst = v; + } else if (srci < RZ_ARRAY_SIZE(op->src)) { + op->src[srci++] = v; + } else { + rz_analysis_value_free(v); + } + } else if (o->kind == C55_OP_IMM && srci < RZ_ARRAY_SIZE(op->src)) { + RzAnalysisValue *v = rz_analysis_value_new(); + if (!v) { + continue; + } + v->type = RZ_ANALYSIS_VAL_IMM; + v->imm = (st64)o->imm; + op->src[srci++] = v; + } + } + if (has_mem) { + if (is_store) { + op->direction = RZ_ANALYSIS_OP_DIR_WRITE; + } else if (is_load || op->direction == 0) { + op->direction = RZ_ANALYSIS_OP_DIR_READ; + } + } +} + +/** + * \brief Analysis entry point for the "c2x" CPU. + * \param analysis Current analysis session + * \param op Operation to fill in + * \param addr Address \p buf was read from + * \param buf Instruction bytes + * \param len Number of readable bytes in \p buf + * \param mask Which parts of \p op the caller wants filled + * \return Instruction length in bytes, or -1 on an undecodable word + */ +RZ_IPI int tms320_c2x_op(RzAnalysis *analysis, RzAnalysisOp *op, ut64 addr, + const ut8 *buf, int len, RzAnalysisOpMask mask) { + if (!op || !buf || len < 1) { + return 0; + } + op->addr = addr; + op->type = RZ_ANALYSIS_OP_TYPE_NULL; + C55Insn ci; + if (c55_decode(&c2x_arch_desc, buf, len, &ci)) { + c55_fill_analysis(&c2x_arch_desc, &ci, op); + c2x_fill_op_access(analysis, &c2x_arch_desc, &ci, op); + if (mask & RZ_ANALYSIS_OP_MASK_IL) { + op->il_op = c55_lift(&c2x_arch_desc, &ci, op->addr); + } + } else { + // Undecodable word: flag it and give a one-word fallback size, but + // report the failure to the caller with -1 (the common plugin convention). + op->type = RZ_ANALYSIS_OP_TYPE_ILL; + op->size = 1; + return -1; + } + return op->size; +} + +/* + * Complete TMS320C2x encoding map (for extending c2x_table). Bit legend: + * A 7-bit direct dma | M 3-bit AR-modify | N 4-bit next-ARP | T 4-bit shift + * S 3-bit shift | R 3-bit AR# | P 4-bit port | D 8-bit imm | K small imm + * W 16-bit imm (next word) | C 2-bit compare | B 16-bit branch target (word) + * + * 0xxx ADD A,T / M,T,N 40xx ZALH 50xx LST 78xx SST 8xxx IN A,P + * 1xxx SUB 41xx ZALS 51xx LST1 79xx SST1 9xxx BIT A,T + * 2xxx LAC 42xx LACT 52xx LDP 7Axx POPD Axx-Bxx MPYK W + * 3xxx LAR R,A 43xx ADDC 53xx LPH 7Bxx ZALR Cxxx LARK R,D + * 38xx MPY 3Cxx LT 44xx SUBH 54xx PSHD 7Cxx SPL C8xx LDPK K + * 39xx SQRA 3Dxx LTA 45xx SUBS 55xx MAR/NOP 7Dxx SPH CAxx LACK (CA00=ZAC) + * 3Axx MPYA 3Exx LTP 46xx SUBT 56xx DMOV 7Exx ADRK CBxx RPTK + * 3Bxx MPYS 3Fxx LTD 47xx SUBC 57xx BITT 7Fxx SBRK CCxx ADDK + * 48xx ADDH 58xx TBLR CDxx SUBK + * 49xx ADDS 59xx TBLW 5Cxx MACD B,* CExx (no-op block) + * 4Axx ADDT 5Axx SQRS 5Dxx MAC B,* CFxx MPYS + * 4Bxx RPT 5Bxx LTS 5Exx BC B Dx00 LRLK R,W + * 4Cxx XOR 5Fxx BNC B Dx01 LALK W,T + * 4Dxx OR 60xx SACL S Dx02 ADLK + * 4Exx AND 68xx SACH S Exxx OUT A,P Dx03 SBLK + * 4Fxx SUBB 70xx SAR R,A Dx04 ANDK + * Dx05 ORK + * CE block (exact): CE00 EINT CE01 DINT CE02 ROVM CE03 SOVM CE04 CNFD Dx06 XORK + * CE05 CNFP CE06 RSXM CE07 SSXM CE08-B SPM K CE0C RXF CE0D SXF + * CE0E-F FORT K CE14 PAC CE15 APAC CE16 SPAC CE18 SFL CE19 SFR CE1B ABS + * CE1C PUSH CE1D POP CE1E TRAP CE1F IDLE CE20 RTXM CE21 STXM CE23 NEG + * CE24 CALA CE25 BACC CE26 RET CE27 CMPL CE30 RC CE31 SC CE32 RTC CE33 STC + * CE34 ROL CE35 ROR CE36 RFSM CE37 SFSM CE38 RHM CE39 SHM CE3C-F CONF K + * CE50-3 CMPR C CEx2 (110011101mmm0010) NORM M + * F-block branches (opcode + B word + addressing byte, bit7=1): F0 BV F1 BGZ + * F2 BLEZ F3 BLZ F4 BGEZ F5 BNZ F6 BZ F7 BNV (F8 BBZ F9 BBNZ FA BIOZ + * FB BANZ FE CALL FF B are the conventional assignments; verify the tail). + * BLKD/BLKP are the block-move forms (opcode + source-address word). + */ diff --git a/librz/arch/isa/tms320/c2x/c2x.h b/librz/arch/isa/tms320/c2x/c2x.h new file mode 100644 index 0000000000..d9dc53d9a8 --- /dev/null +++ b/librz/arch/isa/tms320/c2x/c2x.h @@ -0,0 +1,214 @@ +// SPDX-FileCopyrightText: 2026 RizinOrg +// SPDX-License-Identifier: LGPL-3.0-only + +#ifndef RZ_TMS320_C2X_H +#define RZ_TMS320_C2X_H + +#include "../c55_ir.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \file + * TMS320C2x (legacy, e.g. TMS320C25) disassembly + RzIL, plugged into the + * shared C55 decode engine. The C2x is a 16-bit word-addressed fixed-point DSP + * with a single 32-bit accumulator (ACC) plus carry, a 16-bit temporary T, a + * 32-bit product register P (with a programmable product shifter PM), eight + * 16-bit auxiliary registers AR0..7 selected by the 3-bit ARP, a 9-bit data + * page pointer DP, and ST0/ST1 status words. Program/data addresses are 16-bit. + * + * Encodings are taken from the public TMS320C2x instruction set (the opcode bit + * patterns match MAME's tested TMS320x25 disassembler). Instruction words are + * stored MSB-first (big-endian), so the engine's words_le swap is NOT used: the + * leading byte already carries the opcode field that the table matches on. + */ + +/// TMS320C2x instruction identifiers (the C55Insn.id / RzAnalysisOp.id values). +enum { + C2X_INS_INVALID = 0, + C2X_INS_NOP, + // accumulator arithmetic + C2X_INS_ADD, + C2X_INS_ADDH, + C2X_INS_ADDS, + C2X_INS_ADDT, + C2X_INS_ADDC, + C2X_INS_SUB, + C2X_INS_SUBH, + C2X_INS_SUBS, + C2X_INS_SUBT, + C2X_INS_SUBC, + C2X_INS_SUBB, + C2X_INS_LAC, + C2X_INS_LACT, + C2X_INS_LACK, + C2X_INS_ZAC, + C2X_INS_ZALH, + C2X_INS_ZALS, + C2X_INS_ZALR, + C2X_INS_ADDK, + C2X_INS_SUBK, + C2X_INS_ABS, + C2X_INS_NEG, + C2X_INS_CMPL, + C2X_INS_SFL, + C2X_INS_SFR, + C2X_INS_ROL, + C2X_INS_ROR, + C2X_INS_NORM, + // store / load accumulator parts + C2X_INS_SACL, + C2X_INS_SACH, + C2X_INS_PAC, + C2X_INS_APAC, + C2X_INS_SPAC, + C2X_INS_LPH, + C2X_INS_SPL, + C2X_INS_SPH, + // auxiliary registers / pointers + C2X_INS_LAR, + C2X_INS_SAR, + C2X_INS_LARK, + C2X_INS_LARP, + C2X_INS_MAR, + C2X_INS_LDP, + C2X_INS_LDPK, + C2X_INS_ADRK, + C2X_INS_SBRK, + // T / P register and multiply + C2X_INS_LT, + C2X_INS_LTA, + C2X_INS_LTD, + C2X_INS_LTP, + C2X_INS_LTS, + C2X_INS_MPY, + C2X_INS_MPYK, + C2X_INS_MPYA, + C2X_INS_MPYS, + C2X_INS_MPYU, + C2X_INS_SQRA, + C2X_INS_SQRS, + C2X_INS_MAC, + C2X_INS_MACD, + // logical + C2X_INS_AND, + C2X_INS_OR, + C2X_INS_XOR, + C2X_INS_ANDK, + C2X_INS_ORK, + C2X_INS_XORK, + // status / long immediate + C2X_INS_LST, + C2X_INS_LST1, + C2X_INS_SST, + C2X_INS_SST1, + C2X_INS_LALK, + C2X_INS_ADLK, + C2X_INS_SBLK, + C2X_INS_LRLK, + C2X_INS_RPT, + C2X_INS_RPTK, + // memory move / table / IO + C2X_INS_DMOV, + C2X_INS_PSHD, + C2X_INS_POPD, + C2X_INS_PUSH, + C2X_INS_POP, + C2X_INS_BITT, + C2X_INS_BIT, + C2X_INS_TBLR, + C2X_INS_TBLW, + C2X_INS_BLKD, + C2X_INS_BLKP, + C2X_INS_IN, + C2X_INS_OUT, + // control flow + C2X_INS_B, + C2X_INS_BACC, + C2X_INS_CALA, + C2X_INS_CALL, + C2X_INS_RET, + C2X_INS_BANZ, + C2X_INS_BV, + C2X_INS_BGZ, + C2X_INS_BLEZ, + C2X_INS_BLZ, + C2X_INS_BGEZ, + C2X_INS_BNZ, + C2X_INS_BZ, + C2X_INS_BNV, + C2X_INS_BBZ, + C2X_INS_BBNZ, + C2X_INS_BIOZ, + C2X_INS_BC, + C2X_INS_BNC, + C2X_INS_TRAP, + C2X_INS_IDLE, + // status-bit / mode controls (CE block) + C2X_INS_EINT, + C2X_INS_DINT, + C2X_INS_ROVM, + C2X_INS_SOVM, + C2X_INS_CNFD, + C2X_INS_CNFP, + C2X_INS_RSXM, + C2X_INS_SSXM, + C2X_INS_SPM, + C2X_INS_RXF, + C2X_INS_SXF, + C2X_INS_FORT, + C2X_INS_RC, + C2X_INS_SC, + C2X_INS_RTC, + C2X_INS_STC, + C2X_INS_RFSM, + C2X_INS_SFSM, + C2X_INS_RHM, + C2X_INS_SHM, + C2X_INS_RTXM, + C2X_INS_STXM, + C2X_INS_CMPR, + C2X_INS_CONF, +}; + +// Legacy C2x/C5x memory model (shared by both cores). The data/program space +// is word-addressed, but the RzIL VM memory is byte-addressed, so a word +// address scales to a byte address by C2X_WORD_BYTES. C2X_MEM_ADDR_BITS is the +// width of that byte-address space (the il_config mem_key_size): 24 bits leave +// room for a 16-bit word address to scale x2 without wrapping. +#define C2X_WORD_BYTES 2 +#define C2X_MEM_ADDR_BITS 24 + +extern const C55ArchDesc c2x_arch_desc; + +// Shared decode-table pieces, reused by the C5x superset (c5x/c5x.c). The +// operand extractors are referenced by the row macros in c2x_rowdefs.h; the +// resolver/classifier/mnemonic helpers are reused (and extended) by C5x. +RZ_IPI void c2x_x_mem(const C55ArchDesc *a, ut64 bits, const C55OpDesc *d, C55Operand *out); +RZ_IPI void c2x_x_nextarp(const C55ArchDesc *a, ut64 bits, const C55OpDesc *d, C55Operand *out); +RZ_IPI void c2x_x_shift(const C55ArchDesc *a, ut64 bits, const C55OpDesc *d, C55Operand *out); +RZ_IPI void c2x_x_reg(const C55ArchDesc *a, ut64 bits, const C55OpDesc *d, C55Operand *out); +RZ_IPI void c2x_x_imm(const C55ArchDesc *a, ut64 bits, const C55OpDesc *d, C55Operand *out); +RZ_IPI void c2x_x_branch(const C55ArchDesc *a, ut64 bits, const C55OpDesc *d, C55Operand *out); +RZ_IPI const C55RegInfo *c2x_reg_info(C55RegClass cls, ut8 num, C55SubReg sub); +RZ_IPI ut32 c2x_op_type(ut16 id); +RZ_IPI const char *c2x_mnemonic(ut16 id); + +RZ_IPI int tms320_c2x_op(RzAnalysis *analysis, RzAnalysisOp *op, ut64 addr, + const ut8 *buf, int len, RzAnalysisOpMask mask); +RZ_IPI void c2x_fill_op_access(RzAnalysis *analysis, const C55ArchDesc *a, const C55Insn *insn, RzAnalysisOp *op); + +RZ_IPI RzAnalysisILConfig *tms320_c2x_il_config(RZ_NONNULL RzAnalysis *analysis); + +RZ_IPI RzILOpEffect *c2x_lift(const C55Insn *insn, ut64 pc); + +RZ_IPI RzILOpPure *c2x_ea(const C55ArchDesc *a, const C55Operand *m); + +#ifdef __cplusplus +} +#endif + +#endif /* RZ_TMS320_C2X_H */ diff --git a/librz/arch/isa/tms320/c2x/c2x_core_rows.inc b/librz/arch/isa/tms320/c2x/c2x_core_rows.inc new file mode 100644 index 0000000000..610c801f31 --- /dev/null +++ b/librz/arch/isa/tms320/c2x/c2x_core_rows.inc @@ -0,0 +1,154 @@ +// SPDX-FileCopyrightText: 2026 RizinOrg +// SPDX-License-Identifier: LGPL-3.0-only + +// clang-format off + // TMS320C2x core instruction rows (shared with the C5x superset) + // top-nibble arithmetic with 4-bit shift: ADD / SUB / LAC + { OP(0xf080, 0x0000), .id = C2X_INS_ADD, .len = 2, .ops = { MEM(0), SHF(8, 4) } }, + { OP(0xf080, 0x0080), .id = C2X_INS_ADD, .len = 2, .ops = { MEM(0), SHF(8, 4), NARP(0) } }, + { OP(0xf080, 0x1000), .id = C2X_INS_SUB, .len = 2, .ops = { MEM(0), SHF(8, 4) } }, + { OP(0xf080, 0x1080), .id = C2X_INS_SUB, .len = 2, .ops = { MEM(0), SHF(8, 4), NARP(0) } }, + { OP(0xf080, 0x2000), .id = C2X_INS_LAC, .len = 2, .ops = { MEM(0), SHF(8, 4) } }, + { OP(0xf080, 0x2080), .id = C2X_INS_LAC, .len = 2, .ops = { MEM(0), SHF(8, 4), NARP(0) } }, + + // LAR / SAR: 5-bit opcode (00110rrr / 01110rrr) + R + { OP(0xf880, 0x3000), .id = C2X_INS_LAR, .len = 2, .ops = { AR(8), MEM(0) } }, + { OP(0xf880, 0x3080), .id = C2X_INS_LAR, .len = 2, .ops = { AR(8), MEM(0), NARP(0) } }, + { OP(0xf880, 0x7000), .id = C2X_INS_SAR, .len = 2, .ops = { AR(8), MEM(0) } }, + { OP(0xf880, 0x7080), .id = C2X_INS_SAR, .len = 2, .ops = { AR(8), MEM(0), NARP(0) } }, + + // single-byte-opcode memory block 0x38..0x5B: multiply / T-P / logical / move + MEMOP_D(0x38, C2X_INS_MPY), MEMOP_I(0x38, C2X_INS_MPY), + MEMOP_D(0x39, C2X_INS_SQRA), MEMOP_I(0x39, C2X_INS_SQRA), + MEMOP_D(0x3a, C2X_INS_MPYA), MEMOP_I(0x3a, C2X_INS_MPYA), + MEMOP_D(0x3b, C2X_INS_MPYS), MEMOP_I(0x3b, C2X_INS_MPYS), + MEMOP_D(0x3c, C2X_INS_LT), MEMOP_I(0x3c, C2X_INS_LT), + MEMOP_D(0x3d, C2X_INS_LTA), MEMOP_I(0x3d, C2X_INS_LTA), + MEMOP_D(0x3e, C2X_INS_LTP), MEMOP_I(0x3e, C2X_INS_LTP), + MEMOP_D(0x3f, C2X_INS_LTD), MEMOP_I(0x3f, C2X_INS_LTD), + MEMOP_D(0x40, C2X_INS_ZALH), MEMOP_I(0x40, C2X_INS_ZALH), + MEMOP_D(0x41, C2X_INS_ZALS), MEMOP_I(0x41, C2X_INS_ZALS), + MEMOP_D(0x42, C2X_INS_LACT), MEMOP_I(0x42, C2X_INS_LACT), + MEMOP_D(0x43, C2X_INS_ADDC), MEMOP_I(0x43, C2X_INS_ADDC), + MEMOP_D(0x44, C2X_INS_SUBH), MEMOP_I(0x44, C2X_INS_SUBH), + MEMOP_D(0x45, C2X_INS_SUBS), MEMOP_I(0x45, C2X_INS_SUBS), + MEMOP_D(0x46, C2X_INS_SUBT), MEMOP_I(0x46, C2X_INS_SUBT), + MEMOP_D(0x47, C2X_INS_SUBC), MEMOP_I(0x47, C2X_INS_SUBC), + MEMOP_D(0x48, C2X_INS_ADDH), MEMOP_I(0x48, C2X_INS_ADDH), + MEMOP_D(0x49, C2X_INS_ADDS), MEMOP_I(0x49, C2X_INS_ADDS), + MEMOP_D(0x4a, C2X_INS_ADDT), MEMOP_I(0x4a, C2X_INS_ADDT), + MEMOP_D(0x4b, C2X_INS_RPT), MEMOP_I(0x4b, C2X_INS_RPT), + MEMOP_D(0x4c, C2X_INS_XOR), MEMOP_I(0x4c, C2X_INS_XOR), + MEMOP_D(0x4d, C2X_INS_OR), MEMOP_I(0x4d, C2X_INS_OR), + MEMOP_D(0x4e, C2X_INS_AND), MEMOP_I(0x4e, C2X_INS_AND), + MEMOP_D(0x4f, C2X_INS_SUBB), MEMOP_I(0x4f, C2X_INS_SUBB), + MEMOP_D(0x50, C2X_INS_LST), MEMOP_I(0x50, C2X_INS_LST), + MEMOP_D(0x51, C2X_INS_LST1), MEMOP_I(0x51, C2X_INS_LST1), + MEMOP_D(0x52, C2X_INS_LDP), MEMOP_I(0x52, C2X_INS_LDP), + MEMOP_D(0x53, C2X_INS_LPH), MEMOP_I(0x53, C2X_INS_LPH), + MEMOP_D(0x54, C2X_INS_PSHD), MEMOP_I(0x54, C2X_INS_PSHD), + MEMOP_D(0x56, C2X_INS_DMOV), MEMOP_I(0x56, C2X_INS_DMOV), + MEMOP_D(0x57, C2X_INS_BITT), MEMOP_I(0x57, C2X_INS_BITT), + MEMOP_D(0x58, C2X_INS_TBLR), MEMOP_I(0x58, C2X_INS_TBLR), + MEMOP_D(0x59, C2X_INS_TBLW), MEMOP_I(0x59, C2X_INS_TBLW), + MEMOP_D(0x5a, C2X_INS_SQRS), MEMOP_I(0x5a, C2X_INS_SQRS), + MEMOP_D(0x5b, C2X_INS_LTS), MEMOP_I(0x5b, C2X_INS_LTS), + + // 0x5C/0x5D: multiply-accumulate with program memory (2-word, %B = pma) + PMAOP_D(0x5c, C2X_INS_MACD), PMAOP_I(0x5c, C2X_INS_MACD), + PMAOP_D(0x5d, C2X_INS_MAC), PMAOP_I(0x5d, C2X_INS_MAC), + + // store accumulator low/high with 3-bit shift (%S) + { OP(0xf880, 0x6000), .id = C2X_INS_SACL, .len = 2, .ops = { MEM(0), SHF(8, 3) } }, + { OP(0xf880, 0x6080), .id = C2X_INS_SACL, .len = 2, .ops = { MEM(0), SHF(8, 3), NARP(0) } }, + { OP(0xf880, 0x6800), .id = C2X_INS_SACH, .len = 2, .ops = { MEM(0), SHF(8, 3) } }, + { OP(0xf880, 0x6880), .id = C2X_INS_SACH, .len = 2, .ops = { MEM(0), SHF(8, 3), NARP(0) } }, + + // 0x78..0x7D single-byte-opcode block: store status / stack + MEMOP_D(0x78, C2X_INS_SST), MEMOP_I(0x78, C2X_INS_SST), + MEMOP_D(0x79, C2X_INS_SST1), MEMOP_I(0x79, C2X_INS_SST1), + MEMOP_D(0x7a, C2X_INS_POPD), MEMOP_I(0x7a, C2X_INS_POPD), + MEMOP_D(0x7b, C2X_INS_ZALR), MEMOP_I(0x7b, C2X_INS_ZALR), + MEMOP_D(0x7c, C2X_INS_SPL), MEMOP_I(0x7c, C2X_INS_SPL), + MEMOP_D(0x7d, C2X_INS_SPH), MEMOP_I(0x7d, C2X_INS_SPH), + + // ADRK / SBRK: add/subtract short immediate (full low byte) to/from AR(ARP) + { OP(0xff00, 0x7e00), .id = C2X_INS_ADRK, .len = 2, .ops = { IMM(0, 8, 0) } }, + { OP(0xff00, 0x7f00), .id = C2X_INS_SBRK, .len = 2, .ops = { IMM(0, 8, 0) } }, + + // IN / OUT: 4-bit port (%P) in bits 11-8 + { OP(0xf080, 0x8000), .id = C2X_INS_IN, .len = 2, .ops = { MEM(0), IMM(8, 4, 0) } }, + { OP(0xf080, 0x8080), .id = C2X_INS_IN, .len = 2, .ops = { MEM(0), IMM(8, 4, 0), NARP(0) } }, + { OP(0xf080, 0xe000), .id = C2X_INS_OUT, .len = 2, .ops = { MEM(0), IMM(8, 4, 0) } }, + { OP(0xf080, 0xe080), .id = C2X_INS_OUT, .len = 2, .ops = { MEM(0), IMM(8, 4, 0), NARP(0) } }, + + // BIT: 4-bit bit-code (%T) in bits 11-8 + { OP(0xf080, 0x9000), .id = C2X_INS_BIT, .len = 2, .ops = { MEM(0), SHF(8, 4) } }, + { OP(0xf080, 0x9080), .id = C2X_INS_BIT, .len = 2, .ops = { MEM(0), SHF(8, 4), NARP(0) } }, + + // immediate-operand instructions + { OP(0xe000, 0xa000), .id = C2X_INS_MPYK, .len = 2, .ops = { IMM(0, 13, 1) } }, + { OP(0xf800, 0xc000), .id = C2X_INS_LARK, .len = 2, .ops = { AR(8), IMM(0, 8, 0) } }, + { OP(0xfe00, 0xc800), .id = C2X_INS_LDPK, .len = 2, .ops = { IMM(0, 9, 0) } }, + { FIX(0xca00, C2X_INS_ZAC) }, // LACK #0 == ZAC (specific row before the range) + { OP(0xff00, 0xca00), .id = C2X_INS_LACK, .len = 2, .ops = { IMM(0, 8, 0) } }, + { OP(0xff00, 0xcb00), .id = C2X_INS_RPTK, .len = 2, .ops = { IMM(0, 8, 0) } }, + // NORM is CEx2 with the indirect AR-modify field in bits 6:4 (always indirect). + { OP(0xff8f, 0xce82), .id = C2X_INS_NORM, .len = 2, .ops = { MEM(0) } }, + { OP(0xff00, 0xcc00), .id = C2X_INS_ADDK, .len = 2, .ops = { IMM(0, 8, 0) } }, + { OP(0xff00, 0xcd00), .id = C2X_INS_SUBK, .len = 2, .ops = { IMM(0, 8, 0) } }, + + // 0xCF is MPYU (multiply unsigned). The MAME *disassembler* table mislabels + // it as a second "mpys", but the MAME execution table (and the TI ISA) run + // MPYU here; we follow the architecturally-correct mnemonic. + MEMOP_D(0xcf, C2X_INS_MPYU), MEMOP_I(0xcf, C2X_INS_MPYU), + + // CE block: no-operand status / mode / control + { FIX(0xce00, C2X_INS_EINT) }, { FIX(0xce01, C2X_INS_DINT) }, + { FIX(0xce02, C2X_INS_ROVM) }, { FIX(0xce03, C2X_INS_SOVM) }, + { FIX(0xce04, C2X_INS_CNFD) }, { FIX(0xce05, C2X_INS_CNFP) }, + { FIX(0xce06, C2X_INS_RSXM) }, { FIX(0xce07, C2X_INS_SSXM) }, + { OP(0xfffc, 0xce08), .id = C2X_INS_SPM, .len = 2, .ops = { IMM(0, 2, 0) } }, + { FIX(0xce0c, C2X_INS_RXF) }, { FIX(0xce0d, C2X_INS_SXF) }, + { FIX(0xce14, C2X_INS_PAC) }, { FIX(0xce15, C2X_INS_APAC) }, { FIX(0xce16, C2X_INS_SPAC) }, + { FIX(0xce18, C2X_INS_SFL) }, { FIX(0xce19, C2X_INS_SFR) }, + { FIX(0xce1b, C2X_INS_ABS) }, { FIX(0xce1c, C2X_INS_PUSH) }, { FIX(0xce1d, C2X_INS_POP) }, + { FIX(0xce1e, C2X_INS_TRAP) }, { FIX(0xce1f, C2X_INS_IDLE) }, + { FIX(0xce20, C2X_INS_RTXM) }, { FIX(0xce21, C2X_INS_STXM) }, + { FIX(0xce23, C2X_INS_NEG) }, { FIX(0xce24, C2X_INS_CALA) }, { FIX(0xce25, C2X_INS_BACC) }, + { FIX(0xce26, C2X_INS_RET) }, { FIX(0xce27, C2X_INS_CMPL) }, + { FIX(0xce30, C2X_INS_RC) }, { FIX(0xce31, C2X_INS_SC) }, + { FIX(0xce32, C2X_INS_RTC) }, { FIX(0xce33, C2X_INS_STC) }, + { FIX(0xce34, C2X_INS_ROL) }, { FIX(0xce35, C2X_INS_ROR) }, + { FIX(0xce36, C2X_INS_RFSM) }, { FIX(0xce37, C2X_INS_SFSM) }, + { FIX(0xce38, C2X_INS_RHM) }, { FIX(0xce39, C2X_INS_SHM) }, + // CONF K (TMS320C26): two low bits select the RAM block configuration (ST1.CNF) + { OP(0xfffc, 0xce3c), .id = C2X_INS_CONF, .len = 2, .ops = { IMM(0, 2, 0) } }, + { OP(0xfffc, 0xce50), .id = C2X_INS_CMPR, .len = 2, .ops = { IMM(0, 2, 0) } }, + + // D block: long-immediate (opcode word + 16-bit data word) + { OP(0xf8ff, 0xd000), .id = C2X_INS_LRLK, .len = 4, .ops = { AR(24), IMM(0, 16, 0) } }, + { OP(0xf0ff, 0xd001), .id = C2X_INS_LALK, .len = 4, .ops = { IMM(0, 16, 0), SHF(24, 4) } }, + { OP(0xf0ff, 0xd002), .id = C2X_INS_ADLK, .len = 4, .ops = { IMM(0, 16, 0), SHF(24, 4) } }, + { OP(0xf0ff, 0xd003), .id = C2X_INS_SBLK, .len = 4, .ops = { IMM(0, 16, 0), SHF(24, 4) } }, + { OP(0xf0ff, 0xd004), .id = C2X_INS_ANDK, .len = 4, .ops = { IMM(0, 16, 0), SHF(24, 4) } }, + { OP(0xf0ff, 0xd005), .id = C2X_INS_ORK, .len = 4, .ops = { IMM(0, 16, 0), SHF(24, 4) } }, + { OP(0xf0ff, 0xd006), .id = C2X_INS_XORK, .len = 4, .ops = { IMM(0, 16, 0), SHF(24, 4) } }, + + // control transfer (opcode word + 16-bit target word). BC/BNC confirmed at + // 0x5E/0x5F; the F-block follows the same opcode-byte + bit7 pattern. + BROP(0x5e, C2X_INS_BC), BROP(0x5f, C2X_INS_BNC), + BROP(0xf0, C2X_INS_BV), BROP(0xf1, C2X_INS_BGZ), BROP(0xf2, C2X_INS_BLEZ), + BROP(0xf3, C2X_INS_BLZ), BROP(0xf4, C2X_INS_BGEZ), BROP(0xf5, C2X_INS_BNZ), + BROP(0xf6, C2X_INS_BZ), BROP(0xf7, C2X_INS_BNV), BROP(0xf8, C2X_INS_BBZ), + BROP(0xf9, C2X_INS_BBNZ), BROP(0xfa, C2X_INS_BIOZ), BANZOP(0xfb, C2X_INS_BANZ), + // 0xFC/0xFD: block move from program/data memory (2-word, %B = source addr) + PMAOP_D(0xfc, C2X_INS_BLKP), PMAOP_I(0xfc, C2X_INS_BLKP), + PMAOP_D(0xfd, C2X_INS_BLKD), PMAOP_I(0xfd, C2X_INS_BLKD), + BROP(0xfe, C2X_INS_CALL), BROP(0xff, C2X_INS_B), + + // AR-pointer / nop (specific rows before the general MAR indirect row) + { FIX(0x5500, C2X_INS_NOP) }, + { OP(0xfff8, 0x5588), .id = C2X_INS_LARP, .len = 2, .ops = { IMM(0, 3, 0) } }, + { OP(0xff80, 0x5580), .id = C2X_INS_MAR, .len = 2, .ops = { MEM(0), NARP(0) } }, + // clang-format on diff --git a/librz/arch/isa/tms320/c2x/c2x_rowdefs.h b/librz/arch/isa/tms320/c2x/c2x_rowdefs.h new file mode 100644 index 0000000000..4144016d6b --- /dev/null +++ b/librz/arch/isa/tms320/c2x/c2x_rowdefs.h @@ -0,0 +1,59 @@ +// SPDX-FileCopyrightText: 2026 RizinOrg +// SPDX-License-Identifier: LGPL-3.0-only + +/** + * \file + * Shared decode-table row macros for the TMS320C2x/C5x tables. Included + * immediately before a `static const C55InsnDef _table[] = {` body and + * paired with c2x_rowundefs.h afterwards. Requires the c2x_x_* operand + * extractors (declared in c2x.h) to be visible. + */ + +// head-space mask/match: the 16-bit opcode word occupies head bits 31:16. +#define OP(wmask, wmatch) .mask = ((ut32)(wmask) << 16), .match = ((ut32)(wmatch) << 16) +#define MEM(lo_) { .lo = (lo_), .width = 8, .fn = c2x_x_mem } +#define NARP(lo_) { .lo = (lo_), .width = 4, .fn = c2x_x_nextarp } +#define SHF(lo_, w_) { .lo = (lo_), .width = (w_), .fn = c2x_x_shift } +#define AR(lo_) { .lo = (lo_), .width = 3, .fn = c2x_x_reg } +#define IMM(lo_, w_, p_) { .lo = (lo_), .width = (w_), .fn = c2x_x_imm, .param = (p_) } +#define BR(lo_) { .lo = (lo_), .width = 16, .fn = c2x_x_branch } + +// memory-reference opcode keyed on the whole high byte + the direct/indirect bit +#define MEMOP_D(hb, id_) \ + { \ + OP(0xff80, ((ut16)(hb) << 8) | 0x0000), .id = (id_), .len = 2, .ops = { MEM(0) } \ + } +#define MEMOP_I(hb, id_) \ + { \ + OP(0xff80, ((ut16)(hb) << 8) | 0x0080), .id = (id_), .len = 2, .ops = { MEM(0), \ + NARP(0) } \ + } +#define FIX(word, id_) OP(0xffff, (word)), .id = (id_), .len = 2 +#define BROP(hb, id_) \ + { \ + OP(0xff80, ((ut16)(hb) << 8) | 0x0080), .id = (id_), .len = 4, .ops = { BR(0) } \ + } +// BANZ also carries the indirect addressing byte (in the leading word, bits +// 31:16) that selects the loop-counter AR post-modify (*- etc.); decode it so +// the lifter can apply the auxiliary-register update. +#define BANZOP(hb, id_) \ + { \ + OP(0xff80, ((ut16)(hb) << 8) | 0x0080), .id = (id_), .len = 4, .ops = { BR(0), \ + MEM(16), \ + NARP(16) } \ + } + +// 2-word instruction carrying a 16-bit memory address in the trailing word plus +// a destination addressing byte in the leading word (mac/macd/blkp/blkd). The +// leading word sits in packed bits 31:16, so MEM/NARP index from bit 16. +#define PMAOP_D(hb, id_) \ + { \ + OP(0xff80, ((ut16)(hb) << 8) | 0x0000), .id = (id_), .len = 4, .ops = { IMM(0, 16, 0), \ + MEM(16) } \ + } +#define PMAOP_I(hb, id_) \ + { \ + OP(0xff80, ((ut16)(hb) << 8) | 0x0080), .id = (id_), .len = 4, .ops = { IMM(0, 16, 0), \ + MEM(16), \ + NARP(16) } \ + } diff --git a/librz/arch/isa/tms320/c2x/c2x_rowundefs.h b/librz/arch/isa/tms320/c2x/c2x_rowundefs.h new file mode 100644 index 0000000000..b9993b0bed --- /dev/null +++ b/librz/arch/isa/tms320/c2x/c2x_rowundefs.h @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2026 RizinOrg +// SPDX-License-Identifier: LGPL-3.0-only + +#undef OP +#undef MEM +#undef NARP +#undef SHF +#undef AR +#undef IMM +#undef BR +#undef MEMOP_D +#undef MEMOP_I +#undef FIX +#undef BROP +#undef BANZOP +#undef PMAOP_D +#undef PMAOP_I diff --git a/librz/arch/isa/tms320/c55_ir.c b/librz/arch/isa/tms320/c55_ir.c index 5ddae8f8de..8e704d9f94 100644 --- a/librz/arch/isa/tms320/c55_ir.c +++ b/librz/arch/isa/tms320/c55_ir.c @@ -25,6 +25,31 @@ // decode engine // --------------------------------------------------------------------------- +/** + * \brief Whether \p arch encodes a parallel-execution bit in its opcode words. + * \param arch Architecture being decoded + * \return true if instructions of \p arch can carry a parallel flag + * + * The C54x, C2x and C5x are word-oriented and have no such bit, so their + * instructions never decode as the parallel form. + */ +static inline bool c55_arch_has_parallel_bit(C55Arch arch) { + return arch != C55_ARCH_C54X && arch != C55_ARCH_C2X && arch != C55_ARCH_C5X; +} + +/** + * \brief Whether \p arch counts program-memory addresses in 16-bit words. + * \param arch Architecture being decoded + * \return true if a program address operand is a word index, not a byte offset + * + * The C54x/C2x/C5x fetch 16-bit words and their branch/call operands count words, + * so a target has to be doubled to land in the byte address space the analysis + * and the RzIL memory model use. + */ +static inline bool c55_arch_word_addressed(C55Arch arch) { + return arch == C55_ARCH_C2X || arch == C55_ARCH_C5X || arch == C55_ARCH_C54X; +} + // Pack the first \p n bytes (<= 8) MSB-first into a word: byte 0 is most // significant, so a field's bit index counts from the LSB of the instruction. static ut64 c55_pack(const ut8 *buf, int n) { @@ -148,9 +173,8 @@ bool c55_decode(const C55ArchDesc *a, const ut8 *buf, int len, C55Insn *out) { // of the leading byte is the "||" flag rather than part of the opcode. // A row opts into this by leaving that bit unconstrained in its mask // (e.g. 0xfe000000), in which case the bit's value selects the parallel - // form; rows that pin the bit (0xff000000) treat it as opcode. C54x is - // word-oriented and has no such bit, so it never carries a parallel flag. - out->parallel = a->arch != C55_ARCH_C54X && !def->no_parallel && (def->mask & 0x01000000) == 0 && (head & 0x01000000) != 0; + // form; rows that pin the bit (0xff000000) treat it as opcode. + out->parallel = c55_arch_has_parallel_bit(a->arch) && !def->no_parallel && (def->mask & 0x01000000) == 0 && (head & 0x01000000) != 0; const ut64 bits = c55_pack(buf, ilen); if (def->alt_bit && (c55_field(bits, (ut8)(def->alt_bit - 1), 1) != 0)) { // a variant selector beyond the 4-byte match head (e.g. firssub vs @@ -500,10 +524,10 @@ static ut64 c55_branch_target(const C55Insn *insn, ut64 pc) { if (o->abs_target) { // Absolute target: the operand is the destination address // itself (24-bit program space), not a pc-relative offset. - // The C54x counts program words here, so scale the target + // Word-addressed cores count words here, so scale the target // into the byte address space the analysis works in. ut64 target = v & 0xffffff; - return insn->arch == C55_ARCH_C54X ? target * 2 : target; + return c55_arch_word_addressed(insn->arch) ? target * 2 : target; } st64 soff = o->reltarget_unsigned ? (st64)v @@ -3711,6 +3735,18 @@ static void c55_fmt_reg(RzStrBuf *sb, const C55ArchDesc *a, const C55Reg *r) { } static void c55_fmt_mem(RzStrBuf *sb, const C55ArchDesc *a, const C55Operand *m) { + if (a->arch == C55_ARCH_C2X || a->arch == C55_ARCH_C5X) { + // Only the direct form reaches this helper: the indirect spellings + // (*, *+, *BR0+ and the ",arX" next-ARP suffix) carry their own text in + // C55Operand.raw, which the operand loop emits before calling here. + // Assert it so a row that forgets to set raw is caught rather than + // silently rendered as a bogus data-page offset. + rz_warn_if_fail(m->amode == C55_AM_DIRECT); + // The page itself comes from DP at run time, so only the 7-bit offset + // is known statically. + rz_strbuf_appendf(sb, "0x%" PFMT32x, (ut32)m->disp & 0x7f); + return; + } if (a->arch == C55_ARCH_C54X) { // C54x addressing syntax differs from C55x (AR0-indexed *arN+0, circular // *arN+%, bit-reverse *arN+0B). Render it self-contained and return; the @@ -4148,8 +4184,8 @@ char *c55_format(const C55ArchDesc *a, const C55Insn *insn) { // with the '#' prefix (the sftl dst, #1 / #-1 forms). rz_strbuf_appendf(&sb, "#%" PFMT64d, (st64)op->imm); } else if (op->addr) { - if (a->arch == C55_ARCH_C54X) { - // C54x renders program/data addresses as a bare hex value + if (a->arch == C55_ARCH_C54X || a->arch == C55_ARCH_C2X || a->arch == C55_ARCH_C5X) { + // C54x/C2x render program/data addresses as a bare hex value // (the '#' prefix is reserved for immediates). rz_strbuf_appendf(&sb, "0x%" PFMT64x, op->imm); } else { diff --git a/librz/arch/isa/tms320/c55_ir.h b/librz/arch/isa/tms320/c55_ir.h index 2d7c05e16d..a19a4766c5 100644 --- a/librz/arch/isa/tms320/c55_ir.h +++ b/librz/arch/isa/tms320/c55_ir.h @@ -36,6 +36,8 @@ typedef enum { C55_ARCH_C54X = 0, ///< TMS320C54x C55_ARCH_C55X, ///< TMS320C55x C55_ARCH_C55XPLUS, ///< TMS320C55x+ + C55_ARCH_C2X, ///< TMS320C2x (legacy single-accumulator fixed-point) + C55_ARCH_C5X, ///< TMS320C5x (second-generation fixed-point; C2x-compatible superset) } C55Arch; /** Register class; family-wide superset, each arch uses only its subset. */ diff --git a/librz/arch/meson.build b/librz/arch/meson.build index 9305660736..d5b2699cfb 100644 --- a/librz/arch/meson.build +++ b/librz/arch/meson.build @@ -321,6 +321,7 @@ arch_isa_sources = [ 'isa/tms320/c55_ir.c', 'isa/tms320/c54x/c54x.c', 'isa/tms320/c54x/c54x_il.c', + 'isa/tms320/c2x/c2x.c', 'isa/tms320/c55x_plus/c55plus_arch.c', 'isa/tms320/c55x/c55x_analysis.c', 'isa/tms320/c55x_plus/c55plus_analysis.c', diff --git a/librz/arch/p/analysis/analysis_tms320.c b/librz/arch/p/analysis/analysis_tms320.c index bce401a812..80b434b8fd 100644 --- a/librz/arch/p/analysis/analysis_tms320.c +++ b/librz/arch/p/analysis/analysis_tms320.c @@ -9,6 +9,7 @@ #include #include #include +#include #include typedef struct tms320_ctx_t { @@ -25,6 +26,8 @@ int tms320_analysis_op(RzAnalysis *analysis, RzAnalysisOp *op, ut64 addr, const return tms320_c64x_op(analysis, op, addr, buf, len, mask, context->c64x); } else if (cpu && rz_str_casecmp(cpu, "c54x") == 0) { return tms320_c54x_op(analysis, op, addr, buf, len, mask); + } else if (cpu && rz_str_casecmp(cpu, "c2x") == 0) { + return tms320_c2x_op(analysis, op, addr, buf, len, mask); } return tms320_c55x_op_byte(analysis, op, addr, buf, len, mask); } @@ -109,6 +112,56 @@ static char *get_reg_profile(RZ_BORROW RzAnalysis *a) { "ctr xpc .16 52 0 # Extended program counter\n" "ctr pc .24 54 0 # Program counter\n"); } + if (cpu0 && rz_str_casecmp(cpu0, "c2x") == 0) { + // TMS320C2x: a single 32-bit accumulator ACC (with ACCL/ACCH 16-bit + // halves overlapping it), the 16-bit temporary T, the 32-bit product + // register P (PL/PH halves), eight 16-bit auxiliary registers AR0-AR7, + // the 3-bit ARP pointer (held in a 16-bit slot), the 9-bit DP data page, + // the ST0/ST1 status words and a 16-bit PC. SP is a synthetic stack + // pointer (the hardware stack is not memory-mapped). These names match + // c2x_reg_info()'s il_var bindings so the lifter resolves in the IL VM. + return rz_str_dup( + "=PC\tpc\n" + "=SP\tsp\n" + "=BP\tsp\n" + "=A0\tar0\n" + "=A1\tar1\n" + "=A2\tar2\n" + "=A3\tar3\n" + "=R0\tacc\n" + "ctr acc .32 0 0\n" // Accumulator + "gpr accl .16 0 0\n" // Accumulator low word + "gpr acch .16 2 0\n" // Accumulator high word + "ctr t .16 4 0\n" // Temporary register + "ctr p .32 6 0\n" // Product register + "gpr pl .16 6 0\n" // Product low word + "gpr ph .16 8 0\n" // Product high word + "gpr ar0 .16 10 0\n" // Auxiliary register 0 + "gpr ar1 .16 12 0\n" // Auxiliary register 1 + "gpr ar2 .16 14 0\n" // Auxiliary register 2 + "gpr ar3 .16 16 0\n" // Auxiliary register 3 + "gpr ar4 .16 18 0\n" // Auxiliary register 4 + "gpr ar5 .16 20 0\n" // Auxiliary register 5 + "gpr ar6 .16 22 0\n" // Auxiliary register 6 + "gpr ar7 .16 24 0\n" // Auxiliary register 7 + "ctr arp .16 26 0\n" // Auxiliary register pointer + "ctr dp .16 28 0\n" // Data page pointer + "ctr st0 .16 30 0\n" // Status register 0 + "ctr st1 .16 32 0\n" // Status register 1 + "ctr sp .16 34 0\n" // Stack pointer (synthetic) + "ctr pc .16 36 0\n" // Program counter + // status/mode bits modelled individually for the IL lifter (they + // also live inside ST0/ST1 on real silicon; ST0/ST1 are composed + // from / decomposed to these by SST/SST1/LST/LST1) + "flg c .1 38.0 0\n" // Carry + "flg ov .1 39.0 0\n" // Overflow (sticky until tested) + "flg tc .1 40.0 0\n" // Test/control bit + "gpr ovm .1 41.0 0\n" // Overflow saturation mode + "gpr sxm .1 42.0 0\n" // Sign-extension mode + "gpr pm .2 43.0 0\n" // Product shift mode + "gpr arb .16 44 0\n" // Auxiliary register pointer backup + "gpr rptc .16 46 0\n"); // Repeat counter + } if (is_c5000(rz_analysis_get_cpu(a))) { p = "=PC pc\n" @@ -457,7 +510,7 @@ static RzAnalysisILConfig *tms320_il_config(RzAnalysis *analysis) { RzAnalysisPlugin rz_analysis_plugin_tms320 = { .name = "tms320", .arch = "tms320", - .bits = 32, + .bits = 16 | 32, .desc = "TMS320 DSP family code analysis plugin", .init = tms320_analysis_init, .fini = tms320_analysis_fini, diff --git a/librz/arch/p/asm/asm_tms320.c b/librz/arch/p/asm/asm_tms320.c index 165c68b82e..cf3166f581 100644 --- a/librz/arch/p/asm/asm_tms320.c +++ b/librz/arch/p/asm/asm_tms320.c @@ -8,6 +8,7 @@ #include #include #include +#include #include typedef struct tms_cs_context_t { @@ -28,6 +29,8 @@ static int tms320_disassemble(const RzAsm *a, RzAsmOp *op, const ut8 *buf, int l desc = &c55x_arch_desc; } else if (a->cpu && !rz_str_casecmp(a->cpu, "c54x")) { desc = &c54x_arch_desc; + } else if (a->cpu && !rz_str_casecmp(a->cpu, "c2x")) { + desc = &c2x_arch_desc; } else { rz_asm_op_set_asm(op, "unknown asm.cpu"); return op->size = -1; @@ -76,6 +79,7 @@ static char *tms320_mnemonics(const RzAsm *a, int id, bool json) { static char **tms320_cpu_descriptions() { static char *cpu_desc[] = { "c54x", "Texas Instruments TMS320C54x DSP family", + "c2x", "Texas Instruments TMS320C2x legacy fixed-point DSP family", "c55x", "Texas Instruments TMS320C55x DSP family", "c55x+", "Texas Instruments TMS320C55x+ DSP family", "c64x", "Texas Instruments TMS320C64x DSP family", @@ -87,10 +91,10 @@ static char **tms320_cpu_descriptions() { RzAsmPlugin rz_asm_plugin_tms320 = { .name = "tms320", .arch = "tms320", - .cpus = "c54x,c55x,c55x+,c64x", - .desc = "Texas Instruments TMS320 DSP family (c54x,c55x,c55x+,c64x) disassembler", + .cpus = "c54x,c55x,c55x+,c2x,c64x", + .desc = "Texas Instruments TMS320 DSP family (c54x,c55x,c55x+,c2x,c64x) disassembler", .license = "LGPL3", - .bits = 32, + .bits = 16 | 32, .endian = RZ_SYS_ENDIAN_LITTLE | RZ_SYS_ENDIAN_BIG, .init = tms320_init, .fini = tms320_fini, diff --git a/librz/arch/types/cc-tms320-16.sdb.txt b/librz/arch/types/cc-tms320-16.sdb.txt new file mode 100644 index 0000000000..df0b0fa0a5 --- /dev/null +++ b/librz/arch/types/cc-tms320-16.sdb.txt @@ -0,0 +1,19 @@ +default.cc=c2x + +c2x=cc +cc.c2x.arg0=ar0 +cc.c2x.arg1=ar1 +cc.c2x.arg2=ar2 +cc.c2x.arg3=ar3 +cc.c2x.argn=stack +cc.c2x.maxargs=4 +cc.c2x.ret=acc + +c5x=cc +cc.c5x.arg0=ar0 +cc.c5x.arg1=ar1 +cc.c5x.arg2=ar2 +cc.c5x.arg3=ar3 +cc.c5x.argn=stack +cc.c5x.maxargs=4 +cc.c5x.ret=acc diff --git a/librz/arch/types/meson.build b/librz/arch/types/meson.build index dc979005b1..0c6a331346 100644 --- a/librz/arch/types/meson.build +++ b/librz/arch/types/meson.build @@ -13,6 +13,7 @@ sdb_types_files = [ 'cc-sparc-32', 'cc-spc700-16', 'cc-sysz-64', + 'cc-tms320-16', 'cc-tms320-32', 'cc-tricore-32', 'cc-x86-16', diff --git a/test/db/analysis/tms320.c2x_16 b/test/db/analysis/tms320.c2x_16 new file mode 100644 index 0000000000..1736668a6e --- /dev/null +++ b/test/db/analysis/tms320.c2x_16 @@ -0,0 +1,178 @@ +NAME=c2x analysis: register profile (PC alias resolves) +FILE== +CMDS=<