rizin/librz/debug/trace.c
Rot127 87b75910b5
Port remaining debug commands to RzShell (#4753)
* Port `dmhb` to RzShell
* Port `dmhf` to RzShell
* Remove 'to' argument from rz_core_debug_continue_until(). It was never used in the whole function. Hence, whatever it was supposed to do, it didn't
* Port `dcu` to RzShell
* Fix tests of `dmh[bf]` commands
* Port `dmi` commands to RzShell
* Port `dmx` to RzShell
* Remove `dta` command.

Originally it limited the addresses to trace.
But it was not used in any test, and the implementation was not optimal.
It allowed only specific addresses (no range).
Also worked on strings (not ut64 values).
Also, the usefulness is limited, considering the alternative
to just not tracing a certain address.

* Add quiet and quietest mode to `dmi` commands.

This also removes the test for `dmias`.
The command was not documented and it doesn't seem to
do anything special `dmia` can't do.
2024-12-06 08:10:25 +08:00

285 lines
7.5 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(dbg, dbg->reg->name[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)) {
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 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 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;
}
/***
* Get all trace info
* @param dbg core->dbg
* @param offset offset of address
* @return a RzList of RzListInfo
*/
RZ_API RZ_OWN RzList /*<RzListInfo *>*/ *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_new();
if (!info_list) {
return NULL;
}
RzDebugTracepoint *trace;
rz_list_foreach (dbg->trace->traces, iter, trace) {
if (trace->tag && !(tag & trace->tag)) {
continue;
}
RzListInfo *info = RZ_NEW0(RzListInfo);
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;
}