RzAnalysisILVM wraps around the low-level RzILVM and enables emulation of real code from disassembly, rather than raw IL. Analysis plugins now don't actively initialize the vm anymore, but return a fully declarative description RzAnalysisILConfig of how to set up the vm and optionally its initial state. This also enables multiple IL vms to exist at the same time as plugins can not mess with the global vm anymore. See the added integration test for an example.
307 lines
7.9 KiB
C
307 lines
7.9 KiB
C
// SPDX-FileCopyrightText: 2008-2020 pancake <pancake@nopcode.org>
|
|
// SPDX-License-Identifier: LGPL-3.0-only
|
|
|
|
#include <rz_debug.h>
|
|
|
|
/* Old debug trace implementation */
|
|
RZ_API RzDebugTrace *rz_debug_trace_new(void) {
|
|
RzDebugTrace *t = RZ_NEW0(RzDebugTrace);
|
|
if (!t) {
|
|
return NULL;
|
|
}
|
|
t->tag = 1; // UT32_MAX;
|
|
t->addresses = NULL;
|
|
t->enabled = false;
|
|
t->traces = rz_list_new();
|
|
if (!t->traces) {
|
|
rz_debug_trace_free(t);
|
|
return NULL;
|
|
}
|
|
t->traces->free = free;
|
|
t->ht = ht_pp_new0();
|
|
if (!t->ht) {
|
|
rz_debug_trace_free(t);
|
|
return NULL;
|
|
}
|
|
return t;
|
|
}
|
|
|
|
RZ_API void rz_debug_trace_free(RzDebugTrace *trace) {
|
|
if (!trace) {
|
|
return;
|
|
}
|
|
rz_list_purge(trace->traces);
|
|
free(trace->traces);
|
|
ht_pp_free(trace->ht);
|
|
RZ_FREE(trace);
|
|
}
|
|
|
|
// TODO: added overlap/mask support here...
|
|
// TODO: think about tagged traces
|
|
RZ_API int rz_debug_trace_tag(RzDebug *dbg, int tag) {
|
|
// if (tag>0 && tag<31) core->dbg->trace->tag = 1<<(sz-1);
|
|
return (dbg->trace->tag = (tag > 0) ? tag : UT32_MAX);
|
|
}
|
|
|
|
RZ_API bool rz_debug_trace_ins_before(RzDebug *dbg) {
|
|
RzListIter *it, *it_tmp;
|
|
RzAnalysisValue *val;
|
|
ut8 buf_pc[32];
|
|
|
|
// Analyze current instruction
|
|
ut64 pc = rz_debug_reg_get(dbg, dbg->reg->name[RZ_REG_NAME_PC]);
|
|
if (!dbg->iob.read_at) {
|
|
return false;
|
|
}
|
|
if (!dbg->iob.read_at(dbg->iob.io, pc, buf_pc, sizeof(buf_pc))) {
|
|
return false;
|
|
}
|
|
dbg->cur_op = RZ_NEW0(RzAnalysisOp);
|
|
if (!dbg->cur_op) {
|
|
return false;
|
|
}
|
|
if (!rz_analysis_op(dbg->analysis, dbg->cur_op, pc, buf_pc, sizeof(buf_pc), RZ_ANALYSIS_OP_MASK_VAL)) {
|
|
rz_analysis_op_free(dbg->cur_op);
|
|
dbg->cur_op = NULL;
|
|
return false;
|
|
}
|
|
|
|
// resolve mem write address
|
|
rz_list_foreach_safe (dbg->cur_op->access, it, it_tmp, val) {
|
|
switch (val->type) {
|
|
case RZ_ANALYSIS_VAL_REG:
|
|
if (!(val->access & RZ_ANALYSIS_ACC_W)) {
|
|
rz_list_delete(dbg->cur_op->access, it);
|
|
}
|
|
break;
|
|
case RZ_ANALYSIS_VAL_MEM:
|
|
if (val->memref > 32) {
|
|
eprintf("Error: adding changes to %d bytes in memory.\n", val->memref);
|
|
rz_list_delete(dbg->cur_op->access, it);
|
|
break;
|
|
}
|
|
|
|
if (val->access & RZ_ANALYSIS_ACC_W) {
|
|
// resolve memory address
|
|
ut64 addr = 0;
|
|
addr += val->delta;
|
|
if (val->seg) {
|
|
addr += rz_reg_get_value(dbg->reg, val->seg);
|
|
}
|
|
if (val->reg) {
|
|
addr += rz_reg_get_value(dbg->reg, val->reg);
|
|
}
|
|
if (val->regdelta) {
|
|
int mul = val->mul ? val->mul : 1;
|
|
addr += mul * rz_reg_get_value(dbg->reg, val->regdelta);
|
|
}
|
|
// resolve address into base for ins_after
|
|
val->base = addr;
|
|
} else {
|
|
rz_list_delete(dbg->cur_op->access, it);
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
RZ_API bool rz_debug_trace_ins_after(RzDebug *dbg) {
|
|
rz_return_val_if_fail(dbg->cur_op, false);
|
|
RzListIter *it;
|
|
RzAnalysisValue *val;
|
|
|
|
// Add reg/mem write change
|
|
rz_debug_reg_sync(dbg, RZ_REG_TYPE_ANY, false);
|
|
rz_list_foreach (dbg->cur_op->access, it, val) {
|
|
if (!(val->access & RZ_ANALYSIS_ACC_W)) {
|
|
continue;
|
|
}
|
|
|
|
switch (val->type) {
|
|
case RZ_ANALYSIS_VAL_REG: {
|
|
if (!val->reg) {
|
|
RZ_LOG_ERROR("invalid register, unable to trace register state\n");
|
|
continue;
|
|
}
|
|
ut64 data = rz_reg_get_value(dbg->reg, val->reg);
|
|
|
|
// add reg write
|
|
rz_debug_session_add_reg_change(dbg->session, val->reg->arena, val->reg->offset, data);
|
|
break;
|
|
}
|
|
case RZ_ANALYSIS_VAL_MEM: {
|
|
ut8 buf[32] = { 0 };
|
|
if (!dbg->iob.read_at(dbg->iob.io, val->base, buf, val->memref)) {
|
|
eprintf("Error reading memory at 0x%" PFMT64x "\n", val->base);
|
|
break;
|
|
}
|
|
|
|
// add mem write
|
|
size_t i;
|
|
for (i = 0; i < val->memref; i++) {
|
|
rz_debug_session_add_mem_change(dbg->session, val->base + i, buf[i]);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
rz_analysis_op_free(dbg->cur_op);
|
|
dbg->cur_op = NULL;
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* something happened at the given pc that we need to trace
|
|
*/
|
|
RZ_API int rz_debug_trace_pc(RzDebug *dbg, ut64 pc) {
|
|
ut8 buf[32];
|
|
RzAnalysisOp op = { 0 };
|
|
if (!dbg->iob.is_valid_offset(dbg->iob.io, pc, 0)) {
|
|
eprintf("trace_pc: cannot read memory at 0x%" PFMT64x "\n", pc);
|
|
return false;
|
|
}
|
|
(void)dbg->iob.read_at(dbg->iob.io, pc, buf, sizeof(buf));
|
|
if (rz_analysis_op(dbg->analysis, &op, pc, buf, sizeof(buf), RZ_ANALYSIS_OP_MASK_ESIL) < 1) {
|
|
eprintf("trace_pc: cannot get opcode size at 0x%" PFMT64x "\n", pc);
|
|
return false;
|
|
}
|
|
rz_debug_trace_op(dbg, &op);
|
|
rz_analysis_op_fini(&op);
|
|
return true;
|
|
}
|
|
|
|
RZ_API void rz_debug_trace_op(RzDebug *dbg, RzAnalysisOp *op) {
|
|
static ut64 oldpc = UT64_MAX; // Must trace the previously traced instruction
|
|
if (dbg->trace->enabled) {
|
|
if (dbg->analysis->esil) {
|
|
rz_analysis_esil_trace_op(dbg->analysis->esil, op);
|
|
} else {
|
|
if (dbg->verbose) {
|
|
eprintf("Run aeim to get dbg->analysis->esil initialized\n");
|
|
}
|
|
}
|
|
}
|
|
if (oldpc != UT64_MAX) {
|
|
rz_debug_trace_add(dbg, oldpc, op->size); // XXX review what this line really do
|
|
}
|
|
oldpc = op->addr;
|
|
}
|
|
|
|
RZ_API void rz_debug_trace_at(RzDebug *dbg, const char *str) {
|
|
// TODO: parse offsets and so use ut64 instead of strstr()
|
|
free(dbg->trace->addresses);
|
|
dbg->trace->addresses = (str && *str) ? strdup(str) : NULL;
|
|
}
|
|
|
|
RZ_API RzDebugTracepoint *rz_debug_trace_get(RzDebug *dbg, ut64 addr) {
|
|
int tag = dbg->trace->tag;
|
|
return ht_pp_find(dbg->trace->ht,
|
|
sdb_fmt("trace.%d.%" PFMT64x, tag, addr), NULL);
|
|
}
|
|
|
|
static int cmpaddr(const void *_a, const void *_b) {
|
|
const RzListInfo *a = _a, *b = _b;
|
|
return (rz_itv_begin(a->pitv) > rz_itv_begin(b->pitv)) ? 1 : (rz_itv_begin(a->pitv) < rz_itv_begin(b->pitv)) ? -1
|
|
: 0;
|
|
}
|
|
|
|
RZ_API void rz_debug_trace_list(RzDebug *dbg, int mode, ut64 offset) {
|
|
int tag = dbg->trace->tag;
|
|
RzListIter *iter;
|
|
bool flag = false;
|
|
RzList *info_list = rz_list_new();
|
|
if (!info_list && mode == '=') {
|
|
return;
|
|
}
|
|
RzDebugTracepoint *trace;
|
|
rz_list_foreach (dbg->trace->traces, iter, trace) {
|
|
if (!trace->tag || (tag & trace->tag)) {
|
|
switch (mode) {
|
|
case 'q':
|
|
dbg->cb_printf("0x%" PFMT64x "\n", trace->addr);
|
|
break;
|
|
case '=': {
|
|
RzListInfo *info = RZ_NEW0(RzListInfo);
|
|
if (!info) {
|
|
rz_list_free(info_list);
|
|
return;
|
|
}
|
|
info->pitv = (RzInterval){ trace->addr, trace->size };
|
|
info->vitv = info->pitv;
|
|
info->perm = -1;
|
|
info->name = rz_str_newf("%d", trace->times);
|
|
info->extra = rz_str_newf("%d", trace->count);
|
|
rz_list_append(info_list, info);
|
|
flag = true;
|
|
} break;
|
|
case 1:
|
|
case '*':
|
|
dbg->cb_printf("dt+ 0x%" PFMT64x " %d\n", trace->addr, trace->times);
|
|
break;
|
|
default:
|
|
dbg->cb_printf("0x%08" PFMT64x " size=%d count=%d times=%d tag=%d\n",
|
|
trace->addr, trace->size, trace->count, trace->times, trace->tag);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (flag) {
|
|
rz_list_sort(info_list, cmpaddr);
|
|
RzTable *table = rz_table_new();
|
|
table->cons = rz_cons_singleton();
|
|
RzIO *io = dbg->iob.io;
|
|
rz_table_visual_list(table, info_list, offset, 1,
|
|
rz_cons_get_size(NULL), io->va);
|
|
io->cb_printf("\n%s\n", rz_table_tostring(table));
|
|
rz_table_free(table);
|
|
rz_list_free(info_list);
|
|
}
|
|
}
|
|
|
|
// XXX: find better name, make it public?
|
|
static int rz_debug_trace_is_traceable(RzDebug *dbg, ut64 addr) {
|
|
if (dbg->trace->addresses) {
|
|
char addr_str[32];
|
|
snprintf(addr_str, sizeof(addr_str), "0x%08" PFMT64x, addr);
|
|
if (!strstr(dbg->trace->addresses, addr_str)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
RZ_API RzDebugTracepoint *rz_debug_trace_add(RzDebug *dbg, ut64 addr, int size) {
|
|
RzDebugTracepoint *tp;
|
|
int tag = dbg->trace->tag;
|
|
if (!rz_debug_trace_is_traceable(dbg, addr)) {
|
|
return NULL;
|
|
}
|
|
rz_analysis_trace_bb(dbg->analysis, addr);
|
|
tp = RZ_NEW0(RzDebugTracepoint);
|
|
if (!tp) {
|
|
return NULL;
|
|
}
|
|
tp->stamp = rz_time_now();
|
|
tp->addr = addr;
|
|
tp->tags = tag;
|
|
tp->size = size;
|
|
tp->count = ++dbg->trace->count;
|
|
tp->times = 1;
|
|
rz_list_append(dbg->trace->traces, tp);
|
|
ht_pp_update(dbg->trace->ht,
|
|
sdb_fmt("trace.%d.%" PFMT64x, tag, addr), tp);
|
|
return tp;
|
|
}
|
|
|
|
RZ_API void rz_debug_trace_reset(RzDebug *dbg) {
|
|
RzDebugTrace *t = dbg->trace;
|
|
rz_list_purge(t->traces);
|
|
ht_pp_free(t->ht);
|
|
t->ht = ht_pp_new0();
|
|
t->traces = rz_list_new();
|
|
t->traces->free = free;
|
|
}
|