Register roles (RzRegisterId) are now associated directly with an RzRegItem in RzReg rather than mapping to name strings, which previously needed an additional hashtable lookup to get more information about the register. Conversely, if the name is needed from an RzRegItem, it is available directly as a member. This is not a pure refactor as there were cases before where a register name was assigned to a role in the register profile, but no register actually existed under that name. Such cases will now cause a warning to be printed during profile load and the role association will be ignored. Changes in register profiles in this commit are for fixing such cases.
309 lines
8.1 KiB
C
309 lines
8.1 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->enabled = false;
|
|
t->traces = rz_list_new();
|
|
if (!t->traces) {
|
|
rz_debug_trace_free(t);
|
|
return NULL;
|
|
}
|
|
t->traces->free = free;
|
|
t->ht = ht_sp_new(HT_STR_DUP, NULL, NULL);
|
|
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_sp_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_by_role(dbg, RZ_REG_NAME_PC);
|
|
if (!dbg->iob.read_at) {
|
|
RZ_LOG_ERROR("dbg->iob.read_at missing\n");
|
|
return false;
|
|
}
|
|
if (!dbg->iob.read_at(dbg->iob.io, pc, buf_pc, sizeof(buf_pc))) {
|
|
RZ_LOG_ERROR("dbg->iob.read_at failure -- pc 0x%" PFMT64x "\n", pc);
|
|
return false;
|
|
}
|
|
dbg->cur_op = rz_analysis_op_new();
|
|
if (!dbg->cur_op) {
|
|
RZ_LOG_ERROR("rz_analysis_op_new failure\n");
|
|
return false;
|
|
}
|
|
if (rz_analysis_op(dbg->analysis, dbg->cur_op, pc, buf_pc, sizeof(buf_pc), RZ_ANALYSIS_OP_MASK_VAL) < 1) {
|
|
RZ_LOG_ERROR("rz_analysis_op failure -- pc 0x%" PFMT64x "\n", pc);
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* \brief Add register/memory changes to the debug session.
|
|
*
|
|
* \param dbg RzDebug instance containing the session.
|
|
* \return false The current instruction is not known so no changes can be recorded.
|
|
* \return true Otherwise.
|
|
*/
|
|
RZ_API bool rz_debug_trace_ins_after(RZ_NONNULL RzDebug *dbg) {
|
|
rz_return_val_if_fail(dbg, false);
|
|
if (!dbg->cur_op) { // Can happen if hard stepping is available and code is unknown to Rizin
|
|
return 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)) {
|
|
RZ_LOG_ERROR("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) {
|
|
RZ_LOG_ERROR("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) {
|
|
RzAnalysisEsil *esil = rz_analysis_get_esil(dbg->analysis);
|
|
if (esil) {
|
|
const char *eexpr = rz_strbuf_get(&op->esil);
|
|
rz_analysis_esil_trace_op(esil, op->addr, eexpr);
|
|
} else {
|
|
if (dbg->verbose) {
|
|
RZ_LOG_WARN("debug: Run aeim to get 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 RzDebugTracepoint *rz_debug_trace_get(RzDebug *dbg, ut64 addr) {
|
|
char tmpbuf[64];
|
|
int tag = dbg->trace->tag;
|
|
return ht_sp_find(dbg->trace->ht,
|
|
rz_strf(tmpbuf, "trace.%d.%" PFMT64x, tag, addr), NULL);
|
|
}
|
|
|
|
static int cmpaddr(const void *_a, const void *_b, void *user) {
|
|
const RzDbgListInfo *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;
|
|
}
|
|
|
|
/***
|
|
* Get all trace info
|
|
* \param dbg core->dbg
|
|
* \param offset offset of address
|
|
* \return a RzList of RzDbgListInfo
|
|
*/
|
|
RZ_API RZ_OWN RzList /*<RzDbgListInfo *>*/ *rz_debug_traces_info(RzDebug *dbg, ut64 offset) {
|
|
rz_return_val_if_fail(dbg, NULL);
|
|
int tag = dbg->trace->tag;
|
|
RzListIter *iter;
|
|
RzList *info_list = rz_list_newf((RzListFree)rz_debug_listinfo_free);
|
|
if (!info_list) {
|
|
return NULL;
|
|
}
|
|
|
|
RzDebugTracepoint *trace;
|
|
rz_list_foreach (dbg->trace->traces, iter, trace) {
|
|
if (trace->tag && !(tag & trace->tag)) {
|
|
continue;
|
|
}
|
|
RzDbgListInfo *info = RZ_NEW0(RzDbgListInfo);
|
|
if (!info) {
|
|
rz_list_free(info_list);
|
|
return NULL;
|
|
}
|
|
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);
|
|
}
|
|
|
|
rz_list_sort(info_list, cmpaddr, NULL);
|
|
return info_list;
|
|
}
|
|
|
|
RZ_API RzDebugTracepoint *rz_debug_trace_add(RzDebug *dbg, ut64 addr, int size) {
|
|
RzDebugTracepoint *tp;
|
|
char tmpbuf[64];
|
|
int tag = dbg->trace->tag;
|
|
rz_analysis_trace_bb(dbg->analysis, addr);
|
|
tp = RZ_NEW0(RzDebugTracepoint);
|
|
if (!tp) {
|
|
return NULL;
|
|
}
|
|
tp->stamp = rz_time_now_mono();
|
|
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_sp_update(dbg->trace->ht,
|
|
rz_strf(tmpbuf, "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_sp_free(t->ht);
|
|
t->ht = ht_sp_new(HT_STR_DUP, NULL, NULL);
|
|
t->traces = rz_list_new();
|
|
t->traces->free = free;
|
|
}
|
|
|
|
RZ_API RZ_OWN RzDbgListInfo *rz_debug_listinfo_new(RZ_NULLABLE const char *name, RzInterval pitv, RzInterval vitv, int perm, RZ_NULLABLE const char *extra) {
|
|
RzDbgListInfo *info = RZ_NEW(RzDbgListInfo);
|
|
if (!info) {
|
|
return NULL;
|
|
}
|
|
info->name = rz_str_dup(name);
|
|
info->pitv = pitv;
|
|
info->vitv = vitv;
|
|
info->perm = perm;
|
|
info->extra = rz_str_dup(extra);
|
|
return info;
|
|
}
|
|
|
|
RZ_API void rz_debug_listinfo_free(RZ_NULLABLE RzDbgListInfo *info) {
|
|
if (!info) {
|
|
return;
|
|
}
|
|
free(info->name);
|
|
free(info->extra);
|
|
free(info);
|
|
}
|