* Rename rz_list_find_ptr() to rz_list_find_val() * Rename rz_list_delete_data() to rz_list_delete_val()
464 lines
12 KiB
C
464 lines
12 KiB
C
// SPDX-FileCopyrightText: 2009-2020 pancake <pancake@nopcode.org>
|
|
// SPDX-License-Identifier: LGPL-3.0-only
|
|
|
|
#include <rz_bp.h>
|
|
|
|
static void rz_bp_item_free(RzBreakpointItem *b) {
|
|
free(b->name);
|
|
free(b->bbytes);
|
|
free(b->obytes);
|
|
free(b->module_name);
|
|
free(b->data);
|
|
free(b->cond);
|
|
free(b->expr);
|
|
free(b);
|
|
}
|
|
|
|
/**
|
|
* Construct a new RzBreakpoint instance
|
|
* \param ctx global context in which the instance will operate (giving mappings, etc)
|
|
*/
|
|
RZ_API RzBreakpoint *rz_bp_new(RZ_BORROW RZ_NONNULL RzBreakpointContext *ctx) {
|
|
rz_return_val_if_fail(ctx, NULL);
|
|
|
|
RzBreakpoint *bp = RZ_NEW0(RzBreakpoint);
|
|
if (!bp) {
|
|
return NULL;
|
|
}
|
|
bp->ctx = *ctx;
|
|
bp->bps_idx_count = 16;
|
|
bp->bps_idx = RZ_NEWS0(RzBreakpointItem *, bp->bps_idx_count);
|
|
bp->stepcont = RZ_BP_CONT_NORMAL;
|
|
bp->traces = rz_bp_traptrace_new();
|
|
bp->cb_printf = (PrintfCallback)printf;
|
|
bp->opcode = NULL;
|
|
bp->bps = rz_list_newf((RzListFree)rz_bp_item_free);
|
|
bp->nhwbps = 0;
|
|
memset(&bp->iob, 0, sizeof(bp->iob));
|
|
return bp;
|
|
}
|
|
|
|
RZ_API void rz_bp_free(RzBreakpoint *bp) {
|
|
if (!bp) {
|
|
return;
|
|
}
|
|
rz_list_free(bp->bps);
|
|
rz_list_free(bp->traces);
|
|
rz_strbuf_free(bp->opcode);
|
|
free(bp->bps_idx);
|
|
free(bp);
|
|
}
|
|
|
|
RZ_API bool rz_bp_set_opcode(RZ_NONNULL RzBreakpoint *bp, ut64 addr) {
|
|
rz_return_val_if_fail(bp, false);
|
|
RZ_FREE_CUSTOM(bp->opcode, rz_strbuf_free);
|
|
if (!bp->ctx.get_sw_breakpoint_at) {
|
|
return false;
|
|
}
|
|
bp->opcode = bp->ctx.get_sw_breakpoint_at(addr, bp->ctx.user);
|
|
return bp->opcode != NULL;
|
|
}
|
|
|
|
/**
|
|
* Get the bytes to place at \p addr in order to set a sw breakpoint there
|
|
* \p return the length of bytes or 0 on failure
|
|
*/
|
|
RZ_API size_t rz_bp_get_bytes(RZ_NONNULL RzBreakpoint *bp, ut64 addr, RZ_NONNULL ut8 *buf, int len) {
|
|
rz_return_val_if_fail(bp && buf, 0);
|
|
if (!rz_bp_set_opcode(bp, addr)) {
|
|
// cannot use get bytes of the opcode.
|
|
return 0;
|
|
}
|
|
|
|
size_t length = 0;
|
|
const ut8 *bytes = rz_strbuf_getbin(bp->opcode, &length);
|
|
memcpy(buf, bytes, RZ_MIN(length, len));
|
|
|
|
return length;
|
|
}
|
|
|
|
/**
|
|
* \brief Get the breakpoint at exactly \p addr
|
|
*/
|
|
RZ_API RZ_BORROW RzBreakpointItem *rz_bp_get_at(RZ_NONNULL RzBreakpoint *bp, ut64 addr) {
|
|
rz_return_val_if_fail(bp, NULL);
|
|
RzListIter *iter;
|
|
RzBreakpointItem *b;
|
|
rz_list_foreach (bp->bps, iter, b) {
|
|
if (b->addr == addr) {
|
|
return b;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* \brief Get the breakpoint b that fulfills `b->addr + b-> size == addr`
|
|
* After hitting a (usually software) breakpoint, the program counter will be directly after it.
|
|
* This way we can trace back the breakpoint matching this program counter.
|
|
*/
|
|
RZ_API RZ_BORROW RzBreakpointItem *rz_bp_get_ending_at(RZ_NONNULL RzBreakpoint *bp, ut64 addr) {
|
|
rz_return_val_if_fail(bp, NULL);
|
|
RzListIter *iter;
|
|
RzBreakpointItem *b;
|
|
rz_list_foreach (bp->bps, iter, b) {
|
|
if (!b->hw && b->addr + b->size == addr) {
|
|
return b;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static inline bool inRange(RzBreakpointItem *b, ut64 addr) {
|
|
return (addr >= b->addr && addr < (b->addr + b->size));
|
|
}
|
|
|
|
static inline bool matchProt(RzBreakpointItem *b, int perm) {
|
|
return (!perm || (perm && b->perm));
|
|
}
|
|
|
|
RZ_API RzBreakpointItem *rz_bp_get_in(RzBreakpoint *bp, ut64 addr, int perm) {
|
|
RzBreakpointItem *b;
|
|
RzListIter *iter;
|
|
rz_list_foreach (bp->bps, iter, b) {
|
|
// eprintf ("---ataddr--- 0x%08"PFMT64x" %d %d %x\n", b->addr, b->size, b->recoil, b->perm);
|
|
// Check addr within range and provided perm matches (or null)
|
|
if (inRange(b, addr) && matchProt(b, perm)) {
|
|
return b;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
RZ_API RzBreakpointItem *rz_bp_enable(RzBreakpoint *bp, ut64 addr, int set, int count) {
|
|
RzBreakpointItem *b = rz_bp_get_in(bp, addr, 0);
|
|
if (b) {
|
|
b->enabled = set;
|
|
b->togglehits = count;
|
|
return b;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
RZ_API bool rz_bp_enable_all(RzBreakpoint *bp, int set) {
|
|
RzListIter *iter;
|
|
RzBreakpointItem *b;
|
|
rz_list_foreach (bp->bps, iter, b) {
|
|
b->enabled = set;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static void unlinkBreakpoint(RzBreakpoint *bp, RzBreakpointItem *b) {
|
|
for (int i = 0; i < bp->bps_idx_count; i++) {
|
|
if (bp->bps_idx[i] == b) {
|
|
bp->bps_idx[i] = NULL;
|
|
}
|
|
}
|
|
rz_list_delete_val(bp->bps, b);
|
|
}
|
|
|
|
/**
|
|
* Put an allocated RzBreakpointItem into the RzBreakpoint's list and give it an index
|
|
*/
|
|
RZ_IPI void rz_bp_item_insert(RzBreakpoint *bp, RzBreakpointItem *b) {
|
|
int i;
|
|
/* find empty slot */
|
|
for (i = 0; i < bp->bps_idx_count; i++) {
|
|
if (!bp->bps_idx[i]) {
|
|
break;
|
|
}
|
|
}
|
|
if (i == bp->bps_idx_count) {
|
|
/* allocate new slot */
|
|
bp->bps_idx_count += 16; // allocate space for 16 more bps
|
|
RzBreakpointItem **newbps = realloc(bp->bps_idx, bp->bps_idx_count * sizeof(RzBreakpointItem *));
|
|
if (newbps) {
|
|
bp->bps_idx = newbps;
|
|
for (int j = i; j < bp->bps_idx_count; j++) {
|
|
bp->bps_idx[j] = NULL;
|
|
}
|
|
} else {
|
|
bp->bps_idx_count -= 16; // allocate space for 16 more bps
|
|
i = 0; // avoid oob below
|
|
}
|
|
}
|
|
/* empty slot */
|
|
bp->bps_idx[i] = b;
|
|
bp->nbps++;
|
|
rz_list_append(bp->bps, b);
|
|
}
|
|
|
|
/* TODO: detect overlapping of breakpoints */
|
|
static RzBreakpointItem *rz_bp_add(RzBreakpoint *bp, const ut8 *obytes, ut64 addr, int size, int hw, int perm) {
|
|
if (addr == UT64_MAX || size < 1) {
|
|
return NULL;
|
|
}
|
|
if (rz_bp_get_in(bp, addr, perm)) {
|
|
RZ_LOG_ERROR("Breakpoint already set at this address.\n");
|
|
return NULL;
|
|
}
|
|
RzBreakpointItem *b = RZ_NEW0(RzBreakpointItem);
|
|
if (!b) {
|
|
return NULL;
|
|
}
|
|
b->addr = addr;
|
|
if (bp->baddr > addr) {
|
|
RZ_LOG_ERROR("base addr should not be larger than the breakpoint address.\n");
|
|
}
|
|
if (bp->bpinmaps && !rz_bp_is_valid(bp, b)) {
|
|
RZ_LOG_WARN("Breakpoint won't be placed since it's not in a valid map.\n"
|
|
"You can bypass this check by setting dbg.bpinmaps to false.\n");
|
|
}
|
|
b->delta = addr - bp->baddr;
|
|
b->size = size;
|
|
b->enabled = true;
|
|
b->perm = perm;
|
|
b->hw = hw;
|
|
// NOTE: for hw breakpoints there are no bytes to save/restore
|
|
if (!hw) {
|
|
b->bbytes = calloc(size + 16, 1);
|
|
if (!b->bbytes) {
|
|
goto err;
|
|
}
|
|
if (obytes) {
|
|
b->obytes = malloc(size);
|
|
if (!b->obytes) {
|
|
goto err;
|
|
}
|
|
memcpy(b->obytes, obytes, size);
|
|
} else {
|
|
b->obytes = NULL;
|
|
}
|
|
int ret = rz_bp_get_bytes(bp, b->addr, b->bbytes, size);
|
|
if (ret != size) {
|
|
RZ_LOG_ERROR("Cannot get breakpoint bytes at 0x%08" PFMT64x "\n", b->addr);
|
|
goto err;
|
|
}
|
|
}
|
|
rz_bp_item_insert(bp, b);
|
|
return b;
|
|
err:
|
|
rz_bp_item_free(b);
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* \brief Add a software breakpoint
|
|
* \p size preferred size of the breakpoint, or 0 to determine automatically
|
|
*/
|
|
RZ_API RZ_BORROW RzBreakpointItem *rz_bp_add_sw(RZ_NONNULL RzBreakpoint *bp, ut64 addr, int size, int perm) {
|
|
rz_return_val_if_fail(bp, NULL);
|
|
RzBreakpointItem *item;
|
|
ut8 *bytes;
|
|
if (size < 1) {
|
|
size = rz_bp_size_at(bp, addr);
|
|
}
|
|
if (!(bytes = calloc(1, size))) {
|
|
return NULL;
|
|
}
|
|
memset(bytes, 0, size);
|
|
if (bp->iob.read_at) {
|
|
bp->iob.read_at(bp->iob.io, addr, bytes, size);
|
|
}
|
|
item = rz_bp_add(bp, bytes, addr, size, RZ_BP_TYPE_SW, perm);
|
|
free(bytes);
|
|
return item;
|
|
}
|
|
|
|
RZ_API RzBreakpointItem *rz_bp_add_hw(RzBreakpoint *bp, ut64 addr, int size, int perm) {
|
|
return rz_bp_add(bp, NULL, addr, size, RZ_BP_TYPE_HW, perm);
|
|
}
|
|
|
|
RZ_API bool rz_bp_del_all(RzBreakpoint *bp) {
|
|
if (!rz_list_empty(bp->bps)) {
|
|
rz_list_purge(bp->bps);
|
|
for (int i = 0; i < bp->bps_idx_count; i++) {
|
|
bp->bps_idx[i] = NULL;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
RZ_API bool rz_bp_del(RzBreakpoint *bp, ut64 addr) {
|
|
RzListIter *iter;
|
|
RzBreakpointItem *b;
|
|
/* No _safe loop necessary because we return immediately after the delete. */
|
|
rz_list_foreach (bp->bps, iter, b) {
|
|
if (b->addr == addr) {
|
|
unlinkBreakpoint(bp, b);
|
|
// rz_list_delete (bp->bps, iter);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
RZ_API int rz_bp_set_trace(RzBreakpoint *bp, ut64 addr, int set) {
|
|
RzBreakpointItem *b = rz_bp_get_in(bp, addr, 0);
|
|
if (b) {
|
|
b->trace = set;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
RZ_API int rz_bp_set_trace_all(RzBreakpoint *bp, int set) {
|
|
RzListIter *iter;
|
|
RzBreakpointItem *b;
|
|
rz_list_foreach (bp->bps, iter, b) {
|
|
b->trace = set;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
RZ_API RzBreakpointItem *rz_bp_get_index(RzBreakpoint *bp, int idx) {
|
|
if (idx >= 0 && idx < bp->bps_idx_count) {
|
|
return bp->bps_idx[idx];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
RZ_API int rz_bp_get_index_at(RzBreakpoint *bp, ut64 addr) {
|
|
for (int i = 0; i < bp->bps_idx_count; i++) {
|
|
if (bp->bps_idx[i] && bp->bps_idx[i]->addr == addr) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
RZ_API int rz_bp_del_index(RzBreakpoint *bp, int idx) {
|
|
if (idx >= 0 && idx < bp->bps_idx_count) {
|
|
rz_list_delete_val(bp->bps, bp->bps_idx[idx]);
|
|
bp->bps_idx[idx] = 0;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* \brief Current software breakpoint size
|
|
* \param bits bitness or 0 if unspecified
|
|
*/
|
|
RZ_API size_t rz_bp_size(RZ_NONNULL RzBreakpoint *bp) {
|
|
rz_return_val_if_fail(bp, 0);
|
|
if (!bp->opcode) {
|
|
// cannot use get the size of the opcode.
|
|
return 0;
|
|
}
|
|
|
|
return rz_strbuf_length(bp->opcode);
|
|
}
|
|
|
|
/**
|
|
* \brief Get the software breakpoint size at a given address
|
|
*/
|
|
RZ_API size_t rz_bp_size_at(RZ_NONNULL RzBreakpoint *bp, ut64 addr) {
|
|
rz_return_val_if_fail(bp, 0);
|
|
if (!bp->ctx.get_sw_breakpoint_size_at) {
|
|
// cannot use get the size of the opcode.
|
|
return 0;
|
|
}
|
|
return bp->ctx.get_sw_breakpoint_size_at(addr, bp->ctx.user);
|
|
}
|
|
|
|
// Check if the breakpoint is in a valid map
|
|
RZ_API bool rz_bp_is_valid(RzBreakpoint *bp, RzBreakpointItem *b) {
|
|
if (!bp->bpinmaps) {
|
|
return true;
|
|
}
|
|
if (!bp->ctx.is_mapped) {
|
|
return false;
|
|
}
|
|
return bp->ctx.is_mapped(b->addr, b->perm, bp->ctx.user);
|
|
}
|
|
|
|
/**
|
|
* \brief set the condition for a RzBreakpointItem
|
|
*
|
|
* \param item brekapoint item to set value for
|
|
* \param cond value of cond to be set; if NULL is passed, then the cond value of \p item will be set to NULL
|
|
* \return bool true if succesful; false otherwise; if false returned, then \p item will not have been modified
|
|
*/
|
|
RZ_API bool rz_bp_item_set_cond(RZ_NONNULL RzBreakpointItem *item, RZ_NULLABLE const char *cond) {
|
|
rz_return_val_if_fail(item, false);
|
|
|
|
char *tmp_cond = NULL;
|
|
if (cond) {
|
|
tmp_cond = rz_str_dup(cond);
|
|
if (!tmp_cond) {
|
|
return false;
|
|
}
|
|
}
|
|
free(item->cond);
|
|
item->cond = tmp_cond;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* \brief set the data for a RzBreakpointItem
|
|
*
|
|
* \param item brekapoint item to set value for
|
|
* \param data value of data to be set; if NULL is passed, then the data value of \p item will be set to NULL
|
|
* \return bool true if succesful; false otherwise; if false returned, then \p item will not have been modified
|
|
*/
|
|
RZ_API bool rz_bp_item_set_data(RZ_NONNULL RzBreakpointItem *item, RZ_NULLABLE const char *data) {
|
|
rz_return_val_if_fail(item, false);
|
|
|
|
char *tmp_data = NULL;
|
|
if (data) {
|
|
tmp_data = rz_str_dup(data);
|
|
if (!tmp_data) {
|
|
return false;
|
|
}
|
|
}
|
|
free(item->data);
|
|
item->data = tmp_data;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* \brief set the expr for a RzBreakpointItem
|
|
*
|
|
* \param item brekapoint item to set value for
|
|
* \param expr value of expr to be set; if NULL is passed, then the expr value of \p item will be set to NULL
|
|
* \return bool true if succesful; false otherwise; if false returned, then \p item will not have been modified
|
|
*/
|
|
RZ_API bool rz_bp_item_set_expr(RZ_NONNULL RzBreakpointItem *item, RZ_NULLABLE const char *expr) {
|
|
rz_return_val_if_fail(item, false);
|
|
|
|
char *tmp_expr = NULL;
|
|
if (expr) {
|
|
tmp_expr = rz_str_dup(expr);
|
|
if (!tmp_expr) {
|
|
return false;
|
|
}
|
|
}
|
|
free(item->expr);
|
|
item->expr = tmp_expr;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* \brief set the name for a RzBreakpointItem
|
|
*
|
|
* \param item brekapoint item to set value for
|
|
* \param name value of name to be set; if NULL is passed, then the name value of \p item will be set to NULL
|
|
* \return bool true if succesful; false otherwise; if false returned, then \p item will not have been modified
|
|
*/
|
|
RZ_API bool rz_bp_item_set_name(RZ_NONNULL RzBreakpointItem *item, RZ_NULLABLE const char *name) {
|
|
rz_return_val_if_fail(item, false);
|
|
|
|
char *tmp_name = NULL;
|
|
if (name) {
|
|
tmp_name = rz_str_dup(name);
|
|
if (!tmp_name) {
|
|
return false;
|
|
}
|
|
}
|
|
free(item->name);
|
|
item->name = tmp_name;
|
|
return true;
|
|
}
|