From 4221d56cb9a717d9546ccddae6f8302aeb437aa5 Mon Sep 17 00:00:00 2001 From: Anton Kochkov Date: Wed, 27 May 2026 05:37:17 +0000 Subject: [PATCH] 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. --- librz/arch/dwarf_process.c | 5 + librz/arch/isa/tms320/c55x/c55x_analysis.c | 733 +++++++++++++++ librz/arch/isa/tms320/c55x/c55x_analysis.h | 14 + .../isa/tms320/c55x_plus/c55plus_analysis.c | 834 ++++++++++++++++-- .../isa/tms320/c55x_plus/c55plus_analysis.h | 4 +- librz/arch/isa/tms320/c55x_plus/utils.c | 46 + librz/arch/isa/tms320/c55x_plus/utils.h | 14 + .../isa/tms320/tms320_dwarf_regnum_table.h | 75 ++ librz/arch/meson.build | 1 + librz/arch/p/analysis/analysis_tms320.c | 66 +- test/db/analysis/tms320.c55x+_32 | 513 +++++++++++ test/db/analysis/tms320.c55x_32 | 698 +++++++++++++++ 12 files changed, 2886 insertions(+), 117 deletions(-) create mode 100644 librz/arch/isa/tms320/c55x/c55x_analysis.c create mode 100644 librz/arch/isa/tms320/c55x/c55x_analysis.h create mode 100644 librz/arch/isa/tms320/c55x_plus/utils.c create mode 100644 librz/arch/isa/tms320/c55x_plus/utils.h create mode 100644 librz/arch/isa/tms320/tms320_dwarf_regnum_table.h create mode 100644 test/db/analysis/tms320.c55x+_32 create mode 100644 test/db/analysis/tms320.c55x_32 diff --git a/librz/arch/dwarf_process.c b/librz/arch/dwarf_process.c index 3e19359dec..417a31ffff 100644 --- a/librz/arch/dwarf_process.c +++ b/librz/arch/dwarf_process.c @@ -480,6 +480,7 @@ static const char *map_dwarf_reg_to_riscv_reg(ut32 reg_num) { #include #include #include +#include /** * \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; } diff --git a/librz/arch/isa/tms320/c55x/c55x_analysis.c b/librz/arch/isa/tms320/c55x/c55x_analysis.c new file mode 100644 index 0000000000..65cdbfedfb --- /dev/null +++ b/librz/arch/isa/tms320/c55x/c55x_analysis.c @@ -0,0 +1,733 @@ +// SPDX-FileCopyrightText: 2014 Ilya V. Matveychikov +// SPDX-FileCopyrightText: 2014 montekki +// SPDX-FileCopyrightText: 2026 RizinOrg +// SPDX-License-Identifier: LGPL-3.0-only + +#include +#include +#include +#include + +#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 0 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; +} diff --git a/librz/arch/isa/tms320/c55x/c55x_analysis.h b/librz/arch/isa/tms320/c55x/c55x_analysis.h new file mode 100644 index 0000000000..03db81853d --- /dev/null +++ b/librz/arch/isa/tms320/c55x/c55x_analysis.h @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2014 Ilya V. Matveychikov +// SPDX-FileCopyrightText: 2014 montekki +// SPDX-FileCopyrightText: 2026 RizinOrg +// SPDX-License-Identifier: LGPL-3.0-only + +#ifndef ANALYSIS_C55X_H +#define ANALYSIS_C55X_H + +#include + +int tms320_c55x_op_byte(RzAnalysis *analysis, RzAnalysisOp *op, ut64 addr, + const ut8 *buf, int len); + +#endif /* ANALYSIS_C55X_H */ diff --git a/librz/arch/isa/tms320/c55x_plus/c55plus_analysis.c b/librz/arch/isa/tms320/c55x_plus/c55plus_analysis.c index 7c092e8269..cd48ec7919 100644 --- a/librz/arch/isa/tms320/c55x_plus/c55plus_analysis.c +++ b/librz/arch/isa/tms320/c55x_plus/c55plus_analysis.c @@ -1,80 +1,806 @@ -// SPDX-FileCopyrightText: 2014 montekki +// SPDX-FileCopyrightText: 2014 Ilya V. Matveychikov +// SPDX-FileCopyrightText: 2014 montekki +// SPDX-FileCopyrightText: 2026 RizinOrg // SPDX-License-Identifier: LGPL-3.0-only #include #include -#include -#include +#include #include #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; } diff --git a/librz/arch/isa/tms320/c55x_plus/c55plus_analysis.h b/librz/arch/isa/tms320/c55x_plus/c55plus_analysis.h index c892132751..be22ffa26f 100644 --- a/librz/arch/isa/tms320/c55x_plus/c55plus_analysis.h +++ b/librz/arch/isa/tms320/c55x_plus/c55plus_analysis.h @@ -1,4 +1,5 @@ // SPDX-FileCopyrightText: 2024 deroad +// SPDX-FileCopyrightText: 2026 RizinOrg // SPDX-License-Identifier: LGPL-3.0-only #ifndef ANALYSIS_C55_PLUS_H @@ -6,6 +7,7 @@ #include -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 */ diff --git a/librz/arch/isa/tms320/c55x_plus/utils.c b/librz/arch/isa/tms320/c55x_plus/utils.c new file mode 100644 index 0000000000..5f855ed166 --- /dev/null +++ b/librz/arch/isa/tms320/c55x_plus/utils.c @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: 2013 th0rpe +// SPDX-License-Identifier: LGPL-3.0-only + +#include +#include +#include +#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); +} diff --git a/librz/arch/isa/tms320/c55x_plus/utils.h b/librz/arch/isa/tms320/c55x_plus/utils.h new file mode 100644 index 0000000000..137ccc10f0 --- /dev/null +++ b/librz/arch/isa/tms320/c55x_plus/utils.h @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2013-2021 th0rpe +// SPDX-License-Identifier: LGPL-3.0-only + +#ifndef UUTILS_H +#define UUTILS_H + +#include +#include +#define C55PLUS_DEBUG 0 + +char *strcat_dup(char *s1, char *s2, st32 n_free); +char *get_hex_str(ut32 hex_num); + +#endif diff --git a/librz/arch/isa/tms320/tms320_dwarf_regnum_table.h b/librz/arch/isa/tms320/tms320_dwarf_regnum_table.h new file mode 100644 index 0000000000..ade8ddbd57 --- /dev/null +++ b/librz/arch/isa/tms320/tms320_dwarf_regnum_table.h @@ -0,0 +1,75 @@ +// SPDX-FileCopyrightText: 2026 RizinOrg +// 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 */ diff --git a/librz/arch/meson.build b/librz/arch/meson.build index f83f2a1d10..ba38ca155f 100644 --- a/librz/arch/meson.build +++ b/librz/arch/meson.build @@ -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', diff --git a/librz/arch/p/analysis/analysis_tms320.c b/librz/arch/p/analysis/analysis_tms320.c index 6b35ead543..8a9304fed1 100644 --- a/librz/arch/p/analysis/analysis_tms320.c +++ b/librz/arch/p/analysis/analysis_tms320.c @@ -1,75 +1,19 @@ // SPDX-FileCopyrightText: 2024 deroad // SPDX-FileCopyrightText: 2014 Ilya V. Matveychikov +// SPDX-FileCopyrightText: 2026 RizinOrg // SPDX-License-Identifier: LGPL-3.0-only #include #include +#include #include #include 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" diff --git a/test/db/analysis/tms320.c55x+_32 b/test/db/analysis/tms320.c55x+_32 new file mode 100644 index 0000000000..6fe867e488 --- /dev/null +++ b/test/db/analysis/tms320.c55x+_32 @@ -0,0 +1,513 @@ +NAME=c55x+ analysis: register profile (PC alias resolves) +FILE== +CMDS=< 1 +FILE=bins/tms320/coff2/19_emulateme_nostd.obj +ARGS=-A +CMDS=<reg +# of the AADD K8, SP instruction to identify the prologue frame size) +afi @ dbg.main~^args +afi @ dbg.main~^locals +EOF +EXPECT=<