1019 lines
34 KiB
C
1019 lines
34 KiB
C
// SPDX-FileCopyrightText: 2016-2020 oddcoder <ahmedsoliman@oddcoder.com>
|
|
// SPDX-FileCopyrightText: 2016-2020 sivaramaaa <sivaramaaa@gmail.com>
|
|
// SPDX-License-Identifier: LGPL-3.0-only
|
|
/* type matching - type propagation */
|
|
|
|
#include <rz_analysis.h>
|
|
#include <rz_util.h>
|
|
#include <rz_util/ht_uu.h>
|
|
#include <rz_core.h>
|
|
#define LOOP_MAX 10
|
|
|
|
// from canalysis.c (core_private.h isn't included: it clashes with the local bb_cmpaddr)
|
|
RZ_IPI void rz_core_add_string_ref(RzCore *core, ut64 xref_from, ut64 xref_to);
|
|
|
|
static bool analysis_emul_init(RzCore *core, RzConfigHold *hc, RzDebugTrace **dt, RzAnalysisEsilTrace **et, RzAnalysisILTrace **rt) {
|
|
RzReg *rreg = rz_analysis_get_reg(core->analysis);
|
|
RzAnalysisEsil *esil = rz_analysis_get_esil(core->analysis);
|
|
if (!esil) {
|
|
return false;
|
|
}
|
|
*dt = core->dbg->trace;
|
|
*et = esil->trace;
|
|
|
|
core->dbg->trace = rz_debug_trace_new();
|
|
esil->trace = rz_analysis_esil_trace_new(esil);
|
|
|
|
rz_config_hold_var(hc, "esil.romem", "dbg.trace",
|
|
"esil.nonull", "dbg.follow", NULL);
|
|
rz_config_set(core->config, "esil.romem", "true");
|
|
rz_config_set(core->config, "dbg.trace", "true");
|
|
rz_config_set(core->config, "esil.nonull", "true");
|
|
rz_config_set_i(core->config, "dbg.follow", false);
|
|
const char *bp = rz_reg_get_name(rreg, RZ_REG_NAME_BP);
|
|
const char *sp = rz_reg_get_name(rreg, RZ_REG_NAME_SP);
|
|
if ((bp && !rz_reg_getv(rreg, bp)) && (sp && !rz_reg_getv(rreg, sp))) {
|
|
RZ_LOG_ERROR("core: stack isn't initialized.\n");
|
|
RZ_LOG_ERROR("core: try running aei and aeim commands before aft for default stack initialization\n");
|
|
return false;
|
|
}
|
|
return (core->dbg->trace && esil->trace);
|
|
}
|
|
|
|
static void analysis_emul_restore(RzCore *core, RzConfigHold *hc, RzDebugTrace *dt, RzAnalysisEsilTrace *et, RzAnalysisILTrace *rt) {
|
|
RzAnalysisEsil *esil = rz_analysis_get_esil(core->analysis);
|
|
rz_config_hold_restore(hc);
|
|
rz_config_hold_free(hc);
|
|
rz_debug_trace_free(core->dbg->trace);
|
|
rz_analysis_esil_trace_free(esil->trace);
|
|
esil->trace = et;
|
|
core->dbg->trace = dt;
|
|
}
|
|
|
|
static bool type_pos_hit(RzAnalysis *analysis, RzILTraceInstruction *instr_trace, bool in_stack, int size, const char *place) {
|
|
if (in_stack) {
|
|
RzReg *rreg = rz_analysis_get_reg(analysis);
|
|
const char *sp_name = rz_reg_get_name(rreg, RZ_REG_NAME_SP);
|
|
ut64 sp = rz_reg_getv(rreg, sp_name);
|
|
|
|
ut64 write_addr = 0LL;
|
|
if (instr_trace && (instr_trace->stats & RZ_IL_TRACE_INS_HAS_MEM_W)) {
|
|
// TODO : This assumes an op will only write to memory once
|
|
// : which may be wrong in some archs. this is only a temporary solution
|
|
RzILTraceMemOp *mem = rz_pvector_at(instr_trace->write_mem_ops, 0);
|
|
write_addr = mem->addr;
|
|
} else {
|
|
// no reg write
|
|
write_addr = 0LL;
|
|
}
|
|
return (write_addr == sp + size);
|
|
}
|
|
|
|
return rz_analysis_il_reg_trace_contains(instr_trace, place, true);
|
|
}
|
|
|
|
static void var_rename(RzAnalysis *analysis, RzAnalysisVar *v, const char *name, ut64 addr) {
|
|
if (!name || !v) {
|
|
return;
|
|
}
|
|
if (!*name || !strcmp(name, "...")) {
|
|
return;
|
|
}
|
|
bool is_default = (rz_str_startswith(v->name, VARPREFIX) || rz_str_startswith(v->name, ARGPREFIX));
|
|
if (*name == '*') {
|
|
rz_warn_if_reached();
|
|
name++;
|
|
}
|
|
// longer name tends to be meaningful like "src" instead of "s1"
|
|
if (!is_default && (strlen(v->name) > strlen(name))) {
|
|
return;
|
|
}
|
|
RzAnalysisFunction *fcn = rz_analysis_get_fcn_in(analysis, addr, 0);
|
|
if (!fcn) {
|
|
return;
|
|
}
|
|
rz_analysis_var_rename(v, name, false);
|
|
}
|
|
|
|
static void var_type_set_sign(RzAnalysis *analysis, RzAnalysisVar *var, bool sign) {
|
|
rz_return_if_fail(analysis && var);
|
|
RzTypeDB *typedb = rz_analysis_get_type_db(analysis);
|
|
// Check if it's integral number first
|
|
if (rz_type_is_integral(typedb, var->type)) {
|
|
rz_type_integral_set_sign(typedb, &var->type, sign);
|
|
}
|
|
}
|
|
|
|
static bool var_type_simple_to_complex(const RzTypeDB *typedb, RzType *a, RzType *b) {
|
|
// `*int*` types to anything else of the typeclass
|
|
if (rz_type_is_integral(typedb, a) && !rz_type_is_char_ptr_nested(a) && !rz_type_is_integral(typedb, b)) {
|
|
return true;
|
|
}
|
|
// `*int*` types to pointers or arrays
|
|
if (rz_type_is_integral(typedb, a) && !rz_type_is_char_ptr_nested(a) && b->kind != RZ_TYPE_KIND_IDENTIFIER) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// TODO: Handle also non-atomic types here
|
|
static void var_type_set(RzAnalysis *analysis, RzAnalysisVar *var, RZ_BORROW RzType *type, bool ref, bool resolve_overlaps) {
|
|
rz_return_if_fail(analysis && var && type);
|
|
RzTypeDB *typedb = rz_analysis_get_type_db(analysis);
|
|
// removing this return makes 64bit vars become 32bit
|
|
if (rz_type_is_default(typedb, type) || rz_type_atomic_is_void(typedb, type)) {
|
|
// default or void (not void* !) type
|
|
return;
|
|
}
|
|
if (!rz_type_is_default(typedb, var->type) && !rz_type_is_void_ptr(var->type) && !var_type_simple_to_complex(typedb, var->type, type)) {
|
|
// return since type is already propagated
|
|
// except for "void *", since "void *" => "char *" is possible
|
|
// except for simple types to the more complex types
|
|
return;
|
|
}
|
|
// Since the type could be used by something else we should clone it
|
|
RzType *cloned = rz_type_clone(type);
|
|
if (!cloned) {
|
|
RZ_LOG_ERROR("core: cannot clone the type for the variable \"%s.%s\"\n", var->fcn->name, var->name);
|
|
return;
|
|
}
|
|
// Make a pointer of the existing type
|
|
if (ref) {
|
|
// By default we create non-const pointers
|
|
RzType *ptrtype = rz_type_pointer_of_type(typedb, cloned, false);
|
|
if (!ptrtype) {
|
|
RZ_LOG_ERROR("core: cannot convert the type for the variable \"%s.%s\" into pointer\n", var->fcn->name, var->name);
|
|
return;
|
|
}
|
|
rz_analysis_var_set_type(var, ptrtype, resolve_overlaps);
|
|
return;
|
|
}
|
|
|
|
rz_analysis_var_set_type(var, cloned, resolve_overlaps);
|
|
}
|
|
|
|
static void var_type_set_resolve_overlaps(RzAnalysis *analysis, RzAnalysisVar *var, RZ_BORROW RzType *type, bool ref) {
|
|
var_type_set(analysis, var, type, ref, true);
|
|
}
|
|
|
|
static void vars_resolve_overlaps(RzPVector /*<RzAnalysisVar *>*/ *vars) {
|
|
for (size_t i = 0; i < rz_pvector_len(vars); i++) {
|
|
RzAnalysisVar *var = rz_pvector_at(vars, i);
|
|
rz_analysis_var_resolve_overlaps(var);
|
|
}
|
|
}
|
|
|
|
static void var_type_set_str(RzAnalysis *analysis, RzAnalysisVar *var, const char *type, bool ref) {
|
|
rz_return_if_fail(analysis && var && type);
|
|
char *error_msg = NULL;
|
|
RzTypeDB *typedb = rz_analysis_get_type_db(analysis);
|
|
RzType *realtype = rz_type_parse_string_single(typedb->parser, type, &error_msg);
|
|
if (!realtype && error_msg) {
|
|
RZ_LOG_ERROR("core: fail to parse type \"%s\":\n%s\n", type, error_msg);
|
|
free(error_msg);
|
|
return;
|
|
}
|
|
var_type_set_resolve_overlaps(analysis, var, realtype, ref);
|
|
// Since we clone the type here, free it
|
|
rz_type_free(realtype);
|
|
}
|
|
|
|
static void get_src_regname(RzCore *core, ut64 addr, char *regname, int size) {
|
|
RzAnalysis *analysis = core->analysis;
|
|
RzReg *rreg = rz_analysis_get_reg(analysis);
|
|
int bits = rz_analysis_get_bits(analysis);
|
|
RzAnalysisOp *op = rz_core_analysis_op(core, addr, RZ_ANALYSIS_OP_MASK_VAL | RZ_ANALYSIS_OP_MASK_ESIL);
|
|
if (!op || rz_strbuf_is_empty(&op->esil)) {
|
|
rz_analysis_op_free(op);
|
|
return;
|
|
}
|
|
char *op_esil = rz_str_dup(rz_strbuf_get(&op->esil));
|
|
char *tmp = strchr(op_esil, ',');
|
|
if (tmp) {
|
|
*tmp = '\0';
|
|
}
|
|
memset(regname, 0, size);
|
|
RzRegItem *ri = rz_reg_get(rreg, op_esil, -1);
|
|
if (ri) {
|
|
if ((bits == 64) && (ri->size == 32)) {
|
|
const char *reg = rz_reg_32_to_64(rreg, op_esil);
|
|
if (reg) {
|
|
free(op_esil);
|
|
op_esil = rz_str_dup(reg);
|
|
}
|
|
}
|
|
strncpy(regname, op_esil, size - 1);
|
|
}
|
|
free(op_esil);
|
|
rz_analysis_op_free(op);
|
|
}
|
|
|
|
static ut64 get_addr(RzAnalysis *analysis, const char *regname, int idx) {
|
|
if (!regname || !*regname) {
|
|
return UT64_MAX;
|
|
}
|
|
RzAnalysisEsil *esil = rz_analysis_get_esil(analysis);
|
|
|
|
RzAnalysisEsilTrace *etrace = esil->trace;
|
|
RzILTraceInstruction *instruction_trace = rz_analysis_esil_get_instruction_trace(etrace, idx);
|
|
RzILTraceRegOp *reg_op = rz_analysis_il_get_reg_op_trace(instruction_trace, regname, false);
|
|
|
|
if (!reg_op) {
|
|
return UT64_MAX;
|
|
}
|
|
|
|
return reg_op->value;
|
|
}
|
|
|
|
static RzList /*<char *>*/ *parse_format(RzCore *core, char *fmt) {
|
|
if (!fmt || !*fmt) {
|
|
return NULL;
|
|
}
|
|
RzList *ret = rz_list_new();
|
|
if (!ret) {
|
|
return NULL;
|
|
}
|
|
Sdb *s = rz_analysis_get_sdb_formats(core->analysis);
|
|
const char *spec = rz_config_get(core->config, "analysis.types.spec");
|
|
char arr[10] = { 0 };
|
|
char tmpbuf[0x300]; // caller has the size limit of 0x200
|
|
char *ptr = strchr(fmt, '%');
|
|
fmt[strlen(fmt) - 1] = '\0';
|
|
while (ptr) {
|
|
ptr++;
|
|
// strip [width] specifier
|
|
while (IS_DIGIT(*ptr)) {
|
|
ptr++;
|
|
}
|
|
rz_str_ncpy(arr, ptr, sizeof(arr) - 1);
|
|
char *tmp = arr;
|
|
while (tmp && (IS_LOWER(*tmp) || IS_UPPER(*tmp))) {
|
|
tmp++;
|
|
}
|
|
*tmp = '\0';
|
|
const char *query = rz_strf(tmpbuf, "spec.%s.%s", spec, arr);
|
|
char *type = (char *)sdb_const_get(s, query);
|
|
if (type) {
|
|
rz_list_append(ret, type);
|
|
}
|
|
ptr = strchr(ptr, '%');
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static void retype_callee_arg(RzAnalysis *analysis, const char *callee_name, bool in_stack, const char *place, int size, RZ_BORROW RzType *type) {
|
|
RzAnalysisFunction *fcn = rz_analysis_get_function_byname(analysis, callee_name);
|
|
if (!fcn) {
|
|
return;
|
|
}
|
|
if (in_stack) {
|
|
RzAnalysisVar *var = rz_analysis_function_get_stack_var_at(fcn, size - fcn->bp_off + 8);
|
|
if (!var) {
|
|
return;
|
|
}
|
|
var_type_set_resolve_overlaps(analysis, var, type, false);
|
|
} else {
|
|
RzAnalysisVar *rvar = rz_analysis_function_get_reg_var_at(fcn, place);
|
|
if (!rvar) {
|
|
return;
|
|
}
|
|
var_type_set_resolve_overlaps(analysis, rvar, type, false);
|
|
RzAnalysisVar *lvar = rz_analysis_var_get_dst_var(rvar);
|
|
if (lvar) {
|
|
var_type_set_resolve_overlaps(analysis, lvar, type, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
RzAnalysisOp *op_cache_get(HtUP *cache, RzCore *core, ut64 addr) {
|
|
RzAnalysisOp *op = ht_up_find(cache, addr, NULL);
|
|
if (op) {
|
|
return op;
|
|
}
|
|
op = rz_core_analysis_op(core, addr, RZ_ANALYSIS_OP_MASK_BASIC | RZ_ANALYSIS_OP_MASK_VAL);
|
|
if (!ht_up_insert(cache, addr, op)) {
|
|
rz_analysis_op_free(op);
|
|
return NULL;
|
|
}
|
|
return op;
|
|
}
|
|
|
|
RzCallable *function_type_derive(RzAnalysis *analysis, RZ_NONNULL const char *fcn_name, bool *owned) {
|
|
rz_return_val_if_fail(fcn_name && owned, NULL);
|
|
// Because in the case of `rz_type_func_get()` we get the borrowed
|
|
// pointer to the RzCallable and in the case of `rz_analysis_function_derive_type()`
|
|
// we get the owned pointer, we should free the pointer in the one case but not another.
|
|
*owned = false;
|
|
RzTypeDB *typedb = rz_analysis_get_type_db(analysis);
|
|
RzCallable *callable = rz_type_func_get(typedb, fcn_name);
|
|
if (!callable) {
|
|
RzAnalysisFunction *fcn = rz_analysis_get_function_byname(analysis, fcn_name);
|
|
if (!fcn) {
|
|
return NULL;
|
|
}
|
|
callable = rz_analysis_function_derive_type(analysis, fcn);
|
|
if (!callable) {
|
|
return NULL;
|
|
}
|
|
*owned = true;
|
|
}
|
|
return callable;
|
|
}
|
|
|
|
bool function_argument_type_derive(RZ_NULLABLE const RzCallable *callable, int arg_num, RzType **type, char **name) {
|
|
rz_return_val_if_fail(type && name, false);
|
|
if (!callable) {
|
|
return false;
|
|
}
|
|
if (arg_num >= rz_pvector_len(callable->args)) {
|
|
return false;
|
|
}
|
|
RzCallableArg *arg = rz_pvector_at(callable->args, arg_num);
|
|
if (!arg) {
|
|
return false;
|
|
}
|
|
*type = rz_type_clone(arg->type);
|
|
*name = rz_str_dup(arg->name);
|
|
return true;
|
|
}
|
|
|
|
#define DEFAULT_MAX 3
|
|
#define REGNAME_SIZE 10
|
|
#define MAX_INSTR 5
|
|
|
|
/**
|
|
* type match at a call instruction inside another function
|
|
*
|
|
* \param fcn_name name of the callee
|
|
* \param addr addr of the call instruction
|
|
* \param baddr addr of the caller function
|
|
* \param cc cc of the callee
|
|
* \param prev_idx index in the esil trace
|
|
* \param userfnc whether the callee is a user function (affects propagation direction)
|
|
* \param caddr addr of the callee
|
|
*/
|
|
static void type_match(RzCore *core, char *fcn_name, ut64 addr, ut64 baddr, const char *cc,
|
|
int prev_idx, bool userfnc, ut64 caddr, HtUP *op_cache) {
|
|
RzAnalysisEsil *esil = rz_analysis_get_esil(core->analysis);
|
|
RzTypeDB *typedb = rz_analysis_get_type_db(core->analysis);
|
|
RzAnalysisEsilTrace *etrace = esil->trace;
|
|
RzAnalysis *analysis = core->analysis;
|
|
RzList *types = NULL;
|
|
char tmpbuf[1024];
|
|
|
|
int idx = rz_pvector_len(etrace->instructions) - 1;
|
|
|
|
bool verbose = rz_config_get_i(core->config, "analysis.types.verbose");
|
|
bool stack_rev = false, in_stack = false, format = false;
|
|
|
|
if (!fcn_name || !cc) {
|
|
return;
|
|
}
|
|
int i, j, pos = 0, size = 0, max = rz_type_func_args_count(typedb, fcn_name);
|
|
const char *place = rz_analysis_cc_arg(analysis, cc, ST32_MAX);
|
|
rz_cons_break_push(NULL, NULL);
|
|
|
|
if (place && !strcmp(place, "stack_rev")) {
|
|
stack_rev = true;
|
|
}
|
|
place = rz_analysis_cc_arg(analysis, cc, 0);
|
|
if (place && rz_str_startswith("stack", place)) {
|
|
in_stack = true;
|
|
}
|
|
if (verbose && !strncmp(fcn_name, "sym.imp.", 8)) {
|
|
RZ_LOG_ERROR("core: %s missing function definition\n", fcn_name + 8);
|
|
}
|
|
if (!max || max == -1) {
|
|
if (!in_stack) {
|
|
max = rz_analysis_cc_max_arg(analysis, cc);
|
|
} else {
|
|
max = DEFAULT_MAX;
|
|
}
|
|
}
|
|
// Because in the case of `rz_type_func_get()` we get the borrowed
|
|
// pointer to the RzCallable and in the case of `rz_analysis_function_derive_type()`
|
|
// we get the owned pointer, we should free the pointer in the one case but not another.
|
|
bool owned = false;
|
|
RzCallable *callable = function_type_derive(analysis, fcn_name, &owned);
|
|
// We don't check if the callable is NULL because we not always need it, for example
|
|
// in case of the `format` set it's unnecessary for the type derivation.
|
|
for (i = 0; i < max; i++) {
|
|
int arg_num = stack_rev ? (max - 1 - i) : i;
|
|
RzType *type = NULL;
|
|
char *name = NULL;
|
|
if (format) {
|
|
if (rz_list_empty(types)) {
|
|
break;
|
|
}
|
|
const char *typestr = rz_list_get_n(types, pos++);
|
|
if (typestr) {
|
|
type = rz_type_parse_string_single(typedb->parser, typestr, NULL);
|
|
}
|
|
} else {
|
|
if (!function_argument_type_derive(callable, arg_num, &type, &name)) {
|
|
continue;
|
|
}
|
|
}
|
|
if (!type && !userfnc) {
|
|
free(name);
|
|
continue;
|
|
}
|
|
if (!in_stack) {
|
|
// XXX: param arg_num must be fixed to support floating point register
|
|
place = rz_analysis_cc_arg(analysis, cc, arg_num);
|
|
if (place && rz_str_startswith("stack", place)) {
|
|
in_stack = true;
|
|
}
|
|
}
|
|
char regname[REGNAME_SIZE] = { 0 };
|
|
ut64 xaddr = UT64_MAX;
|
|
bool memref = false;
|
|
bool cmt_set = false;
|
|
bool res = false;
|
|
// Backtrace instruction from source sink to prev source sink
|
|
for (j = idx; j >= prev_idx; j--) {
|
|
RzILTraceInstruction *instr_trace = rz_analysis_esil_get_instruction_trace(etrace, j);
|
|
ut64 instr_addr = instr_trace->addr;
|
|
if (instr_addr < baddr) {
|
|
break;
|
|
}
|
|
RzAnalysisOp *op = op_cache_get(op_cache, core, instr_addr);
|
|
if (!op) {
|
|
break;
|
|
}
|
|
RzAnalysisOp *next_op = op_cache_get(op_cache, core, instr_addr + op->size);
|
|
if (!next_op || (j != idx && (next_op->type == RZ_ANALYSIS_OP_TYPE_CALL || next_op->type == RZ_ANALYSIS_OP_TYPE_JMP))) {
|
|
break;
|
|
}
|
|
RzAnalysisVar *var = rz_analysis_get_used_function_var(analysis, op->addr);
|
|
|
|
// FIXME : It seems also assume only read memory once ?
|
|
if (op->type == RZ_ANALYSIS_OP_TYPE_MOV && (instr_trace->stats & RZ_IL_TRACE_INS_HAS_MEM_R)) {
|
|
memref = !(!memref && var && var->storage.type == RZ_ANALYSIS_VAR_STORAGE_STACK);
|
|
}
|
|
// Match type from function param to instr
|
|
if (type_pos_hit(analysis, instr_trace, in_stack, size, place)) {
|
|
if (!cmt_set && type && name) {
|
|
char *typestr = rz_type_as_string(typedb, type);
|
|
const char *maybe_space = type->kind == RZ_TYPE_KIND_POINTER ? "" : " ";
|
|
rz_meta_set_string(analysis, RZ_META_TYPE_VARTYPE, instr_addr,
|
|
rz_strf(tmpbuf, "%s%s%s", typestr, maybe_space, name));
|
|
cmt_set = true;
|
|
if ((op->ptr && op->ptr != UT64_MAX) && !strcmp(name, "format")) {
|
|
RzFlagItem *f = rz_flag_get_by_spaces(core->flags, op->ptr, RZ_FLAGS_FS_STRINGS, NULL);
|
|
if (!f) {
|
|
// A format argument (e.g. scanf's "%s") is a string even when
|
|
// shorter than the detection threshold; create the reference so
|
|
// it is shown as a string and parsed for variadic typing below.
|
|
size_t saved_min = core->bin->str_search_cfg.min_length;
|
|
core->bin->str_search_cfg.min_length = 1;
|
|
rz_core_add_string_ref(core, op->addr, op->ptr);
|
|
core->bin->str_search_cfg.min_length = saved_min;
|
|
f = rz_flag_get_by_spaces(core->flags, op->ptr, RZ_FLAGS_FS_STRINGS, NULL);
|
|
}
|
|
if (f) {
|
|
char formatstr[0x200];
|
|
int read = rz_io_nread_at(core->io, f->offset, (ut8 *)formatstr, RZ_MIN(sizeof(formatstr) - 1, f->size));
|
|
if (read > 0) {
|
|
formatstr[read] = '\0';
|
|
if ((types = parse_format(core, formatstr))) {
|
|
max += rz_list_length(types);
|
|
}
|
|
format = true;
|
|
}
|
|
}
|
|
}
|
|
free(typestr);
|
|
}
|
|
if (var) {
|
|
if (!userfnc) {
|
|
// not a userfunction, propagate the callee's arg types into our function's vars
|
|
var_type_set_resolve_overlaps(analysis, var, type, memref);
|
|
var_rename(analysis, var, name, addr);
|
|
} else {
|
|
// callee is a userfunction, propagate our variable's type into the callee's args
|
|
retype_callee_arg(analysis, fcn_name, in_stack, place, size, var->type);
|
|
}
|
|
res = true;
|
|
} else {
|
|
get_src_regname(core, instr_addr, regname, sizeof(regname));
|
|
xaddr = get_addr(analysis, regname, j);
|
|
}
|
|
}
|
|
// Type propagate by following source reg
|
|
if (!res && *regname && rz_analysis_il_get_reg_op_trace(instr_trace, regname, true)) {
|
|
if (var) {
|
|
if (!userfnc) {
|
|
// not a userfunction, propagate the callee's arg types into our function's vars
|
|
var_type_set_resolve_overlaps(analysis, var, type, memref);
|
|
var_rename(analysis, var, name, addr);
|
|
} else {
|
|
// callee is a userfunction, propagate our variable's type into the callee's args
|
|
retype_callee_arg(analysis, fcn_name, in_stack, place, size, var->type);
|
|
}
|
|
res = true;
|
|
} else {
|
|
switch (op->type) {
|
|
case RZ_ANALYSIS_OP_TYPE_MOV:
|
|
case RZ_ANALYSIS_OP_TYPE_PUSH:
|
|
get_src_regname(core, instr_addr, regname, sizeof(regname));
|
|
break;
|
|
case RZ_ANALYSIS_OP_TYPE_LEA:
|
|
case RZ_ANALYSIS_OP_TYPE_LOAD:
|
|
case RZ_ANALYSIS_OP_TYPE_STORE:
|
|
res = true;
|
|
break;
|
|
}
|
|
}
|
|
} else if (var && res && xaddr && (xaddr != UT64_MAX)) { // Type progation using value
|
|
char tmp[REGNAME_SIZE] = { 0 };
|
|
get_src_regname(core, instr_addr, tmp, sizeof(tmp));
|
|
ut64 ptr = get_addr(analysis, tmp, j);
|
|
if (ptr == xaddr) {
|
|
if (type) {
|
|
var_type_set_resolve_overlaps(analysis, var, type, memref);
|
|
} else {
|
|
var_type_set_str(analysis, var, "int", memref);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
int bits = rz_analysis_get_bits(analysis);
|
|
size += bits / 8;
|
|
rz_type_free(type);
|
|
free(name);
|
|
}
|
|
if (owned) {
|
|
rz_type_callable_free(callable);
|
|
}
|
|
rz_list_free(types);
|
|
rz_cons_break_pop();
|
|
}
|
|
|
|
static int bb_cmpaddr(const void *_a, const void *_b, void *user) {
|
|
const RzAnalysisBlock *a = _a, *b = _b;
|
|
return a->addr > b->addr ? 1 : (a->addr < b->addr ? -1 : 0);
|
|
}
|
|
|
|
void handle_stack_canary(RzCore *core, RzAnalysisOp *aop, int cur_idx) {
|
|
RzAnalysisEsil *esil = rz_analysis_get_esil(core->analysis);
|
|
RzILTraceInstruction *prev_trace = rz_analysis_esil_get_instruction_trace(
|
|
esil->trace,
|
|
cur_idx - 1);
|
|
|
|
ut64 mov_addr;
|
|
if (prev_trace) {
|
|
mov_addr = prev_trace->addr;
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
RzAnalysisOp *mop = rz_core_analysis_op(core, mov_addr, RZ_ANALYSIS_OP_MASK_VAL | RZ_ANALYSIS_OP_MASK_BASIC);
|
|
if (mop) {
|
|
RzAnalysisVar *mopvar = rz_analysis_get_used_function_var(core->analysis, mop->addr);
|
|
ut32 type = mop->type & RZ_ANALYSIS_OP_TYPE_MASK;
|
|
if (type == RZ_ANALYSIS_OP_TYPE_MOV) {
|
|
var_rename(core->analysis, mopvar, "canary", aop->addr);
|
|
}
|
|
}
|
|
rz_analysis_op_free(mop);
|
|
}
|
|
|
|
struct ReturnTypeAnalysisCtx {
|
|
bool resolved;
|
|
RzType *ret_type;
|
|
char *ret_reg;
|
|
};
|
|
|
|
static inline bool return_type_analysis_context_unresolved(struct ReturnTypeAnalysisCtx *ctx) {
|
|
return !ctx->resolved && ctx->ret_type && ctx->ret_reg;
|
|
}
|
|
|
|
// Progate return type passed using pointer
|
|
static void propagate_return_type_pointer(RzCore *core, RzAnalysisOp *aop, RzPVector /*<RzAnalysisVar *>*/ *used_vars, ut64 addr, struct ReturnTypeAnalysisCtx *ctx) {
|
|
// int *ret; *ret = strlen(s);
|
|
// TODO: memref check , dest and next src match
|
|
char nsrc[REGNAME_SIZE] = { 0 };
|
|
void **uvit;
|
|
get_src_regname(core, addr, nsrc, sizeof(nsrc));
|
|
if (ctx->ret_reg && *nsrc && strstr(ctx->ret_reg, nsrc) && aop->direction == RZ_ANALYSIS_OP_DIR_READ) {
|
|
if (used_vars && !rz_pvector_empty(used_vars)) {
|
|
rz_pvector_foreach (used_vars, uvit) {
|
|
RzAnalysisVar *var = *uvit;
|
|
var_type_set(core->analysis, var, ctx->ret_type, true, false);
|
|
}
|
|
vars_resolve_overlaps(used_vars);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Forward propagation of function return type
|
|
static void propagate_return_type(RzCore *core, RzAnalysisOp *aop, RzAnalysisOp *next_op, RzILTraceInstruction *trace, struct ReturnTypeAnalysisCtx *ctx, RzPVector /*<RzAnalysisVar *>*/ *used_vars) {
|
|
char src[REGNAME_SIZE] = { 0 };
|
|
void **uvit;
|
|
|
|
// TODO : handle multiple registers case, this is a temporary solution
|
|
RzILTraceRegOp *single_write_reg = NULL;
|
|
if (trace && (trace->stats & RZ_IL_TRACE_INS_HAS_REG_W)) {
|
|
single_write_reg = rz_pvector_at(trace->write_reg_ops, 0);
|
|
}
|
|
|
|
get_src_regname(core, aop->addr, src, sizeof(src));
|
|
ut32 type = aop->type & RZ_ANALYSIS_OP_TYPE_MASK;
|
|
if (ctx->ret_reg && *src && strstr(ctx->ret_reg, src)) {
|
|
if (used_vars && !rz_pvector_empty(used_vars) && aop->direction == RZ_ANALYSIS_OP_DIR_WRITE) {
|
|
rz_pvector_foreach (used_vars, uvit) {
|
|
RzAnalysisVar *var = *uvit;
|
|
var_type_set(core->analysis, var, ctx->ret_type, false, false);
|
|
}
|
|
vars_resolve_overlaps(used_vars);
|
|
ctx->resolved = true;
|
|
} else if (type == RZ_ANALYSIS_OP_TYPE_MOV) {
|
|
RZ_FREE(ctx->ret_reg);
|
|
if (single_write_reg && single_write_reg->reg_name) {
|
|
ctx->ret_reg = rz_str_dup(single_write_reg->reg_name);
|
|
}
|
|
}
|
|
} else if (single_write_reg) {
|
|
if (ctx->ret_reg &&
|
|
(single_write_reg->reg_name && strstr(ctx->ret_reg, single_write_reg->reg_name))) {
|
|
ctx->resolved = true;
|
|
} else if (type == RZ_ANALYSIS_OP_TYPE_MOV &&
|
|
(next_op && next_op->type == RZ_ANALYSIS_OP_TYPE_MOV)) {
|
|
// Progate return type passed using pointer
|
|
propagate_return_type_pointer(core, aop, used_vars, next_op->addr, ctx);
|
|
}
|
|
}
|
|
}
|
|
|
|
struct TypeAnalysisCtx {
|
|
struct ReturnTypeAnalysisCtx *retctx;
|
|
int cur_idx;
|
|
const char *prev_dest;
|
|
bool str_flag;
|
|
};
|
|
|
|
void propagate_types_among_used_variables(RzCore *core, HtUP *op_cache, RzAnalysisFunction *fcn, RzAnalysisBlock *bb, RzAnalysisOp *aop, struct TypeAnalysisCtx *ctx) {
|
|
RzPVector *used_vars = rz_analysis_function_get_vars_used_at(fcn, aop->addr);
|
|
bool chk_constraint = rz_config_get_b(core->config, "analysis.types.constraint");
|
|
RzAnalysisOp *next_op = op_cache_get(op_cache, core, aop->addr + aop->size);
|
|
RzTypeDB *typedb = rz_analysis_get_type_db(core->analysis);
|
|
void **uvit;
|
|
RzType *prev_type = NULL;
|
|
int prev_idx = 0;
|
|
bool prev_var = false;
|
|
char *fcn_name = NULL;
|
|
bool userfnc = false;
|
|
bool prop = false;
|
|
ut32 type = aop->type & RZ_ANALYSIS_OP_TYPE_MASK;
|
|
RzAnalysis *analysis = core->analysis;
|
|
|
|
RzAnalysisEsil *esil = rz_analysis_get_esil(core->analysis);
|
|
RzAnalysisEsilTrace *etrace = esil->trace;
|
|
RzILTraceInstruction *cur_instr_trace = rz_analysis_esil_get_instruction_trace(etrace, ctx->cur_idx);
|
|
|
|
if (aop->type == RZ_ANALYSIS_OP_TYPE_CALL || aop->type & RZ_ANALYSIS_OP_TYPE_UCALL) {
|
|
char *full_name = NULL;
|
|
ut64 callee_addr;
|
|
if (aop->type == RZ_ANALYSIS_OP_TYPE_CALL) {
|
|
RzAnalysisFunction *fcn_call = rz_analysis_get_fcn_in(core->analysis, aop->jump, -1);
|
|
if (fcn_call) {
|
|
full_name = fcn_call->name;
|
|
callee_addr = fcn_call->addr;
|
|
}
|
|
} else if (aop->ptr != UT64_MAX) {
|
|
RzFlagItem *flag = rz_flag_get_by_spaces(core->flags, aop->ptr, RZ_FLAGS_FS_IMPORTS, NULL);
|
|
if (flag && flag->realname) {
|
|
char *reloc_prefix = (char *)rz_str_rstr(flag->realname, "reloc.");
|
|
full_name = reloc_prefix ? reloc_prefix + 6 : flag->realname;
|
|
callee_addr = aop->ptr;
|
|
}
|
|
}
|
|
// TODO: Apart from checking the types database, we should also derive the information
|
|
// from the RzAnalysisFunction if nothing was found in the RzTypeDB
|
|
if (full_name) {
|
|
if (rz_type_func_exist(typedb, full_name)) {
|
|
fcn_name = rz_str_dup(full_name);
|
|
} else {
|
|
fcn_name = rz_analysis_function_name_guess(typedb, full_name);
|
|
}
|
|
if (!fcn_name) {
|
|
fcn_name = rz_str_dup(full_name);
|
|
userfnc = true;
|
|
}
|
|
const char *Cc = rz_analysis_cc_func(core->analysis, fcn_name);
|
|
if (Cc && rz_analysis_cc_exist(core->analysis, Cc)) {
|
|
char *cc = rz_str_dup(Cc);
|
|
type_match(core, fcn_name, aop->addr, bb->addr, cc, prev_idx, userfnc, callee_addr, op_cache);
|
|
prev_idx = ctx->cur_idx;
|
|
ctx->retctx->ret_type = rz_type_func_ret(typedb, fcn_name);
|
|
RZ_FREE(ctx->retctx->ret_reg);
|
|
const char *rr = rz_analysis_cc_ret(core->analysis, cc);
|
|
if (rr) {
|
|
ctx->retctx->ret_reg = rz_str_dup(rr);
|
|
}
|
|
ctx->retctx->resolved = false;
|
|
free(cc);
|
|
}
|
|
if (!strcmp(fcn_name, "__stack_chk_fail")) {
|
|
handle_stack_canary(core, aop, ctx->cur_idx);
|
|
}
|
|
free(fcn_name);
|
|
}
|
|
} else if (return_type_analysis_context_unresolved(ctx->retctx)) {
|
|
// Forward propagation of function return type
|
|
propagate_return_type(core, aop, next_op, cur_instr_trace, ctx->retctx, used_vars);
|
|
}
|
|
// Type propagation using instruction access pattern
|
|
if (used_vars && !rz_pvector_empty(used_vars)) {
|
|
rz_pvector_foreach (used_vars, uvit) {
|
|
RzAnalysisVar *var = *uvit;
|
|
bool sign = false;
|
|
if ((type == RZ_ANALYSIS_OP_TYPE_CMP) && next_op) {
|
|
if (next_op->sign) {
|
|
sign = true;
|
|
} else {
|
|
// cmp [local_ch], rax ; jb
|
|
// set to "unsigned"
|
|
var_type_set_sign(core->analysis, var, false);
|
|
}
|
|
}
|
|
// cmp [local_ch], rax ; jge
|
|
if (sign || aop->sign) {
|
|
// set to "signed"
|
|
var_type_set_sign(core->analysis, var, true);
|
|
}
|
|
// lea rax , str.hello ; mov [local_ch], rax;
|
|
// mov rdx , [local_4h] ; mov [local_8h], rdx;
|
|
if (ctx->prev_dest && (type == RZ_ANALYSIS_OP_TYPE_MOV || type == RZ_ANALYSIS_OP_TYPE_STORE)) {
|
|
char reg[REGNAME_SIZE] = { 0 };
|
|
get_src_regname(core, aop->addr, reg, sizeof(reg));
|
|
bool match = strstr(ctx->prev_dest, reg) != NULL;
|
|
if (ctx->str_flag && match) {
|
|
var_type_set_str(core->analysis, var, "const char *", false);
|
|
}
|
|
if (prop && match && prev_var && prev_type) {
|
|
// Here we clone the type
|
|
var_type_set(core->analysis, var, prev_type, false, false);
|
|
}
|
|
}
|
|
if (chk_constraint && var && (type == RZ_ANALYSIS_OP_TYPE_CMP && aop->disp != UT64_MAX) && next_op && next_op->type == RZ_ANALYSIS_OP_TYPE_CJMP) {
|
|
bool jmp = false;
|
|
RzAnalysisOp *jmp_op = { 0 };
|
|
ut64 jmp_addr = next_op->jump;
|
|
RzAnalysisBlock *jmpbb = rz_analysis_fcn_bbget_in(core->analysis, fcn, jmp_addr);
|
|
if (!jmpbb) {
|
|
continue;
|
|
}
|
|
|
|
// Check exit status of jmp branch
|
|
int i;
|
|
for (i = 0; i < MAX_INSTR; i++) {
|
|
jmp_op = rz_core_analysis_op(core, jmp_addr, RZ_ANALYSIS_OP_MASK_BASIC);
|
|
if (!jmp_op) {
|
|
break;
|
|
}
|
|
if ((jmp_op->type == RZ_ANALYSIS_OP_TYPE_RET && rz_analysis_block_contains(jmpbb, jmp_addr)) || jmp_op->type == RZ_ANALYSIS_OP_TYPE_CJMP) {
|
|
jmp = true;
|
|
rz_analysis_op_free(jmp_op);
|
|
break;
|
|
}
|
|
jmp_addr += jmp_op->size;
|
|
rz_analysis_op_free(jmp_op);
|
|
}
|
|
RzTypeConstraint constr = {
|
|
.cond = jmp ? rz_type_cond_invert(next_op->cond) : next_op->cond,
|
|
.val = aop->val
|
|
};
|
|
// Only keep constraints that carry useful information: a known
|
|
// immediate value (UT64_MAX means the compared value was not a
|
|
// resolvable immediate) and a condition that the rest of the
|
|
// type engine knows how to represent. Besides the interval
|
|
// bounds (<, <=, >, >=) this includes the equality and
|
|
// inequality conditions produced by je/jne-style branches.
|
|
// This avoids recording degenerate constraints such as
|
|
// "> 0xffffffffffffffff".
|
|
bool useful_cond = constr.cond == RZ_TYPE_COND_LE || constr.cond == RZ_TYPE_COND_LT ||
|
|
constr.cond == RZ_TYPE_COND_GE || constr.cond == RZ_TYPE_COND_GT ||
|
|
constr.cond == RZ_TYPE_COND_EQ || constr.cond == RZ_TYPE_COND_NE;
|
|
if (constr.val != UT64_MAX && useful_cond) {
|
|
// Avoid recording the same constraint more than once (the
|
|
// emulation may revisit the same comparison).
|
|
bool dup = false;
|
|
RzTypeConstraint *existing;
|
|
rz_vector_foreach (&var->constraints, existing) {
|
|
if (existing->cond == constr.cond && existing->val == constr.val) {
|
|
dup = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!dup) {
|
|
rz_analysis_var_add_constraint(var, &constr);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
vars_resolve_overlaps(used_vars);
|
|
}
|
|
prev_var = (used_vars && !rz_pvector_empty(used_vars) && aop->direction == RZ_ANALYSIS_OP_DIR_READ);
|
|
ctx->str_flag = false;
|
|
prop = false;
|
|
ctx->prev_dest = NULL;
|
|
switch (type) {
|
|
case RZ_ANALYSIS_OP_TYPE_MOV:
|
|
case RZ_ANALYSIS_OP_TYPE_LEA:
|
|
case RZ_ANALYSIS_OP_TYPE_LOAD:
|
|
if (aop->ptr && aop->refptr && aop->ptr != UT64_MAX) {
|
|
if (type == RZ_ANALYSIS_OP_TYPE_LOAD) {
|
|
ut8 buf[256] = { 0 };
|
|
rz_io_read_at_mapped(core->io, aop->ptr, buf, sizeof(buf) - 1);
|
|
ut64 ptr = rz_read_ble(buf, core->print->big_endian, aop->refptr * 8);
|
|
if (ptr && ptr != UT64_MAX) {
|
|
RzFlagItem *f = rz_flag_get_by_spaces(core->flags, ptr, RZ_FLAGS_FS_STRINGS, NULL);
|
|
if (f) {
|
|
ctx->str_flag = true;
|
|
}
|
|
}
|
|
} else if (rz_flag_exist_at(core->flags, "str", 3, aop->ptr)) {
|
|
ctx->str_flag = true;
|
|
}
|
|
}
|
|
|
|
// Assert : reg write only once here
|
|
RzILTraceRegOp *w_reg = NULL;
|
|
if (cur_instr_trace) {
|
|
if (cur_instr_trace->stats & RZ_IL_TRACE_INS_HAS_REG_W) {
|
|
w_reg = rz_pvector_at(cur_instr_trace->write_reg_ops, 0);
|
|
if (w_reg) {
|
|
RzStrConstPool *cpool = rz_analysis_get_const_pool(analysis);
|
|
ctx->prev_dest = rz_str_constpool_get(cpool, w_reg->reg_name);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (used_vars && !rz_pvector_empty(used_vars)) {
|
|
rz_pvector_foreach (used_vars, uvit) {
|
|
RzAnalysisVar *var = *uvit;
|
|
// mov dword [local_4h], str.hello;
|
|
if (ctx->str_flag) {
|
|
var_type_set_str(core->analysis, var, "const char *", false);
|
|
}
|
|
prev_type = var->type;
|
|
prop = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#define OP_CACHE_LIMIT 8192
|
|
|
|
RZ_API void rz_core_analysis_type_match(RzCore *core, RzAnalysisFunction *fcn, HtUU *loop_table) {
|
|
rz_return_if_fail(core && core->analysis && fcn);
|
|
|
|
RzAnalysisEsil *esil = rz_analysis_get_esil(core->analysis);
|
|
if (!esil) {
|
|
RZ_LOG_ERROR("core: please run aeim first.\n");
|
|
return;
|
|
}
|
|
|
|
RzAnalysis *analysis = core->analysis;
|
|
RzReg *reg = rz_analysis_get_reg(core->analysis);
|
|
const int mininstrsz = rz_analysis_archinfo(analysis, RZ_ANALYSIS_ARCHINFO_MIN_OP_SIZE);
|
|
const int minopcode = RZ_MAX(1, mininstrsz);
|
|
RzConfigHold *hc = rz_config_hold_new(core->config);
|
|
if (!hc) {
|
|
return;
|
|
}
|
|
RzDebugTrace *dt = NULL;
|
|
RzAnalysisEsilTrace *et = NULL;
|
|
RzAnalysisILTrace *rt = NULL;
|
|
if (!analysis_emul_init(core, hc, &dt, &et, &rt) || !fcn) {
|
|
analysis_emul_restore(core, hc, dt, et, rt);
|
|
return;
|
|
}
|
|
|
|
// Reserve bigger ht to avoid rehashing
|
|
RzDebugTrace *dtrace = core->dbg->trace;
|
|
HtSPOptions opt = dtrace->ht->opt;
|
|
ht_sp_free(dtrace->ht);
|
|
dtrace->ht = ht_sp_new_opt_size(&opt, fcn->ninstr);
|
|
|
|
// Create a new context to store the return type propagation state
|
|
struct ReturnTypeAnalysisCtx retctx = {
|
|
.resolved = false,
|
|
.ret_type = NULL,
|
|
.ret_reg = NULL,
|
|
};
|
|
struct TypeAnalysisCtx ctx = {
|
|
.retctx = &retctx,
|
|
.cur_idx = 0,
|
|
.prev_dest = NULL,
|
|
.str_flag = false
|
|
};
|
|
|
|
HtUP *op_cache = NULL;
|
|
const char *pc = rz_reg_get_name(reg, RZ_REG_NAME_PC);
|
|
if (!pc) {
|
|
goto out_function;
|
|
}
|
|
RzRegItem *r = rz_reg_get(reg, pc, -1);
|
|
if (!r) {
|
|
goto out_function;
|
|
}
|
|
rz_cons_break_push(NULL, NULL);
|
|
rz_pvector_sort(fcn->bbs, bb_cmpaddr, NULL);
|
|
|
|
// TODO: The algorithm can be more accurate if blocks are followed by their jmp/fail, not just by address
|
|
RzAnalysisBlock *bb;
|
|
void **vit;
|
|
rz_pvector_foreach (fcn->bbs, vit) {
|
|
bb = (RzAnalysisBlock *)*vit;
|
|
ut64 addr = bb->addr;
|
|
rz_reg_set_value(reg, r, addr);
|
|
ht_up_free(op_cache);
|
|
op_cache = ht_up_new(NULL, (HtUPFreeValue)rz_analysis_op_free);
|
|
if (!op_cache) {
|
|
break;
|
|
}
|
|
while (1) {
|
|
if (rz_cons_is_breaked()) {
|
|
goto out_function;
|
|
}
|
|
ut64 pcval = rz_reg_getv(reg, pc);
|
|
if ((addr >= bb->addr + bb->size) || (addr < bb->addr) || pcval != addr) {
|
|
break;
|
|
}
|
|
RzAnalysisOp *aop = op_cache_get(op_cache, core, addr);
|
|
if (!aop) {
|
|
break;
|
|
}
|
|
if (aop->type == RZ_ANALYSIS_OP_TYPE_ILL) {
|
|
addr += minopcode;
|
|
continue;
|
|
}
|
|
|
|
// CHECK_ME : why we hold a loop_count here ?
|
|
// : can we remove it ?
|
|
// when set null, do not track loop count
|
|
if (loop_table) {
|
|
ut64 loop_count = ht_uu_find(loop_table, addr, NULL);
|
|
if (loop_count > LOOP_MAX || aop->type == RZ_ANALYSIS_OP_TYPE_RET) {
|
|
break;
|
|
}
|
|
loop_count += 1;
|
|
ht_uu_update(loop_table, addr, loop_count);
|
|
}
|
|
|
|
if (rz_analysis_op_nonlinear(aop->type)) { // skip the instr
|
|
rz_reg_set_value(reg, r, addr + aop->size);
|
|
} else {
|
|
rz_core_esil_step(core, UT64_MAX, NULL, NULL, false);
|
|
}
|
|
|
|
RzPVector *ins_traces = esil->trace->instructions;
|
|
ctx.cur_idx = rz_pvector_len(ins_traces) - 1;
|
|
RzList *fcns = rz_analysis_get_functions_in(analysis, aop->addr);
|
|
if (!fcns) {
|
|
break;
|
|
}
|
|
RzListIter *it;
|
|
RzAnalysisFunction *fcn;
|
|
rz_list_foreach (fcns, it, fcn) {
|
|
propagate_types_among_used_variables(core, op_cache, fcn, bb, aop, &ctx);
|
|
}
|
|
addr += aop->size;
|
|
rz_list_free(fcns);
|
|
// Recreate op_cache if it grows too large to avoid
|
|
// excessive memory usage.
|
|
if (ht_up_size(op_cache) > OP_CACHE_LIMIT) {
|
|
ht_up_free(op_cache);
|
|
op_cache = ht_up_new(NULL, (HtUPFreeValue)rz_analysis_op_free);
|
|
if (!op_cache) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Type propagation for register based args
|
|
rz_pvector_foreach (&fcn->vars, vit) {
|
|
RzAnalysisVar *rvar = *vit;
|
|
if (rvar->storage.type == RZ_ANALYSIS_VAR_STORAGE_REG) {
|
|
RzAnalysisVar *lvar = rz_analysis_var_get_dst_var(rvar);
|
|
// Note that every `var_type_set_resolve_overlaps()` call could remove some variables
|
|
// due to the overlaps resolution
|
|
if (lvar) {
|
|
// Propagate local var type = to => register-based var
|
|
var_type_set(analysis, rvar, lvar->type, false, false);
|
|
// Propagate local var type <= from = register-based var
|
|
var_type_set(analysis, lvar, rvar->type, false, false);
|
|
}
|
|
}
|
|
}
|
|
vars_resolve_overlaps(&fcn->vars);
|
|
out_function:
|
|
free(retctx.ret_reg);
|
|
ht_up_free(op_cache);
|
|
rz_cons_break_pop();
|
|
analysis_emul_restore(core, hc, dt, et, rt);
|
|
}
|