// SPDX-FileCopyrightText: 2019 pancake // SPDX-FileCopyrightText: 2019 thestr4ng3r // SPDX-License-Identifier: LGPL-3.0-only #include static bool get_functions_block_cb(RzAnalysisBlock *block, void *user) { RzList *list = user; RzListIter *iter; RzAnalysisFunction *fcn; rz_list_foreach (block->fcns, iter, fcn) { if (rz_list_contains(list, fcn)) { continue; } rz_list_push(list, fcn); } return true; } RZ_API RzList /**/ *rz_analysis_get_functions_in(RzAnalysis *analysis, ut64 addr) { RzList *list = rz_list_new(); if (!list) { return NULL; } rz_analysis_blocks_foreach_in(analysis, addr, get_functions_block_cb, list); return list; } static bool get_function_block_cb(RzAnalysisBlock *block, void *user) { RzAnalysisFunction **pfcn = user; RzListIter *iter; RzAnalysisFunction *fcn; rz_list_foreach (block->fcns, iter, fcn) { *pfcn = fcn; break; } return true; } /** * \brief Returns the first function that have a basic block containing the given address \p addr * * \param analysis A pointer to the `RzAnalysis` object used for analysis. * \param addr The address to find the function in. * * \return RzAnalysisFunction* Pointer to the `RzAnalysisFunction` object if found, otherwise NULL. */ RZ_API RZ_BORROW RzAnalysisFunction *rz_analysis_first_function_in(RZ_NONNULL RZ_BORROW RzAnalysis *analysis, ut64 addr) { rz_return_val_if_fail(analysis, NULL); RzAnalysisFunction *fcn = NULL; rz_analysis_blocks_foreach_in(analysis, addr, get_function_block_cb, &fcn); return fcn; } // check if name is already registered static bool function_name_exists(RzAnalysis *analysis, const char *name, ut64 addr) { bool found = false; if (addr == UT64_MAX) { RZ_LOG_ERROR("Invalid function address (-1) '%s'\n", name); return true; } if (!name) { RZ_LOG_INFO("Empty function name, we must auto generate one\n"); return true; } RzAnalysisFunction *f = ht_pp_find(analysis->ht_name_fun, name, &found); if (f && found) { return true; } return false; } // check if there's a function already in the given address static bool function_already_defined_at(RzAnalysis *analysis, const char *name, ut64 addr) { bool found = false; RzAnalysisFunction *f = ht_up_find(analysis->ht_addr_fun, addr, &found); if (f && found) { return true; } return false; } static void inst_vars_kv_free(HtUPKv *kv) { rz_pvector_free(kv->value); } static void labels_kv_free(HtUPKv *kv) { free(kv->value); } static void label_addrs_kv_free(HtPPKv *kv) { free(kv->key); free(kv->value); } RZ_API RzAnalysisFunction *rz_analysis_function_new(RzAnalysis *analysis) { RzAnalysisFunction *fcn = RZ_NEW0(RzAnalysisFunction); if (!fcn) { return NULL; } fcn->analysis = analysis; fcn->addr = UT64_MAX; fcn->cc = rz_str_constpool_get(&analysis->constpool, rz_analysis_cc_default(analysis)); fcn->bits = analysis->bits; fcn->bbs = rz_list_new(); fcn->has_changed = true; fcn->bp_frame = true; fcn->is_noreturn = false; fcn->meta._min = UT64_MAX; rz_pvector_init(&fcn->vars, (RzPVectorFree)rz_analysis_var_free); fcn->inst_vars = ht_up_new(NULL, inst_vars_kv_free, NULL); fcn->labels = ht_up_new(NULL, labels_kv_free, NULL); fcn->label_addrs = ht_pp_new(NULL, label_addrs_kv_free, NULL); return fcn; } RZ_API void rz_analysis_function_free(void *_fcn) { RzAnalysisFunction *fcn = _fcn; if (!_fcn) { return; } RzAnalysisBlock *block; RzListIter *iter; rz_list_foreach (fcn->bbs, iter, block) { rz_list_delete_data(block->fcns, fcn); rz_analysis_block_unref(block); } rz_list_free(fcn->bbs); RzAnalysis *analysis = fcn->analysis; if (ht_up_find(analysis->ht_addr_fun, fcn->addr, NULL) == _fcn) { ht_up_delete(analysis->ht_addr_fun, fcn->addr); } if (ht_pp_find(analysis->ht_name_fun, fcn->name, NULL) == _fcn) { ht_pp_delete(analysis->ht_name_fun, fcn->name); } rz_pvector_fini(&fcn->vars); ht_up_free(fcn->inst_vars); ht_up_free(fcn->labels); ht_pp_free(fcn->label_addrs); rz_type_free(fcn->ret_type); free(fcn->name); rz_list_free(fcn->imports); free(fcn); } /** * \brief Adds a new function to the analysis * * \param analysis The current RzAnalysis. * \param fcn The RzAnalysisFunction to add. * * \return True in case of success or false if it already exists. */ RZ_API bool rz_analysis_add_function(RzAnalysis *analysis, RzAnalysisFunction *fcn) { rz_return_val_if_fail(analysis && fcn, false); if (function_name_exists(analysis, fcn->name, fcn->addr)) { RZ_LOG_WARN("Function name '%s' already exists\n", fcn->name); return false; } if (function_already_defined_at(analysis, fcn->name, fcn->addr)) { RZ_LOG_WARN("Function '%s' already defined at specified address 0x%08" PFMT64x "\n", fcn->name, fcn->addr); return false; } if (analysis->cb.on_fcn_new) { analysis->cb.on_fcn_new(analysis, analysis->core, fcn); } if (analysis->flg_fcn_set) { analysis->flg_fcn_set(analysis->flb.f, fcn->name, fcn->addr, rz_analysis_function_size_from_entry(fcn)); } fcn->is_noreturn = rz_analysis_noreturn_at_addr(analysis, fcn->addr); rz_list_append(analysis->fcns, fcn); return ht_pp_insert(analysis->ht_name_fun, fcn->name, fcn) && ht_up_insert(analysis->ht_addr_fun, fcn->addr, fcn); } RZ_API RzAnalysisFunction *rz_analysis_create_function(RzAnalysis *analysis, const char *name, ut64 addr, RzAnalysisFcnType type) { RzAnalysisFunction *fcn = rz_analysis_function_new(analysis); if (!fcn) { return NULL; } fcn->addr = addr; fcn->type = type; fcn->cc = rz_str_constpool_get(&analysis->constpool, rz_analysis_cc_default(analysis)); fcn->bits = analysis->bits; if (name) { free(fcn->name); fcn->name = strdup(name); } else { const char *fcnprefix = analysis->coreb.cfgGet ? analysis->coreb.cfgGet(analysis->coreb.core, "analysis.fcnprefix") : NULL; if (RZ_STR_ISEMPTY(fcnprefix)) { fcnprefix = "fcn"; } fcn->name = rz_str_newf("%s.%08" PFMT64x, fcnprefix, fcn->addr); } if (!rz_analysis_add_function(analysis, fcn)) { rz_analysis_function_free(fcn); return NULL; } return fcn; } RZ_API bool rz_analysis_function_delete(RzAnalysisFunction *fcn) { return rz_list_delete_data(fcn->analysis->fcns, fcn); } /** * \brief Returns the function which has its entrypoint at \p addr or NULL if non was found. * * \param analysis The current RzAnalysis. * \param addr The address of the function to get. * * \return The function with an entrypoint at \p addr or NULL if non was found. */ RZ_API RzAnalysisFunction *rz_analysis_get_function_at(const RzAnalysis *analysis, ut64 addr) { bool found = false; RzAnalysisFunction *f = ht_up_find(analysis->ht_addr_fun, addr, &found); if (f && found) { return f; } return NULL; } typedef struct { HtUP *inst_vars_new; st64 delta; } InstVarsRelocateCtx; static bool inst_vars_relocate_cb(void *user, const ut64 k, const void *v) { InstVarsRelocateCtx *ctx = user; ht_up_insert(ctx->inst_vars_new, k - ctx->delta, (void *)v); return true; } RZ_API bool rz_analysis_function_relocate(RzAnalysisFunction *fcn, ut64 addr) { if (fcn->addr == addr) { return true; } if (rz_analysis_get_function_at(fcn->analysis, addr)) { return false; } ht_up_delete(fcn->analysis->ht_addr_fun, fcn->addr); // relocate the var accesses (their addrs are relative to the function addr) st64 delta = (st64)addr - (st64)fcn->addr; void **it; rz_pvector_foreach (&fcn->vars, it) { RzAnalysisVar *var = *it; RzAnalysisVarAccess *acc; rz_vector_foreach(&var->accesses, acc) { acc->offset -= delta; } } InstVarsRelocateCtx ctx = { .inst_vars_new = ht_up_new(NULL, inst_vars_kv_free, NULL), .delta = delta }; if (ctx.inst_vars_new) { ht_up_foreach(fcn->inst_vars, inst_vars_relocate_cb, &ctx); // Do not free the elements of the Ht, because they were moved to ctx.inst_vars_new fcn->inst_vars->opt.freefn = NULL; ht_up_free(fcn->inst_vars); fcn->inst_vars = ctx.inst_vars_new; } fcn->addr = addr; ht_up_insert(fcn->analysis->ht_addr_fun, addr, fcn); return true; } RZ_API bool rz_analysis_function_rename(RzAnalysisFunction *fcn, const char *name) { RzAnalysis *analysis = fcn->analysis; RzAnalysisFunction *existing = ht_pp_find(analysis->ht_name_fun, name, NULL); if (existing) { if (existing == fcn) { // fcn->name == name, nothing to do return true; } return false; } char *newname = strdup(name); if (!newname) { return false; } bool in_tree = ht_pp_delete(analysis->ht_name_fun, fcn->name); free(fcn->name); fcn->name = newname; if (in_tree) { // only re-insert if it really was in the tree before ht_pp_insert(analysis->ht_name_fun, fcn->name, fcn); } return true; } RZ_API void rz_analysis_function_add_block(RzAnalysisFunction *fcn, RzAnalysisBlock *bb) { if (rz_list_contains(bb->fcns, fcn)) { return; } rz_list_append(bb->fcns, fcn); // associate the given fcn with this bb rz_analysis_block_ref(bb); rz_list_append(fcn->bbs, bb); if (fcn->meta._min != UT64_MAX) { if (bb->addr + bb->size > fcn->meta._max) { fcn->meta._max = bb->addr + bb->size; } if (bb->addr < fcn->meta._min) { fcn->meta._min = bb->addr; } } if (fcn->analysis->cb.on_fcn_bb_new) { fcn->analysis->cb.on_fcn_bb_new(fcn->analysis, fcn->analysis->core, fcn, bb); } } RZ_API void rz_analysis_function_remove_block(RzAnalysisFunction *fcn, RzAnalysisBlock *bb) { rz_list_delete_data(bb->fcns, fcn); if (fcn->meta._min != UT64_MAX && (fcn->meta._min == bb->addr || fcn->meta._max == bb->addr + bb->size)) { // If a block is removed at the beginning or end, updating min/max is not trivial anymore, just invalidate fcn->meta._min = UT64_MAX; } rz_list_delete_data(fcn->bbs, bb); rz_analysis_block_unref(bb); } static void ensure_fcn_range(RzAnalysisFunction *fcn) { if (fcn->meta._min != UT64_MAX) { // recalculate only if invalid return; } ut64 minval = UT64_MAX; ut64 maxval = UT64_MIN; RzAnalysisBlock *block; RzListIter *iter; rz_list_foreach (fcn->bbs, iter, block) { if (block->addr < minval) { minval = block->addr; } if (block->addr + block->size > maxval) { maxval = block->addr + block->size; } } fcn->meta._min = minval; fcn->meta._max = minval == UT64_MAX ? UT64_MAX : maxval; } RZ_API ut64 rz_analysis_function_linear_size(RzAnalysisFunction *fcn) { ensure_fcn_range(fcn); return fcn->meta._max - fcn->meta._min; } RZ_API ut64 rz_analysis_function_min_addr(RzAnalysisFunction *fcn) { ensure_fcn_range(fcn); return fcn->meta._min; } RZ_API ut64 rz_analysis_function_max_addr(RzAnalysisFunction *fcn) { ensure_fcn_range(fcn); return fcn->meta._max; } RZ_API ut64 rz_analysis_function_size_from_entry(RzAnalysisFunction *fcn) { ensure_fcn_range(fcn); return fcn->meta._min == UT64_MAX ? 0 : fcn->meta._max - fcn->addr; } RZ_API ut64 rz_analysis_function_realsize(const RzAnalysisFunction *fcn) { RzListIter *iter; RzAnalysisBlock *bb; ut64 sz = 0; if (!sz) { rz_list_foreach (fcn->bbs, iter, bb) { sz += bb->size; } } return sz; } static bool fcn_in_cb(RzAnalysisBlock *block, void *user) { RzListIter *iter; RzAnalysisFunction *fcn; rz_list_foreach (block->fcns, iter, fcn) { if (fcn == user) { return false; } } return true; } RZ_API bool rz_analysis_function_contains(RzAnalysisFunction *fcn, ut64 addr) { // fcn_in_cb breaks with false if it finds the fcn return !rz_analysis_blocks_foreach_in(fcn->analysis, addr, fcn_in_cb, fcn); } RZ_API bool rz_analysis_function_was_modified(RzAnalysisFunction *fcn) { rz_return_val_if_fail(fcn, false); RzListIter *it; RzAnalysisBlock *bb; rz_list_foreach (fcn->bbs, it, bb) { if (rz_analysis_block_was_modified(bb)) { return true; } } return false; } RZ_API RZ_BORROW RzList /**/ *rz_analysis_function_list(RzAnalysis *analysis) { rz_return_val_if_fail(analysis, NULL); return analysis->fcns; } #define MIN_MATCH_LEN 4 static RZ_OWN char *function_name_try_guess(RzTypeDB *typedb, RZ_NONNULL char *name) { if (strlen(name) < MIN_MATCH_LEN) { return NULL; } if (rz_type_func_exist(typedb, name)) { return strdup(name); } return NULL; } static inline bool is_auto_named(char *func_name, size_t slen) { return slen > 4 && (rz_str_startswith(func_name, "fcn.") || rz_str_startswith(func_name, "loc.")); } static inline bool has_rz_prefixes(char *func_name, int offset, size_t slen) { return slen > 4 && (offset + 3 < slen) && func_name[offset + 3] == '.'; } static char *strip_rz_prefixes(char *func_name, size_t slen) { // strip rizin prefixes (sym, sym.imp, etc') int offset = 0; while (has_rz_prefixes(func_name, offset, slen)) { offset += 4; } return func_name + offset; } static char *strip_common_prefixes_stdlib(char *func_name) { // strip common prefixes from standard lib functions if (rz_str_startswith(func_name, "__isoc99_")) { func_name += 9; } else if (rz_str_startswith(func_name, "__libc_") && !strstr(func_name, "_main")) { func_name += 7; } else if (rz_str_startswith(func_name, "__GI_")) { func_name += 5; } return func_name; } static char *strip_dll_prefix(char *func_name) { char *tmp = strstr(func_name, "dll_"); if (tmp) { return tmp + 3; } return func_name; } static void clean_function_name(char *func_name) { char *last = (char *)rz_str_lchr(func_name, '_'); if (!last || !rz_str_isnumber(last + 1)) { return; } *last = '\0'; } /** * \brief Checks if the function name was generated by Rizin automatically */ RZ_API bool rz_analysis_function_is_autonamed(RZ_NONNULL char *name) { size_t len = strlen(name); return (len >= MIN_MATCH_LEN) && (is_auto_named(name, len) || has_rz_prefixes(name, 0, len)); } /** * \brief Checks if varions function name variations present in the database * * Tries to remove different prefixes from the Rizin autonames, * standard libraries prefixes, various Windows-specific prefixes and checks * every attempt in the function database. If there is a match - returns it. * * \param typedb RzTypeDB instance * \param name Function name to check */ RZ_API RZ_OWN char *rz_analysis_function_name_guess(RzTypeDB *typedb, RZ_NONNULL char *name) { rz_return_val_if_fail(typedb && name, NULL); char *str = name; char *result = NULL; size_t slen = strlen(str); if (slen < MIN_MATCH_LEN || is_auto_named(str, slen)) { return NULL; } str = strip_rz_prefixes(str, slen); str = strip_common_prefixes_stdlib(str); str = strip_dll_prefix(str); if ((result = function_name_try_guess(typedb, str))) { return result; } str = strdup(str); clean_function_name(str); if (*str == '_' && (result = function_name_try_guess(typedb, str + 1))) { free(str); return result; } free(str); return result; }