rizin/binrz/rz-test/run.c
Rot127 42dc673fa5 String/Hex-Search 6/9: Add core implementation of new search (by deroad).
Adds the core implementation of the new search.

The rough architecture is the following:

A search for a certain type of information (strings, bytes, keys etc.)
creates a collections of items to search for (byte patterns, regular expressions etc.).

Then specifies some settings how the search (number of threads, maxum hits...)
and the finding is performed (string length, inverse match etc.).

It also defines a search space, which is currently only the IO buffer.
But can be anything in the future, like a graphs or the knowledge base.

The search splits up the search space into windows (for IO: address ranges)
and dispatches each search window into a 'find()' thread.

The 'find()' handler (provided by a specific search implementation)
checks the given window and produces search hits matching the elements in the search collection.

The main search handler collects the hits of the dispatched workers
and returns them to the user.

Note: The byte and string search implementations are added in the next two commits.

Part 6/9. Likely won't build in between parts.

Co-authored-by: wargio <deroad@kumo.xn--q9jyb4c>
2025-02-22 04:12:49 +08:00

606 lines
17 KiB
C

// SPDX-FileCopyrightText: 2020 thestr4ng3r <info@florianmaerkl.de>
// SPDX-License-Identifier: LGPL-3.0-only
#include "rz_test.h"
#include <rz_util/rz_str.h>
#include <rz_util/rz_regex.h>
#include <rz_cons.h>
#if __WINDOWS__
static ut8 *crlf2lf(ut8 *str) {
char *src = (char *)str;
char *dest = src;
while (*src) {
*dest = *src;
if (src[0] == '\r' && src[1] == '\n') {
// dest does not move
} else {
dest++;
}
src++;
}
*dest = '\0';
return str;
}
#else
#define crlf2lf(x) (x)
#endif
static RzSubprocessOutput *subprocess_runner(const char *file, const char *args[], size_t args_size,
const char *envvars[], const char *envvals[], size_t env_size, ut64 timeout_ms, void *user) {
RzSubprocess *proc = rz_subprocess_start(file, args, args_size, envvars, envvals, env_size);
if (!proc) {
return NULL;
}
RzSubprocessWaitReason r = rz_subprocess_wait(proc, timeout_ms);
if (r == RZ_SUBPROCESS_TIMEDOUT) {
rz_subprocess_kill(proc);
}
RzSubprocessOutput *out = rz_subprocess_drain(proc);
if (out) {
out->timeout = r == RZ_SUBPROCESS_TIMEDOUT;
out->out = crlf2lf(out->out);
out->err = crlf2lf(out->err);
}
rz_subprocess_free(proc);
return out;
}
#if __WINDOWS__
static char *convert_win_cmds(const char *cmds) {
char *r = malloc(strlen(cmds) + 1);
if (!r) {
return NULL;
}
char *p = r;
while (*cmds) {
if (*cmds == '!' || (*cmds == '\"' && cmds[1] == '!')) {
// Adjust shell syntax for Windows,
// only for lines starting with ! or "!
char c;
for (; c = *cmds, c; cmds++) {
if (c == '\\') {
// replace \$ by $
c = *++cmds;
if (c == '$') {
*p++ = '$';
} else {
*p++ = '\\';
*p++ = c;
}
} else if (c == '$') {
// replace ${VARNAME} by %VARNAME%
c = *++cmds;
if (c == '{') {
*p++ = '%';
cmds++;
for (; c = *cmds, c && c != '}'; cmds++) {
*p++ = c;
}
if (c) { // must check c to prevent overflow
*p++ = '%';
}
} else {
*p++ = '$';
*p++ = c;
}
} else {
*p++ = c;
if (c == '\n') {
cmds++;
break;
}
}
}
continue;
}
// Nothing to do, just copy the line
char *lend = strchr(cmds, '\n');
size_t llen;
if (lend) {
llen = lend - cmds + 1;
} else {
llen = strlen(cmds);
}
memcpy(p, cmds, llen);
cmds += llen;
p += llen;
}
*p = '\0';
return rz_str_replace(r, "/dev/null", "nul", true);
}
#endif
static RzSubprocessOutput *run_rz_test(RzTestRunConfig *config, ut64 timeout_ms, const char *cmds, RzList /*<char *>*/ *files, RzList /*<char *>*/ *extra_args, bool load_plugins, RzTestCmdRunner runner, void *user) {
RzPVector args;
rz_pvector_init(&args, NULL);
rz_pvector_push(&args, "-escr.utf8=0");
rz_pvector_push(&args, "-escr.color=0");
rz_pvector_push(&args, "-escr.interactive=0");
rz_pvector_push(&args, "-eflirt.sigdb.load.system=false");
rz_pvector_push(&args, "-esearch.show_progress=false");
rz_pvector_push(&args, "-eflirt.sigdb.load.home=false");
rz_pvector_push(&args, "-N");
RzListIter *it;
void *extra_arg, *file_arg;
rz_list_foreach (extra_args, it, extra_arg) {
rz_pvector_push(&args, extra_arg);
}
rz_pvector_push(&args, "-qc");
#if __WINDOWS__
char *wcmds = convert_win_cmds(cmds);
rz_pvector_push(&args, wcmds);
#else
rz_pvector_push(&args, (void *)cmds);
#endif
rz_list_foreach (files, it, file_arg) {
rz_pvector_push(&args, file_arg);
}
const char *envvars[] = {
#if __WINDOWS__
"ANSICON",
#endif
"RZ_NOPLUGINS"
};
const char *envvals[] = {
#if __WINDOWS__
"1",
#endif
"1"
};
#if __WINDOWS__
size_t env_size = load_plugins ? 1 : 2;
#else
size_t env_size = load_plugins ? 0 : 1;
#endif
RzSubprocessOutput *out = runner(config->rz_cmd, args.v.a, rz_pvector_len(&args), envvars, envvals, env_size, timeout_ms, user);
rz_pvector_clear(&args);
#if __WINDOWS__
free(wcmds);
#endif
return out;
}
RZ_API RzSubprocessOutput *rz_test_run_cmd_test(RzTestRunConfig *config, RzCmdTest *test, RzTestCmdRunner runner, void *user) {
RzList *extra_args = test->args.value ? rz_str_split_duplist(test->args.value, " ", true) : NULL;
RzList *files = test->file.value ? rz_str_split_duplist(test->file.value, "\n", true) : NULL;
RzListIter *it;
RzListIter *tmpit;
char *token;
rz_list_foreach_safe (extra_args, it, tmpit, token) {
if (!*token) {
rz_list_delete(extra_args, it);
}
}
rz_list_foreach_safe (files, it, tmpit, token) {
if (!*token) {
rz_list_delete(files, it);
}
}
if (rz_list_empty(files)) {
if (!files) {
files = rz_list_new();
} else {
files->free = NULL;
}
rz_list_push(files, "=");
}
ut64 timeout_ms = test->timeout.set ? test->timeout.value * 1000 : config->timeout_ms;
RzSubprocessOutput *out = run_rz_test(config, timeout_ms, test->cmds.value, files, extra_args, test->load_plugins, runner, user);
rz_list_free(extra_args);
rz_list_free(files);
return out;
}
RZ_API RZ_OWN RzStrBuf *rz_test_regex_full_match_str(RZ_NONNULL const char *pattern, RZ_NONNULL const char *text) {
return rz_regex_full_match_str(pattern, text, RZ_REGEX_ZERO_TERMINATED, RZ_REGEX_EXTENDED, RZ_REGEX_DEFAULT,
"\n");
}
RZ_API bool rz_test_cmp_cmd_output(const char *output, const char *expect, const char *regexp) {
if (regexp) {
RzStrBuf *match_str = rz_test_regex_full_match_str(regexp, output);
bool equal = false;
size_t expect_len = strlen(expect);
if (expect_len && expect[expect_len - 1] == '\n') {
// Ignore newline in expect
expect_len--;
size_t match_str_len = rz_strbuf_length(match_str);
if (match_str_len && rz_strbuf_get(match_str)[match_str_len - 1] == '\n') {
// Ignore newline in match_str
match_str_len--;
}
if (expect_len != match_str_len) {
rz_strbuf_free(match_str);
return false;
}
equal = !rz_str_cmp(expect, rz_strbuf_get(match_str), expect_len);
} else {
equal = RZ_STR_EQ(expect, rz_strbuf_get(match_str));
}
rz_strbuf_free(match_str);
return equal;
}
return !strcmp(expect, output);
}
RZ_API bool rz_test_check_cmd_test(RzSubprocessOutput *out, RzCmdTest *test) {
if (!out || out->ret != 0 || !out->out || !out->err || out->timeout) {
return false;
}
const char *expect_out = test->expect.value;
const char *regexp_out = test->regexp_out.value;
if (expect_out && !rz_test_cmp_cmd_output((char *)out->out, expect_out, regexp_out)) {
return false;
}
const char *expect_err = test->expect_err.value;
const char *regexp_err = test->regexp_err.value;
if (expect_err && !rz_test_cmp_cmd_output((char *)out->err, expect_err, regexp_err)) {
return false;
}
return true;
}
#define JQ_CMD "jq"
RZ_API bool rz_test_check_jq_available(void) {
const char *args[] = { "." };
const char *invalid_json = "this is not json lol";
RzSubprocess *proc = rz_subprocess_start(JQ_CMD, args, 1, NULL, NULL, 0);
if (proc) {
rz_subprocess_stdin_write(proc, (const ut8 *)invalid_json, strlen(invalid_json));
rz_subprocess_wait(proc, UT64_MAX);
}
bool invalid_detected = proc && rz_subprocess_ret(proc) != 0;
rz_subprocess_free(proc);
const char *valid_json = "{\"this is\":\"valid json\",\"lol\":true}";
proc = rz_subprocess_start(JQ_CMD, args, 1, NULL, NULL, 0);
if (proc) {
rz_subprocess_stdin_write(proc, (const ut8 *)valid_json, strlen(valid_json));
rz_subprocess_wait(proc, UT64_MAX);
}
bool valid_detected = proc && rz_subprocess_ret(proc) == 0;
rz_subprocess_free(proc);
return invalid_detected && valid_detected;
}
RZ_API RzSubprocessOutput *rz_test_run_json_test(RzTestRunConfig *config, RzJsonTest *test, RzTestCmdRunner runner, void *user) {
RzList *files = rz_list_new();
rz_list_push(files, (void *)config->json_test_file);
RzSubprocessOutput *ret = run_rz_test(config, config->timeout_ms, test->cmd, files, NULL, test->load_plugins, runner, user);
rz_list_free(files);
return ret;
}
RZ_API bool rz_test_check_json_test(RzSubprocessOutput *out, RzJsonTest *test) {
if (!out || out->ret != 0 || !out->out || !out->err || out->timeout) {
return false;
}
const char *args[] = { "." };
RzSubprocess *proc = rz_subprocess_start(JQ_CMD, args, 1, NULL, NULL, 0);
rz_subprocess_stdin_write(proc, (const ut8 *)out->out, strlen((char *)out->out));
rz_subprocess_wait(proc, UT64_MAX);
bool ret = rz_subprocess_ret(proc) == 0;
rz_subprocess_free(proc);
return ret;
}
RZ_API RzAsmTestOutput *rz_test_run_asm_test(RzTestRunConfig *config, RzAsmTest *test) {
RzAsmTestOutput *out = RZ_NEW0(RzAsmTestOutput);
if (!out) {
return NULL;
}
out->as_ret = out->disas_ret = out->il_ret = INT_MAX;
RzPVector args;
rz_pvector_init(&args, NULL);
if (test->arch) {
rz_pvector_push(&args, "-a");
rz_pvector_push(&args, (void *)test->arch);
}
if (test->cpu) {
rz_pvector_push(&args, "-c");
rz_pvector_push(&args, (void *)test->cpu);
}
char bits[0x20];
if (test->bits) {
snprintf(bits, sizeof(bits), "%d", test->bits);
rz_pvector_push(&args, "-b");
rz_pvector_push(&args, bits);
}
if (test->mode & RZ_ASM_TEST_MODE_BIG_ENDIAN) {
rz_pvector_push(&args, "-e");
}
char offset[0x20];
if (test->offset) {
rz_snprintf(offset, sizeof(offset), "0x%" PFMT64x, test->offset);
rz_pvector_push(&args, "-o");
rz_pvector_push(&args, offset);
}
if (test->mode & RZ_ASM_TEST_MODE_ASSEMBLE) {
rz_pvector_push(&args, test->disasm);
RzSubprocess *proc = rz_subprocess_start(config->rz_asm_cmd, args.v.a, rz_pvector_len(&args), NULL, NULL, 0);
if (rz_subprocess_wait(proc, config->timeout_ms) == RZ_SUBPROCESS_TIMEDOUT) {
rz_subprocess_kill(proc);
out->as_timeout = true;
goto rip;
}
char *as_err = (char *)crlf2lf(rz_subprocess_err(proc, NULL));
rz_str_trim(as_err);
out->as_err = as_err;
out->as_ret = rz_subprocess_ret(proc);
if (out->as_ret != 0) {
goto rip;
}
char *hex = (char *)crlf2lf(rz_subprocess_out(proc, NULL));
size_t hexlen = strlen(hex);
if (!hexlen) {
goto rip;
}
ut8 *bytes = malloc(hexlen);
int byteslen = rz_hex_str2bin(hex, bytes);
free(hex);
if (byteslen <= 0) {
free(bytes);
goto rip;
}
out->bytes = bytes;
out->bytes_size = (size_t)byteslen;
rip:
rz_pvector_pop(&args);
rz_subprocess_free(proc);
}
if (test->mode & RZ_ASM_TEST_MODE_DISASSEMBLE) {
char *hex = rz_hex_bin2strdup(test->bytes, test->bytes_size);
if (!hex) {
goto beach;
}
rz_pvector_push(&args, "-d");
rz_pvector_push(&args, hex);
RzSubprocess *proc = rz_subprocess_start(config->rz_asm_cmd, args.v.a, rz_pvector_len(&args), NULL, NULL, 0);
if (rz_subprocess_wait(proc, config->timeout_ms) == RZ_SUBPROCESS_TIMEDOUT) {
rz_subprocess_kill(proc);
out->disas_timeout = true;
goto ship;
}
char *disas_err = (char *)crlf2lf(rz_subprocess_err(proc, NULL));
rz_str_trim(disas_err);
out->disas_err = disas_err;
out->disas_ret = rz_subprocess_ret(proc);
if (out->disas_ret != 0) {
goto ship;
}
char *disasm = (char *)crlf2lf(rz_subprocess_out(proc, NULL));
rz_str_trim(disasm);
out->disasm = disasm;
ship:
free(hex);
rz_pvector_pop(&args);
rz_pvector_pop(&args);
rz_subprocess_free(proc);
}
if (test->il) {
char *hex = rz_hex_bin2strdup(test->bytes, test->bytes_size);
if (!hex) {
goto beach;
}
rz_pvector_push(&args, "-I");
rz_pvector_push(&args, hex);
RzSubprocess *proc = rz_subprocess_start(config->rz_asm_cmd, args.v.a, rz_pvector_len(&args), NULL, NULL, 0);
if (rz_subprocess_wait(proc, config->timeout_ms) == RZ_SUBPROCESS_TIMEDOUT) {
rz_subprocess_kill(proc);
out->il_timeout = true;
} else {
char *il = (char *)crlf2lf(rz_subprocess_out(proc, NULL));
rz_str_trim(il);
char *il_err = (char *)crlf2lf(rz_subprocess_err(proc, NULL));
rz_str_trim(il_err);
out->il = il;
out->il_err = il_err;
out->il_ret = rz_subprocess_ret(proc);
out->il_failed = out->il_ret != 0;
}
free(hex);
rz_pvector_pop(&args);
rz_pvector_pop(&args);
rz_subprocess_free(proc);
}
beach:
rz_pvector_clear(&args);
return out;
}
RZ_API bool rz_test_check_asm_test(RzAsmTestOutput *out, RzAsmTest *test) {
if (!out) {
return false;
}
if (test->mode & RZ_ASM_TEST_MODE_ASSEMBLE) {
if (!out->bytes || !test->bytes || out->bytes_size != test->bytes_size || out->as_timeout) {
return false;
}
if (memcmp(out->bytes, test->bytes, test->bytes_size) != 0) {
return false;
}
}
if (test->mode & RZ_ASM_TEST_MODE_DISASSEMBLE) {
if (!out->disasm || !test->disasm || out->as_timeout) {
return false;
}
if (strcmp(out->disasm, test->disasm) != 0) {
return false;
}
}
if (test->il) {
// expect some IL, no failure, no report and no timeout
if (!out->il || out->il_failed || RZ_STR_ISNOTEMPTY(out->il_err) || out->il_timeout) {
return false;
}
// IL must also be correct
if (strcmp(out->il, test->il) != 0) {
return false;
}
}
return true;
}
RZ_API void rz_test_asm_test_output_free(RzAsmTestOutput *out) {
if (!out) {
return;
}
free(out->disasm);
free(out->bytes);
free(out->il);
free(out->as_err);
free(out->disas_err);
free(out->il_err);
free(out);
}
RZ_API RzSubprocessOutput *rz_test_run_fuzz_test(RzTestRunConfig *config, RzFuzzTest *test, RzTestCmdRunner runner, void *user) {
const char *cmd = "aaa";
RzList *files = rz_list_new();
rz_list_push(files, test->file);
#if ASAN
if (rz_str_endswith(test->file, "/swift_read")) {
cmd = "?F";
}
#endif
RzSubprocessOutput *ret = run_rz_test(config, config->timeout_ms, cmd, files, NULL, false, runner, user);
rz_list_free(files);
return ret;
}
RZ_API bool rz_test_check_fuzz_test(RzSubprocessOutput *out) {
return out && out->ret == 0 && out->out && out->err && !out->timeout;
}
RZ_API char *rz_test_test_name(RzTest *test) {
switch (test->type) {
case RZ_TEST_TYPE_CMD:
if (test->cmd_test->name.value) {
return strdup(test->cmd_test->name.value);
}
return strdup("<unnamed>");
case RZ_TEST_TYPE_ASM:
return rz_str_newf("<asm> %s", test->asm_test->disasm ? test->asm_test->disasm : "");
case RZ_TEST_TYPE_JSON:
return rz_str_newf("<json> %s", test->json_test->cmd ? test->json_test->cmd : "");
case RZ_TEST_TYPE_FUZZ:
return rz_str_newf("<fuzz> %s", test->fuzz_test->file);
}
return NULL;
}
RZ_API bool rz_test_broken(RzTest *test) {
switch (test->type) {
case RZ_TEST_TYPE_CMD:
return test->cmd_test->broken.value;
case RZ_TEST_TYPE_ASM:
return test->asm_test->mode & RZ_ASM_TEST_MODE_BROKEN ? true : false;
case RZ_TEST_TYPE_JSON:
return test->json_test->broken;
case RZ_TEST_TYPE_FUZZ:
return false;
}
return false;
}
RZ_API RzTestResultInfo *rz_test_run_test(RzTestRunConfig *config, RzTest *test) {
RzTestResultInfo *ret = RZ_NEW0(RzTestResultInfo);
if (!ret) {
return NULL;
}
ret->test = test;
bool success = false;
ut64 start_time = rz_time_now_mono();
switch (test->type) {
case RZ_TEST_TYPE_CMD: {
RzCmdTest *cmd_test = test->cmd_test;
RzSubprocessOutput *out = rz_test_run_cmd_test(config, cmd_test, subprocess_runner, NULL);
success = rz_test_check_cmd_test(out, cmd_test);
ret->proc_out = out;
ret->timeout = out && out->timeout;
ret->run_failed = !out;
break;
}
case RZ_TEST_TYPE_ASM: {
RzAsmTest *asm_test = test->asm_test;
RzAsmTestOutput *out = rz_test_run_asm_test(config, asm_test);
success = rz_test_check_asm_test(out, asm_test);
ret->asm_out = out;
if (out) {
ret->timeout = out->as_timeout || out->disas_timeout || out->il_timeout;
}
ret->run_failed = !out;
break;
}
case RZ_TEST_TYPE_JSON: {
RzJsonTest *json_test = test->json_test;
RzSubprocessOutput *out = rz_test_run_json_test(config, json_test, subprocess_runner, NULL);
success = rz_test_check_json_test(out, json_test);
ret->proc_out = out;
if (out) {
ret->timeout = out->timeout;
}
ret->run_failed = !out;
break;
}
case RZ_TEST_TYPE_FUZZ: {
RzFuzzTest *fuzz_test = test->fuzz_test;
RzSubprocessOutput *out = rz_test_run_fuzz_test(config, fuzz_test, subprocess_runner, NULL);
success = rz_test_check_fuzz_test(out);
ret->proc_out = out;
if (out) {
ret->timeout = out->timeout;
}
ret->run_failed = !out;
}
}
ret->time_elapsed = rz_time_now_mono() - start_time;
bool broken = rz_test_broken(test);
#if ASAN
#if !RZ_ASSERT_STDOUT
#error RZ_ASSERT_STDOUT undefined or 0
#endif
RzSubprocessOutput *out = ret->proc_out;
if (!success && test->type == RZ_TEST_TYPE_CMD && strstr(test->path, "/dbg") && (!out->out || (!strstr((char *)out->out, "WARNING:") && !strstr((char *)out->out, "ERROR:") && !strstr((char *)out->out, "FATAL:"))) && (!out->err || (!strstr((char *)out->err, "Sanitizer") && !strstr((char *)out->err, "runtime error:")))) {
broken = true;
}
#endif
if (!success) {
ret->result = broken ? RZ_TEST_RESULT_BROKEN : RZ_TEST_RESULT_FAILED;
} else {
ret->result = broken ? RZ_TEST_RESULT_FIXED : RZ_TEST_RESULT_OK;
}
return ret;
}
RZ_API void rz_test_test_result_info_free(RzTestResultInfo *result) {
if (!result) {
return;
}
if (result->test) {
switch (result->test->type) {
case RZ_TEST_TYPE_CMD:
case RZ_TEST_TYPE_JSON:
case RZ_TEST_TYPE_FUZZ:
rz_subprocess_output_free(result->proc_out);
break;
case RZ_TEST_TYPE_ASM:
rz_test_asm_test_output_free(result->asm_out);
break;
}
}
free(result);
}