arch/tms320: c55x and c55x+ analysis classifiers (byte-driven)

Rewrites the C55x and C55x+ analysis classifiers as pure byte-level
dispatch -- no mnemonic-string matching, no round-trip through the
disassembler -- and adds the supporting infrastructure they need to
produce useful RzAnalysisOp metadata.

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

* librz/arch/isa/tms320/c55x/c55x_analysis.{c,h} -- C55x baseline
  classifier, ~360 lines, 256-entry size table extracted from the
  decoder's table.h.
* librz/arch/isa/tms320/c55x_plus/c55plus_analysis.c -- C55x+
  classifier rewritten in the same shape, ~470 lines covering 90+
  opcodes with byte-level disambiguation for 0x02 / 0x03 / 0x74 /
  0x76 / 0x7B / 0xC5.
* librz/arch/isa/tms320/tms320_dwarf_regnum_table.h plus a hook in
  librz/arch/dwarf_process.c -- TI cgt55 ABI DWARF register-number
  mapping, so the cl55 compiler's .debug_info variable locations
  resolve into rizin register names instead of returning the dummy
  "?" placeholder.
* librz/arch/p/analysis/analysis_tms320.c -- thin dispatcher that
  picks the per-cpu classifier and stops carrying the tms320_dasm_t
  engine in analysis state.

Why a byte-driven classifier
============================

The old classifier round-tripped through the disassembler and did
strncasecmp() on the mnemonic string. Three problems:

1. It kept a tms320_dasm_t engine alive in the analysis context
   just to read its 'syntax' buffer after every classify call.
   Removing it shrinks the per-analysis state and removes a
   tms320_dasm_init/_fini pair from the analysis_init/_fini path.

2. It only set op->type -- never op->jump, op->fail, op->stackop,
   op->stackptr, op->val, op->eob. Basic-block formation followed
   only the most obvious control flow, and call/ret/push/pop
   semantics were invisible to higher-level analysis.

3. It couldn't disambiguate predicated versus unconditional calls:
   the disassembler emits 'callcc' vs 'call', but the substring
   match missed the conditional fail-path for CALLCC.

The new classifiers fix all three:

  - Read the leading byte (and second-byte refinements where the
    encoding family is shared) directly from buf.
  - Resolve jump and call targets from BE-stored displacement and
    absolute fields, with correct sign extension for the 8-bit and
    16-bit relative forms.
  - Read 24-bit absolute targets via rz_read_at_be24().
  - Set op->fail = addr + size for every conditional jump/call,
    op->eob = true for unconditional branches and RET so basic-block
    walkers terminate correctly.
  - Track the stack: PSH/POP per ISA cluster, CALL/CALLCC +2,
    RET/RETI -2.
  - Capture INTR/TRAP immediates in op->val via set_imm().
  - Disambiguate sub-opcodes that share a leading byte by reading
    the relevant bits of the second byte. For C55x, the most
    notable case is 0x48 (RPT/RPTADD/RPTSUB/RET/RETI) which uses
    bits 0-2 of byte 1; for C55x+ the disambiguations are 0x02,
    0x03, 0x74, 0x76, 0x7B and 0xC5.
  - Handle parallel-prefix bytes (odd-valued leading bytes below
    0x80 in C55x like 0x03, 0x05, 0x07, 0x11, ...) by treating
    them as a 1-byte prefix and dispatching on byte 1 so paired
    '|| retcc', '|| bcc', etc. classify correctly.

Both classifiers ship analyzer helpers (set_cjmp, set_call, set_jmp,
set_ret, set_cret, set_push, set_pop, set_imm, set_mem_width,
set_dst_reg, set_ireg, set_dir, set_disp) so each opcode entry fills
the RzAnalysisOp ptr / val / stackop / stackptr / fail / eob fields
uniformly across both architectures.

DWARF register mapping
======================

Loading any cl55-compiled TI COFF v2 with debug info (every
emulateme*.ticoff2.dbg.coff in rizin-testbins) used to fire:

  ERROR: No DWARF register mapping function defined for tms320 32 bits

per variable, because dwarf_process.c had no entry for arch=tms320.
The new tms320_dwarf_regnum_table.h covers the cgt55 ABI numbering:
AC0-AC3, T0-T3, AR0-AR7, SP/SSP/CDP, BK03/BK47/BKC, DP/PDP, CSR,
BRC0/BRC1, TRN0/TRN1, RPTC, IER0/IER1, IFR0/IFR1, DBIER0/DBIER1,
IVPD/IVPH, ST0_55..ST3_55 (42 entries). Reach into the table is
guarded; out-of-range numbers fall back to NULL so the caller
surfaces the dummy "?" instead of confidently picking the wrong
register.

Wrigley3G coverage
==================

Validation against a 3.1 MB Wrigley3G baseband firmware (Motorola
Droid A855, MSG39UPEU_A1.19_1.80, partition CG45.img) found 31
leading-byte values producing real instructions classified as NULL.
The c55x+ classifier here covers those:

  0x50-0x5F        MOV memory/register cluster
  0x88, 0x8A       MOV ACx <-> mem high/low halves
  0x8C             ADD with carry, mem -> ACx
  0x97             Dual-memory MOV (parallel)
  0xA0             MOV with parallel dual addressing
  0xAC, 0xAD       MOV #k16, ACx (long immediate)
  0xB4, 0xB5       MOV with rounding and shift
  0xB6, 0xB7       ADD with shift (T-register or immediate)
  0xC0, 0xC2, 0xC4 ADD #k16 with shift slots
  0xCC             Packed ADD :: MOV dual-instruction encoding
  0xD0             MOV ACx, dbl(*(#abs24))
  0x2E, 0x2F       XCCPART predicated execute
  0x0B, 0x23       Wrigley silicon pseudo-ops (TRAP)
  0xC6             BFXTR / BFXPA bit-field extract (MOV)

The 0x03 family classifier extends from a 4-bit (0xF0) to a 6-bit
(0xC0) mask so the full encoded range resolves:

  0x03 0x00-0x3F   INTR #k5
  0x03 0x40-0x7F   TRAP #k5
  0x03 0x80-0xBF   SWAP register pairs
  0x03 0xC0-0xFF   SIM_TRIG (Wrigley-specific simulator trigger)

Coverage on Wrigley3G rises from 94.4% to 97.4% (2000-sample
random survey).

Tests
=====

Two new test suites land alongside the classifiers:

  test/db/analysis/tms320.c55x_32       11 tests (batched)
  test/db/analysis/tms320.c55x+_32      13 tests (batched + binary
                                                  fixtures)

Tests are intentionally batched -- each test bundles 10-12 opcode
checks behind one rizin process spawn instead of one per check.
That brings both suites down to under 0.5 seconds combined.

The c55x+ suite includes six binary-fixture tests against the
companion rizin-testbins drop-in tms320/coff2/*.obj corpus,
covering function discovery (afl), stack-pointer tracking
(afvs / afS), data-section walk (iS), and globals enumeration
(is). The c55x suite covers tms320/emulateme_nostd.ccsv5.c55x
.ticoff2.dbg.coff from the existing rizin-testbins tree.

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

  TI SPRU374    'TMS320C55x DSP Mnemonic Instruction Set Reference
                Guide' (publicly available) -- C55x baseline.
  TI SWPU086    'TMS320C55x+ DSP Algebraic Instruction Set Reference
                Guide' (May 2005) -- C55x+ instruction encodings.
  TI SWPU104    'TMS320C55x+ DSP Mnemonic Instruction Set Reference
                Guide' (December 2006) -- C55x+ mnemonic forms.
This commit is contained in:
Anton Kochkov 2026-05-27 05:37:17 +00:00 committed by NOT XVilka
parent e0ce234a35
commit 4221d56cb9
12 changed files with 2886 additions and 117 deletions

View file

@ -480,6 +480,7 @@ static const char *map_dwarf_reg_to_riscv_reg(ut32 reg_num) {
#include <xtensa/xtensa_dwarf_regnum_table.h>
#include <alpha/alpha_dwarf_regnum_table.h>
#include <h8300/h8300_dwarf_regnum_table.h>
#include <tms320/tms320_dwarf_regnum_table.h>
/**
* \brief Returns a function that maps a DWARF register number to a register name
@ -561,6 +562,10 @@ static DWARF_RegisterMapping dwarf_register_mapping_query(RZ_NONNULL const char
return h8300h_register_name;
}
if (RZ_STR_EQ(arch, "tms320")) {
return tms320_c55x_register_name;
}
RZ_LOG_ERROR("No DWARF register mapping function defined for %s %d bits\n", arch, bits);
return map_dwarf_register_dummy;
}

View file

@ -0,0 +1,733 @@
// SPDX-FileCopyrightText: 2014 Ilya V. Matveychikov <i.matveychikov@milabs.ru>
// SPDX-FileCopyrightText: 2014 montekki <fedor.sakharov@gmail.com>
// SPDX-FileCopyrightText: 2026 RizinOrg <info@rizin.re>
// SPDX-License-Identifier: LGPL-3.0-only
#include <string.h>
#include <rz_types.h>
#include <rz_endian.h>
#include <rz_analysis.h>
#include "c55x_analysis.h"
/**
* \file c55x_analysis.c
*
* TMS320C55x (base) analysis: classify opcodes, resolve branch
* targets, set basic-block fallthrough, fill in stack effects.
*
* Pure byte-level dispatch -- no mnemonic-string matching. Each
* recognised opcode is dispatched on its leading byte (with second-
* byte refinement where the prefix family is shared by multiple
* instructions).
*
* The encoding map below was extracted from TI SPRU374 (TMS320C55x
* DSP Mnemonic Instruction Set Reference Guide, public) and cross-
* referenced against the rizin c55x decoder's internal opcode table
* (librz/arch/isa/tms320/c55x/table.h, originally by th0rpe 2013).
*
* Key differences from the C55x+ ('+'-suffixed Ryujin / Low-Power
* C55x) instruction set:
*
* - 0x21 is the parallel-instruction marker (`|| nop`), NOT RET.
* RET in baseline C55x is encoded as the 2-byte form 0x48 0x88;
* RETI as 0x48 0xA8.
* - 0x00 is RPTCC (3-byte conditional repeat), NOT NOP_16.
* NOP is the 1-byte 0x20 (same as C55x+).
* - 0x02 is RETCC (conditional return), NOT B/CALL indirect.
* - 0x04 / 0x06 / 0x08 / 0x4A are the short B/BCC/CALL forms;
* 0x6A / 0x6B / 0x6C / 0x6E are the 24-bit absolute forms.
* - INTR / TRAP are 2-byte 0x95 ?? (bit 7 of byte 2 selects).
* - Many control-flow opcodes use a second-byte high bit to flip
* between B / CALL or related variants.
*
* Encoding cross-validated against rz-asm output for the testbins
* c55x emulateme binary (tms320/emulateme_nostd.ccsv5.c55x.ticoff2.*)
* and against TI SPRU374 sec.4 (Instruction Set Reference).
*
* Byte-order note: C55x branch displacements and absolute targets
* are stored MSB-first within the instruction stream -- see SPRU374
* sec.3. Extracted with rz_read_at_be16() / rz_read_at_be24() to avoid
* any unaligned-int dereference.
*/
/* Sign-extend an n-bit value to st32. */
static inline st32 sign_extend(ut32 v, ut32 bits) {
const ut32 mask = (1u << bits) - 1;
v &= mask;
if (v & (1u << (bits - 1))) {
return (st32)(v | ~mask);
}
return (st32)v;
}
/* Set conditional-jump fields: type=cjmp, jump=target, fail=fallthrough. */
static inline void set_cjmp(RzAnalysisOp *op, ut64 target) {
op->type = RZ_ANALYSIS_OP_TYPE_CJMP;
op->jump = target;
op->fail = op->addr + op->size;
op->direction = RZ_ANALYSIS_OP_DIR_EXEC;
}
/* Set conditional-call fields: type=ccall, jump=target, fail=fallthrough. */
static inline void set_ccall(RzAnalysisOp *op, ut64 target) {
op->type = RZ_ANALYSIS_OP_TYPE_CCALL;
op->jump = target;
op->fail = op->addr + op->size;
op->stackop = RZ_ANALYSIS_STACK_INC;
op->stackptr = 2;
op->direction = RZ_ANALYSIS_OP_DIR_EXEC;
}
/* Set unconditional-call fields: type=call, jump=target. */
static inline void set_call(RzAnalysisOp *op, ut64 target) {
op->type = RZ_ANALYSIS_OP_TYPE_CALL;
op->jump = target;
op->fail = op->addr + op->size;
op->stackop = RZ_ANALYSIS_STACK_INC;
op->stackptr = 2;
op->direction = RZ_ANALYSIS_OP_DIR_EXEC;
}
/* Set unconditional-jump fields. */
static inline void set_jmp(RzAnalysisOp *op, ut64 target) {
op->type = RZ_ANALYSIS_OP_TYPE_JMP;
op->jump = target;
op->eob = true;
op->direction = RZ_ANALYSIS_OP_DIR_EXEC;
}
/* Mark an instruction as a return, with stack accounting. */
static inline void set_ret(RzAnalysisOp *op) {
op->type = RZ_ANALYSIS_OP_TYPE_RET;
op->eob = true;
op->stackop = RZ_ANALYSIS_STACK_INC;
op->stackptr = -2;
}
/* Record a memory access width (in bytes: 1, 2, or 4) for loads and
* stores whose effective address is computed at runtime (e.g. via
* an address register or SP+disp). */
static inline void set_mem_width(RzAnalysisOp *op, int width) {
op->refptr = width;
op->ptrsize = width;
}
/* Record an immediate value (for "mov #k, dst" / "add #k, dst" etc.). */
static inline void set_imm(RzAnalysisOp *op, st64 val) {
op->val = (ut64)val;
}
/* Stack push: write+decrement. Track the byte delta. */
static inline void set_push(RzAnalysisOp *op, int delta) {
op->type = RZ_ANALYSIS_OP_TYPE_PUSH;
op->stackop = RZ_ANALYSIS_STACK_INC;
op->stackptr = delta;
}
/* Stack pop: read+increment. Track the byte delta. */
static inline void set_pop(RzAnalysisOp *op, int delta) {
op->type = RZ_ANALYSIS_OP_TYPE_POP;
op->stackop = RZ_ANALYSIS_STACK_INC;
op->stackptr = delta;
}
/* Conditional return -- like RET but doesn't end the basic block
* (fallthrough is possible if the condition is false). */
static inline void set_cret(RzAnalysisOp *op) {
op->type = RZ_ANALYSIS_OP_TYPE_CRET;
op->fail = op->addr + op->size;
op->stackop = RZ_ANALYSIS_STACK_INC;
op->stackptr = -2;
}
/* Set op->reg (destination register name) for instructions whose
* destination register is encoded statically in the leading byte(s).
* The string is borrowed and must point to static storage. */
static inline void set_dst_reg(RzAnalysisOp *op, const char *name) {
op->reg = name;
}
/* Set op->ireg (register used for indirect memory computation) for
* register-indirect loads, stores, branches and calls (e.g. B ACx,
* CALL ACx). The string is borrowed and must point to static storage. */
static inline void set_ireg(RzAnalysisOp *op, const char *name) {
op->ireg = name;
}
/* Set op->direction so higher-level analysis knows whether the op
* reads from memory (LOAD-style), writes to memory (STORE-style),
* jumps (EXEC), or just references an address (REF). */
static inline void set_dir(RzAnalysisOp *op, RzAnalysisOpDirection dir) {
op->direction = dir;
}
/* Set op->disp (displacement) for memory references that compute
* their effective address as `base_register + disp`. */
static inline void set_disp(RzAnalysisOp *op, st64 disp) {
op->disp = (ut64)disp;
}
/* ACx selector tables -- index 0..3 corresponds to AC0..AC3. */
static const char *const c55x_acc_names[4] = { "ac0", "ac1", "ac2", "ac3" };
/* General-purpose register table indexed by the 4-bit field used in
* 0x14 (AADD register form) and a number of other instructions:
* 0..3 -> AC0..AC3 (accumulators)
* 4..7 -> T0..T3 (temporary registers)
* 8..f -> AR0..AR7 (auxiliary / address registers)
* Confirmed against rz-asm output on the c55x decoder. */
static const char *const c55x_gpr_names[16] = {
"ac0", "ac1", "ac2", "ac3",
"t0", "t1", "t2", "t3",
"ar0", "ar1", "ar2", "ar3", "ar4", "ar5", "ar6", "ar7"
};
/* Per-leading-byte instruction size, extracted from the c55x
* decoder's opcode table (librz/arch/isa/tms320/c55x/table.h --
* originally by th0rpe 2013, sourced from TI SPRU374). Bytes not
* documented in the table default to size=1 so the analyzer
* advances and re-syncs on the next byte rather than getting stuck.
*
* Note that some c55x instructions are even longer (up to 7 bytes
* total) due to immediate-operand suffixes; the table value is the
* size of the *leading* fixed-form, not necessarily the size of the
* decoded instruction. This works for analysis purposes (we don't
* need exact instruction boundaries; we need enough bytes to read
* branch displacements and at least classify the type).
*
* For boundary-critical analyses (basic-block formation), the
* disassembler is still authoritative; the analyzer's size estimate
* just needs to be non-zero and not lie about a branch's
* displacement bytes being there. */
static const ut8 c55x_op_sizes[256] = {
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1, 3, 3, 3, 3, /* 0x00 */
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0x10 */
1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 0x20 */
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 0x30 */
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 0x40 */
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 0x50 */
2, 1, 1, 1, 1, 1, 1, 1, 5, 5, 4, 4, 4, 4, 4, 4, /* 0x60 */
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, /* 0x70 */
3, 3, 4, 4, 4, 4, 4, 4, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x80 */
2, 2, 2, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 2, 2, /* 0x90 */
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 0xa0 */
2, 1, 1, 1, 2, 2, 2, 2, 2, 1, 1, 2, 2, 1, 1, 1, /* 0xb0 */
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 0xc0 */
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0xd0 */
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0xe0 */
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 1, 1, /* 0xf0 */
};
static int c55x_op_size(const ut8 *buf, int len) {
if (len < 1) {
return 0;
}
const ut8 sz = c55x_op_sizes[buf[0]];
return ((int)sz <= len) ? (int)sz : 0;
}
int tms320_c55x_op_byte(RzAnalysis *analysis, RzAnalysisOp *op, ut64 addr,
const ut8 *buf, int len) {
if (!op || !buf || len < 1) {
return 0;
}
const int sz = c55x_op_size(buf, len);
if (sz == 0) {
return 0;
}
op->addr = addr;
op->size = sz;
op->type = RZ_ANALYSIS_OP_TYPE_NULL;
/* C55x parallel-instruction marker: bit 0 of the leading
* opcode byte, when the byte is in the parallel-capable range
* 0x01..0x5F, is the 'execute in parallel with the previous
* instruction' flag -- not a separate prefix byte. So 0x03 is
* RETCC-with-parallel-bit-set (same encoding/operands as 0x02
* but executed in parallel), 0x05 is BCC-with-parallel, 0x07
* is B-with-parallel, etc. Per SPRU374 sec.5 ('Parallel Execution
* of Instructions') and cross-checked against the disassembler:
*
* $ rz-asm -a tms320 -c c55x -d 020405 -> retcc t0 == 0
* $ rz-asm -a tms320 -c c55x -d 030405 -> || retcc t0 == 0
*
* Above the 0x60 boundary, odd-byte opcodes are unrelated
* (0x69 CALLCC, 0x6B B abs, etc.); we only mask the bit in the
* parallel-capable range so we don't merge unrelated opcodes.
*
* The instruction size from the table is the same for the even
* and odd siblings (since they encode the same instruction). */
ut8 op_byte = buf[0];
if ((op_byte & 0x01) && op_byte < 0x60) {
op_byte &= ~0x01;
}
switch (op_byte) {
/* ---- 0x00: RPTCC k8, cond (3-byte conditional repeat) -------- */
case 0x00:
op->type = RZ_ANALYSIS_OP_TYPE_REP;
op->fail = addr + sz;
break;
/* ---- 0x02: RETCC cond (conditional return) ------------------- */
case 0x02:
set_cret(op);
break;
/* ---- 0x04: BCC k8, cond (8-bit signed relative cond branch) -- */
case 0x04:
if (sz >= 2) {
set_cjmp(op, addr + sz + sign_extend(buf[1], 8));
}
break;
/* ---- 0x06: B L16 (16-bit relative unconditional) ------------- */
case 0x06:
if (sz >= 3) {
const ut32 disp = rz_read_at_be16(buf, 1);
set_jmp(op, addr + sz + sign_extend(disp, 16));
}
break;
/* ---- 0x08: CALL L16 (16-bit relative call) ------------------- */
case 0x08:
if (sz >= 3) {
const ut32 disp = rz_read_at_be16(buf, 1);
set_call(op, addr + sz + sign_extend(disp, 16));
}
break;
/* ---- 0x0C: RPT k16 ------------------------------------------- */
case 0x0c:
op->type = RZ_ANALYSIS_OP_TYPE_REP;
break;
/* ---- 0x0E: RPTB pmad (block-repeat) -------------------------- */
case 0x0e:
op->type = RZ_ANALYSIS_OP_TYPE_REP;
break;
/* ---- 0x20: NOP (1 byte) -------------------------------------- */
case 0x20:
op->type = RZ_ANALYSIS_OP_TYPE_NOP;
break;
/* ---- 0x21: || nop (parallel marker -- NOT a return) ---------- */
case 0x21:
op->type = RZ_ANALYSIS_OP_TYPE_NOP;
break;
/* ---- 0x38: PSH (single-word push) ---------------------------- */
case 0x38:
set_push(op, 1);
break;
/* ---- 0x3A: POP ----------------------------------------------- */
case 0x3a:
set_pop(op, -1);
break;
/* ---- 0x48: 2-byte multi-form: RPT CSR / RPTADD / RPTSUB / RET / RETI
* differentiated by bits 0-2 of buf[1] (per the table.h
* INSN_MASK(8,3,value) where bit 8 of the 16-bit BE
* instruction is bit 0 of byte 1). The mapping:
* buf[1] & 0x07 == 0 -> RPT CSR
* buf[1] & 0x07 == 1 -> RPTADD CSR, TAx
* buf[1] & 0x07 == 2 -> RPTADD CSR, K4
* buf[1] & 0x07 == 3 -> RPTSUB CSR, K4
* buf[1] & 0x07 == 4 -> RET
* buf[1] & 0x07 == 5 -> RETI
*/
case 0x48:
if (sz >= 2) {
switch (buf[1] & 0x07) {
case 4: /* RET */
case 5: /* RETI */
set_ret(op);
break;
case 0: /* RPT CSR */
case 1: /* RPTADD CSR, TAx */
case 2: /* RPTADD CSR, K4 */
case 3: /* RPTSUB CSR, K4 */
op->type = RZ_ANALYSIS_OP_TYPE_REP;
break;
default:
op->type = RZ_ANALYSIS_OP_TYPE_NOP;
break;
}
}
break;
/* ---- 0x49-0x4F miscellaneous 2-byte forms (most aren't control
* flow). 0x4A is the 2-byte short B; everything else stays
* NULL or is handled below. */
case 0x4a: /* B k8 (8-bit signed relative B) -- 2 bytes */
if (sz >= 2) {
set_jmp(op, addr + sz + sign_extend(buf[1], 8));
}
break;
case 0x4c: /* RPT k8 (3-byte) */
op->type = RZ_ANALYSIS_OP_TYPE_REP;
break;
case 0x4e:
/* AADD K8, SP -- address arithmetic (frame setup).
*
* Semantic: SP = SP + K8 (signed).
*
* rizin's stack-effect convention is that op->stackptr is
* the amount by which SP *decreases* (i.e. the amount the
* stack frame grows). On c55x the stack grows downward, so
* K8 < 0 -> SP decreases -> frame grows -> stackptr = -K8
* K8 > 0 -> SP increases -> frame shrinks -> stackptr = -K8
* which is just stackptr = -K8 in both cases. See
* rz_analysis_op_apply_sp_effect() in librz/arch/op.c. */
op->type = RZ_ANALYSIS_OP_TYPE_ADD;
if (sz >= 2) {
const st8 k8 = (st8)buf[1];
set_imm(op, k8);
set_dst_reg(op, "sp");
set_disp(op, k8);
op->stackop = RZ_ANALYSIS_STACK_INC;
op->stackptr = -k8;
}
break;
/* ---- 0x60-0x66: BCC variants (8-bit signed relative cond branch).
* Multiple variants encode different condition register
* interpretations but all are conditional jumps with
* 8-bit signed displacement at byte 1. */
case 0x60:
case 0x61:
case 0x62:
case 0x63:
case 0x64:
case 0x65:
case 0x66:
if (sz >= 2) {
set_cjmp(op, addr + sz + sign_extend(buf[1], 8));
}
break;
/* ---- 0x6A / 0x6B: B P24 (24-bit absolute unconditional) ------ */
case 0x6a:
case 0x6b:
if (sz >= 4) {
set_jmp(op, rz_read_at_be24(buf, 1));
}
break;
/* ---- 0x6C: CALL P24 (24-bit absolute call) ------------------- */
case 0x6c:
if (sz >= 4) {
set_call(op, rz_read_at_be24(buf, 1));
}
break;
/* ---- 0x6E: CALLCC P24, cond (conditional 24-bit call) -------- */
case 0x6e:
if (sz >= 4) {
/* Format: 6E hh ll cond -- disp is BE16 at offset 1. */
const ut32 disp = rz_read_at_be16(buf, 1);
set_ccall(op, addr + sz + sign_extend(disp, 16));
}
break;
/* ---- 0x6F: BCC P24, cond -- alternate cond branch ------------- */
case 0x6f:
if (sz >= 4) {
const ut32 disp = rz_read_at_be16(buf, 1);
set_cjmp(op, addr + sz + sign_extend(disp, 16));
}
break;
/* ---- 0x91: B ACx (indirect register branch) ------------------ */
case 0x91:
op->type = RZ_ANALYSIS_OP_TYPE_UJMP;
op->eob = true;
op->fail = addr + sz;
if (sz >= 2) {
set_ireg(op, c55x_acc_names[buf[1] & 0x03]);
}
break;
/* ---- 0x92 / 0x93: CALL ACx (indirect register call) ---------- */
case 0x92:
case 0x93:
op->type = RZ_ANALYSIS_OP_TYPE_UCALL;
op->fail = addr + sz;
op->stackop = RZ_ANALYSIS_STACK_INC;
op->stackptr = 2;
if (sz >= 2) {
set_ireg(op, c55x_acc_names[buf[1] & 0x03]);
}
break;
/* ---- 0x94: RESET --------------------------------------------- */
case 0x94:
op->type = RZ_ANALYSIS_OP_TYPE_TRAP;
op->family = RZ_ANALYSIS_OP_FAMILY_CPU;
break;
/* ---- 0x95: INTR #k5 or TRAP #k5 (bit 7 of buf[1] selects) ---- */
case 0x95:
if (sz >= 2) {
const ut8 k5 = buf[1] & 0x1f;
if (buf[1] & 0x80) {
op->type = RZ_ANALYSIS_OP_TYPE_TRAP;
} else {
op->type = RZ_ANALYSIS_OP_TYPE_SWI;
}
set_imm(op, k5);
op->family = RZ_ANALYSIS_OP_FAMILY_CPU;
}
break;
/* ---- 0x96 / 0x97 / 0x9E / 0x9F: XCC (predicated execute) ----- */
case 0x96:
case 0x97:
case 0x9e:
case 0x9f:
op->type = RZ_ANALYSIS_OP_TYPE_CMP;
op->family = RZ_ANALYSIS_OP_FAMILY_CPU;
break;
/* ---- PSH/POP variants ---------------------------------------- */
case 0xb5:
case 0xb7:
case 0xe4:
/* PSH variants: 0xb5 PSH Smem, 0xb7 PSH dbl(Smem),
* 0xe4 PSH dbl(Lmem). All decrement SP. */
set_push(op, (op_byte == 0xb7 || op_byte == 0xe4) ? 2 : 1);
set_dst_reg(op, "sp");
set_dir(op, RZ_ANALYSIS_OP_DIR_WRITE);
break;
case 0xb8:
case 0xb9:
case 0xbb:
/* POP variants: 0xb8 / 0xb9 POP dbl(Smem),
* 0xbb POP Smem. All increment SP. */
set_pop(op, (op_byte == 0xbb) ? -1 : -2);
set_dst_reg(op, "sp");
set_dir(op, RZ_ANALYSIS_OP_DIR_READ);
break;
/* ---- Common arithmetic/logical/move bytes ------------------- */
case 0x10:
case 0x18:
case 0x28:
case 0x72:
case 0x7d:
case 0xd9:
case 0xf4:
op->type = RZ_ANALYSIS_OP_TYPE_AND;
break;
case 0x1a:
case 0x2a:
case 0x73:
case 0x7e:
case 0xda:
case 0xf5:
op->type = RZ_ANALYSIS_OP_TYPE_OR;
break;
case 0x1c:
case 0x2c:
case 0x74:
case 0x7f:
case 0xdb:
case 0xf6:
op->type = RZ_ANALYSIS_OP_TYPE_XOR;
break;
case 0x12:
case 0xf0:
case 0xf1:
op->type = RZ_ANALYSIS_OP_TYPE_CMP;
break;
case 0x14:
/* AADD k16, AC/AR -- address-arithmetic add.
* Encoding: 14 <src_high_nib>0 <dst_high_nib>0.
* Both register fields are 4 bits in the high nibble of
* buf[1] / buf[2]; see c55x_gpr_names[] for the map. */
op->type = RZ_ANALYSIS_OP_TYPE_LEA;
if (sz >= 3) {
const ut8 src_idx = (buf[1] >> 4) & 0x0f;
const ut8 dst_idx = (buf[2] >> 4) & 0x0f;
set_dst_reg(op, c55x_gpr_names[dst_idx]);
set_ireg(op, c55x_gpr_names[src_idx]);
}
break;
case 0x16:
case 0x22:
case 0x3c:
case 0x3e:
case 0x44:
case 0x52:
case 0x75:
case 0x78:
/* MOV-family bytes that don't have a uniform low-nibble
* register encoding -- type only. */
op->type = RZ_ANALYSIS_OP_TYPE_MOV;
break;
case 0xa0:
case 0xa1:
case 0xa2:
case 0xa3:
case 0xa4:
case 0xa5:
case 0xa6:
case 0xa7:
case 0xa8:
case 0xa9:
case 0xaa:
case 0xab:
case 0xac:
case 0xad:
case 0xae:
case 0xaf:
/* MOV Smem, REG -- LOAD-family. Low nibble of the leading
* byte selects the destination register; for the SP-relative
* common case (bit 0 of buf[1] clear), the source memory
* operand is `*sp(#disp)` where disp = buf[1]>>1. */
op->type = RZ_ANALYSIS_OP_TYPE_MOV;
set_dst_reg(op, c55x_gpr_names[op_byte & 0x0f]);
set_dir(op, RZ_ANALYSIS_OP_DIR_READ);
set_mem_width(op, 2);
if (sz >= 2 && !(buf[1] & 0x01)) {
set_ireg(op, "sp");
set_disp(op, buf[1] >> 1);
}
break;
case 0xc0:
case 0xc1:
case 0xc2:
case 0xc3:
case 0xc4:
case 0xc5:
case 0xc6:
case 0xc7:
case 0xc8:
case 0xc9:
case 0xca:
case 0xcb:
case 0xcc:
case 0xcd:
case 0xce:
case 0xcf:
/* MOV REG, Smem -- STORE-family. Low nibble of the leading
* byte selects the source register; for the SP-relative
* common case, the destination memory operand is
* `*sp(#disp)` where disp = buf[1]>>1. */
op->type = RZ_ANALYSIS_OP_TYPE_MOV;
set_dst_reg(op, c55x_gpr_names[op_byte & 0x0f]);
set_dir(op, RZ_ANALYSIS_OP_DIR_WRITE);
set_mem_width(op, 2);
if (sz >= 2 && !(buf[1] & 0x01)) {
set_ireg(op, "sp");
set_disp(op, buf[1] >> 1);
}
break;
case 0x24:
case 0x40:
case 0x5a:
case 0x70:
case 0x7a:
case 0x7b:
case 0x81:
case 0xd6:
op->type = RZ_ANALYSIS_OP_TYPE_ADD;
break;
case 0x26:
case 0x42:
case 0x71:
case 0x7c:
case 0xd7:
case 0xd8:
op->type = RZ_ANALYSIS_OP_TYPE_SUB;
break;
case 0x2e: /* MAX */
case 0x30: /* MIN */
op->type = RZ_ANALYSIS_OP_TYPE_CMP;
break;
case 0x32: /* ABS -- see comment in c55x_plus analyzer */
op->type = RZ_ANALYSIS_OP_TYPE_NULL;
break;
case 0x34: /* NEG */
op->type = RZ_ANALYSIS_OP_TYPE_SUB;
break;
case 0x36: /* NOT */
op->type = RZ_ANALYSIS_OP_TYPE_NOT;
break;
case 0x46: /* BCLR */
case 0xec: /* BSET */
op->type = RZ_ANALYSIS_OP_TYPE_MOV;
break;
case 0x50:
case 0x5c: /* SFTL */
op->type = RZ_ANALYSIS_OP_TYPE_SHL;
break;
case 0x54: /* ADDV */
op->type = RZ_ANALYSIS_OP_TYPE_ADD;
break;
case 0x56:
case 0x83: /* MAC */
case 0x58:
case 0x82:
case 0xfd: /* MPY */
case 0x1e:
case 0x79: /* MPYK */
case 0x84: /* MAS */
case 0x86:
case 0x87:
case 0xd1:
case 0xd3: /* MPYM */
case 0xd0: /* MACMZ */
case 0xd2:
case 0xd4: /* MACM */
case 0xd5: /* MASM */
case 0xf8: /* MPYMK */
op->type = RZ_ANALYSIS_OP_TYPE_MUL;
break;
case 0x5e: /* SWAP */
op->type = RZ_ANALYSIS_OP_TYPE_XCHG;
break;
case 0x76: /* BFXTR */
op->type = RZ_ANALYSIS_OP_TYPE_MOV;
break;
case 0x77: /* AMOV */
case 0x85:
case 0xb4: /* AMAR */
op->type = RZ_ANALYSIS_OP_TYPE_LEA;
break;
case 0xb6: /* DELAY */
op->type = RZ_ANALYSIS_OP_TYPE_MOV;
op->family = RZ_ANALYSIS_OP_FAMILY_CPU;
break;
case 0xdc:
case 0xe0: /* BTST */
op->type = RZ_ANALYSIS_OP_TYPE_AND;
break;
case 0xe3: /* BTSTSET */
op->type = RZ_ANALYSIS_OP_TYPE_AND;
break;
case 0xde: /* ADDSUBCC */
op->type = RZ_ANALYSIS_OP_TYPE_ADD;
break;
case 0xf2:
case 0xf3: /* BAND */
op->type = RZ_ANALYSIS_OP_TYPE_AND;
break;
default:
/* Unknown leading byte. Mark as NULL so the analyzer can
* keep walking but won't merge it into a basic block. The
* size=1 default in c55x_op_size() means we advance one
* byte and try the next one -- useful for parallel-prefixed
* instructions where the leading 0x01/0x03/0x05/... bytes
* are themselves the parallel marker. */
break;
}
return op->size;
}

View file

@ -0,0 +1,14 @@
// SPDX-FileCopyrightText: 2014 Ilya V. Matveychikov <i.matveychikov@milabs.ru>
// SPDX-FileCopyrightText: 2014 montekki <fedor.sakharov@gmail.com>
// SPDX-FileCopyrightText: 2026 RizinOrg <info@rizin.re>
// SPDX-License-Identifier: LGPL-3.0-only
#ifndef ANALYSIS_C55X_H
#define ANALYSIS_C55X_H
#include <rz_analysis.h>
int tms320_c55x_op_byte(RzAnalysis *analysis, RzAnalysisOp *op, ut64 addr,
const ut8 *buf, int len);
#endif /* ANALYSIS_C55X_H */

View file

@ -1,80 +1,806 @@
// SPDX-FileCopyrightText: 2014 montekki <i.matveychikov@milabs.ru>
// SPDX-FileCopyrightText: 2014 Ilya V. Matveychikov <i.matveychikov@milabs.ru>
// SPDX-FileCopyrightText: 2014 montekki <fedor.sakharov@gmail.com>
// SPDX-FileCopyrightText: 2026 RizinOrg <info@rizin.re>
// SPDX-License-Identifier: LGPL-3.0-only
#include <string.h>
#include <rz_types.h>
#include <rz_lib.h>
#include <rz_asm.h>
#include <rz_endian.h>
#include <rz_analysis.h>
#include "c55plus_analysis.h"
#include "ins.h"
int tms320_c55x_plus_op(RzAnalysis *analysis, RzAnalysisOp *op, ut64 addr, const ut8 *buf, int len) {
ut16 *ins = (ut16 *)buf;
ut32 ins_len;
/**
* \file c55plus_analysis.c
*
* TMS320C55x+ analysis: classify opcodes, resolve branch targets, set
* basic-block fallthrough, fill in src/dst/val and stack effects.
*
* Pure byte-level dispatch -- no mnemonic-string matching. Each
* recognised opcode is dispatched on its leading byte (or leading byte
* + a small subset of the second byte where the prefix family is
* shared by multiple instructions).
*
* The encoding map below was extracted from SWPU104 chapter 6 (Dec
* 2006 'Algebraic Instruction Set' reference) and SWPU086 chapter 4
* (May 2005 'CPU Reference Guide', Preliminary), then cross-validated
* against TI dis55.exe v4.3.6 (CCSv5 c55x_plus SDK, Feb 2010) on the
* testbins#289 c55xp corpus.
*
* Branch and control-flow encodings:
*
* 0x00 .. NOP / IDLE / RETI / to_word sec.6.5.11, sec.6.5.20, sec.6.5.16
* 0x02 b1 B/CALL ACx (indirect register) sec.6.5.2, sec.6.5.6
* 0x03 b1 INTR #k4 / TRAP #k4 sec.6.5.13, sec.6.5.19
* 0x04 / 0x06 XCC predicated execute sec.6.5.9
* 0x05 / 0x07 XCCPART sec.6.5.9
* 0x08 RETCC sec.6.5.17
* 0x20 NOP (1 byte) sec.6.5.11
* 0x21 RET (1 byte) sec.6.5.16
* 0x68 hh ll B short-relative sec.6.5.2
* 0x69 hh ll CALL short-relative sec.6.5.6
* 0x6A ss dst BCC short-relative (8-bit) sec.6.5.1
* 0x6C / 0x6D RPT / RPTCC sec.6.5.14
* 0x6E / 0x6F RPTBLOCAL / RPTB sec.6.5.14
* 0x9A hh ll d BCC long-relative (16-bit) sec.6.5.1
* 0x9B hh ll d CALLCC long-relative sec.6.5.5
* 0x9C hh ll d B long-absolute (24-bit) sec.6.5.2
* 0x9D hh ll d CALL long-absolute sec.6.5.6
* 0x9E / 0x9F B / CALL with far() prefix sec.6.5.2, sec.6.5.6
* 0xD8 ... BCC far-absolute (5-byte) sec.6.5.1
* 0xD9 ... CALLCC far-absolute sec.6.5.5
* 0xDA / 0xDB BCC / BCCU short-form sec.6.5.1
* 0xDC / 0xDD BCC / BCCU sec.6.5.1
* 0xDE / 0xDF BCC / BCCU sec.6.5.1
*
* Stack-affecting:
*
* 0x0D PSHBOTH ACx (2 words) sec.6.7.6
* 0x0E PSH dbl(ACx) (2 words) sec.6.7
* 0x0F POP dbl(ACx) (2 words)
* 0x61 PSH dbl(mem) (2 words)
* 0x70 PSH dual-register (2 words)
* 0x71 POP dual-register
* 0x94 PSH ACx, mem (2 words)
*
* Byte-order note: C55x+ branch displacements and absolute targets are
* stored MSB-first within the instruction stream even though the
* surrounding processor is little-endian -- see SWPU104 sec.3.5. We use
* rz_read_at_be16() / rz_read_at_be24() from rz_endian.h to extract
* them unambiguously without unaligned-int dereference.
*/
if (!buf || len <= 0) {
/* Helper: sign-extend an n-bit value (for short conditional branch
* relative displacements, which are 8-bit signed in some encodings
* and 16-bit signed in others). */
static inline st32 sign_extend(ut32 v, ut32 bits) {
const ut32 mask = (1u << bits) - 1;
v &= mask;
if (v & (1u << (bits - 1))) {
return (st32)(v | ~mask);
}
return (st32)v;
}
/* Set conditional-jump fields: type=cjmp, jump=target, fail=fallthrough. */
static inline void set_cjmp(RzAnalysisOp *op, ut64 addr, ut64 target) {
op->type = RZ_ANALYSIS_OP_TYPE_CJMP;
op->jump = target;
op->fail = addr + op->size;
op->direction = RZ_ANALYSIS_OP_DIR_EXEC;
}
/* Set conditional-call fields: type=ccall, jump=target, fail=fallthrough. */
static inline void set_ccall(RzAnalysisOp *op, ut64 addr, ut64 target) {
op->type = RZ_ANALYSIS_OP_TYPE_CCALL;
op->jump = target;
op->fail = addr + op->size;
op->stackop = RZ_ANALYSIS_STACK_INC;
op->stackptr = 2;
op->direction = RZ_ANALYSIS_OP_DIR_EXEC;
}
/* Set unconditional-call fields: type=call, jump=target. */
static inline void set_call(RzAnalysisOp *op, ut64 target) {
op->type = RZ_ANALYSIS_OP_TYPE_CALL;
op->jump = target;
op->stackop = RZ_ANALYSIS_STACK_INC;
op->stackptr = 2;
op->direction = RZ_ANALYSIS_OP_DIR_EXEC;
}
/* Set unconditional-jump fields. */
static inline void set_jmp(RzAnalysisOp *op, ut64 target) {
op->type = RZ_ANALYSIS_OP_TYPE_JMP;
op->jump = target;
op->direction = RZ_ANALYSIS_OP_DIR_EXEC;
}
/* Mark an instruction as a return, with stack accounting. */
static inline void set_ret(RzAnalysisOp *op) {
op->type = RZ_ANALYSIS_OP_TYPE_RET;
op->eob = true;
op->stackop = RZ_ANALYSIS_STACK_INC;
op->stackptr = -2;
}
/* Conditional return -- like RET but doesn't end the basic block
* (fallthrough is possible if the condition is false). */
static inline void set_cret(RzAnalysisOp *op) {
op->type = RZ_ANALYSIS_OP_TYPE_CRET;
op->fail = op->addr + op->size;
op->stackop = RZ_ANALYSIS_STACK_INC;
op->stackptr = -2;
}
/* Stack push: write+decrement. Track the byte delta. */
static inline void set_push(RzAnalysisOp *op, int delta) {
op->type = RZ_ANALYSIS_OP_TYPE_PUSH;
op->stackop = RZ_ANALYSIS_STACK_INC;
op->stackptr = delta;
}
/* Stack pop: read+increment. Track the byte delta. */
static inline void set_pop(RzAnalysisOp *op, int delta) {
op->type = RZ_ANALYSIS_OP_TYPE_POP;
op->stackop = RZ_ANALYSIS_STACK_INC;
op->stackptr = delta;
}
/* Record an immediate value (for "mov #k, dst" / "add #k, dst" etc.). */
static inline void set_imm(RzAnalysisOp *op, st64 val) {
op->val = (ut64)val;
}
/* Record a memory access width (in bytes: 1, 2, or 4) for loads and
* stores whose effective address is computed at runtime. */
static inline void set_mem_width(RzAnalysisOp *op, int width) {
op->refptr = width;
op->ptrsize = width;
}
/* Set op->reg (destination register name) for instructions whose
* destination register is encoded statically in the leading byte(s).
* The string is borrowed and must point to static storage. */
static inline void set_dst_reg(RzAnalysisOp *op, const char *name) {
op->reg = name;
}
/* Set op->ireg (register used for indirect memory computation) for
* register-indirect loads, stores, branches and calls (e.g. B ACx,
* CALL ACx). The string is borrowed and must point to static storage. */
static inline void set_ireg(RzAnalysisOp *op, const char *name) {
op->ireg = name;
}
/* Set op->direction so higher-level analysis knows whether the op
* reads from memory (LOAD-style), writes to memory (STORE-style),
* jumps (EXEC), or just references an address (REF). */
static inline void set_dir(RzAnalysisOp *op, RzAnalysisOpDirection dir) {
op->direction = dir;
}
/* Set op->disp (displacement) for memory references that compute
* their effective address as `base_register + disp`. */
static inline void set_disp(RzAnalysisOp *op, st64 disp) {
op->disp = (ut64)disp;
}
/* ACx selector tables -- index 0..3 corresponds to AC0..AC3. */
static const char *const c55xp_acc_names[4] = { "ac0", "ac1", "ac2", "ac3" };
int tms320_c55x_plus_op(RzAnalysis *analysis, RzAnalysisOp *op, ut64 addr,
const ut8 *buf, int len) {
if (!op || !buf || len < 1) {
return 0;
}
ins_len = get_ins_len(buf[0]);
if (ins_len == 0) {
const ut32 ins_len = get_ins_len(buf[0]);
if (ins_len == 0 || (int)ins_len > len) {
return 0;
}
op->addr = addr;
op->size = ins_len;
op->type = RZ_ANALYSIS_OP_TYPE_NULL;
if (ins_len == 1) {
if (*ins == 0x20) {
switch (buf[0]) {
/* ---- 0x00 family: NOP_16 / IDLE / RETI / to_word --------------- */
case 0x00:
if (ins_len < 2) {
op->type = RZ_ANALYSIS_OP_TYPE_NOP;
} else if (*ins == 0x21) {
op->type = RZ_ANALYSIS_OP_TYPE_RET;
break;
}
} else if (ins_len >= 4 && buf[0] == 0xD8) {
// BCC conditional absolute jump
op->type = RZ_ANALYSIS_OP_TYPE_CJMP;
op->jump = (buf[1] << 16) | (buf[2] << 8) | buf[3];
} else if (ins_len >= 2 && buf[0] == 0x6A) {
// BCC conditional relative jump
op->type = RZ_ANALYSIS_OP_TYPE_CJMP;
op->jump = addr + ((st8)buf[1]) + ins_len;
} else if (ins_len >= 3 && buf[0] == 0x9A) {
// BCC conditional relative jump
op->type = RZ_ANALYSIS_OP_TYPE_CJMP;
op->jump = addr + (st16)((buf[1] << 8) | buf[2]) + ins_len;
} else if (ins_len >= 4 && buf[0] == 0x9C) {
// B unconditional absolute jump
op->type = RZ_ANALYSIS_OP_TYPE_JMP;
op->jump = (buf[1] << 16) | (buf[2] << 8) | buf[3];
} else if (ins_len >= 3 && buf[0] == 0x68) {
// B unconditional relative jump
op->type = RZ_ANALYSIS_OP_TYPE_JMP;
op->jump = addr + (st16)((buf[1] << 8) | buf[2]) + ins_len;
} else if (ins_len == 2 && buf[0] == 0x02) {
// CALL unconditional absolute call with acumulator register ACx
switch (buf[1]) {
case 0x20: /* IDLE */
op->type = RZ_ANALYSIS_OP_TYPE_NOP;
op->family = RZ_ANALYSIS_OP_FAMILY_CPU;
break;
case 0xc0: /* RETI */
set_ret(op);
set_dst_reg(op, "sp");
break;
default:
op->type = RZ_ANALYSIS_OP_TYPE_NOP;
break;
}
break;
op->type = RZ_ANALYSIS_OP_TYPE_UCALL;
/* ---- 0x01 family: rptsub --------------------------------------- */
case 0x01:
op->type = RZ_ANALYSIS_OP_TYPE_REP;
break;
/* ---- 0x02 family: B/CALL ACx indirect; both eob -----------------*/
case 0x02: {
if (ins_len < 2) {
break;
}
/* Bit 7 of the second byte selects CALL (=1) vs B (=0).
* The low 2 bits of buf[1] select ACx (0..3). */
const bool is_call = (buf[1] & 0x80) != 0;
const ut8 ac_idx = buf[1] & 0x03;
if (is_call) {
op->type = RZ_ANALYSIS_OP_TYPE_UCALL;
op->stackop = RZ_ANALYSIS_STACK_INC;
op->stackptr = 2;
set_dir(op, RZ_ANALYSIS_OP_DIR_EXEC);
} else {
op->type = RZ_ANALYSIS_OP_TYPE_UJMP;
op->eob = true;
set_dir(op, RZ_ANALYSIS_OP_DIR_EXEC);
}
set_ireg(op, c55xp_acc_names[ac_idx]);
op->fail = addr + ins_len;
} else if (ins_len >= 3 && buf[0] == 0x69) {
// CALL unconditional relative call
op->type = RZ_ANALYSIS_OP_TYPE_CALL;
op->jump = addr + (st16)((buf[1] << 8) | buf[2]) + ins_len;
} else if (ins_len >= 3 && buf[0] == 0x9D) {
// CALL unconditional absolute call
op->type = RZ_ANALYSIS_OP_TYPE_CALL;
op->jump = (buf[1] << 16) | (buf[2] << 8) | buf[3];
} else if (ins_len >= 3 && buf[0] == 0x9B) {
// CALLCC conditional relative call
op->type = RZ_ANALYSIS_OP_TYPE_CALL;
op->jump = addr + (st16)((buf[1] << 8) | buf[2]) + ins_len;
} else if (ins_len >= 4 && buf[0] == 0xD9) {
// CALLCC conditional absolute call
op->type = RZ_ANALYSIS_OP_TYPE_CALL;
op->jump = (buf[1] << 16) | (buf[2] << 8) | buf[3];
} else {
op->type = RZ_ANALYSIS_OP_TYPE_UNK;
break;
}
/* ---- 0x03 family: INTR / TRAP / SWAP / SIM_TRIG ---------------
* Differentiated by buf[1] high nibble:
* 0x0?,0x1?,0x2?,0x3? -> intr #k5 (SWPU104 6.5.13)
* 0x4?,0x5? -> trap #k5 (SWPU104 6.5.19)
* 0x8?,0x9?,0xa?,0xb? -> swap regs (SWPU104 6.7.x)
* 0xc?..0xf? -> sim_trig (Wrigley silicon)
*/
case 0x03:
if (ins_len < 2) {
break;
}
switch (buf[1] & 0xc0) {
case 0x00: /* intr #k5 */
op->type = RZ_ANALYSIS_OP_TYPE_SWI;
set_imm(op, buf[1] & 0x1f);
op->family = RZ_ANALYSIS_OP_FAMILY_CPU;
break;
case 0x40: /* trap #k5 */
op->type = RZ_ANALYSIS_OP_TYPE_TRAP;
set_imm(op, buf[1] & 0x1f);
op->family = RZ_ANALYSIS_OP_FAMILY_CPU;
break;
case 0x80: /* swap */
op->type = RZ_ANALYSIS_OP_TYPE_XCHG;
break;
case 0xc0: /* sim_trig - Wrigley simulator trigger */
op->type = RZ_ANALYSIS_OP_TYPE_TRAP;
op->family = RZ_ANALYSIS_OP_FAMILY_CPU;
break;
}
break;
/* ---- 0x04 / 0x06: XCC (predicated execute) --------------------- */
case 0x04:
case 0x06:
op->type = RZ_ANALYSIS_OP_TYPE_CMP;
op->family = RZ_ANALYSIS_OP_FAMILY_CPU;
break;
/* ---- 0x05 / 0x07: XCCPART -------------------------------------- */
case 0x05:
case 0x07:
op->type = RZ_ANALYSIS_OP_TYPE_CMP;
op->family = RZ_ANALYSIS_OP_FAMILY_CPU;
break;
/* ---- 0x08: RETCC (conditional ret) ----------------------------- */
case 0x08:
set_cret(op);
set_dst_reg(op, "sp");
break;
/* ---- 0x0A: BCLR/BSET status-register-bit ---------------------- */
case 0x0a:
/* Modifies a status-reg bit -- surface as MOV (the closest fit
* in the rizin optype set; the immediate-side bit pattern is
* not extracted here). */
op->type = RZ_ANALYSIS_OP_TYPE_MOV;
break;
/* ---- 0x0C: AADD addr-add ------------------------------------- */
case 0x0c:
/* AADD K8, SP -- prologue/epilogue frame adjustment.
*
* Semantic: SP = SP + K8 (signed). rizin's convention is
* that op->stackptr is the amount by which SP *decreases*,
* so for AADD that is -K8. See c55x_analysis.c 0x4e and
* rz_analysis_op_apply_sp_effect() in librz/arch/op.c. */
op->type = RZ_ANALYSIS_OP_TYPE_ADD;
if (ins_len >= 2) {
const st8 k8 = (st8)buf[1];
set_imm(op, k8);
set_dst_reg(op, "sp");
set_disp(op, k8);
op->stackop = RZ_ANALYSIS_STACK_INC;
op->stackptr = -k8;
}
break;
/* ---- 0x0D: PSHBOTH ------------------------------------------- */
case 0x0d:
op->type = RZ_ANALYSIS_OP_TYPE_UPUSH;
op->stackop = RZ_ANALYSIS_STACK_INC;
op->stackptr = 2;
set_dst_reg(op, "sp");
set_dir(op, RZ_ANALYSIS_OP_DIR_WRITE);
break;
/* ---- 0x0E / 0x0F: PSH/POP dbl -------------------------------- */
case 0x0e:
set_push(op, 2);
set_dst_reg(op, "sp");
set_dir(op, RZ_ANALYSIS_OP_DIR_WRITE);
break;
case 0x0f:
set_pop(op, -2);
set_dst_reg(op, "sp");
set_dir(op, RZ_ANALYSIS_OP_DIR_READ);
break;
/* ---- 0x20: NOP ----------------------------------------------- */
case 0x20:
op->type = RZ_ANALYSIS_OP_TYPE_NOP;
break;
/* ---- 0x21: RET ----------------------------------------------- */
case 0x21:
set_ret(op);
set_dst_reg(op, "sp");
break;
/* ---- 0x24-0x26: PSH variants --------------------------------- */
case 0x24:
case 0x25:
case 0x26:
set_push(op, 1);
set_dst_reg(op, "sp");
set_dir(op, RZ_ANALYSIS_OP_DIR_WRITE);
break;
/* ---- 0x27: CIRC -- circular addressing helper ---------------- */
case 0x27:
op->type = RZ_ANALYSIS_OP_TYPE_MOV;
break;
/* ---- 0x60: DELAY --------------------------------------------- */
case 0x60:
op->type = RZ_ANALYSIS_OP_TYPE_MOV;
op->family = RZ_ANALYSIS_OP_FAMILY_CPU;
break;
/* ---- 0x61: PSH dbl(mem) -------------------------------------- */
case 0x61:
set_push(op, 2);
set_dst_reg(op, "sp");
set_dir(op, RZ_ANALYSIS_OP_DIR_WRITE);
break;
/* ---- 0x68: B short-relative (16-bit) ------------------------- */
case 0x68:
if (ins_len >= 3) {
const ut32 disp = rz_read_at_be16(buf, 1);
set_jmp(op, addr + 3 + sign_extend(disp, 16));
op->eob = true;
}
break;
/* ---- 0x69: CALL short-relative ------------------------------- */
case 0x69:
if (ins_len >= 3) {
const ut32 disp = rz_read_at_be16(buf, 1);
set_call(op, addr + 3 + sign_extend(disp, 16));
op->fail = addr + ins_len;
}
break;
/* ---- 0x6A: BCC short-relative -------------------------------- */
case 0x6a:
if (ins_len >= 3) {
/* 6A ss dst -- ss is 8-bit signed displacement. */
set_cjmp(op, addr, addr + 3 + sign_extend(buf[1], 8));
}
break;
/* ---- 0x6C: RPT ----------------------------------------------- */
case 0x6c:
op->type = RZ_ANALYSIS_OP_TYPE_REP;
break;
/* ---- 0x6D: RPTCC --------------------------------------------- */
case 0x6d:
op->type = RZ_ANALYSIS_OP_TYPE_REP;
op->fail = addr + ins_len;
break;
/* ---- 0x6E/0x6F: RPTBLOCAL / RPTB ----------------------------- */
case 0x6e:
case 0x6f:
op->type = RZ_ANALYSIS_OP_TYPE_REP;
break;
/* ---- 0x70/0x71: PSH/POP dual-register ------------------------ */
case 0x70:
set_push(op, 2);
set_dst_reg(op, "sp");
set_dir(op, RZ_ANALYSIS_OP_DIR_WRITE);
break;
case 0x71:
set_pop(op, -2);
set_dst_reg(op, "sp");
set_dir(op, RZ_ANALYSIS_OP_DIR_READ);
break;
/* ---- 0x72: ASUB addr-sub ------------------------------------- */
case 0x72:
op->type = RZ_ANALYSIS_OP_TYPE_SUB;
break;
/* ---- 0x74-0x76: ADD/AND/{ABS/NEG/MAX/MIN} -------------------- */
case 0x74:
/* ADD smem,ACx -- but bit 7 of buf[2] flips ADD <-> SUB
* for one of the addressing-mode subforms. */
if (ins_len >= 3 && (buf[2] & 0x80)) {
op->type = RZ_ANALYSIS_OP_TYPE_SUB;
} else {
op->type = RZ_ANALYSIS_OP_TYPE_ADD;
}
break;
case 0x75: op->type = RZ_ANALYSIS_OP_TYPE_AND; break;
case 0x76:
/* 0x76 family -- unary arithmetic on accumulators. The two
* MSB-of-byte bits select the subfamily:
*
* buf[1] & 0x80 == 0: ABS (buf[2]&0x80==0) or NEG (==1)
* buf[1] & 0x80 == 1: MAX (buf[2]&0x80==0) or MIN (==1)
*
* ABS has no clean rizin optype (the RZ_ANALYSIS_OP_TYPE_ABS
* code 44 is missing from the optypes table -- renders as
* 'undefined') so we leave it at NULL. */
if (ins_len >= 3) {
const ut8 b1_top = buf[1] & 0x80;
const ut8 b2_top = buf[2] & 0x80;
if (b1_top) {
op->type = RZ_ANALYSIS_OP_TYPE_CMP; /* MAX/MIN */
(void)b2_top;
} else if (b2_top) {
op->type = RZ_ANALYSIS_OP_TYPE_SUB; /* NEG */
} else {
/* ABS -- see comment above */
op->type = RZ_ANALYSIS_OP_TYPE_NULL;
}
}
break;
/* ---- 0x77: MOV ----------------------------------------------- */
case 0x77:
op->type = RZ_ANALYSIS_OP_TYPE_MOV;
break;
/* ---- 0x79: ROUND --------------------------------------------- */
case 0x79:
op->type = RZ_ANALYSIS_OP_TYPE_MOV;
break;
/* ---- 0x7B: ADD #k7, ACx (b1 0x00-0x0F) or MOV #k8, Tx (0xB0-0xBF) */
case 0x7b:
if (ins_len >= 3) {
set_imm(op, buf[2]);
if ((buf[1] & 0xf0) == 0xb0) {
op->type = RZ_ANALYSIS_OP_TYPE_MOV;
} else {
op->type = RZ_ANALYSIS_OP_TYPE_ADD;
}
} else {
op->type = RZ_ANALYSIS_OP_TYPE_MOV;
}
break;
/* ---- 0x80/0x81/0x82: ADD/SUB long-imm ------------------------ */
case 0x80: op->type = RZ_ANALYSIS_OP_TYPE_ADD; break;
case 0x81:
case 0x82: op->type = RZ_ANALYSIS_OP_TYPE_SUB; break;
/* ---- 0x84/0x85/0x86: AND/OR/XOR ------------------------------ */
case 0x84: op->type = RZ_ANALYSIS_OP_TYPE_AND; break;
case 0x85: op->type = RZ_ANALYSIS_OP_TYPE_OR; break;
case 0x86: op->type = RZ_ANALYSIS_OP_TYPE_XOR; break;
/* ---- 0x89: BCLR --------------------------------------------- */
case 0x89:
op->type = RZ_ANALYSIS_OP_TYPE_MOV;
break;
/* ---- 0x8D / 0x8E: ADD smem,ACx ------------------------------ */
case 0x8d:
case 0x8e:
op->type = RZ_ANALYSIS_OP_TYPE_ADD;
break;
/* ---- 0x90/0x91/0x92/0x93/0x94: various ----------------------- */
case 0x90: op->type = RZ_ANALYSIS_OP_TYPE_ADD; break;
case 0x91: op->type = RZ_ANALYSIS_OP_TYPE_AND; break; /* btstclr */
case 0x92:
case 0x93: op->type = RZ_ANALYSIS_OP_TYPE_MUL; break; /* sqrm */
case 0x94:
set_push(op, 2);
set_dst_reg(op, "sp");
set_dir(op, RZ_ANALYSIS_OP_DIR_WRITE);
break;
/* ---- 0x9A: BCC long-relative (16-bit) ------------------------ */
case 0x9a:
if (ins_len >= 4) {
const ut32 disp = rz_read_at_be16(buf, 1);
set_cjmp(op, addr, addr + ins_len + sign_extend(disp, 16));
}
break;
/* ---- 0x9B: CALLCC long-relative ------------------------------ */
case 0x9b:
if (ins_len >= 4) {
const ut32 disp = rz_read_at_be16(buf, 1);
set_ccall(op, addr, addr + ins_len + sign_extend(disp, 16));
}
break;
/* ---- 0x9C: B long-absolute (24-bit) -------------------------- */
case 0x9c:
if (ins_len >= 4) {
set_jmp(op, rz_read_at_be24(buf, 1));
op->eob = true;
}
break;
/* ---- 0x9D: CALL long-absolute -------------------------------- */
case 0x9d:
if (ins_len >= 4) {
set_call(op, rz_read_at_be24(buf, 1));
op->fail = addr + ins_len;
}
break;
/* ---- 0x9E / 0x9F: B / CALL with far() prefix ----------------- */
case 0x9e:
if (ins_len >= 4) {
set_jmp(op, rz_read_at_be24(buf, 1));
op->eob = true;
}
break;
case 0x9f:
if (ins_len >= 4) {
set_call(op, rz_read_at_be24(buf, 1));
op->fail = addr + ins_len;
}
break;
/* ---- 0xA1/0xA4/0xA6/0xA7/0xA8: arith & compare --------------- */
case 0xa1: op->type = RZ_ANALYSIS_OP_TYPE_ADD; break;
case 0xa4: op->type = RZ_ANALYSIS_OP_TYPE_CMP; break;
case 0xa6:
case 0xa7: op->type = RZ_ANALYSIS_OP_TYPE_SHL; break; /* SFTS/SFTL */
case 0xa8: op->type = RZ_ANALYSIS_OP_TYPE_ROL; break;
case 0xa9: op->type = RZ_ANALYSIS_OP_TYPE_MOV; break; /* EXP */
case 0xaa:
case 0xab: op->type = RZ_ANALYSIS_OP_TYPE_MUL; break;
case 0xae:
op->type = RZ_ANALYSIS_OP_TYPE_SUB;
break;
/* ---- 0xB0: BCC short-form (3-byte conditional jump) ---------- */
case 0xb0:
if (ins_len >= 4) {
const ut32 disp = rz_read_at_be16(buf, 2);
set_cjmp(op, addr, addr + ins_len + sign_extend(disp, 16));
}
break;
case 0xb1:
op->type = RZ_ANALYSIS_OP_TYPE_ADD;
break;
case 0xb2:
op->type = RZ_ANALYSIS_OP_TYPE_CMP;
break;
case 0xb3:
op->type = RZ_ANALYSIS_OP_TYPE_ADD;
break;
/* ---- 0xB8/0xB9/0xBA/0xBB: MAC/MPY families ------------------- */
case 0xb8:
case 0xb9:
case 0xba:
case 0xbb: op->type = RZ_ANALYSIS_OP_TYPE_MUL; break;
case 0xbc:
op->type = RZ_ANALYSIS_OP_TYPE_MOV;
break; /* BFXTR */
/* ---- 0xC1/0xC3/0xC5: AND/OR/XOR long-imm ---------------------
* For 0xC5 specifically, bit 7 of buf[1] selects XOR (=1);
* if not XOR, bit 7 of buf[2] picks OR (=1) over AND (=0). The
* 0xC1 and 0xC3 slots are AND / OR variants for different
* addressing forms (mem vs ACx) -- they have only one operation. */
case 0xc1: op->type = RZ_ANALYSIS_OP_TYPE_AND; break;
case 0xc3: op->type = RZ_ANALYSIS_OP_TYPE_OR; break;
case 0xc5:
if (ins_len >= 3 && (buf[1] & 0x80)) {
op->type = RZ_ANALYSIS_OP_TYPE_XOR;
} else if (ins_len >= 3 && (buf[2] & 0x80)) {
op->type = RZ_ANALYSIS_OP_TYPE_OR;
} else {
op->type = RZ_ANALYSIS_OP_TYPE_AND;
}
break;
case 0xc6: op->type = RZ_ANALYSIS_OP_TYPE_MOV; break; /* BFXTR / BFXPA - bit-field extract / pack */
case 0xc7: op->type = RZ_ANALYSIS_OP_TYPE_MUL; break; /* MPYK */
case 0xc8:
case 0xc9:
case 0xca:
case 0xcb: op->type = RZ_ANALYSIS_OP_TYPE_MUL; break;
case 0xce: op->type = RZ_ANALYSIS_OP_TYPE_MUL; break; /* SQDST */
case 0xd1: op->type = RZ_ANALYSIS_OP_TYPE_MOV; break; /* COPY */
case 0xd2: op->type = RZ_ANALYSIS_OP_TYPE_SUB; break;
case 0xd4:
op->type = RZ_ANALYSIS_OP_TYPE_CMP;
break; /* MAXDIFF */
/* ---- 0xD8: BCC far-absolute (5-byte) ------------------------- */
case 0xd8:
if (ins_len >= 5) {
set_cjmp(op, addr, rz_read_at_be24(buf, 1));
}
break;
/* ---- 0xD9: CALLCC far-absolute (5-byte) ---------------------- */
case 0xd9:
if (ins_len >= 5) {
set_ccall(op, addr, rz_read_at_be24(buf, 1));
}
break;
/* ---- 0xDA-0xDF: BCC/BCCU register-compare conditional ------- */
case 0xda:
case 0xdb:
case 0xdc:
case 0xdd:
case 0xde:
case 0xdf:
/* 5-byte form: DA/DB ARx cmp RRx ll hh dd (16-bit signed disp).
* The target is encoded as a relative 16-bit displacement
* at bytes 3..4 in BE byte order. */
if (ins_len >= 5) {
const ut32 disp = rz_read_at_be16(buf, 3);
set_cjmp(op, addr, addr + ins_len + sign_extend(disp, 16));
}
break;
/* ---- 0xE0/0xE1/0xE2/0xE3/0xE8/0xE9/0xEC/0xED: MAC/MPY parallel */
case 0xe0:
case 0xe1:
case 0xe2:
case 0xe3:
case 0xe8:
case 0xe9:
case 0xec:
case 0xed: op->type = RZ_ANALYSIS_OP_TYPE_MUL; break;
case 0xea:
case 0xeb: op->type = RZ_ANALYSIS_OP_TYPE_LEA; break; /* AMAR parallel */
case 0xee: op->type = RZ_ANALYSIS_OP_TYPE_MUL; break; /* MPYK */
default:
/* MOV-family bytes 0x48-0x4F (mov #imm, mem) - common in
* real firmware, classified as MOV here. */
if (buf[0] >= 0x48 && buf[0] <= 0x4f) {
op->type = RZ_ANALYSIS_OP_TYPE_MOV;
break;
}
/* Memory<->register MOV cluster, byte 0x50-0x5F. Per the
* SWPU104 encoding map: 0x50 (mem <- ARx high), 0x51-0x53
* (mem <- ACx parts), 0x54-0x57 (COPY), 0x58 (mem -> ACx),
* 0x59 (mem<<16 -> ACx), 0x5A/0x5B (mem -> ACx halves),
* 0x5C (40-bit dbl mov), 0x5D (ACx >> 1 -> dbl mem). All
* MOV-family for analysis purposes; the 0x5C / 0x5D dbl
* forms access 4 bytes, the rest 2. */
if (buf[0] >= 0x50 && buf[0] <= 0x5f) {
op->type = RZ_ANALYSIS_OP_TYPE_MOV;
set_mem_width(op, (buf[0] == 0x5c || buf[0] == 0x5d) ? 4 : 2);
break;
}
/* AMAR family - address-modifying instructions (LEA-like). */
if (buf[0] == 0x62 || buf[0] == 0x63) {
op->type = RZ_ANALYSIS_OP_TYPE_LEA;
break;
}
/* MOV-family bytes 0x88, 0x8A: ACx <-> mem variants. */
if (buf[0] == 0x88 || buf[0] == 0x8a) {
op->type = RZ_ANALYSIS_OP_TYPE_MOV;
break;
}
/* 0x8C: ADD with carry, mem -> ACx. */
if (buf[0] == 0x8c) {
op->type = RZ_ANALYSIS_OP_TYPE_ADD;
break;
}
/* 0x97: dual-mem MOV. */
if (buf[0] == 0x97) {
op->type = RZ_ANALYSIS_OP_TYPE_MOV;
break;
}
/* 0xA0, 0xAC, 0xAD: MOV with parallel dual addressing or
* MOV #imm,ACx (long form). */
if (buf[0] == 0xa0 || buf[0] == 0xac || buf[0] == 0xad) {
op->type = RZ_ANALYSIS_OP_TYPE_MOV;
break;
}
/* 0xB4, 0xB5: MOV with rounding / shift. */
if (buf[0] == 0xb4 || buf[0] == 0xb5) {
op->type = RZ_ANALYSIS_OP_TYPE_MOV;
break;
}
/* 0xB6, 0xB7: ADD with shift (T-register or immediate). */
if (buf[0] == 0xb6 || buf[0] == 0xb7) {
op->type = RZ_ANALYSIS_OP_TYPE_ADD;
break;
}
/* 0xC0, 0xC2, 0xC4: ADD #k16 with optional shift. */
if (buf[0] == 0xc0 || buf[0] == 0xc2 || buf[0] == 0xc4) {
op->type = RZ_ANALYSIS_OP_TYPE_ADD;
break;
}
/* 0xCC: dual-instruction packed encoding (ADD :: MOV). The
* primary operation that affects control flow / data flow
* is the ADD, so classify as ADD. */
if (buf[0] == 0xcc) {
op->type = RZ_ANALYSIS_OP_TYPE_ADD;
break;
}
/* 0xD0: MOV ACx, dbl(*(#abs24)) -- 4-byte (dbl) memory move. */
if (buf[0] == 0xd0) {
op->type = RZ_ANALYSIS_OP_TYPE_MOV;
set_mem_width(op, 4);
break;
}
/* 0x2E, 0x2F: XCCPART predicated execute. */
if (buf[0] == 0x2e || buf[0] == 0x2f) {
op->type = RZ_ANALYSIS_OP_TYPE_CMP;
op->family = RZ_ANALYSIS_OP_FAMILY_CPU;
break;
}
/* 0x0B (ecopr__), 0x23 (estop_byte): pseudo opcodes
* specific to the Wrigley silicon. Used as emulation /
* coprocessor traps; classify as TRAP. */
if (buf[0] == 0x0b || buf[0] == 0x23) {
op->type = RZ_ANALYSIS_OP_TYPE_TRAP;
op->family = RZ_ANALYSIS_OP_FAMILY_CPU;
break;
}
/* Anything we have not catalogued: leave op->type at its
* default (NULL). We intentionally do NOT mark unknown
* leading bytes as ILL: many bytes in the 0x10..0x1f and
* 0x30..0x3f ranges are valid parallel-instruction prefixes
* (0x39 = MACK, etc.) that decode to multi-instruction
* forms only when paired with the right following bytes.
* Flagging them as ILL would mislead the basic-block
* walker and the colorizer, which treats ILL as
* "definitely-invalid" and renders the opcode in bold
* red. Leaving as NULL lets the disassembler's own
* "invalid" rendering speak for itself per-instruction. */
break;
}
return op->size;
}

View file

@ -1,4 +1,5 @@
// SPDX-FileCopyrightText: 2024 deroad <wargio@libero.it>
// SPDX-FileCopyrightText: 2026 RizinOrg <info@rizin.re>
// SPDX-License-Identifier: LGPL-3.0-only
#ifndef ANALYSIS_C55_PLUS_H
@ -6,6 +7,7 @@
#include <rz_analysis.h>
int tms320_c55x_plus_op(RzAnalysis *analysis, RzAnalysisOp *op, ut64 addr, const ut8 *buf, int len);
int tms320_c55x_plus_op(RzAnalysis *analysis, RzAnalysisOp *op, ut64 addr,
const ut8 *buf, int len);
#endif /* ANALYSIS_C55_PLUS_H */

View file

@ -0,0 +1,46 @@
// SPDX-FileCopyrightText: 2013 th0rpe <josediazfer@yahoo.es>
// SPDX-License-Identifier: LGPL-3.0-only
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "utils.h"
static char hex_str[] = "01234567890abcdef";
// TODO: Add in a Coverity modelling file
char *strcat_dup(char *s1, char *s2, st32 n_free) {
char *res;
ut32 len_s1 = s1 ? strlen(s1) : 0;
ut32 len_s2 = s2 ? strlen(s2) : 0;
if (!(res = (char *)malloc(len_s1 + len_s2 + 1))) {
return NULL;
}
if (len_s1 > 0) {
memcpy(res, s1, len_s1);
}
if (len_s2 > 0) {
memcpy(res + len_s1, s2, len_s2);
}
res[len_s1 + len_s2] = '\0';
if (n_free == 1) {
RZ_FREE(s1);
} else if (n_free == 2) {
RZ_FREE(s2);
} else if (n_free == 3) {
RZ_FREE(s1);
RZ_FREE(s2);
}
return res;
}
char *get_hex_str(ut32 hex_num) {
char aux[3];
aux[2] = '\0';
aux[1] = hex_str[hex_num & 0xF];
aux[0] = hex_str[(hex_num >> 4) & 0xF];
return rz_str_dup(aux);
}

View file

@ -0,0 +1,14 @@
// SPDX-FileCopyrightText: 2013-2021 th0rpe <josediazfer@yahoo.es>
// SPDX-License-Identifier: LGPL-3.0-only
#ifndef UUTILS_H
#define UUTILS_H
#include <rz_types.h>
#include <rz_util.h>
#define C55PLUS_DEBUG 0
char *strcat_dup(char *s1, char *s2, st32 n_free);
char *get_hex_str(ut32 hex_num);
#endif

View file

@ -0,0 +1,75 @@
// SPDX-FileCopyrightText: 2026 RizinOrg <info@rizin.re>
// SPDX-License-Identifier: LGPL-3.0-only
#ifndef RZ_TMS320_DWARF_REGNUM_TABLE_H
#define RZ_TMS320_DWARF_REGNUM_TABLE_H
/* DWARF register numbering for TMS320 cores, as emitted by TI
* Code Composer Studio (CCS) v5+ tools.
*
* The numbering below covers TMS320C55x and TMS320C55x+ (extended
* .L/.H/.G sub-register views) as used by the cl55 compiler. The
* baseline mapping comes from TI's published cgt55 ABI tables; the
* .H / .G aliases sit at the same DWARF number as the .L view since
* the compiler always emits the .L name for scalar accesses.
*
* The mapping is intentionally conservative -- entries beyond the
* range CCSv5 actually emits return NULL so the caller surfaces a
* "dummy" register name rather than confidently picking the wrong
* one. */
static const char *const map_dwarf_reg_to_tms320_c55x[] = {
[0] = "ac0",
[1] = "ac1",
[2] = "ac2",
[3] = "ac3",
[4] = "t0",
[5] = "t1",
[6] = "t2",
[7] = "t3",
[8] = "ar0",
[9] = "ar1",
[10] = "ar2",
[11] = "ar3",
[12] = "ar4",
[13] = "ar5",
[14] = "ar6",
[15] = "ar7",
[16] = "sp",
[17] = "ssp",
[18] = "cdp",
[19] = "bk03",
[20] = "bk47",
[21] = "bkc",
[22] = "dp",
[23] = "pdp",
[24] = "csr",
[25] = "brc0",
[26] = "brc1",
[27] = "trn0",
[28] = "trn1",
[29] = "rptc",
[30] = "ier0",
[31] = "ier1",
[32] = "ifr0",
[33] = "ifr1",
[34] = "dbier0",
[35] = "dbier1",
[36] = "ivpd",
[37] = "ivph",
[38] = "st0_55",
[39] = "st1_55",
[40] = "st2_55",
[41] = "st3_55",
};
#define TMS320_C55X_REG_MAX (sizeof(map_dwarf_reg_to_tms320_c55x) / sizeof(map_dwarf_reg_to_tms320_c55x[0]))
static inline const char *tms320_c55x_register_name(ut32 reg_num) {
if (reg_num < TMS320_C55X_REG_MAX) {
return map_dwarf_reg_to_tms320_c55x[reg_num];
}
return NULL;
}
#endif /* RZ_TMS320_DWARF_REGNUM_TABLE_H */

View file

@ -313,6 +313,7 @@ arch_isa_sources = [
'isa/sparc/sparc_il.c',
'isa/sparc/sparc_il_ops.c',
'isa/spc700/spc700dis.c',
'isa/tms320/c55x/c55x_analysis.c',
'isa/tms320/c55x_plus/c55plus.c',
'isa/tms320/c55x_plus/c55plus_analysis.c',
'isa/tms320/c55x_plus/c55plus_decode.c',

View file

@ -1,75 +1,19 @@
// SPDX-FileCopyrightText: 2024 deroad <wargio@libero.it>
// SPDX-FileCopyrightText: 2014 Ilya V. Matveychikov <i.matveychikov@milabs.ru>
// SPDX-FileCopyrightText: 2026 RizinOrg <info@rizin.re>
// SPDX-License-Identifier: LGPL-3.0-only
#include <rz_analysis.h>
#include <tms320/tms320_dasm.h>
#include <tms320/c55x/c55x_analysis.h>
#include <tms320/c55x_plus/c55plus_analysis.h>
#include <tms320/c64x/c64x.h>
typedef struct tms320_ctx_t {
void *c64x;
tms320_dasm_t engine;
} Tms320Context;
static bool match(const char *str, const char *token) {
return !strncasecmp(str, token, strlen(token));
}
static int tms320_c55x_op(RzAnalysis *analysis, RzAnalysisOp *op, ut64 addr, const ut8 *buf, int len, tms320_dasm_t *engine) {
const char *str = engine->syntax;
op->delay = 0;
op->size = tms320_dasm(engine, buf, len);
op->type = RZ_ANALYSIS_OP_TYPE_NULL;
str = strstr(str, "||") ? str + 3 : str;
if (match(str, "B ")) {
op->type = RZ_ANALYSIS_OP_TYPE_JMP;
if (match(str, "B AC")) {
op->type = RZ_ANALYSIS_OP_TYPE_UJMP;
}
} else if (match(str, "BCC ") || match(str, "BCCU ")) {
op->type = RZ_ANALYSIS_OP_TYPE_CJMP;
} else if (match(str, "CALL ")) {
op->type = RZ_ANALYSIS_OP_TYPE_CALL;
if (match(str, "CALL AC")) {
op->type = RZ_ANALYSIS_OP_TYPE_UCALL;
}
} else if (match(str, "CALLCC ")) {
op->type = RZ_ANALYSIS_OP_TYPE_CCALL;
} else if (match(str, "RET")) {
op->type = RZ_ANALYSIS_OP_TYPE_RET;
if (match(str, "RETCC")) {
op->type = RZ_ANALYSIS_OP_TYPE_CRET;
}
} else if (match(str, "MOV ")) {
op->type = RZ_ANALYSIS_OP_TYPE_MOV;
} else if (match(str, "PSHBOTH ")) {
op->type = RZ_ANALYSIS_OP_TYPE_UPUSH;
} else if (match(str, "PSH ")) {
op->type = RZ_ANALYSIS_OP_TYPE_PUSH;
} else if (match(str, "POPBOTH ") || match(str, "POP ")) {
op->type = RZ_ANALYSIS_OP_TYPE_POP;
} else if (match(str, "CMP ")) {
op->type = RZ_ANALYSIS_OP_TYPE_CMP;
} else if (match(str, "CMPAND ")) {
op->type = RZ_ANALYSIS_OP_TYPE_ACMP;
} else if (match(str, "NOP")) {
op->type = RZ_ANALYSIS_OP_TYPE_NOP;
} else if (match(str, "INTR ")) {
op->type = RZ_ANALYSIS_OP_TYPE_SWI;
} else if (match(str, "TRAP ")) {
op->type = RZ_ANALYSIS_OP_TYPE_TRAP;
} else if (match(str, "INVALID")) {
op->type = RZ_ANALYSIS_OP_TYPE_UNK;
}
return op->size;
}
int tms320_analysis_op(RzAnalysis *analysis, RzAnalysisOp *op, ut64 addr, const ut8 *buf, int len, RzAnalysisOpMask mask) {
Tms320Context *context = (Tms320Context *)analysis->plugin_data;
@ -79,7 +23,7 @@ int tms320_analysis_op(RzAnalysis *analysis, RzAnalysisOp *op, ut64 addr, const
} else if (cpu && rz_str_casecmp(cpu, "c64x") == 0) {
return tms320_c64x_op(analysis, op, addr, buf, len, mask, context->c64x);
}
return tms320_c55x_op(analysis, op, addr, buf, len, &context->engine);
return tms320_c55x_op_byte(analysis, op, addr, buf, len);
}
static bool tms320_analysis_init(void **user) {
@ -89,7 +33,6 @@ static bool tms320_analysis_init(void **user) {
}
context->c64x = tms320_c64x_new();
tms320_dasm_init(&context->engine);
*user = context;
return true;
}
@ -99,7 +42,6 @@ static bool tms320_analysis_fini(void *user) {
Tms320Context *context = (Tms320Context *)user;
tms320_c64x_free(context->c64x);
tms320_dasm_fini(&context->engine);
free(context);
return true;
}
@ -194,7 +136,7 @@ static char *get_reg_profile(RZ_BORROW RzAnalysis *a) {
"ctr xssp .23 569 0 # Extended system stack pointer\n";
} else {
p =
"=PC pc\n"
"=PC pce1\n"
"=A0 a4\n"
"=A1 b4\n"
"=A2 a6\n"

View file

@ -0,0 +1,513 @@
NAME=c55x+ analysis: register profile (PC alias resolves)
FILE==
CMDS=<<EOF
e asm.arch=tms320
e analysis.cpu=c55x+
arp~^=PC
EOF
EXPECT=<<EOF
=PC pc
EOF
RUN
NAME=c55x+ analysis: opcode classification (batched)
FILE=malloc://64
CMDS=<<EOF
e asm.arch=tms320
e analysis.cpu=c55x+
wx 20 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^size
wx 21 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^size
wx 2021 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^size
ao 1 @ 1~^type
ao 1 @ 0~^size
wx 68002d @ 0
ao 1 @ 0~^type
ao 1 @ 0~^jump
wx 690028 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^jump
ao 1 @ 0~^fail
wx 6a2320 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^jump
ao 1 @ 0~^fail
wx 9a000020 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^jump
ao 1 @ 0~^fail
wx 9b000020 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^jump
wx 9c000020 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^jump
wx 9d000020 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^jump
wx d800000020 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^jump
ao 1 @ 0~^fail
wx d900000020 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^jump
wx dba0210010 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^fail
wx 0240 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^fail
wx 0280 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^fail
wx 740020 @ 0
ao 1 @ 0~^type
wx 7b0001 @ 0
ao 1 @ 0~^type
wx 740081 @ 0
ao 1 @ 0~^type
wx 770030 @ 0
ao 1 @ 0~^type
wx c5000000ff @ 0
ao 1 @ 0~^type
wx c50181000f @ 0
ao 1 @ 0~^type
wx c5a3230001 @ 0
ao 1 @ 0~^type
wx a4323300 @ 0
ao 1 @ 0~^type
wx a6818133 @ 0
ao 1 @ 0~^type
wx a7810131 @ 0
ao 1 @ 0~^type
wx c832803400 @ 0
ao 1 @ 0~^type
wx c832003400 @ 0
ao 1 @ 0~^type
wx 2461e400 @ 0
ao 1 @ 0~^type
wx 713233 @ 0
ao 1 @ 0~^type
wx 0d25 @ 0
ao 1 @ 0~^type
wx 621240 @ 0
ao 1 @ 0~^type
wx ae21000b @ 0
ao 1 @ 0~^type
wx 760000 @ 0
ao 1 @ 0~^type
wx 760080 @ 0
ao 1 @ 0~^type
wx 768100 @ 0
ao 1 @ 0~^type
wx 768180 @ 0
ao 1 @ 0~^type
wx 0020 @ 0
ao 1 @ 0~^type
wx 00c0 @ 0
ao 1 @ 0~^type
wx 0305 @ 0
ao 1 @ 0~^type
wx 0344 @ 0
ao 1 @ 0~^type
wx 0a2a @ 0
ao 1 @ 0~^type
wx 0a0a @ 0
ao 1 @ 0~^type
wx ff @ 0
ao 1 @ 0~^type
wx 6a0500200000000000 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^jump
ao 1 @ 0~^fail
wx 0380 @ 0
ao 1 @ 0~^type
wx 03c0 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^family
wx 035f @ 0
ao 1 @ 0~^type
ao 1 @ 0~^val
wx 5811223344 @ 0
ao 1 @ 0~^type
wx ac010005 @ 0
ao 1 @ 0~^type
wx b401020304 @ 0
ao 1 @ 0~^type
wx b600112233 @ 0
ao 1 @ 0~^type
wx 8c80112233 @ 0
ao 1 @ 0~^type
wx c000010002 @ 0
ao 1 @ 0~^type
wx d000abcd12 @ 0
ao 1 @ 0~^type
wx 2e10 @ 0
ao 1 @ 0~^type
wx 0b11 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^family
wx 2300 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^family
wx c6112233 @ 0
ao 1 @ 0~^type
wx 0380 @ 0
ao 1 @ 0~^type
wx 03c0 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^family
wx 035f @ 0
ao 1 @ 0~^type
ao 1 @ 0~^val
wx 5811223344 @ 0
ao 1 @ 0~^type
wx ac010005 @ 0
ao 1 @ 0~^type
wx b401020304 @ 0
ao 1 @ 0~^type
wx b600112233 @ 0
ao 1 @ 0~^type
wx 8c80112233 @ 0
ao 1 @ 0~^type
wx c000010002 @ 0
ao 1 @ 0~^type
wx d000abcd12 @ 0
ao 1 @ 0~^type
wx 2e10 @ 0
ao 1 @ 0~^type
wx 0b11 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^family
wx 2300 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^family
wx c6112233 @ 0
ao 1 @ 0~^type
EOF
EXPECT=<<EOF
type: nop
size: 1
type: ret
size: 1
type: nop
size: 1
type: ret
size: 1
type: jmp
jump: 0x00000030
type: call
jump: 0x0000002b
fail: 0x00000003
type: cjmp
jump: 0x00000026
fail: 0x00000003
type: cjmp
jump: 0x00000004
fail: 0x00000004
type: ccall
jump: 0x00000004
type: jmp
jump: 0x00000020
type: call
jump: 0x00000020
type: cjmp
jump: 0x00000000
fail: 0x00000005
type: ccall
jump: 0x00000000
type: cjmp
fail: 0x00000005
type: ujmp
fail: 0x00000002
type: ucall
fail: 0x00000002
type: add
type: add
type: sub
type: mov
type: and
type: or
type: xor
type: cmp
type: shl
type: shl
type: mul
type: mul
type: push
type: pop
type: upush
type: lea
type: sub
type: null
type: sub
type: cmp
type: cmp
type: nop
type: ret
type: swi
type: trap
type: mov
type: mov
type: null
type: cjmp
jump: 0x00000008
fail: 0x00000003
type: xchg
type: trap
family: cpu
type: trap
val: 0x0000001f
type: mov
type: mov
type: mov
type: add
type: add
type: add
type: mov
type: cmp
type: trap
family: cpu
type: trap
family: cpu
type: mov
type: xchg
type: trap
family: cpu
type: trap
val: 0x0000001f
type: mov
type: mov
type: mov
type: add
type: add
type: add
type: mov
type: cmp
type: trap
family: cpu
type: trap
family: cpu
type: mov
EOF
RUN
NAME=c55x+ analysis (no debug): 18_full_program.obj -- 6-function callgraph, stack tracking
FILE=bins/tms320/coff2/18_full_program.obj
ARGS=-A
CMDS=<<EOF
iI~^arch
iI~^cpu
afl~?
afs @ sym._main
avgl~?
afb @ sym._main~?
afi @ sym._main~^num-bbs
afi @ sym._main~^stackframe
afi @ sym._main~^call-refs
EOF
EXPECT=<<EOF
arch tms320
cpu c55x+
6
int main(int argc, char **argv, char **envp);
0
1
num-bbs: 1
stackframe: 6
call-refs: 0x00000034 C 0x00000042 C 0x0000004c C
EOF
RUN
NAME=c55x+ analysis (no debug): 19_emulateme_nostd.obj -- multi-BB cyclomatic > 1
FILE=bins/tms320/coff2/19_emulateme_nostd.obj
ARGS=-A
CMDS=<<EOF
iI~^cpu
afl~?
afs @ sym._main
avgl~?
afb @ sym._main~?
afi @ sym._main~^num-bbs
afi @ sym._main~^stackframe
afi @ sym._main~^cyclomatic-complexity
EOF
EXPECT=<<EOF
cpu c55x+
5
int main(int argc, char **argv, char **envp);
0
5
num-bbs: 5
stackframe: 6
cyclomatic-complexity: 5
EOF
RUN
NAME=c55x+ analysis (no debug): 17_data_layout.obj -- section table + functions
FILE=bins/tms320/coff2/17_data_layout.obj
ARGS=-A
CMDS=<<EOF
iI~^cpu
afl~?
iSq~^0x~?
iSq~^0x~text~?
iSq~^0x~bss~?
iSq~^0x~const~?
avgl~?
EOF
EXPECT=<<EOF
cpu c55x+
4
5
1
1
1
0
EOF
RUN
NAME=c55x+ analysis (no debug): 15_fir_filter.obj -- pdf reflines and MAC classification
FILE=bins/tms320/coff2/15_fir_filter.obj
ARGS=-A
CMDS=<<EOF
iI~^cpu
afl~?
afs @ sym._fir_step
afb @ sym._fir_step~?
afi @ sym._fir_step~^cyclomatic-cost
afi @ sym._fir_step~^cyclomatic-complexity
EOF
EXPECT=<<EOF
cpu c55x+
4
void sym._fir_step();
1
cyclomatic-cost: 0
cyclomatic-complexity: 1
EOF
RUN
NAME=c55x+ analysis: RzAnalysisOp operand fields (reg / ireg / disp / direction)
FILE==
CMDS=<<EOF
e asm.arch=tms320
e analysis.cpu=c55x+
# B AC0 -- 0x02 with bit 7 clear, low 2 bits select AC; reg=none, ireg=ac0
wx 0200 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^ireg
ao 1 @ 0~^direction
# CALL AC2 -- 0x02 with bit 7 set; ireg=ac2, stackptr=2
wx 0282 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^ireg
ao 1 @ 0~^stackptr
ao 1 @ 0~^direction
# INTR 5 -- 0x03 with high 2 bits=00; val=5, family=cpu
wx 0305 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^val
ao 1 @ 0~^family
# TRAP 7 -- 0x03 with high 2 bits=01; val=7, family=cpu
wx 0347 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^val
ao 1 @ 0~^family
# RETCC -- 0x08; stackptr=-2, reg=sp
wx 0840 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^reg
ao 1 @ 0~^stackptr
# PSHBOTH -- 0x0d; reg=sp, direction=write, stackptr=2
wx 0d25 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^reg
ao 1 @ 0~^direction
ao 1 @ 0~^stackptr
# PSH dbl -- 0x0e; stackptr=2
wx 0e25 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^reg
ao 1 @ 0~^direction
ao 1 @ 0~^stackptr
# POP dbl -- 0x0f; stackptr=-2
wx 0f25 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^reg
ao 1 @ 0~^direction
ao 1 @ 0~^stackptr
# RET -- 0x21; reg=sp, stackptr=-2
wx 21 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^reg
ao 1 @ 0~^stackptr
# RETI -- 0x00 0xc0; reg=sp, stackptr=-2
wx 00c0 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^reg
ao 1 @ 0~^stackptr
EOF
EXPECT=<<EOF
type: ujmp
ireg: ac0
direction: exec
type: ucall
ireg: ac2
stackptr: 2
direction: exec
type: swi
val: 0x00000005
family: cpu
type: trap
val: 0x00000007
family: cpu
type: cret
reg: sp
stackptr: -2
type: upush
reg: sp
direction: write
stackptr: 2
type: push
reg: sp
direction: write
stackptr: 2
type: pop
reg: sp
direction: read
stackptr: -2
type: ret
reg: sp
stackptr: -2
type: ret
reg: sp
stackptr: -2
EOF
RUN
NAME=c55x+ analysis: stackframe tracking propagates to afi (high-level summariser picks up stackptr)
FILE=bins/tms320/coff2/16_compiler_style.obj
ARGS=-A
CMDS=<<EOF
# Each function's prologue/epilogue should produce the right stackframe.
# This verifies the analyzer's stackop/stackptr fields on AADD K8,SP
# (case 0x0c) and PSH/POP families reach the function summariser.
afi @ sym._w~^stackframe:0
afi @ sym._setup_locals~^stackframe:0
afi @ sym._three_arg_caller~^stackframe:0
afi @ sym._no_locals~^stackframe:0
EOF
EXPECT=<<EOF
stackframe: 0
stackframe: 2
stackframe: 6
stackframe: 0
EOF
RUN

View file

@ -0,0 +1,698 @@
NAME=c55x analysis: register profile (PC alias resolves)
FILE==
CMDS=<<EOF
e asm.arch=tms320
e analysis.cpu=c55x
arp~^=PC
EOF
EXPECT=<<EOF
=PC pc
EOF
RUN
NAME=c55x analysis: opcode classification (batched)
FILE=malloc://64
CMDS=<<EOF
e asm.arch=tms320
e analysis.cpu=c55x
wx 20 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^size
wx 21 @ 0
ao 1 @ 0~^type
wx 040500 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^jump
ao 1 @ 0~^fail
wx 060020 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^jump
wx 080020 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^jump
ao 1 @ 0~^stackptr
wx 0c1020 @ 0
ao 1 @ 0~^type
wx 0e0020 @ 0
ao 1 @ 0~^type
wx 4804 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^stackptr
wx 4805 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^stackptr
wx 4800 @ 0
ao 1 @ 0~^type
wx 020405 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^stackptr
ao 1 @ 0~^fail
wx 4a10 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^jump
wx 6005 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^jump
ao 1 @ 0~^fail
wx 6a102030 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^jump
wx 6b102030 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^jump
wx 6c102030 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^jump
ao 1 @ 0~^stackptr
wx 6e002000 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^stackptr
ao 1 @ 0~^fail
wx 6f002000 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^fail
wx 9120 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^fail
wx 9220 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^stackptr
ao 1 @ 0~^fail
wx 9400 @ 0
ao 1 @ 0~^type
wx 950f @ 0
ao 1 @ 0~^type
ao 1 @ 0~^val
wx 958f @ 0
ao 1 @ 0~^type
ao 1 @ 0~^val
wx 9601 @ 0
ao 1 @ 0~^type
wx 3800 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^stackptr
wx 3a00 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^stackptr
wx 030405 @ 0
ao 1 @ 0~^type
wx 050500 @ 0
ao 1 @ 0~^type
wx 070020 @ 0
ao 1 @ 0~^type
wx 090020 @ 0
ao 1 @ 0~^type
wx 69102030 @ 0
ao 1 @ 0~^type
wx 2410 @ 0
ao 1 @ 0~^type
wx 2610 @ 0
ao 1 @ 0~^type
wx 2210 @ 0
ao 1 @ 0~^type
wx 2810 @ 0
ao 1 @ 0~^type
wx 2a10 @ 0
ao 1 @ 0~^type
wx 2c10 @ 0
ao 1 @ 0~^type
wx 2e10 @ 0
ao 1 @ 0~^type
wx 3010 @ 0
ao 1 @ 0~^type
wx 3410 @ 0
ao 1 @ 0~^type
wx 3610 @ 0
ao 1 @ 0~^type
wx 122030 @ 0
ao 1 @ 0~^type
wx 5010 @ 0
ao 1 @ 0~^type
wx 5610 @ 0
ao 1 @ 0~^type
wx 5810 @ 0
ao 1 @ 0~^type
wx 5e10 @ 0
ao 1 @ 0~^type
wx 85101020 @ 0
ao 1 @ 0~^type
wx a01122 @ 0
ao 1 @ 0~^type
wx ac1122 @ 0
ao 1 @ 0~^type
wx c01122 @ 0
ao 1 @ 0~^type
wx cc1122 @ 0
ao 1 @ 0~^type
wx 20 @ 0
ao 1 @ 0~^type
wx 21 @ 0
ao 1 @ 0~^type
wx 040500 @ 0
ao 1 @ 0~^type
wx 060020 @ 0
ao 1 @ 0~^type
wx 080020 @ 0
ao 1 @ 0~^type
wx 0c1020 @ 0
ao 1 @ 0~^type
wx 0e0020 @ 0
ao 1 @ 0~^type
wx 4cff @ 0
ao 1 @ 0~^type
wx 4804 @ 0
ao 1 @ 0~^type
wx 38 @ 0
ao 1 @ 0~^type
wx 3a @ 0
ao 1 @ 0~^type
wx 6010 @ 0
ao 1 @ 0~^type
wx 6a010203 @ 0
ao 1 @ 0~^type
wx 6b010203 @ 0
ao 1 @ 0~^type
wx 6c010203 @ 0
ao 1 @ 0~^type
wx 91 @ 0
ao 1 @ 0~^type
wx 92 @ 0
ao 1 @ 0~^type
wx 93 @ 0
ao 1 @ 0~^type
wx a000 @ 0
ao 1 @ 0~^type
wx a500 @ 0
ao 1 @ 0~^type
wx c000 @ 0
ao 1 @ 0~^type
wx c500 @ 0
ao 1 @ 0~^type
wx 7a01 @ 0
ao 1 @ 0~^type
wx 7b00 @ 0
ao 1 @ 0~^type
wx 7c01 @ 0
ao 1 @ 0~^type
wx 8100 @ 0
ao 1 @ 0~^type
wx 581234 @ 0
ao 1 @ 0~^type
wx 821234 @ 0
ao 1 @ 0~^type
wx d01234 @ 0
ao 1 @ 0~^type
wx 5000 @ 0
ao 1 @ 0~^type
wx 3600 @ 0
ao 1 @ 0~^type
wx 3400 @ 0
ao 1 @ 0~^type
wx 2e00 @ 0
ao 1 @ 0~^type
wx 3000 @ 0
ao 1 @ 0~^type
wx 5e00 @ 0
ao 1 @ 0~^type
wx 76020304 @ 0
ao 1 @ 0~^type
wx 77020304 @ 0
ao 1 @ 0~^type
wx 85020304 @ 0
ao 1 @ 0~^type
wx 6c00ff00 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^jump
ao 1 @ 0~^stackptr
wx 6a012345 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^jump
wx 4804 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^stackptr
wx 38 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^stackptr
wx 3a @ 0
ao 1 @ 0~^type
ao 1 @ 0~^stackptr
EOF
EXPECT=<<EOF
type: nop
size: 1
type: nop
type: cjmp
jump: 0x00000008
fail: 0x00000003
type: jmp
jump: 0x00000023
type: call
jump: 0x00000023
stackptr: 2
type: rep
type: rep
type: ret
stackptr: -2
type: ret
stackptr: -2
type: rep
type: cret
stackptr: -2
fail: 0x00000003
type: jmp
jump: 0x00000012
type: cjmp
jump: 0x00000007
fail: 0x00000002
type: jmp
jump: 0x00102030
type: jmp
jump: 0x00102030
type: call
jump: 0x00102030
stackptr: 2
type: ccall
stackptr: 2
fail: 0x00000004
type: cjmp
fail: 0x00000004
type: ujmp
fail: 0x00000002
type: ucall
stackptr: 2
fail: 0x00000002
type: trap
type: swi
val: 0x0000000f
type: trap
val: 0x0000000f
type: cmp
type: push
stackptr: 1
type: pop
stackptr: -1
type: cret
type: cjmp
type: jmp
type: call
type: null
type: add
type: sub
type: mov
type: and
type: or
type: xor
type: cmp
type: cmp
type: sub
type: not
type: cmp
type: shl
type: mul
type: mul
type: xchg
type: lea
type: mov
type: mov
type: mov
type: mov
type: nop
type: nop
type: cjmp
type: jmp
type: call
type: rep
type: rep
type: rep
type: ret
type: push
type: pop
type: cjmp
type: jmp
type: jmp
type: call
type: ujmp
type: ucall
type: ucall
type: mov
type: mov
type: mov
type: mov
type: add
type: add
type: sub
type: add
type: mul
type: mul
type: mul
type: shl
type: not
type: sub
type: cmp
type: cmp
type: xchg
type: mov
type: lea
type: lea
type: call
jump: 0x0000ff00
stackptr: 2
type: jmp
jump: 0x00012345
type: ret
stackptr: -2
type: push
stackptr: 1
type: pop
stackptr: -1
EOF
RUN
NAME=c55x analysis: 10_full_program.o
FILE=bins/tms320/coff1/10_full_program.o
CMDS=<<EOF
iI~^arch
iI~^cpu
iI~^bintype
aaa
afl~?
afl~sym._main$~?
afl~sym._iir_lowpass$~?
afl~sym._fir_lowpass$~?
afl~sym._compress$~?
afl~sym._timer_isr$~?
EOF
EXPECT=<<EOF
arch tms320
cpu c54x
bintype coff
10
1
1
1
1
1
EOF
RUN
NAME=c55x binary fixture: c54x 09_data_layout.o -- section table (.bss / .data discovered)
FILE=bins/tms320/coff1/09_data_layout.o
CMDS=<<EOF
iI~^arch
iI~^cpu
iSq~^0x~?
EOF
EXPECT=<<EOF
arch tms320
cpu c54x
7
EOF
RUN
NAME=c55x binary fixture: c54x 04_fir_filter.o -- FIR functions discovered
FILE=bins/tms320/coff1/04_fir_filter.o
CMDS=<<EOF
iI~^arch
iI~^cpu
aaa
afl~?
EOF
EXPECT=<<EOF
arch tms320
cpu c54x
6
EOF
RUN
NAME=c55x analysis (with debug): emulateme c55x dbg.coff -- DWARF args/globals/sigs/bbs/pdf
FILE=bins/tms320/emulateme_nostd.ccsv5.c55x.ticoff2.dbg.coff
ARGS=-A
CMDS=<<EOF
iI~^arch
iI~^cpu
afl~?
afs @ dbg.main
afvl @ dbg.main
avgl~?
avgl~uart_address
avgl~seckrit
afb @ dbg.main~?
afi @ dbg.main~^num-bbs
afi @ dbg.main~^stackframe
afi @ dbg.main~^args
pdf @ sym._decrypt~int sym
pdf @ sym._decrypt~call
EOF
EXPECT=<<EOF
arch tms320
cpu c55x
14
int main(int argc);
arg int argc @ ar4
7
global nstd_uint8_t * uart_address @ 0x800000
global nstd_uint8_t [17] seckrit @ 0x4800000
3
num-bbs: 3
stackframe: 7
args: 1
/ int sym._decrypt();
| 0x000001db call 0xFE52 ; fcn.00000030
EOF
RUN
NAME=c55x analysis (with debug): emulateme c55x rel.coff -- DWARF still works on relocatable
FILE=bins/tms320/emulateme_nostd.ccsv5.c55x.ticoff2.rel.coff
ARGS=-A
CMDS=<<EOF
iI~^cpu
afl~?
afs @ dbg.main
afvl @ dbg.main
avgl~?
afi @ dbg.main~^stackframe
afi @ dbg.main~^args
EOF
EXPECT=<<EOF
cpu c55x
13
int main(int argc);
arg int argc @ ar4
7
stackframe: 6
args: 1
EOF
RUN
NAME=c55x analysis (with debug): c55xp dbg.coff loaded as c55x -- DWARF still works
FILE=bins/tms320/emulateme_nostd.ccsv5.c55xp.ticoff2.dbg.coff
ARGS=-A
CMDS=<<EOF
iI~^cpu
afl~?
afs @ dbg.main
afvl @ dbg.main
avgl~?
afi @ dbg.main~^args
EOF
EXPECT=<<EOF
cpu c55x
13
int main(int argc);
arg int argc @ ar4
7
args: 1
EOF
RUN
NAME=c55x analysis (no debug): emulateme c55x dbg.stripped.coff -- no DWARF args/globals
FILE=bins/tms320/emulateme_nostd.ccsv5.c55x.ticoff2.dbg.stripped.coff
ARGS=-A
CMDS=<<EOF
iI~^cpu
afl~?
afs @ sym._main
afvl @ sym._main
avgl~?
afb @ sym._main~?
afi @ sym._main~^num-bbs
afi @ sym._main~^stackframe
afi @ sym._main~^call-refs
EOF
EXPECT=<<EOF
cpu c55x
14
int main(int argc, char **argv, char **envp);
0
1
num-bbs: 1
stackframe: 2
call-refs: 0x00000235 C
EOF
RUN
NAME=c55x analysis (no debug): emulateme c55x rel.stripped.coff -- no DWARF args/globals
FILE=bins/tms320/emulateme_nostd.ccsv5.c55x.ticoff2.rel.stripped.coff
ARGS=-A
CMDS=<<EOF
iI~^cpu
afs @ sym._main
afvl @ sym._main
avgl~?
afb @ sym._main~?
afi @ sym._main~^stackframe
afi @ sym._main~^call-refs
EOF
EXPECT=<<EOF
cpu c55x
int main(int argc, char **argv, char **envp);
0
3
stackframe: 14
call-refs: 0x000002bb C 0x00000364 C
EOF
RUN
NAME=c55x analysis (no debug): c55xp dbg.stripped.coff -- no DWARF, byte-driven still works
FILE=bins/tms320/emulateme_nostd.ccsv5.c55xp.ticoff2.dbg.stripped.coff
ARGS=-A
CMDS=<<EOF
iI~^cpu
afl~?
afs @ sym._main
avgl~?
afi @ sym._main~^stackframe:0
EOF
EXPECT=<<EOF
cpu c55x
13
int main(int argc, char **argv, char **envp);
0
stackframe: 2
EOF
RUN
NAME=c55x analysis: RzAnalysisOp operand fields (reg / ireg / disp / direction)
FILE==
CMDS=<<EOF
e asm.arch=tms320
e analysis.cpu=c55x
# AADD K8, SP -- frame adjustment; reg=sp, disp=K8, stackop=inc, stackptr=K8
wx 4e08 @ 0
ao 1 @ 0~^val
ao 1 @ 0~^disp
ao 1 @ 0~^reg
ao 1 @ 0~^stackop
ao 1 @ 0~^stackptr
# B AC1 -- indirect branch; ireg=ac1, direction=exec
wx 9101 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^ireg
# CALL AC2 -- indirect call; ireg=ac2, direction=exec, stackptr=2
wx 9202 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^ireg
ao 1 @ 0~^stackptr
# AADD ac0, ar3 -- register-to-register; reg=ar3 (dst), ireg=ac0 (src)
wx 1400b0 @ 0
ao 1 @ 0~^reg
ao 1 @ 0~^ireg
# MOV *sp(#0x10), ac1 -- LOAD; reg=ac1, ireg=sp, disp=0x10, direction=read, refptr=2
wx a120 @ 0
ao 1 @ 0~^reg
ao 1 @ 0~^ireg
ao 1 @ 0~^disp
ao 1 @ 0~^direction
ao 1 @ 0~^refptr
# MOV ar3, *sp(#0x10) -- STORE; reg=ar3, ireg=sp, disp=0x10, direction=write
wx cb20 @ 0
ao 1 @ 0~^reg
ao 1 @ 0~^ireg
ao 1 @ 0~^disp
ao 1 @ 0~^direction
# PSH dbl -- 0xb7; stackptr=2 (dbl word push)
wx b700 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^reg
ao 1 @ 0~^direction
ao 1 @ 0~^stackptr
# POP single -- 0xbb; stackptr=-1
wx bb00 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^reg
ao 1 @ 0~^direction
ao 1 @ 0~^stackptr
# B 0x100 -- 24-bit absolute branch; direction=exec
wx 6a000100 @ 0
ao 1 @ 0~^type
ao 1 @ 0~^jump
ao 1 @ 0~^direction
# INTR 0xA -- SWI; val=0xA, family=cpu
wx 950a @ 0
ao 1 @ 0~^type
ao 1 @ 0~^val
ao 1 @ 0~^family
EOF
EXPECT=<<EOF
val: 0x00000008
disp: 0x00000008
reg: sp
stackop: inc
stackptr: -8
type: ujmp
ireg: ac1
type: ucall
ireg: ac2
stackptr: 2
reg: ar3
ireg: ac0
reg: ac1
ireg: sp
disp: 0x00000010
direction: read
refptr: 2
reg: ar3
ireg: sp
disp: 0x00000010
direction: write
type: push
reg: sp
direction: write
stackptr: 2
type: pop
reg: sp
direction: read
stackptr: -1
type: jmp
jump: 0x00000100
direction: exec
type: swi
val: 0x0000000a
family: cpu
EOF
RUN
NAME=c55x analysis: stackframe tracking propagates to afi (high-level summariser picks up stackptr)
FILE=bins/tms320/emulateme_nostd.ccsv5.c55x.ticoff2.dbg.coff
ARGS=-A
CMDS=<<EOF
# Each function with a prologue should have a non-zero stackframe.
# This verifies the analyzer's stackop/stackptr fields are reaching afi.
afi @ dbg.main~^stackframe:0
afi @ sym._decrypt~^stackframe:0
afi @ sym._c_strlen~^stackframe:0
afi @ sym._uart_write_hex~^stackframe:0
# afi shows the dbg.main args via afvl integration (which reads op->reg
# of the AADD K8, SP instruction to identify the prologue frame size)
afi @ dbg.main~^args
afi @ dbg.main~^locals
EOF
EXPECT=<<EOF
stackframe: 7
stackframe: 0
stackframe: 4
stackframe: 2
args: 1
locals: 0
EOF
RUN