// SPDX-FileCopyrightText: 2008-2020 pancake // SPDX-License-Identifier: LGPL-3.0-only #include /* 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 /**/ *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); }