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 @@
+
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)