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