librz/arch/tms320: add TMS320C5x disassembler and analysis

Add the real TMS320C5x (C50/C51/C53) object encoding. The C5x is
source-compatible with the C2x but encodes instructions differently, so
it cannot reuse the C2x decode table: a dedicated C5x decode front-end
fills the shared C55 instruction representation, carrying the C2x ids for
shared-semantics instructions (so the common consumers apply unchanged)
and new C5x-only ids for the C5x additions (ACCB ops, parallel-logic,
memory-mapped register access, conditional execute/call/return, block
moves, ...). Wired into the tms320 asm and analysis plugins under cpu
"c5x", with the C5x mnemonic and op-type tables and register profile.
Includes disassembly and opcode classification tests.
This commit is contained in:
Anton Kochkov 2026-06-29 21:13:43 +00:00 committed by NOT XVilka
parent 7b36a031ca
commit 148b54a9fc
12 changed files with 1544 additions and 12 deletions

View file

@ -0,0 +1,263 @@
// SPDX-FileCopyrightText: 2026 RizinOrg <info@rizin.re>
// SPDX-License-Identifier: LGPL-3.0-only
/**
* \file
* TMS320C5x architecture descriptor, analysis entry and RzIL VM configuration.
*
* The C5x has its own decode front-end (c5x_decode, the real C5x object
* encoding) and reuses the shared C55 consumers (c55_format, c55_fill_analysis,
* c55_lift) through the descriptor below. Shared-semantics instructions carry
* the C2x ids and route through the C2x mnemonic/op-type/lifter; the C5x-only
* ids are handled by c5x_mnemonic/c5x_op_type/c5x_lift.
*/
#include "c5x.h"
#include "../c2x/c2x.h"
/**
* \brief Map an instruction id to its C5x mnemonic.
* \param id C5X_INS_* id, or a shared C2X_INS_* id
* \return Static mnemonic string, never NULL
*
* Only the C5x-only ids and the shared ids whose canonical C5x spelling
* differs from the C2x one are handled here; the rest delegate to
* \ref c2x_mnemonic.
*/
RZ_IPI const char *c5x_mnemonic(ut16 id) {
switch (id) {
// shared ids whose canonical C5x spelling differs from the C2x mnemonic
case C2X_INS_ADDK: return "add";
case C2X_INS_SUBK: return "sub";
case C2X_INS_LARK: return "lar";
// 16-bit immediate ALU forms (decoded as the C2x long-immediate ops)
case C2X_INS_LALK: return "lacc";
case C2X_INS_ADLK: return "add";
case C2X_INS_SBLK: return "sub";
case C2X_INS_ANDK: return "and";
case C2X_INS_ORK: return "or";
case C2X_INS_XORK: return "xor";
case C2X_INS_RPTK: return "rpt";
case C2X_INS_LDPK: return "ldp";
// C5x-only mnemonics
case C5X_INS_LACC: return "lacc";
case C5X_INS_LACL: return "lacl";
case C5X_INS_LACB: return "lacb";
case C5X_INS_SACB: return "sacb";
case C5X_INS_EXAR: return "exar";
case C5X_INS_ADDB: return "addb";
case C5X_INS_SBB: return "sbb";
case C5X_INS_ADCB: return "adcb";
case C5X_INS_SBBB: return "sbbb";
case C5X_INS_CRGT: return "crgt";
case C5X_INS_CRLT: return "crlt";
case C5X_INS_ANDB: return "andb";
case C5X_INS_ORB: return "orb";
case C5X_INS_XORB: return "xorb";
case C5X_INS_ROLB: return "rolb";
case C5X_INS_RORB: return "rorb";
case C5X_INS_SFLB: return "sflb";
case C5X_INS_SFRB: return "sfrb";
case C5X_INS_SAMM: return "samm";
case C5X_INS_LAMM: return "lamm";
case C5X_INS_LMMR: return "lmmr";
case C5X_INS_SMMR: return "smmr";
case C5X_INS_BLDD: return "bldd";
case C5X_INS_BLPD: return "blpd";
case C5X_INS_BLDP: return "bldp";
case C5X_INS_MADS: return "mads";
case C5X_INS_MADD: return "madd";
case C5X_INS_SPLK: return "splk";
case C5X_INS_BCND: return "bcnd";
case C5X_INS_BCNDD: return "bcndd";
case C5X_INS_CC: return "cc";
case C5X_INS_CCD: return "ccd";
case C5X_INS_RETC: return "retc";
case C5X_INS_RETCD: return "retcd";
case C5X_INS_RETD: return "retd";
case C5X_INS_XC: return "xc";
case C5X_INS_BSAR: return "bsar";
case C5X_INS_ZAP: return "zap";
case C5X_INS_ZPR: return "zpr";
case C5X_INS_SATH: return "sath";
case C5X_INS_SATL: return "satl";
case C5X_INS_BACCD: return "baccd";
case C5X_INS_CALAD: return "calad";
case C5X_INS_APL: return "apl";
case C5X_INS_OPL: return "opl";
case C5X_INS_XPL: return "xpl";
case C5X_INS_CPL: return "cpl";
case C5X_INS_RPTB: return "rptb";
case C5X_INS_RPTZ: return "rptz";
case C5X_INS_SETC: return "setc";
case C5X_INS_CLRC: return "clrc";
case C5X_INS_IDLE2: return "idle2";
case C5X_INS_NMI: return "nmi";
case C5X_INS_RETE: return "rete";
case C5X_INS_RETI: return "reti";
case C5X_INS_INTR: return "intr";
case C5X_INS_BD: return "bd";
case C5X_INS_CALLD: return "calld";
case C5X_INS_BANZD: return "banzd";
case C5X_INS_LST: return "lst";
case C5X_INS_SST: return "sst";
default: return c2x_mnemonic(id);
}
}
/**
* \brief Map an instruction id to its RzAnalysisOp type.
* \param id C5X_INS_* id, or a shared C2X_INS_* id
* \return RZ_ANALYSIS_OP_TYPE_* value for \p id
*
* Shared ids delegate to \ref c2x_op_type.
*/
RZ_IPI ut32 c5x_op_type(ut16 id) {
switch (id) {
case C5X_INS_LACC:
case C5X_INS_LACL:
case C5X_INS_LACB:
case C5X_INS_SACB:
case C5X_INS_EXAR:
case C5X_INS_SAMM:
case C5X_INS_LAMM:
case C5X_INS_LMMR:
case C5X_INS_SMMR:
case C5X_INS_BLDD:
case C5X_INS_BLPD:
case C5X_INS_BLDP:
case C5X_INS_SPLK:
return RZ_ANALYSIS_OP_TYPE_MOV;
case C5X_INS_ADDB:
case C5X_INS_ADCB:
return RZ_ANALYSIS_OP_TYPE_ADD;
case C5X_INS_SBB:
case C5X_INS_SBBB:
return RZ_ANALYSIS_OP_TYPE_SUB;
case C5X_INS_ANDB:
return RZ_ANALYSIS_OP_TYPE_AND;
case C5X_INS_ORB:
return RZ_ANALYSIS_OP_TYPE_OR;
case C5X_INS_XORB:
return RZ_ANALYSIS_OP_TYPE_XOR;
case C5X_INS_CRGT:
case C5X_INS_CRLT:
case C5X_INS_SATH:
case C5X_INS_SATL:
case C5X_INS_APL:
case C5X_INS_OPL:
case C5X_INS_XPL:
case C5X_INS_CPL:
case C5X_INS_MADS:
case C5X_INS_MADD:
return RZ_ANALYSIS_OP_TYPE_MOV;
case C5X_INS_ROLB:
case C5X_INS_SFLB:
case C5X_INS_BSAR:
return RZ_ANALYSIS_OP_TYPE_SHL;
case C5X_INS_RORB:
case C5X_INS_SFRB:
return RZ_ANALYSIS_OP_TYPE_SHR;
case C5X_INS_ZAP:
case C5X_INS_ZPR:
return RZ_ANALYSIS_OP_TYPE_MOV;
case C5X_INS_SETC:
case C5X_INS_CLRC:
return RZ_ANALYSIS_OP_TYPE_MOV;
case C5X_INS_LST:
case C5X_INS_SST:
return RZ_ANALYSIS_OP_TYPE_MOV;
case C5X_INS_BCND:
case C5X_INS_BCNDD:
case C5X_INS_XC:
case C5X_INS_BANZD:
return RZ_ANALYSIS_OP_TYPE_CJMP;
case C5X_INS_BD:
return RZ_ANALYSIS_OP_TYPE_JMP;
case C5X_INS_CC:
case C5X_INS_CCD:
return RZ_ANALYSIS_OP_TYPE_CCALL;
case C5X_INS_CALLD:
return RZ_ANALYSIS_OP_TYPE_CALL;
case C5X_INS_BACCD:
return RZ_ANALYSIS_OP_TYPE_UJMP;
case C5X_INS_CALAD:
return RZ_ANALYSIS_OP_TYPE_UCALL;
case C5X_INS_RETC:
case C5X_INS_RETCD:
return RZ_ANALYSIS_OP_TYPE_CRET;
case C5X_INS_RETD:
return RZ_ANALYSIS_OP_TYPE_RET;
case C5X_INS_RPTB:
case C5X_INS_RPTZ:
return RZ_ANALYSIS_OP_TYPE_NOP;
case C5X_INS_IDLE2:
case C5X_INS_NMI:
case C5X_INS_INTR:
return RZ_ANALYSIS_OP_TYPE_NULL;
case C5X_INS_RETE:
case C5X_INS_RETI:
return RZ_ANALYSIS_OP_TYPE_RET;
default:
return c2x_op_type(id);
}
}
/**
* \brief C5x architecture descriptor for the shared C55 engine.
*
* Carries the C5x's own decoder and the consumers that fall back to the C2x
* implementations for the shared instruction ids.
*/
const C55ArchDesc c5x_arch_desc = {
.arch = C55_ARCH_C5X,
.cpu_name = "c5x",
.table = NULL, // decoded by c5x_decode(), not the shared table engine
.table_len = 0,
.insn_len = NULL,
.reg_info = c2x_reg_info, // operands reference only C2x reg classes (ARn)
.mnemonic = c5x_mnemonic,
.op_type = c5x_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,
.cond_exec_prefix = false,
.parallel_prefix = false,
};
/**
* \brief Analysis entry point for the "c5x" 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_c5x_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;
int n = c5x_decode(buf, len, &ci);
if (n > 0) {
c55_fill_analysis(&c5x_arch_desc, &ci, op);
c2x_fill_op_access(analysis, &c5x_arch_desc, &ci, op);
if (mask & RZ_ANALYSIS_OP_MASK_IL) {
op->il_op = c55_lift(&c5x_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;
}

View file

@ -0,0 +1,112 @@
// SPDX-FileCopyrightText: 2026 RizinOrg <info@rizin.re>
// SPDX-License-Identifier: LGPL-3.0-only
/**
* \file
* TMS320C5x (second-generation fixed-point DSP, e.g. TMS320C50/C51/C53).
*
* The C5x is source-compatible with the C2x (same assembly mnemonics) but uses
* a completely different object encoding, so it has its own decode front-end
* (c5x_decode, ported from the authoritative TMS320C5x opcode table) rather than
* the C2x decode table. The decoder fills the shared C55Insn; shared-semantics
* instructions reuse the C2x instruction ids and the C2x lifter, while the
* C5x-only instructions (ACCB buffer ops, parallel-logic, memory-mapped register
* access, conditional execute/call/return, block moves, ...) carry the C5X_INS_*
* ids below and are handled by c5x_mnemonic/c5x_op_type/c5x_lift, which delegate
* to the C2x consumers for the shared ids.
*/
#ifndef RZ_TMS320_C5X_H
#define RZ_TMS320_C5X_H
#include "../c2x/c2x.h"
#ifdef __cplusplus
extern "C" {
#endif
/// C5x-only instruction ids (shared-semantics ops reuse the C2X_INS_* ids). The
/// base sits above the C2x enum so the two id spaces never collide.
enum {
C5X_INS_BASE = 0x200,
C5X_INS_LACC, ///< load ACC with shift (C2x LAC)
C5X_INS_LACL, ///< load ACC low, zero-extended
C5X_INS_LACB, ///< load ACC from ACCB
C5X_INS_SACB, ///< store ACC to ACCB
C5X_INS_EXAR, ///< exchange ACC and ACCB
C5X_INS_ADDB, ///< ACC += ACCB
C5X_INS_SBB, ///< ACC -= ACCB
C5X_INS_ADCB, ///< ACC += ACCB + carry
C5X_INS_SBBB, ///< ACC -= ACCB + borrow
C5X_INS_CRGT, ///< ACC = max(ACC, ACCB)
C5X_INS_CRLT, ///< ACC = min(ACC, ACCB)
C5X_INS_ANDB, ///< ACC &= ACCB
C5X_INS_ORB, ///< ACC |= ACCB
C5X_INS_XORB, ///< ACC ^= ACCB
C5X_INS_ROLB, ///< rotate ACC:ACCB left through carry
C5X_INS_RORB, ///< rotate ACC:ACCB right through carry
C5X_INS_SFLB, ///< shift ACC:ACCB left
C5X_INS_SFRB, ///< shift ACC:ACCB right
C5X_INS_SAMM, ///< store ACC to a memory-mapped register
C5X_INS_LAMM, ///< load ACC from a memory-mapped register
C5X_INS_LMMR, ///< load a memory-mapped register (long immediate addr)
C5X_INS_SMMR, ///< store a memory-mapped register (long immediate addr)
C5X_INS_BLDD, ///< block move data to data
C5X_INS_BLPD, ///< block move program to data
C5X_INS_BLDP, ///< block move data to program
C5X_INS_MADS, ///< multiply-accumulate with data move (BMAR address)
C5X_INS_MADD, ///< multiply-accumulate with data move and delay
C5X_INS_SPLK, ///< store parallel long immediate constant
C5X_INS_BCND, ///< conditional branch
C5X_INS_BCNDD, ///< delayed conditional branch
C5X_INS_CC, ///< conditional call
C5X_INS_CCD, ///< delayed conditional call
C5X_INS_RETC, ///< conditional return
C5X_INS_RETCD, ///< delayed conditional return
C5X_INS_RETD, ///< unconditional delayed return
C5X_INS_XC, ///< conditionally execute next n words
C5X_INS_BSAR, ///< barrel shift ACC right
C5X_INS_ZAP, ///< clear ACC and PREG
C5X_INS_ZPR, ///< clear PREG
C5X_INS_SATH, ///< saturate ACC high
C5X_INS_SATL, ///< saturate ACC low
C5X_INS_BACCD, ///< delayed branch to ACC address
C5X_INS_CALAD, ///< delayed call to ACC address
C5X_INS_APL, ///< AND data with DBMR / long immediate
C5X_INS_OPL, ///< OR data with DBMR / long immediate
C5X_INS_XPL, ///< XOR data with DBMR / long immediate
C5X_INS_CPL, ///< compare data with DBMR / long immediate
C5X_INS_RPTB, ///< repeat block
C5X_INS_RPTZ, ///< repeat next instruction, clearing ACC and PREG
C5X_INS_SETC, ///< set a control bit
C5X_INS_CLRC, ///< clear a control bit
C5X_INS_IDLE2, ///< idle until interrupt (low-power)
C5X_INS_NMI, ///< non-maskable interrupt
C5X_INS_RETE, ///< return from interrupt with enable
C5X_INS_RETI, ///< return from interrupt
C5X_INS_INTR, ///< software interrupt
C5X_INS_BD, ///< delayed branch
C5X_INS_CALLD, ///< delayed call
C5X_INS_BANZD, ///< delayed branch on AR not zero
C5X_INS_LST, ///< load status register STn (C5x #n, mem form)
C5X_INS_SST, ///< store status register STn (C5x #n, mem form)
};
RZ_IPI int c5x_decode(const ut8 *buf, int len, C55Insn *out);
RZ_IPI const char *c5x_mnemonic(ut16 id);
RZ_IPI ut32 c5x_op_type(ut16 id);
RZ_IPI RzILOpEffect *c5x_lift(const C55Insn *insn, ut64 pc);
extern const C55ArchDesc c5x_arch_desc;
RZ_IPI int tms320_c5x_op(RzAnalysis *analysis, RzAnalysisOp *op, ut64 addr,
const ut8 *buf, int len, RzAnalysisOpMask mask);
RZ_IPI RzAnalysisILConfig *tms320_c5x_il_config(RZ_NONNULL RzAnalysis *analysis);
#ifdef __cplusplus
}
#endif
#endif /* RZ_TMS320_C5X_H */

View file

@ -0,0 +1,681 @@
// SPDX-FileCopyrightText: 2026 RizinOrg <info@rizin.re>
// SPDX-License-Identifier: LGPL-3.0-only
/**
* \file
* TMS320C5x decode front-end (the real C5x object encoding).
*
* The C5x is source-compatible with the C2x but encodes instructions
* differently, so it cannot reuse the C2x decode table. c5x_decode() ports the
* authoritative TMS320C5x opcode table directly and fills the shared C55Insn:
* shared-semantics instructions carry the C2X_INS_* ids (so the C2x lifter,
* op-type and mnemonic tables apply unchanged) and C5x-only instructions carry
* the C5X_INS_* ids handled by c5x.c.
*
* Memory operands use one addressing byte (the low 8 bits of the opcode word):
* bit 7 selects direct (0) or indirect (1). For indirect, bits 6:3 are a 4-bit
* sub-mode (the LSB of which requests a next-ARP load from bits 2:0): 0=*, 2=*-,
* 4=*+, 8=*BR0-, A=*0-, C=*0+, E=*BR0+. Operand order matches the C2x
* convention so the shared consumers render and lift them identically.
*/
#include "c5x.h"
#include <rz_util.h>
static const char *const c5x_indir_raw[16] = {
"*", "*", "*-", "*-", "*+", "*+", "*?", "*?",
"*br0-", "*br0-", "*0-", "*0-", "*0+", "*0+", "*br0+", "*br0+"
};
static const C55AddrMode c5x_indir_amode[16] = {
C55_AM_INDIRECT, C55_AM_INDIRECT, C55_AM_POSTDEC, C55_AM_POSTDEC,
C55_AM_POSTINC, C55_AM_POSTINC, C55_AM_INDIRECT, C55_AM_INDIRECT,
C55_AM_BITREV_SUB, C55_AM_BITREV_SUB, C55_AM_POSTSUB, C55_AM_POSTSUB,
C55_AM_POSTADD, C55_AM_POSTADD, C55_AM_BITREV, C55_AM_BITREV
};
static const char *const c5x_arx_raw[8] = {
"ar0", "ar1", "ar2", "ar3", "ar4", "ar5", "ar6", "ar7"
};
// A data-memory operand from one addressing byte: direct (DP-relative, in disp)
// or indirect (ARP-relative, rendered via raw).
static void mem_op(C55Operand *out, ut8 low) {
out->kind = C55_OP_MEM;
out->access = 16;
if (low & 0x80) {
ut8 sub = (low >> 3) & 0xf;
out->amode = c5x_indir_amode[sub];
out->reg.cls = C55_RC_AR;
out->raw = c5x_indir_raw[sub];
} else {
out->amode = C55_AM_DIRECT;
out->disp = (st32)(low & 0x7f);
}
}
// The next-ARP operand (rendered ",arN") when an indirect sub-mode is odd.
static bool narp_op(C55Operand *out, ut8 low) {
if ((low & 0x80) && ((low >> 3) & 1)) {
out->kind = C55_OP_REG;
out->reg.cls = C55_RC_AR;
out->reg.num = low & 7;
out->width = 16;
out->raw = c5x_arx_raw[low & 7];
return true;
}
return false;
}
static void imm_op(C55Operand *out, ut64 v, ut8 width, bool is_signed) {
out->kind = C55_OP_IMM;
out->imm = v;
out->width = width;
out->imm_signed = is_signed;
}
static void shift_op(C55Operand *out, ut8 sh) {
out->kind = C55_OP_IMM;
out->imm = sh;
out->width = 16;
}
static void ar_op(C55Operand *out, ut8 n) {
out->kind = C55_OP_REG;
out->reg.cls = C55_RC_AR;
out->reg.num = n & 7;
out->width = 16;
out->raw = c5x_arx_raw[n & 7];
}
// 16-bit absolute branch/call target carried in the trailing word.
static void target_op(C55Operand *out, ut16 t) {
out->kind = C55_OP_IMM;
out->imm = t;
out->width = 16;
out->addr = true;
out->abs_target = true;
}
// Condition fragments for the 0xE0-0xFF group, rendered verbatim. zl/cv/tp are
// packed across the opcode's high and low nibbles (see the C5x opcode table).
static const char *const c5x_zl[16] = {
"", "gt", "neq", "gt", "", "lt", "neq", "lt", "", "gt", "eq", "geq", "", "lt", "eq", "leq"
};
static const char *const c5x_cv[16] = {
"", "nc", "nov", "nc nov", "", "c", "nov", "c nov", "", "nc", "ov", "nc ov", "", "c", "ov", "c ov"
};
static const char *const c5x_tp[4] = { "bio", "tc", "ntc", "" };
// Control bits addressed by SETC/CLRC (sub 0x40-0x4f), selected by (sub >> 1).
static const char *const c5x_ctrl_bits[8] = {
"intm", "ovm", "cnf", "sxm", "hold", "tc", "xf", "carry"
};
// 16-bit long-immediate ALU forms in the 0xB<sub> group, indexed by the low
// nibble; the zero entries are not long-immediate ops.
static const ut16 c5x_long_imm_alu[16] = {
[0x8] = C2X_INS_LALK, [0x9] = C2X_INS_ADLK, [0xa] = C2X_INS_SBLK, [0xb] = C2X_INS_ANDK, [0xc] = C2X_INS_ORK, [0xd] = C2X_INS_XORK
};
// Append the active zl/cv/tp condition fragments as verbatim operands.
static void cond_ops(C55Insn *out, ut16 op) {
ut8 zlcvmask = op & 0xf;
ut8 zlcv = (op >> 4) & 0xf;
ut8 zl = (zlcv & 0xc) | ((zlcvmask >> 2) & 3);
ut8 cv = ((zlcv << 2) & 0xc) | (zlcvmask & 3);
ut8 tp = (op >> 8) & 3;
const char *frag[3] = { c5x_zl[zl], c5x_cv[cv], c5x_tp[tp] };
for (int i = 0; i < 3; i++) {
if (frag[i][0]) {
out->ops[out->n_ops].kind = C55_OP_NONE;
out->ops[out->n_ops].raw = frag[i];
out->n_ops++;
}
}
}
#define ID(_id) (out->id = (ut16)(_id))
/**
* \brief Fill a "mem [narp]" instruction form.
* \param out Instruction to fill in
* \param id Instruction id to set
* \param low Addressing byte (low 8 bits of the opcode word)
* \return Instruction length in bytes
*/
static int mem_plain_insn(C55Insn *out, ut16 id, ut8 low) {
out->id = id;
mem_op(&out->ops[0], low);
out->n_ops = 1;
if (narp_op(&out->ops[1], low)) {
out->n_ops++;
}
out->size = 2;
return 2;
}
/**
* \brief Fill a "mem shift [narp]" instruction form.
* \param out Instruction to fill in
* \param id Instruction id to set
* \param low Addressing byte (low 8 bits of the opcode word)
* \param shift Shift amount encoded in the opcode
* \return Instruction length in bytes
*/
static int mem_shift_insn(C55Insn *out, ut16 id, ut8 low, ut8 shift) {
out->id = id;
mem_op(&out->ops[0], low);
shift_op(&out->ops[1], shift);
out->n_ops = 2;
if (narp_op(&out->ops[2], low)) {
out->n_ops++;
}
out->size = 2;
return 2;
}
/**
* \brief Fill a "mem [narp] #imm16" instruction form.
* \param out Instruction to fill in
* \param id Instruction id to set
* \param low Addressing byte (low 8 bits of the opcode word)
* \param w2 Trailing word carrying the long immediate
* \return Instruction length in bytes
*
* The next-ARP operand keeps its source order ahead of the immediate, so the
* immediate lands at whichever slot the optional \p narp_op left free.
*/
static int mem_imm2_insn(C55Insn *out, ut16 id, ut8 low, ut16 w2) {
out->id = id;
mem_op(&out->ops[0], low);
out->n_ops = 1;
if (narp_op(&out->ops[1], low)) {
out->n_ops++;
}
imm_op(&out->ops[out->n_ops], w2, 16, false);
out->n_ops++;
out->size = 4;
return 4;
}
/**
* \brief Fill a single-immediate-operand instruction form.
* \param out Instruction to fill in
* \param id Instruction id to set
* \param v Immediate value, already masked to its encoded width
* \param width Immediate width in bits
* \return Instruction length in bytes
*/
static int imm_insn(C55Insn *out, ut16 id, ut64 v, ut8 width) {
out->id = id;
imm_op(&out->ops[0], v, width, false);
out->n_ops = 1;
out->size = 2;
return 2;
}
// The BE group (0xBExx): ACCB ops, control bits, 2-word immediates.
static int decode_be(C55Insn *out, ut16 op, ut16 w2) {
ut8 sub = op & 0xff;
switch (sub) {
case 0x00: ID(C2X_INS_ABS); return 2;
case 0x01: ID(C2X_INS_CMPL); return 2;
case 0x02: ID(C2X_INS_NEG); return 2;
case 0x03: ID(C2X_INS_PAC); return 2;
case 0x04: ID(C2X_INS_APAC); return 2;
case 0x05: ID(C2X_INS_SPAC); return 2;
case 0x09: ID(C2X_INS_SFL); return 2;
case 0x0a: ID(C2X_INS_SFR); return 2;
case 0x0c: ID(C2X_INS_ROL); return 2;
case 0x0d: ID(C2X_INS_ROR); return 2;
case 0x10: ID(C5X_INS_ADDB); return 2;
case 0x11: ID(C5X_INS_ADCB); return 2;
case 0x12: ID(C5X_INS_ANDB); return 2;
case 0x13: ID(C5X_INS_ORB); return 2;
case 0x14: ID(C5X_INS_ROLB); return 2;
case 0x15: ID(C5X_INS_RORB); return 2;
case 0x16: ID(C5X_INS_SFLB); return 2;
case 0x17: ID(C5X_INS_SFRB); return 2;
case 0x18: ID(C5X_INS_SBB); return 2;
case 0x19: ID(C5X_INS_SBBB); return 2;
case 0x1a: ID(C5X_INS_XORB); return 2;
case 0x1b: ID(C5X_INS_CRGT); return 2;
case 0x1c: ID(C5X_INS_CRLT); return 2;
case 0x1d: ID(C5X_INS_EXAR); return 2;
case 0x1e: ID(C5X_INS_SACB); return 2;
case 0x1f: ID(C5X_INS_LACB); return 2;
case 0x20: ID(C2X_INS_BACC); return 2;
case 0x21: ID(C5X_INS_BACCD); return 2;
case 0x22: ID(C2X_INS_IDLE); return 2;
case 0x23: ID(C5X_INS_IDLE2); return 2;
case 0x30: ID(C2X_INS_CALA); return 2;
case 0x32: ID(C2X_INS_POP); return 2;
case 0x38: ID(C5X_INS_RETI); return 2;
case 0x3a: ID(C5X_INS_RETE); return 2;
case 0x3c: ID(C2X_INS_PUSH); return 2;
case 0x3d: ID(C5X_INS_CALAD); return 2;
case 0x51: ID(C2X_INS_TRAP); return 2;
case 0x52: ID(C5X_INS_NMI); return 2;
case 0x58: ID(C5X_INS_ZPR); return 2;
case 0x59: ID(C5X_INS_ZAP); return 2;
case 0x5a: ID(C5X_INS_SATH); return 2;
case 0x5b: ID(C5X_INS_SATL); return 2;
default: break;
}
if (sub >= 0x40 && sub <= 0x4f) {
ID((sub & 1) ? C5X_INS_SETC : C5X_INS_CLRC);
out->ops[0].kind = C55_OP_NONE;
out->ops[0].raw = c5x_ctrl_bits[(sub >> 1) & 7];
out->n_ops = 1;
return 2;
}
if (sub >= 0x60 && sub <= 0x7f) {
ID(C5X_INS_INTR);
imm_op(&out->ops[0], op & 0x1f, 8, false);
out->n_ops = 1;
return 2;
}
switch (sub) {
case 0x80:
ID(C2X_INS_MPY);
imm_op(&out->ops[0], w2, 16, true);
out->n_ops = 1;
return 4;
case 0x81:
ID(C2X_INS_AND);
imm_op(&out->ops[0], w2, 16, false);
out->n_ops = 1;
return 4;
case 0x82:
ID(C2X_INS_OR);
imm_op(&out->ops[0], w2, 16, false);
out->n_ops = 1;
return 4;
case 0x83:
ID(C2X_INS_XOR);
imm_op(&out->ops[0], w2, 16, false);
out->n_ops = 1;
return 4;
case 0xc4:
ID(C2X_INS_RPT);
imm_op(&out->ops[0], w2, 16, false);
out->n_ops = 1;
return 4;
case 0xc5:
ID(C5X_INS_RPTZ);
imm_op(&out->ops[0], w2, 16, false);
out->n_ops = 1;
return 4;
case 0xc6:
ID(C5X_INS_RPTB);
target_op(&out->ops[0], w2);
out->n_ops = 1;
return 4;
default: return 0;
}
}
// The BF group (0xBFxx): LAR/SPM, CMPR, shifted long-immediate ALU, BSAR.
static int decode_bf(C55Insn *out, ut16 op, ut16 w2) {
ut8 sub = (op >> 4) & 0xf;
ut8 shift = op & 0xf;
if (sub == 0x0) {
if (op & 0x8) {
ID(C2X_INS_LAR);
ar_op(&out->ops[0], op & 7);
imm_op(&out->ops[1], w2, 16, false);
out->n_ops = 2;
return 4;
}
ID(C2X_INS_SPM);
imm_op(&out->ops[0], op & 3, 8, false);
out->n_ops = 1;
return 2;
}
if (sub == 0x4) {
ID(C2X_INS_CMPR);
imm_op(&out->ops[0], op & 3, 8, false);
out->n_ops = 1;
return 2;
}
if (sub == 0xe) {
ID(C5X_INS_BSAR);
imm_op(&out->ops[0], shift + 1, 8, false);
out->n_ops = 1;
return 2;
}
if (c5x_long_imm_alu[sub]) {
ID(c5x_long_imm_alu[sub]);
imm_op(&out->ops[0], w2, 16, false);
out->n_ops = 1;
if (shift) {
shift_op(&out->ops[1], shift);
out->n_ops = 2;
}
return 4;
}
return 0;
}
/**
* \brief Decode one C5x instruction into the shared C55 representation.
* \param buf Instruction bytes, big-endian 16-bit words
* \param len Number of readable bytes in \p buf
* \param out Instruction to fill in
* \return Instruction length in bytes (2 or 4), or 0 on an undefined opcode
*
* Two-word forms consume the following word as their trailing operand, so
* \p len must cover both words for those to decode.
*/
RZ_IPI int c5x_decode(const ut8 *buf, int len, C55Insn *out) {
if (!buf || !out || len < 2) {
return 0;
}
memset(out, 0, sizeof(*out));
out->arch = C55_ARCH_C5X;
ut16 op = ((ut16)buf[0] << 8) | buf[1];
ut16 w2 = (len >= 4) ? (((ut16)buf[2] << 8) | buf[3]) : 0;
ut8 base = (op >> 8) & 0xff;
ut8 low = op & 0xff;
ut8 sh4 = (op >> 8) & 0xf;
ut8 sh3 = (op >> 8) & 7;
ut8 ar = (op >> 8) & 7;
if (base <= 0x07) { // lar arN, mem
ID(C2X_INS_LAR);
ar_op(&out->ops[0], ar);
mem_op(&out->ops[1], low);
out->n_ops = 2;
if (narp_op(&out->ops[2], low)) {
out->n_ops++;
}
out->size = 2;
return 2;
}
switch (base) {
case 0x08: return mem_plain_insn(out, C5X_INS_LAMM, low);
case 0x09: return mem_imm2_insn(out, C5X_INS_SMMR, low, w2);
case 0x0a: return mem_plain_insn(out, C2X_INS_SUBC, low);
case 0x0b: return mem_plain_insn(out, C2X_INS_RPT, low);
case 0x0c: return mem_imm2_insn(out, C2X_INS_OUT, low, w2);
case 0x0d: return mem_plain_insn(out, C2X_INS_LDP, low);
case 0x0e:
case 0x0f: {
ID(C5X_INS_LST);
imm_op(&out->ops[0], base & 1, 8, false);
mem_op(&out->ops[1], low);
out->n_ops = 2;
if (narp_op(&out->ops[2], low)) {
out->n_ops++;
}
out->size = 2;
return 2;
}
default: break;
}
if (base >= 0x10 && base <= 0x1f) {
return mem_shift_insn(out, C5X_INS_LACC, low, sh4);
}
if (base >= 0x20 && base <= 0x2f) {
return mem_shift_insn(out, C2X_INS_ADD, low, sh4);
}
if (base >= 0x30 && base <= 0x3f) {
return mem_shift_insn(out, C2X_INS_SUB, low, sh4);
}
if (base >= 0x40 && base <= 0x4f) { // bit code, mem
ID(C2X_INS_BIT);
imm_op(&out->ops[0], sh4, 8, false);
mem_op(&out->ops[1], low);
out->n_ops = 2;
if (narp_op(&out->ops[2], low)) {
out->n_ops++;
}
out->size = 2;
return 2;
}
switch (base) {
case 0x50: return mem_plain_insn(out, C2X_INS_MPYA, low);
case 0x51: return mem_plain_insn(out, C2X_INS_MPYS, low);
case 0x52: return mem_plain_insn(out, C2X_INS_SQRA, low);
case 0x53: return mem_plain_insn(out, C2X_INS_SQRS, low);
case 0x54: return mem_plain_insn(out, C2X_INS_MPY, low);
case 0x55: return mem_plain_insn(out, C2X_INS_MPYU, low);
case 0x57: return mem_plain_insn(out, C5X_INS_BLDP, low);
case 0x58: return mem_plain_insn(out, C5X_INS_XPL, low);
case 0x59: return mem_plain_insn(out, C5X_INS_OPL, low);
case 0x5a: return mem_plain_insn(out, C5X_INS_APL, low);
case 0x5b: return mem_plain_insn(out, C5X_INS_CPL, low);
case 0x5c: return mem_imm2_insn(out, C5X_INS_XPL, low, w2);
case 0x5d: return mem_imm2_insn(out, C5X_INS_OPL, low, w2);
case 0x5e: return mem_imm2_insn(out, C5X_INS_APL, low, w2);
case 0x5f: return mem_imm2_insn(out, C5X_INS_CPL, low, w2);
case 0x60: return mem_plain_insn(out, C2X_INS_ADDC, low);
case 0x61: return mem_shift_insn(out, C2X_INS_ADD, low, 16);
case 0x62: return mem_plain_insn(out, C2X_INS_ADDS, low);
case 0x63: return mem_plain_insn(out, C2X_INS_ADDT, low);
case 0x64: return mem_plain_insn(out, C2X_INS_SUBB, low);
case 0x65: return mem_shift_insn(out, C2X_INS_SUB, low, 16);
case 0x66: return mem_plain_insn(out, C2X_INS_SUBS, low);
case 0x67: return mem_plain_insn(out, C2X_INS_SUBT, low);
case 0x68: return mem_plain_insn(out, C2X_INS_ZALR, low);
case 0x69: return mem_plain_insn(out, C5X_INS_LACL, low);
case 0x6a: return mem_shift_insn(out, C5X_INS_LACC, low, 16);
case 0x6b: return mem_plain_insn(out, C2X_INS_LACT, low);
case 0x6c: return mem_plain_insn(out, C2X_INS_XOR, low);
case 0x6d: return mem_plain_insn(out, C2X_INS_OR, low);
case 0x6e: return mem_plain_insn(out, C2X_INS_AND, low);
case 0x6f: return mem_plain_insn(out, C2X_INS_BITT, low);
case 0x70: return mem_plain_insn(out, C2X_INS_LTA, low);
case 0x71: return mem_plain_insn(out, C2X_INS_LTP, low);
case 0x72: return mem_plain_insn(out, C2X_INS_LTD, low);
case 0x73: return mem_plain_insn(out, C2X_INS_LT, low);
case 0x74: return mem_plain_insn(out, C2X_INS_LTS, low);
case 0x75: return mem_plain_insn(out, C2X_INS_LPH, low);
case 0x76: return mem_plain_insn(out, C2X_INS_PSHD, low);
case 0x77: return mem_plain_insn(out, C2X_INS_DMOV, low);
default: break;
}
if (base == 0x78 || base == 0x7c) {
return imm_insn(out, base == 0x78 ? C2X_INS_ADRK : C2X_INS_SBRK, low, 8);
}
{
ut16 brid = 0;
switch (base) {
case 0x79: brid = C2X_INS_B; break;
case 0x7a: brid = C2X_INS_CALL; break;
case 0x7b: brid = C2X_INS_BANZ; break;
case 0x7d: brid = C5X_INS_BD; break;
case 0x7e: brid = C5X_INS_CALLD; break;
case 0x7f: brid = C5X_INS_BANZD; break;
default: break;
}
if (brid) {
ID(brid);
target_op(&out->ops[0], w2);
mem_op(&out->ops[1], low | 0x80); // branch addressing is always indirect
out->n_ops = 2;
if (narp_op(&out->ops[2], low | 0x80)) {
out->n_ops++;
}
out->has_branch = true;
out->branch_target = w2;
out->size = 4;
return 4;
}
}
if (base >= 0x80 && base <= 0x87) { // sar arN, mem
ID(C2X_INS_SAR);
ar_op(&out->ops[0], ar);
mem_op(&out->ops[1], low);
out->n_ops = 2;
if (narp_op(&out->ops[2], low)) {
out->n_ops++;
}
out->size = 2;
return 2;
}
switch (base) {
case 0x88: return mem_plain_insn(out, C5X_INS_SAMM, low);
case 0x89: return mem_imm2_insn(out, C5X_INS_LMMR, low, w2);
case 0x8a: return mem_plain_insn(out, C2X_INS_POPD, low);
case 0x8b:
if (low == 0) {
ID(C2X_INS_NOP);
out->n_ops = 0;
out->size = 2;
return 2;
}
return mem_plain_insn(out, C2X_INS_MAR, low);
case 0x8c: return mem_plain_insn(out, C2X_INS_SPL, low);
case 0x8d: return mem_plain_insn(out, C2X_INS_SPH, low);
case 0x8e:
case 0x8f: {
ID(C5X_INS_SST);
imm_op(&out->ops[0], base & 1, 8, false);
mem_op(&out->ops[1], low);
out->n_ops = 2;
if (narp_op(&out->ops[2], low)) {
out->n_ops++;
}
out->size = 2;
return 2;
}
default: break;
}
if (base >= 0x90 && base <= 0x97) {
return mem_shift_insn(out, C2X_INS_SACL, low, sh3);
}
if (base >= 0x98 && base <= 0x9f) {
return mem_shift_insn(out, C2X_INS_SACH, low, sh3);
}
switch (base) {
case 0xa0: return mem_imm2_insn(out, C2X_INS_NORM, low, w2); // norm mem, #w2 (the trailing word is a count/operand)
case 0xa2: return mem_imm2_insn(out, C2X_INS_MAC, low, w2);
// MACD is MAC's data-move twin and carries the same trailing pma word; the
// BMAR-addressed single-word variants are MADS/MADD below.
case 0xa3: return mem_imm2_insn(out, C2X_INS_MACD, low, w2);
case 0xa4: return mem_plain_insn(out, C5X_INS_BLPD, low);
case 0xa5: return mem_imm2_insn(out, C5X_INS_BLPD, low, w2);
case 0xa6: return mem_plain_insn(out, C2X_INS_TBLR, low);
case 0xa7: return mem_plain_insn(out, C2X_INS_TBLW, low);
case 0xa8:
case 0xa9: return mem_imm2_insn(out, C5X_INS_BLDD, low, w2);
case 0xaa: return mem_plain_insn(out, C5X_INS_MADS, low);
case 0xab: return mem_plain_insn(out, C5X_INS_MADD, low);
case 0xac:
case 0xad: return mem_plain_insn(out, C5X_INS_BLDD, low);
case 0xae: return mem_imm2_insn(out, C5X_INS_SPLK, low, w2);
case 0xaf: return mem_imm2_insn(out, C2X_INS_IN, low, w2);
default: break;
}
if (base >= 0xb0 && base <= 0xb7) { // lar arN, #imm8 (C2x LARK; rendered "lar")
ID(C2X_INS_LARK);
ar_op(&out->ops[0], ar);
imm_op(&out->ops[1], low, 8, false);
out->n_ops = 2;
out->size = 2;
return 2;
}
switch (base) {
case 0xb8:
return imm_insn(out, C2X_INS_ADDK, low, 8);
case 0xb9:
return imm_insn(out, C5X_INS_LACL, low, 8);
case 0xba:
return imm_insn(out, C2X_INS_SUBK, low, 8);
case 0xbb:
return imm_insn(out, C2X_INS_RPTK, low, 8);
case 0xbc:
case 0xbd:
return imm_insn(out, C2X_INS_LDPK, op & 0x1ff, 16);
case 0xbe: {
int r = decode_be(out, op, w2);
if (r) {
out->size = (ut8)r;
}
return r;
}
case 0xbf: {
int r = decode_bf(out, op, w2);
if (r) {
out->size = (ut8)r;
}
return r;
}
default: break;
}
if (base >= 0xc0 && base <= 0xdf) {
return 0; // undefined opcode range
}
// 0xE0-0xFF: conditional branch / call / return / execute group.
if (base >= 0xe0 && base <= 0xe3) {
ID(C5X_INS_BCND);
target_op(&out->ops[0], w2);
out->n_ops = 1;
cond_ops(out, op);
out->has_branch = true;
out->branch_target = w2;
out->size = 4;
return 4;
}
if ((base >= 0xe4 && base <= 0xe7) || (base >= 0xf4 && base <= 0xf7)) {
ID(C5X_INS_XC);
imm_op(&out->ops[0], ((op >> 12) & 1) + 1, 8, false);
out->n_ops = 1;
cond_ops(out, op);
out->size = 2;
return 2;
}
if (base >= 0xe8 && base <= 0xeb) {
ID(C5X_INS_CC);
target_op(&out->ops[0], w2);
out->n_ops = 1;
cond_ops(out, op);
out->has_branch = true;
out->branch_target = w2;
out->size = 4;
return 4;
}
if (base >= 0xec && base <= 0xef) {
if (op == 0xef00) {
ID(C2X_INS_RET);
out->size = 2;
return 2;
}
ID(C5X_INS_RETC);
cond_ops(out, op);
out->size = 2;
return 2;
}
if (base >= 0xf0 && base <= 0xf3) {
ID(C5X_INS_BCNDD);
target_op(&out->ops[0], w2);
out->n_ops = 1;
cond_ops(out, op);
out->has_branch = true;
out->branch_target = w2;
out->size = 4;
return 4;
}
if (base >= 0xf8 && base <= 0xfb) {
ID(C5X_INS_CCD);
target_op(&out->ops[0], w2);
out->n_ops = 1;
cond_ops(out, op);
out->has_branch = true;
out->branch_target = w2;
out->size = 4;
return 4;
}
if (base >= 0xfc && base <= 0xff) {
if (op == 0xff00) {
ID(C5X_INS_RETD); // unconditional delayed return
out->size = 2;
return 2;
}
ID(C5X_INS_RETCD);
cond_ops(out, op);
out->size = 2;
return 2;
}
return 0;
}

View file

@ -322,6 +322,8 @@ arch_isa_sources = [
'isa/tms320/c54x/c54x.c',
'isa/tms320/c54x/c54x_il.c',
'isa/tms320/c2x/c2x.c',
'isa/tms320/c5x/c5x.c',
'isa/tms320/c5x/c5x_decode.c',
'isa/tms320/c55x_plus/c55plus_arch.c',
'isa/tms320/c55x/c55x_analysis.c',
'isa/tms320/c55x_plus/c55plus_analysis.c',

View file

@ -10,6 +10,7 @@
#include <tms320/c55x_plus/c55plus_analysis.h>
#include <tms320/c54x/c54x.h>
#include <tms320/c2x/c2x.h>
#include <tms320/c5x/c5x.h>
#include <tms320/c64x/c64x.h>
typedef struct tms320_ctx_t {
@ -28,6 +29,8 @@ int tms320_analysis_op(RzAnalysis *analysis, RzAnalysisOp *op, ut64 addr, const
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);
} else if (cpu && rz_str_casecmp(cpu, "c5x") == 0) {
return tms320_c5x_op(analysis, op, addr, buf, len, mask);
}
return tms320_c55x_op_byte(analysis, op, addr, buf, len, mask);
}
@ -162,6 +165,72 @@ static char *get_reg_profile(RZ_BORROW RzAnalysis *a) {
"gpr arb .16 44 0\n" // Auxiliary register pointer backup
"gpr rptc .16 46 0\n"); // Repeat counter
}
if (cpu0 && rz_str_casecmp(cpu0, "c5x") == 0) {
// TMS320C5x: the C2x register file plus the C5x additions — the 32-bit
// accumulator buffer ACCB, the TREG1/TREG2 multiplier/shift registers,
// the processor-mode status PMST, the index register INDX, the
// auxiliary-compare register ARCR, the circular-buffer start/end
// pointers CBSR1/CBER1/CBSR2/CBER2, the block-repeat registers
// BRCR/PASR/PAER, the block-move address BMAR, the dynamic bit-mask
// DBMR and the global-memory register GREG. The core names match the
// C2x lifter's il_var bindings (the C5x lifter reuses it).
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 accb .32 4 0\n" // Accumulator buffer
"ctr t .16 8 0\n" // Temporary/multiplicand register (TREG0)
"ctr treg1 .16 10 0\n" // TREG1 (dynamic shift count)
"ctr treg2 .16 12 0\n" // TREG2 (dynamic bit position)
"ctr p .32 14 0\n" // Product register
"gpr pl .16 14 0\n" // Product low word
"gpr ph .16 16 0\n" // Product high word
"gpr ar0 .16 18 0\n" // Auxiliary register 0
"gpr ar1 .16 20 0\n" // Auxiliary register 1
"gpr ar2 .16 22 0\n" // Auxiliary register 2
"gpr ar3 .16 24 0\n" // Auxiliary register 3
"gpr ar4 .16 26 0\n" // Auxiliary register 4
"gpr ar5 .16 28 0\n" // Auxiliary register 5
"gpr ar6 .16 30 0\n" // Auxiliary register 6
"gpr ar7 .16 32 0\n" // Auxiliary register 7
"ctr arp .16 34 0\n" // Auxiliary register pointer
"ctr dp .16 36 0\n" // Data page pointer
"ctr st0 .16 38 0\n" // Status register 0
"ctr st1 .16 40 0\n" // Status register 1
"ctr pmst .16 42 0\n" // Processor mode status register
"ctr indx .16 44 0\n" // Index register
"ctr arcr .16 46 0\n" // Auxiliary register compare register
"ctr cbsr1 .16 48 0\n" // Circular buffer 1 start address
"ctr cber1 .16 50 0\n" // Circular buffer 1 end address
"ctr cbsr2 .16 52 0\n" // Circular buffer 2 start address
"ctr cber2 .16 54 0\n" // Circular buffer 2 end address
"ctr brcr .16 56 0\n" // Block repeat counter register
"ctr pasr .16 58 0\n" // Block repeat program address start
"ctr paer .16 60 0\n" // Block repeat program address end
"ctr bmar .16 62 0\n" // Block move address register
"ctr dbmr .16 64 0\n" // Dynamic bit manipulation register
"ctr greg .16 66 0\n" // Global memory allocation register
"ctr sp .16 68 0\n" // Stack pointer (synthetic)
"ctr pc .16 70 0\n" // Program counter
// status/mode bits modelled individually for the shared C2x-core
// lifter (they also live inside ST0/ST1 on silicon)
"flg c .1 72.0 0\n" // Carry
"flg ov .1 73.0 0\n" // Overflow (sticky until tested)
"flg tc .1 74.0 0\n" // Test/control bit
"gpr ovm .1 75.0 0\n" // Overflow saturation mode
"gpr sxm .1 76.0 0\n" // Sign-extension mode
"gpr pm .2 77.0 0\n" // Product shift mode
"gpr arb .16 78 0\n" // Auxiliary register pointer backup
"gpr rptc .16 80 0\n"); // Repeat counter
}
if (is_c5000(rz_analysis_get_cpu(a))) {
p =
"=PC pc\n"

View file

@ -9,6 +9,7 @@
#include <tms320/c55x/c55x_analysis.h>
#include <tms320/c54x/c54x.h>
#include <tms320/c2x/c2x.h>
#include <tms320/c5x/c5x.h>
#include <tms320/c64x/c64x.h>
typedef struct tms_cs_context_t {
@ -31,13 +32,22 @@ static int tms320_disassemble(const RzAsm *a, RzAsmOp *op, const ut8 *buf, int l
desc = &c54x_arch_desc;
} else if (a->cpu && !rz_str_casecmp(a->cpu, "c2x")) {
desc = &c2x_arch_desc;
} else if (a->cpu && !rz_str_casecmp(a->cpu, "c5x")) {
desc = &c5x_arch_desc;
} else {
rz_asm_op_set_asm(op, "unknown asm.cpu");
return op->size = -1;
}
if (desc) {
C55Insn insn;
if (c55_decode(desc, buf, len, &insn)) {
bool ok;
if (desc == &c5x_arch_desc) {
// The C5x has its own decode front-end (real C5x encoding).
ok = c5x_decode(buf, len, &insn) > 0;
} else {
ok = c55_decode(desc, buf, len, &insn);
}
if (ok) {
char *s = c55_format(desc, &insn);
if (s) {
rz_asm_op_set_asm(op, s);
@ -80,6 +90,7 @@ static char **tms320_cpu_descriptions() {
static char *cpu_desc[] = {
"c54x", "Texas Instruments TMS320C54x DSP family",
"c2x", "Texas Instruments TMS320C2x legacy fixed-point DSP family",
"c5x", "Texas Instruments TMS320C5x fixed-point DSP family (C2x-compatible superset)",
"c55x", "Texas Instruments TMS320C55x DSP family",
"c55x+", "Texas Instruments TMS320C55x+ DSP family",
"c64x", "Texas Instruments TMS320C64x DSP family",
@ -91,8 +102,8 @@ static char **tms320_cpu_descriptions() {
RzAsmPlugin rz_asm_plugin_tms320 = {
.name = "tms320",
.arch = "tms320",
.cpus = "c54x,c55x,c55x+,c2x,c64x",
.desc = "Texas Instruments TMS320 DSP family (c54x,c55x,c55x+,c2x,c64x) disassembler",
.cpus = "c54x,c55x,c55x+,c2x,c5x,c64x",
.desc = "Texas Instruments TMS320 DSP family (c54x,c55x,c55x+,c2x,c5x,c64x) disassembler",
.license = "LGPL3",
.bits = 16 | 32,
.endian = RZ_SYS_ENDIAN_LITTLE | RZ_SYS_ENDIAN_BIG,

View file

@ -0,0 +1,112 @@
NAME=c5x analysis: register profile (PC alias resolves)
FILE==
CMDS=<<EOF
e asm.arch=tms320
e analysis.cpu=c5x
arp~^=PC
EOF
EXPECT=<<EOF
=PC pc
EOF
RUN
NAME=c5x analysis: C5x-specific registers present (ACCB / PMST / circular buffer)
FILE==
CMDS=<<EOF
e asm.arch=tms320
e analysis.cpu=c5x
arp~accb,pmst,cbsr1,bmar
EOF
EXPECT=<<EOF
ctr accb .32 4 0
ctr pmst .16 42 0
ctr cbsr1 .16 48 0
ctr bmar .16 62 0
EOF
RUN
NAME=c5x analysis: opcode classification (shared C2x core)
FILE=malloc://64
CMDS=<<EOF
e asm.arch=tms320
e analysis.cpu=c5x
wx 8b00
ao 1 @ 0~^type
wx 79800040
ao 1 @ 0~^type
ao 1 @ 0~^jump
wx ef00
ao 1 @ 0~^type
EOF
EXPECT=<<EOF
type: nop
type: jmp
jump: 0x00000080
type: ret
EOF
RUN
NAME=c5x analysis: register argument detection (ar0/ar1 via calling convention)
FILE=malloc://64
CMDS=<<EOF
e asm.arch=tms320
e asm.bits=16
e analysis.cpu=c5x
wx 801081111010ef00
af
afc c5x
afc
afvl
EOF
EXPECT=<<EOF
c5x
arg int16_t arg1 @ ar0
arg int16_t arg2 @ ar1
EOF
RUN
NAME=c5x analysis: op fields filled (direct data pointer, access direction, register source)
FILE=malloc://64
CMDS=<<EOF
e asm.arch=tms320
e asm.bits=16
e analysis.cpu=c5x
wx 1010
ao 1 @ 0~^direction
ao 1 @ 0~^ptr
wx 9010
ao 1 @ 0~^type
ao 1 @ 0~^direction
ao 1 @ 0~^ptr
wx 8010
ao 1 @ 0~^type
ao 1 @ 0~^reg
ao 1 @ 0~^direction
EOF
EXPECT=<<EOF
direction: read
ptr: 0x00000010
type: store
direction: write
ptr: 0x00000010
type: store
reg: ar0
direction: write
EOF
RUN
# Same exerciser built for the C5x; "dbl" lands at word 0x4d.
NAME=c5x analysis: emulateme function recovery
FILE=bins/tms320/c5x_legacy/emulateme.bin
ARGS=-a tms320 -b 16 -e asm.cpu=c5x -e analysis.cpu=c5x
CMDS=<<EOF
aaa
afl~[0,2]
axt @ 0x9a
EOF
EXPECT=<<EOF
0x00000000 154
0x0000009a 8
fcn.00000000 0x7e [CALL] call 0x4d, *
EOF
RUN

View file

@ -0,0 +1,154 @@
# Function recovery on typezoo.bin, compiled from c2x_legacy/src/typezoo.c: 18
# functions with distinct signatures, plus the entry stub. Program addresses
# count 16-bit words, so a function sits at twice its symbol value (square =
# word 0x1a = byte 0x34).
NAME=c2x analysis: typezoo function boundaries
FILE=bins/tms320/c2x_legacy/typezoo_c2x.bin
ARGS=-a tms320 -b 16 -e asm.cpu=c2x -e analysis.cpu=c2x
CMDS=<<EOF
aaa
afl~?
afl~[0,2]
EOF
EXPECT=<<EOF
19
0x00000000 42
0x0000002a 6
0x00000030 4
0x00000034 24
0x0000004c 22
0x00000062 24
0x0000007a 26
0x00000094 44
0x000000c0 24
0x000000d8 4
0x000000dc 24
0x000000f4 60
0x00000130 82
0x00000182 64
0x000001c2 60
0x000001fe 28
0x0000021a 26
0x00000234 20
0x00000248 256
EOF
RUN
# The sizes above tile the image with no gaps, matching the compiler layout.
NAME=c2x analysis: typezoo callers of square, add2 and sum_array
FILE=bins/tms320/c2x_legacy/typezoo_c2x.bin
ARGS=-a tms320 -b 16 -e asm.cpu=c2x -e analysis.cpu=c2x
CMDS=<<EOF
aaa
axt @ 0x34~CALL
axt @ 0x4c~CALL
axt @ 0xf4~CALL
EOF
EXPECT=<<EOF
fcn.000001fe 0x206 [CALL] call 0x1a
fcn.0000021a 0x21e [CALL] call 0x1a
fcn.0000021a 0x228 [CALL] call 0x1a
fcn.0000021a 0x22e [CALL] call 0x26
fcn.00000234 0x242 [CALL] call 0x7a
EOF
RUN
NAME=c2x analysis: typezoo main call graph
FILE=bins/tms320/c2x_legacy/typezoo_c2x.bin
ARGS=-a tms320 -b 16 -e asm.cpu=c2x -e analysis.cpu=c2x
CMDS=<<EOF
aaa
afi @ 0x248~call-refs
EOF
EXPECT=<<EOF
call-refs: 0x0000002a C 0x00000182 C 0x0000028c J 0x00000234 C 0x00000130 C 0x0000021a C 0x000001fe C 0x00000062 C 0x0000007a C 0x00000094 C 0x000000d8 C 0x000000c0 C 0x000001c2 C 0x000000d8 C 0x00000030 C
EOF
RUN
NAME=c2x analysis: typezoo calling convention
FILE=bins/tms320/c2x_legacy/typezoo_c2x.bin
ARGS=-a tms320 -b 16 -e asm.cpu=c2x -e analysis.cpu=c2x
CMDS=<<EOF
aaa
afi @ 0x7a~call-convention
afi @ 0x34~call-convention
EOF
EXPECT=<<EOF
call-convention: c2x
call-convention: c2x
EOF
RUN
# The C5x build of the same source recovers the same layout on its own ISA.
NAME=c5x analysis: typezoo function boundaries
FILE=bins/tms320/c5x_legacy/typezoo_c5x.bin
ARGS=-a tms320 -b 16 -e asm.cpu=c5x -e analysis.cpu=c5x
CMDS=<<EOF
aaa
afl~?
afl~[0,2]
EOF
EXPECT=<<EOF
19
0x00000000 42
0x0000002a 6
0x00000030 4
0x00000034 24
0x0000004c 22
0x00000062 24
0x0000007a 26
0x00000094 44
0x000000c0 22
0x000000d6 4
0x000000da 24
0x000000f2 60
0x0000012e 82
0x00000180 64
0x000001c0 60
0x000001fc 28
0x00000218 26
0x00000232 20
0x00000246 256
EOF
RUN
NAME=c5x analysis: typezoo callers of square and add2
FILE=bins/tms320/c5x_legacy/typezoo_c5x.bin
ARGS=-a tms320 -b 16 -e asm.cpu=c5x -e analysis.cpu=c5x
CMDS=<<EOF
aaa
axt @ 0x34~CALL
axt @ 0x4c~CALL
EOF
EXPECT=<<EOF
fcn.000001fc 0x204 [CALL] call 0x1a, *
fcn.00000218 0x21c [CALL] call 0x1a, *
fcn.00000218 0x226 [CALL] call 0x1a, *
fcn.00000218 0x22c [CALL] call 0x26, *
EOF
RUN
# cc-tms320-16.sdb is keyed by arch and bits, not by CPU, so both cores share
# its default.cc; the c5x convention is still selectable.
NAME=c5x analysis: typezoo calling convention
FILE=bins/tms320/c5x_legacy/typezoo_c5x.bin
ARGS=-a tms320 -b 16 -e asm.cpu=c5x -e analysis.cpu=c5x
CMDS=<<EOF
aaa
afcl
afi @ 0x7a~call-convention
afc c5x @ 0x96
afi @ 0x7a~call-convention
EOF
EXPECT=<<EOF
c2x
c5x
reg
call-convention: c2x
call-convention: c2x
EOF
RUN

126
test/db/asm/tms320_c5x_16 Normal file
View file

@ -0,0 +1,126 @@
# TMS320C5x disassembly + RzIL. The C5x is source-compatible with the C2x but
# uses a different object encoding, decoded by the dedicated C5x front-end
# (c5x_decode). Shared-semantics instructions carry the C2x ids and lift through
# the C2x lifter; the C5x-only instructions (ACCB ops, parallel-logic, memory-
# mapped register access, conditional execute/call/return, block moves, ...)
# carry the C5X_INS_* ids. Vectors are real C5x opcodes (assembled by the C5x
# tool-chain) covering direct/indirect/shift/next-ARP forms.
d "lacc 0x9, #0x0" 1009
d "lacc 0x9, #0x4" 1409
d "lacc *+, #0x0" 10a0
d "lacc *+, #0x0, ar2" 10aa
d "lacc 0x9, #0x10" 6a09
d "add 0x9, #0x0" 2009
d "add 0x9, #0x4" 2409
d "add *-, #0x0" 2090
d "add 0x9, #0x10" 6109
d "sub 0x9, #0x0" 3009
d "sub *0+, #0x0, ar3" 30eb
d "sacl 0x10, #0x0" 9010
d "sacl *+, #0x1" 91a0
d "sach 0x10, #0x1" 9910
d "and 0x9" 6e09
d "or 0x9" 6d09
d "xor 0x9" 6c09
d "lacl 0x9" 6909
d "lacl #0x42" b942
d "lar ar0, 0x9" 0009
d "lar ar1, #0x10" b110
d "sar ar0, 0x9" 8009
d "lt 0x9" 7309
d "lta 0x9" 7009
d "ltp 0x9" 7109
d "ltd 0x9" 7209
d "mpy 0x9" 5409
d "mpyu 0x9" 5509
d "sqra 0x9" 5209
d "pac" be03
d "apac" be04
d "spac" be05
d "abs" be00
d "neg" be02
d "cmpl" be01
d "sfl" be09
d "sfr" be0a
d "rol" be0c
d "ror" be0d
d "zalr 0x9" 6809
d "lph 0x9" 7509
d "spl 0x9" 8c09
d "sph 0x9" 8d09
d "add #0x12" b812
d "sub #0x12" ba12
d "rpt #0x7" bb07
d "ldp #0x4" bc04
d "adrk #0x8" 7808
d "sbrk #0x8" 7c08
d "spm #0x2" bf02
d "lacb" be1f
d "sacb" be1e
d "exar" be1d
d "addb" be10
d "sbb" be18
d "andb" be12
d "orb" be13
d "xorb" be1a
d "crgt" be1b
d "crlt" be1c
d "zap" be59
d "zpr" be58
d "setc ovm" be43
d "clrc sxm" be46
d "setc carry" be4f
d "clrc tc" be4a
d "samm 0x10" 8810
d "lamm 0x10" 0810
d "bldd 0x9" ac09
d "blpd 0x9" a409
d "splk 0x10, #0x1234" ae101234
d "in 0x9, #0x5" af090005
d "out 0x9, #0x5" 0c090005
d "mac 0x9, #0x1234" a2091234
d "macd 0x9, #0x1234" a3091234
d "ret" ef00
d "b 0x40, *" 79800040
d "call 0x50, *" 7a800050
d "bcnd 0x40, geq" e38c0040
d "cc 0x50, neq" eb080050
d "retc geq" ef8c
d "bd 0x40, *" 7d800040
d "calld 0x50, *" 7e800050
d "rptz #0x7" bec50007
d "rptb 0x40" bec60040
d "bsar #0x4" bfe3
d "lst #0x0, 0x9" 0e09
d "sst #0x1, 0x10" 8f10
d "tblr 0x9" a609
d "tblw 0x9" a709
d "dmov 0x9" 7709
d "subc 0x9" 0a09
d "bldp 0x9" 5709
d "mads 0x9" aa09
d "madd 0x9" ab09
d "apl 0x9" 5a09
d "opl 0x9" 5909
d "xpl 0x9" 5809
d "cpl 0x9" 5b09
d "addc 0x9" 6009
d "subb 0x9" 6409
d "adds 0x9" 6209
d "subs 0x9" 6609
d "addt 0x9" 6309
d "subt 0x9" 6709
d "lact 0x9" 6b09
d "bacc" be20
d "cala" be30
d "push" be3c
d "pop" be32
d "pshd 0x9" 7609
d "popd 0x9" 8a09
d "bit #0x9, 0x0" 4900
d "bitt 0x9" 6f09
d "idle" be22
d "idle2" be23
d "trap" be51
d "rete" be3a
d "reti" be38

View file

@ -58,7 +58,7 @@ _dA__ 8 16 snes LGPL3 SuperNES CPU disassembler
_dA_I 32 64 sparc BSD Sun SPARC Capstone-based disassembler
_dA__ 16 spc700 LGPL3 Sony SPC700 (Nintendo SuperNES sound-chip) disassembler
_dA__ 32 64 sysz BSD IBM SystemZ (S/390) Capstone-based disassembler
_dA_I 16 32 tms320 LGPL3 Texas Instruments TMS320 DSP family (c54x,c55x,c55x+,c2x,c64x) disassembler
_dA_I 16 32 tms320 LGPL3 Texas Instruments TMS320 DSP family (c54x,c55x,c55x+,c2x,c5x,c64x) disassembler
_dA_I 32 tricore BSD Siemens TriCore Capstone-based disassembler (by billow)
_dAeI 32 v810 LGPL3 NEC V810 disassembler (by pancake)
_dAeI 32 v850 LGPL3 NEC/Renesas V850 disassembler
@ -186,6 +186,7 @@ qpx PowerPC with Quad Processing eXtensions
v9 SPARC V9: 64-bit RISC architecture specification
c54x Texas Instruments TMS320C54x DSP family
c2x Texas Instruments TMS320C2x legacy fixed-point DSP family
c5x Texas Instruments TMS320C5x fixed-point DSP family (C2x-compatible superset)
c55x Texas Instruments TMS320C55x DSP family
c55x+ Texas Instruments TMS320C55x+ DSP family
c64x Texas Instruments TMS320C64x DSP family

File diff suppressed because one or more lines are too long

View file

@ -664,6 +664,7 @@ ARGS=-m tms320
EXPECT=<<EOF
c54x Texas Instruments TMS320C54x DSP family
c2x Texas Instruments TMS320C2x legacy fixed-point DSP family
c5x Texas Instruments TMS320C5x fixed-point DSP family (C2x-compatible superset)
c55x Texas Instruments TMS320C55x DSP family
c55x+ Texas Instruments TMS320C55x+ DSP family
c64x Texas Instruments TMS320C64x DSP family