diff --git a/.github/labeler.yml b/.github/labeler.yml index c314078a40c..f0fec3f040e 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -165,6 +165,11 @@ RzSocket: - any-glob-to-any-file: - librz/socket/**/* +RzInquiry: +- changed-files: + - any-glob-to-any-file: + - librz/inquiry/**/* + RzSyscall: - changed-files: - any-glob-to-any-file: diff --git a/doc/abstract_interpreter/state_diagrams.svg b/doc/abstract_interpreter/state_diagrams.svg new file mode 100644 index 00000000000..0c00768e4b5 --- /dev/null +++ b/doc/abstract_interpreter/state_diagrams.svg @@ -0,0 +1,261 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ls + exp + Error + + + + + + + + + + + + + + B I + + + + + + + + T + + + + + + + + + + + wait + - + + + + + + + + + T + + wait + - + + + + + + + + + B II + B III + + + + + + + + + + + + + + + + + wait + - + + + + + + + + + clear + open + B IV + + + + T + + + + + + + + + + + wait + - + + + + + + + + + clear + B IV + + + + T + + + + + + emulation + error + + + + + + + + + + + + + + init + B I - Get entry point + + + + + + + emulation + B II + memory + B III + basic block + + + + + + + + + clean + B IV + sync + + + + + + + T + + + term + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + T + + + + + + + + + diff --git a/librz/arch/analysis.c b/librz/arch/analysis.c index 74c117739e5..a695c8d8d8b 100644 --- a/librz/arch/analysis.c +++ b/librz/arch/analysis.c @@ -986,6 +986,77 @@ RZ_API bool rz_analysis_op_is_call(RZ_NONNULL const RzAnalysisOp *op) { return false; } +/** + * \brief Checks if \p op changes the control flow. + * It checks op->type, op->eob for it. So any jump, call, trap, or illegal + * instruction. + * It also returns true for conditional instructions. + * + * \return True if \p op changes the control flow. False otherwise. + */ +RZ_API bool rz_analysis_op_changes_control_flow(RZ_NONNULL const RzAnalysisOp *op) { + rz_return_val_if_fail(op, false); + switch (op->type & RZ_ANALYSIS_OP_TYPE_MASK) { + case RZ_ANALYSIS_OP_TYPE_JMP: + case RZ_ANALYSIS_OP_TYPE_UJMP: + case RZ_ANALYSIS_OP_TYPE_RJMP: + case RZ_ANALYSIS_OP_TYPE_IJMP: + case RZ_ANALYSIS_OP_TYPE_IRJMP: + case RZ_ANALYSIS_OP_TYPE_CJMP: + case RZ_ANALYSIS_OP_TYPE_RCJMP: + case RZ_ANALYSIS_OP_TYPE_MJMP: + case RZ_ANALYSIS_OP_TYPE_MCJMP: + case RZ_ANALYSIS_OP_TYPE_UCJMP: + case RZ_ANALYSIS_OP_TYPE_CALL: + case RZ_ANALYSIS_OP_TYPE_UCALL: + case RZ_ANALYSIS_OP_TYPE_RCALL: + case RZ_ANALYSIS_OP_TYPE_ICALL: + case RZ_ANALYSIS_OP_TYPE_IRCALL: + case RZ_ANALYSIS_OP_TYPE_CCALL: + case RZ_ANALYSIS_OP_TYPE_UCCALL: + case RZ_ANALYSIS_OP_TYPE_RET: + case RZ_ANALYSIS_OP_TYPE_CRET: + case RZ_ANALYSIS_OP_TYPE_ILL: + case RZ_ANALYSIS_OP_TYPE_UNK: + case RZ_ANALYSIS_OP_TYPE_TRAP: + case RZ_ANALYSIS_OP_TYPE_SWI: + case RZ_ANALYSIS_OP_TYPE_CSWI: + case RZ_ANALYSIS_OP_TYPE_LEAVE: + case RZ_ANALYSIS_OP_TYPE_SWITCH: + return true; + default: + return false; + } +} + +/** + * \brief Returns true if the \p op is a direct call. + */ +RZ_API bool rz_analysis_op_is_direct_call(RZ_NONNULL const RzAnalysisOp *op) { + rz_return_val_if_fail(op, false); + switch (op->type & RZ_ANALYSIS_OP_TYPE_MASK) { + case RZ_ANALYSIS_OP_TYPE_CALL: + case RZ_ANALYSIS_OP_TYPE_CCALL: + return true; + default: + return false; + } +} + +/** + * \brief Returns true if the \p op is a direct call. + */ +RZ_API bool rz_analysis_op_is_direct_jump(RZ_NONNULL const RzAnalysisOp *op) { + rz_return_val_if_fail(op, false); + switch (op->type & RZ_ANALYSIS_OP_TYPE_MASK) { + case RZ_ANALYSIS_OP_TYPE_JMP: + case RZ_ANALYSIS_OP_TYPE_CJMP: + return true; + default: + return false; + } +} + RZ_API void rz_analysis_purge(RzAnalysis *analysis) { rz_analysis_hint_clear(analysis); rz_interval_tree_fini(&analysis->meta); diff --git a/librz/arch/fcn.c b/librz/arch/fcn.c index fa4eeb3108b..714c5f49d87 100644 --- a/librz/arch/fcn.c +++ b/librz/arch/fcn.c @@ -1854,6 +1854,30 @@ RZ_API RzAnalysisFunction *rz_analysis_get_function_byname(RzAnalysis *a, const return NULL; } +/** + * \brief Adds a basic block to the RzAnalysis. + * It disassembles it to determine the instructions and their offset. + */ +RZ_API bool rz_analysis_add_bb(RzAnalysis *a, ut64 addr, ut64 size) { + rz_return_val_if_fail(a && size > 0, false); + if (size > a->opt.bb_max_size) { + RZ_LOG_ERROR("Cannot allocate such big bb of %" PFMT64d " bytes at 0x%08" PFMT64x "\n", (st64)size, addr); + rz_warn_if_reached(); + return false; + } + RzAnalysisBlock *block = rz_analysis_get_block_at(a, addr); + if (block) { + rz_analysis_delete_block(block); + } + + block = rz_analysis_create_block(a, addr, size); + if (!block) { + return false; + } + rz_analysis_block_analyze_ops(block); + return true; +} + /* rename RzAnalysisFunctionBB.add() */ RZ_API bool rz_analysis_fcn_add_bb(RzAnalysis *a, RzAnalysisFunction *fcn, ut64 addr, ut64 size, ut64 jump, ut64 fail) { if (size == 0) { diff --git a/librz/arch/isa/hexagon/hexagon_il.c b/librz/arch/isa/hexagon/hexagon_il.c index 47defc91d56..30b159abf2c 100644 --- a/librz/arch/isa/hexagon/hexagon_il.c +++ b/librz/arch/isa/hexagon/hexagon_il.c @@ -228,7 +228,7 @@ static RZ_OWN RzILOpEffect *hex_pkt_to_il_seq(HexPkt *pkt) { rz_pvector_clear(pkt->il_ops); // We need at least the instruction op and the packet commit. // So if there aren't at least two ops something went wrong. - RZ_LOG_WARN("Invalid il ops sequence! There should be at least two il ops per packet.\n"); + RZ_LOG_WARN("Invalid il ops sequence! There should be at least two il ops per packet. pkt_addr = 0x%" PFMT32x "\n", pkt->pkt_addr); return NULL; } RzILOpEffect *complete_seq = EMPTY(); diff --git a/librz/arch/p/analysis/analysis_hexagon.c b/librz/arch/p/analysis/analysis_hexagon.c index 4409ff951a2..fe8d9c17177 100644 --- a/librz/arch/p/analysis/analysis_hexagon.c +++ b/librz/arch/p/analysis/analysis_hexagon.c @@ -30,7 +30,11 @@ RZ_API int hexagon_v6_op(RzAnalysis *analysis, RzAnalysisOp *op, ut64 addr, cons hexagon_reverse_opcode(&rev, addr, NULL, analysis); HexPkt *p = hex_get_pkt(rev.state, addr); if (p) { + op->hexagon_pkt_addr = p->pkt_addr; rev.pkt_fully_decoded = p->is_valid; + if (mask & RZ_ANALYSIS_OP_MASK_INSN_PKT) { + op->size = rz_list_length(p->bin) * HEX_INSN_SIZE; + } } if (mask & RZ_ANALYSIS_OP_MASK_IL) { op->il_op = hex_get_il_op(addr, rev.pkt_fully_decoded, rev.state); diff --git a/librz/arch/xrefs.c b/librz/arch/xrefs.c index 9b339583d9a..7be05e6d11c 100644 --- a/librz/arch/xrefs.c +++ b/librz/arch/xrefs.c @@ -5,6 +5,8 @@ // SPDX-License-Identifier: LGPL-3.0-only #include "analysis_private.h" +#include "rz_analysis.h" +#include "rz_util/rz_assert.h" #include /* @@ -214,6 +216,10 @@ RZ_API const char *rz_analysis_xrefs_type_tostring(RzAnalysisXRefType type) { return "DATA"; case RZ_ANALYSIS_XREF_TYPE_STRING: return "STRING"; + case RZ_ANALYSIS_XREF_TYPE_MEM_WRITE: + return "MEM_WRITE"; + case RZ_ANALYSIS_XREF_TYPE_RETURN: + return "RETURN"; case RZ_ANALYSIS_XREF_TYPE_NULL: default: return "UNKNOWN"; @@ -306,6 +312,89 @@ RZ_API const char *rz_analysis_ref_type_tostring(RzAnalysisXRefType t) { return "data"; case RZ_ANALYSIS_XREF_TYPE_STRING: return "string"; + case RZ_ANALYSIS_XREF_TYPE_MEM_WRITE: + return "write"; + case RZ_ANALYSIS_XREF_TYPE_CALL_RET: + return "call_ret_pt"; + case RZ_ANALYSIS_XREF_TYPE_RETURN: + return "return"; } return "unknown"; } + +static bool read_up_to(RzAnalysis *analysis, ut64 addr, ut8 *buf, size_t buf_size) { + rz_mem_set_num(buf, buf_size, 0); + do { + if (analysis->iob.read_at(analysis->iob.io, addr, buf, buf_size) > 0) { + return true; + } + buf_size--; + } while (buf_size > 0); + return false; +} + +/** + * \brief Returns all control flow targets of call/jmp instructions within the \p sections. + * NOTE: This function disassembles all instructions within the boundaries and checks for calls/jmps. + * It DOES NOT use the existing xrefs. + * + * \param analysis The analysis plugin. + * \param sections The RzBinSections to disassemble to find call/jmp instructions. + * \param include_call_return_pts If true, it will add addresses after a call instruction as well. + * \param cf_targets The found call targets of all disassemble calls. + * + * \return True in case of success, false otherwise. + */ +RZ_API bool rz_analysis_get_all_cf_targets(RzAnalysis *analysis, + const RzPVector /**/ *sections, + bool include_call_return_pts, + RZ_NONNULL RZ_OUT RzSetU *cf_targets) { + rz_return_val_if_fail(analysis && analysis->cur && sections && cf_targets, false); + size_t buf_size = (analysis->cur->bits / 8) * 16; + ut8 *buf = RZ_NEWS0(ut8, buf_size); + if (!buf_size || !buf) { + return false; + } + + RzAnalysisOp op = { 0 }; + void **it; + rz_pvector_foreach (sections, it) { + RzBinSection *sec = *it; + if (!(sec->perm & RZ_PERM_X)) { + continue; + } + ut64 addr = sec->vaddr; + while (addr < sec->vaddr + sec->size) { + if (!read_up_to(analysis, addr, buf, buf_size)) { + RZ_LOG_WARN("Failed to read memory at 0x%" PFMT64x " size: %" PFMTSZu ".\n", addr, buf_size); + rz_analysis_op_fini(&op); + break; + } + if (rz_analysis_op(analysis, &op, addr, buf, buf_size, RZ_ANALYSIS_OP_MASK_BASIC) <= 0) { + rz_analysis_op_fini(&op); + addr += op.size; + continue; + } + // Only add jump targets going to executable regions. + bool op_is_call = rz_analysis_op_is_direct_call(&op); + bool op_is_jump = rz_analysis_op_is_direct_jump(&op); + if ((op_is_call || op_is_jump) && op.jump != UT64_MAX) { + rz_set_u_add(cf_targets, op.jump); + if (op.fail != UT64_MAX) { + if (op_is_jump) { + rz_set_u_add(cf_targets, op.fail); + } + } + } + if (include_call_return_pts && op_is_call) { + // If it is a call, also add the following instruction as reference. + // Because it is likely a return point. + rz_set_u_add(cf_targets, op.addr + op.size); + } + addr += op.size; + rz_analysis_op_fini(&op); + } + } + free(buf); + return true; +} diff --git a/librz/bin/format/mach0/mach0.c b/librz/bin/format/mach0/mach0.c index 0375375e522..c5c6dd5e9ff 100644 --- a/librz/bin/format/mach0/mach0.c +++ b/librz/bin/format/mach0/mach0.c @@ -2028,6 +2028,13 @@ RzPVector /**/ *MACH0_(get_maps)(RzBinFile *bf) { return ret; } +static bool is_linking_related_section(ut64 sec_type) { + return sec_type & S_SYMBOL_STUBS || + sec_type & S_LAZY_SYMBOL_POINTERS || + sec_type & S_NON_LAZY_SYMBOL_POINTERS || + sec_type & S_LAZY_DYLIB_SYMBOL_POINTERS; +} + RzPVector /**/ *MACH0_(get_segments)(RzBinFile *bf) { struct MACH0_(obj_t) *bin = bf->o->bin_obj; if (bin->sections_cache) { @@ -2077,6 +2084,9 @@ RzPVector /**/ *MACH0_(get_segments)(RzBinFile *bf) { s->flags = bin->sects[i].flags & 0xFFFFFF00; // XXX flags s->paddr = (ut64)bin->sects[i].offset; + if (is_linking_related_section(s->type)) { + s->layout.role = RZ_BIN_SECTION_ROLE_LINKING; + } int segment_index = 0; // s->perm =prot2perm (bin->segs[j].initprot); for (j = 0; j < bin->nsegs; j++) { diff --git a/librz/bin/p/bin_elf.inc b/librz/bin/p/bin_elf.inc index 5f32a8132f4..181070c5756 100644 --- a/librz/bin/p/bin_elf.inc +++ b/librz/bin/p/bin_elf.inc @@ -857,6 +857,12 @@ static RzPVector /**/ *sections_obj(ELFOBJ *obj, size_t psize) { ptr->layout.element_size = sizeof(Elf_(Addr)); ptr->layout.count = section->size / sizeof(Elf_(Addr)); } + if (section->type == SHT_RELA || + section->type == SHT_DYNAMIC || + section->type == SHT_DYNSYM || + (section->name && strstr(section->name, ".plt"))) { + ptr->layout.role = RZ_BIN_SECTION_ROLE_LINKING; + } ptr->size = section->type != SHT_NOBITS ? section->size : 0; ptr->vsize = section->size; ptr->paddr = section->offset; diff --git a/librz/config/serialize_config.c b/librz/config/serialize_config.c index e33eee2dbd8..e7ba69749f9 100644 --- a/librz/config/serialize_config.c +++ b/librz/config/serialize_config.c @@ -5,15 +5,40 @@ #include #include "config_internal.h" +static RzSetS *build_exclude_set(const char **strings) { + if (!strings) { + return NULL; + } + HtSP *r = rz_set_s_new(HT_STR_DUP); + if (!r) { + return NULL; + } + for (; *strings; strings++) { + rz_set_s_add(r, *strings); + } + return r; +} + +typedef struct serialze_ctx_t { + Sdb *db; + HtSP *exclude; +} SerializeCtx; + static bool config_serialize_to_sdb(const RzConfigEntry *entry, void *user) { - Sdb *db = user; + SerializeCtx *ctx = user; if (entry->is_variable) { + if (ctx->exclude && rz_set_s_contains(ctx->exclude, entry->var.name)) { + return true; + } char *value = rz_config_var_as_string(&entry->var); - sdb_set(db, entry->var.name, value); + sdb_set(ctx->db, entry->var.name, value); free(value); } else { const RzConfigNode *node = &entry->node; - sdb_set(db, node->name, node->value); + if (ctx->exclude && rz_set_s_contains(ctx->exclude, node->name)) { + return true; + } + sdb_set(ctx->db, node->name, node->value); } return true; } @@ -29,11 +54,16 @@ static bool config_serialize_to_sdb(const RzConfigEntry *entry, void *user) { * ... * */ -RZ_API void rz_serialize_config_save(RZ_NONNULL Sdb *db, RZ_NONNULL RzConfig *config) { - rz_config_iterate_over(config, config_serialize_to_sdb, db); +RZ_API void rz_serialize_config_save(RZ_NONNULL Sdb *db, RZ_NONNULL RzConfig *config, RZ_NULLABLE const char **exclude) { + rz_return_if_fail(db && config); + SerializeCtx ctx = { .db = db, .exclude = build_exclude_set(exclude) }; + if (exclude && !ctx.exclude) { + return; + } + rz_config_iterate_over(config, config_serialize_to_sdb, &ctx); } -typedef struct deserialize_ctx_s { +typedef struct deserialize_ctx_t { RzConfig *config; HtSP *exclude; } DeserializeCtx; @@ -65,15 +95,9 @@ static bool config_deserialize_from_sdb(void *user, const SdbKv *kv) { RZ_API bool rz_serialize_config_load(RZ_NONNULL Sdb *db, RZ_NONNULL RzConfig *config, RZ_NULLABLE const char **exclude) { rz_return_val_if_fail(db && config, false); - DeserializeCtx ctx = { config, NULL }; - if (exclude) { - ctx.exclude = ht_sp_new(HT_STR_DUP, NULL, NULL); - if (!ctx.exclude) { - return false; - } - for (; *exclude; exclude++) { - ht_sp_insert(ctx.exclude, *exclude, NULL); - } + DeserializeCtx ctx = { .config = config, .exclude = build_exclude_set(exclude) }; + if (exclude && !ctx.exclude) { + return false; } sdb_foreach(db, config_deserialize_from_sdb, &ctx); ht_sp_free(ctx.exclude); diff --git a/librz/core/cconfig.c b/librz/core/cconfig.c index 881203f122b..21a103f5d4d 100644 --- a/librz/core/cconfig.c +++ b/librz/core/cconfig.c @@ -3859,6 +3859,9 @@ RZ_API int rz_core_config_init(RzCore *core) { SETB("flirt.sigdb.load.extra", true, "Load signatures from the extra path"); SETB("flirt.sigdb.load.home", true, "Load signatures from the home path"); + /* Inquiry. All of these are experimental and not stored in projects. */ + rz_config_add_bool(cfg, "inquiry.comment", "Experimental: Write comments at each address describing the state during abstract interpretation for debugging", false); + return true; } diff --git a/librz/core/cfile.c b/librz/core/cfile.c index 66793f1ce01..b7d1c9e5bb7 100644 --- a/librz/core/cfile.c +++ b/librz/core/cfile.c @@ -609,7 +609,7 @@ RZ_API void rz_core_sysenv_begin(RzCore *core) { rz_sys_setenv("RZ_ENDIAN", rz_asm_is_big_endian_set(core->rasm) ? "big" : "little"); rz_sys_setenv("RZ_BSIZE", rz_strf(tmpbuf, "%d", core->blocksize)); - // dump current config file so other r2 tools can use the same options + // dump current config file so other rizin tools can use the same options char *config_sdb_path = NULL; int config_sdb_fd = rz_file_mkstemp(NULL, &config_sdb_path); if (config_sdb_fd >= 0) { @@ -617,7 +617,7 @@ RZ_API void rz_core_sysenv_begin(RzCore *core) { } Sdb *config_sdb = sdb_new(NULL, config_sdb_path, 0); - rz_serialize_config_save(config_sdb, core->config); + rz_serialize_config_save(config_sdb, core->config, NULL); sdb_sync(config_sdb); sdb_free(config_sdb); rz_sys_setenv("RZ_CONFIG", config_sdb_path); diff --git a/librz/core/cmd/cmd_analysis.c b/librz/core/cmd/cmd_analysis.c index 1eca5574951..f2cfc548f14 100644 --- a/librz/core/cmd/cmd_analysis.c +++ b/librz/core/cmd/cmd_analysis.c @@ -10,6 +10,7 @@ #include #include "../core_private.h" +#include "rz_analysis.h" #define MAX_SCAN_SIZE 0x7ffffff @@ -2125,9 +2126,12 @@ RZ_IPI RzCmdStatus rz_analysis_function_xrefs_handler(RzCore *core, int argc, co case RZ_ANALYSIS_XREF_TYPE_NULL: rz_cons_printf("0x%08" PFMT64x " ", xref->to); break; + case RZ_ANALYSIS_XREF_TYPE_MEM_WRITE: + case RZ_ANALYSIS_XREF_TYPE_CALL_RET: case RZ_ANALYSIS_XREF_TYPE_CODE: case RZ_ANALYSIS_XREF_TYPE_CALL: case RZ_ANALYSIS_XREF_TYPE_DATA: + case RZ_ANALYSIS_XREF_TYPE_RETURN: rz_cons_printf("0x%08" PFMT64x " ", xref->to); rz_core_seek(core, xref->from, 1); rz_core_print_disasm_instructions(core, 0, 1); diff --git a/librz/core/cmd/cmd_inquiry.c b/librz/core/cmd/cmd_inquiry.c new file mode 100644 index 00000000000..0a7130e3a69 --- /dev/null +++ b/librz/core/cmd/cmd_inquiry.c @@ -0,0 +1,122 @@ +// SPDX-FileCopyrightText: 2025 RizinOrg +// SPDX-License-Identifier: LGPL-3.0-only + +#include "rz_util/rz_set.h" +#include +#include +#include +#include +#include +#include +#include +#include + +static bool core_interp_run(RzCore *core) { + RzInterpResultDimen dimens = RZ_INTERP_RESULT_DIMEN_XREFS; + if (rz_config_get_bool(core->config, "inquiry.comment")) { + dimens |= RZ_INTERP_RESULT_DIMEN_COMMENTS; + } + RzSetU *entry_points = rz_set_u_new(); + if (!entry_points) { + return RZ_CMD_STATUS_ERROR; + } + rz_set_u_add(entry_points, core->offset); + bool success = rz_interp_driver_run(core, entry_points, dimens); + if (!success) { + RZ_LOG_ERROR("Analysis failed.\n"); + } + return success; +} + +RZ_IPI RzCmdStatus rz_inquiry_analyze_function_handler(RzCore *core, int argc, const char **argv) { + rz_return_val_if_fail(core->analysis && core->io && core->bin->cur && core->bin->cur->o, RZ_CMD_STATUS_ERROR); + return core_interp_run(core) ? RZ_CMD_STATUS_OK : RZ_CMD_STATUS_ERROR; +} + +static RzVector /**/ *get_ignored_code_regions( + const RzPVector /**/ *symbols, + RzPVector /**/ *sections, + const RzPVector /**/ *io_maps) { + void **it; + RzVector *v = rz_vector_new(sizeof(RzInterval), NULL, NULL); + rz_pvector_foreach (sections, it) { + RzBinSection *sec = *it; + if (sec->layout.role == RZ_BIN_SECTION_ROLE_LINKING || !(sec->perm & RZ_PERM_X)) { + RzInterval itv = { .addr = sec->vaddr, .size = sec->vsize }; + rz_vector_push(v, &itv); + } + } + rz_pvector_free(sections); + + rz_pvector_foreach (io_maps, it) { + RzIOMap *map = *it; + if (!(map->perm & RZ_PERM_X)) { + RzInterval itv = { .addr = map->itv.addr, .size = map->itv.size }; + rz_vector_push(v, &itv); + } + } + + rz_pvector_foreach (symbols, it) { + RzBinSymbol *sym = *it; + if (sym->is_imported) { + RzInterval itv = { .addr = sym->vaddr, .size = sym->size }; + rz_vector_push(v, &itv); + } + } + return v; +} + +RZ_IPI RzCmdStatus rz_inquiry_interpreter_prototype_handler(RzCore *core, int argc, const char **argv) { + rz_return_val_if_fail(core->analysis && core->io && core->bin->cur && core->bin->cur->o, RZ_CMD_STATUS_ERROR); + RzSetU *entry_points = rz_set_u_new(); + if (!entry_points) { + return RZ_CMD_STATUS_ERROR; + } + bool run_fcn_detection = RZ_STR_EQ(argv[1], "-f"); + + ut64 entry_point; + if (argc == (run_fcn_detection ? 2 : 1)) { + // No specific entry point given. Pick the one provided by the bin plugin. + entry_point = rz_bin_get_first_entrypoint(core->bin->cur->o); + rz_set_u_add(entry_points, entry_point); + } else { + // Add all entry points given as arguments. + for (size_t i = (run_fcn_detection ? 2 : 1); i < argc; i++) { + ut64 entry_point = rz_num_get(core->num, argv[i]); + rz_set_u_add(entry_points, entry_point); + } + } + bool success = rz_interp_driver_run(core, entry_points, RZ_INTERP_RESULT_DIMEN_XREFS); + eprintf("Finished reference recovery: %s\n", success ? "OK" : "FAIL"); + if (!success) { + return RZ_CMD_STATUS_ERROR; + } + + RzSetU *symbol_addresses = rz_set_u_new(); + if (!symbol_addresses || !rz_inquiry_get_fcn_symbol_addr(core, symbol_addresses)) { + rz_warn_if_reached(); + return RZ_CMD_STATUS_ERROR; + } + const RzPVector *symbols = rz_bin_object_get_symbols(core->bin->cur->o); + RzPVector *fcns = rz_pvector_new((RzPVectorFree)rz_inquiry_function_free); + + if (!fcns || !symbols) { + return RZ_CMD_STATUS_ERROR; + } + if (run_fcn_detection) { + RzVector *ignored_code_regions = get_ignored_code_regions( + rz_bin_object_get_symbols(core->bin->cur->o), + rz_bin_object_get_sections(core->bin->cur->o), + rz_io_maps(core->io)); + eprintf("Perform function deduction: "); + success &= rz_inquiry_function_deduction(core->analysis, core->inquiry, symbol_addresses, symbols, ignored_code_regions, fcns); + rz_vector_free(ignored_code_regions); + eprintf("%s\n", success ? "OK" : "FAIL"); + } + rz_set_u_free(symbol_addresses); + + success &= rz_inquiry_convert_and_add_to_analysis(core->analysis, core->inquiry, fcns, symbols); + rz_pvector_free(fcns); + + return success ? RZ_CMD_STATUS_OK : RZ_CMD_STATUS_ERROR; +} diff --git a/librz/core/cmd/cmd_system.c b/librz/core/cmd/cmd_system.c index 4386c6778a8..5ff9ea57a48 100644 --- a/librz/core/cmd/cmd_system.c +++ b/librz/core/cmd/cmd_system.c @@ -28,7 +28,7 @@ static char *config_path(RzCore *core) { free(path); return NULL; } - rz_serialize_config_save(sdb, core->config); + rz_serialize_config_save(sdb, core->config, NULL); sdb_sync(sdb); sdb_free(sdb); diff --git a/librz/core/cmd_descs/cmd_analysis.yaml b/librz/core/cmd_descs/cmd_analysis.yaml index 22a7ea38f58..9f23cf3319a 100644 --- a/librz/core/cmd_descs/cmd_analysis.yaml +++ b/librz/core/cmd_descs/cmd_analysis.yaml @@ -2619,3 +2619,34 @@ commands: comment: "memwrites" - text: "NOTE:" comment: "mem{reads,writes} with PIC only fetch the offset" + - name: aI + summary: New experimental RzInquiry analysis + subcommands: + - name: aIf + summary: analyze function at current seek + cname: inquiry_analyze_function + args: [] + - name: aIp + summary: Abstract Interpreter Prototype + cname: inquiry_interpreter_prototype + args: + - name: f + type: RZ_CMD_ARG_TYPE_OPTION + flags: RZ_CMD_ARG_FLAG_OPTION + default_value: 0 + - name: entry_points + type: RZ_CMD_ARG_TYPE_RZNUM + flags: RZ_CMD_ARG_FLAG_ARRAY + optional: true + details: + - name: Examples + entries: + - text: "aIp" + arg_str: "" + comment: "Run prototype RzIL analysis detecting cross references." + - text: "aIp" + arg_str: " 0x1000" + comment: "Run prototype RzIL analysis starting at address 0x1000." + - text: "aIp" + arg_str: " -f" + comment: "Run prototype RzIL analysis with function detection." diff --git a/librz/core/cmd_descs/cmd_descs.c b/librz/core/cmd_descs/cmd_descs.c index 01391ad1207..a7d33237f30 100644 --- a/librz/core/cmd_descs/cmd_descs.c +++ b/librz/core/cmd_descs/cmd_descs.c @@ -66,6 +66,7 @@ static const RzCmdDescDetail analysis_hint_set_optype_details[2]; static const RzCmdDescDetail analysis_hint_set_immbase_details[3]; static const RzCmdDescDetail analysis_hint_set_offset_details[2]; static const RzCmdDescDetail analyze_esil_insn_access_details[4]; +static const RzCmdDescDetail inquiry_interpreter_prototype_details[2]; static const RzCmdDescDetail basefind_compute_details[2]; static const RzCmdDescDetail cmd_cmp_unified_details[2]; static const RzCmdDescDetail cw_details[2]; @@ -428,6 +429,7 @@ static const RzCmdDescArg analyze_esil_emu_fcn_find_args_args[2]; static const RzCmdDescArg analyze_esil_int_list_load_args[2]; static const RzCmdDescArg analyze_esil_int_remove_args[2]; static const RzCmdDescArg analyze_esil_insn_access_args[4]; +static const RzCmdDescArg inquiry_interpreter_prototype_args[3]; static const RzCmdDescArg block_args[2]; static const RzCmdDescArg block_decrease_args[2]; static const RzCmdDescArg block_increase_args[2]; @@ -8449,6 +8451,50 @@ static const RzCmdDescHelp analyze_esil_insn_access_help = { .args = analyze_esil_insn_access_args, }; +static const RzCmdDescHelp aI_help = { + .summary = "New experimental RzInquiry analysis", +}; +static const RzCmdDescArg inquiry_analyze_function_args[] = { + { 0 }, +}; +static const RzCmdDescHelp inquiry_analyze_function_help = { + .summary = "analyze function at current seek", + .args = inquiry_analyze_function_args, +}; + +static const RzCmdDescDetailEntry inquiry_interpreter_prototype_Examples_detail_entries[] = { + { .text = "aIp", .arg_str = "", .comment = "Run prototype RzIL analysis detecting cross references." }, + { .text = "aIp", .arg_str = " 0x1000", .comment = "Run prototype RzIL analysis starting at address 0x1000." }, + { .text = "aIp", .arg_str = " -f", .comment = "Run prototype RzIL analysis with function detection." }, + { 0 }, +}; +static const RzCmdDescDetail inquiry_interpreter_prototype_details[] = { + { .name = "Examples", .entries = inquiry_interpreter_prototype_Examples_detail_entries }, + { 0 }, +}; +static const RzCmdDescArg inquiry_interpreter_prototype_args[] = { + { + .name = "f", + .type = RZ_CMD_ARG_TYPE_OPTION, + .flags = RZ_CMD_ARG_FLAG_OPTION, + .default_value = "0", + + }, + { + .name = "entry_points", + .type = RZ_CMD_ARG_TYPE_RZNUM, + .flags = RZ_CMD_ARG_FLAG_ARRAY, + .optional = true, + + }, + { 0 }, +}; +static const RzCmdDescHelp inquiry_interpreter_prototype_help = { + .summary = "Abstract Interpreter Prototype", + .details = inquiry_interpreter_prototype_details, + .args = inquiry_interpreter_prototype_args, +}; + static const RzCmdDescHelp b_help = { .summary = "Display or change the block size", }; @@ -23907,6 +23953,14 @@ RZ_IPI void rzshell_cmddescs_init(RzCore *core) { RzCmdDesc *aea_cd = rz_cmd_desc_group_modes_new(core->rcmd, ae_cd, "aea", RZ_OUTPUT_MODE_STANDARD | RZ_OUTPUT_MODE_JSON, rz_analyze_esil_insn_access_handler, &analyze_esil_insn_access_help, &aea_help); rz_warn_if_fail(aea_cd); + RzCmdDesc *aI_cd = rz_cmd_desc_group_new(core->rcmd, ae_cd, "aI", NULL, NULL, &aI_help); + rz_warn_if_fail(aI_cd); + RzCmdDesc *inquiry_analyze_function_cd = rz_cmd_desc_argv_new(core->rcmd, aI_cd, "aIf", rz_inquiry_analyze_function_handler, &inquiry_analyze_function_help); + rz_warn_if_fail(inquiry_analyze_function_cd); + + RzCmdDesc *inquiry_interpreter_prototype_cd = rz_cmd_desc_argv_new(core->rcmd, aI_cd, "aIp", rz_inquiry_interpreter_prototype_handler, &inquiry_interpreter_prototype_help); + rz_warn_if_fail(inquiry_interpreter_prototype_cd); + RzCmdDesc *b_cd = rz_cmd_desc_group_state_new(core->rcmd, root_cd, "b", RZ_OUTPUT_MODE_STANDARD | RZ_OUTPUT_MODE_JSON, rz_block_handler, &block_help, &b_help); rz_warn_if_fail(b_cd); RzCmdDesc *block_decrease_cd = rz_cmd_desc_argv_new(core->rcmd, b_cd, "b-", rz_block_decrease_handler, &block_decrease_help); diff --git a/librz/core/cmd_descs/cmd_descs.h b/librz/core/cmd_descs/cmd_descs.h index 2521aa4b607..0a3f106570e 100644 --- a/librz/core/cmd_descs/cmd_descs.h +++ b/librz/core/cmd_descs/cmd_descs.h @@ -965,6 +965,10 @@ RZ_IPI RzCmdStatus rz_analyze_esil_int_list_load_handler(RzCore *core, int argc, RZ_IPI RzCmdStatus rz_analyze_esil_int_remove_handler(RzCore *core, int argc, const char **argv); // "aea" RZ_IPI RzCmdStatus rz_analyze_esil_insn_access_handler(RzCore *core, int argc, const char **argv, RzOutputMode mode); +// "aIf" +RZ_IPI RzCmdStatus rz_inquiry_analyze_function_handler(RzCore *core, int argc, const char **argv); +// "aIp" +RZ_IPI RzCmdStatus rz_inquiry_interpreter_prototype_handler(RzCore *core, int argc, const char **argv); // "b" RZ_IPI RzCmdStatus rz_block_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state); // "b-" diff --git a/librz/core/core.c b/librz/core/core.c index aa3918a55c1..9a82db448ff 100644 --- a/librz/core/core.c +++ b/librz/core/core.c @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2009-2020 pancake // SPDX-License-Identifier: LGPL-3.0-only +#include "rz_inquiry.h" #include #include #include @@ -1615,6 +1616,7 @@ RZ_API bool rz_core_init(RzCore *core) { /* XXX memory leak */ return false; } + core->inquiry = rz_inquiry_new(); core->ev = rz_event_new(core); core->max_cmd_depth = RZ_CONS_CMD_DEPTH + 1; core->sdb = sdb_new(NULL, "rzkv.sdb", 0); // XXX: path must be in home? @@ -1870,6 +1872,7 @@ RZ_API void rz_core_fini(RzCore *c) { RZ_FREE_CUSTOM(c->hash, rz_hash_free); RZ_FREE_CUSTOM(c->ropchain, rz_list_free); RZ_FREE_CUSTOM(c->ev, rz_event_free); + RZ_FREE_CUSTOM(c->inquiry, rz_inquiry_free); RZ_FREE(c->cmdlog); RZ_FREE(c->lastsearch); RZ_FREE(c->cons->pager); diff --git a/librz/core/libs.c b/librz/core/libs.c index 9ee9e7010e4..fc26a64510a 100644 --- a/librz/core/libs.c +++ b/librz/core/libs.c @@ -37,6 +37,7 @@ CB(bin, bin) CB(demangler, bin->demangler) CB(egg, egg) CB(hash, hash) +CB(inquiry, inquiry) static bool lib_arch_cb(RzLibPlugin *pl, void *user, void *data) { RzArchPlugin *hand = (RzArchPlugin *)data; @@ -122,6 +123,7 @@ RZ_API void rz_core_loadlibs_init(RzCore *core) { DF(EGG, "egg plugins", egg); DF(HASH, "hash plugins", hash); DF(ARCH, "(dis)assembler & analysis plugins", arch); + DF(INTERPRETER, "inquiry interpreter plugins", inquiry); core->times->loadlibs_init_time = rz_time_now_mono() - prev; } diff --git a/librz/core/meson.build b/librz/core/meson.build index 9db4c9736d4..c599dbb02f8 100644 --- a/librz/core/meson.build +++ b/librz/core/meson.build @@ -90,6 +90,7 @@ rz_core_sources = [ 'cmd/cmd_help.c', 'cmd/cmd_history.c', 'cmd/cmd_info.c', + 'cmd/cmd_inquiry.c', 'cmd/cmd_interactive.c', 'cmd/cmd_interactive_panel.c', 'cmd/cmd_interpret.c', @@ -166,6 +167,7 @@ rz_core_deps = [ rz_config_dep, rz_bin_dep, rz_mark_dep, + rz_inquiry_dep, platform_deps, dependency('rzgdb'), rzheap_dep, @@ -204,6 +206,7 @@ modules += { 'rz_core': { 'rz_socket', 'rz_type', 'rz_io', + 'rz_inquiry', 'rz_lang', 'rz_hash', 'rz_flag', diff --git a/librz/core/serialize_core.c b/librz/core/serialize_core.c index d5ab35c323b..86dd086b52a 100644 --- a/librz/core/serialize_core.c +++ b/librz/core/serialize_core.c @@ -22,28 +22,21 @@ static void file_save(RZ_NONNULL Sdb *db, RZ_NONNULL RzCore *core, RZ_NULLABLE c static bool file_load(RZ_NONNULL Sdb *db, RZ_NONNULL RzCore *core, RZ_NULLABLE const char *prj_file, RZ_NULLABLE RzSerializeResultInfo *res); -RZ_API void rz_serialize_core_save(RZ_NONNULL Sdb *db, RZ_NONNULL RzCore *core, RZ_NULLABLE const char *prj_file) { - file_save(sdb_ns(db, "file", true), core, prj_file); - rz_serialize_config_save(sdb_ns(db, "config", true), core->config); - rz_serialize_flag_save(sdb_ns(db, "flags", true), core->flags); - rz_serialize_mark_save(sdb_ns(db, "marks", true), core->marks); - rz_serialize_analysis_save(sdb_ns(db, "analysis", true), core->analysis); - rz_serialize_debug_save(sdb_ns(db, "debug", true), core->dbg); - rz_serialize_core_seek_save(sdb_ns(db, "seek", true), core); - - char buf[0x20]; - if (snprintf(buf, sizeof(buf), "0x%" PFMT64x, core->offset) < 0) { - return; - } - sdb_set(db, "offset", buf); +/* + * Config Exclusions: + * Most exlusions only affect loading and are still written to the file, in case + * they become interesting to load later, or for informative purposes. + * Some other config vars may be considered experimental and subject to frequent change, + * so they should also be exluded from saving to avoid polluting project files and + * having to introduce migrations. + */ - if (snprintf(buf, sizeof(buf), "0x%" PFMT32x, core->blocksize) < 0) { - return; - } - sdb_set(db, "blocksize", buf); -} +static const char *config_exclude_save[] = { + "inquiry.comment", // experimental + NULL +}; -static const char *config_exclude[] = { +static const char *config_exclude_load[] = { "dir.home", "dir.libs", "dir.magic", @@ -67,9 +60,31 @@ static const char *config_exclude[] = { "scr.utf8", "scr.utf8.curvy", "ghidra.sleighhome", // also important for Cutter + "inquiry.comment", // experimental NULL }; +RZ_API void rz_serialize_core_save(RZ_NONNULL Sdb *db, RZ_NONNULL RzCore *core, RZ_NULLABLE const char *prj_file) { + file_save(sdb_ns(db, "file", true), core, prj_file); + rz_serialize_config_save(sdb_ns(db, "config", true), core->config, config_exclude_save); + rz_serialize_flag_save(sdb_ns(db, "flags", true), core->flags); + rz_serialize_mark_save(sdb_ns(db, "marks", true), core->marks); + rz_serialize_analysis_save(sdb_ns(db, "analysis", true), core->analysis); + rz_serialize_debug_save(sdb_ns(db, "debug", true), core->dbg); + rz_serialize_core_seek_save(sdb_ns(db, "seek", true), core); + + char buf[0x20]; + if (snprintf(buf, sizeof(buf), "0x%" PFMT64x, core->offset) < 0) { + return; + } + sdb_set(db, "offset", buf); + + if (snprintf(buf, sizeof(buf), "0x%" PFMT32x, core->blocksize) < 0) { + return; + } + sdb_set(db, "blocksize", buf); +} + RZ_API bool rz_serialize_core_load(RZ_NONNULL Sdb *db, RZ_NONNULL RzCore *core, bool load_bin_io, RZ_NULLABLE const char *prj_file, RZ_NULLABLE RzSerializeResultInfo *res) { Sdb *subdb; @@ -79,7 +94,7 @@ RZ_API bool rz_serialize_core_load(RZ_NONNULL Sdb *db, RZ_NONNULL RzCore *core, if (load_bin_io) { SUB("file", file_load(subdb, core, prj_file, res)); } - SUB("config", rz_serialize_config_load(subdb, core->config, config_exclude)); + SUB("config", rz_serialize_config_load(subdb, core->config, config_exclude_load)); SUB("flags", rz_serialize_flag_load(subdb, core->flags, res)); SUB("marks", rz_serialize_mark_load(subdb, core->marks, res)); SUB("analysis", rz_serialize_analysis_load(subdb, core->analysis, res)); diff --git a/librz/il/definitions/mem.c b/librz/il/definitions/mem.c index d23a693b81b..fbc0eb1cf4b 100644 --- a/librz/il/definitions/mem.c +++ b/librz/il/definitions/mem.c @@ -154,7 +154,27 @@ static RzBitVector *read_n_bits(RzBuffer *buf, ut32 n_bits, RzBitVector *key, bo return value; } -static bool write_n_bits(RzBuffer *buf, RzBitVector *key, RzBitVector *value, bool big_endian) { +RZ_API bool rz_il_loadw_into(RZ_NONNULL RzBuffer *mem_buf, RZ_NONNULL RZ_OUT RzBitVector *out_bv, RZ_NONNULL const RzBitVector *key, ut32 n_bits, bool big_endian) { + rz_return_val_if_fail(mem_buf && out_bv && key, false); + ut64 address = rz_bv_to_ut64(key); + size_t n_bytes = rz_bv_len_bytes(out_bv); + ut8 *data = calloc(n_bytes, 1); + if (!data) { + return false; + } + // we ignore bad reads. RzBuffer fills up with its "overflow byte" on failure. + rz_buf_read_at(mem_buf, address, data, n_bytes); + if (big_endian) { + rz_bv_set_from_bytes_be(out_bv, data, 0, n_bits); + } else { + rz_bv_set_from_bytes_le(out_bv, data, 0, n_bits); + } + free(data); + return true; +} + +RZ_API bool rz_il_storew(RZ_NONNULL RzBuffer *buf, RZ_NONNULL const RzBitVector *key, RZ_NONNULL const RzBitVector *value, bool big_endian) { + rz_return_val_if_fail(buf && key && value, false); ut64 address = rz_bv_to_ut64(key); ut32 n_bytes = rz_bv_len_bytes(value); @@ -186,14 +206,32 @@ RZ_API RzBitVector *rz_il_mem_loadw(RzILMem *mem, RzBitVector *key, ut32 n_bits, return read_n_bits(mem->buf, n_bits, key, big_endian); } +/** + * Load data of the given size from the given address into the bitvector \p out_bv. + * + * \param out_bv The bitvector to write the loaded data into. + * \param key address (bitvector) + * \param n_bits How many bits to read. This also determines the size of the returned bitvector + * \return True on success, false on failure. + */ +RZ_API bool rz_il_mem_loadw_into(RZ_NONNULL RzILMem *mem, + RZ_OUT RZ_NONNULL RzBitVector *out_bv, + RZ_NONNULL const RzBitVector *key, + ut32 n_bits, + bool big_endian) { + rz_return_val_if_fail(mem && key && n_bits, false); + return_val_if_key_len_wrong(mem, key, NULL); + return rz_il_loadw_into(mem->buf, out_bv, key, n_bits, big_endian); +} + /** * Store an entire word or arbitrary size at an address * \param key address * \param value data * \return whether the store succeeded */ -RZ_API bool rz_il_mem_storew(RzILMem *mem, RzBitVector *key, RzBitVector *value, bool big_endian) { +RZ_API bool rz_il_mem_storew(RzILMem *mem, const RzBitVector *key, const RzBitVector *value, bool big_endian) { rz_return_val_if_fail(mem && key && value, false); return_val_if_key_len_wrong(mem, key, false); - return write_n_bits(mem->buf, key, value, big_endian); + return rz_il_storew(mem->buf, key, value, big_endian); } diff --git a/librz/include/meson.build b/librz/include/meson.build index e6ca0ef5f77..c7713c418f1 100644 --- a/librz/include/meson.build +++ b/librz/include/meson.build @@ -29,6 +29,7 @@ include_files = [ 'rz_heap_glibc.h', 'rz_heap_jemalloc.h', 'rz_il.h', + 'rz_inquiry.h', 'rz_io.h', 'rz_lang.h', 'rz_lib.h', @@ -142,6 +143,13 @@ rz_util_files = [ ] install_headers(rz_util_files, install_dir: join_paths(rizin_incdir, 'rz_util')) +rz_inquiry_sub_files = [ + 'rz_inquiry/rz_interpreter.h', + 'rz_inquiry/rz_il_cache.h', + 'rz_inquiry/rz_bcfg.h', +] +install_headers(rz_inquiry_sub_files, install_dir: join_paths(rizin_incdir, 'rz_inquiry')) + rz_il_definitions_files = [ 'rz_il/definitions/bool.h', 'rz_il/definitions/definitions.h', diff --git a/librz/include/rz_analysis.h b/librz/include/rz_analysis.h index f6782431f75..d645cef2545 100644 --- a/librz/include/rz_analysis.h +++ b/librz/include/rz_analysis.h @@ -357,7 +357,8 @@ typedef enum { RZ_ANALYSIS_OP_MASK_OPEX = (1 << 3), // It fills RzAnalysisop->opex info RZ_ANALYSIS_OP_MASK_DISASM = (1 << 4), // It fills RzAnalysisop->mnemonic // should be RzAnalysisOp->disasm // only from rz_core_analysis_op() RZ_ANALYSIS_OP_MASK_IL = (1 << 5), // It fills RzAnalysisop->il_op - RZ_ANALYSIS_OP_MASK_ALL = RZ_ANALYSIS_OP_MASK_ESIL | RZ_ANALYSIS_OP_MASK_VAL | RZ_ANALYSIS_OP_MASK_HINT | RZ_ANALYSIS_OP_MASK_OPEX | RZ_ANALYSIS_OP_MASK_DISASM | RZ_ANALYSIS_OP_MASK_IL + RZ_ANALYSIS_OP_MASK_INSN_PKT = (1 << 6), // Set op.size to the size of the instruction packet, not the single instruction. + RZ_ANALYSIS_OP_MASK_ALL = RZ_ANALYSIS_OP_MASK_ESIL | RZ_ANALYSIS_OP_MASK_VAL | RZ_ANALYSIS_OP_MASK_HINT | RZ_ANALYSIS_OP_MASK_OPEX | RZ_ANALYSIS_OP_MASK_DISASM | RZ_ANALYSIS_OP_MASK_IL | RZ_ANALYSIS_OP_MASK_INSN_PKT } RzAnalysisOpMask; typedef enum { @@ -888,6 +889,7 @@ typedef struct rz_analysis_op_t { RzAnalysisSwitchOp *switch_op; RzAnalysisHint hint; RzAnalysisDataType datatype; + ut64 hexagon_pkt_addr; } RzAnalysisOp; #define RZ_TYPE_COND_SINGLE(x) (!x->arg[1] || x->arg[0] == x->arg[1]) @@ -955,17 +957,45 @@ typedef struct rz_analysis_task_item { typedef enum { RZ_ANALYSIS_XREF_TYPE_NULL = 0, - RZ_ANALYSIS_XREF_TYPE_CODE = 'c', // code ref + RZ_ANALYSIS_XREF_TYPE_CODE = 'c', ///< Unspecified code reference. RZ_ANALYSIS_XREF_TYPE_CALL = 'C', // code ref (call) - RZ_ANALYSIS_XREF_TYPE_DATA = 'd', // mem ref - RZ_ANALYSIS_XREF_TYPE_STRING = 's' // string ref + RZ_ANALYSIS_XREF_TYPE_DATA = 'd', ///< Unspecified data memory access. + RZ_ANALYSIS_XREF_TYPE_STRING = 's', ///< String reference + /** + * \brief Memory read reference. + * Probably not the same as RZ_ANALYSIS_XREF_TYPE_DATA, but the data + * reference is used everywhere and changes the output of the disassembly. + * So keep it for now like that to have better test output. + */ + RZ_ANALYSIS_XREF_TYPE_MEM_READ = RZ_ANALYSIS_XREF_TYPE_DATA, + RZ_ANALYSIS_XREF_TYPE_MEM_WRITE = 'w', ///< Memory write reference + + RZ_ANALYSIS_XREF_TYPE_CALL_RET = 'r', // Call return point (next pc after a call). + RZ_ANALYSIS_XREF_TYPE_RETURN = 'R', // A returning jump } RzAnalysisXRefType; typedef struct rz_analysis_ref_t { + ut64 bb_addr; ///< Temporary member to make prototype function detection work. ut64 from; ut64 to; RzAnalysisXRefType type; } RzAnalysisXRef; + +/** + * \brief A struct combining information about an instruction storing the + * next instruction pointer of a basic block. + * This indicates that the ending branch instruction in a basic block is + * a call instruction. + */ +typedef struct { + ut64 bb_addr; ///< The address of the basic block which stores the NPC, WARNING: may not actually be filled yet! + ut64 store_addr; ///< The address of the instruction packet storing the NPC. Might be 0, if it was not added by the interpreter. + ut64 candidate_addr; ///< Address of the call candidate instruction packet. Might be 0, if it was not added by the interpreter. + ut64 npc; ///< The NPC stored. Should point after a basic block. + ut64 target; ///< The address the call candidate jumps to. + bool in_mem; ///< True if the NPC is written to memory. False if it is written to a register. +} RzAnalysisCallCandidate; + RZ_API const char *rz_analysis_ref_type_tostring(RzAnalysisXRefType t); /* represents a reference line from one address (from) to another (to) */ @@ -1500,6 +1530,10 @@ RZ_API bool rz_analysis_op_fini(RzAnalysisOp *op); RZ_API int rz_analysis_op_reg_delta(RzAnalysis *analysis, ut64 addr, const char *name); RZ_API bool rz_analysis_op_is_eob(const RzAnalysisOp *op); RZ_API bool rz_analysis_op_is_call(RZ_NONNULL const RzAnalysisOp *op); +RZ_API bool rz_analysis_op_changes_control_flow(RZ_NONNULL const RzAnalysisOp *op); +RZ_API bool rz_analysis_op_is_direct_call(RZ_NONNULL const RzAnalysisOp *op); +RZ_API bool rz_analysis_op_is_call(RZ_NONNULL const RzAnalysisOp *op); +RZ_API bool rz_analysis_op_is_direct_jump(RZ_NONNULL const RzAnalysisOp *op); RZ_API RzList /**/ *rz_analysis_op_list_new(void); RZ_API int rz_analysis_op(RZ_NONNULL RzAnalysis *analysis, RZ_OUT RzAnalysisOp *op, ut64 addr, const ut8 *data, ut64 len, RzAnalysisOpMask mask); RZ_API RzAnalysisOp *rz_analysis_op_hexstr(RzAnalysis *analysis, ut64 addr, const char *hexstr); @@ -1567,6 +1601,7 @@ RZ_API int rz_analysis_fcn(RzAnalysis *analysis, RzAnalysisFunction *fcn, ut64 a RZ_API int rz_analysis_fcn_del(RzAnalysis *analysis, ut64 addr); RZ_API int rz_analysis_fcn_del_locs(RzAnalysis *analysis, ut64 addr); RZ_API bool rz_analysis_fcn_add_bb(RzAnalysis *analysis, RzAnalysisFunction *fcn, ut64 addr, ut64 size, ut64 jump, ut64 fail); +RZ_API bool rz_analysis_add_bb(RzAnalysis *a, ut64 addr, ut64 size); RZ_API bool rz_analysis_check_fcn(RzAnalysis *analysis, ut8 *buf, ut16 bufsz, ut64 addr, ut64 low, ut64 high); RZ_API void rz_analysis_function_check_bp_use(RzAnalysisFunction *fcn); @@ -1606,6 +1641,11 @@ RZ_API bool rz_analysis_xrefs_set(RzAnalysis *analysis, ut64 from, ut64 to, RzAn RZ_API bool rz_analysis_xrefs_deln(RzAnalysis *analysis, ut64 from, ut64 to, RzAnalysisXRefType type); RZ_API bool rz_analysis_xref_del(RzAnalysis *analysis, ut64 from, ut64 to); +RZ_API bool rz_analysis_get_all_cf_targets(RzAnalysis *analysis, + const RzPVector /**/ *sections, + bool include_call_return_pts, + RZ_NONNULL RZ_OUT RzSetU *branch_targets); + /* var.c */ RZ_API RZ_BORROW RzAnalysisVar *rz_analysis_function_set_var( RzAnalysisFunction *fcn, diff --git a/librz/include/rz_bin.h b/librz/include/rz_bin.h index 3350ed64bde..d240e0ec816 100644 --- a/librz/include/rz_bin.h +++ b/librz/include/rz_bin.h @@ -632,12 +632,18 @@ typedef struct rz_bin_map_t { #define RZ_BIN_MAX_HANDLED_LAYOUT_SIZE (1024 * 1024 * 2) +typedef enum { + RZ_BIN_SECTION_ROLE_UNSPECIFIED = 0, + RZ_BIN_SECTION_ROLE_LINKING, ///< Section is used for (dynamic) linking. +} RzBinSectionRole; + /** * \brief A repetitive data layout of a section. The type will be applied to * element_size * element_count. */ typedef struct rz_bin_section_format_t { int /* RzAnalysisMetaType */ type; ///< The meta type of the elements. + RzBinSectionRole role; ///< The role a section has. ut64 element_size; ///< Size of an element in this section. size_t count; ///< The number of consecutive elements in this section. } RzBinSectionLayout; diff --git a/librz/include/rz_config.h b/librz/include/rz_config.h index ef0ae99237c..7b3fa437f6c 100644 --- a/librz/include/rz_config.h +++ b/librz/include/rz_config.h @@ -233,7 +233,7 @@ static inline bool rz_config_node_is_str(const RzConfigNode *node) { /* serialize */ -RZ_API void rz_serialize_config_save(RZ_NONNULL Sdb *db, RZ_NONNULL RzConfig *config); +RZ_API void rz_serialize_config_save(RZ_NONNULL Sdb *db, RZ_NONNULL RzConfig *config, RZ_NULLABLE const char **exclude); RZ_API bool rz_serialize_config_load(RZ_NONNULL Sdb *db, RZ_NONNULL RzConfig *config, RZ_NULLABLE const char **exclude); #endif diff --git a/librz/include/rz_core.h b/librz/include/rz_core.h index ac4ba471675..089117dfffc 100644 --- a/librz/include/rz_core.h +++ b/librz/include/rz_core.h @@ -4,6 +4,7 @@ #ifndef RZ_CORE_H #define RZ_CORE_H +#include #include #include #include @@ -294,6 +295,7 @@ struct rz_core_t { ut8 ptr_alignment_III; // NOTE: Do not change the order of fields above! // They are used in pointer passing hacks in rz_types.h. + RzInquiry *inquiry; RzIO *io; HtSP /**/ *plugins; ///< List of registered core plugins HtSP /**/ *plugin_contexts; ///< Per-core plugin state diff --git a/librz/include/rz_il/definitions/mem.h b/librz/include/rz_il/definitions/mem.h index ab541284e51..73a8228eec8 100644 --- a/librz/include/rz_il/definitions/mem.h +++ b/librz/include/rz_il/definitions/mem.h @@ -13,6 +13,16 @@ extern "C" { typedef ut32 RzILMemIndex; +/** + * \brief Low-level reading function used in rz_il_mem_loadw_into + */ +RZ_API bool rz_il_loadw_into(RZ_NONNULL RzBuffer *mem_buf, RZ_NONNULL RZ_OUT RzBitVector *out_bv, RZ_NONNULL const RzBitVector *key, ut32 n_bits, bool big_endian); + +/** + * \brief Low-level writing function used in rz_il_mem_storew + */ +RZ_API bool rz_il_storew(RZ_NONNULL RzBuffer *buf, RZ_NONNULL const RzBitVector *key, RZ_NONNULL const RzBitVector *value, bool big_endian); + /** * \brief A single memory as part of the RzIL VM. * @@ -36,7 +46,12 @@ RZ_API ut32 rz_il_mem_value_len(RzILMem *mem); RZ_API RzBitVector *rz_il_mem_load(RzILMem *mem, RzBitVector *key); RZ_API bool rz_il_mem_store(RzILMem *mem, RzBitVector *key, RzBitVector *value); RZ_API RzBitVector *rz_il_mem_loadw(RzILMem *mem, RzBitVector *key, ut32 n_bits, bool big_endian); -RZ_API bool rz_il_mem_storew(RzILMem *mem, RzBitVector *key, RzBitVector *value, bool big_endian); +RZ_API bool rz_il_mem_loadw_into(RZ_NONNULL RzILMem *mem, + RZ_OUT RZ_NONNULL RzBitVector *out_bv, + RZ_NONNULL const RzBitVector *key, + ut32 n_bits, + bool big_endian); +RZ_API bool rz_il_mem_storew(RzILMem *mem, const RzBitVector *key, const RzBitVector *value, bool big_endian); #ifdef __cplusplus } diff --git a/librz/include/rz_inquiry.h b/librz/include/rz_inquiry.h new file mode 100644 index 00000000000..f6f40ed9f39 --- /dev/null +++ b/librz/include/rz_inquiry.h @@ -0,0 +1,104 @@ +// SPDX-FileCopyrightText: 2025 RizinOrg +// SPDX-License-Identifier: LGPL-3.0-only + +/** + * \file The header file for the RzInquiry module provides the declarations + * for RzInquiry plugins. As well as access to broader analysis functions. + */ + +#ifndef RZ_INQUIRY +#define RZ_INQUIRY + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +/** + * \brief The number of iterations inquiry checks for a user given signal. + * Checking it costs performance, so it is just checked the X iterations. + */ +#define RZ_INQUIRY_CHECK_USER_SIGNAL_ITC 1000 + +typedef struct rz_inquiry_plugin_t { + const char *name; + const char *author; + const char *version; + const char *desc; + const char *license; + RzInterpValueAbstraction *value_abstraction; +} RzInquiryPlugin; + +typedef struct { + /** + * \brief RzInquiry interpreter plugins. Indexed by name. + */ + HtSP /**/ *plugins; + HtSP /**/ *plugins_data; + + HtUP /**/ *call_candidates; ///< Indexed by address of basic block with the call candidate. + RzVector /**/ *dynamic_xrefs; ///< All xrefs the interpreter detected. + RzInquiryBCFG *bcfg; ///< The control flow graph all the basic blocks build. +} RzInquiry; + +RZ_IPI bool rz_inquiry_bcfg_complement(RzInquiry *iq, RzVector /**/ *insn_to_insn_edges); + +RZ_IPI void rz_inquiry_add_xref(RzInquiry *iq, const RzAnalysisXRef *xref); + +RZ_API bool rz_inquiry_plugin_add(RZ_BORROW RZ_NONNULL RzInquiry *inquiry, RZ_NONNULL RzInquiryPlugin *plugin); +RZ_API bool rz_inquiry_plugin_del(RZ_BORROW RZ_NONNULL RzInquiry *inquiry, RZ_NONNULL RzInquiryPlugin *plugin); + +RZ_API RZ_OWN RzInquiry *rz_inquiry_new(void); +RZ_API void rz_inquiry_free(RZ_OWN RZ_NULLABLE RzInquiry *q); + +RZ_API bool rz_inquiry_xref_interpreter_filter(RZ_NONNULL const RzAnalysisXRef *xref, RZ_NONNULL const RzPVector /**/ *allowed_segments); + +RZ_API bool rz_inquiry_interpreter(RzCore *core, RZ_OWN RzSetU /**/ *entry_points); + +RZ_API bool rz_inquiry_function_deduction( + RZ_NONNULL RZ_BORROW RzAnalysis *analysis, + RZ_NONNULL RZ_BORROW RzInquiry *inquiry, + RZ_NONNULL RZ_BORROW RzSetU *symbol_addresses, + RZ_NONNULL const RzPVector /**/ *symbols, + RZ_NONNULL const RzVector /**/ *ignored_code, + RZ_NONNULL RZ_OUT RzPVector /**/ *inquiry_fcns); + +//============ +// Algorithms +//============ + +/** + * \brief A simple function deduced by inquiry algorithms. + */ +typedef struct { + RzVector /**/ *entry_points; + RzInquiryBCFG *bcfg; +} RzInquiryFunction; + +RZ_IPI RZ_OWN RzInquiryFunction *rz_inquiry_function_new(); +RZ_API RZ_OWN char *rz_inquiry_function_str(const RzInquiryFunction *fcn); + +RZ_API bool rz_inquiry_get_fcn_symbol_addr(RzCore *core, RZ_OUT RzSetU *symbol_targets); + +RZ_API void rz_inquiry_function_free(RZ_NULLABLE RZ_OWN RzInquiryFunction *fcn); + +RZ_API bool rz_inquiry_algo_revng_fcn_detection( + RzSetU *symbol_addresses, + const HtUP /**/ *call_candidates, + const RzInquiryBCFG *bcfg, + RZ_OUT RzPVector /**/ *fcns, + RZ_NONNULL const RzVector /**/ *ignored_code); + +RZ_IPI bool rz_inquiry_convert_and_add_to_analysis( + RzAnalysis *analysis, + RzInquiry *inquiry, + const RzPVector /**/ *fcns, + const RzPVector /**/ *symbols); + +#ifdef __cplusplus +} +#endif +#endif // RZ_INQUIRY diff --git a/librz/include/rz_inquiry/rz_bcfg.h b/librz/include/rz_inquiry/rz_bcfg.h new file mode 100644 index 00000000000..32924a84d39 --- /dev/null +++ b/librz/include/rz_inquiry/rz_bcfg.h @@ -0,0 +1,77 @@ +// SPDX-FileCopyrightText: 2026 Rot127 +// SPDX-License-Identifier: LGPL-3.0-only + +#ifndef RZ_INQUIRY_BCFG_H +#define RZ_INQUIRY_BCFG_H + +#include +#include + +/** + * \brief A block of instructions which end in a jump/call/exit. + */ +typedef struct { + ut64 addr; + ut64 size; +} RzInquiryBlock; + +/** + * \brief The different kind of BB CFG edges. + */ +typedef enum { + RZ_INQUIRY_BCFG_EDGE_TYPE_NONE = 0, + /** + * \brief An control flow change via a RzIL JMP. The "from" BB ends with a jump. + */ + RZ_INQUIRY_BCFG_EDGE_TYPE_JMP, + /** + * \brief A control flow edge between two blocks where the "from" block does not end with a JUMP. + * It is between two blocks which were split from a single one. + */ + RZ_INQUIRY_BCFG_EDGE_TYPE_CF, + /** + * \brief An control flow change via a RzIL JMP but by a call candidate. + * The "from" BB ends with a jump, very likely to a procedure. + */ + RZ_INQUIRY_BCFG_EDGE_TYPE_CALL, + /** + * \brief This edge does not depict a control flow edge. + * It is an opaque edge between a call instruction and the (possible) return point of the procedure called. + * Note that the return point is often, but not always, the instruction at the following memory address after the call. + * There are multiple exceptions to it though, like tail calls, non-returning procedures, or delayed calls. + */ + RZ_INQUIRY_BCFG_EDGE_TYPE_CALL_RET, + /** + * \brief An edge from a return instruction to its target. + */ + RZ_INQUIRY_BCFG_EDGE_TYPE_RETURN, +} RzInquiryBCFGEdgeType; + +/** + * \brief A control flow graph with blocks as nodes. + * Edges can be of different jump, and call/return type. + */ +typedef struct { + /** + * \brief The CFG discovered during interpretation. + * The node data are the addresses of basic blocks, cast to (void *). + */ + RzGraph /**/ *graph; +} RzInquiryBCFG; + +RZ_IPI RZ_OWN RzInquiryBCFG *rz_inquiry_bcfg_new(RzGraphImplType impl_type); +RZ_IPI void rz_inquiry_bcfg_free(RZ_NULLABLE RZ_OWN RzInquiryBCFG *bcfg); +RZ_IPI bool rz_inquiry_bcfg_add_block(RzInquiryBCFG *cfg, ut64 addr, ut64 size); + +RZ_IPI bool rz_inquiry_bcfg_get_block(const RzInquiryBCFG *cfg, ut64 bb_addr, RZ_OUT RzInquiryBlock *bb); +RZ_IPI bool rz_inquiry_bcfg_del_out_edges(RzInquiryBCFG *cfg, ut64 bb_addr); +RZ_IPI bool rz_inquiry_bcfg_add_edge(RzInquiryBCFG *cfg, ut64 from_bb, ut64 to_bb, RzInquiryBCFGEdgeType type); +RZ_IPI bool rz_inquiry_bcfg_add_edge_xref(RzInquiryBCFG *cfg, const RzVector /**/ *xrefs); +RZ_IPI bool rz_inquiry_bcfg_del_edge(RzInquiryBCFG *cfg, ut64 from_bb, ut64 to_bb); +RZ_API RZ_OWN RzIterator /**/ *rz_inquiry_bcfg_get_outgoing_edges(const RzInquiryBCFG *cfg, ut64 bb_addr); +RZ_API RZ_OWN RzIterator /**/ *rz_inquiry_bcfg_get_incoming_edges(const RzInquiryBCFG *cfg, ut64 bb_addr); + +RZ_IPI bool rz_inquiry_bcfg_reduce(RzInquiryBCFG *cfg); +RZ_IPI RZ_OWN char *rz_inquiry_bcfg_as_dot(const RzInquiryBCFG *bcfg, RZ_NULLABLE const char *name); + +#endif // RZ_INQUIRY_BCFG_H diff --git a/librz/include/rz_inquiry/rz_il_cache.h b/librz/include/rz_inquiry/rz_il_cache.h new file mode 100644 index 00000000000..85430e8f335 --- /dev/null +++ b/librz/include/rz_inquiry/rz_il_cache.h @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: 2026 Rot127 +// SPDX-License-Identifier: LGPL-3.0-only + +#ifndef RZ_INQUIRY_IL_CACHE_H +#define RZ_INQUIRY_IL_CACHE_H + +#include "rz_types.h" +#include +#include + +typedef struct rz_il_cache_t RzILCache; + +typedef struct { + RzILOpEffect *effect; ///< Vector with the IL ops of an instruction packets. + size_t insn_pkt_size; ///< The size of the instruction packet. Used to increment the PC if no JMP occurred. +} RzILCacheInsnPkt; + +/** + * \brief A sequence of instructions of which the last instruction is always + * a branch or a terminating instruction. + */ +typedef struct { + RzPVector /**/ *il_ops; ///< The sequence of IL operations of this block. + size_t size; ///< The number of bytes the instructions of the block cover in memory. + ut64 addr; ///< The address where the block starts. +} RzILCacheBlock; + +typedef enum { + RZ_IL_CACHE_CONFIG_DEFAULT = 0, + /** + * \brief The IL cache will replace un-lifted instructions + * with a NOP. + */ + RZ_IL_CACHE_CONFIG_NOP_UNLIFTED = 1 << 0 +} RzILCacheConfig; + +RZ_API RZ_OWN char *rz_il_cache_block_str(RZ_NONNULL const RzILCacheBlock *block); + +RZ_API RZ_OWN RzILCache *rz_il_cache_new( + RZ_BORROW RZ_NONNULL RzAnalysis *analysis, + RZ_BORROW RZ_NONNULL RzIO *io, + RzILCacheConfig config); +RZ_API void rz_il_cache_free(RZ_OWN RZ_NULLABLE RzILCache *cache); + +RZ_API bool rz_il_cache_serve(RZ_NONNULL RzILCache *cache); +RZ_API RZ_OWN const RzILCacheBlock *rz_il_cache_lift_il_block(RzILCache *cache, ut64 addr); + +#endif // RZ_INQUIRY_IL_CACHE_H diff --git a/librz/include/rz_inquiry/rz_interpreter.h b/librz/include/rz_inquiry/rz_interpreter.h new file mode 100644 index 00000000000..77d62ea04f8 --- /dev/null +++ b/librz/include/rz_inquiry/rz_interpreter.h @@ -0,0 +1,242 @@ +// SPDX-FileCopyrightText: 2025 RizinOrg +// SPDX-License-Identifier: LGPL-3.0-only + +/** + * \file The header file for the RzInterp contains declarations for + * all RzIL based interpreters. + */ + +#ifndef RZ_INTERPRETER +#define RZ_INTERPRETER + +#include +#include +#include +#include +#include + +typedef struct rz_interp_run_context_t RzInterpRunContext; +typedef struct rz_interp_result_t RzInterpResult; + +/** + * \brief An abstract value representing a set of RzILVal + * + * The actual abstraction and structure of this is defined by the plugin in use. + * `struct rz_interp_opaque_abstr_val_t` is defined nowhere. Is is used to ensure + * type-checking of passing this opaque pointer. + */ +typedef struct rz_interp_abstr_val_t RzInterpAbstrVal; + +/** + * \brief Helper to explicitly cast a plugin-defined abstract value to an opaque RzInterpAbstrVal + */ +static inline RzInterpAbstrVal *rz_interp_abstr_val_pack(void *val) { + return val; +} + +/** + * \brief Helper to explicitly cast an opaque RzInterpAbstrVal to a plugin-defined abstract value + */ +static inline void *rz_interp_abstr_val_unpack(const RzInterpAbstrVal *val) { + return (void *)val; +} + +typedef enum { + RZ_INTERP_PC_CONST, ///< Single known value + RZ_INTERP_PC_UNREACHABLE, ///< Bottom/unreachable state, if this is set, pc field is unused and undefined + RZ_INTERP_PC_ANY ///< Top state, if this is set, pc field is unused and undefined +} RzInterpPCState; + +typedef struct { + ut64 pc; ///< Interpreter location in the code. This is not necessarily identical to the ISA's program counter register, but simply points to the instruction to execute next. + RzInterpPCState pc_state; + + HtUP /**/ *globals; ///< Global variables (mostly registers). Indexed by DJB2 hash of global name. + HtUP /**/ *locals; ///< Local variables. Indexed by DJB2 hash of the local name. + HtUP /**/ *lets; ///< Let variables. Indexed by DJB2 hash of the let name. +} RzInterpAbstrState; + +/** + * \brief Basic Block as part of the abstract interpretation loop + * Represents a block of instructions of which only the last may have a control effect. + * Unlike IL blocks, the last instruction may also not have a control effect, which is + * the case when the instruction directly following this block has an in-edge. + * And unlike in RzAnalysisBlock, a call instruction also terminates an interpreter block. + */ +typedef struct { + RzIntervalNode *node; ///< Backref to the node containing this block. end value is inclusive. TODO: remove this if RBTree is used directly + /** + * Least upper bound of all states discovered at the entry of the block. + * pc_state of this state must be RZ_INTERP_PC_CONST and pc points to the first instruction of the block. + */ + RzInterpAbstrState *entry_state; // TODO: flatten to remove indirection + RzVector /**/ insn_offsets; ///< starting at the second instruction in the block (since first is always 0), offsets from the start of the block + bool bounds_resolved; ///< Set to true once insn_offsets and node->end are filled. + bool uninterpreted; ///< True if the entry state has not yet been started to interpret, i.e. the block is part of RzInterpFunctionState.queue + bool non_fallthrough_in; ///< True if there is an in-edge to this block that is not only a fallthrough. Used when merging consecutive blocks after interpretation. + bool added_to_analysis; ///< Only used after interpretation, when adding to analysis. Marks blocks that have been merged with the previous. + + // Out-edges + bool fallthrough; ///< if true, there is an edge to the block after the end of this one + RzVector /**/ jump_targets; ///< Explicit jump targets, does not contain fallthrough address +} RzInterpBlock; + +typedef struct rz_interp_instance_t RzInterpInstance; + +typedef struct { + const char *name; + + RZ_OWN RzInterpAbstrVal *(*val_new_top)(void); ///< allocate a new abstract value and initialize it as top + void (*val_free)(RzInterpAbstrVal *val); + bool (*is_top)(RZ_NONNULL const RzInterpAbstrVal *val); ///< return whether the given value is top + bool (*may_be_bool)(RZ_NONNULL const RzInterpAbstrVal *val, bool value); ///< return whether the given value's concrete set contains \p value + void (*set_top)(RZ_OUT RZ_NONNULL RzInterpAbstrVal *dst); ///< Set \p val to be top + void (*set_const_bool)(RZ_OUT RZ_NONNULL RzInterpAbstrVal *dst, bool src); ///< set \p dst to the least value that includes \p src + void (*set_const_bv)(RZ_OUT RZ_NONNULL RzInterpAbstrVal *dst, RZ_IN RZ_NONNULL RzBitVector *src); ///< set \p dst to the least value that includes \p src + + /** + * \brief Concretize an abstract value to a single bit vector, if possible + * + * If \p val represents exactly one concrete value, this returns true. + * Additionally, the concrete value is written to \p out if passed. + */ + bool (*to_concrete_const)(RZ_NONNULL const RzInterpAbstrVal *val, RZ_NULLABLE RZ_OUT RzBitVector *out); + + RZ_OWN void (*copy)(RzInterpAbstrVal *dst, const RzInterpAbstrVal *src); + + /** + * \brief Performs the join operation on two abstract values (least upper bound, lattice theory) + * + * This is the least upper bound (lattice theory). In detail, it means \p a should be set to the least + * abstract value whose corresponding concrete value set is a superset of both the concrete value sets + * of \p a and \p b. + * + * \return True if a was changed + */ + bool (*join)(RZ_BORROW RZ_INOUT RzInterpAbstrVal *a, RZ_BORROW RZ_IN const RzInterpAbstrVal *b); + + /** + * \brief Builds a string for printing an abstract value. + * + * \return Returns false in case of error, True otherwise. + */ + bool (*val_as_str)(RZ_NONNULL const RzInterpAbstrVal *state, + RZ_NONNULL RZ_OUT RzStrBuf *str_buf); + + void (*eval_cast)(ut32 length, RZ_NONNULL const RzInterpAbstrVal *fill, RZ_INOUT RZ_NONNULL RzInterpAbstrVal *val); ///< RZ_IL_OP_CAST + void (*eval_shift)(bool right, RZ_NONNULL RZ_INOUT RzInterpAbstrVal *x, RZ_NONNULL const RzInterpAbstrVal *y, RZ_NONNULL const RzInterpAbstrVal *fill_bit); ///< RZ_IL_OP_SHIFTL, RZ_IL_OP_SHIFTR + + /** + * \brief Evaluate a binary operation on two abstract values + * + * \param code The operation to evaluate. May be any IL op that takes exactly two bitvector or boolean operands. + * \param x Output, as well as `x` (or `low`) operand + * \param y `y` (or `low`) operand + */ + void (*eval_binop)(RzILOpPureCode code, RZ_NONNULL RZ_INOUT RzInterpAbstrVal *x, RZ_NONNULL const RzInterpAbstrVal *y); + + /** + * \brief Evaluate a unary operation on an abstract value + * + * \param code The operation to evaluate. May be any IL op that takes exactly one bitvector or boolean operand. + * \param val Output, as well as operand + */ + void (*eval_unop)(RzILOpPureCode code, RZ_NONNULL RZ_INOUT RzInterpAbstrVal *val); + +} RzInterpValueAbstraction; + +typedef struct rz_interp_io_read_request_t { + size_t mem_idx; ///< The memory space to read/write. + bool big_endian; ///< Set if the data is big endian ordered. + const RzBitVector *addr; ///< The address to read/write. + RzBitVector *ld_data; ///< The bit vector to load into. It is BORROWED. + size_t n_bits; ///< The number of bits to read/write. +} RzInterpIOReadRequest; + +typedef enum rz_interp_io_read_result_t { + RZ_INTERP_IO_READ_RESULT_OK, ///< ld_data fully written + RZ_INTERP_IO_READ_RESULT_TOP, ///< result should be considered top (memory contents unknown) + RZ_INTERP_IO_READ_RESULT_BREAK ///< interpreter is signaled to stop +} RzInterpIOReadResult; + +typedef enum rz_interp_lift_block_result_t { + RZ_INTERP_LIFT_BLOCK_RESULT_OK, ///< block_out is filled + RZ_INTERP_LIFT_BLOCK_RESULT_FAILED, + RZ_INTERP_LIFT_BLOCK_RESULT_BREAK ///< interpreter is signaled to stop +} RzInterpLiftBlockResult; + +typedef struct rz_interp_config_t { + RzInterpValueAbstraction *val_domain; + + void *cb_user; + RzInterpIOReadResult (*io_read)(RZ_NONNULL RZ_OWN RzInterpIOReadRequest *req, void *user); + RzInterpLiftBlockResult (*lift_block)(ut64 addr, RZ_OUT const RzILCacheBlock **block_out, void *user); +} RzInterpConfig; + +/** + * \brief Root local data of a single interpreter thread + */ +RZ_LIFETIME(RzInquiry) +struct rz_interp_instance_t { + RzInterpConfig config; + + RzAnalysisILContext *il_ctx; ///< Context about available global vars and memory + HtUP *var_name_hashes; ///< Map of DJB2 hashes to variable names. + RZ_DEPRECATE const char *arch_name; ///< Name of architecture. Used only by work-arounds until we have RzArch. +}; + +RZ_API RZ_OWN RzInterpInstance *rz_interp_instance_new(RzAnalysis *analysis, RZ_NONNULL RZ_BORROW const RzInterpConfig *config); +RZ_API void rz_interp_instance_free(RZ_OWN RZ_NULLABLE RzInterpInstance *iset); + +/** + * \brief Dimensions describing what kind of information should be retrieved from the interpreter run + */ +typedef enum rz_interp_result_dimen_t { + /** + * \brief The basic information that is always included in every run + * This includes block boundaries, control flow edges and entry states for each block. + */ + RZ_INTERP_RESULT_DIMEN_BASE = 0, + RZ_INTERP_RESULT_DIMEN_XREFS = 1 << 0, ///< fills RzInterpResult.xrefs + RZ_INTERP_RESULT_DIMEN_COMMENTS = 1 << 1 ///< per-address textual state description for debugging, fills RzInterpResult.comments +} RzInterpResultDimen; + +/** + * \brief Local data during an interpreter run + */ +struct rz_interp_run_context_t { + RzInterpInstance *inst; //< parent interpreter thread + + RzList /**/ *queue; ///< States that have to be interpreted still. If this is empty, a fixpoint has been reached. + /** + * \brief All currently discovered blocks by address. + * TODO: If the interval tree concept is kept, this should eventually be refactored to use RBTree directly and embed RBNode + * inside RzInterpBlock to remove the additional indirection. + */ + RzIntervalTree /**/ blocks; + RzInterpResultDimen res_dimen; + RzInterpResult *res; ///< If not NULL, a fixpoint has been reached already and we are now collecting results + + // Tracking data local to a single block interpretation + RzInterpBlock *block; ///< The currently interpreted interp block + ut64 il_block_end; ///< The address directly after the last instruction of the currently interpreted IL block, may be further than the last instruction of the interp block! + RzInterpAbstrState *astate; ///< The abstract state of the interpreter. + ut64 insn_addr; ///< The address of the currently evaluated instruction. This is not equal to astate->pc since that is already advanced by the instruction size. + RzAnalysisCallCandidate call_cand; ///< Data of a call candidate. +}; + +struct rz_interp_result_t { + ut64 entry; ///< always filled + RzIntervalTree /**/ blocks; ///< always filled + RzVector /**/ xrefs; ///< filled if RZ_INTERP_RESULT_DIMEN_XREFS is requested + HtUP /**/ *comments; ///< filled if RZ_INTERP_RESULT_DIMEN_COMMENTS +} /*RzInterpResult*/; + +RZ_API RzInterpResult *rz_interp_run(RzInterpInstance *inst, ut64 entry_point, RzInterpResultDimen dimen); +RZ_API bool rz_interp_result_apply_to_analysis(RZ_NONNULL RzInterpResult *res, RZ_NONNULL RzAnalysis *analysis); + +extern RZ_API RzInterpValueAbstraction rz_interp_value_domain_const; + +RZ_API bool rz_interp_driver_run(RzCore *core, RZ_OWN RzSetU *entry_points, RzInterpResultDimen dimens); + +#endif // RZ_INTERPRETER diff --git a/librz/include/rz_lib.h b/librz/include/rz_lib.h index f652ba80a4a..e0a00bfcea0 100644 --- a/librz/include/rz_lib.h +++ b/librz/include/rz_lib.h @@ -51,6 +51,7 @@ typedef enum { RZ_LIB_TYPE_EGG, ///< rz_egg plugin RZ_LIB_TYPE_DEMANGLER, ///< demanglers RZ_LIB_TYPE_ARCH, ///< demanglers + RZ_LIB_TYPE_INTERPRETER, ///< Analysis interpreters RZ_LIB_TYPE_UNKNOWN } RzLibType; diff --git a/librz/include/rz_th.h b/librz/include/rz_th.h index aedbcf24a84..9a4b2109e49 100644 --- a/librz/include/rz_th.h +++ b/librz/include/rz_th.h @@ -104,6 +104,7 @@ RZ_API bool rz_th_queue_is_full(RZ_NONNULL RzThreadQueue *queue); RZ_API size_t rz_th_queue_size(RZ_NONNULL RzThreadQueue *queue); RZ_API void rz_th_queue_close_when_empty(RZ_NONNULL RzThreadQueue *queue); RZ_API void rz_th_queue_close(RZ_NONNULL RzThreadQueue *queue); +RZ_API void rz_th_queue_open(RZ_NONNULL RzThreadQueue *queue); RZ_API bool rz_th_queue_is_closed(RZ_NONNULL RzThreadQueue *queue); RZ_API RZ_OWN RzAtomicBool *rz_atomic_bool_new(bool value); diff --git a/librz/include/rz_types.h b/librz/include/rz_types.h index 1650376f017..e9865c15f86 100644 --- a/librz/include/rz_types.h +++ b/librz/include/rz_types.h @@ -52,6 +52,8 @@ extern "C" { #define RZ_DEPRECATE /* should not be used in new code and should/will be removed in the future */ #endif +#define RZ_LIFETIME(X) /* The lifetime of the object is as long as the given X. What X is depends on the context. */ + #define RZ_IFNULL(x) /* default value for the pointer when null */ #ifdef __GNUC__ #define RZ_UNUSED __attribute__((__unused__)) diff --git a/librz/inquiry/README.md b/librz/inquiry/README.md new file mode 100644 index 00000000000..d2092153e9f --- /dev/null +++ b/librz/inquiry/README.md @@ -0,0 +1,166 @@ + + + +# RzInquiry Module + +Module implementing basic and advanced binary analysis. + +## TODO + +* rename interp to absint +* rename valueabstraction to valuedomain +* update tests +* branch and remove everything that should not be merged at first +* rebase/squash abstr-intr-prototype on top of new branch + +## Interpreter Notes + +Ideas for optimization (do not implement any of these without profiling first): +* evaluation (`eval_pure` in `interpreter.c` and possibly also `eval_effect`) + often temporarily allocates temporary abstract values if there is more than + one operand in an op. + E.g. for `(add x y)`, the output RzAbstractVal can be reused to evaluate `x` + into first, but `y` is allocated dynamically on the heap. + Because the size of dynamic values depends on the plugin, we can't use + regular stack memory. However we could build our own stack to the side and + re-use allocations that way. + For example an `RzVector` where the element size is given by the plugin. +* In evaluation of an op, one could often short-circuit if one operand is + already known to be top. + E.g. for `(add x y)` with constant/top abstraction, if `x` evaluates to top, + we don't have to evaluate `y` anymore because the result will be top anyway. + However, this may not always be the case, e.g. for `(logand x y)`, if `x` is + top, `y` may evaluate to 0 and the result will be a constant again. And if + the plugin defines a more fine-grained abstraction then even more cases will + result in non-top results. + So the plugin must decide whether to short-circuit or not. It has to be + profiled if the cost of asking the plugin all the time is really compensated + by avoided evalutations in practical code. + +## Multiple entrypoints in one block + +Code: +``` +B> fallthrough + fallthrough +A> fallthrough + jmp +``` + +B and A are in-edges. When interpreting, we first only get the full IL blocks +where B extends until the jmp and A does not know there may be instructions +before. +Desired outcome is to have blocks like this without overlap: + +``` +------------------ +| B> fallthrough | +| fallthrough | +------------------ + | +------------------ +| A> fallthrough | +| jmp | +------------------ +``` + +rot127 solved it by just interpreting the IL blocks as they come and at the end +reducing the overlapping blocks. + +thestr4ng3r's approach is to handle the reduction as part of the interpreter loop: + +Case 1: + A was already discovered. + B is discovered later. + => It must extend only until the start of A. + +Case 2: + B was already discovered. + A is discovered later. + => B must be split. + +Special cases: +Jump into the middle of an instruction. In such cases, overlaps between blocks +are permitted. Perhaps when an instruction meets again with one from another +block, this should be merged. + +### Call detection issues + +Call detection is based on a store of the block end addr before the jump. +Consider the following ARMv4 code for an indirect call (blx was introduced in ARMv5): + +``` +A> mov lr, pc +B> mov pc, r0 +C> ... +``` + +both A and B are block entries. + +1. If A is discovered before B, the call is recognized and C is also added as a fallthrough entry. +2. If B is discovered before A, the call is not recognized because A and B are two separate blocks already. + +The ideal outcome is probably that this is **not** recognized as a call since +it is unlikely to be meant to be one in this pattern, but this is not practical +as explained in detail in the section below. +Since for this special case, we will not decide whether the jump is a call, we +have to simply interpret it as both a call and a direct jump. + +Idea: have some flags per block, something like: +- Fallthrough +- Stores bb-end, WARNING: bb-end must be detected from the IL cache block that definitely ends with an explicit jmp + +#### Detailed reasoning why avoiding call detection is not possible + +Consider patterns like this, where there is an in-edge to A and to B. + +``` +A> mov lr, pc +B> jmp 1324 +C> ... +``` + +For all such patterns across an entire interpretation, we now want to **not** +consider the jmp as a call, but only because of the in-edge B. However: + +* To make the decision whether a block from entry A is calling, it is necessary + to know that there is no entry B into the block splitting pc storage and + jump. +* This is only possible to know once a fixpoint has been reached already. But + reaching the final fixpoint also depends on whether A is calling, so there is + a circular dependency. + +One might try to approach this by postponing adding any successor states of A +if it is detected that it might be calling, then loop until a temporary +fixpoint is reached, then continue from A, again postponing deciding on +successors of potentially calling blocks and do this until a final fixpoint has +been reached. +But if wethen detect an edge B, we have violated our condition that we want +this pattern to always not be recognized as calling. But if from this we might +rewind and conclude that A is not calling, that edge B would disappear, so A +should be calling again and it is a circular dependency again. + +We could weaken our initial condition to allow for A to be calling while B is +there if B is detected only because of A later on, but then consider this +example: + +``` +A> mov lr, pc +B> jmp 1234 +C> jmp B' +... +A'> mov lr, pc +B'> jmp 4321 +C'> jmp B +``` + +There is no unique solution for minimizing calling blocks with edges into them +anymore. Either A is considered calling and A' is not, or the other way around. + +In conclusion, it is impossible to guarantee that blocks that have a +call-splitting in-edge are never treated as calling, and it is also impossible +to minimize the number of such occurrences deterministically (avoiding +arbitrary heuristics). +And even if we tried to minimize these cases and use some heuristics to decide +between multiple solutions, the added complexity to the algorithm would likely +be justified by how little practical usefulness this has. diff --git a/librz/inquiry/algorithms/revng_fcn_detection.c b/librz/inquiry/algorithms/revng_fcn_detection.c new file mode 100644 index 00000000000..8d811bc02f0 --- /dev/null +++ b/librz/inquiry/algorithms/revng_fcn_detection.c @@ -0,0 +1,260 @@ +// SPDX-FileCopyrightText: 2026 RizinOrg +// SPDX-License-Identifier: LGPL-3.0-only + +/** + * \file This is the function detection algorithm inspired by + * paper: "REV.NG: A Unified Binary Analysis Framework to Recover CFGs and Function Boundaries" + * chapter: "4.2 Function boundaries recovery" + * doi: 10.1145/3033019.3033028 + * + * The actually algorithm is really simple. + * + * TERMS: + * + * Basic Block (BB): A sequence of instructions having exactly one entry point and + * one jump/call/exit at the end. + * Call candidate: RzAnalysisCallCandidate + * Candidate function entry points (CFEP): Addresses of possible function entry point. + * Return Addresses: Address after a call candidate BB, with an xref to it. + * Basic Block CFG: Control Flow Graph with basic blocks as nodes. + * + * IN: + * - Call candidates. + * - CFEPs + * - Return Addresses + * - Basic Block CFG (bcfg) + * + * ALGO: + * + * It simply iterates over all CFEPs. + * For each one it follows its edges in the bcfg. + * If an edge belongs to a call, it is NOT taken. + * + * Every walked edge and the basic blocks are added to the function. + * + * Tail calls are not modelled. + * + * OUT: + * - List of functions + * - Each functions starts with one CFEP and is a sub-graph in the bcfg. + */ + +#include "rz_analysis.h" +#include "rz_inquiry/rz_bcfg.h" +#include "rz_types_base.h" +#include "rz_util/ht_up.h" +#include "rz_util/rz_assert.h" +#include "rz_util/rz_graph.h" +#include "rz_util/rz_iterator.h" +#include "rz_util/rz_log.h" +#include "rz_util/rz_set.h" +#include "rz_vector.h" +#include +#include +#include + +static int cmp(const ut64 *a, const ut64 *b, void *user) { + if (*a > *b) { + return 1; + } else if (*a < *b) { + return -1; + } + return 0; +} + +static bool jumps_to_ignored_code(const RzVector *v, ut64 jump_target) { + void *it; + rz_vector_foreach (v, it) { + RzInterval *itv = it; + if (rz_itv_contain(*itv, jump_target)) { + return true; + } + } + return false; +} + +static void recurse_into_fcn_bbs( + RzInquiryFunction *fcn, + ut64 predecessor_bb_addr, + ut64 this_bb_addr, + RzInquiryBCFGEdgeType edge_type, + RzSetU *visited_fcn_bbs, + const RzVector /**/ *cfep_addresses, + const HtUP /**/ *call_candidates, + const RzInquiryBCFG *binary_bcfg, + RZ_NONNULL const RzVector /**/ *ignored_code) { + if (rz_set_u_contains(visited_fcn_bbs, this_bb_addr)) { + return; + } + rz_set_u_add(visited_fcn_bbs, this_bb_addr); + + // + // Add edge + // + RzInquiryBlock this_bb = { 0 }; + if (!rz_inquiry_bcfg_get_block(binary_bcfg, this_bb_addr, &this_bb)) { + rz_warn_if_reached(); + goto err_return; + } + if (!rz_inquiry_bcfg_add_block(fcn->bcfg, this_bb.addr, this_bb.size)) { + rz_warn_if_reached(); + goto err_return; + } + + if (edge_type != RZ_INQUIRY_BCFG_EDGE_TYPE_NONE) { + RzInquiryBlock from_bb = { 0 }; + if (!rz_inquiry_bcfg_get_block(binary_bcfg, predecessor_bb_addr, &from_bb)) { + rz_warn_if_reached(); + goto err_return; + } + if (!rz_inquiry_bcfg_add_block(fcn->bcfg, from_bb.addr, from_bb.size)) { + rz_warn_if_reached(); + goto err_return; + } + if (!rz_inquiry_bcfg_add_edge(fcn->bcfg, predecessor_bb_addr, this_bb_addr, edge_type)) { + rz_warn_if_reached(); + goto err_return; + } + } + + // + // Visit neighbors + // + RzIterator *successors = rz_inquiry_bcfg_get_outgoing_edges(binary_bcfg, this_bb_addr); + if (!successors) { + // Node has no successors. + goto err_return; + } + + const RzGraphEdge *e; + rz_iterator_foreach(successors, e) { + RzInquiryBCFGEdgeType etype = (RzInquiryBCFGEdgeType)(utptr)rz_graph_edge_get_data(e); + ut64 succ_addr = rz_graph_node_get_id(rz_graph_edge_get_to(e)); + switch (etype) { + case RZ_INQUIRY_BCFG_EDGE_TYPE_NONE: + case RZ_INQUIRY_BCFG_EDGE_TYPE_CF: + case RZ_INQUIRY_BCFG_EDGE_TYPE_JMP: + // Just an edge between two basic blocks. + break; + case RZ_INQUIRY_BCFG_EDGE_TYPE_CALL_RET: + // This is the next instruction packet after a call candidate. + // It might be an non-existing edge which was only added as a guess. + // In general all instructions after a call are added to the with + // an edge, to increase coverage. + // Even for call which never return (being a tail call or exit). + // + // The prototype doesn't handle tail calls. But to not loose too much + // precission we can check here if the NPC after the call (this_bb address), + // is a candidate function entry point. + // If so, we can assume that the NPC is in fact not a return point of a procedure. + // Hence, it shouldn't be added to the function CFG. + if (rz_vector_contains(cfep_addresses, &succ_addr)) { + continue; + } + break; + case RZ_INQUIRY_BCFG_EDGE_TYPE_CALL: + case RZ_INQUIRY_BCFG_EDGE_TYPE_RETURN: + continue; + } + if (jumps_to_ignored_code(ignored_code, succ_addr)) { + continue; + } + + recurse_into_fcn_bbs( + fcn, + this_bb_addr, + succ_addr, + etype, + visited_fcn_bbs, + cfep_addresses, + call_candidates, + binary_bcfg, + ignored_code); + } + rz_iterator_free(successors); + +err_return: + return; +} + +static void fill_candidate_fcn_entry_points( + const RzInquiryBCFG *binary_bcfg, + const HtUP /**/ *call_candidates, + RzVector *cfep_addresses) { + void **it; + RzIterator *iter = ht_up_as_iter(call_candidates); + rz_iterator_foreach(iter, it) { + RzAnalysisCallCandidate *cc = *it; + RzIterator *predecessor = rz_inquiry_bcfg_get_outgoing_edges(binary_bcfg, cc->bb_addr); + const RzGraphEdge *e; + rz_iterator_foreach(predecessor, e) { + RzInquiryBCFGEdgeType type = (RzInquiryBCFGEdgeType)(utptr)rz_graph_edge_get_data(e); + if (type == RZ_INQUIRY_BCFG_EDGE_TYPE_CALL_RET) { + // TODO: Remove this check + const RzGraphNode *nr = rz_graph_edge_get_to(e); + ut64 target = rz_graph_node_get_id(nr); + rz_warn_if_fail(target == cc->npc); + } else if (type == RZ_INQUIRY_BCFG_EDGE_TYPE_CALL) { + const RzGraphNode *nc = rz_graph_edge_get_to(e); + ut64 target = rz_graph_node_get_id(nc); + rz_vector_push(cfep_addresses, &target); + RZ_LOG_DEBUG("Add cfep at 0x%" PFMT64x " (call: 0x%" PFMT64x ") " + "based on BB 0x%" PFMT64x "\n", + cc->bb_addr, cc->candidate_addr, target); + } + } + rz_iterator_free(predecessor); + } + rz_iterator_free(iter); + rz_vector_sort(cfep_addresses, (RzVectorComparator)cmp, false, NULL); +} + +RZ_API bool rz_inquiry_algo_revng_fcn_detection( + RzSetU *symbol_addresses, + RZ_NONNULL const HtUP /**/ *call_candidates, + RZ_NONNULL const RzInquiryBCFG *binary_bcfg, + RZ_NONNULL RZ_OUT RzPVector /**/ *fcns, + RZ_NONNULL const RzVector /**/ *ignored_code) { + rz_return_val_if_fail(call_candidates && binary_bcfg && fcns, false); + + // Candidate function entry points + RzVector *cfep_addresses = rz_vector_new(sizeof(ut64), NULL, NULL); + RzIterator *iter = rz_set_u_as_iter(symbol_addresses); + ut64 *addr; + rz_iterator_foreach(iter, addr) { + rz_vector_push(cfep_addresses, addr); + } + rz_iterator_free(iter); + fill_candidate_fcn_entry_points(binary_bcfg, call_candidates, cfep_addresses); + + // Set of handled cfep + RzSetU *cfep_handled = rz_set_u_new(); + + ut64 *elem; + rz_vector_foreach (cfep_addresses, elem) { + ut64 cfep_addr = *elem; + if (rz_set_u_contains(cfep_handled, cfep_addr)) { + continue; + } + + RzSetU *visited_bbs = rz_set_u_new(); + RzInquiryFunction *fcn = rz_inquiry_function_new(); + rz_vector_push(fcn->entry_points, &cfep_addr); + recurse_into_fcn_bbs(fcn, + UT64_MAX, + cfep_addr, + RZ_INQUIRY_BCFG_EDGE_TYPE_NONE, + visited_bbs, + cfep_addresses, + call_candidates, + binary_bcfg, + ignored_code); + rz_set_u_free(visited_bbs); + rz_set_u_add(cfep_handled, cfep_addr); + rz_pvector_push(fcns, fcn); + } + + rz_set_u_free(cfep_handled); + rz_vector_free(cfep_addresses); + return true; +} diff --git a/librz/inquiry/bcfg.c b/librz/inquiry/bcfg.c new file mode 100644 index 00000000000..3be87c9b42d --- /dev/null +++ b/librz/inquiry/bcfg.c @@ -0,0 +1,349 @@ +// SPDX-FileCopyrightText: 2026 RizinOrg +// SPDX-License-Identifier: LGPL-3.0-only + +#include "rz_analysis.h" +#include "rz_types_base.h" +#include "rz_util/rz_assert.h" +#include "rz_util/rz_graph.h" +#include "rz_util/rz_iterator.h" +#include "rz_vector.h" +#include + +static ut64 hash_node(const void *data) { + const RzInquiryBlock *bb = data; + return bb->addr; +} + +static RZ_OWN char *node_formatter(const RzGraphNode *n) { + const RzInquiryBlock *bb = rz_graph_node_get_data(n); + return rz_str_newf("[label=\"0x%" PFMT64x ":%" PFMT64u "\"]", + bb->addr, bb->size); +} + +static RZ_OWN char *edge_formatter(const RzGraphEdge *e) { + RzInquiryBCFGEdgeType type = (utptr)rz_graph_edge_get_data(e); + switch (type) { + case RZ_INQUIRY_BCFG_EDGE_TYPE_NONE: + return rz_str_dup("[label=Unknown]"); + case RZ_INQUIRY_BCFG_EDGE_TYPE_JMP: + case RZ_INQUIRY_BCFG_EDGE_TYPE_CF: + return NULL; + case RZ_INQUIRY_BCFG_EDGE_TYPE_CALL_RET: + return rz_str_dup("[style=dotted label=npc]"); + case RZ_INQUIRY_BCFG_EDGE_TYPE_CALL: + return rz_str_dup("[style=dotted label=call]"); + case RZ_INQUIRY_BCFG_EDGE_TYPE_RETURN: + return rz_str_dup("[style=dotted label=ret]"); + } + rz_warn_if_reached(); + return NULL; +} + +RZ_IPI RZ_OWN char *rz_inquiry_bcfg_as_dot(const RzInquiryBCFG *bcfg, RZ_NULLABLE const char *name) { + rz_return_val_if_fail(bcfg, NULL); + return rz_graph_as_dot_str(bcfg->graph, name, node_formatter, edge_formatter); +} + +RZ_IPI RZ_OWN RzInquiryBCFG *rz_inquiry_bcfg_new(RzGraphImplType impl_type) { + RzInquiryBCFG *bcfg = RZ_NEW0(RzInquiryBCFG); + if (!bcfg) { + return NULL; + } + bcfg->graph = rz_graph_new(impl_type, hash_node, free, NULL); + if (!bcfg->graph) { + rz_inquiry_bcfg_free(bcfg); + return NULL; + } + return bcfg; +} + +RZ_IPI void rz_inquiry_bcfg_free(RZ_NULLABLE RZ_OWN RzInquiryBCFG *bcfg) { + if (!bcfg) { + return; + } + rz_graph_free(bcfg->graph); + free(bcfg); +} + +static bool edge_from(const RzGraphEdge *e, void *addr) { + ut64 from_addr = (utptr)addr; + return rz_graph_node_get_id(rz_graph_edge_get_from(e)) == from_addr; +} + +RZ_IPI bool rz_inquiry_bcfg_del_out_edges(RzInquiryBCFG *cfg, ut64 bb_addr) { + rz_return_val_if_fail(cfg, false); + return rz_graph_del_edges(cfg->graph, edge_from, RZ_GRAPH_INT_AS_DATA(bb_addr)) != RZ_GRAPH_STATUS_ERR; +} + +/** + * \brief Adds an edge to the block CFG. + * + * \param cfg The block CFG to edit. + * \param from_bb The address of the block with the branch. + * Not the address of the branch instruction! + * \param to_bb The address of the block the branch leads to. + * \param type The type of the edge. + * + * \return True if edge was added. False for error or if a node doesn't exist. + */ +RZ_IPI bool rz_inquiry_bcfg_add_edge(RzInquiryBCFG *cfg, ut64 from_bb, ut64 to_bb, RzInquiryBCFGEdgeType type) { + RzGraphStatus s = rz_graph_add_edge_by_id(cfg->graph, from_bb, to_bb, RZ_GRAPH_INT_AS_DATA(type)); + if (s == RZ_GRAPH_STATUS_OK || s == RZ_GRAPH_STATUS_EXISTED) { + return true; + } + return false; +} + +RZ_IPI bool rz_inquiry_bcfg_del_edge(RzInquiryBCFG *cfg, ut64 from_bb, ut64 to_bb) { + return rz_graph_del_edge_by_id(cfg->graph, from_bb, to_bb) != RZ_GRAPH_STATUS_ERR; +} + +static bool is_cf_edge(const RzGraphEdge *e, void *unused) { + RzInquiryBCFGEdgeType type = (utptr)rz_graph_edge_get_data(e); + return type == RZ_INQUIRY_BCFG_EDGE_TYPE_CF || type == RZ_INQUIRY_BCFG_EDGE_TYPE_NONE; +} + +/** + * \brief Updates or adds an edge to the block CFG. + * Only edges of type RZ_INQUIRY_BCFG_EDGE_TYPE_CF are updated to \p type. + * Otherwise the edge is not updated. + * + * \param cfg The block CFG to edit. + * \param from_bb The address of the block with the branch. + * Not the address of the branch instruction! + * \param to_bb The address of the block the branch leads to. + * \param type The type of the edge. + * + * \return True if edge was added. False in case of error. + */ +RZ_IPI bool rz_inquiry_bcfg_update_edge(RzInquiryBCFG *cfg, ut64 from_bb, ut64 to_bb, RzInquiryBCFGEdgeType type) { + return rz_graph_update_edge_by_id(cfg->graph, from_bb, to_bb, RZ_GRAPH_INT_AS_DATA(type), is_cf_edge, NULL) != RZ_GRAPH_STATUS_ERR; +} + +RZ_IPI bool rz_inquiry_bcfg_get_block(const RzInquiryBCFG *cfg, ut64 bb_addr, RZ_OUT RZ_NULLABLE RzInquiryBlock *bb) { + rz_return_val_if_fail(cfg, false); + const RzGraphNode *n = rz_graph_find_node(cfg->graph, bb_addr); + if (!n) { + RZ_LOG_WARN("Could not find BB at 0x%" PFMT64x "\n", bb_addr); + return false; + } + if (bb) { + const RzInquiryBlock *n_data = rz_graph_node_get_data(n); + bb->addr = n_data->addr; + bb->size = n_data->size; + } + return true; +} + +/** + * \brief Neighbors of outgoing edges. + */ +RZ_API RZ_OWN RzIterator /**/ *rz_inquiry_bcfg_get_outgoing_edges(const RzInquiryBCFG *cfg, ut64 bb_addr) { + rz_return_val_if_fail(cfg, NULL); + return rz_graph_out_edges_by_id(cfg->graph, bb_addr); +} + +/** + * \brief Neighbors of incoming edges. + */ +RZ_API RZ_OWN RzIterator /**/ *rz_inquiry_bcfg_get_incoming_edges(const RzInquiryBCFG *cfg, ut64 bb_addr) { + rz_return_val_if_fail(cfg, NULL); + return rz_graph_in_edges_by_id(cfg->graph, bb_addr); +} + +/** + * \brief Does not update the BB if it is already present. + * Returns false if it already exists. + */ +RZ_IPI bool rz_inquiry_bcfg_add_block(RzInquiryBCFG *cfg, ut64 addr, ut64 size) { + RzInquiryBlock *bb = RZ_NEW(RzInquiryBlock); + if (!bb) { + return false; + } + bb->addr = addr; + bb->size = size; + rz_graph_add_node(cfg->graph, bb, NULL); + // const RzInquiryBB *nbb = rz_graph_node_get_data(n); + // if (nbb->size != size) { + // rz_warn_if_reached(); + // } + return true; +} + +RZ_IPI bool rz_inquiry_bcfg_add_edge_xref(RzInquiryBCFG *cfg, const RzVector /**/ *xrefs) { + RzAnalysisXRef *xref; + rz_vector_foreach (xrefs, xref) { + switch (xref->type) { + case RZ_ANALYSIS_XREF_TYPE_CODE: + if (!rz_inquiry_bcfg_update_edge(cfg, xref->bb_addr, xref->to, RZ_INQUIRY_BCFG_EDGE_TYPE_JMP)) { + RZ_LOG_DEBUG("Did not add JMP edge: 0x%" PFMT64x " -> 0x%" PFMT64x "\n", xref->bb_addr, xref->to); + } + break; + case RZ_ANALYSIS_XREF_TYPE_CALL: + if (!rz_inquiry_bcfg_update_edge(cfg, xref->bb_addr, xref->to, RZ_INQUIRY_BCFG_EDGE_TYPE_CALL)) { + RZ_LOG_DEBUG("Did not add CALL edge: 0x%" PFMT64x " -> 0x%" PFMT64x "\n", xref->bb_addr, xref->to); + } + RzInquiryBlock bb = { 0 }; + if (!rz_inquiry_bcfg_get_block(cfg, xref->bb_addr, &bb)) { + rz_warn_if_reached(); + break; + } + ut64 ret_addr = bb.addr + bb.size; + if (!rz_inquiry_bcfg_update_edge(cfg, xref->bb_addr, ret_addr, RZ_INQUIRY_BCFG_EDGE_TYPE_CALL_RET)) { + RZ_LOG_DEBUG("Did not add CALL_RET edge: 0x%" PFMT64x " -> 0x%" PFMT64x "\n", xref->bb_addr, ret_addr); + } + break; + case RZ_ANALYSIS_XREF_TYPE_RETURN: + if (!rz_inquiry_bcfg_update_edge(cfg, xref->bb_addr, xref->to, RZ_INQUIRY_BCFG_EDGE_TYPE_RETURN)) { + RZ_LOG_DEBUG("Did not add RETURN edge: 0x%" PFMT64x " -> 0x%" PFMT64x "\n", xref->bb_addr, xref->to); + } + break; + case RZ_ANALYSIS_XREF_TYPE_NULL: + case RZ_ANALYSIS_XREF_TYPE_DATA: + case RZ_ANALYSIS_XREF_TYPE_STRING: + case RZ_ANALYSIS_XREF_TYPE_MEM_WRITE: + case RZ_ANALYSIS_XREF_TYPE_CALL_RET: + continue; + } + } + return true; +} + +static int cmp(const RzInquiryBlock *a, const RzInquiryBlock *b, void *user) { + if (a->addr < b->addr) { + return -1; + } else if (a->addr > b->addr) { + return 1; + } + return 0; +} + +/** + * \brief Reduces all blocks in the cfg to their minimum size. + * Removing duplicates and split overlapping blocks to basic blocks. + * + * This function makes each block just have a single entry point. + * So each becomes a basic block (one Entry/one Exit). + * + * Example: + * + * +- 0x1000 + * | + * | + * | +- 0x1058 + * | | + * | | +- 0x105c + * | | | + * | | | + * | | | + * +-+-+- 0x1080 + * | + * | JUMP + * v + * +-- 0x1084 + * | + * | + * | +- 0x108c + * | | + * | | + * +-+- 0x10a0 + * + * Gets converted to: + * + * +-- 0x1000 + * | + * +-- + * | CTRL FLOW + * v + * +-- 0x1058 + * | + * +-- + * | CTRL FLOW + * v + * +-- 0x105c + * | + * | + * | + * +-- 0x1080 + * | + * | JUMP + * v + * +-- 0x1084 + * | + * +-- + * | CTRL FLOW + * v + * +-- 0x108c + * | + * | + * +-- 0x10a0 + */ +RZ_IPI bool rz_inquiry_bcfg_reduce(RzInquiryBCFG *cfg) { + RzPVector blocks = { 0 }; + rz_pvector_init(&blocks, NULL); + rz_pvector_reserve(&blocks, rz_graph_get_n_nodes(cfg->graph)); + + RzGraphNode *n; + RzIterator *iter = rz_graph_get_nodes(cfg->graph); + rz_iterator_foreach(iter, n) { + RzInquiryBlock *b = rz_graph_node_get_data_mut(n); + rz_pvector_push(&blocks, b); // Cast because API is not constified. + } + rz_iterator_free(iter); + + // Sorting by starting address. + // Note that this means overlapping blocks (with the same end address) + // will lie sequentially in this vector. + rz_pvector_sort(&blocks, (RzPVectorComparator)cmp, NULL); + + RzVector outedges = { 0 }; + rz_vector_init(&outedges, sizeof(ut64), NULL, NULL); + // It is unlikely any node will have more than 8 outgoing edges. + rz_vector_reserve(&outedges, 8); + + size_t n_blocks = rz_pvector_len(&blocks); + for (size_t i = 0; n_blocks && i < n_blocks - 1; ++i) { + RzInquiryBlock *a = rz_pvector_at(&blocks, i); + + // Split of all blocks a overlaps with. + for (; i < n_blocks - 1; ++i) { + RzInquiryBlock *b = rz_pvector_at(&blocks, i + 1); + if ((a->addr + a->size) != (b->addr + b->size)) { + // End addresses don't match => b lies not within a. + break; + } + // b and a have the same end address => b lies within a. + // Shrink a. + a->size = b->addr - a->addr; + RzIterator *out_iter = rz_graph_out_edges_by_id(cfg->graph, a->addr); + if (!out_iter) { + // No edges to update. + // Check for blocks b overlaps with. + a = b; + continue; + } + + // Replace a's outgoing edges with a single edge to b, + RzGraphEdge *e; + rz_iterator_foreach(out_iter, e) { + const RzGraphNode *to_node = rz_graph_edge_get_to(e); + ut64 to = rz_graph_node_get_id(to_node); + rz_vector_push(&outedges, &to); + } + rz_iterator_free(out_iter); + + ut64 *to; + rz_vector_foreach (&outedges, to) { + rz_graph_del_edge_by_id(cfg->graph, a->addr, *to); + } + rz_vector_purge(&outedges); + rz_graph_add_edge_by_id(cfg->graph, a->addr, b->addr, RZ_GRAPH_INT_AS_DATA(RZ_INQUIRY_BCFG_EDGE_TYPE_CF)); + // Check for blocks b overlaps with. + a = b; + } + } + rz_vector_fini(&outedges); + rz_pvector_fini(&blocks); + + return true; +} diff --git a/librz/inquiry/il_cache.c b/librz/inquiry/il_cache.c new file mode 100644 index 00000000000..978810920a5 --- /dev/null +++ b/librz/inquiry/il_cache.c @@ -0,0 +1,177 @@ +// SPDX-FileCopyrightText: 2026 Rot127 +// SPDX-License-Identifier: LGPL-3.0-only + +#include "rz_analysis.h" +#include "rz_il/rz_il_opcodes.h" +#include "rz_types.h" +#include "rz_util/ht_up.h" +#include "rz_util/rz_assert.h" +#include "rz_util/rz_log.h" +#include "rz_util/rz_str.h" +#include "rz_vector.h" +#include + +RZ_API RZ_OWN char *rz_il_cache_block_str(RZ_NONNULL const RzILCacheBlock *block) { + rz_return_val_if_fail(block, NULL); + return rz_str_newf("[0x%" PFMT64x ", 0x%" PFMT64x ")", block->addr, block->addr + block->size); +} + +struct rz_il_cache_t { + RZ_BORROW RzAnalysis *analysis; + RZ_BORROW RzIO *io; + RzILCacheConfig config; ///< The cache configuration. + HtUP /**/ *cache; +}; + +static void rz_il_cache_insn_pkt_free(RZ_NULLABLE RZ_OWN RzILCacheInsnPkt *pkt) { + if (!pkt) { + return; + } + if (pkt->effect) { + rz_il_op_effect_free(pkt->effect); + } + free(pkt); +} + +static void rz_il_cache_block_free(RZ_NULLABLE RZ_OWN RzILCacheBlock *il_bb) { + if (!il_bb) { + return; + } + rz_pvector_free(il_bb->il_ops); + free(il_bb); +} + +RZ_OWN RzILCacheBlock *lift_il_block(const RzILCache *cache, ut64 addr) { + RzILCacheBlock *il_block = NULL; + RzAnalysisOp op = { 0 }; + rz_analysis_op_init(&op); + // Estimate a reasonable number of bytes to read. + int max_read_size = (rz_analysis_plugin_current(cache->analysis)->bits / 8) * 16; + ut8 *buf = RZ_NEWS0(ut8, max_read_size); + if (!max_read_size || !buf) { + rz_warn_if_reached(); + goto fail; + } + + il_block = RZ_NEW0(RzILCacheBlock); + if (!il_block) { + rz_warn_if_reached(); + goto fail; + } + il_block->il_ops = rz_pvector_new((RzPVectorFree)rz_il_cache_insn_pkt_free); + if (!il_block->il_ops) { + rz_warn_if_reached(); + goto fail; + } + il_block->addr = addr; + bool sparc_add_delayed_insn = false; + bool changes_cf = true; + do { + // Don't use rz_io_read_at_mapped() here. + // It fails if it reads beyond a mapped memory region. + // Although this is expected here. rz_io_nread_at() on the other hand just + // reads less bytes. + if (rz_io_nread_at(cache->io, addr, buf, max_read_size) == 0) { + goto fail; + } + if (rz_analysis_op(cache->analysis, &op, addr, buf, max_read_size, + RZ_ANALYSIS_OP_MASK_IL | RZ_ANALYSIS_OP_MASK_BASIC | RZ_ANALYSIS_OP_MASK_INSN_PKT) <= 0) { + RZ_LOG_DEBUG("Failed to decode IL op\n"); + goto fail; + } + bool lifted = true; + if (!op.il_op && cache->config & RZ_IL_CACHE_CONFIG_NOP_UNLIFTED) { + // Not lifted. Map to NOP + lifted = false; + op.il_op = rz_il_op_new_nop(); + } + RzILCacheInsnPkt *pkt = RZ_NEW0(RzILCacheInsnPkt); + pkt->effect = op.il_op; + // Take ownership of IL op pointer. + op.il_op = NULL; + pkt->insn_pkt_size = op.size; + il_block->size += op.size; + rz_pvector_push(il_block->il_ops, pkt); + + if (lifted) { + changes_cf = rz_analysis_op_changes_control_flow(&op); + } else { + changes_cf = false; + } + + addr += op.size; + rz_analysis_op_fini(&op); + rz_mem_memzero(buf, max_read_size); + if (sparc_add_delayed_insn) { + // Instruction was added, now the block is complete. + break; + } + if (changes_cf && RZ_STR_EQ(rz_analysis_plugin_current(cache->analysis)->arch, "sparc")) { + // We need to add the instruction after the branch. + // So one more iteration is needed. + sparc_add_delayed_insn = true; + changes_cf = false; + } + } while (!changes_cf); + + free(buf); + return il_block; + +fail: + free(buf); + rz_analysis_op_fini(&op); + rz_il_cache_block_free(il_block); + return NULL; +} + +RZ_API const RzILCacheBlock *rz_il_cache_lift_il_block(RzILCache *cache, ut64 addr) { + rz_return_val_if_fail(cache && rz_analysis_plugin_current(cache->analysis) && cache->io, NULL); + RzILCacheBlock *block = ht_up_find(cache->cache, addr, NULL); + if (block) { + char *bstr = rz_il_cache_block_str(block); + free(bstr); + return block; + } + block = lift_il_block(cache, addr); + if (!block) { + RZ_LOG_DEBUG("ILCache: Failed to lift block at 0x%" PFMT64x "\n", addr); + return NULL; + } + ht_up_insert(cache->cache, block->addr, block); + return block; +} + +RZ_API void rz_il_cache_free(RZ_OWN RZ_NULLABLE RzILCache *cache) { + if (!cache) { + return; + } + ht_up_free(cache->cache); + free(cache); +} + +RZ_API RZ_OWN RzILCache *rz_il_cache_new( + RZ_BORROW RZ_NONNULL RzAnalysis *analysis, + RZ_BORROW RZ_NONNULL RzIO *io, + RzILCacheConfig config) { + rz_return_val_if_fail(analysis && io, NULL); + RzILCache *cache = RZ_NEW0(RzILCache); + if (!cache) { + rz_warn_if_reached(); + return NULL; + } + cache->analysis = analysis; + cache->io = io; + + cache->cache = ht_up_new(NULL, (HtUPFreeValue)rz_il_cache_block_free); + if (!cache->cache) { + goto err; + } + + cache->config = config; + return cache; + +err: + rz_warn_if_reached(); + rz_il_cache_free(cache); + return NULL; +} diff --git a/librz/inquiry/inquiry.c b/librz/inquiry/inquiry.c new file mode 100644 index 00000000000..5d77d9bc4ed --- /dev/null +++ b/librz/inquiry/inquiry.c @@ -0,0 +1,341 @@ +// SPDX-FileCopyrightText: 2025 RizinOrg +// SPDX-License-Identifier: LGPL-3.0-only + +#include +#include +#include + +#include "rz_analysis.h" +#include "rz_bin.h" +#include "rz_inquiry/rz_bcfg.h" +#include "rz_types.h" +#include "rz_util/ht_pp.h" +#include "rz_util/ht_sp.h" +#include "rz_util/ht_up.h" +#include "rz_util/rz_graph.h" +#include "rz_util/rz_iterator.h" +#include "rz_util/rz_log.h" +#include "rz_util/rz_set.h" +#include "rz_util/rz_str.h" +#include "rz_vector.h" +#include +#include +#include +#include + +#if 0 +RZ_LIB_VERSION(rz_inquiry); + +static RzInquiryPlugin *inquiry_static_plugins[] = { RZ_INQUIRY_STATIC_PLUGINS }; + +RZ_API const size_t rz_inquiry_get_n_plugins() { + return RZ_ARRAY_SIZE(inquiry_static_plugins); +} + +RZ_API RZ_BORROW RzInquiryPlugin *rz_inquiry_get_plugin(size_t index) { + if (index >= RZ_ARRAY_SIZE(inquiry_static_plugins)) { + return NULL; + } + return inquiry_static_plugins[index]; +} +#endif + +RZ_API bool rz_inquiry_plugin_add(RZ_BORROW RZ_NONNULL RzInquiry *inquiry, RZ_OWN RZ_NONNULL RzInquiryPlugin *plugin) { + rz_return_val_if_fail(inquiry && plugin, false); + if (!plugin->value_abstraction) { + rz_warn_if_reached(); + return false; + } + + if (!ht_sp_insert(inquiry->plugins, plugin->name, plugin)) { + RZ_LOG_WARN("Plugin '%s' was already added.\n", plugin->name); + return true; + } + + void **p_data = RZ_NEW0(void *); + if (!ht_sp_insert(inquiry->plugins_data, plugin->name, p_data)) { + rz_warn_if_reached(); + return false; + } + return true; +} + +RZ_API bool rz_inquiry_plugin_del(RZ_BORROW RZ_NONNULL RzInquiry *inquiry, RZ_OWN RZ_NONNULL RzInquiryPlugin *plugin) { + rz_return_val_if_fail(inquiry && plugin, false); + + void **p_data = ht_sp_find(inquiry->plugins_data, plugin->name, NULL); +#if 0 + if (plugin->p_interpreter->fini) { + plugin->p_interpreter->fini(p_data ? *p_data : NULL); + } +#endif + free(p_data); + if (plugin->value_abstraction) { + return ht_sp_delete(inquiry->plugins, plugin->name); + } + rz_warn_if_reached(); + return false; +} + +RZ_API void rz_inquiry_function_free(RZ_NULLABLE RZ_OWN RzInquiryFunction *fcn) { + if (!fcn) { + return; + } + rz_inquiry_bcfg_free(fcn->bcfg); + rz_vector_free(fcn->entry_points); + free(fcn); +} + +RZ_IPI RZ_OWN RzInquiryFunction *rz_inquiry_function_new() { + RzInquiryFunction *fcn = RZ_NEW0(RzInquiryFunction); + if (!fcn) { + return NULL; + } + fcn->bcfg = rz_inquiry_bcfg_new(RZ_GRAPH_IMPL_LIST); + fcn->entry_points = rz_vector_new(sizeof(ut64), NULL, NULL); + if (!fcn->bcfg || !fcn->entry_points) { + rz_inquiry_function_free(fcn); + return NULL; + } + return fcn; +} + +RZ_API RZ_OWN char *rz_inquiry_function_str(const RzInquiryFunction *fcn) { + RzStrBuf *buf = rz_strbuf_new(""); + rz_strbuf_append(buf, "ifcn: ["); + ut64 *it; + size_t i = 0; + rz_vector_foreach (fcn->entry_points, it) { + rz_strbuf_appendf(buf, "%s0x%" PFMT64x, (i++ > 0 ? ", " : " "), *it); + } + rz_strbuf_append(buf, " ]\n"); + RzIterator *iter = rz_graph_get_nodes(fcn->bcfg->graph); + RzGraphNode *n; + rz_iterator_foreach(iter, n) { + const RzInquiryBlock *bb = rz_graph_node_get_data(n); + rz_strbuf_appendf(buf, "\t0x%" PFMT64x ":0x%" PFMT64x "\n", bb->addr, bb->size); + } + rz_iterator_free(iter); + return rz_strbuf_drain(buf); +} + +RZ_API RZ_OWN RzInquiry *rz_inquiry_new(void) { + RzInquiry *iq = RZ_NEW0(RzInquiry); + if (!iq) { + return NULL; + } + iq->plugins = ht_sp_new(HT_STR_CONST, NULL, NULL); + iq->plugins_data = ht_sp_new(HT_STR_CONST, NULL, NULL); + iq->call_candidates = ht_up_new(NULL, free); + iq->dynamic_xrefs = rz_vector_new(sizeof(RzAnalysisXRef), NULL, NULL); + iq->bcfg = rz_inquiry_bcfg_new(RZ_GRAPH_IMPL_LIST); + if (!iq->plugins || !iq->plugins_data || !iq->bcfg) { + ht_sp_free(iq->plugins); + ht_sp_free(iq->plugins_data); + rz_inquiry_bcfg_free(iq->bcfg); + free(iq); + return NULL; + } + +#if 0 + for (size_t i = 0; i < RZ_ARRAY_SIZE(inquiry_static_plugins); ++i) { + rz_inquiry_plugin_add(iq, inquiry_static_plugins[i]); + } +#endif + return iq; +} + +RZ_API void rz_inquiry_free(RZ_OWN RZ_NULLABLE RzInquiry *iq) { + if (!iq) { + return; + } +#if 0 + for (size_t i = 0; i < RZ_ARRAY_SIZE(inquiry_static_plugins); ++i) { + rz_inquiry_plugin_del(iq, inquiry_static_plugins[i]); + } +#endif + ht_sp_free(iq->plugins); + ht_sp_free(iq->plugins_data); + ht_up_free(iq->call_candidates); + rz_inquiry_bcfg_free(iq->bcfg); + rz_vector_free(iq->dynamic_xrefs); + free(iq); +} + +RZ_IPI void rz_inquiry_add_xref(RzInquiry *iq, const RzAnalysisXRef *xref) { + rz_vector_push(iq->dynamic_xrefs, (void *)xref); +} + +RZ_API bool rz_inquiry_xref_interpreter_filter(RZ_NONNULL const RzAnalysisXRef *xref, RZ_NONNULL const RzPVector /**/ *allowed_segments) { + rz_return_val_if_fail(xref && allowed_segments, false); + void **it; + rz_pvector_foreach (allowed_segments, it) { + const RzBinSection *sec = *it; + + ut64 start = sec->vaddr; + ut64 end = start + sec->vsize; + if (RZ_BETWEEN_EXCL(start, xref->to, end)) { + switch (xref->type) { + case RZ_ANALYSIS_XREF_TYPE_CALL_RET: + case RZ_ANALYSIS_XREF_TYPE_CALL: + case RZ_ANALYSIS_XREF_TYPE_CODE: + case RZ_ANALYSIS_XREF_TYPE_RETURN: + return sec->perm & RZ_PERM_X; + case RZ_ANALYSIS_XREF_TYPE_MEM_WRITE: + return sec->perm & RZ_PERM_W; + case RZ_ANALYSIS_XREF_TYPE_MEM_READ: + case RZ_ANALYSIS_XREF_TYPE_STRING: + return sec->perm & RZ_PERM_R; + case RZ_ANALYSIS_XREF_TYPE_NULL: + rz_warn_if_reached(); + return false; + } + } + } + return false; +} + +RZ_API bool rz_inquiry_get_fcn_symbol_addr(RzCore *core, RZ_OUT RzSetU *symbol_targets) { + rz_return_val_if_fail(core && symbol_targets, false); + RzPVector /**/ *sections = rz_bin_object_get_sections(core->bin->cur->o); + if (!sections) { + rz_warn_if_reached(); + return false; + } + // Add function addresses of function symbols in the executable region. + void **it; + const RzPVector *symbols = rz_bin_object_get_symbols(core->bin->cur->o); + if (!symbols) { + rz_pvector_free(sections); + rz_warn_if_reached(); + return false; + } + rz_pvector_foreach (symbols, it) { + RzBinSymbol *sym = *it; + if (!RZ_STR_EQ(sym->type, RZ_BIN_TYPE_FUNC_STR)) { + continue; + } + void **it2; + rz_pvector_foreach (sections, it2) { + RzBinSection *sec = *it2; + if (sec->perm & RZ_PERM_X && + RZ_BETWEEN_EXCL(sec->vaddr, sym->vaddr, sec->vaddr + sec->vsize)) { + rz_set_u_add(symbol_targets, sym->vaddr); + break; + } + } + } + rz_pvector_free(sections); + return true; +} + +RZ_API bool rz_inquiry_convert_and_add_to_analysis( + RzAnalysis *analysis, + RzInquiry *inquiry, + const RzPVector /**/ *fcns, + const RzPVector /**/ *symbols) { + rz_return_val_if_fail(analysis && inquiry && fcns && symbols, false); + // Add all discovered binary blocks to analysis + + RzIterator *iter = rz_graph_get_nodes(inquiry->bcfg->graph); + RzGraphNode *n; + rz_iterator_foreach(iter, n) { + const RzInquiryBlock *bb = rz_graph_node_get_data(n); + rz_analysis_add_bb(analysis, bb->addr, bb->size); + RzAnalysisBlock *abb = rz_analysis_get_block_at(analysis, bb->addr); + RzIterator *out_edges = rz_inquiry_bcfg_get_outgoing_edges(inquiry->bcfg, bb->addr); + if (!out_edges) { + continue; + } + RzGraphEdge *e; + rz_iterator_foreach(out_edges, e) { + ut64 target = rz_graph_node_get_id(rz_graph_edge_get_to(e)); + RzInquiryBCFGEdgeType type = (RzInquiryBCFGEdgeType)(utptr)rz_graph_edge_get_data(e); + switch (type) { + default: + continue; + case RZ_INQUIRY_BCFG_EDGE_TYPE_CALL_RET: + case RZ_INQUIRY_BCFG_EDGE_TYPE_CF: + case RZ_INQUIRY_BCFG_EDGE_TYPE_JMP: { + if (abb->jump == UT64_MAX && abb->fail != target) { + abb->jump = target; + } else if (abb->fail == UT64_MAX && abb->jump != target) { + abb->fail = target; + } else if (abb->fail != target && abb->jump != target) { + RZ_LOG_WARN("The basic block at 0x%" PFMT64x " has more than two outgoing edges.\n" + "\t\tHas jump = 0x%" PFMT64x " fail = 0x%" PFMT64x ". Will miss = 0x%" PFMT64x " (%d)\n", + bb->addr, abb->jump, abb->fail, + target, type); + } + break; + } + } + } + rz_iterator_free(out_edges); + } + rz_iterator_free(iter); + + RzAnalysisXRef *xref; + rz_vector_foreach (inquiry->dynamic_xrefs, xref) { + rz_analysis_xrefs_set(analysis, xref->from, xref->to, xref->type); + } + + // Convert the Inquiry functions to analysis function. + void **it; + rz_pvector_foreach (fcns, it) { + RzInquiryFunction *fcn = *it; + + ut64 fcn_addr = *(ut64 *)rz_vector_head(fcn->entry_points); + char new_fcn_name[64] = { 0 }; + void **it; + rz_pvector_foreach (symbols, it) { + RzBinSymbol *s = *it; + if (s->vaddr == fcn_addr && RZ_STR_EQ(s->type, RZ_BIN_TYPE_FUNC_STR)) { + rz_strf(new_fcn_name, "sym.%s", s->name); + break; + } + } + if (new_fcn_name[0] == '\0') { + rz_strf(new_fcn_name, "fcn_0x%" PFMT64x, fcn_addr); + } + RzAnalysisFunction *afcn = rz_analysis_create_function(analysis, new_fcn_name, fcn_addr, RZ_ANALYSIS_FCN_TYPE_FCN); + if (!afcn) { + rz_warn_if_reached(); + continue; + } + + RzIterator *iter = rz_graph_get_nodes(fcn->bcfg->graph); + RzGraphNode *n; + rz_iterator_foreach(iter, n) { + const RzInquiryBlock *bb = rz_graph_node_get_data(n); + RzAnalysisBlock *abb = rz_analysis_get_block_at(analysis, bb->addr); + if (!abb && !(abb = rz_analysis_create_block(analysis, bb->addr, bb->size))) { + rz_warn_if_reached(); + continue; + } + rz_analysis_function_add_block(afcn, abb); + } + rz_iterator_free(iter); + } + return true; +} + +RZ_API bool rz_inquiry_function_deduction( + RZ_NONNULL RZ_BORROW RzAnalysis *analysis, + RZ_NONNULL RZ_BORROW RzInquiry *inquiry, + RZ_NONNULL RZ_BORROW RzSetU *symbol_addresses, + RZ_NONNULL const RzPVector /**/ *symbols, + RZ_NONNULL const RzVector /**/ *ignored_code, + RZ_NONNULL RZ_OUT RzPVector /**/ *inquiry_fcns) { + rz_return_val_if_fail(analysis && inquiry && symbol_addresses && symbols && ignored_code && inquiry_fcns, false); + if (!rz_inquiry_algo_revng_fcn_detection( + symbol_addresses, + inquiry->call_candidates, + inquiry->bcfg, + inquiry_fcns, + ignored_code)) { + rz_warn_if_reached(); + return false; + } + return true; +} diff --git a/librz/inquiry/interp/driver.c b/librz/inquiry/interp/driver.c new file mode 100644 index 00000000000..3dbfd3879c3 --- /dev/null +++ b/librz/inquiry/interp/driver.c @@ -0,0 +1,351 @@ +// SPDX-FileCopyrightText: 2026 Florian Märkl +// SPDX-FileCopyrightText: 2025-2026 Rot127 +// SPDX-License-Identifier: LGPL-3.0-only + +/** + * \file Interpreter Driver + * + * Main loop spawning and serving interpreter threads, + * as well as integrating results as they are emitted. + */ + +#include +#include + +/** + * + * 1 queue for entry points main -> interps + * 1 request_rbuf for requests from interps -> main + * n_thread response_rbuf for responses main -> interp (rbuf here only works if for every request that causes a response, the requester always also waits for the response) + * + * break must close at least request_rbuf + * + */ + +typedef struct interp_thread InterpThread; + +/** + * \brief Message from an interpreter to the main loop + */ +typedef struct interp_driver_message_t { + enum { + DRIVER_MESSAGE_IO_READ, + DRIVER_MESSAGE_LIFT_BLOCK, + DRIVER_MESSAGE_INTERP_RESULT + } type; + union { + struct { + RzInterpIOReadRequest *request; + InterpThread *requester; + } io_read; + struct { + ut64 addr; + InterpThread *requester; + } lift_block; + RzInterpResult *interp_result; + } payload; +} InterpDriverMessage; + +#define DRIVER_MAIN_CH_SIZE 16 ///< TODO: optimize this to be small but blocking any interpreters under practical circumstances + +typedef struct interp_driver_t { + RzThreadQueue /**/ *entry_points_ch; ///< Main delivers entry points to multiple interpreters with this. TODO: linked list is not optimal, but rbuf may lead to starvation + RzThreadRingBuf *main_ch; ///< Channel to main. Multiple interpreters send info requests and analysis results with this. + RzInterpResultDimen dimens; +} InterpDriver; + +/** + * \brief Message from the main loop to an interpreter thread + * Currently corresponds to exactly one request sent on InterpDriver.main_ch + */ +typedef struct interp_thread_message_t { + enum { + INTERP_MESSAGE_IO_READ_RESULT, + INTERP_MESSAGE_LIFT_BLOCK_RESULT + } type; + union { + RzInterpIOReadResult io_read_result; + const RzILCacheBlock *lift_block_result; + } payload; +} InterpThreadMessage; + +struct interp_thread { + InterpDriver *driver; + RzThread *th; + RzInterpInstance *inst; + + /** + * \brief Channel to this thread. + * Main delivers responses to requests on InterpDriver.main_ch to this in the exact order they were requested. + */ + RzThreadRingBuf *ch; +} /* InterpThread */; + +static void *interp_th(void *user) { + InterpThread *ctx = user; + RzInterpInstance *inst = ctx->inst; + while (true) { + ut64 *entry_point; + if (!rz_th_queue_pop(ctx->driver->entry_points_ch, false, (void **)&entry_point)) { + // closed, no more entry points to interpret + break; + } + ut64 entry_point_val = *entry_point; + free(entry_point); + RzInterpResult *res = rz_interp_run(inst, entry_point_val, ctx->driver->dimens); + if (res) { + InterpDriverMessage msg = { + .type = DRIVER_MESSAGE_INTERP_RESULT, + .payload = { + .interp_result = res + } + }; + rz_th_ring_buf_put(ctx->driver->main_ch, &msg); + } else { + RZ_LOG_ERROR("Interpreter run failed for entry point 0x%" PFMT64x "\n", entry_point_val); + } + } + return NULL; +} + +static RzInterpIOReadResult send_io_read(RZ_NONNULL RzInterpIOReadRequest *req, void *user) { + InterpThread *th = user; + InterpDriverMessage msg = { + .type = DRIVER_MESSAGE_IO_READ, + .payload = { + .io_read = { + .request = req, + .requester = th + } + } + }; + if (rz_th_ring_buf_put(th->driver->main_ch, &msg) != RZ_THREAD_RING_BUF_OK) { + return RZ_INTERP_IO_READ_RESULT_BREAK; + } + InterpThreadMessage ret; + if (rz_th_ring_buf_take_blocking(th->ch, &ret) != RZ_THREAD_RING_BUF_OK) { + return RZ_INTERP_IO_READ_RESULT_BREAK; + } + rz_return_val_if_fail(ret.type != INTERP_MESSAGE_IO_READ_RESULT, RZ_INTERP_IO_READ_RESULT_BREAK); + return ret.payload.io_read_result; +} + +static RzInterpLiftBlockResult send_lift_il_block(ut64 addr, const RzILCacheBlock **block_out, void *user) { + InterpThread *th = user; + InterpDriverMessage msg = { + .type = DRIVER_MESSAGE_LIFT_BLOCK, + .payload = { + .lift_block = { + .addr = addr, + .requester = th + } + } + }; + if (rz_th_ring_buf_put(th->driver->main_ch, &msg) != RZ_THREAD_RING_BUF_OK) { + return RZ_INTERP_LIFT_BLOCK_RESULT_BREAK; + } + InterpThreadMessage ret; + if (rz_th_ring_buf_take_blocking(th->ch, &ret) != RZ_THREAD_RING_BUF_OK) { + return RZ_INTERP_LIFT_BLOCK_RESULT_BREAK; + } + rz_return_val_if_fail(ret.type == INTERP_MESSAGE_LIFT_BLOCK_RESULT, RZ_INTERP_LIFT_BLOCK_RESULT_BREAK); + if (ret.payload.lift_block_result) { + *block_out = ret.payload.lift_block_result; + return RZ_INTERP_LIFT_BLOCK_RESULT_OK; + } + return RZ_INTERP_LIFT_BLOCK_RESULT_FAILED; +} + +static InterpThread *interp_thread_new(RzAnalysis *analysis, InterpDriver *driver) { + InterpThread *ctx = RZ_NEW(InterpThread); + if (!ctx) { + return NULL; + } + ctx->driver = driver; + ctx->ch = rz_th_ring_buf_new(1, sizeof(InterpThreadMessage)); // At the moment, threads directly wait after a single request, so size 1 is enough + if (!ctx->ch) { + goto err_ctx; + } + RzInterpConfig interp_config = { + .val_domain = &rz_interp_value_domain_const, + .cb_user = ctx, + .io_read = send_io_read, + .lift_block = send_lift_il_block + }; + ctx->inst = rz_interp_instance_new(analysis, &interp_config); + if (!ctx->inst) { + goto err_ch; + } + ctx->th = rz_th_new(interp_th, ctx); + if (!ctx->th) { + goto err_inst; + } + return ctx; +err_inst: + rz_interp_instance_free(ctx->inst); +err_ch: + rz_th_ring_buf_free(ctx->ch); +err_ctx: + free(ctx); + return NULL; +} + +static void interp_thread_free(InterpThread *th) { + if (!th) { + return; + } + rz_th_wait(th->th); + rz_th_free(th->th); + rz_th_ring_buf_free(th->ch); + rz_interp_instance_free(th->inst); + free(th); +} + +static RzInterpIOReadResult handle_io_request(const RzAnalysisILContext *il_ctx, RzInterpIOReadRequest *io_req) { + RZ_LOG_DEBUG("inquiry: Received IO read request: mem:%" PFMTSZd " 0x%" PFMT64x "\n", + io_req->mem_idx, + rz_bv_to_ut64(io_req->addr)); + RzILMemIndex mem_idx = io_req->mem_idx; + if (mem_idx > rz_vector_len(&il_ctx->memory)) { + rz_warn_if_reached(); + return RZ_INTERP_IO_READ_RESULT_TOP; + } + if (rz_bv_len(io_req->addr) == 64 && rz_bv_msb(io_req->addr)) { + // TODO: remove this + RZ_LOG_ERROR("Due to the Unix seek() implementation, addresses with the " + "63 bit set can't be addresses.\n"); + return RZ_INTERP_IO_READ_RESULT_TOP; + } + RzAnalysisILMem *mem = rz_vector_index_ptr(&il_ctx->memory, mem_idx); + if (!mem->base_buf) { + return RZ_INTERP_IO_READ_RESULT_TOP; + } + // TODO: here only memory should be read that can be assumed to be constant! + bool ok = rz_il_loadw_into(mem->base_buf, io_req->ld_data, io_req->addr, io_req->n_bits, io_req->big_endian); + RZ_LOG_DEBUG("inquiry: Sent IO read result. Success = %s.\n", rz_str_bool(ok)); + return ok ? RZ_INTERP_IO_READ_RESULT_TOP : RZ_INTERP_IO_READ_RESULT_TOP; +} + +RZ_API bool rz_interp_driver_run(RzCore *core, RZ_OWN RzSetU *entry_points, RzInterpResultDimen dimens) { + bool return_code = false; + + bool user_sent_signal = false; + + RzILCache *il_cache = rz_il_cache_new(core->analysis, core->io, RZ_IL_CACHE_CONFIG_NOP_UNLIFTED); + if (!il_cache) { + goto err_none; + } + + InterpDriver driver = { + .dimens = dimens + }; + driver.entry_points_ch = rz_th_queue_new(RZ_THREAD_QUEUE_UNLIMITED, free); + if (!driver.entry_points_ch) { + goto err_il_cache; + } + driver.main_ch = rz_th_ring_buf_new(DRIVER_MAIN_CH_SIZE, sizeof(InterpDriverMessage)); + if (!driver.main_ch) { + goto err_entry_points_ch; + } + + // Push all root entries + size_t entries_pushed = 0; + RzIterator *it = rz_set_u_as_iter(entry_points); + ut64 *entry; + rz_iterator_foreach (it, entry) { + ut64 *tmp = RZ_NEW(ut64); + if (!tmp) { + goto err_main_ch; + } + *tmp = *entry; + if (!rz_th_queue_push(driver.entry_points_ch, tmp, true)) { + goto err_main_ch; + } + entries_pushed++; + } + + size_t n_threads = 1; + InterpThread **threads = RZ_NEWS0(InterpThread *, n_threads); + if (!threads) { + goto err_main_ch; + } + + // Initialize and spawn the interpreters. + for (size_t i = 0; i < n_threads; ++i) { + threads[i] = interp_thread_new(core->analysis, &driver); + if (!threads[i]) { + goto err_threads; + } + } + + // TODO: rz_cons_break_push + + // Serve the interpreters + size_t entries_finished = 0; + while (entries_finished < entries_pushed) { + InterpDriverMessage msg; + if (rz_th_ring_buf_take_blocking(driver.main_ch, &msg) != RZ_THREAD_RING_BUF_OK) { + // closed + break; + } + switch (msg.type) { + case DRIVER_MESSAGE_IO_READ: { + RzInterpIOReadResult res = handle_io_request(msg.payload.io_read.requester->inst->il_ctx, msg.payload.io_read.request); + InterpThreadMessage res_msg = { + .type = INTERP_MESSAGE_IO_READ_RESULT, + .payload = { + .io_read_result = res + } + }; + if (rz_th_ring_buf_put(msg.payload.io_read.requester->ch, &res_msg) != RZ_THREAD_RING_BUF_OK) { + // should not be closed + rz_warn_if_reached(); + } + break; + } + case DRIVER_MESSAGE_LIFT_BLOCK: { + const RzILCacheBlock *block = rz_il_cache_lift_il_block(il_cache, msg.payload.lift_block.addr); + InterpThreadMessage res_msg = { + .type = INTERP_MESSAGE_LIFT_BLOCK_RESULT, + .payload = { + .lift_block_result = block + } + }; + if (rz_th_ring_buf_put(msg.payload.lift_block.requester->ch, &res_msg) != RZ_THREAD_RING_BUF_OK) { + // should not be closed + rz_warn_if_reached(); + } + break; + } + case DRIVER_MESSAGE_INTERP_RESULT: { + RzInterpResult *res = msg.payload.interp_result; + if (!rz_interp_result_apply_to_analysis(res, core->analysis)) { + RZ_LOG_WARN("Failed to apply to analysis\n"); + } + entries_finished++; + break; + } + default: + rz_warn_if_reached(); + break; + } + } + + return_code = true; + +err_threads: + // Close channel to make interp threads stop. + rz_th_queue_close(driver.entry_points_ch); + for (size_t i = 0; i < n_threads; i++) { + interp_thread_free(threads[i]); + } + free(threads); +err_main_ch: + rz_th_ring_buf_free(driver.main_ch); +err_entry_points_ch: + rz_th_queue_free(driver.entry_points_ch); +err_il_cache: + rz_il_cache_free(il_cache); +err_none: + return return_code && !user_sent_signal; +} diff --git a/librz/inquiry/interp/interp_priv.h b/librz/inquiry/interp/interp_priv.h new file mode 100644 index 00000000000..4fff938f500 --- /dev/null +++ b/librz/inquiry/interp/interp_priv.h @@ -0,0 +1,32 @@ + +#ifndef RZ_INTERP_PRIV_H +#define RZ_INTERP_PRIV_H + +#include + +// These functions are actually internal, but need RZ_API to be called from unit tests +RZ_API bool rz_interp_run_context_init(RzInterpRunContext *ctx, RzInterpInstance *inst); +RZ_API void rz_interp_run_context_fini(RzInterpRunContext *ctx); +RZ_API RzInterpBlock *rz_interp_block_at(RzInterpRunContext *ctx, ut64 addr); +RZ_API void rz_interp_block_resolve_bounds(RzInterpRunContext *ctx, RzInterpBlock *interp_block, const RzILCacheBlock *il_block); + +static inline ut64 rz_interp_block_get_start(RzInterpBlock *block) { + return block->entry_state->pc; +} + +/** end is inclusive */ +static inline ut64 rz_interp_block_get_end(RzInterpBlock *block) { + return block->node->end; +} + +RZ_API void rz_interp_run_push(RZ_BORROW RZ_NONNULL RzInterpRunContext *ctx, RZ_BORROW RZ_NONNULL RzInterpAbstrState *as, bool is_fallthrough); + +RZ_API RZ_OWN RzInterpAbstrState *rz_interp_abstr_state_new( + RZ_NONNULL RzInterpInstance *inst); +RZ_API void rz_interp_abstr_state_free(RzInterpInstance *inst, RZ_OWN RZ_NULLABLE RzInterpAbstrState *state); +RZ_API void rz_interp_abstr_state_set_pc_const(RzInterpAbstrState *state, ut64 pc); +RZ_API RZ_OWN RzInterpAbstrState *rz_interp_abstr_state_clone(RZ_NONNULL RzInterpInstance *iset, const RzInterpAbstrState *state); +RZ_API bool rz_interp_abstr_state_as_str(RZ_NONNULL RzInterpInstance *inst, RZ_NONNULL const RzInterpAbstrState *state, RZ_NONNULL RZ_OUT RzStrBuf *sb); +RZ_API void rz_interp_abstr_state_as_str_short(RZ_NONNULL RzInterpInstance *inst, RZ_NONNULL const RzInterpAbstrState *astate, RZ_NONNULL RZ_OUT RzStrBuf *sb); + +#endif diff --git a/librz/inquiry/interp/interpreter.c b/librz/inquiry/interp/interpreter.c new file mode 100644 index 00000000000..761b83774f9 --- /dev/null +++ b/librz/inquiry/interp/interpreter.c @@ -0,0 +1,1561 @@ +// SPDX-FileCopyrightText: 2026 Florian Märkl +// SPDX-FileCopyrightText: 2025-2026 Rot127 +// SPDX-License-Identifier: LGPL-3.0-only + +/** + * \file The API implementation for all analysis interpreters. + */ + +#include "rz_analysis.h" +#include "rz_util/rz_assert.h" +#include "rz_util/rz_log.h" +#include +#include "interp_priv.h" +#include +#include +#include +#include + +static RzInterpValueAbstraction *val_domain(const RzInterpInstance *inst) { + return inst->config.val_domain; +} + +///////////////////////////////////////////////////////// +/** + * \name RzInterpAbstrState + * @{ + */ + +/** + * \brief Initializes an abstract state for specified abstract kinds. Optionally with a list of registers. + * The register name list should always be given if the architecture has some. + */ +RZ_API RZ_OWN RzInterpAbstrState *rz_interp_abstr_state_new( + RZ_NONNULL RzInterpInstance *inst) { + rz_return_val_if_fail(inst, NULL); + RzInterpAbstrState *state = RZ_NEW0(RzInterpAbstrState); + if (!state) { + return NULL; + } + state->pc_state = RZ_INTERP_PC_UNREACHABLE; + // Initialize the register file with uninitialized abstract values. + state->globals = ht_up_new(NULL, NULL); + for (size_t i = 0; i < inst->il_ctx->reg_binding->regs_count; i++) { + const char *rname = inst->il_ctx->reg_binding->regs[i].name; + RzInterpAbstrVal *aval = val_domain(inst)->val_new_top(); + if (!aval) { + rz_warn_if_reached(); + ht_up_free(state->globals); + free(state); + return NULL; + } + ut64 djb2_reg_hash = rz_str_djb2_hash(rname); + if (!ht_up_insert(state->globals, djb2_reg_hash, aval)) { + RZ_LOG_ERROR("Failed to add %s to the global variable map.", rname); + ht_up_free(state->globals); + free(state); + } + } + state->locals = ht_up_new(NULL, NULL); + state->lets = ht_up_new(NULL, NULL); + return state; +} + +static void var_set_free(RzInterpInstance *inst, HtUP *vars) { + if (!vars) { + return; + } + RzIterator *it = ht_up_as_iter(vars); + RzInterpAbstrVal **v; + rz_iterator_foreach(it, v) { + val_domain(inst)->val_free(*v); + } + rz_iterator_free(it); + ht_up_free(vars); +} + +RZ_API void rz_interp_abstr_state_free(RzInterpInstance *inst, RZ_OWN RZ_NULLABLE RzInterpAbstrState *state) { + if (!state) { + return; + } + var_set_free(inst, state->globals); + var_set_free(inst, state->locals); + var_set_free(inst, state->lets); + free(state); +} + +RZ_API void rz_interp_abstr_state_set_pc_const(RzInterpAbstrState *state, ut64 pc) { + rz_return_if_fail(state); + state->pc = pc; + state->pc_state = RZ_INTERP_PC_CONST; +} + +static bool reset_state(RzInterpInstance *inst, RZ_BORROW RzInterpAbstrState *state, ut64 entry_point) { + state->pc_state = RZ_INTERP_PC_CONST; + state->pc = entry_point; + + RzIterator *it = ht_up_as_iter_keys(state->globals); + ut64 *k; + rz_iterator_foreach(it, k) { + ut64 djb2_reg_name = *k; + RzInterpAbstrVal *av = ht_up_find(state->globals, djb2_reg_name, NULL); + if (av) { + val_domain(inst)->set_top(av); + } + } + rz_iterator_free(it); + return true; +} + +#define STR_TOP "⊤" +#define STR_BOTTOM "⊥" + +RZ_API bool rz_interp_abstr_state_as_str(RZ_NONNULL RzInterpInstance *inst, RZ_NONNULL const RzInterpAbstrState *state, RZ_NONNULL RZ_OUT RzStrBuf *sb) { + rz_return_val_if_fail(state && sb, false); + + rz_strbuf_append(sb, "Globals\n\n"); + rz_strbuf_append(sb, "\tpc = "); + if (state->pc_state == RZ_INTERP_PC_CONST) { + rz_strbuf_appendf(sb, "0x%" PFMT64x, state->pc); + } else { + rz_strbuf_append(sb, state->pc_state == RZ_INTERP_PC_ANY ? STR_TOP : STR_BOTTOM); + } + rz_strbuf_append(sb, "\n\n"); + + RzIterator *it = ht_up_as_iter_keys(state->globals); + ut64 *k; + rz_iterator_foreach(it, k) { + const char *gname = ht_up_find(inst->var_name_hashes, *k, NULL); + rz_strbuf_appendf(sb, "\t%s = ", gname); + RzInterpAbstrVal *av = ht_up_find(state->globals, *k, NULL); + val_domain(inst)->val_as_str(av, sb); + rz_strbuf_append(sb, "\n"); + } + rz_iterator_free(it); + return true; +} + +RZ_API void rz_interp_abstr_state_as_str_short(RZ_NONNULL RzInterpInstance *inst, RZ_NONNULL const RzInterpAbstrState *astate, RZ_NONNULL RZ_OUT RzStrBuf *sb) { + bool first = true; + RzIterator *it = ht_up_as_iter_keys(astate->globals); + ut64 *k; + bool all_top = true; + rz_iterator_foreach(it, k) { + ut64 djb2_reg_name = *k; + RzInterpAbstrVal *av = ht_up_find(astate->globals, djb2_reg_name, NULL); + if (!av || val_domain(inst)->is_top(av)) { + continue; + } + all_top = false; + if (!first) { + rz_strbuf_append(sb, ", "); + } + first = false; + const char *varname = ht_up_find(inst->var_name_hashes, djb2_reg_name, NULL); + rz_strbuf_appendf(sb, "%s = ", varname); + val_domain(inst)->val_as_str(av, sb); + } + if (all_top) { + rz_strbuf_append(sb, STR_TOP); + } +} + +static HtUP *var_set_clone(const RzInterpInstance *inst, HtUP *vars) { + HtUP *r = ht_up_new(NULL, NULL); + if (!r) { + return NULL; + } + RzIterator *it = ht_up_as_iter_keys(vars); + ut64 *key; + rz_iterator_foreach(it, key) { + RzInterpAbstrVal *val = val_domain(inst)->val_new_top(); + if (!val) { + break; + } + val_domain(inst)->copy(val, ht_up_find(vars, *key, NULL)); + ht_up_insert(r, *key, val); + } + return r; +} + +RZ_API RZ_OWN RzInterpAbstrState *rz_interp_abstr_state_clone(RZ_NONNULL RzInterpInstance *iset, const RzInterpAbstrState *state) { + RzInterpAbstrState *r = RZ_NEW0(RzInterpAbstrState); + if (!state) { + return NULL; + } + r->pc = state->pc; + r->pc_state = state->pc_state; + r->globals = var_set_clone(iset, state->globals); + r->locals = var_set_clone(iset, state->locals); + r->lets = var_set_clone(iset, state->lets); + return r; +} + +/** + * \brief Join (least upper bound) on var sets + * \return True if a was changed + */ +static bool join_vars(RzInterpInstance *inst, RZ_BORROW RZ_INOUT HtUP *a, RZ_BORROW RZ_IN HtUP *b) { + RzIterator *it = ht_up_as_iter_keys(a); + ut64 *k; + bool changed = false; + rz_iterator_foreach(it, k) { + RzInterpAbstrVal *av = ht_up_find(a, *k, NULL); + RzInterpAbstrVal *bv = ht_up_find(b, *k, NULL); + if (!av || !bv) { + continue; + } + if (val_domain(inst)->join(av, bv)) { + changed = true; + } + } + return changed; +} + +bool join_state(RzInterpInstance *inst, RZ_BORROW RZ_INOUT RzInterpAbstrState *a, RZ_BORROW RZ_IN const RzInterpAbstrState *b) { + bool global_change = join_vars(inst, a->globals, b->globals); + bool local_change = join_vars(inst, a->locals, b->locals); + // lets are not be relevant here since they are immutable within their scope + return global_change || local_change; +} + +/// @} + +// Our analysis loop works by first performing abstract interpretation until a fixpoint is reached, +// and only in a second pass collecting analysis information from it such as xrefs. +// That is because before the fixpoint, abstract states will not yet represent all possible concrete states. +// +// Other approaches are conceivable, such as doing analysis every time a block is evaluated and if +// it is evaluated again, throwing away the previous results, so we encapsulate the logic for when to +// do what in these functions: + +/** Whether during evaluation, analysis results should be collected */ +static inline bool interp_is_analyzing(RzInterpRunContext *ctx) { + return ctx->res != NULL; +} + +/** Whether during evaluation, new states may be discoveres */ +static inline bool interp_is_collecting_states(RzInterpRunContext *ctx) { + return ctx->res == NULL; +} + +static void interp_add_comment(RzInterpRunContext *ctx, ut64 addr, const char *cmt) { + // building the commment string passed to this function is expensive, so assert that it is only called + // when actually requested. + rz_return_if_fail(interp_is_analyzing(ctx) && (ctx->res_dimen & RZ_INTERP_RESULT_DIMEN_COMMENTS)); + RzStrBuf sb; + rz_strbuf_init(&sb); + char *existing = ht_up_find(ctx->res->comments, addr, NULL); + if (existing) { + rz_strbuf_appendf(&sb, "%s; ", existing); + } + rz_strbuf_append(&sb, cmt); + char *val = rz_strbuf_drain_nofree(&sb); + if (!ht_up_update(ctx->res->comments, addr, val)) { + free(val); + } +} + +///////////////////////////////////////////////////////// +/** + * \name Interpreter Blocks + * @{ + */ + +static RzInterpBlock *interp_block_new(RzInterpInstance *inst, RZ_BORROW RZ_NONNULL RzInterpAbstrState *entry_state) { + RzInterpBlock *block = RZ_NEW0(RzInterpBlock); + if (!block) { + return NULL; + } + block->entry_state = rz_interp_abstr_state_clone(inst, entry_state); + if (!block->entry_state) { + free(block); + return NULL; + } + rz_vector_init(&block->insn_offsets, sizeof(ut16), NULL, NULL); + rz_vector_init(&block->jump_targets, sizeof(ut64), NULL, NULL); + return block; +} + +static void interp_block_free(RzInterpInstance *inst, RzInterpBlock *block) { + if (!block) { + return; + } + rz_interp_abstr_state_free(inst, block->entry_state); + rz_vector_fini(&block->insn_offsets); + rz_vector_fini(&block->jump_targets); + free(block); +} + +static void interp_blocks_init(RzInterpRunContext *ctx) { + rz_interval_tree_init(&ctx->blocks, NULL); +} + +static void interp_blocks_free(RzInterpRunContext *ctx) { + RzIntervalTreeIter it; + RzInterpBlock *block; + rz_interval_tree_foreach(&ctx->blocks, it, block) { + interp_block_free(ctx->inst, block); + } + rz_interval_tree_fini(&ctx->blocks); +} + +static RzInterpBlock *interp_block_create(RzInterpRunContext *ctx, RZ_BORROW RZ_NONNULL RzInterpAbstrState *as) { + rz_return_val_if_fail(ctx && as && as->pc_state == RZ_INTERP_PC_CONST, NULL); + RzInterpBlock *block = interp_block_new(ctx->inst, as); + if (!block) { + return NULL; + } + RzIntervalNode *node = rz_interval_tree_insert(&ctx->blocks, block->entry_state->pc, block->entry_state->pc, block); + if (!node) { + rz_warn_if_reached(); + return NULL; + } + block->node = node; + return block; +} + +static void interp_block_add_non_fallthrough_target(RzInterpBlock *block, ut64 target) { + // linear search may be inefficient, but practically the number of targets is often small + if (rz_vector_contains(&block->jump_targets, &target)) { + return; + } + rz_vector_push(&block->jump_targets, &target); +} + +RZ_API RzInterpBlock *rz_interp_block_at(RzInterpRunContext *ctx, ut64 addr) { + return rz_interval_tree_at(&ctx->blocks, addr); +} + +/** Mark a block that its current entry_state has not been explored fully yet */ +static void interp_block_mark_uninterpreted(RzInterpRunContext *ctx, RzInterpBlock *block) { + if (block->uninterpreted) { + return; + } + block->uninterpreted = true; + rz_list_push(ctx->queue, block); +} + +typedef struct interp_block_with_op_at_ctx_t { + ut64 addr; + RzInterpBlock *found; + size_t *hit_op_idx; +} InterpBlockWithOpAtCtx; + +static int interp_block_with_op_at_cmp(const void *a, const void *b, void *user) { + const ut16 *av = a; + const ut16 *bv = b; + return (st32)*av - (st32)*bv; +} + +static bool interp_block_with_op_at_cb(RzIntervalNode *node, void *user) { + InterpBlockWithOpAtCtx *lctx = user; + RzInterpBlock *block = node->data; + ut16 off = (ut16)(lctx->addr - rz_interp_block_get_start(block)); + // insn_offsets does not contain the first instruction, which is just fine here since we are not looking for that + size_t hit_op_idx = rz_vector_find_sorted(&block->insn_offsets, &off, interp_block_with_op_at_cmp, NULL); + if (hit_op_idx != SZT_MAX) { + lctx->found = block; + *lctx->hit_op_idx = hit_op_idx + 1; // + 1 because insn_offsets omits the first + return false; + } + return true; +} + +static RzInterpBlock *interp_block_with_op_at(RzInterpRunContext *ctx, ut64 addr, size_t *hit_op_idx) { + InterpBlockWithOpAtCtx lctx = { + .addr = addr, + .found = NULL, + .hit_op_idx = hit_op_idx + }; + rz_interval_tree_all_in(&ctx->blocks, addr, true, interp_block_with_op_at_cb, &lctx); + return lctx.found; +} + +static int interp_block_addr_cmp(const void *incoming, const RBNode *in_tree, void *user) { + ut64 incoming_start = *(ut64 *)incoming; + ut64 other_start = container_of(in_tree, const RzIntervalNode, node)->start; + if (incoming_start < other_start) { + return -1; + } + if (incoming_start > other_start) { + return 1; + } + return 0; +} + +static void interp_block_resize(RzInterpRunContext *ctx, RzInterpBlock *block, ut64 new_end) { + // Warning: the resize operation may invalidate the node pointer! But in reality, it only does so + // if the start address has changed, so it is ok to leave the reference in interp_block->node as-is. + rz_interval_tree_resize(&ctx->blocks, block->node, rz_interp_block_get_start(block), new_end); +} + +/** + * Resize the block to cover the instructions, or until the following block + * and fill instruction offsets. + * It may also split another block if \p interp_block starts at one of its instruction addresses. + */ +RZ_API void rz_interp_block_resolve_bounds(RzInterpRunContext *ctx, RzInterpBlock *interp_block, const RzILCacheBlock *il_block) { + if (interp_block->bounds_resolved) { + return; + } + interp_block->bounds_resolved = true; + ut64 block_start = rz_interp_block_get_start(interp_block); + + // Blocks may overlap, but one block must not start at an instruction start of another. + // We have to consider two cases here, depending on the order in which blocks have been discovered. + + // Case A: Our block would start at an instruction start of another block that starts before us and falls through. + // We can move the instruction information from the preceding block in that case. This way, case B is already handled as well. + size_t hit_op_idx = 0; + RzInterpBlock *preceding = interp_block_with_op_at(ctx, rz_interp_block_get_start(interp_block), &hit_op_idx); + if (preceding) { + size_t total_ops_count = rz_vector_len(&preceding->insn_offsets) + 1; + size_t our_ops_count = total_ops_count - hit_op_idx; + rz_vector_reserve(&interp_block->insn_offsets, our_ops_count - 1); + for (size_t i = hit_op_idx + 1; i < total_ops_count; i++) { + ut64 addr = *(ut16 *)rz_vector_index_ptr(&preceding->insn_offsets, i - 1); + addr += rz_interp_block_get_start(preceding); + addr -= block_start; + ut16 off = (ut16)addr; + rz_vector_push(&interp_block->insn_offsets, &off); + } + rz_vector_remove_range(&preceding->insn_offsets, hit_op_idx - 1, rz_vector_len(&preceding->insn_offsets) - (hit_op_idx -1), NULL); + rz_vector_shrink(&preceding->insn_offsets); + interp_block_resize(ctx, interp_block, rz_interp_block_get_end(preceding)); + interp_block_resize(ctx, preceding, block_start - 1); + preceding->fallthrough = true; + rz_vector_fini(&interp_block->jump_targets); + memmove(&interp_block->jump_targets, &preceding->jump_targets, sizeof(interp_block->jump_targets)); + rz_vector_init(&preceding->jump_targets, interp_block->jump_targets.elem_size, interp_block->jump_targets.free, interp_block->jump_targets.free_user); + + // The state reachable from the preceding block reaching our start address must be joined into our block's entry state. + // Hint: For performance, it would actually be better to reinterpret the preceding block before our block, otherwise + // ours will likely be interperted twice. + interp_block_mark_uninterpreted(ctx, preceding); + return; + } + + // Case B: Our block will fall through until one instruction start hits exactly another existing block. + // So we close our block once any of our instructions hit exactly the start of another block. + // (There is also the case where two non-start instructions hit, but we ignore this for now since results will + // still be correct) + // The next candidate for hitting is always the first whose address is greater than or equal to the instruction + // address (excluding the block start itself, since that would find our own block) so we search forward + // from a lower bound. + ut64 search_next_addr = interp_block->node->start + 1; + RBIter next_it = rz_rbtree_lower_bound_forward(&ctx->blocks.root->node, &search_next_addr, interp_block_addr_cmp, NULL); + + size_t insns_count = rz_pvector_len(il_block->il_ops); + rz_return_if_fail(insns_count > 0); + // interp_block->instruction_offsets is assumed to be empty here + ut64 cur = block_start; + rz_vector_reserve(&interp_block->insn_offsets, insns_count); + for (size_t i = 0; i < rz_pvector_len(il_block->il_ops); i++) { + RzILCacheInsnPkt *insn = rz_pvector_at(il_block->il_ops, i); + if (i > 0) { + // Close block if hitting another block's start address + while (rz_rbtree_iter_has(&next_it)) { + RzIntervalNode *next_node = rz_interval_tree_iter_get(&next_it); + if (next_node->start > cur) { + break; + } + if (next_node->start == cur) { + // hit found, the block will not include this instruction anymore. + goto close; + } + } + + ut16 off = cur - block_start; + rz_vector_push(&interp_block->insn_offsets, &off); + } + cur += insn->insn_pkt_size; + } +close: + interp_block_resize(ctx, interp_block, cur - 1); +} + +/* + * \brief Register a newly discovered state + * + * This will join the state with the already known one at the same pc and add it to the + * queue for further interpretation if there were changes. + */ +RZ_API void rz_interp_run_push(RZ_BORROW RZ_NONNULL RzInterpRunContext *ctx, RZ_BORROW RZ_NONNULL RzInterpAbstrState *as, bool is_fallthrough) { + rz_return_if_fail(interp_is_collecting_states(ctx)); + if (as->pc_state == RZ_INTERP_PC_ANY) { + RZ_LOG_DEBUG("Encountered state with unknown/top pc\n"); + return; + } + if (as->pc_state != RZ_INTERP_PC_CONST) { + rz_warn_if_reached(); + return; + } + RzStrBuf sb; + rz_strbuf_init(&sb); + rz_interp_abstr_state_as_str_short(ctx->inst, as, &sb); + RZ_LOG_DEBUG("PUSH 0x%" PFMT64x ": %s\n", as->pc, rz_strbuf_get(&sb)); + rz_strbuf_fini(&sb); + RzInterpBlock *block = rz_interp_block_at(ctx, as->pc); + if (block) { + if (join_state(ctx->inst, block->entry_state, as)) { + interp_block_mark_uninterpreted(ctx, block); + } + } else { + block = interp_block_create(ctx, as); + if (!block) { + return; + } + interp_block_mark_uninterpreted(ctx, block); + } + if (!is_fallthrough) { + block->non_fallthrough_in = true; + } +} + +static RzInterpBlock *rz_interp_run_pop(RZ_BORROW RZ_NONNULL RzInterpRunContext *ctx) { + RzInterpBlock *r = rz_list_pop(ctx->queue); + if (!r) { + return NULL; + } + r->uninterpreted = false; + return r; +} + +/// @} + +///////////////////////////////////////////////////////// + +RZ_API RZ_OWN RzInterpInstance *rz_interp_instance_new(RzAnalysis *analysis, RZ_NONNULL RZ_BORROW const RzInterpConfig *config) { + rz_return_val_if_fail(analysis && config && config->val_domain && config->io_read && config->lift_block, NULL); + + RzInterpInstance *inst = RZ_NEW0(RzInterpInstance); + if (!inst) { + return NULL; + } + inst->config = *config; + + const RzAnalysisPlugin *cur = rz_analysis_plugin_current(analysis); + if (!cur || !cur->arch) { + goto err_inst; + } + inst->arch_name = cur->arch; + + RzAnalysisILContext *il_ctx = rz_analysis_il_context_resolve(analysis); + if (!il_ctx) { + RZ_LOG_ERROR("Failed to create analysis IL context.\n"); + goto err_inst; + } + + inst->il_ctx = il_ctx; + + inst->var_name_hashes = ht_up_new(NULL, free); + if (!inst->var_name_hashes) { + goto err_il_ctx; + } + for (size_t i = 0; i < il_ctx->reg_binding->regs_count; i++) { + const char *rname = il_ctx->reg_binding->regs[i].name; + ut64 djb2_reg_hash = rz_str_djb2_hash(rname); + if (!ht_up_insert(inst->var_name_hashes, djb2_reg_hash, rz_str_dup(rname))) { + RZ_LOG_ERROR("DJB2 hash collision of the register name %s. DJB2 hash = 0x%" PFMT64x "\n", + rname, djb2_reg_hash); + goto err_var_name_hashes; + } + } + + return inst; +err_var_name_hashes: + ht_up_free(inst->var_name_hashes); +err_il_ctx: + rz_analysis_il_context_free(il_ctx); +err_inst: + free(inst); + return NULL; +} + +RZ_API void rz_interp_instance_free(RZ_OWN RZ_NULLABLE RzInterpInstance *inst) { + if (!inst) { + return; + } + ht_up_free(inst->var_name_hashes); + rz_analysis_il_context_free(inst->il_ctx); + free(inst); +} + +static void report_yield_xref( + RzInterpRunContext *ctx, + size_t insn_pkt_size, + ut64 from, + const RzInterpAbstrVal *to, + RzAnalysisXRefType type) { + if (!interp_is_analyzing(ctx) || !(ctx->res_dimen & RZ_INTERP_RESULT_DIMEN_XREFS)) { + return; + } + RzBitVector to_bv; + rz_bv_init(&to_bv, 64); + if (!val_domain(ctx->inst)->to_concrete_const(to, &to_bv) || rz_bv_len(&to_bv) > 64) { + // Isn't reported + // TODO: we might also want to report multiple values here depending on the value domain + goto cleanup; + } + if (type == RZ_ANALYSIS_XREF_TYPE_CODE && + RZ_STR_EQ(ctx->inst->arch_name, "hexagon") && + from + insn_pkt_size == rz_bv_to_ut64(&to_bv)) { + // Ugly work around. + // Because we don't have RzArch yet the Hexagon plugin adds a JUMP at the + // end of each and every instruction packet. + // This is necessary because the RzIL VM would otherwise just add 4 to the PC, + // which is too little for a packet with 2+ instructions. + // We don't want to report the code references to the next instruction + // packet. So skip them here. + goto cleanup; + } + + ut64 to_addr = rz_bv_to_ut64(&to_bv); + RzAnalysisXRef *xref = rz_vector_push(&ctx->res->xrefs, NULL); + if (!xref) { + goto cleanup; + } + xref->bb_addr = 0; // TODO? ctx->astate->bb_addr; + xref->from = from; + xref->to = to_addr; + xref->type = type; +cleanup: + rz_bv_fini(&to_bv); +} + +/** + * \brief Report the store of the next PC and report it as possible return point. + */ +static bool report_yield_call_candiate( + RzInterpRunContext *ctx) { + + // TODO +#if 0 + RzAnalysisCallCandidate cc = { 0 }; + // TODO? put the bb addr into the call candidate? Currently we do not know it. + memcpy(&cc, &ctx->call_cand, sizeof(ctx->call_cand)); + if (rz_th_ring_buf_put(cc_rbuf->rbuf, &cc) != RZ_THREAD_RING_BUF_OK) { + return false; + } +#endif + return true; +} + +void write_var_to_state(RzInterpInstance *inst, + RzInterpAbstrState *astate, + RzILVarKind kind, + ut64 var_id, + const RzInterpAbstrVal *data) { + HtUP *ht_vals; + switch (kind) { + default: + rz_warn_if_reached(); + return; + case RZ_IL_VAR_KIND_GLOBAL: + ht_vals = astate->globals; + break; + case RZ_IL_VAR_KIND_LOCAL: + ht_vals = astate->locals; + break; + case RZ_IL_VAR_KIND_LOCAL_PURE: + ht_vals = astate->lets; + break; + } + RzInterpAbstrVal *av = ht_up_find(ht_vals, var_id, NULL); + if (!av) { + if (kind == RZ_IL_VAR_KIND_GLOBAL) { + RZ_LOG_WARN("New global variable created: 0x%" PFMT64x "\n", var_id) + return; + } + av = val_domain(inst)->val_new_top(); + if (!av) { + rz_warn_if_reached(); + return; + } + ht_up_insert(ht_vals, var_id, av); + } + val_domain(inst)->copy(av, data); +} + +bool read_var_from_state(RzInterpInstance *inst, + RzInterpAbstrState *astate, + RzILVarKind kind, + ut64 var_id, + RZ_OUT RzInterpAbstrVal *data) { + HtUP *ht_vals; + switch (kind) { + default: + rz_warn_if_reached(); + return false; + case RZ_IL_VAR_KIND_GLOBAL: + ht_vals = astate->globals; + break; + case RZ_IL_VAR_KIND_LOCAL: + ht_vals = astate->locals; + break; + case RZ_IL_VAR_KIND_LOCAL_PURE: + ht_vals = astate->lets; + break; + } + RzInterpAbstrVal *av = ht_up_find(ht_vals, var_id, NULL); + if (!av) { + // Variable doesn't exist. + // This should never happen and is a bug. + rz_warn_if_reached(); + return false; + } + val_domain(inst)->copy(data, av); + return true; +} + +static bool store_abstr_data( + RzInterpInstance *iset, + RzILMemIndex mem_idx, + const RzInterpAbstrVal *addr, + const RzInterpAbstrVal *src) { + // TODO: handle with memory abstractions + return true; +} + +bool load_abstr_data( + RzInterpInstance *inst, + RzILMemIndex mem_idx, + const RzBitVector *addr, + size_t n_bits, + RZ_OUT RzInterpAbstrVal *out) { + RzInterpIOReadRequest io_req = { 0 }; + + RzBitVector out_bv; + rz_bv_init(&out_bv, n_bits); + + io_req.addr = addr; + io_req.ld_data = &out_bv; + io_req.mem_idx = mem_idx; + io_req.n_bits = n_bits; + io_req.big_endian = inst->il_ctx->config->big_endian; + RzInterpIOReadResult read_res = inst->config.io_read(&io_req, inst->config.cb_user); + if (read_res == RZ_INTERP_IO_READ_RESULT_BREAK) { + // TODO: break + } + if (read_res != RZ_INTERP_IO_READ_RESULT_OK) { + val_domain(inst)->set_top(out); + return true; + } + val_domain(inst)->set_const_bv(out, &out_bv); + + char *bytes = rz_bv_as_hex_string(&out_bv, true); + RZ_LOG_DEBUG("prototype: READ @ mem:%" PFMT32d " 0x%" PFMT64x " : %s\n", mem_idx, rz_bv_to_ut64(io_req.addr), bytes); + free(bytes); + return true; +} + +static bool set_abstr_pc(RzInterpInstance *inst, RzInterpAbstrState *state, RzInterpAbstrVal *pc) { + rz_return_val_if_fail(state && pc, false); + RzBitVector pc_bv; + rz_bv_init(&pc_bv, 64); + if (val_domain(inst)->to_concrete_const(pc, &pc_bv)) { + state->pc_state = RZ_INTERP_PC_CONST; + state->pc = rz_bv_to_ut64(&pc_bv); + } else { + state->pc_state = RZ_INTERP_PC_ANY; + } + rz_bv_fini(&pc_bv); + RZ_LOG_DEBUG("prototype: set_abstr_pc() - Set PC: 0x%" PFMT64x " (%s)\n", + state->pc, state->pc_state == RZ_INTERP_PC_CONST ? "Constant" : "Top"); + return true; +} + +static bool value_indicates_ret_addr_write(RzInterpRunContext *ctx, RzInterpAbstrVal *val) { + RzBitVector bv; + rz_bv_init(&bv, 64); + // Hint: pc addrs coming from the lifters are currently just opaque bitvectors. + // So we do not know whether the constant contents of val are actually taken from the architecture's + // pc register or match the instruction address by chance only. + // We could add a flag to RzILOpArgsBV that would be set by lifters to indicate that the constant value + // originates from the pc and use that here. + // This may also help with the sparc workaround. + // The downside is that a pattern like this in non-relocatable code would not be detected as call: + // 0x42 mov lr, 0x4a + // 0x46 mov pc, r0 + // 0x4a ... + // but it is questionable whether that should be even detected at all, since there is no way to know + // if it is intended as call or jump. + bool ret = val_domain(ctx->inst)->to_concrete_const(val, &bv) && + (rz_bv_to_ut64(&bv) == ctx->il_block_end || + // Sparc stores the call instruction PC into o8. + // The return instruction jumps then to o7+8. + (rz_str_startswith(ctx->inst->arch_name, "sparc") && rz_bv_to_ut64(&bv) == ctx->astate->pc)); + rz_bv_fini(&bv); + return ret; +} + +static bool eval_pure(RzInterpRunContext *ctx, const RzILOpPure *pure, RZ_OUT RzInterpAbstrVal *out) { + switch (pure->code) { + default: + case RZ_IL_OP_VAR: { + if (!read_var_from_state(ctx->inst, ctx->astate, pure->op.var.kind, pure->op.var.hash, out)) { + RZ_LOG_ERROR("prototype: VAR failed to evaluate. The %s '%s' doesn't exist.\n", + rz_il_var_kind_name(pure->op.var.kind), + pure->op.var.v); + return false; + } + break; + } + case RZ_IL_OP_LET: { + ut64 vhash = pure->op.let.hash; + if (!eval_pure(ctx, pure->op.let.exp, out)) { + RZ_LOG_ERROR("prototype: LET expression failed to evaluate.\n"); + return false; + } + write_var_to_state(ctx->inst, ctx->astate, RZ_IL_VAR_KIND_LOCAL_PURE, vhash, out); + // Evaluate body + if (!eval_pure(ctx, pure->op.let.body, out)) { + RZ_LOG_ERROR("prototype: LET body failed to evaluate.\n"); + return false; + } + // No need to free the LET variable. + // It is simply overwritten next time. + break; + } + case RZ_IL_OP_ITE: { + if (!eval_pure(ctx, pure->op.ite.condition, out)) { + RZ_LOG_ERROR("prototype: ITE condition failed to evaluate.\n"); + return false; + } + + RzBitVector cond_bv; + rz_bv_init(&cond_bv, 64); + if (!val_domain(ctx->inst)->to_concrete_const(out, &cond_bv)) { + // Can't decide which pure to evaluate. + rz_bv_fini(&cond_bv); + goto map_to_top; + } + bool cond_bool = !rz_bv_is_zero_vector(&cond_bv); + + // TODO: eval both if top + if (cond_bool) { + if (!eval_pure(ctx, pure->op.ite.x, out)) { + RZ_LOG_ERROR("prototype: ITE x failed to evaluate.\n"); + return false; + } + } else { + if (!eval_pure(ctx, pure->op.ite.y, out)) { + RZ_LOG_ERROR("prototype: ITE y failed to evaluate.\n"); + return false; + } + } + break; + } + case RZ_IL_OP_B0: + val_domain(ctx->inst)->set_const_bool(out, false); + break; + case RZ_IL_OP_B1: + val_domain(ctx->inst)->set_const_bool(out, false); + break; + case RZ_IL_OP_CAST: { + if (!eval_pure(ctx, pure->op.cast.val, out)) { + RZ_LOG_ERROR("prototype: CAST val failed to evaluate.\n"); + return false; + } + RzInterpAbstrVal *fill_bit = val_domain(ctx->inst)->val_new_top(); + if (!fill_bit) { + return false; + } + if (!eval_pure(ctx, pure->op.cast.fill, fill_bit)) { + RZ_LOG_ERROR("prototype: CAST fill failed to evaluate.\n"); + return false; + } + val_domain(ctx->inst)->eval_cast(pure->op.cast.length, fill_bit, out); + val_domain(ctx->inst)->val_free(fill_bit); + break; + } + case RZ_IL_OP_BITV: + val_domain(ctx->inst)->set_const_bv(out, pure->op.bitv.value); + break; + case RZ_IL_OP_APPEND: + case RZ_IL_OP_LOGAND: + case RZ_IL_OP_AND: + case RZ_IL_OP_LOGOR: + case RZ_IL_OP_OR: + case RZ_IL_OP_LOGXOR: + case RZ_IL_OP_XOR: + case RZ_IL_OP_ADD: + case RZ_IL_OP_SUB: + case RZ_IL_OP_SLE: + case RZ_IL_OP_ULE: + case RZ_IL_OP_EQ: + case RZ_IL_OP_MUL: + case RZ_IL_OP_MOD: + case RZ_IL_OP_DIV: { + RzILOpPure *px; + RzILOpPure *py; + if (pure->code == RZ_IL_OP_APPEND) { + // we use low as the x/out value because in the case of constant operands, + // appending high bits to a bitvector is more efficient than prepending + // low bits in place. + px = pure->op.append.low; + py = pure->op.append.high; + } else { + px = pure->op.binop.x; + py = pure->op.binop.y; + } + if (!eval_pure(ctx, px, out)) { + RZ_LOG_ERROR("prototype: binop x failed to evaluate.\n"); + return false; + } + // Hint: As an optimization, we could short-circuit if out is top here. + // However it entirely depends on the plugin whether this is possible, or we lose a lot of precision by doing so. + RzInterpAbstrVal *y = val_domain(ctx->inst)->val_new_top(); + if (!y) { + return false; + } + if (!eval_pure(ctx, py, y)) { + RZ_LOG_ERROR("prototype: binop y failed to evaluate.\n"); + return false; + } + val_domain(ctx->inst)->eval_binop(pure->code, out, y); + val_domain(ctx->inst)->val_free(y); + break; + } + case RZ_IL_OP_LOGNOT: + case RZ_IL_OP_INV: + case RZ_IL_OP_IS_ZERO: + case RZ_IL_OP_LSB: + case RZ_IL_OP_MSB: + case RZ_IL_OP_NEG: { + RzILOpPure *x = pure->op.unop.x; + if (!eval_pure(ctx, x, out)) { + RZ_LOG_ERROR("prototype: unop x failed to evaluate.\n"); + return false; + } + val_domain(ctx->inst)->eval_unop(pure->code, out); + break; + } + case RZ_IL_OP_SHIFTL: + case RZ_IL_OP_SHIFTR: { + RzILOpPure *px = pure->code == RZ_IL_OP_SHIFTR ? pure->op.shiftr.x : pure->op.shiftl.x; + RzILOpPure *py = pure->code == RZ_IL_OP_SHIFTR ? pure->op.shiftr.y : pure->op.shiftl.y; + RzILOpPure *pfill_bit = pure->code == RZ_IL_OP_SHIFTR ? pure->op.shiftr.fill_bit : pure->op.shiftl.fill_bit; + if (!eval_pure(ctx, px, out)) { + RZ_LOG_ERROR("prototype: SHIFT(L/R) x failed to evaluate.\n"); + return false; + } + // Hint: As an optimization, we could short-circuit if out is top here. + // However it entirely depends on the plugin whether this is possible, or we lose a lot of precision by doing so. + RzInterpAbstrVal *y = val_domain(ctx->inst)->val_new_top(); + if (!y) { + return false; + } + if (!eval_pure(ctx, py, y)) { + RZ_LOG_ERROR("prototype: SHIFT(L/R) y failed to evaluate.\n"); + return false; + } + RzInterpAbstrVal *fill_bit = val_domain(ctx->inst)->val_new_top(); + if (!fill_bit) { + val_domain(ctx->inst)->val_free(y); + return false; + } + if (!eval_pure(ctx, pfill_bit, fill_bit)) { + RZ_LOG_ERROR("prototype: SHIFT(L/R) fill_bit failed to evaluate.\n"); + val_domain(ctx->inst)->val_free(y); + return false; + } + val_domain(ctx->inst)->eval_shift(pure->code == RZ_IL_OP_SHIFTR, out, y, fill_bit); + val_domain(ctx->inst)->val_free(y); + val_domain(ctx->inst)->val_free(fill_bit); + break; + } + case RZ_IL_OP_LOADW: + case RZ_IL_OP_LOAD: { + RzILOpPure *key = pure->code == RZ_IL_OP_LOAD ? pure->op.load.key : pure->op.loadw.key; + RzILMemIndex mem_idx = pure->code == RZ_IL_OP_LOAD ? 0 : pure->op.loadw.mem; + if (!eval_pure(ctx, key, out)) { + RZ_LOG_ERROR("prototype: LOAD/LOADW key failed to evaluate.\n"); + return false; + } + + // Hint: Instead of supporting only a single constant load addr and mapping all other + // loads to top, if the concrete set of the address is reasonably small, we could load + // from all possible addresses and join the results. + RzBitVector ld_addr; + rz_bv_init(&ld_addr, 64); + if (!val_domain(ctx->inst)->to_concrete_const(out, &ld_addr)) { + rz_bv_fini(&ld_addr); + goto map_to_top; + } + if (rz_bv_len(&ld_addr) == 64) { + // TODO: Remove normalization. + // Unset bit 63 is required, because the RzBuffer API only supports + // st64 addresses. + RzBitVector mask = { 0 }; + rz_bv_init(&mask, 64); + rz_bv_set_from_ut64(&mask, 0x7fffffffffffffff); + rz_bv_and_inplace(&ld_addr, &mask); + } + + report_yield_xref(ctx, 0, ctx->insn_addr, out, RZ_ANALYSIS_XREF_TYPE_MEM_READ); + size_t n_bits = pure->code == RZ_IL_OP_LOAD ? ctx->inst->il_ctx->config->mem_key_size : pure->op.loadw.n_bits; + if (!load_abstr_data(ctx->inst, mem_idx, &ld_addr, n_bits, out)) { + rz_bv_fini(&ld_addr); + goto map_to_top; + } + rz_bv_fini(&ld_addr); + break; + } + case RZ_IL_OP_SDIV: + case RZ_IL_OP_SMOD: + case RZ_IL_OP_FLOAT: + case RZ_IL_OP_FBITS: + case RZ_IL_OP_IS_FINITE: + case RZ_IL_OP_IS_NAN: + case RZ_IL_OP_IS_INF: + case RZ_IL_OP_IS_FZERO: + case RZ_IL_OP_IS_FNEG: + case RZ_IL_OP_IS_FPOS: + case RZ_IL_OP_FNEG: + case RZ_IL_OP_FABS: + case RZ_IL_OP_FCAST_INT: + case RZ_IL_OP_FCAST_SINT: + case RZ_IL_OP_FCAST_FLOAT: + case RZ_IL_OP_FCAST_SFLOAT: + case RZ_IL_OP_FCONVERT: + case RZ_IL_OP_FREQUAL: + case RZ_IL_OP_FSUCC: + case RZ_IL_OP_FPRED: + case RZ_IL_OP_FORDER: + case RZ_IL_OP_FROUND: + case RZ_IL_OP_FSQRT: + case RZ_IL_OP_FRSQRT: + case RZ_IL_OP_FADD: + case RZ_IL_OP_FSUB: + case RZ_IL_OP_FMUL: + case RZ_IL_OP_FDIV: + case RZ_IL_OP_FMOD: + case RZ_IL_OP_FHYPOT: + case RZ_IL_OP_FPOW: + case RZ_IL_OP_FMAD: + case RZ_IL_OP_FROOTN: + case RZ_IL_OP_FPOWN: + case RZ_IL_OP_FCOMPOUND: + case RZ_IL_OP_FEXCEPT: + RZ_LOG_ERROR("Unhandled pure %" PFMT32d "\n", pure->code); + // Not implemented. + goto map_to_top; + } + return true; + +map_to_top: + val_domain(ctx->inst)->set_top(out); + return true; +} + +static void eval_call(RzInterpRunContext *ctx) { + // For calls, assume control flow will continue like fallthrough. + // But any data that may be modified by the callee must be set to top. + // TODO: this should depend on the ABI, some data may be preserved. + RzIterator *it = ht_up_as_iter(ctx->astate->globals); + RzInterpAbstrVal **av; + rz_iterator_foreach(it, av) { + val_domain(ctx->inst)->set_top(*av); + } +} + +static bool eval_effect(RzInterpRunContext *ctx, + const RzILOpEffect *effect, + size_t insn_pkt_size) { + rz_return_val_if_fail(ctx->astate->pc_state == RZ_INTERP_PC_CONST, false); + ut64 pc = ctx->astate->pc; + RzInterpAbstrVal *eval_out = NULL; + + switch (effect->code) { + default: + case RZ_IL_OP_EMPTY: + break; + case RZ_IL_OP_NOP: { + break; + } + case RZ_IL_OP_SEQ: { + if (!eval_effect(ctx, effect->op.seq.x, insn_pkt_size)) { + goto error; + } + if (!eval_effect(ctx, effect->op.seq.y, insn_pkt_size)) { + goto error; + } + break; + } + case RZ_IL_OP_SET: { + eval_out = val_domain(ctx->inst)->val_new_top(); + ut64 vhash = effect->op.set.hash; + if (!eval_out || !eval_pure(ctx, effect->op.set.x, eval_out)) { + goto error; + } + RzILVarKind kind = effect->op.set.is_local ? RZ_IL_VAR_KIND_LOCAL : RZ_IL_VAR_KIND_GLOBAL; + write_var_to_state(ctx->inst, ctx->astate, kind, vhash, eval_out); + if (value_indicates_ret_addr_write(ctx, eval_out) && + kind == RZ_IL_VAR_KIND_GLOBAL) { + // Hint: this ret-addr store detection currently only works across a single interp block. + // Consider the following ARMv4 code for an indirect call (blx was introduced in ARMv5): + // ``` + // A> mov lr, pc + // B> mov pc, r0 + // C> ... + // ``` + // + // both A and B are block entries. + // + // 1. If A is discovered before B, the call is recognized at that point. Once B is detected, C will not be + // reached by fallthrough anymore. + // 2. If B is discovered before A, the call is not recognized in the first place because A and B are two + // separate blocks already. + // + // It is inconvenient that we may have a fixpoint where C could be considered only partially evaluated, + // but as long as we don't find a practical example where this happens and thus also don't have a good + // example for the expected analysis outcome, we leave it as-is. + // + // Making C reachable even with the in-edge at B could work by for example by marking A as ret-addr-storing + // and using that information when evaluating B. + ctx->call_cand.store_addr = pc; + ctx->call_cand.npc = ctx->il_block_end; + ctx->call_cand.in_mem = false; + } + break; + } + case RZ_IL_OP_JMP: { + eval_out = val_domain(ctx->inst)->val_new_top(); + if (!eval_out || !eval_pure(ctx, effect->op.jmp.dst, eval_out)) { + goto error; + } + RzBitVector eval_out_bv; + rz_bv_init(&eval_out_bv, 64); + bool is_const = val_domain(ctx->inst)->to_concrete_const(eval_out, &eval_out_bv); + if (!is_const) { + RZ_LOG_DEBUG("PC is going to be set to an abstract value! Current PC = 0x%" PFMT64x "\n", pc); + } + bool is_call = !!ctx->call_cand.store_addr; + + if (is_const) { + ut64 target = rz_bv_to_ut64(&eval_out_bv); + RZ_LOG_DEBUG("prototype: JMP - Set PC: 0x%" PFMT64x " -> 0x%" PFMT64x "\n", pc, target); + RzAnalysisXRefType xref_type = RZ_ANALYSIS_XREF_TYPE_CODE; + + if (is_call) { + // An instruction in this basic block stored the next PC. + // Report a call candidate and assume this jump is a call. + ctx->call_cand.candidate_addr = pc; + ctx->call_cand.target = target; + report_yield_call_candiate(ctx); + + xref_type = RZ_ANALYSIS_XREF_TYPE_CALL; + } + + report_yield_xref(ctx, insn_pkt_size, ctx->insn_addr, eval_out, xref_type); + + // Clear the call candidate tracking variable. + memset(&ctx->call_cand, 0, sizeof(ctx->call_cand)); + } + + if (is_call) { + eval_call(ctx); + } else { + set_abstr_pc(ctx->inst, ctx->astate, eval_out); + } + rz_bv_fini(&eval_out_bv); + break; + } + case RZ_IL_OP_BRANCH: { + eval_out = val_domain(ctx->inst)->val_new_top(); + if (!eval_out || !eval_pure(ctx, effect->op.branch.condition, eval_out)) { + goto error; + } + bool may_be_true = val_domain(ctx->inst)->may_be_bool(eval_out, true); + bool may_be_false = val_domain(ctx->inst)->may_be_bool(eval_out, false); + ut64 fallthrough_pc = ctx->astate->pc; + if (may_be_true && may_be_false) { + RzInterpAbstrState *true_state = rz_interp_abstr_state_clone(ctx->inst, ctx->astate); + RzInterpAbstrState *false_state = ctx->astate; + ctx->astate = true_state; + if (!eval_effect(ctx, effect->op.branch.true_eff, insn_pkt_size)) { + goto error; + } + ctx->astate = false_state; + if (!eval_effect(ctx, effect->op.branch.false_eff, insn_pkt_size)) { + goto error; + } + if (true_state->pc_state == false_state->pc_state && true_state->pc == false_state->pc) { + // identical target location, simply join the data and continue + join_state(ctx->inst, true_state, false_state); + } else if (interp_is_collecting_states(ctx)) { + // different jump targets, branch rather than resorting to top pc + rz_interp_run_push(ctx, true_state, true_state->pc_state == RZ_INTERP_PC_CONST && true_state->pc == fallthrough_pc); + if (true_state->pc_state == RZ_INTERP_PC_CONST && !rz_vector_contains(&ctx->block->jump_targets, &true_state->pc)) { + rz_vector_push(&ctx->block->jump_targets, &true_state->pc); + } + // false_state is already in ctx->inst->astate and will be continued automatically + } + rz_interp_abstr_state_free(ctx->inst, true_state); + } else if (may_be_true) { + if (!eval_effect(ctx, effect->op.branch.true_eff, insn_pkt_size)) { + goto error; + } + } else if (may_be_false) { + if (!eval_effect(ctx, effect->op.branch.false_eff, insn_pkt_size)) { + goto error; + } + } + break; + } + case RZ_IL_OP_STORE: + case RZ_IL_OP_STOREW: { + RzInterpAbstrVal *st_addr = val_domain(ctx->inst)->val_new_top(); + if (!st_addr) { + goto error; + } + RzILOpPure *key = effect->code == RZ_IL_OP_STORE ? effect->op.store.key : effect->op.storew.key; + RzILMemIndex mem_idx = effect->code == RZ_IL_OP_STORE ? 0 : effect->op.storew.mem; + if (!eval_pure(ctx, key, st_addr)) { + RZ_LOG_ERROR("prototype: STORE/STOREW key failed to evaluate.\n"); + val_domain(ctx->inst)->val_free(st_addr); + goto error; + } + RzBitVector st_addr_bv; + rz_bv_init(&st_addr_bv, 64); + if (val_domain(ctx->inst)->to_concrete_const(st_addr, &st_addr_bv)) { + if (rz_bv_len(&st_addr_bv) == 64) { + // TODO: Remove normalization. + // Unset bit 63 is required, because the RzBuffer API only supports + // st64 addresses. + RzBitVector mask = { 0 }; + rz_bv_init(&mask, 64); + rz_bv_set_from_ut64(&mask, 0x7fffffffffffffff); + rz_bv_and_inplace(&st_addr_bv, &mask); + } + report_yield_xref(ctx, insn_pkt_size, ctx->insn_addr, st_addr, RZ_ANALYSIS_XREF_TYPE_MEM_WRITE); + } + + RzILOpPure *pval = effect->code == RZ_IL_OP_STORE ? effect->op.store.value : effect->op.storew.value; + eval_out = val_domain(ctx->inst)->val_new_top(); + if (!eval_out || !eval_pure(ctx, pval, eval_out)) { + RZ_LOG_ERROR("prototype: STORE/STOREW x failed to evaluate.\n"); + rz_bv_fini(&st_addr_bv); + val_domain(ctx->inst)->val_free(st_addr); + goto error; + } + if (value_indicates_ret_addr_write(ctx, eval_out)) { + ctx->call_cand.store_addr = pc; + ctx->call_cand.npc = ctx->il_block_end; + ctx->call_cand.in_mem = true; + } + if (!store_abstr_data(ctx->inst, mem_idx, st_addr, eval_out)) { + rz_bv_fini(&st_addr_bv); + goto error; + } + val_domain(ctx->inst)->val_free(st_addr); + rz_bv_fini(&st_addr_bv); + break; + } + case RZ_IL_OP_GOTO: + case RZ_IL_OP_BLK: + case RZ_IL_OP_REPEAT: + RZ_LOG_ERROR("Unhandled effect %" PFMT32d "\n", effect->code); + // Ignore for now. + break; + } + val_domain(ctx->inst)->val_free(eval_out); + return true; +error: + val_domain(ctx->inst)->val_free(eval_out); + return false; +} + +static bool eval_block(RZ_NONNULL RzInterpRunContext *ctx, RZ_NONNULL RzInterpBlock *interp_block, RZ_NONNULL const RzILCacheBlock *il_block) { + ctx->block = interp_block; + ctx->il_block_end = il_block->addr + il_block->size; + // Reset call candidate tracking for each basic block. + memset(&ctx->call_cand, 0, sizeof(ctx->call_cand)); + + ut64 interp_block_end = rz_interp_block_get_end(ctx->block); + + // Now execute the actual effects of the BLOCK. + RzInterpAbstrState *astate = ctx->astate; + void **it; + rz_pvector_foreach (il_block->il_ops, it) { + ut64 pc = astate->pc; + + if (pc > interp_block_end) { + // block is truncated + break; + } + + RzILCacheInsnPkt *pkt = *it; + ctx->insn_addr = pc; + + // Prepare next pc, the evalutation may overwrite this. + ut64 next_pc = pc + pkt->insn_pkt_size; + rz_interp_abstr_state_set_pc_const(ctx->astate, next_pc); + + if (interp_is_analyzing(ctx) && (ctx->res_dimen & RZ_INTERP_RESULT_DIMEN_COMMENTS)) { + RzStrBuf sb; + rz_strbuf_init(&sb); + rz_interp_abstr_state_as_str_short(ctx->inst, ctx->astate, &sb); + interp_add_comment(ctx, ctx->insn_addr, rz_strbuf_get(&sb)); + rz_strbuf_fini(&sb); + if (pc == il_block->addr) { + interp_add_comment(ctx, ctx->insn_addr, "<-"); + } + if (rz_vector_index_ptr(&il_block->il_ops->v, rz_pvector_len(il_block->il_ops) - 1) == it) { + interp_add_comment(ctx, ctx->insn_addr, "->"); + } + } + + if (!eval_effect(ctx, pkt->effect, pkt->insn_pkt_size)) { + return false; + } + if (astate->pc_state != RZ_INTERP_PC_CONST) { + // unreachable or unknown jump + break; + } + if (interp_is_collecting_states(ctx) && astate->pc != next_pc) { + // Constant jump other than fallthrough, meaning interpretation will continue in another block + interp_block_add_non_fallthrough_target(ctx->block, astate->pc); + break; + } + } + + if (interp_is_collecting_states(ctx) && astate->pc_state != RZ_INTERP_PC_UNREACHABLE) { + bool fallthrough = false; + if (astate->pc_state == RZ_INTERP_PC_CONST && astate->pc == interp_block_end + 1) { + fallthrough = true; + ctx->block->fallthrough = true; + } + rz_interp_run_push(ctx, ctx->astate, fallthrough); + } + + return true; +} + +RZ_API bool rz_interp_run_context_init(RzInterpRunContext *ctx, RzInterpInstance *inst) { + ctx->inst = inst; + ctx->astate = NULL; + ctx->res = NULL; + ctx->queue = rz_list_new(); + if (!ctx->queue) { + return false; + } + interp_blocks_init(ctx); + return true; +} + +RZ_API void rz_interp_run_context_fini(RzInterpRunContext *ctx) { + rz_list_free(ctx->queue); + interp_blocks_free(ctx); +} + +static const RzILCacheBlock *interp_lift_block(RzInterpInstance *inst, ut64 addr) { + const RzILCacheBlock *block; + RzInterpLiftBlockResult res = inst->config.lift_block(addr, &block, inst->config.cb_user); + // TODO: handle break + return res == RZ_INTERP_LIFT_BLOCK_RESULT_OK ? block : NULL; +} + +/** + * \brief Run the interpreter from a single entrypoint until a fixpoint is reached + */ +RZ_API RzInterpResult *rz_interp_run(RzInterpInstance *inst, ut64 entry_point, RzInterpResultDimen dimen) { + rz_return_val_if_fail(inst, NULL); + + // Initialization + RzInterpResult *res = NULL; + RzInterpRunContext ctx = { 0 }; + if (!rz_interp_run_context_init(&ctx, inst)) { + return NULL; + } + + // Prepare the initial state from the given entry point + // Hint: nothing speaks against supporting multiple entry points in a single run + RzInterpAbstrState *estate = rz_interp_abstr_state_new(inst); + memset(&ctx.call_cand, 0, sizeof(ctx.call_cand)); + if (!reset_state(inst, estate, entry_point)) { + rz_warn_if_reached(); + rz_interp_abstr_state_free(inst, estate); + goto cleanup; + } + rz_interp_run_push(&ctx, estate, false); + rz_interp_abstr_state_free(inst, estate); + + // Loop and interpret until a fixpoint has been reached + while (true) { + RzInterpBlock *interp_block = rz_interp_run_pop(&ctx); + if (!interp_block) { + // No uninterpreted states left, fixpoint reached. + break; + } + ctx.astate = rz_interp_abstr_state_clone(inst, interp_block->entry_state); + + const RzILCacheBlock *il_block = interp_lift_block(inst, ctx.astate->pc); + if (!il_block) { + RZ_LOG_ERROR("interpreter: Lifting failed\n"); + // TODO: handle this better + break; + } + + rz_interp_block_resolve_bounds(&ctx, interp_block, il_block); + + // Evaluate the effect on the abstract state. + if (!eval_block(&ctx, interp_block, il_block)) { + // TODO: should this even be able to fail at all? + RZ_LOG_ERROR("interpreter: Eval failed\n"); + goto cleanup; + } + } + + // Fixpoint reached, collect results. + res = RZ_NEW0(RzInterpResult); + if (!res) { + goto cleanup; + } + res->entry = entry_point; + + if (dimen != RZ_INTERP_RESULT_DIMEN_BASE) { + // Evaluate all blocks again once to collect analysis information. + // We do this in an additional pass because until now, the collected abstract states + // did not fully represent all reachable concrete states. + if (dimen & RZ_INTERP_RESULT_DIMEN_XREFS) { + rz_vector_init(&res->xrefs, sizeof(RzAnalysisXRef), NULL, NULL); + } + if (dimen & RZ_INTERP_RESULT_DIMEN_COMMENTS) { + res->comments = ht_up_new(NULL, free); + if (!res->comments) { + free(res); + goto cleanup; + } + } + ctx.res = res; + ctx.res_dimen = dimen; + RzIntervalTreeIter it; + RzInterpBlock *interp_block; + rz_interval_tree_foreach (&ctx.blocks, it, interp_block) { + ctx.astate = rz_interp_abstr_state_clone(inst, interp_block->entry_state); + const RzILCacheBlock *il_block = interp_lift_block(inst, ctx.astate->pc); + if (!il_block) { + RZ_LOG_ERROR("interpreter: Lifting failed\n"); + // TODO: handle this better + break; + } + if (!eval_block(&ctx, interp_block, il_block)) { + // TODO: should this even be able to fail at all? + RZ_LOG_ERROR("interpreter: Eval failed\n"); + break; + } + } + } + + memmove(&res->blocks, &ctx.blocks, sizeof(ctx.blocks)); + memset(&ctx.blocks, 0, sizeof(ctx.blocks)); + +cleanup: + rz_interp_run_context_fini(&ctx); + return res; +} + +static void bb_add_target(RzAnalysisBlock *abb, ut64 target) { + if (abb->jump == UT64_MAX && abb->fail != target) { + abb->jump = target; + } else if (abb->fail == UT64_MAX && abb->jump != target) { + abb->fail = target; + } else if (abb->fail != target && abb->jump != target) { + RZ_LOG_WARN("The basic block at 0x%" PFMT64x " has more than two outgoing edges.\n" + "\t\tHas jump = 0x%" PFMT64x " fail = 0x%" PFMT64x ". Will miss = 0x%" PFMT64x "\n", + abb->addr, abb->jump, abb->fail, + target); + } +} + +RZ_API bool rz_interp_result_apply_to_analysis(RZ_NONNULL RzInterpResult *res, RZ_NONNULL RzAnalysis *analysis) { + rz_return_val_if_fail(res && analysis, false); + // partially copied from rz_inquiry_convert_and_add_to_analysis() + char name[128]; + RzAnalysisFunction *func = rz_analysis_create_function(analysis, rz_strf(name, "inquiry.0x%" PFMT64x, res->entry), res->entry, RZ_ANALYSIS_FCN_TYPE_FCN); + if (!func) { + // TODO: handle better than skipping everything + return false; + } + RzIntervalTreeIter it; + RzInterpBlock *block; + rz_interval_tree_foreach (&res->blocks, it, block) { + if (block->added_to_analysis) { + // has been merged into the previous already + continue; + } + ut64 start = rz_interp_block_get_start(block); + ut64 end_excl = rz_interp_block_get_end(block) + 1; + + if (block->fallthrough && rz_vector_empty(&block->jump_targets)) { + // Merge consecutive blocks if there is no in-edge between them. + // Splits like this happen in the first place because interp blocks only reach until the + // first jump. This may be a call however, which just falls through. + RzIntervalTreeIter next_it = it; + while (true) { + rz_rbtree_iter_next(&next_it); + RzIntervalNode *next_node = NULL; + while (rz_rbtree_iter_has(&next_it)) { + RzIntervalNode *n = rz_interval_tree_iter_get(&next_it); + if (n->start >= end_excl) { + if (n->start == end_excl) { + next_node = n; + } + break; + } + } + RzInterpBlock *next_block; + if (!next_node || (next_block = next_node->data)->non_fallthrough_in) { + // no consecutive block or there is a consecutive block, but is has an in-edge from somewhere else + break; + } + next_block->added_to_analysis = true; + end_excl = next_node->end + 1; + block = next_block; + } + } + + // TODO: add_bb should eventually not be used here since it does its own analysis. + // Instead, we should create the block by hand and apply our analysis info to it. + // Keep in mind we might have to add info from multiple merged blocks here if (see merging above) + rz_analysis_add_bb(analysis, start, end_excl - start); + RzAnalysisBlock *abb = rz_analysis_get_block_at(analysis, start); + rz_analysis_function_add_block(func, abb); + abb->jump = UT64_MAX; + abb->fail = UT64_MAX; + ut64 *target; + rz_vector_foreach(&block->jump_targets, target) { + bb_add_target(abb, *target); + } + if (block->fallthrough) { + bb_add_target(abb, end_excl); + } + } + + // Hint: Some of the xrefs, specifically code and call ones, could be determined from + // the information in blocks as well. So an optimization could be to make the interpreter + // only emit explicit xref info for all remaining events, e.g. mem read/write. + RzAnalysisXRef *xref; + rz_vector_foreach (&res->xrefs, xref) { + if (!rz_analysis_xrefs_set(analysis, xref->from, xref->to, xref->type)) { + RZ_LOG_ERROR("failed to set xref\n"); + } + } + + if (res->comments) { + RzIterator *it = ht_up_as_iter_keys(res->comments); + ut64 *k; + rz_iterator_foreach(it, k) { + const char *cmt = ht_up_find(res->comments, *k, NULL); + if (cmt) { + rz_meta_set_string(analysis, RZ_META_TYPE_COMMENT, *k, cmt); + } + } + rz_iterator_free(it); + } + + return true; +} diff --git a/librz/inquiry/interp/val_abs_constant.c b/librz/inquiry/interp/val_abs_constant.c new file mode 100644 index 00000000000..3503d8ec085 --- /dev/null +++ b/librz/inquiry/interp/val_abs_constant.c @@ -0,0 +1,270 @@ +// SPDX-FileCopyrightText: 2025 RizinOrg +// SPDX-License-Identifier: LGPL-3.0-only + +#include "rz_util/rz_assert.h" +#include + +#include +#include +#include +#include + +#include "rz_util/rz_bitvector.h" + +typedef struct { + /** + * \brief Set if the abstract value represents a single constant bitvector. + * If set, the bit vector below is a valid concrete value. + * If unset it is a top value, i.e. represents the set of all bitvectors. + */ + bool is_const; + /** + * \brief The single constant value. + * If is_const is unset this might hold garbage. + */ + RzBitVector *bv; +} ProtoIntrprAbstrData; + +/** + * \brief Abstract data getter from the RzInterpAbstrVal + */ +#define AD(av) ((ProtoIntrprAbstrData *)rz_interp_abstr_val_unpack(av)) + +static RzInterpAbstrVal *pack(ProtoIntrprAbstrData *val) { + return rz_interp_abstr_val_pack(val); +} + +static RZ_OWN RzInterpAbstrVal *val_new_top() { + ProtoIntrprAbstrData *ad = RZ_NEW0(ProtoIntrprAbstrData); + ad->is_const = false; + ad->bv = rz_bv_new(64); + return pack(ad); +} + +static void val_free(RzInterpAbstrVal *val) { + if (!val) { + return; + } + ProtoIntrprAbstrData *adata = AD(val); + rz_bv_free(adata->bv); + free(adata); +} + +static bool val_is_top(RZ_NONNULL const RzInterpAbstrVal *val) { + return !AD(val)->is_const; +} + +bool static val_may_be_bool(RZ_NONNULL const RzInterpAbstrVal *val, bool value) { + if (!AD(val)->is_const) { + return true; + } + return value != rz_bv_is_zero_vector(AD(val)->bv); +} + +static void val_set_top(RZ_NONNULL RzInterpAbstrVal *val) { + AD(val)->is_const = false; +} + +static void val_set_const_bool(RZ_OUT RZ_NONNULL RzInterpAbstrVal *dst, bool src) { + AD(dst)->is_const = true; + rz_bv_cast_inplace(AD(dst)->bv, 1, false); + rz_bv_set_from_ut64(AD(dst)->bv, src ? 1 : 0); +} + +static void val_set_const_bv(RZ_OUT RZ_NONNULL RzInterpAbstrVal *dst, RZ_IN RZ_NONNULL RzBitVector *src) { + AD(dst)->is_const = true; + rz_bv_cast_inplace(AD(dst)->bv, rz_bv_len(src), false); + rz_bv_copy(AD(dst)->bv, src); +} + +void val_copy(RzInterpAbstrVal *dst_val, const RzInterpAbstrVal *src_val) { + ProtoIntrprAbstrData *dst = AD(dst_val); + ProtoIntrprAbstrData *src = AD(src_val); + rz_return_if_fail(dst && src && dst->bv && src->bv); + rz_bv_cast_inplace(dst->bv, rz_bv_len(src->bv), false); + rz_bv_copy(dst->bv, src->bv); + dst->is_const = src->is_const; +} + +/** + * \brief Join (least upper bound) on values + * \return True if a was changed + */ +static bool join_val(RZ_BORROW RZ_INOUT RzInterpAbstrVal *a, RZ_BORROW RZ_IN const RzInterpAbstrVal *b) { + ProtoIntrprAbstrData *ad = AD(a); + ProtoIntrprAbstrData *bd = AD(b); + if (ad->is_const && bd->is_const && rz_bv_eq(ad->bv, bd->bv)) { + // identical values, a already has the least upper bound + return false; + } + // for anything else, the least upper bound is top + bool changed = ad->is_const; + ad->is_const = false; + return changed; +} + +bool val_as_str(RZ_NONNULL const RzInterpAbstrVal *val, RZ_NONNULL RZ_OUT RzStrBuf *sb) { + rz_return_val_if_fail(val && sb, false); + ProtoIntrprAbstrData *av = AD(val); + if (av->is_const) { + char *s = rz_bv_as_hex_string(av->bv, false); + if (!s) { + return false; + } + rz_strbuf_append(sb, s); + free(s); + } else { + rz_strbuf_append(sb, "⊤"); + } + return true; +} + +static bool to_concrete_const(RZ_NONNULL const RzInterpAbstrVal *val, RZ_NULLABLE RZ_OUT RzBitVector *out) { + if (!AD(val)->is_const) { + return false; + } + if (out) { + rz_bv_cast_inplace(out, rz_bv_len(AD(val)->bv), false); + rz_bv_copy(out, AD(val)->bv); + } + return true; +} + +static void eval_cast(ut32 length, RZ_NONNULL const RzInterpAbstrVal *fill, RZ_INOUT RZ_NONNULL RzInterpAbstrVal *val) { + if (!AD(val)->is_const || !AD(fill)->is_const) { + val_set_top(val); + return; + } + rz_bv_cast_inplace(AD(val)->bv, length, !rz_bv_is_zero_vector(AD(fill)->bv)); +} + +static void eval_shift(bool right, RZ_NONNULL RZ_INOUT RzInterpAbstrVal *x, RZ_NONNULL const RzInterpAbstrVal *y, RZ_NONNULL const RzInterpAbstrVal *fill_bit) { + if (!AD(x)->is_const || !AD(y)->is_const || !AD(fill_bit)->is_const) { + // Hint: there are some more cases that could be handled better: + // - x is either all 1 or all 0 => any shift will produce the same result + // - y is 0 => fill_bit may be top and the result is still known to be just x + // but not sure if these are worth the extra control flow for practical purposes + return; + } + bool (*shift)(RzBitVector *bv, ut32 size, bool fill_bit); + shift = right ? rz_bv_rshift_fill : rz_bv_lshift_fill; + shift(AD(x)->bv, rz_bv_to_ut64(AD(y)->bv), !rz_bv_is_zero_vector(AD(fill_bit)->bv)); +} + +static void eval_binop(RzILOpPureCode code, RZ_NONNULL RZ_INOUT RzInterpAbstrVal *x, RZ_NONNULL const RzInterpAbstrVal *y) { + if (!AD(x)->is_const || !AD(y)->is_const) { + val_set_top(x); + return; + } + RzBitVector *xv = AD(x)->bv; + RzBitVector *yv = AD(y)->bv; + switch (code) { + case RZ_IL_OP_APPEND: + rz_bv_append_inplace(xv, yv); + break; + case RZ_IL_OP_LOGAND: + case RZ_IL_OP_AND: + rz_bv_and_inplace(xv, yv); + break; + case RZ_IL_OP_LOGOR: + case RZ_IL_OP_OR: + rz_bv_or_inplace(xv, yv); + break; + case RZ_IL_OP_LOGXOR: + case RZ_IL_OP_XOR: + rz_bv_xor_inplace(xv, yv); + break; + case RZ_IL_OP_ADD: + rz_bv_add_inplace(xv, yv, NULL); + break; + case RZ_IL_OP_SUB: + rz_bv_sub_inplace(xv, yv, NULL); + break; + case RZ_IL_OP_SLE: + val_set_const_bool(x, rz_bv_sle(xv, yv)); + break; + case RZ_IL_OP_ULE: + val_set_const_bool(x, rz_bv_ule(xv, yv)); + break; + case RZ_IL_OP_EQ: + val_set_const_bool(x, rz_bv_eq(xv, yv)); + break; + case RZ_IL_OP_MUL: + rz_bv_mul_inplace(xv, yv); + break; + case RZ_IL_OP_MOD: + rz_bv_mod_inplace(xv, yv); + break; + case RZ_IL_OP_DIV: + rz_bv_div_inplace(xv, yv); + break; + default: + // unimplemented + val_set_top(x); + break; + } +} + +static void eval_unop(RzILOpPureCode code, RZ_NONNULL RZ_INOUT RzInterpAbstrVal *val) { + if (!AD(val)->is_const) { + return; + } + RzBitVector *bv = AD(val)->bv; + switch (code) { + case RZ_IL_OP_LOGNOT: + case RZ_IL_OP_INV: + rz_bv_not_inplace(bv); + break; + case RZ_IL_OP_IS_ZERO: + val_set_const_bool(val, rz_bv_is_zero_vector(bv)); + break; + case RZ_IL_OP_LSB: + val_set_const_bool(val, rz_bv_lsb(bv)); + break; + case RZ_IL_OP_MSB: + val_set_const_bool(val, rz_bv_msb(bv)); + break; + case RZ_IL_OP_NEG: + rz_bv_neg_inplace(bv); + break; + default: + // unimplemented + val_set_top(val); + break; + } +} + +RZ_API RzInterpValueAbstraction rz_interp_value_domain_const = { + .name = "constant", + .val_new_top = val_new_top, + .val_free = val_free, + .set_top = val_set_top, + .set_const_bool = val_set_const_bool, + .set_const_bv = val_set_const_bv, + .is_top = val_is_top, + .may_be_bool = val_may_be_bool, + .to_concrete_const = to_concrete_const, + .copy = val_copy, + .join = join_val, + .val_as_str = val_as_str, + .eval_cast = eval_cast, + .eval_shift = eval_shift, + .eval_binop = eval_binop, + .eval_unop = eval_unop +}; + +RZ_API RzInquiryPlugin rz_inquiry_plugin_interpreter_prototype = { + .name = "abstr_int_prototype", + .author = "Rot127", + .version = "0.1p", + .desc = "A prototype interpreter for constant/top abstractions.", + .license = "LGPL-3.0-only", + .value_abstraction = &rz_interp_value_domain_const, +}; + +#ifndef RZ_PLUGIN_INCORE +RZ_API RzLibStruct rizin_plugin = { + .type = RZ_LIB_TYPE_INTERPRETER, + .data = &interpreter_prototype +}; +#endif diff --git a/librz/inquiry/meson.build b/librz/inquiry/meson.build new file mode 100644 index 00000000000..599209872e8 --- /dev/null +++ b/librz/inquiry/meson.build @@ -0,0 +1,63 @@ +# SPDX-FileCopyrightText: 2025 RizinOrg +# SPDX-License-Identifier: LGPL-3.0-only + +rz_inquiry_sources = [ + 'algorithms/revng_fcn_detection.c', + 'bcfg.c', + 'inquiry.c', + 'il_cache.c', + 'interp/interpreter.c', + 'interp/val_abs_constant.c', + 'interp/driver.c' +] + +rz_inquiry_inc = [platform_inc] + +rz_inquiry = library('rz_inquiry', rz_inquiry_sources, + include_directories: rz_inquiry_inc, + dependencies: [ + rz_arch_dep, # Legacy analysis module + rz_bin_dep, + rz_config_dep, + rz_cons_dep, + rz_hash_dep, + rz_il_dep, + rz_io_dep, + rz_magic_dep, + rz_reg_dep, + rz_search_dep, + rz_syscall_dep, + rz_type_dep, + rz_util_dep, + ], + install: true, + implicit_include_directories: false, + install_rpath: rpath_lib, + soversion: rizin_libversion, + version: rizin_version, + name_suffix: lib_name_suffix, + name_prefix: lib_name_prefix, +) + +rz_inquiry_dep = declare_dependency(link_with: rz_inquiry, + include_directories: rz_inquiry_inc) +meson.override_dependency('rz_inquiry', rz_inquiry_dep) + +modules += { 'rz_inquiry': { + 'target': rz_inquiry, + 'dependencies': [ + 'rz_arch', + 'rz_bin', + 'rz_config', + 'rz_cons', + 'rz_hash', + 'rz_il', + 'rz_io', + 'rz_magic', + 'rz_reg', + 'rz_search', + 'rz_syscall', + 'rz_type', + 'rz_util' + ] +}} diff --git a/librz/meson.build b/librz/meson.build index aea186af952..7eaf8923d4a 100644 --- a/librz/meson.build +++ b/librz/meson.build @@ -27,6 +27,7 @@ subdir('arch') subdir('sign') subdir('egg') subdir('debug') +subdir('inquiry') subdir('core') subdir('main') diff --git a/librz/util/bitvector.c b/librz/util/bitvector.c index 15eb49e003e..015c6630f04 100644 --- a/librz/util/bitvector.c +++ b/librz/util/bitvector.c @@ -1,7 +1,9 @@ // SPDX-FileCopyrightText: 2021 heersin // SPDX-License-Identifier: LGPL-3.0-only +#include "rz_types.h" #include "rz_util.h" +#include "rz_util/rz_assert.h" #include #include diff --git a/librz/util/buf.c b/librz/util/buf.c index 40824e2a81c..fbcd8179f9c 100644 --- a/librz/util/buf.c +++ b/librz/util/buf.c @@ -13,6 +13,7 @@ #include "buf_io_fd.c" #include "buf_io.c" #include "buf_ref.c" +#include "rz_util/rz_assert.h" #define GET_STRING_BUFFER_SIZE 32 diff --git a/librz/util/thread_queue.c b/librz/util/thread_queue.c index 8034e38c701..eeb6633dab7 100644 --- a/librz/util/thread_queue.c +++ b/librz/util/thread_queue.c @@ -143,6 +143,19 @@ RZ_API void rz_th_queue_close_when_empty(RZ_NONNULL RzThreadQueue *queue) { rz_th_lock_leave(queue->data_lock); } +/** + * \brief Opens a RzThreadQueue (allows to read/write data). + * + * \param queue The RzThreadQueue to close + */ +RZ_API void rz_th_queue_open(RZ_NONNULL RzThreadQueue *queue) { + rz_return_if_fail(queue); + + rz_th_lock_enter(queue->data_lock); + queue->closed = false; + rz_th_lock_leave(queue->data_lock); +} + /** * \brief Closes a RzThreadQueue (once closed you cannot read/write data). * diff --git a/librz/util/thread_ring_buf.c b/librz/util/thread_ring_buf.c index 4d9cd014840..b6dbcf8b17d 100644 --- a/librz/util/thread_ring_buf.c +++ b/librz/util/thread_ring_buf.c @@ -72,10 +72,10 @@ struct rz_th_ring_buf_t { * \param n The number of elements the buffer can hold. * \param elem_size Number of bytes each element has. * - * \return The new rung buffer or NULL in case of failure. + * \return The new ring buffer or NULL in case of failure. */ RZ_API RZ_OWN RzThreadRingBuf *rz_th_ring_buf_new(size_t n, size_t elem_size) { - rz_return_val_if_fail(n > 1 && elem_size > 0, NULL); + rz_return_val_if_fail(n > 0 && elem_size > 0, NULL); RzThreadRingBuf *rbuf = RZ_NEW0(RzThreadRingBuf); if (!rbuf) { rz_warn_if_reached(); diff --git a/test/db/inquiry/interpreter/unmapped_fcn_loop b/test/db/inquiry/interpreter/unmapped_fcn_loop new file mode 100644 index 00000000000..041fdd62217 --- /dev/null +++ b/test/db/inquiry/interpreter/unmapped_fcn_loop @@ -0,0 +1,155 @@ +NAME=Indirect call x86 +FILE=bins/inquiry/interpreter/prototype/x86_unmapped_fcn_in_loop +CMDS=< CALL -> 0x8000050 reloc.target.run + reloc.target.run+26 0x800006a -> CODE -> 0x800009f reloc.recurse+27 + reloc.target.run+34 0x8000072 -> CALL -> 0x80000b0 sym.function_0 + reloc.target.run+34 0x8000072 -> DATA -> 0x80000d0 section..data + reloc.fcn_arr+14 0x8000083 -> CALL -> 0x8000040 section..text + reloc.recurse+25 0x800009d -> CODE -> 0x8000066 reloc.target.run+22 + addr size traced ninstr jump fail fcns calls xrefs +--------------------------------------------------------------------------------------- +0x8000040 9 0 3 0x08000049 0x08000040 0x08000050 0x08000083 +0x8000049 2 0 2 0x08000040 +0x800004b 5 0 1 0x08000050 +0x8000050 22 0 5 0x08000066 0x08000050 0x08000044 +0x8000066 6 0 2 0x0800009f 0x0800006c 0x08000050 0x0800009d +0x800006c 13 0 3 0x08000079 0x08000050 0x080000b0 +0x8000079 10 0 3 0x08000088 0x08000083 0x08000050 +0x8000083 5 0 1 0x08000088 0x08000050 0x08000040 +0x8000088 23 0 8 0x08000066 0x08000050 +0x800009f 9 0 4 0x08000050 0x0800006a +0x80000a8 8 0 1 0x080000b0 +0x80000b0 14 0 4 0x080000be 0x080000b0 0x08000440 0x08000072 +0x80000be 2 0 2 0x080000b0 +0x80000c0 14 0 4 0x080000ce 0x080000c0 0x08000440 +0x80000ce 2 0 2 0x080000c0 +0x8000440 1 0 0 0x08000440 +EOF +EXPECT_ERR=< 0x0800005d cmp dword [rbp-0x0c], 0x03 +| ,==< 0x08000061 jnb 0x800008d +| |: 0x08000063 mov eax, dword [rbp-0x0c] +| |: 0x08000066 mov ecx, eax +| |: 0x08000068 mov al, 0x00 +| |: 0x0800006a call qword [rcx*8+obj.fcn_arr] ; sym..data +| |: ; 0x80000e0; RELOC 32 .data @ 0x080000e0 +| |: ; RETURN XREF from sym.function_0 @ 0x80000aa +| |: 0x08000071 mov qword [rbp-0x18], rax +| |: 0x08000075 mov rax, qword [rbp-0x18] +| |: 0x08000079 movzx eax, byte [rax] +| |: 0x0800007c add eax, dword [rbp-0x08] +| |: 0x0800007f mov dword [rbp-0x08], eax +| |: 0x08000082 mov eax, dword [rbp-0x0c] +| |: 0x08000085 add eax, 0x01 +| |: 0x08000088 mov dword [rbp-0x0c], eax +| |`=< 0x0800008b jmp 0x800005d +| `--> 0x0800008d mov eax, dword [rbp-0x08] +| 0x08000090 add rsp, 0x20 +| 0x08000094 pop rbp +\ 0x08000095 ret + 0x08000096 nop word [rax+rax*1], ax + ; CALL XREF from sym.main @ 0x800006a +/ sym.function_0(); +| 0x080000a0 push rbp +| 0x080000a1 mov rbp, rsp +| 0x080000a4 call sym.some_ptr +| ; RETURN XREF from sym.some_ptr @ 0x80000bf +| 0x080000a9 pop rbp +\ 0x080000aa ret + 0x080000ab nop dword [rax+rax*1], eax + ; CALL XREF from sym.function_0 @ 0x80000a4 + ; CALL XREF from sym.function_1 @ 0x80000c4 + ; CALL XREF from sym.function_2 @ 0x80000d4 +/ sym.some_ptr(); +| 0x080000b0 push rbp +| 0x080000b1 mov rbp, rsp +| 0x080000b4 mov rax, obj.z ; sym..bss +| ; 0x80000f8; RELOC 64 .bss @ 0x080000f8 +| 0x080000be pop rbp +\ 0x080000bf ret +/ sym.function_1(); +| 0x080000c0 push rbp +| 0x080000c1 mov rbp, rsp +| 0x080000c4 call sym.some_ptr +| ; RETURN XREF from sym.some_ptr @ 0x80000bf +| 0x080000c9 pop rbp +\ 0x080000ca ret + 0x080000cb nop dword [rax+rax*1], eax +/ sym.function_2(); +| 0x080000d0 push rbp +| 0x080000d1 mov rbp, rsp +| 0x080000d4 call sym.some_ptr +| ; RETURN XREF from sym.some_ptr @ 0x80000bf +| 0x080000d9 pop rbp +\ 0x080000da ret + section..text+42 0x800006a -> CALL -> 0x80000a0 sym.function_0 + section..text+42 0x800006a -> DATA -> 0x80000e0 section..data + reloc..data+30 0x800008b -> CODE -> 0x800005d section..text+29 + sym.function_0+4 0x80000a4 -> CALL -> 0x80000b0 sym.some_ptr + sym.function_0+10 0x80000aa -> RETURN -> 0x8000071 reloc..data+4 + reloc..bss+9 0x80000bf -> RETURN -> 0x80000a9 sym.function_0+9 + reloc..bss+9 0x80000bf -> RETURN -> 0x80000c9 sym.function_1+9 + reloc..bss+9 0x80000bf -> RETURN -> 0x80000d9 sym.function_2+9 + sym.function_1+4 0x80000c4 -> CALL -> 0x80000b0 sym.some_ptr + sym.function_2+4 0x80000d4 -> CALL -> 0x80000b0 sym.some_ptr + addr size traced ninstr jump fail fcns calls xrefs +------------------------------------------------------------------------------------------------------------- +0x8000040 29 0 6 0x0800005d 0x08000040 +0x800005d 6 0 2 0x0800008d 0x08000063 0x08000040 0x0800008b +0x8000063 14 0 4 0x08000071 0x08000040 0x080000a0 +0x8000071 28 0 9 0x0800005d 0x08000040 0x080000aa +0x800008d 9 0 4 0x08000040 +0x8000096 10 0 1 0x080000a0 +0x80000a0 9 0 3 0x080000a9 0x080000a0 0x080000b0 0x0800006a +0x80000a9 2 0 2 0x080000a0 0x080000bf +0x80000ab 5 0 1 +0x80000b0 16 0 5 0x080000b0 0x080000a4 0x080000d4 0x080000c4 +0x80000c0 9 0 3 0x080000c9 0x080000c0 0x080000b0 +0x80000c9 2 0 2 0x080000c0 0x080000bf +0x80000cb 5 0 1 0x080000d0 +0x80000d0 9 0 3 0x080000d9 0x080000d0 0x080000b0 +0x80000d9 2 0 2 0x080000d0 0x080000bf +EOF +RUN + +NAME=Indirect call hexagon +FILE=bins/inquiry/interpreter/prototype/hexagon_icall_malloc +CMDS=< 0x08000060 [ R2 = memw(FP+##-0xc) +| : 0x08000064 [ P0 = cmp.gtu(R2,##0x2) +| :,=< 0x08000068 [ if (P0) jump:nt 0x80000ac +| ,===< 0x0800006c [ jump 0x8000070 +| `---> 0x08000070 [ R2 = memw(FP+##-0xc) +| :| ;-- .data: +| :| 0x08000074 / immext(##sym.some_ptr) ; 0x80000e8 +| :| ; sym..data; RELOC 32 .data @ 0x080000e8 +| :| ;-- .data: +| :| 0x08000078 \ R2 = memw(R2<<#0x2) ; RELOC 32 .data @ 0x080000e8 +| :| 0x0800007c [ callr R2 +| :| ; RETURN XREF from sym.function_0 @ 0x80000bc +| :| ; RETURN XREF from sym.function_1 @ 0x80000d8 +| :| ; RETURN XREF from sym.function_2 @ 0x80000e4 +| :| 0x08000080 [ memw(FP+##-0x10) = R0 +| :| 0x08000084 [ R2 = memw(FP+##-0x10) +| :| 0x08000088 [ R3 = memub(R2+##0x0) +| :| 0x0800008c [ R2 = memw(FP+##-0x8) +| :| 0x08000090 [ R2 = add(R2,R3) +| :| 0x08000094 [ memw(FP+##-0x8) = R2 +| ,===< 0x08000098 [ jump 0x800009c +| `---> 0x0800009c [ R2 = memw(FP+##-0xc) +| :| 0x080000a0 [ R2 = add(R2,##0x1) +| :| 0x080000a4 [ memw(FP+##-0xc) = R2 +| `==< 0x080000a8 [ jump 0x8000060 +| | ; CODE XREF from sym.main @ 0x8000068 +| `-> 0x080000ac [ R0 = memw(FP+##-0x8) +\ 0x080000b0 [ LR:FP = dealloc_return(FP):raw + ; CALL XREF from sym.main @ 0x800007c +/ sym.function_0(); +| 0x080000b4 [ allocframe(SP,#0x0):raw +| 0x080000b8 [ call sym.some_ptr +| ; RETURN XREF from sym.some_ptr @ 0x80000cc +\ 0x080000bc [ LR:FP = dealloc_return(FP):raw + ; CALL XREF from sym.function_0 @ 0x80000b8 + ; CALL XREF from sym.function_1 @ 0x80000d4 + ; CALL XREF from sym.function_2 @ 0x80000e0 +/ sym.some_ptr(); +| 0x080000c0 [ allocframe(SP,#0x0):raw +| ;-- .bss: +| 0x080000c4 / immext(##sym.some_ptr) ; RELOC 32 .bss @ 0x080000f8 +| ;-- .bss: +| 0x080000c8 \ R0 = ##obj.z ; RELOC 32 .bss @ 0x080000f8 +\ 0x080000cc [ LR:FP = dealloc_return(FP):raw + ; CALL XREF from sym.main @ 0x800007c +/ sym.function_1(); +| 0x080000d0 [ allocframe(SP,#0x0):raw +| 0x080000d4 [ call sym.some_ptr +| ; RETURN XREF from sym.some_ptr @ 0x80000cc +\ 0x080000d8 [ LR:FP = dealloc_return(FP):raw + ; CALL XREF from sym.main @ 0x800007c +/ sym.function_2(); +| 0x080000dc [ allocframe(SP,#0x0):raw +| 0x080000e0 [ call sym.some_ptr +| ; RETURN XREF from sym.some_ptr @ 0x80000cc +\ 0x080000e4 [ LR:FP = dealloc_return(FP):raw + ; DATA XREF from sym.main @ 0x8000074 + ;-- section..data: + ;-- .data: + ;-- .text: + ;-- fcn_arr: + 0x080000e8 .dword 0x080000b4 ; sym.function_0 ; RELOC 32 .text @ 0x08000040 + 0x74 ; [04] -rw- section size 12 named .data + ; DATA XREF from sym.main @ 0x8000074 + ;-- .text: + 0x080000ec .dword 0x080000d0 ; sym.function_1 ; RELOC 32 .text @ 0x08000040 + 0x90 + ; DATA XREF from sym.main @ 0x8000074 + ;-- .text: + 0x080000f0 .dword 0x080000dc ; sym.function_2 ; RELOC 32 .text @ 0x08000040 + 0x9c + 0x080000f4 + ;-- section..bss: + ;-- section..comment: + ;-- .bss: + ;-- z: + 0x080000f8 .dword 0xffffffff ; RELOC TARGET 32 .bss @ 0x080000f8 ; [07] ---- section size 41 named .comment + section..text+40 0x8000068 -> CODE -> 0x80000ac reloc..data.8000078+52 + reloc..data 0x8000074 -> DATA -> 0x80000e8 section..data + reloc..data 0x8000074 -> DATA -> 0x80000ec reloc..text.80000ec + reloc..data 0x8000074 -> DATA -> 0x80000f0 reloc..text.80000f0 + reloc..data.8000078+4 0x800007c -> CALL -> 0x80000b4 sym.function_0 + reloc..data.8000078+4 0x800007c -> CALL -> 0x80000d0 sym.function_1 + reloc..data.8000078+4 0x800007c -> CALL -> 0x80000dc sym.function_2 + reloc..data.8000078+48 0x80000a8 -> CODE -> 0x8000060 section..text+32 + sym.function_0+4 0x80000b8 -> CALL -> 0x80000c0 sym.some_ptr + sym.function_0+8 0x80000bc -> RETURN -> 0x8000080 reloc..data.8000078+8 + reloc..bss.80000c8+4 0x80000cc -> RETURN -> 0x80000bc sym.function_0+8 + reloc..bss.80000c8+4 0x80000cc -> RETURN -> 0x80000d8 sym.function_1+8 + reloc..bss.80000c8+4 0x80000cc -> RETURN -> 0x80000e4 sym.function_2+8 + sym.function_1+4 0x80000d4 -> CALL -> 0x80000c0 sym.some_ptr + sym.function_1+8 0x80000d8 -> RETURN -> 0x8000080 reloc..data.8000078+8 + sym.function_2+4 0x80000e0 -> CALL -> 0x80000c0 sym.some_ptr + sym.function_2+8 0x80000e4 -> RETURN -> 0x8000080 reloc..data.8000078+8 + addr size traced ninstr jump fail fcns calls xrefs +----------------------------------------------------------------------------------------------------------------------------------- +0x8000040 32 0 8 0x08000060 0x08000040 +0x8000060 12 0 3 0x0800006c 0x080000ac 0x08000040 0x080000a8 +0x800006c 4 0 1 0x08000070 0x08000040 +0x8000070 16 0 4 0x08000080 0x08000040 0x080000b4 0x080000d0 0x080000dc +0x8000080 28 0 7 0x0800009c 0x08000040 0x080000e4 0x080000d8 0x080000bc +0x800009c 16 0 4 0x08000060 0x08000040 +0x80000ac 8 0 2 0x08000040 0x08000068 +0x80000b4 8 0 2 0x080000bc 0x080000b4 0x080000c0 0x0800007c +0x80000bc 4 0 1 0x080000b4 0x080000cc +0x80000c0 16 0 4 0x080000c0 0x080000b8 0x080000e0 0x080000d4 +0x80000d0 8 0 2 0x080000d8 0x080000d0 0x080000c0 0x0800007c +0x80000d8 4 0 1 0x080000d0 0x080000cc +0x80000dc 8 0 2 0x080000e4 0x080000dc 0x080000c0 0x0800007c +0x80000e4 4 0 1 0x080000dc 0x080000cc +EOF +EXPECT_ERR=< 0x080000c0 sethi 0x20000, g1 ; RELOC 0 .data @ 0x08000130 +| :| ;-- .data: +| :| 0x080000c4 or g1, 0x130, g2 ; RELOC 0 .data @ 0x08000130 +| :| 0x080000c8 ld [fp+0x7f7], g1 +| :| 0x080000cc srl g1, 0, g1 +| :| 0x080000d0 sllx g1, 3, g1 +| :| 0x080000d4 ldx [g2+g1], g1 ; 0x8000130 +| :| ; obj.fcn_arr +| :| 0x080000d8 call g1 +| :| 0x080000dc nop +| :| ; RETURN XREF from sym.function_0 @ 0x8000070 +| :| ; RETURN XREF from sym.function_1 @ 0x800008c +| :| ; RETURN XREF from sym.function_2 @ 0x80000a8 +| :| 0x080000e0 stx o0, [fp+0x7ef] +| :| 0x080000e4 ldx [fp+0x7ef], g1 +| :| 0x080000e8 ldub [g1], g1 +| :| 0x080000ec and g1, 0xff, g1 +| :| 0x080000f0 ld [fp+0x7fb], g2 +| :| 0x080000f4 add g2, g1, g1 +| :| 0x080000f8 st g1, [fp+0x7fb] +| :| 0x080000fc ld [fp+0x7f7], g1 +| :| 0x08000100 add g1, 1, g1 +| :| 0x08000104 st g1, [fp+0x7f7] +| :| ; CODE XREF from sym.main @ 0x80000bc +| :`-> 0x08000108 ld [fp+0x7f7], g1 +| : 0x0800010c cmp g1, 2 +| `==< 0x08000110 bleu icc, reloc..data +| ; CODE XREF from sym.main @ 0x80000e0 +| 0x08000114 nop +| 0x08000118 ld [fp+0x7fb], g1 +| 0x0800011c sra g1, 0, g1 +| 0x08000120 mov g1, i0 +| 0x08000124 rett i7+8 +\ 0x08000128 nop + 0x0800012c invalid + reloc..bss.8000048+12 0x8000054 -> RETURN -> 0x8000064 sym.function_0+12 + reloc..bss.8000048+12 0x8000054 -> RETURN -> 0x8000080 sym.function_1+12 + reloc..bss.8000048+12 0x8000054 -> RETURN -> 0x800009c sym.function_2+12 + sym.function_0+8 0x8000060 -> CALL -> 0x8000040 section..text + sym.function_0+24 0x8000070 -> RETURN -> 0x80000e0 reloc..data.80000c4+28 + sym.function_1+8 0x800007c -> CALL -> 0x8000040 section..text + sym.function_1+24 0x800008c -> RETURN -> 0x80000e0 reloc..data.80000c4+28 + sym.function_2+8 0x8000098 -> CALL -> 0x8000040 section..text + sym.function_2+24 0x80000a8 -> RETURN -> 0x80000e0 reloc..data.80000c4+28 + sym.main+16 0x80000bc -> CODE -> 0x8000108 reloc..data.80000c4+68 + reloc..data.80000c4+16 0x80000d4 -> DATA -> 0x8000130 section..data + reloc..data.80000c4+16 0x80000d4 -> DATA -> 0x8000138 reloc..text.8000138 + reloc..data.80000c4+16 0x80000d4 -> DATA -> 0x8000140 reloc..text.8000140 + reloc..data.80000c4+24 0x80000dc -> CALL -> 0x8000058 sym.function_0 + reloc..data.80000c4+24 0x80000dc -> CALL -> 0x8000074 sym.function_1 + reloc..data.80000c4+24 0x80000dc -> CALL -> 0x8000090 sym.function_2 + reloc..data.80000c4+28 0x80000e0 -> CODE -> 0x8000114 reloc..data.80000c4+80 + reloc..data.80000c4+80 0x8000114 -> CODE -> 0x80000c0 reloc..data + addr size traced ninstr jump fail fcns calls xrefs +------------------------------------------------------------------------------------------------------------- +0x8000040 24 0 6 0x08000040 0x08000098 0x08000060 0x0800007c +0x8000058 12 0 3 0x08000064 0x08000058 0x08000040 0x080000dc +0x8000060 4 0 1 +0x8000064 16 0 4 0x08000058 0x08000054 +0x8000074 12 0 3 0x08000080 0x08000074 0x08000040 0x080000dc +0x800007c 4 0 1 +0x8000080 16 0 4 0x08000074 0x08000054 +0x8000090 12 0 3 0x0800009c 0x08000090 0x08000040 0x080000dc +0x8000098 4 0 1 +0x800009c 16 0 4 0x08000090 0x08000054 +0x80000ac 20 0 5 0x08000108 0x080000ac +0x80000c0 32 0 8 0x080000e0 0x080000ac 0x08000114 +0x80000dc 4 0 1 0x080000e0 +0x80000e0 40 0 10 0x08000108 0x080000ac 0x0800008c 0x080000a8 0x08000070 +0x8000108 16 0 4 0x080000c0 0x08000118 0x080000ac 0x080000e0 0x080000bc +0x8000114 4 0 1 0x080000e0 +0x8000118 20 0 5 0x080000ac +EOF +EXPECT_ERR=< 0x080000d4 ldr r2, obj.fcn_arr ; [reloc..data:4]=0x8000134 obj.fcn_arr ; "4\U00000001" +| :| 0x080000d8 ldr r3, [fp, -0xc] ; 12 +| :| 0x080000dc ldr r3, [r2, r3, lsl 2] ; 0x8000130 ; "4\U00000001" +| :| 0x080000e0 mov lr, pc +| :| 0x080000e4 bx r3 ; RELOC 0 +| :| ; RETURN XREF from sym.function_0 @ 0x8000070 +| :| ; RETURN XREF from sym.function_1 @ 0x8000090 +| :| 0x080000e8 str r0, [fp, -0x10] ; 16 +| :| 0x080000ec ldr r3, [fp, -0x10] ; 16 +| :| 0x080000f0 ldrb r3, [r3] +| :| 0x080000f4 mov r2, r3 +| :| 0x080000f8 ldr r3, [fp, -8] ; 8 +| :| 0x080000fc add r3, r3, r2 +| :| 0x08000100 str r3, [fp, -8] ; 8 +| :| 0x08000104 ldr r3, [fp, -0xc] ; 12 +| :| 0x08000108 add r3, r3, 1 +| :| 0x0800010c str r3, [fp, -0xc] ; 12 +| :| ; CODE XREF from sym.main @ 0x80000d0 +| :`-> 0x08000110 ldr r3, [fp, -0xc] ; 12 +| : 0x08000114 cmp r3, 2 ; 2 +| `==< 0x08000118 bls 0x80000d4 +| 0x0800011c ldr r3, [fp, -8] ; 8 +| 0x08000120 mov r0, r3 +| 0x08000124 sub sp, fp, 4 +| 0x08000128 pop {fp, lr} +\ 0x0800012c bx lr ; RELOC 0 + ; DATA XREFS from sym.main @ 0x80000d4, 0x80000dc + ;-- .data: + 0x08000130 stmdaeq r0, {r2, r4, r5, r8} ; RELOC 32 .data @ 0x08000134 + ; DATA XREF from sym.main @ 0x80000dc + ;-- section..data: + ;-- .data: + ;-- function_0: + ;-- fcn_arr: + 0x08000134 .dword 0x08000054 ; reloc.target.function_0 ; sym.function_0; RELOC 32 function_0 @ 0x08000054 ; [03] -rw- section size 12 named .data + ; DATA XREF from sym.main @ 0x80000dc + ;-- function_1: + 0x08000138 .dword 0x08000074 ; reloc.target.function_1 ; sym.function_1; RELOC 32 function_1 @ 0x08000074 + ;-- function_2: + 0x0800013c .dword 0x08000094 ; reloc.target.function_2 ; sym.function_2; RELOC 32 function_2 @ 0x08000094 + ;-- section..bss: + ;-- section..comment: + ;-- .bss: + ;-- z: + ;-- .comment: + 0x08000140 .dword 0xffffffff ; RELOC TARGET 32 .bss @ 0x08000140 ; [06] ---- section size 36 named .comment + 0x08000144 invalid + section..text+8 0x800003c -> DATA -> 0x8000050 reloc..bss + section..text+24 0x800004c -> RETURN -> 0x8000060 reloc.target.function_0+12 + section..text+24 0x800004c -> RETURN -> 0x8000080 reloc.target.function_1+12 + section..text+24 0x800004c -> RETURN -> 0x80000a0 reloc.target.function_2+12 + reloc.target.function_0+8 0x800005c -> CALL -> 0x8000034 section..text + reloc.target.function_0+28 0x8000070 -> RETURN -> 0x80000e8 sym.main+52 + reloc.target.function_1+8 0x800007c -> CALL -> 0x8000034 section..text + reloc.target.function_1+28 0x8000090 -> RETURN -> 0x80000e8 sym.main+52 + reloc.target.function_2+8 0x800009c -> CALL -> 0x8000034 section..text + sym.main+28 0x80000d0 -> CODE -> 0x8000110 sym.main+92 + sym.main+32 0x80000d4 -> DATA -> 0x8000130 reloc..data + sym.main+40 0x80000dc -> DATA -> 0x8000130 reloc..data + sym.main+40 0x80000dc -> DATA -> 0x8000134 section..data + sym.main+40 0x80000dc -> DATA -> 0x8000138 reloc.function_1 + sym.main+48 0x80000e4 -> CALL -> 0x8000054 reloc.target.function_0 + sym.main+48 0x80000e4 -> CALL -> 0x8000074 reloc.target.function_1 + sym.main+100 0x8000118 -> CODE -> 0x80000d4 sym.main+32 + addr size traced ninstr jump fail fcns calls xrefs +------------------------------------------------------------------------------------------------------------- +0x8000034 28 0 7 0x08000034 0x0800005c 0x0800009c 0x0800007c +0x8000050 4 0 1 0x08000054 0x0800003c +0x8000054 12 0 3 0x08000060 0x08000054 0x08000034 0x080000e4 +0x8000060 20 0 5 0x08000054 0x0800004c +0x8000074 12 0 3 0x08000080 0x08000074 0x08000034 0x080000e4 +0x8000080 20 0 5 0x08000074 0x0800004c +0x8000094 12 0 3 0x080000a0 0x08000094 0x08000034 +0x80000a0 20 0 5 0x08000094 0x0800004c +0x80000b4 32 0 8 0x08000110 0x080000b4 +0x80000d4 20 0 5 0x080000e8 0x080000b4 0x08000118 +0x80000e8 40 0 10 0x08000110 0x080000b4 0x08000090 0x08000070 +0x8000110 12 0 3 0x080000d4 0x0800011c 0x080000b4 0x080000d0 +0x800011c 20 0 5 0x080000b4 +0x8000134 1 0 1 0x080000dc +EOF +EXPECT_ERR=< 0x08000194 ld v0, 0(gp) ; RELOC 0 .data.rel.local @ 0x080002f0 +| :| 0x08000198 lwu v1, 4(fp) +| :| 0x0800019c dsll v1, v1, 3 +| :| ;-- .data.rel.local: +| :| 0x080001a0 daddiu v0, v0, 0 ; RELOC 0 .data.rel.local @ 0x080002f0 +| :| 0x080001a4 daddu v0, v1, v0 +| :| 0x080001a8 ld v0, 0(v0) +| :| 0x080001ac move t9, v0 +| :| 0x080001b0 jalr t9 + :| 0x080001b4 nop + :| 0x080001b8 sd v0, 8(fp) + :| 0x080001bc ld v0, 8(fp) + :| 0x080001c0 lbu v0, 0(v0) + :| 0x080001c4 lw v1, 0(fp) + :| 0x080001c8 addu v0, v1, v0 + :| 0x080001cc sw v0, 0(fp) + :| 0x080001d0 lw v0, 4(fp) + :| 0x080001d4 addiu v0, v0, 1 + :| 0x080001d8 sw v0, 4(fp) +| :| ; CODE XREF from sym.main @ 0x800018c +| :`-> 0x080001dc lw v0, 4(fp) +| : 0x080001e0 sltiu v0, v0, 3 +| `==< 0x080001e4 bnez v0, reloc..data.rel.local +| 0x080001e8 nop +| 0x080001ec lw v0, 0(fp) +| 0x080001f0 move sp, fp +| 0x080001f4 ld ra, 0x28(sp) +| 0x080001f8 ld fp, 0x20(sp) +| 0x080001fc ld gp, 0x18(sp) +| 0x08000200 daddiu sp, sp, 0x30 +\ 0x08000204 jr ra + reloc.main.8000180+12 0x800018c -> CODE -> 0x80001dc reloc..data.rel.local.80001a0+60 + reloc..data.rel.local.80001a0+68 0x80001e4 -> CODE -> 0x8000194 reloc..data.rel.local + addr size traced ninstr jump fail fcns calls xrefs +--------------------------------------------------------------------------------- +0x8000040 48 0 12 0x08000040 +0x8000070 4 0 1 +0x8000074 48 0 12 0x08000074 +0x80000a4 28 0 7 +0x80000c0 4 0 1 +0x80000c4 48 0 12 0x080000c4 +0x80000f4 28 0 7 +0x8000110 4 0 1 +0x8000114 48 0 12 0x08000114 +0x8000144 28 0 7 +0x8000160 4 0 1 0x08000164 +0x8000164 44 0 11 0x080001dc 0x08000164 +0x8000190 4 0 1 +0x8000194 32 0 8 0x08000164 0x080001e4 +0x80001b4 40 0 10 0x080001dc +0x80001dc 12 0 3 0x08000194 0x080001e8 0x08000164 0x0800018c +0x80001e8 32 0 8 0x08000164 +EOF +EXPECT_ERR=< +// SPDX-License-Identifier: LGPL-3.0-only + +#include "../../librz/inquiry/interp/interp_priv.h" + +#include "minunit.h" + +typedef struct test_interp_t { + RzAnalysis *analysis; + RzIO *io; + RzILCache *il_cache; + RzInterpInstance *inst; +} TestInterp; + +static RzInterpIOReadResult io_read(RzInterpIOReadRequest *req, void *user) { + return RZ_INTERP_IO_READ_RESULT_TOP; // Currently we do not care about contents, just make the interpreter continue +} + +static RzInterpLiftBlockResult lift_block(ut64 addr, const RzILCacheBlock **block_out, void *user) { + TestInterp *interp = user; + const RzILCacheBlock *block = rz_il_cache_lift_il_block(interp->il_cache, addr); + if (block) { + *block_out = block; + return RZ_INTERP_LIFT_BLOCK_RESULT_OK; + } + return RZ_INTERP_LIFT_BLOCK_RESULT_FAILED; +} + +static TestInterp *interp_new(const char *arch, int bits, ut64 baddr, const char *url) { + // for debugging, uncomment: + // eprintf("rz -a %s -b %d -m 0x%" PFMT64x " %s\n", arch, bits, baddr, url); + TestInterp *interp = RZ_NEW(TestInterp); + interp->analysis = rz_analysis_new(NULL); + rz_analysis_use(interp->analysis, arch); + rz_analysis_set_bits(interp->analysis, bits); + interp->io = rz_io_new(); + interp->io->va = 1; + interp->il_cache = rz_il_cache_new(interp->analysis, interp->io, RZ_IL_CACHE_CONFIG_NOP_UNLIFTED); + RzInterpConfig config = { + .val_domain = &rz_interp_value_domain_const, + .cb_user = interp, + .io_read = io_read, + .lift_block = lift_block + }; + interp->inst = rz_interp_instance_new(interp->analysis, &config); + RzIODesc *desc = rz_io_open_at(interp->io, url, RZ_PERM_RX, 0644, baddr, NULL); + if (!desc) { + mu_perror("load code"); + return NULL; + } + return interp; +} + +static void interp_free(TestInterp *interp) { + rz_interp_instance_free(interp->inst); + rz_il_cache_free(interp->il_cache); + rz_io_free(interp->io); + rz_analysis_free(interp->analysis); + free(interp); +} + +static size_t do_extract_blocks(RzInterpResult *res, RzInterpBlock *blocks[], size_t count) { + size_t r = 0; + RzIntervalTreeIter it; + RzInterpBlock *block; + rz_interval_tree_foreach(&res->blocks, it, block) { + if (r < count) { + blocks[r] = block; + } + r++; + } + return r; +} + +static ut64 block_start(RzInterpBlock *block) { + return block->entry_state->pc; +} + +static ut64 block_end(RzInterpBlock *block) { + return block->node->end + 1; +} + +#define STR_HELPER(x) #x +#define STR(x) STR_HELPER(x) + +/** Extract single result from interp as well as its blocks into local vars for easy assertion */ +#define EXTRACT_RESULT(res, blocks_count) \ + mu_assert_notnull(res, "result"); \ + RzInterpBlock *blocks[blocks_count]; \ + mu_assert_eq(do_extract_blocks(res, blocks, blocks_count), blocks_count, "blocks count") + +#define ASSERT_BLOCK(i, start, end, is_fallthrough, jump) do { \ + mu_assert_eq(block_start(blocks[i]), start, "block " STR(i) " start"); \ + mu_assert_eq(block_end(blocks[i]), end, "block " STR(i) " end"); \ + mu_assert_eq(blocks[i]->fallthrough, (is_fallthrough), "fallthrough"); \ + if ((jump) != UT64_MAX) { \ + mu_assert_eq(rz_vector_len(&blocks[i]->jump_targets), 1, "jump targets count"); \ + mu_assert_eq(*(ut64 *)rz_vector_index_ptr(&blocks[i]->jump_targets, 0), (jump), "jump"); \ + } else { \ + mu_assert_eq(rz_vector_len(&blocks[i]->jump_targets), 0, "jump targets count"); \ + } \ + } while (0) + +#define ASSERT_XREF(i, from_v, to_v, type_v) do { \ + RzAnalysisXRef *xref = rz_vector_index_ptr(&res->xrefs, i); \ + mu_assert_eq(xref->from, from_v, "xref " STR(i) " from"); \ + mu_assert_eq(xref->to, to_v, "xref " STR(i) " to"); \ + mu_assert_eq(xref->type, type_v, "xref " STR(i) " type"); \ + } while (0) + +#define ASSERT_ANALYSIS_BLOCK(block, start, end, jumpv, failv) do { \ + mu_assert_eq(((RzAnalysisBlock *)block)->addr, (start), "analysis block start"); \ + mu_assert_eq(((RzAnalysisBlock *)block)->size, (end) - (start), "analysis block size"); \ + mu_assert_eq(((RzAnalysisBlock *)block)->jump, jumpv, "analysis block jump"); \ + mu_assert_eq(((RzAnalysisBlock *)block)->fail, failv, "analysis block fail"); \ + } while (0) + +static size_t blocks_count(RzInterpRunContext *ctx) { + size_t r = 0; + RzIntervalTreeIter it; + RzInterpBlock *block; + rz_interval_tree_foreach(&ctx->blocks, it, block) { + r++; + } + return r; +} + +bool test_interp_block_resolve_bounds_single(void) { + TestInterp *interp = interp_new("arm", 64, 0x10000, "hex://" + "000080d2" // 0x00 mov x0, 0 + // --- + "200080d2" // 0x04 mov x0, 1 <- entry + "400080d2" // 0x08 mov x0, 2 + "600080d2" // 0x0c mov x0, 3 + "800080d2" // 0x10 mov x0, 4 + "a00080d2" // 0x14 mov x0, 5 + "c0035fd6" // 0x18 ret + // --- + ); + + RzInterpRunContext ctx; + mu_assert_true(rz_interp_run_context_init(&ctx, interp->inst), "init run context"); + + RzInterpAbstrState *as = rz_interp_abstr_state_new(interp->inst); + rz_interp_abstr_state_set_pc_const(as, 0x10008); + rz_interp_run_push(&ctx, as, false); + rz_interp_abstr_state_free(interp->inst, as); + + mu_assert_eq(blocks_count(&ctx), 1, "blocks count"); + RzInterpBlock *block = rz_interp_block_at(&ctx, 0x10008); + mu_assert_notnull(block, "block"); + + const RzILCacheBlock *il_block = rz_il_cache_lift_il_block(interp->il_cache, 0x10008); + rz_interp_block_resolve_bounds(&ctx, block, il_block); + mu_assert_true(block->bounds_resolved, "bounds resolved"); + mu_assert_eq(rz_interp_block_get_start(block), 0x10008, "block start"); + mu_assert_eq(rz_interp_block_get_end(block), 0x1001b, "block end"); + mu_assert_eq(rz_vector_len(&block->insn_offsets), 4, "insn count"); + mu_assert_eq(*(ut16 *)rz_vector_index_ptr(&block->insn_offsets, 0), 0x04, "insn offset"); + mu_assert_eq(*(ut16 *)rz_vector_index_ptr(&block->insn_offsets, 1), 0x08, "insn offset"); + mu_assert_eq(*(ut16 *)rz_vector_index_ptr(&block->insn_offsets, 2), 0x0c, "insn offset"); + mu_assert_eq(*(ut16 *)rz_vector_index_ptr(&block->insn_offsets, 3), 0x10, "insn offset"); + + rz_interp_run_context_fini(&ctx); + interp_free(interp); + mu_end; +} + +bool test_interp_block_resolve_bounds_prepend(bool single_op_existing_block, bool single_op_prepend) { + // single_op_existing_block and single_op_prepend are for testing for potential bugs in insn offset handling + + TestInterp *interp = interp_new("arm", 64, 0x10000, "hex://" + // --- blocks for single_op_existing_block == false + "000080d2" // 0x00 mov x0, 0 <- prepended block + "200080d2" // 0x04 mov x0, 1 <- alternative entry if single_op_prepend + // --- + "400080d2" // 0x08 mov x0, 2 <- existing block + "600080d2" // 0x0c mov x0, 3 + "800080d2" // 0x10 mov x0, 4 + "a00080d2" // 0x14 mov x0, 5 + "c0035fd6" // 0x18 ret + // --- + + // --- blocks for single_op_existing_block == true + // 0x10 mov x0, 4 <- prepended block + // 0x14 mov x0, 5 <- alternative entry if single_op_prepend + // --- + // 0x18 ret <- existing block + // --- + ); + + RzInterpRunContext ctx; + mu_assert_true(rz_interp_run_context_init(&ctx, interp->inst), "init run context"); + + ut64 existing_block_start = single_op_existing_block ? 0x10018 : 0x10008; + ut64 prepended_block_start = existing_block_start - (single_op_prepend ? 4 : 8); + + RzInterpAbstrState *as = rz_interp_abstr_state_new(interp->inst); + rz_interp_abstr_state_set_pc_const(as, existing_block_start); + rz_interp_run_push(&ctx, as, false); + rz_interp_abstr_state_free(interp->inst, as); + + as = rz_interp_abstr_state_new(interp->inst); + rz_interp_abstr_state_set_pc_const(as, prepended_block_start); + rz_interp_run_push(&ctx, as, false); + rz_interp_abstr_state_free(interp->inst, as); + + mu_assert_eq(blocks_count(&ctx), 2, "blocks count"); + RzInterpBlock *block = rz_interp_block_at(&ctx, prepended_block_start); + mu_assert_notnull(block, "block"); + + const RzILCacheBlock *il_block = rz_il_cache_lift_il_block(interp->il_cache, prepended_block_start); + rz_interp_block_resolve_bounds(&ctx, block, il_block); + + mu_assert_true(block->bounds_resolved, "bounds resolved"); + mu_assert_eq(rz_interp_block_get_start(block), prepended_block_start, "block start"); + mu_assert_eq(rz_interp_block_get_end(block), existing_block_start - 1, "block end"); + mu_assert_eq(rz_vector_len(&block->insn_offsets), single_op_prepend ? 0 : 1, "insn count"); + if (!single_op_prepend) { + mu_assert_eq(*(ut16 *)rz_vector_index_ptr(&block->insn_offsets, 0), 0x04, "insn offset"); + } + + rz_interp_run_context_fini(&ctx); + interp_free(interp); + mu_end; +} + +bool test_interp_block_resolve_bounds_split(bool single_op_existing_block, bool single_op_split) { + // single_op_existing_block and single_op_prepend are for testing for potential bugs in insn offset handling + + TestInterp *interp = interp_new("arm", 64, 0x10000, "hex://" + // --- blocks for single_op_split == false + "000080d2" // 0x00 mov x0, 0 <- existing block + "200080d2" // 0x04 mov x0, 1 <- alternative entry if single_op_existing_block + // --- + "400080d2" // 0x08 mov x0, 2 <- splitting block + "c0035fd6" // 0x0c ret + // --- + + // --- blocks for single_op_split == false + // 0x00 mov x0, 0 + // 0x04 mov x0, 1 <- existing block + // 0x08 mov x0, 2 <- alternative entry if single_op_existing_block + // --- + // 0x0c ret <- splitting block + // --- + ); + + RzInterpRunContext ctx; + mu_assert_true(rz_interp_run_context_init(&ctx, interp->inst), "init run context"); + + ut64 splitting_block_start = single_op_split ? 0x1000c : 0x10008; + ut64 existing_block_start = splitting_block_start - (single_op_existing_block ? 4 : 8); + + RzInterpAbstrState *as = rz_interp_abstr_state_new(interp->inst); + rz_interp_abstr_state_set_pc_const(as, existing_block_start); + rz_interp_run_push(&ctx, as, false); + rz_interp_abstr_state_free(interp->inst, as); + RzInterpBlock *existing_block = rz_interp_block_at(&ctx, existing_block_start); + mu_assert_notnull(existing_block, "block"); + + const RzILCacheBlock *il_block = rz_il_cache_lift_il_block(interp->il_cache, existing_block_start); + rz_interp_block_resolve_bounds(&ctx, existing_block, il_block); + mu_assert_true(existing_block->bounds_resolved, "bounds resolved"); + mu_assert_eq(rz_interp_block_get_start(existing_block), existing_block_start, "block start"); + mu_assert_eq(rz_interp_block_get_end(existing_block), splitting_block_start + (single_op_split ? 3 : 7), "block end"); + size_t off_count_expect = (single_op_existing_block ? 0 : 1) + (single_op_split ? 1 : 2); + mu_assert_eq(rz_vector_len(&existing_block->insn_offsets), off_count_expect, "insn count"); + for (size_t i = 0; i < off_count_expect; i++) { + mu_assert_eq(*(ut16 *)rz_vector_index_ptr(&existing_block->insn_offsets, i), 4 + 4 * i, "insn offset"); + } + existing_block->fallthrough = false; + ut64 target = 0x12345; + rz_vector_push(&existing_block->jump_targets, &target); + + as = rz_interp_abstr_state_new(interp->inst); + rz_interp_abstr_state_set_pc_const(as, splitting_block_start); + rz_interp_run_push(&ctx, as, false); + rz_interp_abstr_state_free(interp->inst, as); + + mu_assert_eq(blocks_count(&ctx), 2, "blocks count"); + RzInterpBlock *splitting_block = rz_interp_block_at(&ctx, splitting_block_start); + mu_assert_notnull(splitting_block, "block"); + + il_block = rz_il_cache_lift_il_block(interp->il_cache, splitting_block_start); + rz_interp_block_resolve_bounds(&ctx, splitting_block, il_block); + + mu_assert_true(splitting_block->bounds_resolved, "bounds resolved"); + mu_assert_eq(rz_interp_block_get_start(splitting_block), splitting_block_start, "block start"); + mu_assert_eq(rz_interp_block_get_end(splitting_block), splitting_block_start + (single_op_split ? 3 : 7), "block end"); + mu_assert_eq(rz_vector_len(&splitting_block->insn_offsets), single_op_split ? 0 : 1, "insn count"); + if (!single_op_split) { + mu_assert_eq(*(ut16 *)rz_vector_index_ptr(&splitting_block->insn_offsets, 0), 0x04, "insn offset"); + } + + // existing has been modified to go only until the splitting block + mu_assert_true(existing_block->bounds_resolved, "bounds resolved"); + mu_assert_eq(rz_interp_block_get_start(existing_block), existing_block_start, "block start"); + mu_assert_eq(rz_interp_block_get_end(existing_block), splitting_block_start - 1, "block end"); + mu_assert_eq(rz_vector_len(&existing_block->insn_offsets), single_op_existing_block ? 0 : 1, "insn count"); + if (!single_op_existing_block) { + mu_assert_eq(*(ut16 *)rz_vector_index_ptr(&existing_block->insn_offsets, 0), 0x04, "insn offset"); + } + mu_assert_true(existing_block->fallthrough, "existing fallthrough"); + mu_assert_eq(rz_vector_len(&existing_block->jump_targets), 0, "existing jump targets"); + + rz_interp_run_context_fini(&ctx); + interp_free(interp); + mu_end; +} + +bool test_interp_cfg_single_block(void) { + TestInterp *interp = interp_new("arm", 64, 0x10000, "hex://" + "600880d2" // 0x00 mov x0, 0x43 + "c0035fd6" // 0x04 ret + ); + mu_assert_notnull(interp, "init"); + RzInterpResult *res = rz_interp_run(interp->inst, 0x10000, RZ_INTERP_RESULT_DIMEN_BASE); + + EXTRACT_RESULT(res, 1); + mu_assert_eq(res->entry, 0x10000, "result entry"); + ASSERT_BLOCK(0, 0x10000, 0x10008, false, UT64_MAX); + + rz_interp_result_apply_to_analysis(res, interp->analysis); + RzAnalysisFunction *fcn = rz_analysis_get_function_at(interp->analysis, 0x10000); + mu_assert_notnull(fcn, "analysis function"); + mu_assert_eq(rz_pvector_len(fcn->bbs), 1, "analysis block count"); + ASSERT_ANALYSIS_BLOCK(rz_pvector_at(fcn->bbs, 0), 0x10000, 0x10008, UT64_MAX, UT64_MAX); + + interp_free(interp); + mu_end; +} + +bool test_interp_cfg_direct_jmp(void) { + TestInterp *interp = interp_new("arm", 64, 0x10000, "hex://" + "600880d2" // 0x00 mov x0, 0x43 + "03000014" // 0x04 b 0x10 --- + "1f2003d5" // 0x08 nop | + "1f2003d5" // 0x0c nop | + "400880d2" // 0x10 mov x0, 0x42 <-- + "c0035fd6" // 0x14 ret + ); + mu_assert_notnull(interp, "init"); + RzInterpResult *res = rz_interp_run(interp->inst, 0x10000, RZ_INTERP_RESULT_DIMEN_BASE); + + EXTRACT_RESULT(res, 2); + mu_assert_eq(res->entry, 0x10000, "result entry"); + ASSERT_BLOCK(0, 0x10000, 0x10008, false, 0x10010); + ASSERT_BLOCK(1, 0x10010, 0x10018, false, UT64_MAX); + + rz_interp_result_apply_to_analysis(res, interp->analysis); + RzAnalysisFunction *fcn = rz_analysis_get_function_at(interp->analysis, 0x10000); + mu_assert_notnull(fcn, "analysis function"); + mu_assert_eq(rz_pvector_len(fcn->bbs), 2, "analysis block count"); + ASSERT_ANALYSIS_BLOCK(rz_pvector_at(fcn->bbs, 0), 0x10000, 0x10008, 0x10010, UT64_MAX); + ASSERT_ANALYSIS_BLOCK(rz_pvector_at(fcn->bbs, 1), 0x10010, 0x10018, UT64_MAX, UT64_MAX); + + interp_free(interp); + mu_end; +} + +bool test_interp_cfg_branch(void) { + TestInterp *interp = interp_new("arm", 64, 0x10000, "hex://" + "600880d2" // 0x00 mov x0, 0x43 + "69000054" // 0x04 b.ls 0x10 --- + "1f2003d5" // 0x08 nop | + "c0035fd6" // 0x0c ret | + "400880d2" // 0x10 mov x0, 0x42 <-- + "c0035fd6" // 0x14 ret + ); + mu_assert_notnull(interp, "init"); + RzInterpResult *res = rz_interp_run(interp->inst, 0x10000, RZ_INTERP_RESULT_DIMEN_BASE); + + EXTRACT_RESULT(res, 3); + mu_assert_eq(res->entry, 0x10000, "result entry"); + ASSERT_BLOCK(0, 0x10000, 0x10008, true, 0x10010); + ASSERT_BLOCK(1, 0x10008, 0x10010, false, UT64_MAX); + ASSERT_BLOCK(2, 0x10010, 0x10018, false, UT64_MAX); + + rz_interp_result_apply_to_analysis(res, interp->analysis); + RzAnalysisFunction *fcn = rz_analysis_get_function_at(interp->analysis, 0x10000); + mu_assert_notnull(fcn, "analysis function"); + mu_assert_eq(rz_pvector_len(fcn->bbs), 3, "analysis block count"); + ASSERT_ANALYSIS_BLOCK(rz_pvector_at(fcn->bbs, 0), 0x10000, 0x10008, 0x10010, 0x10008); + ASSERT_ANALYSIS_BLOCK(rz_pvector_at(fcn->bbs, 1), 0x10008, 0x10010, UT64_MAX, UT64_MAX); + ASSERT_ANALYSIS_BLOCK(rz_pvector_at(fcn->bbs, 2), 0x10010, 0x10018, UT64_MAX, UT64_MAX); + + interp_free(interp); + mu_end; +} + +bool test_interp_cfg_branch_join(void) { + TestInterp *interp = interp_new("arm", 64, 0x10000, "hex://" + "1f8c04f1" // 0x00 cmp x0, 0x123 + "69000054" // 0x04 b.ls 0x10 --- + // | + "600880d2" // 0x08 mov x0, 0x43 | + "02000014" // 0x0c b 0x14 -- | -- + // | | + "400880d2" // 0x10 mov x0, 0x42 <-- | + // | + "c0035fd6" // 0x14 ret <------ + ); + mu_assert_notnull(interp, "init"); + RzInterpResult *res = rz_interp_run(interp->inst, 0x10000, RZ_INTERP_RESULT_DIMEN_BASE); + + EXTRACT_RESULT(res, 4); + mu_assert_eq(res->entry, 0x10000, "result entry"); + ASSERT_BLOCK(0, 0x10000, 0x10008, true, 0x10010); + ASSERT_BLOCK(1, 0x10008, 0x10010, false, 0x10014); + ASSERT_BLOCK(2, 0x10010, 0x10014, true, UT64_MAX); + ASSERT_BLOCK(3, 0x10014, 0x10018, false, UT64_MAX); + + rz_interp_result_apply_to_analysis(res, interp->analysis); + RzAnalysisFunction *fcn = rz_analysis_get_function_at(interp->analysis, 0x10000); + mu_assert_notnull(fcn, "analysis function"); + mu_assert_eq(rz_pvector_len(fcn->bbs), 4, "analysis block count"); + ASSERT_ANALYSIS_BLOCK(rz_pvector_at(fcn->bbs, 0), 0x10000, 0x10008, 0x10010, 0x10008); + ASSERT_ANALYSIS_BLOCK(rz_pvector_at(fcn->bbs, 1), 0x10008, 0x10010, 0x10014, UT64_MAX); + ASSERT_ANALYSIS_BLOCK(rz_pvector_at(fcn->bbs, 2), 0x10010, 0x10014, 0x10014, UT64_MAX); + ASSERT_ANALYSIS_BLOCK(rz_pvector_at(fcn->bbs, 3), 0x10014, 0x10018, UT64_MAX, UT64_MAX); + + interp_free(interp); + mu_end; +} + +bool test_interp_cfg_multi_entry_fallthrough_jmp(bool swap) { + // This is about a sequence of instructions that ends with some control flow instruction, + // but has two entrypoints into it, where one entrypoint falls through into the other. + // The swapping of the branches is there because potential bugs in the interpreter may + // depend on the order of block addresses added. + TestInterp *interp = interp_new("arm", 64, 0x10000, + !swap ? "hex://" + "a9000054" // 0x00 b.ls 0x14 ------ + "02000014" // 0x04 b 0xc --- | + "1f2003d5" // 0x08 nop | | + "400880d2" // 0x0c mov x0, 0x42 <-- | + "1f2003d5" // 0x10 nop | + "610880d2" // 0x14 mov x1, 0x43 <----- + "c0035fd6" // 0x18 ret + : "hex://" + "69000054" // 0x00 b.ls 0xc --- + "04000014" // 0x04 b 0x14 -- | -- + "1f2003d5" // 0x08 nop | | + "400880d2" // 0x0c mov x0, 0x42 <-- | + "1f2003d5" // 0x10 nop | + "610880d2" // 0x14 mov x1, 0x43 <------ + "c0035fd6" // 0x18 ret + ); + mu_assert_notnull(interp, "init"); + RzInterpResult *res = rz_interp_run(interp->inst, 0x10000, RZ_INTERP_RESULT_DIMEN_BASE); + + EXTRACT_RESULT(res, 4); + mu_assert_eq(res->entry, 0x10000, "result entry"); + ASSERT_BLOCK(0, 0x10000, 0x10004, true, !swap ? 0x10014 : 0x1000c); + ASSERT_BLOCK(1, 0x10004, 0x10008, false, !swap ? 0x1000c : 0x10014); + ASSERT_BLOCK(2, 0x1000c, 0x10014, true, UT64_MAX); + ASSERT_BLOCK(3, 0x10014, 0x1001c, false, UT64_MAX); + + rz_interp_result_apply_to_analysis(res, interp->analysis); + RzAnalysisFunction *fcn = rz_analysis_get_function_at(interp->analysis, 0x10000); + mu_assert_notnull(fcn, "analysis function"); + mu_assert_eq(rz_pvector_len(fcn->bbs), 4, "analysis block count"); + ASSERT_ANALYSIS_BLOCK(rz_pvector_at(fcn->bbs, 0), 0x10000, 0x10004, !swap ? 0x10014 : 0x1000c, 0x10004); + ASSERT_ANALYSIS_BLOCK(rz_pvector_at(fcn->bbs, 1), 0x10004, 0x10008, !swap ? 0x1000c : 0x10014, UT64_MAX); + ASSERT_ANALYSIS_BLOCK(rz_pvector_at(fcn->bbs, 2), 0x1000c, 0x10014, 0x10014, UT64_MAX); + ASSERT_ANALYSIS_BLOCK(rz_pvector_at(fcn->bbs, 3), 0x10014, 0x1001c, UT64_MAX, UT64_MAX); + + interp_free(interp); + mu_end; +} + +bool test_interp_cfg_multi_entry_fallthrough_jmp_before() { + TestInterp *interp = interp_new("arm", 64, 0x10000, "hex://" + "400880d2" // 0x00 mov x0, 0x42 <-- + "1f8c04f1" // 0x04 cmp x0, 0x123 | + "c9ffff54" // 0x08 b.ls 0 --- + "c0035fd6" // 0x0c ret + ); + mu_assert_notnull(interp, "init"); + RzInterpResult *res = rz_interp_run(interp->inst, 0x10008, RZ_INTERP_RESULT_DIMEN_BASE); + + EXTRACT_RESULT(res, 3); + mu_assert_eq(res->entry, 0x10008, "result entry"); + ASSERT_BLOCK(0, 0x10000, 0x10008, true, UT64_MAX); + ASSERT_BLOCK(1, 0x10008, 0x1000c, true, 0x10000); + ASSERT_BLOCK(2, 0x1000c, 0x10010, false, UT64_MAX); + + rz_interp_result_apply_to_analysis(res, interp->analysis); + RzAnalysisFunction *fcn = rz_analysis_get_function_at(interp->analysis, 0x10008); + mu_assert_notnull(fcn, "analysis function"); + mu_assert_eq(rz_pvector_len(fcn->bbs), 3, "analysis block count"); + ASSERT_ANALYSIS_BLOCK(rz_pvector_at(fcn->bbs, 0), 0x10000, 0x10008, 0x10008, UT64_MAX); + ASSERT_ANALYSIS_BLOCK(rz_pvector_at(fcn->bbs, 1), 0x10008, 0x1000c, 0x10000, 0x1000c); + ASSERT_ANALYSIS_BLOCK(rz_pvector_at(fcn->bbs, 2), 0x1000c, 0x10010, UT64_MAX, UT64_MAX); + + interp_free(interp); + mu_end; +} + +bool test_interp_cfg_multi_entry_fallthrough_jmp_inside_self() { + TestInterp *interp = interp_new("arm", 64, 0x10000, "hex://" + "600080d2" // 0x00 mov x0, 3 + "000400d1" // 0x04 sub x0, x0, 1 <-- + "e1ffff54" // 0x08 b.ne 4 --- + "c0035fd6" // 0x0c ret + ); + mu_assert_notnull(interp, "init"); + RzInterpResult *res = rz_interp_run(interp->inst, 0x10000, RZ_INTERP_RESULT_DIMEN_BASE); + + EXTRACT_RESULT(res, 3); + mu_assert_eq(res->entry, 0x10000, "result entry"); + ASSERT_BLOCK(0, 0x10000, 0x10004, true, UT64_MAX); + ASSERT_BLOCK(1, 0x10004, 0x1000c, true, 0X10004); + ASSERT_BLOCK(2, 0x1000c, 0x10010, false, UT64_MAX); + + rz_interp_result_apply_to_analysis(res, interp->analysis); + RzAnalysisFunction *fcn = rz_analysis_get_function_at(interp->analysis, 0x10000); + mu_assert_notnull(fcn, "analysis function"); + mu_assert_eq(rz_pvector_len(fcn->bbs), 3, "analysis block count"); + ASSERT_ANALYSIS_BLOCK(rz_pvector_at(fcn->bbs, 0), 0x10000, 0x10004, 0x10004, UT64_MAX); + ASSERT_ANALYSIS_BLOCK(rz_pvector_at(fcn->bbs, 1), 0x10004, 0x1000c, 0x10004, 0x1000c); + ASSERT_ANALYSIS_BLOCK(rz_pvector_at(fcn->bbs, 2), 0x1000c, 0x10010, UT64_MAX, UT64_MAX); + + interp_free(interp); + mu_end; +} + +bool test_interp_cfg_multi_entry_fallthrough_jmp_inside_other() { + TestInterp *interp = interp_new("arm", 64, 0x10000, "hex://" + "600080d2" // 0x00 mov x0, 3 + "000400d1" // 0x04 sub x0, x0, 1 <-------- + "02000014" // 0x08 b 0x10 --- | + "1f2003d5" // 0x0c nop | | + "a1ffff54" // 0x10 b.ne 4 <-- --- + "c0035fd6" // 0x14 ret + ); + mu_assert_notnull(interp, "init"); + RzInterpResult *res = rz_interp_run(interp->inst, 0x10000, RZ_INTERP_RESULT_DIMEN_BASE); + + EXTRACT_RESULT(res, 4); + mu_assert_eq(res->entry, 0x10000, "result entry"); + ASSERT_BLOCK(0, 0x10000, 0x10004, true, UT64_MAX); + ASSERT_BLOCK(1, 0x10004, 0x1000c, false, 0x10010); + ASSERT_BLOCK(2, 0x10010, 0x10014, true, 0x10004); + ASSERT_BLOCK(3, 0x10014, 0x10018, false, UT64_MAX); + + rz_interp_result_apply_to_analysis(res, interp->analysis); + RzAnalysisFunction *fcn = rz_analysis_get_function_at(interp->analysis, 0x10000); + mu_assert_notnull(fcn, "analysis function"); + mu_assert_eq(rz_pvector_len(fcn->bbs), 4, "analysis block count"); + ASSERT_ANALYSIS_BLOCK(rz_pvector_at(fcn->bbs, 0), 0x10000, 0x10004, 0x10004, UT64_MAX); + ASSERT_ANALYSIS_BLOCK(rz_pvector_at(fcn->bbs, 1), 0x10004, 0x1000c, 0x10010, UT64_MAX); + ASSERT_ANALYSIS_BLOCK(rz_pvector_at(fcn->bbs, 2), 0x10010, 0x10014, 0x10004, 0x10014); + ASSERT_ANALYSIS_BLOCK(rz_pvector_at(fcn->bbs, 3), 0x10014, 0x10018, UT64_MAX, UT64_MAX); + + interp_free(interp); + mu_end; +} + +bool test_interp_cfg_call_link_register(void) { + TestInterp *interp = interp_new("arm", 64, 0x10000, "hex://" + "600880d2" // 0x00 mov x0, 0x43 + "ff430094" // 0x04 bl 0x11000 + "c0035fd6" // 0x08 ret + ); + mu_assert_notnull(interp, "init"); + RzInterpResult *res = rz_interp_run(interp->inst, 0x10000, RZ_INTERP_RESULT_DIMEN_BASE); + + EXTRACT_RESULT(res, 2); + mu_assert_eq(res->entry, 0x10000, "result entry"); + ASSERT_BLOCK(0, 0x10000, 0x10008, true, UT64_MAX); + ASSERT_BLOCK(1, 0x10008, 0x1000c, false, UT64_MAX); + + rz_interp_result_apply_to_analysis(res, interp->analysis); + RzAnalysisFunction *fcn = rz_analysis_get_function_at(interp->analysis, 0x10000); + mu_assert_notnull(fcn, "analysis function"); + mu_assert_eq(rz_pvector_len(fcn->bbs), 1, "analysis block count"); + ASSERT_ANALYSIS_BLOCK(rz_pvector_at(fcn->bbs, 0), 0x10000, 0x1000c, UT64_MAX, UT64_MAX); + + interp_free(interp); + mu_end; +} + +bool test_interp_cfg_call_multi_insn(void) { + TestInterp *interp = interp_new("arm", 32, 0x10000, "hex://" + // this pattern is common on ARMv4 to perform an indirect call + "110aa0e3" // 0x00 mov r0, 0xff000 + "0fe0a0e1" // 0x04 mov lr, pc + "00f0a0e1" // 0x08 mov pc, r0 + "1eff2fe1" // 0x0c bx lr + ); + mu_assert_notnull(interp, "init"); + RzInterpResult *res = rz_interp_run(interp->inst, 0x10000, RZ_INTERP_RESULT_DIMEN_BASE); + + EXTRACT_RESULT(res, 2); + mu_assert_eq(res->entry, 0x10000, "result entry"); + ASSERT_BLOCK(0, 0x10000, 0x1000c, true, UT64_MAX); + ASSERT_BLOCK(1, 0x1000c, 0x10010, false, UT64_MAX); + + rz_interp_result_apply_to_analysis(res, interp->analysis); + RzAnalysisFunction *fcn = rz_analysis_get_function_at(interp->analysis, 0x10000); + mu_assert_notnull(fcn, "analysis function"); + mu_assert_eq(rz_pvector_len(fcn->bbs), 1, "analysis block count"); + ASSERT_ANALYSIS_BLOCK(rz_pvector_at(fcn->bbs, 0), 0x10000, 0x10010, UT64_MAX, UT64_MAX); + + interp_free(interp); + mu_end; +} + +bool test_interp_cfg_call_ret_in_memory(void) { + TestInterp *interp = interp_new("x86", 64, 0x10000, "hex://" + "89c1" // 0x00 mov ecx, eax + "ff14cde0000008" // 0x02 call qword [rcx*8+0x80000e0] + "c3" // 0x09 ret + ); + mu_assert_notnull(interp, "init"); + RzInterpResult *res = rz_interp_run(interp->inst, 0x10000, RZ_INTERP_RESULT_DIMEN_BASE); + + EXTRACT_RESULT(res, 2); + mu_assert_eq(res->entry, 0x10000, "result entry"); + ASSERT_BLOCK(0, 0x10000, 0x10009, true, UT64_MAX); + ASSERT_BLOCK(1, 0x10009, 0x1000a, false, UT64_MAX); + + rz_interp_result_apply_to_analysis(res, interp->analysis); + RzAnalysisFunction *fcn = rz_analysis_get_function_at(interp->analysis, 0x10000); + mu_assert_notnull(fcn, "analysis function"); + mu_assert_eq(rz_pvector_len(fcn->bbs), 1, "analysis block count"); + ASSERT_ANALYSIS_BLOCK(rz_pvector_at(fcn->bbs, 0), 0x10000, 0x1000a, UT64_MAX, UT64_MAX); + + interp_free(interp); + mu_end; +} + +static int xref_cmp(const void *a, const void *b, void *user) { + const RzAnalysisXRef *ax = a; + const RzAnalysisXRef *bx = b; + return (st64)ax->from - (st64)bx->from; // addrs in our tests are small enough that this is fine +} + +bool test_interp_xrefs(void) { + TestInterp *interp = interp_new("arm", 64, 0x10000, "hex://" + "600880d2" // 0x00 mov x0, 0x43 + "a9000054" // 0x04 b.ls 0x18 ------ + "81000090" // 0x08 adrp x1, 0x20000 | + "230440f9" // 0x0c ldr x3, [x1, 8] | + "200800f9" // 0x10 str x0, [x1, 0x10] | + "fb030094" // 0x14 bl 0x1000 | + "400880d2" // 0x18 mov x0, 0x42 <----- + "c0035fd6" // 0x1c ret + ); + mu_assert_notnull(interp, "init"); + RzIODesc *data_desc = rz_io_open_at(interp->io, "malloc://0x100", RZ_PERM_RW, 0644, 0x20000, NULL); + mu_assert_notnull(data_desc, "load data"); + RzInterpResult *res = rz_interp_run(interp->inst, 0x10000, RZ_INTERP_RESULT_DIMEN_XREFS); + + EXTRACT_RESULT(res, 3); + mu_assert_eq(res->entry, 0x10000, "result entry"); + ASSERT_BLOCK(0, 0x10000, 0x10008, true, 0x10018); + ASSERT_BLOCK(1, 0x10008, 0x10018, true, UT64_MAX); + ASSERT_BLOCK(2, 0x10018, 0x10020, false, UT64_MAX); + + mu_assert_eq(rz_vector_len(&res->xrefs), 4, "xrefs count"); + rz_vector_sort(&res->xrefs, xref_cmp, false, NULL); + ASSERT_XREF(0, 0x10004, 0x10018, RZ_ANALYSIS_XREF_TYPE_CODE); + ASSERT_XREF(1, 0x1000c, 0x20008, RZ_ANALYSIS_XREF_TYPE_MEM_READ); + ASSERT_XREF(2, 0x10010, 0x20010, RZ_ANALYSIS_XREF_TYPE_MEM_WRITE); + ASSERT_XREF(3, 0x10014, 0x11000, RZ_ANALYSIS_XREF_TYPE_CALL); + + rz_interp_result_apply_to_analysis(res, interp->analysis); + RzList *l = rz_analysis_xrefs_get_from(interp->analysis, 0x1000c); + mu_assert_eq(rz_list_length(l), 1, "analysis xref"); + RzAnalysisXRef *x = rz_list_first_val(l); + mu_assert_eq(x->from, 0x1000c, "analysis xref from"); + mu_assert_eq(x->to, 0x20008, "analysis xref to"); + mu_assert_eq(x->type, RZ_ANALYSIS_XREF_TYPE_MEM_READ, "analysis xref type"); + + interp_free(interp); + mu_end; +} + +bool test_interp_comments(void) { + TestInterp *interp = interp_new("arm", 64, 0x10000, "hex://" + "600880d2" // 0x00 mov x0, 0x43 + "a9000054" // 0x04 b.ls 0x18 ------ + "81000090" // 0x08 adrp x1, 0x20000 | + "230440f9" // 0x0c ldr x3, [x1, 8] | + "200800f9" // 0x10 str x0, [x1, 0x10] | + "fb030094" // 0x14 bl 0x1000 | + "400880d2" // 0x18 mov x0, 0x42 <----- + "c0035fd6" // 0x1c ret + ); + mu_assert_notnull(interp, "init"); + RzIODesc *data_desc = rz_io_open_at(interp->io, "malloc://0x100", RZ_PERM_RW, 0644, 0x20000, NULL); + mu_assert_notnull(data_desc, "load data"); + RzInterpResult *res = rz_interp_run(interp->inst, 0x10000, RZ_INTERP_RESULT_DIMEN_COMMENTS); + + EXTRACT_RESULT(res, 3); + mu_assert_eq(res->entry, 0x10000, "result entry"); + ASSERT_BLOCK(0, 0x10000, 0x10008, true, 0x10018); + ASSERT_BLOCK(1, 0x10008, 0x10018, true, UT64_MAX); + ASSERT_BLOCK(2, 0x10018, 0x10020, false, UT64_MAX); + + mu_assert_eq(ht_up_size(res->comments), 8, "comments count"); + mu_assert_streq(ht_up_find(res->comments, 0x10000, NULL), "⊤; <-", "comment"); + mu_assert_streq(ht_up_find(res->comments, 0x10004, NULL), "x0 = 0x43; ->", "comment"); + mu_assert_streq(ht_up_find(res->comments, 0x10008, NULL), "x0 = 0x43; <-", "comment"); + mu_assert_streq(ht_up_find(res->comments, 0x1000c, NULL), "x1 = 0x20000, x0 = 0x43", "comment"); + + // contents from here could become different when mem is implemented + mu_assert_streq(ht_up_find(res->comments, 0x10010, NULL), "x1 = 0x20000, x0 = 0x43", "comment"); + mu_assert_streq(ht_up_find(res->comments, 0x10014, NULL), "x1 = 0x20000, x0 = 0x43; ->", "comment"); + mu_assert_streq(ht_up_find(res->comments, 0x10018, NULL), "⊤; <-", "comment"); + mu_assert_streq(ht_up_find(res->comments, 0x10010, NULL), "x1 = 0x20000, x0 = 0x43", "comment"); + mu_assert_streq(ht_up_find(res->comments, 0x1001c, NULL), "x0 = 0x42; ->", "comment"); + + rz_interp_result_apply_to_analysis(res, interp->analysis); + + const char *acmt = rz_meta_get_string(interp->analysis, RZ_META_TYPE_COMMENT, 0x10008); + mu_assert_streq(acmt, "x0 = 0x43; <-", "analysis comment"); + + interp_free(interp); + mu_end; +} + +bool all_tests() { + mu_run_test(test_interp_block_resolve_bounds_single); + mu_run_test(test_interp_block_resolve_bounds_prepend, false, false); + mu_run_test(test_interp_block_resolve_bounds_prepend, false, true); + mu_run_test(test_interp_block_resolve_bounds_prepend, true, false); + mu_run_test(test_interp_block_resolve_bounds_prepend, true, true); + mu_run_test(test_interp_block_resolve_bounds_split, false, false); + mu_run_test(test_interp_block_resolve_bounds_split, false, true); + mu_run_test(test_interp_block_resolve_bounds_split, true, false); + mu_run_test(test_interp_block_resolve_bounds_split, true, true); + mu_run_test(test_interp_cfg_single_block); + mu_run_test(test_interp_cfg_direct_jmp); + mu_run_test(test_interp_cfg_branch); + mu_run_test(test_interp_cfg_branch_join); + mu_run_test(test_interp_cfg_multi_entry_fallthrough_jmp, false); + mu_run_test(test_interp_cfg_multi_entry_fallthrough_jmp, true); + mu_run_test(test_interp_cfg_multi_entry_fallthrough_jmp_before); + mu_run_test(test_interp_cfg_multi_entry_fallthrough_jmp_inside_self); + mu_run_test(test_interp_cfg_multi_entry_fallthrough_jmp_inside_other); + mu_run_test(test_interp_cfg_call_link_register); + mu_run_test(test_interp_cfg_call_multi_insn); + mu_run_test(test_interp_cfg_call_ret_in_memory); + mu_run_test(test_interp_xrefs); + mu_run_test(test_interp_comments); + return tests_passed != tests_run; +} + +mu_main(all_tests) diff --git a/test/unit/test_serialize_config.c b/test/unit/test_serialize_config.c index 122a08d316b..6832a2fea19 100644 --- a/test/unit/test_serialize_config.c +++ b/test/unit/test_serialize_config.c @@ -5,10 +5,12 @@ #include "minunit.h" #include "test_sdb.h" -Sdb *ref_db() { +Sdb *ref_db(bool excluded) { Sdb *db = sdb_new0(); - sdb_set(db, "somestring", "somevalue"); - sdb_set(db, "someint", "42"); + if (!excluded) { + sdb_set(db, "somestring", "somevalue"); + sdb_set(db, "someint", "42"); + } sdb_set(db, "somebiggerint", "0x00001337"); return db; } @@ -20,10 +22,32 @@ bool test_config_save() { rz_config_set_i(config, "somebiggerint", 0x1337); Sdb *db = sdb_new0(); - rz_serialize_config_save(db, config); + rz_serialize_config_save(db, config, NULL); rz_config_free(config); - Sdb *expected = ref_db(); + Sdb *expected = ref_db(false); + assert_sdb_eq(db, expected, "config save"); + sdb_free(db); + sdb_free(expected); + mu_end; +} + +bool test_config_save_exclude() { + static const char *exclude[] = { + "somestring", + "someint", + NULL + }; + RzConfig *config = rz_config_new(NULL); + rz_config_set(config, "somestring", "somevalue"); + rz_config_set_i(config, "someint", 42); + rz_config_set_i(config, "somebiggerint", 0x1337); + + Sdb *db = sdb_new0(); + rz_serialize_config_save(db, config, exclude); + rz_config_free(config); + + Sdb *expected = ref_db(true); assert_sdb_eq(db, expected, "config save"); sdb_free(db); sdb_free(expected); @@ -36,7 +60,7 @@ bool test_config_load() { rz_config_set_i(config, "someint", 0); rz_config_set_i(config, "somebiggerint", 0); - Sdb *db = ref_db(); + Sdb *db = ref_db(false); sdb_set(db, "sneaky", "not part of config"); bool loaded = rz_serialize_config_load(db, config, NULL); sdb_free(db); @@ -61,7 +85,7 @@ bool test_config_load_exclude() { rz_config_set_i(config, "someint", 123); rz_config_set_i(config, "somebiggerint", 0); - Sdb *db = ref_db(); + Sdb *db = ref_db(false); // must load non-excluded here! bool loaded = rz_serialize_config_load(db, config, exclude); sdb_free(db); mu_assert("load success", loaded); @@ -76,9 +100,10 @@ bool test_config_load_exclude() { int all_tests() { mu_run_test(test_config_save); + mu_run_test(test_config_save_exclude); mu_run_test(test_config_load); mu_run_test(test_config_load_exclude); return tests_passed != tests_run; } -mu_main(all_tests) \ No newline at end of file +mu_main(all_tests)