diff --git a/librz/analysis/fcn.c b/librz/analysis/fcn.c index c05cc40440..081d36e1a6 100644 --- a/librz/analysis/fcn.c +++ b/librz/analysis/fcn.c @@ -11,7 +11,6 @@ #define READ_AHEAD 1 #define SDB_KEY_BB "bb.0x%" PFMT64x ".0x%" PFMT64x // XXX must be configurable by the user -#define JMPTBLSZ 512 #define JMPTBL_LEA_SEARCH_SZ 64 #define JMPTBL_MAXFCNSIZE 4096 #define BB_ALIGN 0x10 @@ -521,12 +520,31 @@ static inline bool does_arch_destroys_dst(const char *arch) { return arch && (!strncmp(arch, "arm", 3) || !strcmp(arch, "riscv") || !strcmp(arch, "ppc")); } -static int fcn_recurse(RzAnalysis *analysis, RzAnalysisFunction *fcn, ut64 addr, ut64 len, int depth) { +static int analyze_function_locally(RzAnalysis *analysis, RzAnalysisFunction *fcn, ut64 address) { + rz_return_val_if_fail(analysis && fcn, RZ_ANALYSIS_RET_ERROR); + RzVector tasks; + rz_vector_init(&tasks, sizeof(RzAnalysisTaskItem), NULL, NULL); + RzAnalysisTaskItem item = { fcn, NULL, fcn->stack, address }; + rz_vector_push(&tasks, &item); + int saved_stack = fcn->stack; // TODO: DO NOT use fcn->stack to keep track of stack during analysis + int ret = rz_analysis_run_tasks(&tasks); + rz_vector_fini(&tasks); + fcn->stack = saved_stack; + return ret; +} + +static int run_basic_block_analysis(RzAnalysisTaskItem *item, RzVector *tasks) { + rz_return_val_if_fail(item && tasks, RZ_ANALYSIS_RET_ERROR); + RzAnalysis *analysis = item->fcn->analysis; + RzAnalysisFunction *fcn = item->fcn; + fcn->stack = item->stack; + ut64 addr = item->start_address; + ut64 len = analysis->opt.bb_max_size; const int continue_after_jump = analysis->opt.afterjmp; const int addrbytes = analysis->iob.io ? analysis->iob.io->addrbytes : 1; char *last_reg_mov_lea_name = NULL; char *movbasereg = NULL; - RzAnalysisBlock *bb = NULL; + RzAnalysisBlock *bb = item->block; RzAnalysisBlock *bbg = NULL; int ret = RZ_ANALYSIS_RET_END, skip_ret = 0; bool overlapped = false; @@ -558,19 +576,13 @@ static int fcn_recurse(RzAnalysis *analysis, RzAnalysisFunction *fcn, ut64 addr, bool has_variadic_reg = !!variadic_reg; if (rz_cons_is_breaked()) { + rz_analysis_task_item_new(analysis, tasks, fcn, bb, addr); return RZ_ANALYSIS_RET_END; } if (analysis->sleep) { rz_sys_usleep(analysis->sleep); } - if (depth < 1) { - if (analysis->verbose) { - eprintf("Analysis went too deep at address 0x%" PFMT64x ".\n", addr); - } - return RZ_ANALYSIS_RET_ERROR; // MUST BE TOO DEEP - } - // check if address is readable //: if (!analysis->iob.is_valid_offset(analysis->iob.io, addr, 0)) { if (addr != UT64_MAX && !analysis->iob.io->va) { @@ -586,31 +598,33 @@ static int fcn_recurse(RzAnalysis *analysis, RzAnalysisFunction *fcn, ut64 addr, return RZ_ANALYSIS_RET_ERROR; // MUST BE NOT FOUND } - RzAnalysisBlock *existing_bb = bbget(analysis, addr, analysis->opt.jmpmid && is_x86); - if (existing_bb) { - bool existing_in_fcn = rz_list_contains(existing_bb->fcns, fcn); - existing_bb = rz_analysis_block_split(existing_bb, addr); - if (!existing_in_fcn && existing_bb) { - if (existing_bb->addr == fcn->addr) { - // our function starts directly there, so we steal what is ours! - fcn_takeover_block_recursive(fcn, existing_bb); - } - } + if (!bb) { + RzAnalysisBlock *existing_bb = bbget(analysis, addr, analysis->opt.jmpmid && is_x86); if (existing_bb) { - rz_analysis_block_unref(existing_bb); + bool existing_in_fcn = rz_list_contains(existing_bb->fcns, fcn); + existing_bb = rz_analysis_block_split(existing_bb, addr); + if (!existing_in_fcn && existing_bb) { + if (existing_bb->addr == fcn->addr) { + // our function starts directly there, so we steal what is ours! + fcn_takeover_block_recursive(fcn, existing_bb); + } + } + if (existing_bb) { + rz_analysis_block_unref(existing_bb); + } + if (analysis->opt.recont) { + return RZ_ANALYSIS_RET_END; + } + if (analysis->verbose) { + eprintf("rz_analysis_fcn_bb() fails at 0x%" PFMT64x ".\n", addr); + } + return RZ_ANALYSIS_RET_ERROR; // MUST BE NOT DUP } - if (analysis->opt.recont) { - return RZ_ANALYSIS_RET_END; - } - if (analysis->verbose) { - eprintf("rz_analysis_fcn_bb() fails at 0x%" PFMT64x ".\n", addr); - } - return RZ_ANALYSIS_RET_ERROR; // MUST BE NOT DUP - } - bb = fcn_append_basic_block(analysis, fcn, addr); - // we checked before whether there is a bb at addr, so the create should have succeeded - rz_return_val_if_fail(bb, RZ_ANALYSIS_RET_ERROR); + item->block = bb = fcn_append_basic_block(analysis, fcn, addr); + // we checked before whether there is a bb at addr, so the create should have succeeded + rz_return_val_if_fail(bb, RZ_ANALYSIS_RET_ERROR); + } if (!analysis->leaddrs) { analysis->leaddrs = rz_list_newf(free_leaddr_pair); @@ -662,6 +676,8 @@ static int fcn_recurse(RzAnalysis *analysis, RzAnalysisFunction *fcn, ut64 addr, } while (addrbytes * idx < maxlen) { + ut32 at_delta; + ut64 at; if (!last_is_reg_mov_lea) { free(last_reg_mov_lea_name); last_reg_mov_lea_name = NULL; @@ -670,11 +686,12 @@ static int fcn_recurse(RzAnalysis *analysis, RzAnalysisFunction *fcn, ut64 addr, break; } repeat: + at_delta = addrbytes * idx; + at = addr + at_delta; if (rz_cons_is_breaked()) { + rz_analysis_task_item_new(analysis, tasks, fcn, bb, at); break; } - ut32 at_delta = addrbytes * idx; - ut64 at = addr + at_delta; ut64 bytes_read = RZ_MIN(len - at_delta, sizeof(buf)); ret = read_ahead(analysis, at, buf, bytes_read); @@ -764,7 +781,7 @@ static int fcn_recurse(RzAnalysis *analysis, RzAnalysisFunction *fcn, ut64 addr, bb->jump = at + oplen; if (from_addr != bb->addr) { bb->fail = handle_addr; - ret = rz_analysis_fcn_bb(analysis, fcn, handle_addr, depth); + ret = analyze_function_locally(analysis, fcn, handle_addr); eprintf("(%s) 0x%08" PFMT64x "\n", handle, handle_addr); if (bb->size == 0) { rz_analysis_function_remove_block(fcn, bb); @@ -941,18 +958,24 @@ static int fcn_recurse(RzAnalysis *analysis, RzAnalysisFunction *fcn, ut64 addr, ut64 jmptbl_addr = op.ptr; ut64 casetbl_addr = op.ptr; if (is_delta_pointer_table(analysis, fcn, op.addr, op.ptr, &jmptbl_addr, &casetbl_addr, &jmp_aop)) { - ut64 table_size, default_case = 0; - st64 case_shift; - // we require both checks here since try_get_jmptbl_info uses + // we require both checks here since rz_analysis_get_jmptbl_info uses // BB info of the final jmptbl jump, which is no present with // is_delta_pointer_table just scanning ahead - // try_get_delta_jmptbl_info doesn't work at times where the + // rz_analysis_get_delta_jmptbl_info doesn't work at times where the // lea comes after the cmp/default case cjmp, which can be - // handled with try_get_jmptbl_info - if (try_get_jmptbl_info(analysis, fcn, jmp_aop.addr, bb, &table_size, &default_case, &case_shift) || try_get_delta_jmptbl_info(analysis, fcn, jmp_aop.addr, op.addr, &table_size, &default_case, &case_shift)) { + // handled with rz_analysis_get_jmptbl_info + RzAnalysisJmpTableParams params = { + .jmp_address = jmp_aop.addr, + .jmptbl_loc = jmptbl_addr, + .casetbl_loc = casetbl_addr, + .entry_size = 4, + .jmptbl_off = op.ptr, + .tasks = tasks + }; + if (rz_analysis_get_jmptbl_info(analysis, fcn, bb, jmp_aop.addr, ¶ms) || rz_analysis_get_delta_jmptbl_info(analysis, fcn, jmp_aop.addr, op.addr, ¶ms)) { ret = casetbl_addr == op.ptr - ? try_walkthrough_jmptbl(analysis, fcn, bb, depth, jmp_aop.addr, case_shift, jmptbl_addr, op.ptr, 4, table_size, default_case, 4) - : try_walkthrough_casetbl(analysis, fcn, bb, depth, jmp_aop.addr, case_shift, jmptbl_addr, casetbl_addr, op.ptr, 4, table_size, default_case, 4); + ? rz_analysis_walkthrough_jmptbl(analysis, fcn, bb, ¶ms) + : rz_analysis_walkthrough_casetbl(analysis, fcn, bb, ¶ms); if (ret) { lea_jmptbl_ip = jmp_aop.addr; } @@ -1028,7 +1051,7 @@ static int fcn_recurse(RzAnalysis *analysis, RzAnalysisFunction *fcn, ut64 addr, bb->jump = op.jump; bb->fail = UT64_MAX; } - ret = rz_analysis_fcn_bb(analysis, fcn, op.jump, depth); + rz_analysis_task_item_new(analysis, tasks, fcn, NULL, op.jump); int tc = analysis->opt.tailcall; if (tc) { // eprintf ("TAIL CALL AT 0x%llx\n", op.addr); @@ -1037,11 +1060,11 @@ static int fcn_recurse(RzAnalysis *analysis, RzAnalysisFunction *fcn, ut64 addr, ut8 buf[32]; (void)analysis->iob.read_at(analysis->iob.io, op.jump, (ut8 *)buf, sizeof(buf)); if (rz_analysis_is_prelude(analysis, buf, sizeof(buf))) { - fcn_recurse(analysis, fcn, op.jump, analysis->opt.bb_max_size, depth - 1); + rz_analysis_task_item_new(analysis, tasks, fcn, NULL, op.jump); } } else if (RZ_ABS(diff) > tc) { (void)rz_analysis_xrefs_set(analysis, op.addr, op.jump, RZ_ANALYSIS_REF_TYPE_CALL); - fcn_recurse(analysis, fcn, op.jump, analysis->opt.bb_max_size, depth - 1); + rz_analysis_task_item_new(analysis, tasks, fcn, NULL, op.jump); gotoBeach(RZ_ANALYSIS_RET_END); } } @@ -1080,14 +1103,22 @@ static int fcn_recurse(RzAnalysis *analysis, RzAnalysisFunction *fcn, ut64 addr, } if (analysis->opt.jmptbl) { if (op.ptr != UT64_MAX) { - ut64 table_size, default_case; - table_size = cmpval + 1; - default_case = op.fail; // is this really default case? - if (cmpval != UT64_MAX && default_case != UT64_MAX && (op.reg || op.ireg)) { + if (cmpval != UT64_MAX && op.fail != UT64_MAX && (op.reg || op.ireg)) { + RzAnalysisJmpTableParams params = { + .jmp_address = op.addr, + .case_shift = 0, + .jmptbl_loc = op.ptr, + .casetbl_loc = UT64_MAX, + .entry_size = analysis->bits >> 3, + .table_count = cmpval + 1, + .jmptbl_off = op.ptr, + .default_case = op.fail, + .tasks = tasks + }; if (op.ireg) { - try_walkthrough_jmptbl(analysis, fcn, bb, depth, op.addr, 0, op.ptr, op.ptr, analysis->bits >> 3, table_size, default_case, ret); + rz_analysis_walkthrough_jmptbl(analysis, fcn, bb, ¶ms); } else { // op.reg - walkthrough_arm_jmptbl_style(analysis, fcn, bb, depth, op.addr, op.ptr, analysis->bits >> 3, table_size, default_case, ret); + rz_analysis_walkthrough_arm_jmptbl_style(analysis, fcn, bb, ¶ms); } // check if op.jump and op.fail contain jump table location // clear jump address, because it's jump table location @@ -1100,17 +1131,9 @@ static int fcn_recurse(RzAnalysis *analysis, RzAnalysisFunction *fcn, ut64 addr, } } } - int saved_stack = fcn->stack; - if (continue_after_jump) { - rz_analysis_fcn_bb(analysis, fcn, op.jump, depth); - fcn->stack = saved_stack; - ret = rz_analysis_fcn_bb(analysis, fcn, op.fail, depth); - fcn->stack = saved_stack; - } else { - rz_analysis_fcn_bb(analysis, fcn, op.jump, depth); - fcn->stack = saved_stack; - ret = rz_analysis_fcn_bb(analysis, fcn, op.fail, depth); - fcn->stack = saved_stack; + rz_analysis_task_item_new(analysis, tasks, fcn, NULL, op.fail); + rz_analysis_task_item_new(analysis, tasks, fcn, NULL, op.jump); + if (!continue_after_jump) { if (op.jump < fcn->addr) { if (!overlapped) { bb->jump = op.jump; @@ -1172,12 +1195,17 @@ static int fcn_recurse(RzAnalysis *analysis, RzAnalysisFunction *fcn, ut64 addr, } // switch statement if (analysis->opt.jmptbl && lea_jmptbl_ip != op.addr) { + RzAnalysisJmpTableParams params = { + .jmp_address = op.addr, + .entry_size = analysis->bits >> 3, + .jmptbl_loc = op.ptr, + .jmptbl_off = op.ptr, + .tasks = tasks + }; // op.ireg since rip relative addressing produces way too many false positives otherwise // op.ireg is 0 for rip relative, "rax", etc otherwise if (op.ptr != UT64_MAX && op.ireg) { // direct jump - ut64 table_size, default_case; - st64 case_shift; - if (try_get_jmptbl_info(analysis, fcn, op.addr, bb, &table_size, &default_case, &case_shift)) { + if (rz_analysis_get_jmptbl_info(analysis, fcn, bb, op.addr, ¶ms)) { bool case_table = false; RzAnalysisOp prev_op; analysis->iob.read_at(analysis->iob.io, op.addr - op.size, buf, sizeof(buf)); @@ -1188,30 +1216,26 @@ static int fcn_recurse(RzAnalysis *analysis, RzAnalysisFunction *fcn, ut64 addr, if (prev_op.type == RZ_ANALYSIS_OP_TYPE_MOV && prev_op.disp && prev_op.disp != UT64_MAX && same_reg) { // movzx reg, byte [reg + case_table] // jmp dword [reg*4 + jump_table] - if (try_walkthrough_casetbl(analysis, fcn, bb, depth, op.addr, case_shift, op.ptr, prev_op.disp, op.ptr, analysis->bits >> 3, table_size, default_case, ret)) { + params.casetbl_loc = prev_op.disp; + if (rz_analysis_walkthrough_casetbl(analysis, fcn, bb, ¶ms)) { ret = case_table = true; } } } rz_analysis_op_fini(&prev_op); if (!case_table) { - ret = try_walkthrough_jmptbl(analysis, fcn, bb, depth, op.addr, case_shift, op.ptr, op.ptr, analysis->bits >> 3, table_size, default_case, ret); + ret = rz_analysis_walkthrough_jmptbl(analysis, fcn, bb, ¶ms); } } } else if (op.ptr != UT64_MAX && op.reg) { // direct jump - ut64 table_size, default_case; - st64 case_shift; - if (try_get_jmptbl_info(analysis, fcn, op.addr, bb, &table_size, &default_case, &case_shift)) { - ret = try_walkthrough_jmptbl(analysis, fcn, bb, depth, op.addr, case_shift, op.ptr, op.ptr, analysis->bits >> 3, table_size, default_case, ret); + if (rz_analysis_get_jmptbl_info(analysis, fcn, bb, op.addr, ¶ms)) { + ret = rz_analysis_walkthrough_jmptbl(analysis, fcn, bb, ¶ms); } } else if (movdisp != UT64_MAX) { - ut64 table_size; - ut64 default_case; - ut64 jmptbl_base = 0; ut64 lea_op_off = UT64_MAX; - st64 case_shift; RzListIter *iter; leaddr_pair *pair; + params.jmptbl_off = 0; if (movbasereg) { // find nearest candidate leaddr before op.addr rz_list_foreach_prev(analysis->leaddrs, iter, pair) { @@ -1220,42 +1244,46 @@ static int fcn_recurse(RzAnalysis *analysis, RzAnalysisFunction *fcn, ut64 addr, } if ((lea_op_off == UT64_MAX || lea_op_off > op.addr - pair->op_addr) && pair->reg && !strcmp(movbasereg, pair->reg)) { lea_op_off = op.addr - pair->op_addr; - jmptbl_base = pair->leaddr; + params.jmptbl_off = pair->leaddr; } } } - if (!try_get_jmptbl_info(analysis, fcn, op.addr, bb, &table_size, &default_case, &case_shift)) { - table_size = cmpval + 1; - default_case = -1; + if (!rz_analysis_get_jmptbl_info(analysis, fcn, bb, op.addr, ¶ms)) { + params.table_count = cmpval + 1; + params.default_case = -1; } - ret = try_walkthrough_jmptbl(analysis, fcn, bb, depth, op.addr, case_shift, jmptbl_base + movdisp, jmptbl_base, movscale, table_size, default_case, ret); + params.jmptbl_loc = params.jmptbl_off + movdisp; + params.entry_size = movscale; + ret = rz_analysis_walkthrough_jmptbl(analysis, fcn, bb, ¶ms); cmpval = UT64_MAX; } else if (is_arm) { + params.jmptbl_loc = op.addr + op.size; + params.jmptbl_off = op.addr + 4; + params.default_case = UT64_MAX; if (op.ptrsize == 1) { // TBB ut64 pred_cmpval = try_get_cmpval_from_parents(analysis, fcn, bb, op.ireg); - ut64 table_size = 0; + params.table_count = 0; if (pred_cmpval != UT64_MAX) { - table_size += pred_cmpval; + params.table_count += pred_cmpval; } else { - table_size += cmpval; + params.table_count += cmpval; } - ret = try_walkthrough_jmptbl(analysis, fcn, bb, depth, op.addr, 0, op.addr + op.size, - op.addr + 4, 1, table_size, UT64_MAX, ret); + params.entry_size = 1; + ret = rz_analysis_walkthrough_jmptbl(analysis, fcn, bb, ¶ms); // skip inlined jumptable - idx += table_size; - } - if (op.ptrsize == 2) { // LDRH on thumb/arm + idx += params.table_count; + } else if (op.ptrsize == 2) { // LDRH on thumb/arm ut64 pred_cmpval = try_get_cmpval_from_parents(analysis, fcn, bb, op.ireg); - int tablesize = 1; + params.table_count = 1; if (pred_cmpval != UT64_MAX) { - tablesize += pred_cmpval; + params.table_count += pred_cmpval; } else { - tablesize += cmpval; + params.table_count += cmpval; } - ret = try_walkthrough_jmptbl(analysis, fcn, bb, depth, op.addr, 0, op.addr + op.size, - op.addr + 4, 2, tablesize, UT64_MAX, ret); + params.entry_size = 2; + ret = rz_analysis_walkthrough_jmptbl(analysis, fcn, bb, ¶ms); // skip inlined jumptable - idx += (tablesize * 2); + idx += (params.table_count * 2); } } } @@ -1264,8 +1292,8 @@ static int fcn_recurse(RzAnalysis *analysis, RzAnalysisFunction *fcn, ut64 addr, } if (analysis->opt.ijmp) { if (continue_after_jump) { - rz_analysis_fcn_bb(analysis, fcn, op.jump, depth); - ret = rz_analysis_fcn_bb(analysis, fcn, op.fail, depth); + rz_analysis_task_item_new(analysis, tasks, fcn, NULL, op.fail); + rz_analysis_task_item_new(analysis, tasks, fcn, NULL, op.jump); if (overlapped) { goto analopfinish; } @@ -1282,7 +1310,6 @@ static int fcn_recurse(RzAnalysis *analysis, RzAnalysisFunction *fcn, ut64 addr, } } break; - /* fallthru */ case RZ_ANALYSIS_OP_TYPE_PUSH: last_is_push = true; last_push_addr = op.val; @@ -1307,7 +1334,7 @@ static int fcn_recurse(RzAnalysis *analysis, RzAnalysisFunction *fcn, ut64 addr, op.type = RZ_ANALYSIS_OP_TYPE_JMP; op.jump = last_push_addr; bb->jump = op.jump; - ret = rz_analysis_fcn_bb(analysis, fcn, op.jump, depth); + rz_analysis_task_item_new(analysis, tasks, fcn, NULL, op.jump); goto beach; } if (!op.cond) { @@ -1358,10 +1385,6 @@ static int fcn_recurse(RzAnalysis *analysis, RzAnalysisFunction *fcn, ut64 addr, } } beach: - while (lea_cnt > 0) { - rz_list_delete(analysis->leaddrs, rz_list_tail(analysis->leaddrs)); - lea_cnt--; - } rz_analysis_op_fini(&op); RZ_FREE(last_reg_mov_lea_name); if (bb && bb->size == 0) { @@ -1373,8 +1396,65 @@ beach: return ret; } -RZ_API int rz_analysis_fcn_bb(RzAnalysis *analysis, RzAnalysisFunction *fcn, ut64 addr, int depth) { - return fcn_recurse(analysis, fcn, addr, analysis->opt.bb_max_size, depth - 1); +/** + * \brief Adds a new task item to the `tasks` parameter. + * + * Used to create a new item to the `tasks` parameter + * that can be worked on later by the `rz_analysis_run_tasks` function. + * + * \param analysis Pointer to RzAnalysis instance. + * \param tasks Pointer to RzVector to add a new RzAnalysisTaskItem to. + * \param fcn Pointer to RzAnalysisFunction in which analysis will be performed on. + * \param block Pointer to RzAnalysisBlock in which analysis will be performed on. If null, analysis will take care of block creation. + * \param address Address where analysis will start from + */ +RZ_API bool rz_analysis_task_item_new(RZ_NONNULL RzAnalysis *analysis, RZ_NONNULL RzVector *tasks, RZ_NONNULL RzAnalysisFunction *fcn, RZ_NULLABLE RzAnalysisBlock *block, ut64 address) { + rz_return_val_if_fail(analysis && tasks && fcn, false); + RzAnalysisTaskItem item = { fcn, block, fcn->stack, address }; + RzAnalysisTaskItem *it; + rz_vector_foreach(tasks, it) { + if (item.start_address == it->start_address) { + return true; + } + } + return rz_vector_push(tasks, &item); +} + +/** + * \brief Runs analysis on the task items. + * + * Runs control-flow and variable usage analysis on each of the task items until tasks vector becomes empty. + * Items are removed from the tasks vector as they are processed. + * Items are added to the tasks vector as new basic blocks are found to be analyzed. + * + * \param tasks Pointer to RzVector of RzAnalysisTaskItem to be performed analysis on. + */ +RZ_API int rz_analysis_run_tasks(RZ_NONNULL RzVector *tasks) { + rz_return_val_if_fail(tasks, RZ_ANALYSIS_RET_ERROR); + int ret = RZ_ANALYSIS_RET_ERROR; + while (!rz_vector_empty(tasks)) { + RzAnalysisTaskItem item; + rz_vector_pop(tasks, &item); + int r = run_basic_block_analysis(&item, tasks); + switch (r) { + case RZ_ANALYSIS_RET_NOP: + case RZ_ANALYSIS_RET_ERROR: + case RZ_ANALYSIS_RET_DUP: + case RZ_ANALYSIS_RET_NEW: + if (ret != RZ_ANALYSIS_RET_END) { + ret = r; + } + break; + case RZ_ANALYSIS_RET_END: + default: + ret = r; + break; + } + if (rz_cons_is_breaked()) { + break; + } + } + return ret; } RZ_API bool rz_analysis_check_fcn(RzAnalysis *analysis, ut8 *buf, ut16 bufsz, ut64 addr, ut64 low, ut64 high) { @@ -1487,12 +1567,11 @@ RZ_API int rz_analysis_fcn(RzAnalysis *analysis, RzAnalysisFunction *fcn, ut64 a const int shadow_store = 0x28; // First 4 args + retaddr fcn->stack = fcn->maxstack = fcn->reg_save_area = shadow_store; } - int ret = rz_analysis_fcn_bb(analysis, fcn, addr, analysis->opt.depth); - if (ret < 0) { - if (analysis->verbose) { - eprintf("Failed to analyze basic block at 0x%" PFMT64x "\n", addr); - } - } + RzVector tasks; + rz_vector_init(&tasks, sizeof(RzAnalysisTaskItem), NULL, NULL); + rz_analysis_task_item_new(analysis, &tasks, fcn, NULL, addr); + int ret = rz_analysis_run_tasks(&tasks); + rz_vector_fini(&tasks); if (analysis->opt.endsize && ret == RZ_ANALYSIS_RET_END && rz_analysis_function_realsize(fcn)) { // cfg analysis completed RzListIter *iter; RzAnalysisBlock *bb; @@ -2033,7 +2112,7 @@ static bool analize_addr_cb(ut64 addr, void *user) { RzAnalysisBlock *existing_bb = rz_analysis_get_block_at(analysis, addr); if (!existing_bb || !rz_list_contains(ctx->fcn->bbs, existing_bb)) { int old_len = rz_list_length(ctx->fcn->bbs); - rz_analysis_fcn_bb(ctx->fcn->analysis, ctx->fcn, addr, analysis->opt.depth); + analyze_function_locally(ctx->fcn->analysis, ctx->fcn, addr); if (old_len != rz_list_length(ctx->fcn->bbs)) { rz_analysis_block_recurse(rz_analysis_get_block_at(analysis, addr), mark_as_visited, user); } @@ -2118,7 +2197,7 @@ static void update_analysis(RzAnalysis *analysis, RzList *fcns, HtUP *reachable) // analyze edges that don't have a block RzAnalysisBlock *bb = rz_analysis_get_block_at(analysis, fcn->addr); if (!bb) { - rz_analysis_fcn_bb(analysis, fcn, fcn->addr, analysis->opt.depth); + analyze_function_locally(analysis, fcn, fcn->addr); bb = rz_analysis_get_block_at(analysis, fcn->addr); if (!bb) { continue; diff --git a/librz/analysis/jmptbl.c b/librz/analysis/jmptbl.c index b1b0f49152..4313f7b609 100644 --- a/librz/analysis/jmptbl.c +++ b/librz/analysis/jmptbl.c @@ -12,8 +12,6 @@ if (analysis->verbose) \ eprintf(format, __VA_ARGS__) -#define JMPTBL_MAXSZ 512 - static void apply_case(RzAnalysis *analysis, RzAnalysisBlock *block, ut64 switch_addr, ut64 offset_sz, ut64 case_addr, ut64 id, ut64 case_addr_loc) { // eprintf ("** apply_case: 0x%"PFMT64x " from 0x%"PFMT64x "\n", case_addr, case_addr_loc); rz_meta_set_data_at(analysis, case_addr_loc, offset_sz); @@ -46,64 +44,69 @@ static void apply_switch(RzAnalysis *analysis, ut64 switch_addr, ut64 jmptbl_add // analyze a jmptablle inside a function // maybe rename to rz_analysis_fcn_jmptbl() ? RZ_API bool rz_analysis_jmptbl(RzAnalysis *analysis, RzAnalysisFunction *fcn, RzAnalysisBlock *block, ut64 jmpaddr, ut64 table, ut64 tablesize, ut64 default_addr) { - const int depth = 50; - return try_walkthrough_jmptbl(analysis, fcn, block, depth, jmpaddr, 0, table, table, tablesize, tablesize, default_addr, false); + RzAnalysisJmpTableParams params = { + .jmp_address = jmpaddr, + .case_shift = 0, + .jmptbl_loc = table, + .casetbl_loc = UT64_MAX, + .jmptbl_off = table, + .entry_size = tablesize, + .table_count = tablesize, + .default_case = default_addr, + }; + return rz_analysis_walkthrough_jmptbl(analysis, fcn, block, ¶ms); } -static inline void analyze_new_case(RzAnalysis *analysis, RzAnalysisFunction *fcn, RzAnalysisBlock *block, ut64 ip, ut64 jmpptr, int depth) { - const ut64 block_size = block->size; - (void)rz_analysis_fcn_bb(analysis, fcn, jmpptr, depth - 1); - if (block->size != block_size) { - // block was be split during analysis and does not contain the - // jmp instruction anymore, so we need to search for it and get it again - RzAnalysisSwitchOp *sop = block->switch_op; - block = rz_analysis_find_most_relevant_block_in(analysis, ip); - if (!block) { - rz_warn_if_reached(); - return; - } - block->switch_op = sop; +/** +* \brief Marks for analysis jump table cases with a space optimization for multiple cases corresponding to the same address +* +* This function works similarly to `rz_analysis_walkthrough_jmptbl`, +* with the difference that jump targets are hidden behind a indirection in the case table +* +* \param analysis Pointer to RzAnalysis instance +* \param fcn Pointer to RzAnalysisFunction to add the new cases +* \param block Pointer to RzAnalysisBlock that originates the switch table +* \param params Pointer to RzAnalysisJmpTableParams necessary to analyze the jump table +*/ +RZ_API bool rz_analysis_walkthrough_casetbl(RZ_NONNULL RzAnalysis *analysis, RZ_NONNULL RzAnalysisFunction *fcn, RZ_NONNULL RzAnalysisBlock *block, RZ_NONNULL RzAnalysisJmpTableParams *params) { + rz_return_val_if_fail(analysis && fcn && block && params, false); + bool ret = true; + if (params->table_count == 0) { + params->table_count = analysis->opt.jmptbl_maxcount; } -} - -RZ_API bool try_walkthrough_casetbl(RzAnalysis *analysis, RzAnalysisFunction *fcn, RzAnalysisBlock *block, int depth, ut64 ip, st64 start_casenum_shift, ut64 jmptbl_loc, ut64 casetbl_loc, ut64 jmptbl_off, ut64 sz, ut64 jmptbl_size, ut64 default_case, bool ret0) { - bool ret = ret0; - if (jmptbl_size == 0) { - jmptbl_size = JMPTBL_MAXSZ; - } - if (jmptbl_loc == UT64_MAX) { - aprintf("Warning: Invalid JumpTable location 0x%08" PFMT64x "\n", jmptbl_loc); + if (params->jmptbl_loc == UT64_MAX) { + aprintf("Warning: Invalid JumpTable location 0x%08" PFMT64x "\n", params->jmptbl_loc); return false; } - if (casetbl_loc == UT64_MAX) { - aprintf("Warning: Invalid CaseTable location 0x%08" PFMT64x "\n", jmptbl_loc); + if (params->casetbl_loc == UT64_MAX) { + aprintf("Warning: Invalid CaseTable location 0x%08" PFMT64x "\n", params->jmptbl_loc); return false; } - if (jmptbl_size < 1 || jmptbl_size > ST32_MAX) { - aprintf("Warning: Invalid JumpTable size at 0x%08" PFMT64x "\n", ip); + if (params->table_count < 1 || params->table_count > ST32_MAX) { + aprintf("Warning: Invalid JumpTable size at 0x%08" PFMT64x "\n", params->jmp_address); return false; } ut64 jmpptr, case_idx, jmpptr_idx; - ut8 *jmptbl = calloc(jmptbl_size, sz); - if (!jmptbl || !analysis->iob.read_at(analysis->iob.io, jmptbl_loc, jmptbl, jmptbl_size * sz)) { + ut8 *jmptbl = calloc(params->table_count, params->entry_size); + if (!jmptbl || !analysis->iob.read_at(analysis->iob.io, params->jmptbl_loc, jmptbl, params->table_count * params->entry_size)) { free(jmptbl); return false; } - ut8 *casetbl = calloc(jmptbl_size, sizeof(ut8)); - if (!casetbl || !analysis->iob.read_at(analysis->iob.io, casetbl_loc, casetbl, jmptbl_size)) { + ut8 *casetbl = calloc(params->table_count, sizeof(ut8)); + if (!casetbl || !analysis->iob.read_at(analysis->iob.io, params->casetbl_loc, casetbl, params->table_count)) { free(jmptbl); free(casetbl); return false; } - for (case_idx = 0; case_idx < jmptbl_size; case_idx++) { + for (case_idx = 0; case_idx < params->table_count; case_idx++) { jmpptr_idx = casetbl[case_idx]; - if (jmpptr_idx >= jmptbl_size) { + if (jmpptr_idx >= params->table_count) { ret = false; break; } - switch (sz) { + switch (params->entry_size) { case 1: jmpptr = rz_read_le8(jmptbl + jmpptr_idx); break; @@ -123,7 +126,7 @@ RZ_API bool try_walkthrough_casetbl(RzAnalysis *analysis, RzAnalysisFunction *fc if (!analysis->iob.is_valid_offset(analysis->iob.io, jmpptr, 0)) { st32 jmpdelta = (st32)jmpptr; // jump tables where sign extended movs are used - jmpptr = jmptbl_off + jmpdelta; + jmpptr = params->jmptbl_off + jmpdelta; if (!analysis->iob.is_valid_offset(analysis->iob.io, jmpptr, 0)) { break; } @@ -134,19 +137,19 @@ RZ_API bool try_walkthrough_casetbl(RzAnalysis *analysis, RzAnalysisFunction *fc } } - const ut64 jmpptr_idx_off = casetbl_loc + case_idx; + const ut64 jmpptr_idx_off = params->casetbl_loc + case_idx; rz_meta_set_data_at(analysis, jmpptr_idx_off, 1); rz_analysis_hint_set_immbase(analysis, jmpptr_idx_off, 10); - apply_case(analysis, block, ip, sz, jmpptr, case_idx + start_casenum_shift, jmptbl_loc + jmpptr_idx * sz); - analyze_new_case(analysis, fcn, block, ip, jmpptr, depth); + apply_case(analysis, block, params->jmp_address, params->entry_size, jmpptr, case_idx + params->case_shift, params->jmptbl_loc + jmpptr_idx * params->entry_size); + rz_analysis_task_item_new(analysis, params->tasks, fcn, NULL, jmpptr); } if (case_idx > 0) { - if (default_case == 0) { - default_case = UT64_MAX; + if (params->default_case == 0) { + params->default_case = UT64_MAX; } - apply_switch(analysis, ip, jmptbl_loc, case_idx, default_case); + apply_switch(analysis, params->jmp_address, params->jmptbl_loc, case_idx, params->default_case); } free(jmptbl); @@ -154,30 +157,41 @@ RZ_API bool try_walkthrough_casetbl(RzAnalysis *analysis, RzAnalysisFunction *fc return ret; } -RZ_API bool try_walkthrough_jmptbl(RzAnalysis *analysis, RzAnalysisFunction *fcn, RzAnalysisBlock *block, int depth, ut64 ip, st64 start_casenum_shift, ut64 jmptbl_loc, ut64 jmptbl_off, ut64 sz, ut64 jmptbl_size, ut64 default_case, bool ret0) { - bool ret = ret0; - // jmptbl_size can not always be determined - if (jmptbl_size == 0) { - jmptbl_size = JMPTBL_MAXSZ; +/** +* \brief Marks the jump table cases for analysis +* +* Goes through each case on the jump table, adds necessary flags/metadata and +* a new RzAnalysisTaskItem to `params->tasks` to be analyzed later. +* +* \param analysis Pointer to RzAnalysis instance +* \param fcn Pointer to RzAnalysisFunction to add the new cases +* \param block Pointer to RzAnalysisBlock that originates the switch table +* \param params Pointer to RzAnalysisJmpTableParams necessary to analyze the jump table +*/ +RZ_API bool rz_analysis_walkthrough_jmptbl(RZ_NONNULL RzAnalysis *analysis, RZ_NONNULL RzAnalysisFunction *fcn, RZ_NONNULL RzAnalysisBlock *block, RZ_NONNULL RzAnalysisJmpTableParams *params) { + rz_return_val_if_fail(analysis && fcn && block && params, false); + // table_count can not always be determined + if (params->table_count == 0) { + params->table_count = analysis->opt.jmptbl_maxcount; } - if (jmptbl_loc == UT64_MAX) { - aprintf("Warning: Invalid JumpTable location 0x%08" PFMT64x "\n", jmptbl_loc); + if (params->jmptbl_loc == UT64_MAX) { + aprintf("Warning: Invalid JumpTable location 0x%08" PFMT64x "\n", params->jmptbl_loc); return false; } - if (jmptbl_size < 1 || jmptbl_size > ST32_MAX) { - aprintf("Warning: Invalid JumpTable size at 0x%08" PFMT64x "\n", ip); + if (params->table_count < 1 || params->table_count > ST32_MAX) { + aprintf("Warning: Invalid JumpTable size at 0x%08" PFMT64x "\n", params->jmp_address); return false; } ut64 jmpptr, offs; - ut8 *jmptbl = calloc(jmptbl_size, sz); + ut8 *jmptbl = calloc(params->table_count, params->entry_size); if (!jmptbl) { return false; } bool is_arm = analysis->cur->arch && !strncmp(analysis->cur->arch, "arm", 3); // eprintf ("JMPTBL AT 0x%"PFMT64x"\n", jmptbl_loc); - analysis->iob.read_at(analysis->iob.io, jmptbl_loc, jmptbl, jmptbl_size * sz); - for (offs = 0; offs + sz - 1 < jmptbl_size * sz; offs += sz) { - switch (sz) { + analysis->iob.read_at(analysis->iob.io, params->jmptbl_loc, jmptbl, params->table_count * params->entry_size); + for (offs = 0; offs + params->entry_size - 1 < params->table_count * params->entry_size; offs += params->entry_size) { + switch (params->entry_size) { case 1: jmpptr = (ut64)(ut8)rz_read_le8(jmptbl + offs); break; @@ -201,14 +215,14 @@ RZ_API bool try_walkthrough_jmptbl(RzAnalysis *analysis, RzAnalysisFunction *fcn if (jmpptr == 0 || jmpptr == UT32_MAX || jmpptr == UT64_MAX) { break; } - if (sz == 2 && is_arm) { - jmpptr = ip + 4 + (jmpptr * 2); // tbh [pc, r2, lsl 1] // assume lsl 1 - } else if (sz == 1 && is_arm) { - jmpptr = ip + 4 + (jmpptr * 2); // lbb [pc, r2] // assume lsl 1 + if (params->entry_size == 2 && is_arm) { + jmpptr = params->jmp_address + 4 + (jmpptr * 2); // tbh [pc, r2, lsl 1] // assume lsl 1 + } else if (params->entry_size == 1 && is_arm) { + jmpptr = params->jmp_address + 4 + (jmpptr * 2); // lbb [pc, r2] // assume lsl 1 } else if (!analysis->iob.is_valid_offset(analysis->iob.io, jmpptr, 0)) { st32 jmpdelta = (st32)jmpptr; // jump tables where sign extended movs are used - jmpptr = jmptbl_off + jmpdelta; + jmpptr = params->jmptbl_off + jmpdelta; if (!analysis->iob.is_valid_offset(analysis->iob.io, jmpptr, 0)) { break; } @@ -218,19 +232,19 @@ RZ_API bool try_walkthrough_jmptbl(RzAnalysis *analysis, RzAnalysisFunction *fcn break; } } - apply_case(analysis, block, ip, sz, jmpptr, (offs / sz) + start_casenum_shift, jmptbl_loc + offs); - analyze_new_case(analysis, fcn, block, ip, jmpptr, depth); + apply_case(analysis, block, params->jmp_address, params->entry_size, jmpptr, (offs / params->entry_size) + params->case_shift, params->jmptbl_loc + offs); + rz_analysis_task_item_new(analysis, params->tasks, fcn, NULL, jmpptr); } if (offs > 0) { - if (default_case == 0) { - default_case = UT64_MAX; + if (params->default_case == 0) { + params->default_case = UT64_MAX; } - apply_switch(analysis, ip, jmptbl_loc, offs / sz, default_case); + apply_switch(analysis, params->jmp_address, params->jmptbl_loc, offs / params->entry_size, params->default_case); } free(jmptbl); - return ret; + return true; } static bool detect_casenum_shift(RzAnalysisOp *op, RzRegItem **cmp_reg, st64 *start_casenum_shift) { @@ -255,29 +269,43 @@ static bool detect_casenum_shift(RzAnalysisOp *op, RzRegItem **cmp_reg, st64 *st return false; } -// TODO: RENAME -RZ_API bool try_get_delta_jmptbl_info(RzAnalysis *analysis, RzAnalysisFunction *fcn, ut64 jmp_addr, ut64 lea_addr, ut64 *table_size, ut64 *default_case, st64 *start_casenum_shift) { +/** +* \brief Gets some necessary information about a jump table to perform analysis on +* +* Gets amount of cases inside a jump table, the default case address and the case shift amount +* +* \param analysis Pointer to RzAnalysis instance +* \param fcn Pointer to RzAnalysisFunction where jump table ocurred +* \param jmp_address Address of jump intruction that uses the table +* \param lea_addr Address of lea instruction that loads the address of the jump table base +* \param params Pointer to RzAnalysisJmpTableParams where the results of the function are stored +*/ +RZ_API bool rz_analysis_get_delta_jmptbl_info(RZ_NONNULL RzAnalysis *analysis, RZ_NONNULL RzAnalysisFunction *fcn, ut64 jmp_address, ut64 lea_address, RZ_NONNULL RzAnalysisJmpTableParams *params) { + rz_return_val_if_fail(analysis && fcn && params, false); bool isValid = false; bool foundCmp = false; int i; RzAnalysisOp tmp_aop = { 0 }; - if (lea_addr > jmp_addr) { + if (lea_address > jmp_address) { return false; } - int search_sz = jmp_addr - lea_addr; + + params->jmp_address = jmp_address; + + int search_sz = jmp_address - lea_address; ut8 *buf = malloc(search_sz); if (!buf) { return false; } // search for a cmp register with a reasonable size - analysis->iob.read_at(analysis->iob.io, lea_addr, (ut8 *)buf, search_sz); + analysis->iob.read_at(analysis->iob.io, lea_address, (ut8 *)buf, search_sz); RzVector v; rz_vector_init(&v, sizeof(ut64), NULL, NULL); int len = 0; RzRegItem *cmp_reg = NULL; for (i = 0; i + 8 < search_sz; i += len) { - len = rz_analysis_op(analysis, &tmp_aop, lea_addr + i, buf + i, search_sz - i, RZ_ANALYSIS_OP_MASK_BASIC); + len = rz_analysis_op(analysis, &tmp_aop, lea_address + i, buf + i, search_sz - i, RZ_ANALYSIS_OP_MASK_BASIC); if (len < 1) { len = 1; } @@ -287,7 +315,7 @@ RZ_API bool try_get_delta_jmptbl_info(RzAnalysis *analysis, RzAnalysisFunction * continue; } - *default_case = tmp_aop.jump == tmp_aop.jump + len ? tmp_aop.fail : tmp_aop.jump; + params->default_case = tmp_aop.jump == tmp_aop.jump + len ? tmp_aop.fail : tmp_aop.jump; break; } @@ -303,16 +331,16 @@ RZ_API bool try_get_delta_jmptbl_info(RzAnalysis *analysis, RzAnalysisFunction * // might not actually have a value, let the next step figure out the size then if (tmp_aop.val == UT64_MAX && tmp_aop.refptr == 0) { isValid = true; - *table_size = 0; + params->table_count = 0; } else if (tmp_aop.refptr == 0) { isValid = tmp_aop.val < 0x200; - *table_size = tmp_aop.val + 1; + params->table_count = tmp_aop.val + 1; } else { isValid = tmp_aop.refptr < 0x200; - *table_size = tmp_aop.refptr + 1; + params->table_count = tmp_aop.refptr + 1; } rz_vector_push(&v, &i); - rz_analysis_op(analysis, &tmp_aop, lea_addr + i, buf + i, search_sz - i, RZ_ANALYSIS_OP_MASK_VAL); + rz_analysis_op(analysis, &tmp_aop, lea_address + i, buf + i, search_sz - i, RZ_ANALYSIS_OP_MASK_VAL); if (tmp_aop.dst && tmp_aop.dst->reg) { cmp_reg = tmp_aop.dst->reg; } else if (tmp_aop.reg) { @@ -325,15 +353,15 @@ RZ_API bool try_get_delta_jmptbl_info(RzAnalysis *analysis, RzAnalysisFunction * foundCmp = true; } if (isValid) { - *start_casenum_shift = 0; + params->case_shift = 0; void **it; rz_vector_foreach_prev(&v, it) { const ut64 op_off = *(ut64 *)it; - ut64 op_addr = lea_addr + op_off; + ut64 op_addr = lea_address + op_off; rz_analysis_op(analysis, &tmp_aop, op_addr, buf + op_off, search_sz - op_off, RZ_ANALYSIS_OP_MASK_VAL); - if (detect_casenum_shift(&tmp_aop, &cmp_reg, start_casenum_shift)) { + if (detect_casenum_shift(&tmp_aop, &cmp_reg, ¶ms->case_shift)) { rz_analysis_op_fini(&tmp_aop); break; } @@ -345,8 +373,17 @@ RZ_API bool try_get_delta_jmptbl_info(RzAnalysis *analysis, RzAnalysisFunction * return isValid; } -// TODO: find a better function name -RZ_API int walkthrough_arm_jmptbl_style(RzAnalysis *analysis, RzAnalysisFunction *fcn, RzAnalysisBlock *block, int depth, ut64 ip, ut64 jmptbl_loc, ut64 sz, ut64 jmptbl_size, ut64 default_case, int ret0) { +/** +* \brief Marks for analysis ARM specific jump table cases +* +* This function works similarly to `rz_analysis_walkthrough_jmptbl`, but is specific to ARM +* +* \param analysis Pointer to RzAnalysis instance +* \param fcn Pointer to RzAnalysisFunction to add the new cases +* \param block Pointer to RzAnalysisBlock that originates the switch table +* \param params Pointer to RzAnalysisJmpTableParams necessary to analyze the jump table +*/ +RZ_API bool rz_analysis_walkthrough_arm_jmptbl_style(RZ_NONNULL RzAnalysis *analysis, RZ_NONNULL RzAnalysisFunction *fcn, RZ_NONNULL RzAnalysisBlock *block, RZ_NONNULL RzAnalysisJmpTableParams *params) { /* * Example about arm jump table * @@ -365,30 +402,41 @@ RZ_API int walkthrough_arm_jmptbl_style(RzAnalysis *analysis, RzAnalysisFunction * ; CODE XREF from loc._a_7 (+0x18) * 0x000105e8 bbffffea b sym.input_3 */ - + rz_return_val_if_fail(analysis && fcn && block && params, false); ut64 offs, jmpptr; - int ret = ret0; - if (jmptbl_size == 0) { - jmptbl_size = JMPTBL_MAXSZ; + if (params->table_count == 0) { + params->table_count = analysis->opt.jmptbl_maxcount; } - for (offs = 0; offs + sz - 1 < jmptbl_size * sz; offs += sz) { - jmpptr = jmptbl_loc + offs; - apply_case(analysis, block, ip, sz, jmpptr, offs / sz, jmptbl_loc + offs); - analyze_new_case(analysis, fcn, block, ip, jmpptr, depth); + for (offs = 0; offs + params->entry_size - 1 < params->table_count * params->entry_size; offs += params->entry_size) { + jmpptr = params->jmptbl_loc + offs; + apply_case(analysis, block, params->jmp_address, params->entry_size, jmpptr, offs / params->entry_size, params->jmptbl_loc + offs); + rz_analysis_task_item_new(analysis, params->tasks, fcn, NULL, jmpptr); } if (offs > 0) { - if (default_case == 0 || default_case == UT32_MAX) { - default_case = UT64_MAX; + if (params->default_case == 0 || params->default_case == UT32_MAX) { + params->default_case = UT64_MAX; } - apply_switch(analysis, ip, jmptbl_loc, offs / sz, default_case); + apply_switch(analysis, params->jmp_address, params->jmptbl_loc, offs / params->entry_size, params->default_case); } - return ret; + return true; } -RZ_API bool try_get_jmptbl_info(RzAnalysis *analysis, RzAnalysisFunction *fcn, ut64 addr, RzAnalysisBlock *my_bb, ut64 *table_size, ut64 *default_case, st64 *start_casenum_shift) { +/** +* \brief Gets some necessary information about a jump table to perform analysis on +* +* Gets amount of cases inside a jump table, the default case address and the case shift amount +* +* \param analysis Pointer to RzAnalysis instance +* \param fcn Pointer to RzAnalysisFunction where jump table ocurred +* \param block Pointer to RzAnalysisBlock where the jump instruction related to the jump table ocurred +* \param jmp_address Address of jump intruction that uses the table +* \param params Pointer to RzAnalysisJmpTableParams where the results of the function are stored +*/ +RZ_API bool rz_analysis_get_jmptbl_info(RZ_NONNULL RzAnalysis *analysis, RZ_NONNULL RzAnalysisFunction *fcn, RZ_NONNULL RzAnalysisBlock *block, ut64 jmp_address, RZ_NONNULL RzAnalysisJmpTableParams *params) { + rz_return_val_if_fail(analysis && fcn && params && block, false); bool isValid = false; int i; RzListIter *iter; @@ -398,8 +446,10 @@ RZ_API bool try_get_jmptbl_info(RzAnalysis *analysis, RzAnalysisFunction *fcn, u return false; } + params->jmp_address = jmp_address; + /* if UJMP is in .plt section just skip it */ - RzBinSection *s = analysis->binb.get_vsect_at(analysis->binb.bin, addr); + RzBinSection *s = analysis->binb.get_vsect_at(analysis->binb.bin, jmp_address); if (s && s->name[0]) { bool in_plt = strstr(s->name, ".plt") != NULL; if (!in_plt && strstr(s->name, "_stubs") != NULL) { @@ -413,19 +463,19 @@ RZ_API bool try_get_jmptbl_info(RzAnalysis *analysis, RzAnalysisFunction *fcn, u // search for the predecessor bb rz_list_foreach (fcn->bbs, iter, tmp_bb) { - if (tmp_bb->jump == my_bb->addr || tmp_bb->fail == my_bb->addr) { + if (tmp_bb->jump == block->addr || tmp_bb->fail == block->addr) { prev_bb = tmp_bb; break; } } // predecessor must be a conditional jump if (!prev_bb || !prev_bb->jump || !prev_bb->fail) { - aprintf("Warning: [analysis.jmp.tbl] Missing predecesessor cjmp bb at 0x%08" PFMT64x "\n", addr); + aprintf("Warning: [analysis.jmp.tbl] Missing predecesessor cjmp bb at 0x%08" PFMT64x "\n", jmp_address); return false; } // default case is the jump target of the unconditional jump - *default_case = prev_bb->jump == my_bb->addr ? prev_bb->fail : prev_bb->jump; + params->default_case = prev_bb->jump == block->addr ? prev_bb->fail : prev_bb->jump; RzAnalysisOp tmp_aop = { 0 }; ut8 *bb_buf = calloc(1, prev_bb->size); @@ -436,12 +486,12 @@ RZ_API bool try_get_jmptbl_info(RzAnalysis *analysis, RzAnalysisFunction *fcn, u analysis->iob.read_at(analysis->iob.io, prev_bb->addr, (ut8 *)bb_buf, prev_bb->size); isValid = false; - RzAnalysisHint *hint = rz_analysis_hint_get(analysis, addr); + RzAnalysisHint *hint = rz_analysis_hint_get(analysis, jmp_address); if (hint) { ut64 val = hint->val; rz_analysis_hint_free(hint); if (val != UT64_MAX) { - *table_size = val; + params->table_count = val; return true; } } @@ -470,13 +520,13 @@ RZ_API bool try_get_jmptbl_info(RzAnalysis *analysis, RzAnalysisFunction *fcn, u // might not actually have a value, let the next step figure out the size then if (tmp_aop.val == UT64_MAX && tmp_aop.refptr == 0) { isValid = true; - *table_size = 0; + params->table_count = 0; } else if (tmp_aop.refptr == 0 || tmp_aop.val != UT64_MAX) { isValid = tmp_aop.val < 0x200; - *table_size = tmp_aop.val + 1; + params->table_count = tmp_aop.val + 1; } else { isValid = tmp_aop.refptr < 0x200; - *table_size = tmp_aop.refptr + 1; + params->table_count = tmp_aop.refptr + 1; } if (isValid) { rz_analysis_op_fini(&tmp_aop); @@ -496,7 +546,7 @@ RZ_API bool try_get_jmptbl_info(RzAnalysis *analysis, RzAnalysisFunction *fcn, u break; } if (isValid) { - *start_casenum_shift = 0; + params->case_shift = 0; for (i--; i >= 0; i--) { const ut64 prev_pos = rz_analysis_block_get_op_offset(prev_bb, i); const ut64 op_addr = rz_analysis_block_get_op_addr(prev_bb, i); @@ -507,7 +557,7 @@ RZ_API bool try_get_jmptbl_info(RzAnalysis *analysis, RzAnalysisFunction *fcn, u rz_analysis_op(analysis, &tmp_aop, op_addr, bb_buf + prev_pos, buflen, RZ_ANALYSIS_OP_MASK_VAL); - if (detect_casenum_shift(&tmp_aop, &cmp_reg, start_casenum_shift)) { + if (detect_casenum_shift(&tmp_aop, &cmp_reg, ¶ms->case_shift)) { rz_analysis_op_fini(&tmp_aop); break; } @@ -516,9 +566,5 @@ RZ_API bool try_get_jmptbl_info(RzAnalysis *analysis, RzAnalysisFunction *fcn, u } } free(bb_buf); - // eprintf ("switch at 0x%" PFMT64x "\n\tdefault case 0x%" PFMT64x "\n\t#cases: %d\n", - // addr, - // *default_case, - // *table_size); return isValid; } diff --git a/librz/core/cconfig.c b/librz/core/cconfig.c index a80bcb8bf4..4e0f224976 100644 --- a/librz/core/cconfig.c +++ b/librz/core/cconfig.c @@ -2593,6 +2593,13 @@ static bool cb_analysis_jmptbl(void *user, void *data) { return true; } +static bool cb_analysis_jmptblmax(void *user, void *data) { + RzCore *core = (RzCore *)user; + RzConfigNode *node = (RzConfigNode *)data; + core->analysis->opt.jmptbl_maxcount = node->i_value; + return true; +} + static bool cb_analysis_cjmpref(void *user, void *data) { RzCore *core = (RzCore *)user; RzConfigNode *node = (RzConfigNode *)data; @@ -2874,6 +2881,7 @@ RZ_API int rz_core_config_init(RzCore *core) { SETICB("asm.lines.maxref", 0, &cb_analysis_maxrefs, "Maximum number of reflines to be analyzed and displayed in asm.lines with pd"); SETCB("analysis.jmp.tbl", "true", &cb_analysis_jmptbl, "Analyze jump tables in switch statements"); + SETICB("analysis.jmp.tblmax", 512, &cb_analysis_jmptblmax, "Maximum amount of entries to analyze in jump tables"); SETCB("analysis.jmp.cref", "false", &cb_analysis_cjmpref, "Create references for conditional jumps"); SETCB("analysis.jmp.ref", "true", &cb_analysis_jmpref, "Create references for unconditional jumps"); diff --git a/librz/include/rz_analysis.h b/librz/include/rz_analysis.h index fc2c5da29a..97f6abb0c5 100644 --- a/librz/include/rz_analysis.h +++ b/librz/include/rz_analysis.h @@ -542,6 +542,7 @@ typedef struct rz_analysis_options_t { int nopskip; // skip nops at the beginning of functions int hpskip; // skip `mov reg,reg` and `lea reg,[reg]` int jmptbl; // analyze jump tables + int jmptbl_maxcount; // maximum amount of entries to analyse in a jump table int nonull; bool pushret; // analyze push+ret as jmp bool armthumb; // @@ -887,6 +888,13 @@ typedef struct rz_analysis_bb_t { #undef RzAnalysisBlock } RzAnalysisBlock; +typedef struct rz_analysis_task_item { + RzAnalysisFunction *fcn; // current function + RzAnalysisBlock *block; // block being analyzed + st64 stack; // stack pointer value for variable analysis + ut64 start_address; // if block = NULL, creates block at address, else continues analysis from here +} RzAnalysisTaskItem; + typedef enum { RZ_ANALYSIS_REF_TYPE_NULL = 0, RZ_ANALYSIS_REF_TYPE_CODE = 'c', // code ref @@ -1426,7 +1434,6 @@ RZ_API int rz_analysis_set_big_endian(RzAnalysis *analysis, int boolean); RZ_API ut8 *rz_analysis_mask(RzAnalysis *analysis, int size, const ut8 *data, ut64 at); RZ_API void rz_analysis_trace_bb(RzAnalysis *analysis, ut64 addr); RZ_API const char *rz_analysis_fcntype_tostring(int type); -RZ_API int rz_analysis_fcn_bb(RzAnalysis *analysis, RzAnalysisFunction *fcn, ut64 addr, int depth); RZ_API void rz_analysis_bind(RzAnalysis *b, RzAnalysisBind *bnd); RZ_API bool rz_analysis_set_triplet(RzAnalysis *analysis, const char *os, const char *arch, int bits); RZ_API void rz_analysis_add_import(RzAnalysis *analysis, const char *imp); @@ -1520,6 +1527,9 @@ RZ_API void rz_analysis_function_check_bp_use(RzAnalysisFunction *fcn); RZ_API void rz_analysis_update_analysis_range(RzAnalysis *analysis, ut64 addr, int size); RZ_API void rz_analysis_function_update_analysis(RzAnalysisFunction *fcn); +RZ_API bool rz_analysis_task_item_new(RZ_NONNULL RzAnalysis *analysis, RZ_NONNULL RzVector *tasks, RZ_NONNULL RzAnalysisFunction *fcn, RZ_NULLABLE RzAnalysisBlock *block, ut64 address); +RZ_API int rz_analysis_run_tasks(RZ_NONNULL RzVector *tasks); + #define RZ_ANALYSIS_FCN_VARKIND_LOCAL 'v' RZ_API int rz_analysis_fcn_var_del_byindex(RzAnalysis *a, ut64 fna, const char kind, int scope, ut32 idx); @@ -1652,12 +1662,23 @@ RZ_API const char *rz_analysis_cond_tostring(int cc); /* jmptbl */ RZ_API bool rz_analysis_jmptbl(RzAnalysis *analysis, RzAnalysisFunction *fcn, RzAnalysisBlock *block, ut64 jmpaddr, ut64 table, ut64 tablesize, ut64 default_addr); -// TODO: should be renamed -RZ_API bool try_get_delta_jmptbl_info(RzAnalysis *analysis, RzAnalysisFunction *fcn, ut64 jmp_addr, ut64 lea_addr, ut64 *table_size, ut64 *default_case, st64 *start_casenum_shift); -RZ_API bool try_walkthrough_jmptbl(RzAnalysis *analysis, RzAnalysisFunction *fcn, RzAnalysisBlock *block, int depth, ut64 ip, st64 start_casenum_shift, ut64 jmptbl_loc, ut64 jmptbl_off, ut64 sz, ut64 jmptbl_size, ut64 default_case, bool ret0); -RZ_API bool try_walkthrough_casetbl(RzAnalysis *analysis, RzAnalysisFunction *fcn, RzAnalysisBlock *block, int depth, ut64 ip, st64 start_casenum_shift, ut64 jmptbl_loc, ut64 casetbl_loc, ut64 jmptbl_off, ut64 sz, ut64 jmptbl_size, ut64 default_case, bool ret0); -RZ_API bool try_get_jmptbl_info(RzAnalysis *analysis, RzAnalysisFunction *fcn, ut64 addr, RzAnalysisBlock *my_bb, ut64 *table_size, ut64 *default_case, st64 *start_casenum_shift); -RZ_API int walkthrough_arm_jmptbl_style(RzAnalysis *analysis, RzAnalysisFunction *fcn, RzAnalysisBlock *block, int depth, ut64 ip, ut64 jmptbl_loc, ut64 sz, ut64 jmptbl_size, ut64 default_case, int ret0); +typedef struct rz_jmptable_params_t { + ut64 jmp_address; /// Address of the jump instruction + st64 case_shift; /// Shift that is added to get the real number of the case. + ut64 jmptbl_loc; /// Address of the jump table + ut64 casetbl_loc; /// Address of the indirect case table + ut64 jmptbl_off; /// Base of the jump table + ut64 entry_size; /// Size in bytes of each case entry inside the jump table + ut64 table_count; /// Count of cases inside the jump table + ut64 default_case; /// Code address of the default case of the switch + RzVector *tasks; /// RzVector of RzAnalysisTaskItem to add new tasks to +} RzAnalysisJmpTableParams; + +RZ_API bool rz_analysis_get_delta_jmptbl_info(RZ_NONNULL RzAnalysis *analysis, RZ_NONNULL RzAnalysisFunction *fcn, ut64 jmp_address, ut64 lea_address, RZ_NONNULL RzAnalysisJmpTableParams *params); +RZ_API bool rz_analysis_get_jmptbl_info(RZ_NONNULL RzAnalysis *analysis, RZ_NONNULL RzAnalysisFunction *fcn, RZ_NONNULL RzAnalysisBlock *block, ut64 jmp_address, RZ_NONNULL RzAnalysisJmpTableParams *params); +RZ_API bool rz_analysis_walkthrough_jmptbl(RZ_NONNULL RzAnalysis *analysis, RZ_NONNULL RzAnalysisFunction *fcn, RZ_NONNULL RzAnalysisBlock *block, RZ_NONNULL RzAnalysisJmpTableParams *params); +RZ_API bool rz_analysis_walkthrough_casetbl(RZ_NONNULL RzAnalysis *analysis, RZ_NONNULL RzAnalysisFunction *fcn, RZ_NONNULL RzAnalysisBlock *block, RZ_NONNULL RzAnalysisJmpTableParams *params); +RZ_API bool rz_analysis_walkthrough_arm_jmptbl_style(RZ_NONNULL RzAnalysis *analysis, RZ_NONNULL RzAnalysisFunction *fcn, RZ_NONNULL RzAnalysisBlock *block, RZ_NONNULL RzAnalysisJmpTableParams *params); /* reflines.c */ RZ_API RzList * /**/ rz_analysis_reflines_get(RzAnalysis *analysis, diff --git a/test/db/analysis/avr b/test/db/analysis/avr index 5abb27555d..5de020973c 100644 --- a/test/db/analysis/avr +++ b/test/db/analysis/avr @@ -847,7 +847,7 @@ EXPECT=< 7132 main +0x00018c08 357 8388 -> 7340 main EOF RUN @@ -176,7 +176,7 @@ af afb EOF EXPECT=< 198 sym.___swbuf_r +0x001072c2 11 142 sym.___swbuf_r 0x001073ba 1 18 sym.___swbuf 0x001073d0 5 70 sym.__wcrtomb_r 0x00107416 1 20 sym._wcrtomb @@ -280,7 +280,7 @@ EXPECT=< 198 sym.___swbuf_r +0x00114ba8 11 142 sym.___swbuf_r 0x00114ca0 1 18 sym.___swbuf 0x00114cb6 5 58 sym.__wctomb_r 0x00114cf0 1 18 sym.__write @@ -340,7 +340,7 @@ EXPECT=< 242 sym._cmd_bsize +0x0010333c 4 116 sym._cmd_bsize 0x0010347a 2 46 sym._cmd_seek 0x00103674 10 244 sym._cmd_dump 0x00103768 5 190 sym._cmd_load diff --git a/test/db/analysis/x86_64 b/test/db/analysis/x86_64 index 9d7dea73fc..54ea87207f 100644 --- a/test/db/analysis/x86_64 +++ b/test/db/analysis/x86_64 @@ -5,7 +5,7 @@ aap aflc EOF EXPECT=< 45 sym._obstack_allocated_p 0x00015e70 5 41 -> 36 sym._obstack_memory_used 0x00015e00 9 73225 -> 102 sym._obstack_free +0x00015bd0 3 21 fcn.00015bd0 0x00015cc0 8 256 -> 250 sym._obstack_newchunk 0x00004070 300 6757 -> 6739 main 0x00005bd0 5 153 -> 60 entry.init0 @@ -23,6 +24,11 @@ EXPECT=< 1129 fcn.00015100 0x00013c30 1 17 fcn.00013c30 0x0000c940 3 112 -> 109 fcn.0000c940 +0x0000c790 4 124 -> 117 fcn.0000c790 +0x00014370 1 19 fcn.00014370 +0x00013f90 5 65498 -> 148 fcn.00013f90 +0x00013a40 14 64434 -> 438 fcn.00013a40 +0x0000c810 50 95704 -> 612 fcn.0000c810 0x00014c00 7 82 fcn.00014c00 0x0000fcc0 28 485 -> 456 fcn.0000fcc0 0x00005f20 8 128 -> 114 fcn.00005f20 @@ -42,21 +48,18 @@ EXPECT=< 211 fcn.0000e8c0 0x00006200 5 105 fcn.00006200 0x00007250 1 40 fcn.00007250 +0x00007180 3 85 -> 76 fcn.00007180 0x00006fa0 20 473 -> 448 fcn.00006fa0 0x00014e40 1 20 fcn.00014e40 0x00005c40 45 634 -> 527 fcn.00005c40 0x000063e0 18 401 -> 395 fcn.000063e0 -0x00009380 104 109608 -> 1589 fcn.00009380 +0x00009380 64 1388 -> 1269 fcn.00009380 0x00015020 10 210 -> 199 fcn.00015020 0x00006840 9 197 fcn.00006840 0x00005fa0 34 596 -> 585 fcn.00005fa0 0x00005ec0 6 89 -> 80 fcn.00005ec0 0x000065f0 22 379 -> 375 fcn.000065f0 0x0000bde0 18 1924 -> 1917 fcn.0000bde0 -0x0000c790 4 124 -> 117 fcn.0000c790 -0x00014370 1 19 fcn.00014370 -0x00013f90 5 65498 -> 148 fcn.00013f90 -0x00013a40 14 64434 -> 438 fcn.00013a40 0x00015580 12 193 -> 186 fcn.00015580 0x00014e60 1 56 fcn.00014e60 0x00015650 98 1137 -> 1135 fcn.00015650 @@ -72,7 +75,6 @@ EXPECT=< 190 fcn.00006c60 0x000071e0 37 118072 -> 402 fcn.000071e0 -0x00007180 3 85 -> 76 fcn.00007180 0x00015ad0 7 136 -> 132 fcn.00015ad0 0x00010740 40 480 -> 447 fcn.00010740 0x000073d0 67 1135 -> 1075 fcn.000073d0 @@ -87,6 +89,11 @@ EXPECT=< 2515 fcn.0000f270 0x0000d6b0 17 392 -> 366 fcn.0000d6b0 0x00016270 12 166 -> 152 fcn.00016270 +0x000161a0 12 176 -> 164 fcn.000161a0 +0x00015ea0 6 72 -> 71 fcn.00015ea0 +0x00015ef0 5 41 -> 36 fcn.00015ef0 +0x00016010 25 397 -> 361 fcn.00016010 +0x00015f20 3 66 fcn.00015f20 0x00012530 3 71 fcn.00012530 0x00010c30 310 6399 -> 6311 fcn.00010c30 0x00006910 8 188 -> 175 fcn.00006910 @@ -112,13 +119,14 @@ EXPECT=< 194 fcn.00009b00 0x0000ee80 7 90 -> 82 fcn.0000ee80 0x00006580 1 42 fcn.00006580 -0x0000c810 10 297 -> 292 fcn.0000c810 0x00014fe0 4 64 -> 56 fcn.00014fe0 0x0000d4d0 5 88 fcn.0000d4d0 0x0000d440 7 144 -> 133 fcn.0000d440 0x0000f190 1 23 loc.0000f190 0x000164a0 8 102 -> 96 fcn.000164a0 -0x000165b0 24 55032 -> 248 fcn.000165b0 +0x000165b0 9 139 -> 128 fcn.000165b0 +0x00016640 5 66 -> 63 fcn.00016640 +0x00016690 7 89 fcn.00016690 0x00014180 4 193 -> 170 fcn.00014180 0x0000e3f0 9 83 -> 75 fcn.0000e3f0 0x0000d7e0 17 195 -> 166 fcn.0000d7e0 @@ -142,14 +150,6 @@ EXPECT=< 1004 fcn.000145b0 0x00014d60 8 120 -> 114 fcn.00014d60 0x00015bb0 3 21 fcn.00015bb0 -0x00015bd0 3 21 fcn.00015bd0 -0x00015ea0 6 72 -> 71 fcn.00015ea0 -0x00015ef0 5 41 -> 36 fcn.00015ef0 -0x000161a0 12 176 -> 164 fcn.000161a0 -0x00016010 25 397 -> 361 fcn.00016010 -0x00015f20 3 66 fcn.00015f20 -0x00016640 5 66 -> 63 fcn.00016640 -0x00016690 7 89 fcn.00016690 0x00004000 3 27 fcn.00004000 EOF RUN diff --git a/test/db/cmd/cmd_foreach b/test/db/cmd/cmd_foreach index e9056acbdb..fcbb06b3f1 100644 --- a/test/db/cmd/cmd_foreach +++ b/test/db/cmd/cmd_foreach @@ -8,6 +8,7 @@ sym._obstack_begin_1 sym._obstack_allocated_p sym._obstack_memory_used sym._obstack_free +fcn.00015bd0 sym._obstack_newchunk main entry.init0 diff --git a/test/db/cmd/cmd_zignature b/test/db/cmd/cmd_zignature index a5873d0565..5b9874c93d 100644 --- a/test/db/cmd/cmd_zignature +++ b/test/db/cmd/cmd_zignature @@ -902,7 +902,7 @@ EXPECT=<