From 2e0d490d2fc6de159d005df43ac07f23f884941b Mon Sep 17 00:00:00 2001 From: Giovanni <561184+wargio@users.noreply.github.com> Date: Wed, 6 Jul 2022 11:34:00 +0200 Subject: [PATCH] Add RzBaseFindOpt for custom thread status callback (#2768) --- librz/core/basefind.c | 204 ++++++++++++++++--------------- librz/core/cbin.c | 40 +++++- librz/core/cconfig.c | 12 +- librz/include/rz_basefind.h | 32 ++++- librz/include/rz_io.h | 2 +- librz/include/rz_th.h | 1 + librz/io/io.c | 2 +- librz/util/thread_pool.c | 25 ++-- test/db/cmd/cmd_basefind | 6 +- test/integration/meson.build | 3 +- test/integration/test_basefind.c | 134 ++++++++++++++++++++ 11 files changed, 341 insertions(+), 120 deletions(-) create mode 100644 test/integration/test_basefind.c diff --git a/librz/core/basefind.c b/librz/core/basefind.c index cccf527864..dfcb924ebd 100644 --- a/librz/core/basefind.c +++ b/librz/core/basefind.c @@ -28,7 +28,7 @@ typedef struct basefind_thread_data_t { ut64 current; ut64 base_start; ut64 base_end; - ut64 base_inc; + ut64 alignment; ut64 io_size; ut32 score_min; RzThreadLock *lock; @@ -37,10 +37,11 @@ typedef struct basefind_thread_data_t { BaseFindArray *array; } BaseFindThreadData; -typedef struct basefind_thread_cons_t { - bool progress; +typedef struct basefind_ui_info_t { RzThreadPool *pool; -} BaseFindThreadCons; + void *user; + RzBaseFindThreadInfoCb callback; +} BaseFindUIInfo; static RzBinFile *basefind_new_bin_file(RzCore *core) { // Copied from cbin.c -> rz_core_bin_whole_strings_print @@ -91,7 +92,7 @@ static bool basefind_array_has(const BaseFindArray *array, ut64 value) { return false; } -static BaseFindArray *basefind_create_array_of_addresses(RzCore *core) { +static BaseFindArray *basefind_create_array_of_addresses(RzCore *core, ut32 min_string_len) { RzList *strings = NULL; BaseFindArray *array = NULL; RzBinFile *alloc = NULL; @@ -103,16 +104,10 @@ static BaseFindArray *basefind_create_array_of_addresses(RzCore *core) { } } - ut32 string_min_size = rz_config_get_i(core->config, "basefind.string.min"); - if (string_min_size < 1) { - RZ_LOG_ERROR("basefind: cannot find strings when 'basefind.string.min' is zero.\n"); - rz_goto_if_reached(error); - } - // if this list is sorted we can improve speed via half-interval search - strings = rz_bin_file_strings(current, string_min_size, true); + strings = rz_bin_file_strings(current, min_string_len, true); if (!strings || rz_list_empty(strings)) { - RZ_LOG_ERROR("basefind: cannot find strings in binary with a minimum size of %u.\n", string_min_size); + RZ_LOG_ERROR("basefind: cannot find strings in binary with a minimum size of %u.\n", min_string_len); rz_list_free(strings); return NULL; } @@ -211,7 +206,7 @@ static void *basefind_thread_runner(BaseFindThreadData *bftd) { ut64 base; bfd.array = bftd->array; - for (base = bftd->base_start; base < bftd->base_end; base += bftd->base_inc) { + for (base = bftd->base_start; base < bftd->base_end; base += bftd->alignment) { if (rz_cons_is_breaked()) { break; } @@ -249,38 +244,45 @@ static void *basefind_thread_runner(BaseFindThreadData *bftd) { return NULL; } +static void basefind_set_thread_info(BaseFindThreadData *bftd, RzBaseFindThreadInfo *th_info, ut32 thread_idx) { + ut32 percentage = ((bftd->current - bftd->base_start) * 100) / (bftd->base_end - bftd->base_start); + if (percentage > 100) { + percentage = 100; + } + + th_info->thread_idx = thread_idx; + th_info->begin_address = bftd->base_start; + th_info->current_address = bftd->current; + th_info->end_address = bftd->base_end; + th_info->percentage = percentage; +} + // this thread does not care about thread-safety since it only prints // data that will always be available during its lifetime. -static void *basefind_thread_cons(BaseFindThreadCons *th_cons) { - bool progress = th_cons->progress; - RzThreadPool *pool = th_cons->pool; - size_t pool_size = rz_th_pool_size(pool); - rz_cons_flush(); - int begin_line = rz_cons_get_cur_line(); +static void *basefind_thread_ui(BaseFindUIInfo *ui_info) { + RzThreadPool *pool = ui_info->pool; + ut32 pool_size = rz_th_pool_size(pool); + RzBaseFindThreadInfoCb callback = ui_info->callback; + void *user = ui_info->user; + RzBaseFindThreadInfo th_info; + th_info.n_threads = pool_size; + do { - if (progress) { - rz_cons_gotoxy(1, begin_line); - for (ut32 i = 0; i < pool_size; ++i) { - RzThread *th = rz_th_pool_get_thread(pool, i); - if (!th) { - continue; - } - BaseFindThreadData *bftd = rz_th_get_user(th); - ut32 perc = ((bftd->current - bftd->base_start) * 100) / (bftd->base_end - bftd->base_start); - if (perc > 100) { - perc = 100; - } - rz_cons_printf("basefind: thread %u: 0x%08" PFMT64x " / 0x%08" PFMT64x " %u%%\n", i, bftd->current, bftd->base_end, perc); + for (ut32 i = 0; i < pool_size; ++i) { + RzThread *th = rz_th_pool_get_thread(pool, i); + if (!th) { + continue; + } + BaseFindThreadData *bftd = rz_th_get_user(th); + basefind_set_thread_info(bftd, &th_info, i); + if (!callback(&th_info, user)) { + rz_th_pool_kill(pool); + goto end; } - rz_cons_flush(); - begin_line = rz_cons_get_cur_line() - pool_size; } rz_sys_usleep(100000); - if (rz_cons_is_breaked()) { - rz_th_pool_kill(pool); - break; - } } while (1); +end: return NULL; } @@ -300,68 +302,68 @@ static inline bool create_thread_interval(RzThreadPool *pool, BaseFindThreadData /** * \brief Calculates a list of possible base addresses candidates using the strings position * - * The code finds all the strings in memory with a minimum acceptable size (via basefind.string.min) + * The code finds all the strings in memory with a minimum acceptable size (via opt.min_string_len) * and calculates all possible words 32 or 64 bit large sizes (endianness via cfg.bigendian) in the * given binary. - * These addresses are then compared with the strings and a variable base address (see basefind.base.start - * and basefind.base.end) which is increased over time (see basefind.base.increase). - * The scores are ignored if below basefind.score.min otherwise they are added to the list with the - * associated base address. + * These addresses are then compared with the strings and a variable base address which is increased + * over time by opt.alignment. * - * \param core RzCore struct to use. - * \param pointer_size Pointer size in bits. - * \return RzList Sorted list of pairs (score, address) from highest score to lowest. + * The scores are added to the result list with the associated base address if their score are higher + * than opt.min_score, otherwise they are ignored. + * + * It is possible via opt.callback to set a callback function that can stop the search (when returning + * false) or display the thread statuses (the callback will be called N-times for N spawned threads. + * + * \param core RzCore struct to use. + * \param options Pointer to the RzBaseFindOpt structure. */ -RZ_API RZ_OWN RzList *rz_basefind(RZ_NONNULL RzCore *core, ut32 pointer_size) { - rz_return_val_if_fail(core, NULL); +RZ_API RZ_OWN RzList *rz_basefind(RZ_NONNULL RzCore *core, RZ_NONNULL RzBaseFindOpt *options) { + rz_return_val_if_fail(core && options, NULL); RzList *scores = NULL; BaseFindArray *array = NULL; HtUU *pointers = NULL; - ut64 base_start = 0, base_end = 0, base_inc = 0; - ut32 score_min = 0; - size_t max_threads = 0, pool_size = 1; + size_t pool_size = 1; RzThreadPool *pool = NULL; RzThreadLock *lock = NULL; - bool progress = false; + RzThread *user_thread = NULL; + BaseFindUIInfo ui_info = { 0 }; - if (pointer_size != 32 && pointer_size != 64) { + ut64 base_start = options->start_address; + ut64 base_end = options->end_address; + ut64 alignment = options->alignment; + + if (options->pointer_size != 32 && options->pointer_size != 64) { RZ_LOG_ERROR("basefind: supported pointer sizes are 32 and 64 bits.\n"); return NULL; - } - pointer_size /= 8; - - if (!core->file) { - RZ_LOG_ERROR("basefind: not file was opened via RzCore.\n"); + } else if (!core->file) { + RZ_LOG_ERROR("basefind: the file was not opened via RzCore.\n"); + return NULL; + } else if (base_start >= base_end) { + RZ_LOG_ERROR("basefind: start address is greater or equal to end address.\n"); + return NULL; + } else if (alignment < 1) { + RZ_LOG_ERROR("basefind: the alignment is set to zero bytes.\n"); + return NULL; + } else if (options->min_score < 1) { + RZ_LOG_ERROR("basefind: the minimum score is set to zero.\n"); + return NULL; + } else if (options->min_string_len < 1) { + RZ_LOG_ERROR("basefind: the minimum string length is set to zero.\n"); return NULL; } - base_start = rz_config_get_i(core->config, "basefind.base.start"); - base_end = rz_config_get_i(core->config, "basefind.base.end"); - base_inc = rz_config_get_i(core->config, "basefind.base.increase"); - score_min = rz_config_get_i(core->config, "basefind.score.min"); - max_threads = rz_config_get_i(core->config, "basefind.threads.max"); - progress = rz_config_get_b(core->config, "basefind.progress"); - - if (base_start >= base_end) { - RZ_LOG_ERROR("basefind: option 'basefind.base.start' is greater or equal to 'basefind.base.end'.\n"); - return NULL; - } else if (base_inc < 1) { - RZ_LOG_ERROR("basefind: option 'basefind.base.increase' is zero.\n"); - return NULL; - } else if (base_inc < RZ_BASEFIND_BASE_INCREASE) { - RZ_LOG_WARN("basefind: option 'basefind.base.increase' is less than 0x%x, which may result in a very slow search.\n", RZ_BASEFIND_BASE_INCREASE); + if (alignment < RZ_BASEFIND_BASE_ALIGNMENT) { + RZ_LOG_WARN("basefind: the alignment is less than 0x%x bytes, " + "which may result in a very slow search.\n", + RZ_BASEFIND_BASE_ALIGNMENT); } - if (score_min < 1) { - RZ_LOG_WARN("basefind: option 'basefind.score.min' zero, which may result in a long list of results.\n"); - } - - array = basefind_create_array_of_addresses(core); + array = basefind_create_array_of_addresses(core, options->min_string_len); if (!array) { goto rz_basefind_end; } - pointers = basefind_create_pointer_map(core, pointer_size); + pointers = basefind_create_pointer_map(core, options->pointer_size / 8); if (!pointers) { goto rz_basefind_end; } @@ -372,7 +374,7 @@ RZ_API RZ_OWN RzList *rz_basefind(RZ_NONNULL RzCore *core, ut32 pointer_size) { goto rz_basefind_end; } - pool = rz_th_pool_new(max_threads); + pool = rz_th_pool_new(options->max_threads); if (!pool) { RZ_LOG_ERROR("basefind: cannot allocate thread pool.\n"); goto rz_basefind_end; @@ -396,11 +398,11 @@ RZ_API RZ_OWN RzList *rz_basefind(RZ_NONNULL RzCore *core, ut32 pointer_size) { rz_th_pool_kill(pool); goto rz_basefind_end; } - bftd->base_inc = base_inc; + bftd->alignment = alignment; bftd->base_start = base_start + (sector_size * i); bftd->current = bftd->base_start; bftd->base_end = bftd->base_start + sector_size; - bftd->score_min = score_min; + bftd->score_min = options->min_score; bftd->io_size = io_size; bftd->lock = lock; bftd->scores = scores; @@ -413,22 +415,32 @@ RZ_API RZ_OWN RzList *rz_basefind(RZ_NONNULL RzCore *core, ut32 pointer_size) { } } - BaseFindThreadCons th_cons; - th_cons.progress = progress; - th_cons.pool = pool; - - RzThread *cons_thread = rz_th_new((RzThreadFunction)basefind_thread_cons, &th_cons); - if (!cons_thread) { - rz_th_pool_kill(pool); - goto rz_basefind_end; + if (options->callback) { + ui_info.pool = pool; + ui_info.user = options->user; + ui_info.callback = options->callback; + user_thread = rz_th_new((RzThreadFunction)basefind_thread_ui, &ui_info); + if (!user_thread) { + rz_th_pool_kill(pool); + goto rz_basefind_end; + } } rz_th_pool_wait(pool); - if (progress) { - // ensure to print the 100% - rz_sys_usleep(100000); + if (options->callback) { + RzBaseFindThreadInfo th_info; + th_info.n_threads = pool_size; + for (ut32 i = 0; i < pool_size; ++i) { + RzThread *th = rz_th_pool_get_thread(pool, i); + if (!th) { + continue; + } + BaseFindThreadData *bftd = rz_th_get_user(th); + basefind_set_thread_info(bftd, &th_info, i); + options->callback(&th_info, options->user); + } + rz_th_kill(user_thread); + rz_th_free(user_thread); } - rz_th_kill(cons_thread); - rz_th_free(cons_thread); rz_list_sort(scores, (RzListComparator)basefind_score_compare); diff --git a/librz/core/cbin.c b/librz/core/cbin.c index b4fa4942b4..69ff7a4365 100644 --- a/librz/core/cbin.c +++ b/librz/core/cbin.c @@ -2554,12 +2554,50 @@ RZ_API bool rz_core_bin_cur_segment_print(RzCore *core, RzBinFile *bf, RzCmdStat return rz_core_bin_segments_print(core, bf, state, &filter, hashes); } +static bool core_basefind_progess_status(const RzBaseFindThreadInfo *th_info, void *user) { + rz_cons_flush(); + rz_cons_printf("basefind: thread %u: 0x%08" PFMT64x " / 0x%08" PFMT64x " %u%%\n", + th_info->thread_idx, th_info->current_address, + th_info->end_address, th_info->percentage); + rz_cons_flush(); + if ((th_info->thread_idx + 1) >= th_info->n_threads) { + rz_cons_gotoxy(1, rz_cons_get_cur_line() - th_info->n_threads); + } + return !rz_cons_is_breaked(); +} + +static bool core_basefind_check_ctrl_c(const RzBaseFindThreadInfo *th_info, void *user) { + return !rz_cons_is_breaked(); +} + RZ_API bool rz_core_bin_basefind_print(RzCore *core, ut32 pointer_size, RzCmdStateOutput *state) { rz_return_val_if_fail(core && state, false); RzListIter *it = NULL; RzBaseFindScore *pair = NULL; + RzBaseFindOpt options; + bool progress = rz_config_get_b(core->config, "basefind.progress"); + int begin_line = rz_cons_get_cur_line(); + + options.pointer_size = pointer_size; + options.start_address = rz_config_get_i(core->config, "basefind.search.start"); + options.end_address = rz_config_get_i(core->config, "basefind.search.end"); + options.alignment = rz_config_get_i(core->config, "basefind.alignment"); + options.max_threads = rz_config_get_i(core->config, "basefind.max.threads"); + options.min_score = rz_config_get_i(core->config, "basefind.min.score"); + options.min_string_len = rz_config_get_i(core->config, "basefind.min.string"); + options.callback = progress ? core_basefind_progess_status : core_basefind_check_ctrl_c; + options.user = NULL; + + RzList *scores = rz_basefind(core, &options); + + if (progress) { + // ensure the last printed line is actually the last expected line + // this depends on the number of the threads requested and available + // this requires to be called before checking the results + int n_cores = (int)rz_th_request_physical_cores(options.max_threads); + rz_cons_gotoxy(1, begin_line + n_cores); + } - RzList *scores = rz_basefind(core, pointer_size); if (!scores) { return false; } diff --git a/librz/core/cconfig.c b/librz/core/cconfig.c index 76717151a6..74e43b2dcc 100644 --- a/librz/core/cconfig.c +++ b/librz/core/cconfig.c @@ -3647,12 +3647,12 @@ RZ_API int rz_core_config_init(RzCore *core) { /* basefind */ SETB("basefind.progress", false, "Basefind threads progress (true: enable, false: disable)"); - SETI("basefind.base.start", RZ_BASEFIND_BASE_MIN_ADDRESS, "Basefind start address value"); - SETI("basefind.base.end", RZ_BASEFIND_BASE_MAX_ADDRESS, "Basefind end address value"); - SETI("basefind.base.increase", RZ_BASEFIND_BASE_INCREASE, "Basefind increase address by"); - SETI("basefind.score.min", RZ_BASEFIND_SCORE_MIN_VALUE, "Basefind min score value to consider it valid"); - SETI("basefind.string.min", RZ_BASEFIND_STRING_MIN_LENGTH, "Basefind min string size to find to consider it valid"); - SETI("basefind.threads.max", RZ_THREAD_POOL_ALL_CORES, "Basefind max threads number (when 0 uses all available cores)"); + SETI("basefind.search.start", RZ_BASEFIND_BASE_MIN_ADDRESS, "Basefind start search address"); + SETI("basefind.search.end", RZ_BASEFIND_BASE_MAX_ADDRESS, "Basefind end search address"); + SETI("basefind.alignment", RZ_BASEFIND_BASE_ALIGNMENT, "Basefind alignment in bytes"); + SETI("basefind.min.score", RZ_BASEFIND_SCORE_MIN_VALUE, "Basefind min score value to consider it valid"); + SETI("basefind.min.string", RZ_BASEFIND_STRING_MIN_LENGTH, "Basefind min string size to find to consider it valid"); + SETI("basefind.max.threads", RZ_THREAD_POOL_ALL_CORES, "Basefind max threads number (when 0 uses all available cores)"); /* nkeys */ SETPREF("key.s", "", "override step into action"); diff --git a/librz/include/rz_basefind.h b/librz/include/rz_basefind.h index 0726fe7ab2..a5fc0d57cb 100644 --- a/librz/include/rz_basefind.h +++ b/librz/include/rz_basefind.h @@ -13,15 +13,39 @@ extern "C" { #define RZ_BASEFIND_STRING_MIN_LENGTH (10) #define RZ_BASEFIND_BASE_MIN_ADDRESS (0ull) #define RZ_BASEFIND_BASE_MAX_ADDRESS (0xf0000000ull) -#define RZ_BASEFIND_BASE_INCREASE (0x1000) +#define RZ_BASEFIND_BASE_ALIGNMENT (0x1000) #define RZ_BASEFIND_SCORE_MIN_VALUE (1) typedef struct rz_basefind_t { - ut64 candidate; - ut32 score; + ut64 candidate; ///< Candidate physical base address + ut32 score; ///< Score of the candidate address } RzBaseFindScore; -RZ_API RZ_OWN RzList *rz_basefind(RZ_NONNULL RzCore *core, ut32 pointer_size); +typedef struct rz_basefind_info_t { + ut32 n_threads; ///< Total number of search threads. + ut32 thread_idx; ///< Sesarch thread number. + ut64 begin_address; ///< Thread related search address (start). + ut64 current_address; ///< Thread related search address (current). + ut64 end_address; ///< Thread related search address (end). + ut32 percentage; ///< Progress made by the search thread. +} RzBaseFindThreadInfo; + +// Used to provide user information regarding the running threads and to stop the execution when needed. +typedef bool (*RzBaseFindThreadInfoCb)(const RzBaseFindThreadInfo *th_info, void *user); + +typedef struct rz_basefind_options_t { + size_t max_threads; ///< Max requested number of threads (not guaranteed). + ut32 pointer_size; ///< Pointer size in bits (32 or 64) + ut64 start_address; ///< Start search address + ut64 end_address; ///< End search address + ut64 alignment; ///< Memory alignment in bytes (suggested to set it to RZ_BASEFIND_BASE_ALIGNMENT) + ut32 min_score; ///< Minimum score to reach to be part of the list of possible addresses + ut32 min_string_len; ///< Min string length to search for + RzBaseFindThreadInfoCb callback; ///< When set allows to get the thread information + void *user; ///< User pointer to pass to the callback function for the thread info +} RzBaseFindOpt; + +RZ_API RZ_OWN RzList *rz_basefind(RZ_NONNULL RzCore *core, RZ_NONNULL RzBaseFindOpt *options); #ifdef __cplusplus } diff --git a/librz/include/rz_io.h b/librz/include/rz_io.h index afc2282e1d..c3b28fc617 100644 --- a/librz/include/rz_io.h +++ b/librz/include/rz_io.h @@ -329,7 +329,7 @@ RZ_API bool rz_io_read_at_mapped(RzIO *io, ut64 addr, ut8 *buf, int len); RZ_API int rz_io_nread_at(RzIO *io, ut64 addr, ut8 *buf, int len); RZ_API bool rz_io_write_at(RzIO *io, ut64 addr, const ut8 *buf, int len); RZ_API bool rz_io_read(RzIO *io, ut8 *buf, int len); -RZ_API bool rz_io_write(RzIO *io, ut8 *buf, int len); +RZ_API bool rz_io_write(RzIO *io, const ut8 *buf, int len); RZ_API ut64 rz_io_size(RzIO *io); RZ_API bool rz_io_is_listener(RzIO *io); RZ_API char *rz_io_system(RzIO *io, const char *cmd); diff --git a/librz/include/rz_th.h b/librz/include/rz_th.h index bab8fed853..25d6314853 100644 --- a/librz/include/rz_th.h +++ b/librz/include/rz_th.h @@ -58,6 +58,7 @@ RZ_API void rz_th_cond_wait(RZ_NONNULL RzThreadCond *cond, RZ_NONNULL RzThreadLo RZ_API void rz_th_cond_free(RZ_NULLABLE RzThreadCond *cond); RZ_API size_t rz_th_physical_core_number(); +RZ_API size_t rz_th_request_physical_cores(size_t max_cores); RZ_API RZ_OWN RzThreadPool *rz_th_pool_new(size_t max_threads); RZ_API void rz_th_pool_free(RZ_NULLABLE RzThreadPool *pool); RZ_API bool rz_th_pool_add_thread(RZ_NONNULL RzThreadPool *pool, RZ_NONNULL RzThread *thread); diff --git a/librz/io/io.c b/librz/io/io.c index 1d6ef9723c..d42bcbe889 100644 --- a/librz/io/io.c +++ b/librz/io/io.c @@ -388,7 +388,7 @@ RZ_API bool rz_io_read(RzIO *io, ut8 *buf, int len) { return false; } -RZ_API bool rz_io_write(RzIO *io, ut8 *buf, int len) { +RZ_API bool rz_io_write(RzIO *io, const ut8 *buf, int len) { if (io && buf && len > 0 && rz_io_write_at(io, io->off, buf, len)) { io->off += len; return true; diff --git a/librz/util/thread_pool.c b/librz/util/thread_pool.c index eabc90d557..10a12ab39c 100644 --- a/librz/util/thread_pool.c +++ b/librz/util/thread_pool.c @@ -67,6 +67,22 @@ RZ_API size_t rz_th_physical_core_number() { #endif } +/** + * \brief Returns the maximum number of cores available regardless of the number of cores requested. + * When set to 0, it will be the max number of physical cores. + * + * \param[in] max_cores The maximum number of physical cores to request + * + * \return The actual max number of cores available + */ +RZ_API size_t rz_th_request_physical_cores(size_t max_cores) { + size_t n_cores = rz_th_physical_core_number(); + if (!max_cores) { + return n_cores; + } + return RZ_MIN(n_cores, max_cores); +} + /** * \brief returns a new RzThreadPool structure with a pool of thread * @@ -83,13 +99,8 @@ RZ_API RZ_OWN RzThreadPool *rz_th_pool_new(size_t max_threads) { return NULL; } - size_t cores = rz_th_physical_core_number(); - if (max_threads) { - cores = RZ_MIN(cores, max_threads); - } - - pool->size = cores; - pool->threads = RZ_NEWS0(RzThread *, cores); + pool->size = rz_th_request_physical_cores(max_threads); + pool->threads = RZ_NEWS0(RzThread *, pool->size); if (!pool->threads) { free(pool); return NULL; diff --git a/test/db/cmd/cmd_basefind b/test/db/cmd/cmd_basefind index 1badf56206..c497d5362a 100644 --- a/test/db/cmd/cmd_basefind +++ b/test/db/cmd/cmd_basefind @@ -2,9 +2,9 @@ NAME=basefind via rizin FILE=bins/firmware/stm32f103-dapboot-v1.20-bluepill.bin CMDS=< +// SPDX-License-Identifier: LGPL-3.0-only + +#include +#include +#include "../unit/minunit.h" + +static void basefind_options_set_valid(RzBaseFindOpt *options) { + options->start_address = 0; + options->end_address = 4096; + options->pointer_size = 32; + options->min_score = 1; + options->min_string_len = 10; + options->alignment = RZ_BASEFIND_BASE_ALIGNMENT; + options->max_threads = 1; + options->callback = NULL; + options->user = NULL; +} + +static bool test_basefind_callback_false(const RzBaseFindThreadInfo *th_info, void *user) { + return false; +} + +static bool test_basefind_callback_true(const RzBaseFindThreadInfo *th_info, void *user) { + return true; +} + +int test_rz_basefind_with_callbacks(void) { + RzBaseFindOpt options; + RzList *result = NULL; + RzCore *core = rz_core_new(); + rz_core_file_open_load(core, "bins/firmware/stm32f103-dapboot-v1.20-bluepill.bin", 0, RZ_PERM_R, false); + + // test_basefind_callback_true + basefind_options_set_valid(&options); + options.callback = test_basefind_callback_true; + result = rz_basefind(core, &options); + mu_assert_notnull(result, "valid callback (true)"); + rz_list_free(result); + + // test_basefind_callback_false + basefind_options_set_valid(&options); + options.callback = test_basefind_callback_false; + result = rz_basefind(core, &options); + mu_assert_notnull(result, "valid callback (false)"); + rz_list_free(result); + + rz_core_free(core); + mu_end; +} + +int test_rz_basefind_no_callback(void) { + RzBaseFindOpt options; + RzList *result = NULL; + RzCore *core = rz_core_new(); + rz_core_file_open_load(core, "bins/firmware/stm32f103-dapboot-v1.20-bluepill.bin", 0, RZ_PERM_R, false); + + // valid configuration + basefind_options_set_valid(&options); + result = rz_basefind(core, &options); + mu_assert_notnull(result, "valid pointer_size 32"); + rz_list_free(result); + + // valid configuration + basefind_options_set_valid(&options); + options.pointer_size = 64; + result = rz_basefind(core, &options); + mu_assert_notnull(result, "valid pointer_size 64"); + rz_list_free(result); + + // pointer_size + basefind_options_set_valid(&options); + options.pointer_size = 77; + result = rz_basefind(core, &options); + mu_assert_null(result, "invalid pointer_size"); + + // min_score + basefind_options_set_valid(&options); + options.min_score = 0; + result = rz_basefind(core, &options); + mu_assert_null(result, "invalid min_score"); + + // min_string_len + basefind_options_set_valid(&options); + options.min_string_len = 0; + result = rz_basefind(core, &options); + mu_assert_null(result, "invalid min_string_len"); + + // alignment + basefind_options_set_valid(&options); + options.alignment = 0; + result = rz_basefind(core, &options); + mu_assert_null(result, "invalid alignment"); + + // start == end + basefind_options_set_valid(&options); + options.start_address = 0x1111; + options.end_address = 0x1111; + result = rz_basefind(core, &options); + mu_assert_null(result, "invalid address (start == end)."); + + // start > end + basefind_options_set_valid(&options); + options.start_address = 0x1111; + options.end_address = 0x77; + result = rz_basefind(core, &options); + mu_assert_null(result, "invalid address (start > end)."); + + rz_core_free(core); + mu_end; +} + +int test_rz_basefind_no_core_load(void) { + RzBaseFindOpt options; + RzList *result = NULL; + RzCore *core = rz_core_new(); + + rz_io_open(core->io, "bins/firmware/stm32f103-dapboot-v1.20-bluepill.bin", RZ_PERM_R, 0); + basefind_options_set_valid(&options); + result = rz_basefind(core, &options); + mu_assert_null(result, "file not loaded via core"); + + rz_core_free(core); + mu_end; +} + +int all_tests() { + mu_run_test(test_rz_basefind_no_core_load); + mu_run_test(test_rz_basefind_no_callback); + mu_run_test(test_rz_basefind_with_callbacks); + return tests_passed != tests_run; +} + +mu_main(all_tests)