String search integration tests (#5358)
* Add integration test for a simple string search.
* Clean up rz_str_is_false
* Doxygen
* Add rz_buf_type
* Sort search hits not just by address but also by size.
* Add a paranoid regex Hindi test.
* Add an integration test using RzCore and searching for UTF-16LE.
* Revert "Temporarily remove codecov workflow due to https://github.com/codecov/codecov-action/issues/1877 (#5366)"
This reverts commit dc5d50ce58.
This commit is contained in:
parent
63fa6938a0
commit
a59b090db9
14 changed files with 464 additions and 32 deletions
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
|
@ -336,7 +336,7 @@ jobs:
|
|||
if: matrix.coverage && matrix.enabled
|
||||
run: |
|
||||
gcovr --version
|
||||
gcovr --gcov-ignore-parse-errors=all --txt build/coverage.txt --cobertura build/coverage.xml build
|
||||
gcovr --gcov-ignore-parse-errors=all --txt build/coverage.txt --cobertura build/coverage.xml --merge-mode-functions=MERGE_MODE build
|
||||
- name: Upload coverage to Codecov
|
||||
if: matrix.coverage && matrix.enabled
|
||||
uses: codecov/codecov-action@v5
|
||||
|
|
|
|||
27
codecov.yml
Normal file
27
codecov.yml
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
coverage:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
branches:
|
||||
- dev
|
||||
- 'release-*'
|
||||
if_not_found: failure
|
||||
target: auto
|
||||
threshold: 0.5%
|
||||
patch:
|
||||
default:
|
||||
if_not_found: success
|
||||
target: auto
|
||||
threshold: 0.5%
|
||||
ignore:
|
||||
- "binrz/rz-test"
|
||||
- ".github"
|
||||
- "doc"
|
||||
- "man"
|
||||
- "mk"
|
||||
- "sys"
|
||||
- "test"
|
||||
|
||||
comment:
|
||||
layout: "header, files, components, footer"
|
||||
require_changes: true
|
||||
|
|
@ -293,8 +293,8 @@ RZ_API RZ_OWN RzList /*<RzSearchHit *>*/ *rz_core_search_string(RZ_NONNULL RzCor
|
|||
goto quit;
|
||||
}
|
||||
|
||||
collection = rz_search_collection_strings(&scan_opt, expected);
|
||||
size_t match_alignment = rz_search_find_opt_get_alignment(rz_search_opt_get_find_options(user_opts));
|
||||
collection = rz_search_collection_strings(&scan_opt, expected, match_alignment);
|
||||
if (!collection ||
|
||||
!rz_search_collection_string_add(collection, re_pattern, cflags, match_alignment)) {
|
||||
rz_search_collection_free(collection);
|
||||
|
|
|
|||
|
|
@ -264,7 +264,7 @@ RZ_API RZ_OWN RzSearchCollection *rz_search_collection_bytes();
|
|||
RZ_API bool rz_search_collection_bytes_add(RZ_NONNULL RzSearchCollection *col, RZ_NULLABLE const char *pattern_desc, RZ_NONNULL const ut8 *bytes, RZ_NULLABLE const ut8 *mask, size_t length);
|
||||
RZ_API bool rz_search_collection_bytes_add_pattern(RZ_NONNULL RzSearchCollection *col, RZ_NONNULL RZ_OWN RzSearchBytesPattern *bytes_pattern);
|
||||
|
||||
RZ_API RZ_OWN RzSearchCollection *rz_search_collection_strings(RZ_NONNULL RzUtilStrScanOptions *opts, RzStrEnc expected);
|
||||
RZ_API RZ_OWN RzSearchCollection *rz_search_collection_strings(RZ_BORROW RzUtilStrScanOptions *opts, RzStrEnc expected, size_t alignment);
|
||||
RZ_API bool rz_search_collection_string_add(RZ_NONNULL RzSearchCollection *col, RZ_NONNULL const char *regex_pattern, RzRegexFlags cflags, size_t match_alignment);
|
||||
RZ_API bool rz_search_collection_strings_check_config_improvements(
|
||||
RZ_NULLABLE const RzSearchCollection *col,
|
||||
|
|
@ -279,6 +279,7 @@ RZ_API void rz_search_collection_free(RZ_NULLABLE RzSearchCollection *sc);
|
|||
RZ_API RZ_OWN RzSearchCollection *rz_search_collection_magic(RZ_NONNULL const char *magic_dir);
|
||||
|
||||
RZ_API RZ_OWN RzList /*<RzSearchHit *>*/ *rz_search_on_io(RZ_BORROW RZ_NONNULL RzSearchOpt *opt, RZ_BORROW RZ_NONNULL RzSearchCollection *col, RZ_BORROW RZ_NONNULL RzIO *io, RZ_BORROW RZ_NONNULL RzList /*<RzIOMap *>*/ *search_in);
|
||||
RZ_API RZ_OWN RzList /*<RzSearchHit *>*/ *rz_search_on_buffer(RZ_BORROW RZ_NONNULL RzSearchOpt *opt, RZ_BORROW RZ_NONNULL RzSearchCollection *col, RZ_BORROW RZ_NONNULL RzBuffer *buffer);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
|
|
|||
|
|
@ -158,6 +158,7 @@ RZ_API void rz_buf_set_overflow_byte(RZ_NONNULL RzBuffer *b, ut8 Oxff);
|
|||
RZ_API bool rz_buf_is_bytes_buf(const RzBuffer *b);
|
||||
RZ_DEPRECATE RZ_API RZ_BORROW ut8 *rz_buf_data(RZ_NONNULL RzBuffer *b, RZ_NONNULL RZ_OUT ut64 *size);
|
||||
RZ_API RZ_BORROW const ut8 *rz_buf_get_whole_hot_paths(RZ_NONNULL RzBuffer *b, RZ_NONNULL RZ_OUT ut64 *sz);
|
||||
RZ_API RzBufferType rz_buf_type(RZ_NONNULL const RzBuffer *b);
|
||||
|
||||
/**
|
||||
* \brief Callback to be used with rz_buf_fwd_scan().
|
||||
|
|
|
|||
|
|
@ -151,7 +151,7 @@ RZ_API const char *rz_str_sysbits(const int v);
|
|||
RZ_API char *rz_str_trunc_ellipsis(const char *str, int len);
|
||||
RZ_API const char *rz_str_bool(int b);
|
||||
RZ_API bool rz_str_is_true(const char *s);
|
||||
RZ_API bool rz_str_is_false(const char *s);
|
||||
RZ_API bool rz_str_is_false(RZ_NULLABLE const char *s);
|
||||
RZ_API bool rz_str_is_bool(const char *val);
|
||||
RZ_API const char *rz_str_ansi_chrn(const char *str, size_t n);
|
||||
RZ_API size_t rz_str_ansi_len(const char *str);
|
||||
|
|
|
|||
|
|
@ -72,6 +72,17 @@ RZ_API bool rz_search_opt_set_max_threads(RZ_NONNULL RzSearchOpt *opt, RzThreadN
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Enables the printing of progress during the search.
|
||||
*
|
||||
* \param opt The search options to update.
|
||||
* \param show_progress A string describing the progress type. Can be:
|
||||
* - "off", "no", "false", "0": No progress is printed.
|
||||
* - "interval": The currently searched in interval is printed.
|
||||
* - else: Print number of current hits during search.
|
||||
*
|
||||
* \return True if options were correctly updated, false otherwise.
|
||||
*/
|
||||
RZ_API bool rz_search_opt_set_show_progress_from_str(RZ_NONNULL RzSearchOpt *opt, const char *show_progress) {
|
||||
rz_return_val_if_fail(opt, false);
|
||||
if (rz_str_is_false(show_progress)) {
|
||||
|
|
@ -108,7 +119,12 @@ RZ_API const RzSearchFindOpt *rz_search_opt_get_find_options(RZ_NONNULL const Rz
|
|||
}
|
||||
|
||||
RZ_API RZ_OWN RzSearchFindOpt *rz_search_find_opt_new() {
|
||||
return RZ_NEW0(RzSearchFindOpt);
|
||||
RzSearchFindOpt *fopts = RZ_NEW0(RzSearchFindOpt);
|
||||
if (!fopts) {
|
||||
return NULL;
|
||||
}
|
||||
fopts->alignment = 1;
|
||||
return fopts;
|
||||
}
|
||||
|
||||
RZ_API void rz_search_find_opt_free(RZ_NULLABLE RzSearchFindOpt *opt) {
|
||||
|
|
|
|||
|
|
@ -552,6 +552,8 @@ RZ_API void rz_search_kw_reset(RzSearch *s) {
|
|||
typedef struct search_ctx {
|
||||
RzIO *io; ///< the RzIO struct to use
|
||||
RzThreadLock *io_lock;
|
||||
RzBuffer *buffer; ///< RzBuffer to search in. If this is set, io should be NULL.
|
||||
RzThreadLock *buffer_lock;
|
||||
RzSearchCollection *col; ///< collection to use
|
||||
RzSearchOpt *opt; ///< User options
|
||||
RzThreadQueue /* RzSearchHits */ *hits; ///< Hits list
|
||||
|
|
@ -607,14 +609,28 @@ static bool search_iterator_io_map_cb(void *element, void *user) {
|
|||
ut64 at = window->addr;
|
||||
ut64 size = window->size;
|
||||
|
||||
rz_th_lock_enter(ctx->io_lock);
|
||||
RzBuffer *buffer = rz_io_nread_at_new_buf(ctx->io, at, size);
|
||||
if (!buffer || rz_buf_size(buffer) != size) {
|
||||
RZ_LOG_ERROR("search: failed to read at 0x%08" PFMT64x " (0x%08" PFMT64x " bytes)\n", at, size);
|
||||
RzBuffer *buffer = NULL;
|
||||
if (ctx->io) {
|
||||
rz_th_lock_enter(ctx->io_lock);
|
||||
buffer = rz_io_nread_at_new_buf(ctx->io, at, size);
|
||||
if (!buffer || rz_buf_size(buffer) != size) {
|
||||
RZ_LOG_ERROR("search: failed to read at 0x%08" PFMT64x " (0x%08" PFMT64x " bytes)\n", at, size);
|
||||
rz_th_lock_leave(ctx->io_lock);
|
||||
goto failure;
|
||||
}
|
||||
rz_th_lock_leave(ctx->io_lock);
|
||||
goto failure;
|
||||
} else {
|
||||
ut8 *window_data = RZ_NEWS(ut8, size);
|
||||
rz_th_lock_enter(ctx->buffer_lock);
|
||||
size = rz_buf_read_at(ctx->buffer, at, window_data, size);
|
||||
rz_th_lock_leave(ctx->buffer_lock);
|
||||
buffer = rz_buf_new_with_bytes(window_data, size);
|
||||
free(window_data);
|
||||
if (!buffer) {
|
||||
RZ_LOG_ERROR("search: failed to read at 0x%08" PFMT64x " (0x%08" PFMT64x " bytes)\n", at, size);
|
||||
goto failure;
|
||||
}
|
||||
}
|
||||
rz_th_lock_leave(ctx->io_lock);
|
||||
|
||||
size_t n_hits = 0;
|
||||
RzSearchFindBytesCallback find = col->find;
|
||||
|
|
@ -666,6 +682,26 @@ static RzList /*<RzInterval *>*/ *assemble_search_window_list(RzList /*<RzIOMap
|
|||
return list;
|
||||
}
|
||||
|
||||
static bool perform_sanity_checks(
|
||||
RZ_BORROW RZ_NONNULL RzSearchOpt *opt,
|
||||
RZ_BORROW RZ_NONNULL RzSearchCollection *col) {
|
||||
if (!rz_search_collection_on_bytes_space(col)) {
|
||||
RZ_LOG_ERROR("search: The search collection is not initialized for byte space.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (opt->chunk_size < RZ_SEARCH_MIN_CHUNK_SIZE) {
|
||||
RZ_LOG_ERROR("search: cannot search when buffer size is less than %#" PFMT64x " bytes.\n", RZ_SEARCH_MIN_CHUNK_SIZE);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (rz_search_collection_is_empty(col)) {
|
||||
RZ_LOG_ERROR("search: cannot perform the search when the search collection is empty.\n");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Perform a search within the given search maps of a collection
|
||||
*
|
||||
|
|
@ -689,13 +725,7 @@ RZ_API RZ_OWN RzList /*<RzSearchHit *>*/ *rz_search_on_io(
|
|||
RzList /* RzInterval */ *windows = NULL;
|
||||
RzThread *cancel_th = NULL;
|
||||
|
||||
if (!rz_search_collection_on_bytes_space(col)) {
|
||||
RZ_LOG_ERROR("search: The search collection is not initialized for byte space.\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (opt->chunk_size < RZ_SEARCH_MIN_CHUNK_SIZE) {
|
||||
RZ_LOG_ERROR("search: cannot search when buffer size is less than %#" PFMT64x " bytes.\n", RZ_SEARCH_MIN_CHUNK_SIZE);
|
||||
if (!perform_sanity_checks(opt, col)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
|
@ -704,11 +734,6 @@ RZ_API RZ_OWN RzList /*<RzSearchHit *>*/ *rz_search_on_io(
|
|||
return NULL;
|
||||
}
|
||||
|
||||
if (rz_search_collection_is_empty(col)) {
|
||||
RZ_LOG_ERROR("search: cannot perform the search when the search collection is empty.\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
hits = rz_th_queue_new(RZ_THREAD_QUEUE_UNLIMITED, (RzListFree)rz_search_hit_free);
|
||||
if (!hits) {
|
||||
RZ_LOG_ERROR("search: cannot allocate RzSearchHit queue.\n");
|
||||
|
|
@ -775,6 +800,104 @@ RZ_API RZ_OWN RzList /*<RzSearchHit *>*/ *rz_search_on_io(
|
|||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Perform a search within the given search maps of a collection
|
||||
*
|
||||
* \param opt The RzSearchOpt to use
|
||||
* \param col The RzSearchCollection to use
|
||||
* \param buffer The RzBuffer to search in.
|
||||
*
|
||||
* \return On success returns all the hits.
|
||||
*/
|
||||
RZ_API RZ_OWN RzList /*<RzSearchHit *>*/ *rz_search_on_buffer(
|
||||
RZ_BORROW RZ_NONNULL RzSearchOpt *opt,
|
||||
RZ_BORROW RZ_NONNULL RzSearchCollection *col,
|
||||
RZ_BORROW RZ_NONNULL RzBuffer *buffer) {
|
||||
rz_return_val_if_fail(opt && col && buffer, NULL);
|
||||
search_ctx_t ctx = { 0 };
|
||||
RzList *results = NULL;
|
||||
RzThreadQueue *hits = NULL;
|
||||
RzThreadQueue *intervals = NULL;
|
||||
RzList /* RzInterval */ *windows = NULL;
|
||||
RzThread *cancel_th = NULL;
|
||||
|
||||
if (!perform_sanity_checks(opt, col)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
hits = rz_th_queue_new(RZ_THREAD_QUEUE_UNLIMITED, (RzListFree)rz_search_hit_free);
|
||||
if (!hits) {
|
||||
RZ_LOG_ERROR("search: cannot allocate RzSearchHit queue.\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
intervals = rz_th_queue_new(RZ_THREAD_QUEUE_UNLIMITED, (RzListFree)rz_search_interval_free);
|
||||
if (!intervals) {
|
||||
RZ_LOG_ERROR("search: cannot allocate RzSearchInterval queue.\n");
|
||||
rz_th_queue_free(hits);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
RzList *search_in = rz_list_new();
|
||||
RzIOMap *map = RZ_NEW0(RzIOMap);
|
||||
map->itv.addr = 0;
|
||||
map->itv.size = rz_buf_size(buffer);
|
||||
rz_list_append(search_in, map);
|
||||
windows = assemble_search_window_list(search_in, opt);
|
||||
rz_list_free(search_in);
|
||||
|
||||
if (!windows) {
|
||||
RZ_LOG_ERROR("search: Could not prepare search window queue.\n");
|
||||
rz_th_queue_free(hits);
|
||||
rz_th_queue_free(intervals);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ctx.col = col;
|
||||
ctx.opt = opt;
|
||||
ctx.buffer = buffer;
|
||||
ctx.buffer_lock = rz_th_lock_new(false);
|
||||
ctx.loop = rz_atomic_bool_new(true);
|
||||
ctx.hits = hits;
|
||||
ctx.finished_intervals = intervals;
|
||||
|
||||
if (opt->cancel_cb) {
|
||||
// create cancel thread
|
||||
cancel_th = rz_th_new(search_cancel_th, &ctx);
|
||||
if (!cancel_th) {
|
||||
RZ_LOG_ERROR("search: cannot allocate cancel thread.\n");
|
||||
rz_th_queue_free(hits);
|
||||
rz_th_queue_free(intervals);
|
||||
rz_atomic_bool_free(ctx.loop);
|
||||
rz_list_free(windows);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (!rz_th_iterate_list(windows, search_iterator_io_map_cb, opt->max_threads, &ctx)) {
|
||||
RZ_LOG_ERROR("search: cannot iterate over list.\n");
|
||||
} else {
|
||||
results = rz_th_queue_pop_all(hits);
|
||||
}
|
||||
|
||||
if (cancel_th) {
|
||||
// stop & free cancel thread.
|
||||
rz_atomic_bool_set(ctx.loop, false);
|
||||
rz_th_wait(cancel_th);
|
||||
rz_th_free(cancel_th);
|
||||
rz_atomic_bool_free(ctx.loop);
|
||||
}
|
||||
|
||||
rz_th_lock_free(ctx.io_lock);
|
||||
rz_list_free(windows);
|
||||
rz_th_queue_free(hits);
|
||||
rz_th_queue_free(intervals);
|
||||
|
||||
rz_list_sort(results, (RzListComparator)rz_search_hit_cmp, NULL);
|
||||
rz_list_sorted_uniq(results, (RzListComparator)rz_search_hit_cmp, NULL);
|
||||
return results;
|
||||
}
|
||||
|
||||
RZ_IPI int rz_search_hit_cmp(RZ_NULLABLE RzSearchHit *a, RZ_NULLABLE RzSearchHit *b, void *user) {
|
||||
if (!a && !b) {
|
||||
return 0;
|
||||
|
|
@ -783,9 +906,15 @@ RZ_IPI int rz_search_hit_cmp(RZ_NULLABLE RzSearchHit *a, RZ_NULLABLE RzSearchHit
|
|||
} else if (!b) {
|
||||
return 1;
|
||||
}
|
||||
if (a->address == b->address) {
|
||||
if (a->address < b->address) {
|
||||
return -1;
|
||||
} else if (a->address > b->address) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (a->size == b->size) {
|
||||
return 0;
|
||||
} else if (a->address < b->address) {
|
||||
} else if (a->size < b->size) {
|
||||
return -1;
|
||||
}
|
||||
return 1;
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
#include <rz_vector.h>
|
||||
#include <rz_util/ht_uu.h>
|
||||
#include <rz_util/rz_str_search.h>
|
||||
#include "rz_util/rz_str.h"
|
||||
#include "search_internal.h"
|
||||
|
||||
typedef struct string_search {
|
||||
|
|
@ -102,7 +103,7 @@ static bool string_find(RzSearchFindOpt *fopt, void *user, ut64 offset, const Rz
|
|||
}
|
||||
|
||||
// Everything below is the slow and resource extensive route to search strings.
|
||||
// It will scan the whole buffer for strings, decoding each one with the
|
||||
// It will scan the whole buffer for strings, decode each one with the
|
||||
// correct encoding and length and match them.
|
||||
// This costs a lot. So it is only done for strings with:
|
||||
// A) A funny encodig we can't match directly with RzRegex/PCRE2 (e.g. EBCDIC).
|
||||
|
|
@ -201,13 +202,23 @@ static void string_free(void *user) {
|
|||
/**
|
||||
* \brief Allocates and initialize a string RzSearchCollection
|
||||
*
|
||||
* \param opts The RzUtilStrScanOptions options to use
|
||||
* \param[in] expected The expected encoding
|
||||
* \param opts The RzUtilStrScanOptions options to use.
|
||||
* It is allowed to be NULL iff the expected encoding is
|
||||
* Unicode, has the native machines endianness, and
|
||||
* \p alignment is the same as the encoding code point width.
|
||||
* \param[in] expected The expected encoding
|
||||
* \param[in] alignment The alignment of matches.
|
||||
*
|
||||
* \return On success returns a valid pointer, otherwise NULL
|
||||
*/
|
||||
RZ_API RZ_OWN RzSearchCollection *rz_search_collection_strings(RZ_NONNULL RzUtilStrScanOptions *opts, RzStrEnc expected) {
|
||||
rz_return_val_if_fail(opts, NULL);
|
||||
RZ_API RZ_OWN RzSearchCollection *rz_search_collection_strings(RZ_BORROW RzUtilStrScanOptions *scan_opts, RzStrEnc expected, size_t alignment) {
|
||||
if ((!rz_string_enc_is_utf_native_endian(expected) ||
|
||||
!rz_string_code_points_align(expected, alignment)) &&
|
||||
!scan_opts) {
|
||||
RZ_LOG_ERROR("Initalizeing string search collection failed: opts is not"
|
||||
"allowed to be NULL if the searched encoding has not the same endianness as the machine.\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
StringSearch *ss = RZ_NEW0(StringSearch);
|
||||
if (!ss) {
|
||||
|
|
@ -222,7 +233,9 @@ RZ_API RZ_OWN RzSearchCollection *rz_search_collection_strings(RZ_NONNULL RzUtil
|
|||
return NULL;
|
||||
}
|
||||
|
||||
ss->options = *opts; // Copy the values
|
||||
if (scan_opts) {
|
||||
ss->options = *scan_opts; // Copy because they are shared between threads.
|
||||
}
|
||||
ss->encoding = expected;
|
||||
|
||||
return rz_search_collection_new_bytes_space(string_find, string_is_empty, string_free, ss);
|
||||
|
|
|
|||
|
|
@ -1571,3 +1571,8 @@ RZ_API st64 rz_buf_sleb128(RZ_NONNULL RzBuffer *buffer, RZ_NONNULL st64 *value)
|
|||
*value = sum;
|
||||
return used;
|
||||
}
|
||||
|
||||
RZ_API RzBufferType rz_buf_type(RZ_NONNULL const RzBuffer *b) {
|
||||
rz_return_val_if_fail(b, RZ_BUFFER_INVALID);
|
||||
return b->type;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4044,7 +4044,17 @@ RZ_API bool rz_str_is_true(const char *s) {
|
|||
return !rz_str_casecmp("yes", s) || !rz_str_casecmp("on", s) || !rz_str_casecmp("true", s) || !rz_str_casecmp("1", s);
|
||||
}
|
||||
|
||||
RZ_API bool rz_str_is_false(const char *s) {
|
||||
/**
|
||||
* \brief Returns true if string is case insensitive equal to:
|
||||
* - no
|
||||
* - off
|
||||
* - false
|
||||
* - 0
|
||||
*/
|
||||
RZ_API bool rz_str_is_false(RZ_NULLABLE const char *s) {
|
||||
if (!s) {
|
||||
return false;
|
||||
}
|
||||
return !rz_str_casecmp("no", s) || !rz_str_casecmp("off", s) || !rz_str_casecmp("false", s) || !rz_str_casecmp("0", s) || !*s;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -406,6 +406,22 @@ EOF
|
|||
EXPECT_ERR=
|
||||
RUN
|
||||
|
||||
NAME=String Search - Encoding: utf8 - Hindi - Regex
|
||||
FILE=bins/cmd/search/string_encodings/Hindi-Lipsum.utf8
|
||||
CMDS=<<EOF
|
||||
/z "प.+चने वैश्विक एसलिये .+आदी" r utf8
|
||||
# Requires increasing the block size because -.-
|
||||
b 0x1000
|
||||
# Check string is actually at offset of the hit.
|
||||
ps utf8 unprintable @ hit.string.utf8.0
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x00000086 97 hit.string.utf8.0
|
||||
पहोचने वैश्विक एसलिये पुस्तक हुआआदी विश्व उनका खरिदने समस्याओ मार्गदर्शन रखते तरीके यायेका विश्लेषण पुष्टिकर्ता चुनने सकता बढाता वेबजाल गोपनीयता लिए। संपादक करेसाथ रचना नवंबर
|
||||
EOF
|
||||
EXPECT_ERR=
|
||||
RUN
|
||||
|
||||
NAME=String Search - Encoding: utf8 - Korean2
|
||||
FILE=bins/cmd/search/string_encodings/Korean2-Lipsum.utf8
|
||||
CMDS=<<EOF
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ if get_option('enable_tests') and cli_enabled
|
|||
'pdb',
|
||||
'project_migrate',
|
||||
'rzpipe',
|
||||
'str_search',
|
||||
]
|
||||
|
||||
unit_test_env = environment()
|
||||
|
|
@ -45,6 +46,7 @@ if get_option('enable_tests') and cli_enabled
|
|||
rz_socket_dep,
|
||||
rz_core_dep,
|
||||
rz_io_dep,
|
||||
rz_search_dep,
|
||||
rz_bin_dep,
|
||||
rz_flag_dep,
|
||||
rz_cons_dep,
|
||||
|
|
|
|||
212
test/integration/test_str_search.c
Normal file
212
test/integration/test_str_search.c
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
// SPDX-FileCopyrightText: 2025 Rot127 <unisono@quyllur.org>
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
|
||||
/**
|
||||
* \file Implements integration tests for the string search.
|
||||
* The code here can also serve as example how to use the search API
|
||||
* with minimal dependencies.
|
||||
*
|
||||
* NOTE: These tests must be run from `<repo_root>/test/` and
|
||||
* `git clone https://github.com/rizinorg/rizin-testbins/ <repo_root>/test/bins`
|
||||
* must have been executed before.
|
||||
*
|
||||
* For a general overview how the search is implemented see
|
||||
* librz/search/README.md
|
||||
*/
|
||||
|
||||
#include "../unit/minunit.h"
|
||||
#include <rz_core.h>
|
||||
#include "rz_list.h"
|
||||
#include "rz_search.h"
|
||||
#include "rz_util/rz_buf.h"
|
||||
#include "rz_util/rz_str.h"
|
||||
|
||||
// The files to search in.
|
||||
static const char *files[] = {
|
||||
"./bins/cmd/search/string_encodings/Hindi-Lipsum.utf8",
|
||||
"./bins/elf/analysis/hello-utf-16",
|
||||
|
||||
// Big binaries
|
||||
//
|
||||
// A very big file (1.1GB) with mixed data and Chinese characters.
|
||||
// Code points are aligned to code point width (4 bytes).
|
||||
// File is not in our rizin-testbin repo due to size.
|
||||
// But useful for performance testing.
|
||||
//
|
||||
// sha256: dc365472d8bbfdc3a3d47b5a0d8061c7d18233b131b0ed12fe599b53248629b2
|
||||
// "test/bins/test/bins/test_strings_zh.utf-32-le",
|
||||
//
|
||||
// Big endian strings to search. Search will significantly slower on little endian machines.
|
||||
// sha256: 315e96099d4c0ad7501e47f28a7781b8ed9fef0902bce5e962276a285362a684
|
||||
// "test/bins/test/bins/test_strings_zh.utf-16-be",
|
||||
};
|
||||
|
||||
// Patterns/strings to search in the files from above.
|
||||
static const char *patterns[][3] = {
|
||||
// Same Hindi strings but one is shorter.
|
||||
{ "पहोचने वैश्विक एसलिये पुस्तक हुआआदी", "प.+चने वैश्विक एसलिये .+आ", NULL },
|
||||
{ "heLLo woRlD", NULL, NULL },
|
||||
|
||||
// Big binaries' strings
|
||||
//
|
||||
// First is an actual sub string in the binary.
|
||||
// Second pattern as well, but only if interpreted in UTF-8 strings (file was generated as UTF32-le).
|
||||
// { "些 公司 任何....可", ":.{13,}", NULL },
|
||||
};
|
||||
|
||||
// One element is a single string in this context.
|
||||
// This one is the element size in bytes.
|
||||
// It should be at least as large as the maximum string length (in bytes) you expect.
|
||||
// Mind though, found strings are allowed to be larger than this.
|
||||
// Always check RzSearchHit->size for the real byte length of a string.
|
||||
#define ELEMENT_SIZE 50
|
||||
|
||||
/**
|
||||
* \brief Do a simple literal and regex search for strings in Hindi.
|
||||
*/
|
||||
int test_rz_str_search_single_simple(void) {
|
||||
// Open file as RzBuffer
|
||||
RzBuffer *file_buffer = rz_buf_new_file(files[0], O_RDONLY, 0);
|
||||
mu_assert_notnull(file_buffer, "Failed to open file");
|
||||
|
||||
// Setup search options. These are _not_ specific for the string search.
|
||||
// They are applicable to the whole search module, independently what
|
||||
// is searched (bytes, strings, cryptographic material, values...).
|
||||
// Configuring specific values is optional.
|
||||
RzSearchOpt *search_opts = rz_search_opt_new();
|
||||
mu_assert_notnull(search_opts, "NULL check failed");
|
||||
rz_search_opt_set_max_threads(search_opts, 4);
|
||||
rz_search_opt_set_max_hits(search_opts, 10);
|
||||
rz_search_opt_set_show_progress_from_str(search_opts, "no");
|
||||
rz_search_opt_set_chunk_size(search_opts, ELEMENT_SIZE);
|
||||
|
||||
// The find options allow to configure string specific settings.
|
||||
RzSearchFindOpt *find_opts = rz_search_find_opt_new();
|
||||
mu_assert_notnull(find_opts, "NULL check failed");
|
||||
|
||||
// Set alignment to 1, because we search UTF-8 and its code points are aligned to 1.
|
||||
size_t match_alignment = 1;
|
||||
rz_search_find_opt_set_alignment(find_opts, match_alignment);
|
||||
rz_search_find_opt_set_overlap_match(find_opts, false);
|
||||
|
||||
// Assign find options to the search options.
|
||||
rz_search_opt_set_find_options(search_opts, find_opts);
|
||||
|
||||
// Initialize the collection to search for.
|
||||
// We can pass NULL here to the RzUtilStrScanOptions parameter,
|
||||
// because UTF-8 is endianness independent and can directly match the buffer with PCRE2.
|
||||
// No scanning for strings is required. Hence we don't need the options for it.
|
||||
RzSearchCollection *collection = rz_search_collection_strings(NULL, RZ_STRING_ENC_UTF8, match_alignment);
|
||||
mu_assert_notnull(collection, "NULL check failed");
|
||||
|
||||
// Now add the two patterns we search for
|
||||
rz_search_collection_string_add(collection, patterns[0][0], RZ_REGEX_LITERAL, match_alignment);
|
||||
rz_search_collection_string_add(collection, patterns[0][1], RZ_REGEX_EXTENDED, match_alignment);
|
||||
|
||||
RzList *hits = rz_search_on_buffer(search_opts, collection, file_buffer);
|
||||
mu_assert_eq(rz_list_length(hits), 2, "Incorrect number of strings.");
|
||||
RzListIter *it;
|
||||
const RzSearchHit *hit;
|
||||
rz_list_foreach (hits, it, hit) {
|
||||
mu_assert_true(hit->size == 97 || hit->size == 91, "Incorrect size");
|
||||
mu_assert_eq(hit->address, 0x00000086, "Incorrect address");
|
||||
|
||||
ut8 *hit_str = RZ_NEWS0(ut8, hit->size + 1);
|
||||
mu_assert_notnull(hit_str, "NULL check failed");
|
||||
rz_buf_read_at(file_buffer, hit->address, hit_str, hit->size);
|
||||
printf("Hit 0x%" PFMT64x ", size %" PFMTSZd ": '%s'\n", hit->address, hit->size, hit_str);
|
||||
free(hit_str);
|
||||
}
|
||||
|
||||
mu_end;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Do a string search in a binary file.
|
||||
* The file is opened with as an RzIO instance. Not just as simple buffer.
|
||||
* Useful if the binary has to be analyzed beyond searching strings in it.
|
||||
*/
|
||||
int test_rz_str_search_io_simple(void) {
|
||||
RzCore *core = rz_core_new();
|
||||
mu_assert_notnull(core, "NULL check failed");
|
||||
rz_core_init(core);
|
||||
mu_assert_true(rz_core_file_open_load(core, files[1], 0, RZ_PERM_R, false), "Loading file failed");
|
||||
|
||||
// Setup search options. These are _not_ specific for the string search.
|
||||
// They are applicable to the whole search module, independently what
|
||||
// is searched (bytes, strings, cryptographic material, values...).
|
||||
// Configuring specific values is optional.
|
||||
RzSearchOpt *search_opts = rz_search_opt_new();
|
||||
mu_assert_notnull(search_opts, "NULL check failed");
|
||||
rz_search_opt_set_max_threads(search_opts, 4);
|
||||
rz_search_opt_set_max_hits(search_opts, 10);
|
||||
rz_search_opt_set_show_progress_from_str(search_opts, "no");
|
||||
rz_search_opt_set_chunk_size(search_opts, ELEMENT_SIZE);
|
||||
|
||||
// The find options allow to configure string specific settings.
|
||||
RzSearchFindOpt *find_opts = rz_search_find_opt_new();
|
||||
mu_assert_notnull(find_opts, "NULL check failed");
|
||||
|
||||
// Set alignment to 2, because we search UTF-16 and its code points are aligned to 2.
|
||||
// It is possible to also set it to any other value of course.
|
||||
// But any value not aligned to the code point width of UTF-16 (anything not a multiple of 2)
|
||||
// will slow down the search.
|
||||
// For details see librz/search/README.md
|
||||
size_t match_alignment = 2;
|
||||
rz_search_find_opt_set_alignment(find_opts, match_alignment);
|
||||
rz_search_find_opt_set_overlap_match(find_opts, false);
|
||||
|
||||
// Assign find options to the search options.
|
||||
rz_search_opt_set_find_options(search_opts, find_opts);
|
||||
|
||||
// Please refer to librz/search/README.md for an explanation why string scan options
|
||||
// are needed for an UTF-16 search.
|
||||
RzUtilStrScanOptions scan_opt = {
|
||||
.max_str_length = ELEMENT_SIZE,
|
||||
.min_str_length = 4,
|
||||
.prefer_big_endian = false,
|
||||
.check_ascii_freq = false,
|
||||
};
|
||||
|
||||
RzSearchCollection *collection = rz_search_collection_strings(&scan_opt, RZ_STRING_ENC_UTF16LE, match_alignment);
|
||||
mu_assert_notnull(collection, "NULL check failed");
|
||||
|
||||
// Now add the pattern we search for.
|
||||
rz_search_collection_string_add(collection, patterns[1][0], RZ_REGEX_CASELESS, match_alignment);
|
||||
|
||||
// Get the boundaries the strings are searched in.
|
||||
// The default address ranges are in the main config under `search.from`, `search.to`.
|
||||
// The maps to search in are in the config under `search.in`.
|
||||
RzList *boundaries = rz_core_get_boundaries_select(core, "search.from", "search.to", "search.in");
|
||||
mu_assert_notnull(boundaries, "NULL check failed");
|
||||
mu_assert_true(rz_list_length(boundaries) != 0, "The search boundaries are emtpy");
|
||||
|
||||
RzList *hits = rz_search_on_io(search_opts, collection, core->io, boundaries);
|
||||
|
||||
// Print the hits.
|
||||
// NOTE: The string address is 0x004005ea.
|
||||
// This is the virtual address where the string starts.
|
||||
// If you examine the two bytes before the string's address
|
||||
// you will notice it is preceeded by a BOM:
|
||||
// ```
|
||||
// > px 16 @ 0x004005e8
|
||||
// - offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
|
||||
// 0x004005e8 fffe 4800 6500 6c00 6c00 6f00 2000 5700 ..H.e.l.l.o. .W.
|
||||
// ```
|
||||
// So the string search does not count the BOM to the string!
|
||||
mu_assert_eq(rz_list_length(hits), 1, "Incorrect number of strings.");
|
||||
const RzSearchHit *hit = rz_list_get_n(hits, 0);
|
||||
printf("Hit at 0x%" PFMT64x " size: %" PFMTSZd "\n", hit->address, hit->size);
|
||||
mu_assert_true(hit->size == 22, "Incorrect size");
|
||||
mu_assert_eq(hit->address, 0x004005ea, "Incorrect address");
|
||||
|
||||
mu_end;
|
||||
}
|
||||
|
||||
int all_tests() {
|
||||
mu_run_test(test_rz_str_search_single_simple);
|
||||
mu_run_test(test_rz_str_search_io_simple);
|
||||
return tests_passed != tests_run;
|
||||
}
|
||||
|
||||
mu_main(all_tests)
|
||||
Loading…
Reference in a new issue