Uplift Hexagon architecture to RzIL The general structure is, that every (sub-)instruction has a getter for it's RzIL code. Calling the getter will return the RzIL operation. If RzIL for an instruction is requested, the plugin makes a decision. Because Hexagon only executes whole instruction packets. If the instruction is not the last instruction in a packet, it will simply return `EMPTY()`. If the RzIL for the last instruction in a packet is requested, it will get the RzIL operations for all instructions in the packet, shuffles them into the correct execution order (according to some rules) and returns the complete operation for the packet. The RzIL code was entirely generated with the [rzil-compiler](https://github.com/Rot127/rzil-compiler/), using the semantic definition of the [QEMU Hexagon module](https://github.com/qemu/qemu/tree/master/target/hexagon). Currently successful compile instructions (and tested): ``` [*] 1581/1733 standard instructions compiled. [*] 431/643 HVX instructions compiled. [*] In total: 2012/2376 instructions compiled. ``` It was tested with: - (Semantic tests) `rz-tracetest` against the execution trace of the QEMU Hexagon test binaries. - (Bug free and semi-semtantic test) Adding tests which simply execute the test binaries to ensure leak and segfault free execution. Also it is executed until a certain instruction is reached (end of `main` or `loc.pass` symbol), partially testing it executes correctly. For the uplifting several changes and modernization had to be made: - Enhance consistency of decoding - Allow to disassemble an instruction without copying the result. This is used if the given buffer of instruction bytes is larger than one instruction width. In this case, as many instructions as the buffer can hold are disassembled and buffered for later. - Generally enhance buffering of instructions. - Allow to mark a packet as valid before it is completely decoded (in case we know it must be valid, e.g. if it is a jump target of a valid packet). - Fix (hopefully) all memory leaks of the Hexagon plugin. - Changes to register getters, because RzIL needs finer control to translate alias or explicit register names to their real register. - Getter for register name is now done by table, so for future distinction between DSP version we can just select another table. - Translation functions from register alias or explicit name to their real register. - Each operand contains now it's variable ID (e.g. `d` for register `Rd`) as in the ISA (for mapping in the RzIL code). - Ease debugging by tracking in more precision, if an instruction is added to a stale, active or new packet. - Add registers `C20` - `C29` (not yet present in LLVM) - Some renaming to make the code more readable. |
||
|---|---|---|
| .. | ||
| 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)
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.
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
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 is the path of the file used for the test
- ARGS (optional) are the command line argument passed to rizin (e.g -b 16)
- CMDS are the commands to be executed by the test
- 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).