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.
|
||
|---|---|---|
| .. | ||
| bench | ||
| db | ||
| fuzz/scripts | ||
| integration | ||
| notworking_db | ||
| prj | ||
| scripts | ||
| unit | ||
| .gitignore | ||
| meson.build | ||
| README.md | ||
Rizin tests
Rizin uses both regression and unit tests.
Directory Hierarchy
- db/: The regressions tests sources
- unit/: Unit tests (written in C, using minunit).
- fuzz/: Fuzzing helper scripts
- bins/: Sample binaries (fetched from the external repository)
- bench/: Benchmarks
Requirements
- rizin installed and in
$PATH(you can also use a rizin not in$PATH, but other files like calling convention files, format files, etc. must have been installed). - rz-test compiled and/or installed, which is done by default automatically when building Rizin.
Usage
Regression tests
To run regressions tests use rz-test from within the test directory.
By default it will run all tests under the db subdirectory, however you can
also specify which tests you want to run, by providing its name as argument to
rz-test.
For example, to run only the asm tests for x86_64, you can do rz-test db/asm/x86_64. rz-test provides other interesting options that you can check
out by doing rz-test -h.
An option that you may find interesting, in particular when doing changes that
may affect the output of multiple tests, is the -i option, which enables
interactive mode. When running tests in this mode, rz-test will warn you for
each failed test and it will ask for your input on how to treat the issue. It
can automatically fix the test so that it matches the new output (if that is the
right behaviour!) or it can mark it as broken for you.
Unit tests
To run unit tests, just use ninja -C build test (or meson test -C build)
from the top directory (replace build with the name of the directory you used
to build Rizin).
You can run one specific testcase category (e.g. the whole test_bin.c file) using meson test -C build bin.
If you are using meson test, you should consider using the --print-errorlogs flag.
Benchmarks
In order to be able to run the benchmarks, the -Denable_benchmarks=true switch needs to be specified
when setting up the build directory (e.g. meson setup build -Denable_benchmarks=true).
Afterwards use ninja -C build test --benchmark (or meson test -C build --benchmark)
to run the benchmarks from the top directory (replace build with the name of the directory
you used to build Rizin).
Running a specific set of benchmarks (e.g. bitvector) can be done with ninja -C build test bitvector --benchmark.
To see the detailed benchmark results (iterations, average time per operation, and throughput), you must use the verbose flag:
meson test -C build <benchmark_name> --benchmark -v
Note: Without the -v flag, Meson will only report if the benchmark process finished successfully, but it will not display the timing data table.
Failure Levels
A test can have one of the following results:
- success: The test passed, and that was expected.
- fixed: The test passed, but failure was expected.
- broken: Failure was expected, and happened.
- failed: The test failed unexpectedly. This is a regression.
Writing Assembly tests
Tests for the assembly and disassembly (in db/asm/*) have a different format:
General format:
type "assembly" opcode [offset] [IL]
where type can be any of:
- a meaning "assemble"
- d meaning "disassemble"
- B meaning "broken"
- E stands for cfg.bigendian=true
offset
Some architectures are going to assemble an instruction differently depending on the offset it's written to. Optional.
Examples:
a "ret" c3
d "ret" c3
a "nop" 90 # Assembly is correct
dB "nopppp" 90 # Disassembly test is broken
Multiple instructions in a single test
Some instructions change if they appear together with another instructions. To test two or more instructions in a single test the instructions' assembly text and IL representations can be concatinated with a simicolon.
Example: Branch delay in Sparc:
dE "call g1;nop" 9fc0600001000000 0x40 (set o7 (bv 64 0x40));(seq nop (jmp (var g1)))
Example: ARM conditional blocks with it.
d "ite eq;moveq r0, 1" 0cbf0120 0x0 nop;(branch (var zf) (set r0 (bv 32 0x1)) nop)
IL
To also test lifting an instruction to RzIL, you can append the readable IL representation like so:
d "inc ptr" 3e 0 set(v:ptr, x:add(x:var(v:ptr), y:bitv(bits:0x0000000000000001, len:64)))
This means that rz-test will also perform the lifting from bytes to RzIL, run the validation pass on the result and compare it against the given string.
In this case, passing an offset is mandatory, otherwise the argument would be ambiguous.
General hints
You can merge lines:
adB "nop" 90
acts the same as
aB "nop" 90
dB "nop" 90
The filename is very important. It is used to tell rizin which architecture to use: arch[[_cpu]_bits].
Examples:
x86_32means-a x86 -b 32arm_v7_64means-a arm -b 64
Writing JSON tests
The JSON tests db/json are executed on 3 standard files (1 ELF, 1 MachO, 1 PE). The tests need to be working on the 3 files to pass.
Commands tests
Example commands tests for the other db/ folders:
NAME=test_db
FILE=bins/elf/ls
CMDS=<<EOF
pd 4
EOF
EXPECT=<<EOF
;-- main:
;-- entry0:
;-- func.100001174:
0x100001174 55 Push rbp
0x100001175 4889e5 Mov rbp, rsp
0x100001178 4157 Push r15
EOF
RUN
It is also possible to match specific parts of the output in EXPECT and EXPECT_ERR using regex (with
REGEXP_FILTER_OUT and REGEXP_FILTER_ERR respectively) in case some of the test's output is dynamic:
NAME=bp rebase
FILE=bins/elf/analysis/pie
ARGS=-d
CMDS=<<EOF
aa
db @ main
dbl~main
doc
dbl~main
EOF
REGEXP_FILTER_OUT=([a-zA-Z="]+\s+)
EXPECT=<<EOF
0x000005c5 0x000005c6 1 --x sw break enabled valid main
0x000005c5 0x000005c6 1 --x sw break enabled valid main
EOF
RUN
Without the regex that filtered out the non-deterministic file path and addresses, the expected output would have been the following:
0x566495c5 - 0x566495c6 1 --x sw break enabled valid cmd="" cond="" name="main" module="/home/user/rizin/test/bins/elf/analysis/pie"
0x000005c5 - 0x000005c6 1 --x sw break enabled valid cmd="" cond="" name="main" module="/home/user/rizin/test/bins/elf/analysis/pie"
- NAME is the name of the test, it must be unique
- FILE (optional when
TOOLis set) is the file or input used for the test - TOOL (optional) allows you to override the tool to test (supports only
rizin,rz-ar,rz-asm,rz-ax,rz-bin,rz-diff,rz-find,rz-gg,rz-hash,rz-run,rz-sign,rz-test) - ARGS (optional, unless
TOOLis set) are the command line argument passed to rizin (e.g -b 16) - CMDS are the commands to be executed by the test
- ENVS (optional) allows to set a custom environment variable (example
FOO=bar) - EXIT_STATUS (optional) allows to override the default expected exit status.
- COLOR (optional) allows to use colors in the test (default
0) - UTF8 (optional) allows to use utf-8 in the test (default
0) - EXPECT is the expected output of the test from stdout. If
REGEXP_FILTER_OUTis used,EXPECTmatches only the filtered output. - EXPECT_ERR (optional) is the expected output of the test from stderr. Can be specified in addition or instead of
EXPECT - BROKEN (optional) is 1 if the tests is expected to be fail, 0 or unspecified otherwise
- TIMEOUT (optional) is the number of seconds to wait before considering the test timeout
- REGEXP_FILTER_OUT (optional) apply given regex on stdout before comparing the output to
EXPECT(e.g.REGEXP_FILTER_OUT=([a-zA-Z]+)). This is similar to piping stdout togrep -E "<regex>"and then comparing the matched text withEXPECT. - REGEXP_FILTER_ERR (optional) apply given regex on stderr before comparing the ouput to
EXPECT_ERR
You must end the test by adding RUN keyword
Advices
- For portability reasons do not use shell pipes, use
~ - dont use
pdif not necessary, usepi - All tests use the UTC timezone for consistency.
Unit tests
Assembly, JSON and commands tests are useful to test the overall behaviour of Rizin, but to test new API or new code we suggest to write small unit tests.
The basic structure of a unit test is the following:
#include <rz_XXXXX.h>
#include "minunit.h" // Place at the bottom of includes.
static bool test_my_feature(void) {
// code to test the behaviour
mu_end;
}
static bool all_tests() {
mu_run_test(test_my_feature);
return tests_passed != tests_run;
}
mu_main (all_tests)
Minunit provides various functions to check the actual output of a function with the expected one. For example:
mu_assert_true(actual, message)checks thatactualevaluates to true, otherwise it printsmessageon stderr.mu_assert_false(actual, message)checks thatactualevaluates to false, otherwise it printsmessageon stderr.mu_assert_eq(actual, expected, message)checks that the integer (ut64 at most)actualis equal to the integerexpected, otherwise it printsmessageon stderr.mu_assert_ptreq(actual, expected, message)checks that the pointeractualis equal toexpected.mu_assert_null(actual, message)mu_assert_streq(actual, expected, message)mu_assert_memeq(actual, expected, len, message)- etc.
If you add a unit test file, be sure to also add it to unit/meson.build, so it
is compiled when you compile Rizin.
License
The test files are licensed under GPL 3 (or later).