rizin/test/unit/test_debug.c
billow 7a77b8f7bd
linter: update clang-format installation to version 20 (#5451)
* fix: format with clang-format-20
* fix: fix clang-format linter with rewrite '^#define.*/\*.*\\$'
* fix: update clang-format to version 20 in workflows and documentation
* fix: add SPDX license information to .git-blame-ignore-revs
2025-10-12 08:02:03 +08:00

762 lines
24 KiB
C

// SPDX-FileCopyrightText: 2020 Khairulmizam Samsudin <xource@gmail.com>
// SPDX-License-Identifier: LGPL-3.0-only
#include <rz_debug.h>
#include "minunit.h"
#if __linux__
#include <sys/user.h>
#ifndef offsetof
#define offsetof(type, field) ((size_t)&((type *)0)->field)
#endif
#endif //__linux__
bool test_rz_debug_use(void) {
RzDebug *dbg;
bool res;
RzBreakpointContext bp_ctx = { 0 };
dbg = rz_debug_new(&bp_ctx);
mu_assert_notnull(dbg, "rz_debug_new () failed");
res = rz_debug_use(dbg, "null");
mu_assert_eq(res, true, "rz_debug_use () failed");
rz_debug_free(dbg);
mu_end;
}
bool test_rz_debug_reg_offset(void) {
#if __linux__
#ifdef __x86_64__
#define FPREGS struct user_fpregs_struct
FPREGS regs;
mu_assert_eq(sizeof(regs.cwd), 2, "cwd size");
mu_assert_eq(offsetof(FPREGS, cwd), 0, "cwd offset");
mu_assert_eq(sizeof(regs.rip), 8, "rip size");
mu_assert_eq(offsetof(FPREGS, rip), 8, "rip offset");
mu_assert_eq(sizeof(regs.mxcsr), 4, "mxcsr size");
mu_assert_eq(offsetof(FPREGS, mxcsr), 24, "mxcsr offset");
mu_assert_eq(sizeof(regs.mxcr_mask), 4, "mxcr_mask size");
mu_assert_eq(offsetof(FPREGS, mxcr_mask), 28, "mxcr_mask offset");
mu_assert_eq(sizeof(regs.st_space[0]) * 2, 8, "st0 size");
mu_assert_eq(offsetof(FPREGS, st_space[0]), 32, "st0 offset");
mu_assert_eq(sizeof(regs.xmm_space[0]) * 4, 16, "xmm0 size");
mu_assert_eq(offsetof(FPREGS, xmm_space[0]), 160, "xmm0 offset");
mu_assert_eq(offsetof(FPREGS, padding[0]), 416, "x64");
#endif //__x86_64__
#endif //__linux__
mu_end;
}
/**
* \name Debug Mock Plugins
* The below plugin can be used for unit tests to test RzDebug without having
* to rely on any system-specific behavior.
* @{
*/
/**
* Side-channel to signal failure from inside the plugin
* This is fine to be global since unit-tests are single-threaded.
*/
static bool dbg_mock_failed = false;
static void dbg_mock_fail() {
// use an extra function to debug the tests easier
dbg_mock_failed = true;
}
typedef struct {
ut64 addr;
ut64 size;
int perm; ///< RZ_PERM_RWX
} DebugMockHWBP;
typedef struct {
ut64 pc;
ut8 a; ///< accumulator
bool running;
RzStrBuf output; ///< output of print instructions
bool is_thumb; ///< only used in multibits variant below
RzVector /* <DebugMockHWBP> */ hwbps;
} DebugMockCtx;
static bool dbg_mock_init(RzDebug *dbg, void **user) {
DebugMockCtx *ctx = RZ_NEW0(DebugMockCtx);
ctx->pc = 0x30;
ctx->a = 0;
ctx->running = false;
rz_strbuf_init(&ctx->output);
rz_vector_init(&ctx->hwbps, sizeof(DebugMockHWBP), NULL, NULL);
*user = ctx;
return true;
}
static void dbg_mock_fini(RzDebug *dbg, void *user) {
DebugMockCtx *ctx = user;
rz_strbuf_fini(&ctx->output);
rz_vector_fini(&ctx->hwbps);
free(ctx);
}
static int dbg_mock_attach(RzDebug *dbg, int pid) {
return true;
}
static bool mock_isa_hwbp_at(DebugMockCtx *ctx, ut64 addr, int perm) {
DebugMockHWBP *bp;
rz_vector_foreach (&ctx->hwbps, bp) {
if (bp->addr <= addr && bp->addr + bp->size > addr && (bp->perm & perm)) {
return true;
}
}
return false;
}
static RzDebugReasonType mock_isa_step(DebugMockCtx *ctx, RzIO *io) {
// mock mini instruction set:
static const char *op_nop = "\x00\x00\x00\x00"; ///< nop
static const char *op_break = "STOP"; ///< software breakpoint
static const char *op_print = "PRNT"; ///< print something to ctx->output
static const char op_pfx_load = 'L'; ///< "L<3-nibble hex addr>" load memory at the given addr into a
static const char op_pfx_store = 'S'; ///< "S<3-nibble hex addr>" store a into memory at given addr
if (mock_isa_hwbp_at(ctx, ctx->pc, RZ_PERM_X)) {
return RZ_DEBUG_REASON_BREAKPOINT;
}
ut8 opcode[4];
rz_io_read_at(io, ctx->pc, opcode, sizeof(opcode));
ctx->pc += sizeof(opcode);
if (!memcmp(opcode, op_nop, sizeof(opcode))) {
return RZ_DEBUG_REASON_NONE;
}
if (!memcmp(opcode, op_break, sizeof(opcode))) {
return RZ_DEBUG_REASON_BREAKPOINT;
}
if (!memcmp(opcode, op_print, sizeof(opcode))) {
rz_strbuf_appendf(&ctx->output, "PRNT with next pc = 0x%" PFMT64x "\n", ctx->pc);
return RZ_DEBUG_REASON_NONE;
}
if (*opcode == op_pfx_load || *opcode == op_pfx_store) {
char val[4];
memcpy(val, opcode + 1, 3);
val[3] = 0;
ut64 addr = strtoul(val, NULL, 16);
int perm;
if (*opcode == op_pfx_load) {
rz_io_read_at(io, addr, &ctx->a, 1);
perm = RZ_PERM_R;
} else {
rz_io_write_at(io, addr, &ctx->a, 1);
perm = RZ_PERM_W;
}
if (mock_isa_hwbp_at(ctx, addr, perm)) {
return RZ_DEBUG_REASON_BREAKPOINT;
}
return RZ_DEBUG_REASON_NONE;
}
// invalid instruction
dbg_mock_fail();
return RZ_DEBUG_REASON_ILLEGAL;
}
static bool dbg_mock_step(RzDebug *dbg) {
DebugMockCtx *ctx = dbg->plugin_data;
mock_isa_step(ctx, dbg->iob.io);
return true;
}
static int dbg_mock_cont(RzDebug *dbg, int pid, int tid, int sig) {
DebugMockCtx *ctx = dbg->plugin_data;
ctx->running = true;
return RZ_DEBUG_REASON_NONE;
}
RzDebugReasonType dbg_mock_wait(RzDebug *dbg, int pid) {
DebugMockCtx *ctx = dbg->plugin_data;
if (!ctx->running) {
return RZ_DEBUG_REASON_NONE;
}
RzIO *io = dbg->iob.io;
RzDebugReasonType r = RZ_DEBUG_REASON_NONE;
for (ut64 fuel = 0x100; fuel; fuel--) {
r = mock_isa_step(ctx, io);
if (r != RZ_DEBUG_REASON_NONE) {
break;
}
}
ctx->running = false;
return r;
}
#define DBG_MOCK_REG_PROFILE_SIZE 9
int dbg_mock_reg_read(RzDebug *dbg, int type, ut8 *buf, int size) {
if (type != RZ_REG_TYPE_GPR) {
return 0;
}
DebugMockCtx *ctx = dbg->plugin_data;
if (size < DBG_MOCK_REG_PROFILE_SIZE) {
dbg_mock_fail();
return 0;
}
rz_write_at_le64(buf, ctx->pc, 0);
buf[8] = ctx->a;
return DBG_MOCK_REG_PROFILE_SIZE;
}
int dbg_mock_reg_write(RzDebug *dbg, int type, const ut8 *buf, int size) {
if (type != RZ_REG_TYPE_GPR) {
return 0;
}
DebugMockCtx *ctx = dbg->plugin_data;
if (size < DBG_MOCK_REG_PROFILE_SIZE) {
dbg_mock_fail();
return 0;
}
ctx->pc = rz_read_at_le64(buf, 0);
ctx->a = buf[8];
return DBG_MOCK_REG_PROFILE_SIZE;
}
char *dbg_mock_reg_profile(RzDebug *dbg) {
return strdup(
"=PC pc\n"
"gpr pc .64 0 0\n"
"gpr a .8 8 0\n");
}
static int dbg_mock_breakpoint(RzBreakpoint *bp, RzBreakpointItem *b, bool set) {
RzDebug *dbg = bp->user;
DebugMockCtx *ctx = dbg->plugin_data;
if (set) {
DebugMockHWBP hwbp = {
.addr = b->addr,
.size = b->size,
.perm = b->perm
};
rz_vector_push(&ctx->hwbps, &hwbp);
return true;
}
for (size_t i = 0; i < rz_vector_len(&ctx->hwbps); i++) {
DebugMockHWBP *hwbp = rz_vector_index_ptr(&ctx->hwbps, i);
if (hwbp->addr == b->addr) {
rz_vector_remove_at(&ctx->hwbps, i, NULL);
return true;
}
}
return false;
}
static RzDebugPlugin dbg_mock_plugin = {
.name = "mock_dbg",
.license = "LGPL3",
.arch = "mock_arch",
.init = dbg_mock_init,
.fini = dbg_mock_fini,
.attach = dbg_mock_attach,
.step = dbg_mock_step,
.cont = dbg_mock_cont,
.wait = dbg_mock_wait,
.breakpoint = dbg_mock_breakpoint,
.reg_read = dbg_mock_reg_read,
.reg_write = dbg_mock_reg_write,
.reg_profile = dbg_mock_reg_profile
};
static bool bp_everything_is_mapped(ut64 addr, int perm, void *user) {
return true;
}
static RzStrBuf *bp_mock_sw_opcode_at(ut64 addr, void *user) {
return rz_strbuf_new("STOP");
}
static size_t bp_mock_sw_opcode_size_at(ut64 addr, void *user) {
return 4;
}
static RzBreakpointContext bp_ctx = {
.is_mapped = bp_everything_is_mapped,
.get_sw_breakpoint_at = bp_mock_sw_opcode_at,
.get_sw_breakpoint_size_at = bp_mock_sw_opcode_size_at,
};
/// @}
/**
* \name "Thumb" Debug Mock Plugins
* This is an extension to the above plugin, which supports dynamically switching
* between two instruction sets, like arm thumb.
* @{
*/
static RzDebugReasonType mock_isa_multibits_step(DebugMockCtx *ctx, RzIO *io) {
if (ctx->is_thumb) {
static const char *op_nop = "\x00\x00"; ///< nop
static const char *op_break = "st"; ///< software breakpoint
static const char *op_print = "pr"; ///< print something (just to have something other than 0s)
static const char *op_switch = "sw"; ///< switch to 32bit isa
ut8 opcode[2];
rz_io_read_at(io, ctx->pc, opcode, sizeof(opcode));
ctx->pc += sizeof(opcode);
if (!memcmp(opcode, op_nop, sizeof(opcode))) {
return RZ_DEBUG_REASON_NONE;
}
if (!memcmp(opcode, op_break, sizeof(opcode))) {
return RZ_DEBUG_REASON_BREAKPOINT;
}
if (!memcmp(opcode, op_print, sizeof(opcode))) {
rz_strbuf_appendf(&ctx->output, "pr with next pc = 0x%" PFMT64x "\n", ctx->pc);
return RZ_DEBUG_REASON_NONE;
}
if (!memcmp(opcode, op_switch, sizeof(opcode))) {
ctx->is_thumb = false;
return RZ_DEBUG_REASON_NONE;
}
} else {
static const char *op_nop = "\x00\x00\x00\x00"; ///< nop
static const char *op_break = "STOP"; ///< software breakpoint
static const char *op_print = "PRNT"; ///< print something (just to have something other than 0s)
static const char *op_switch = "SWCH"; ///< switch to 16bit isa
ut8 opcode[4];
rz_io_read_at(io, ctx->pc, opcode, sizeof(opcode));
ctx->pc += sizeof(opcode);
if (!memcmp(opcode, op_nop, sizeof(opcode))) {
return RZ_DEBUG_REASON_NONE;
}
if (!memcmp(opcode, op_break, sizeof(opcode))) {
return RZ_DEBUG_REASON_BREAKPOINT;
}
if (!memcmp(opcode, op_print, sizeof(opcode))) {
rz_strbuf_appendf(&ctx->output, "PRNT with next pc = 0x%" PFMT64x "\n", ctx->pc);
return RZ_DEBUG_REASON_NONE;
}
if (!memcmp(opcode, op_switch, sizeof(opcode))) {
ctx->is_thumb = true;
return RZ_DEBUG_REASON_NONE;
}
}
// invalid instruction
dbg_mock_fail();
return RZ_DEBUG_REASON_ILLEGAL;
}
RzDebugReasonType dbg_mock_multibits_wait(RzDebug *dbg, int pid) {
DebugMockCtx *ctx = dbg->plugin_data;
if (!ctx->running) {
return RZ_DEBUG_REASON_NONE;
}
RzIO *io = dbg->iob.io;
RzDebugReasonType r = RZ_DEBUG_REASON_NONE;
for (ut64 fuel = 0x100; fuel; fuel--) {
r = mock_isa_multibits_step(ctx, io);
if (r != RZ_DEBUG_REASON_NONE) {
break;
}
}
ctx->running = false;
return r;
}
bool dbg_mock_multibits_step(RzDebug *dbg) {
mock_isa_multibits_step(dbg->plugin_data, dbg->iob.io);
return true;
}
static RzDebugPlugin dbg_mock_multibits_plugin = {
.name = "mock_multibits_dbg",
.license = "LGPL3",
.arch = "mock_multibits_arch",
.init = dbg_mock_init,
.fini = dbg_mock_fini,
.attach = dbg_mock_attach,
.cont = dbg_mock_cont,
.wait = dbg_mock_multibits_wait,
.step = dbg_mock_multibits_step,
.reg_read = dbg_mock_reg_read,
.reg_write = dbg_mock_reg_write,
.reg_profile = dbg_mock_reg_profile
};
/// @}
#define SETUP_DEBUG(dbg_plugin, bp_ctx) \
do { \
dbg_mock_failed = false; \
dbg = rz_debug_new(bp_ctx); \
mu_assert_notnull(dbg, "create debug"); \
bool succ = rz_debug_plugin_add(dbg, dbg_plugin); \
mu_assert_true(succ, "add mock debug plugin"); \
io = rz_io_new(); \
rz_io_bind(io, &dbg->iob); \
rz_io_bind(io, &dbg->bp->iob); \
succ = rz_debug_use(dbg, (dbg_plugin)->name); \
mu_assert_true(succ, "use mock debug plugin"); \
} while (0)
/**
* \brief Simple sw breakpoint test
* Start at 0x30, set breakpoint at 0x50 and continue until it gets hit.
*/
static bool test_debug_sw_bp(void) {
RzDebug *dbg;
RzIO *io;
SETUP_DEBUG(&dbg_mock_plugin, &bp_ctx);
rz_io_open_at(io, "malloc://0x1000", RZ_PERM_RW, 0644, 0x0, NULL);
rz_io_write_at(io, 0x50, (const ut8 *)"PRNT", 4);
int r = rz_debug_attach(dbg, 42);
mu_assert_false(dbg_mock_failed, "global failure");
mu_assert_true(r, "attach");
rz_debug_reg_sync(dbg, RZ_REG_TYPE_ANY, false);
mu_assert_false(dbg_mock_failed, "global failure");
ut64 pc = rz_reg_get_value_by_role(dbg->reg, RZ_REG_NAME_PC);
mu_assert_eq(pc, 0x30, "initial pc");
RzBreakpointItem *b = rz_debug_bp_add(dbg, 0x50, 0, false, false, 0, NULL, 0);
mu_assert_false(dbg_mock_failed, "global failure");
mu_assert_notnull(b, "add bp");
mu_assert_false(b->hw, "bp is software");
r = rz_debug_continue(dbg);
mu_assert_false(dbg_mock_failed, "global failure");
mu_assert_true(r, "continue");
rz_debug_reg_sync(dbg, RZ_REG_TYPE_ANY, false);
mu_assert_false(dbg_mock_failed, "global failure");
pc = rz_reg_get_value_by_role(dbg->reg, RZ_REG_NAME_PC);
mu_assert_eq(pc, 0x50, "pc recoiled after hitting sw breakpoint at 0x54");
ut8 data[4];
rz_io_read_at(io, 0x50, data, sizeof(data));
mu_assert_memeq(data, (const ut8 *)"PRNT", 4, "restored original bytes");
DebugMockCtx *ctx = dbg->plugin_data;
mu_assert_streq(rz_strbuf_get(&ctx->output), "", "no print hit");
rz_debug_free(dbg);
rz_io_free(io);
mu_end;
}
/**
* Set up some mixed thumb and non-thumb code, put breakpoints in both parts and check that
* all of them are set up correctly (selecting the right byte patterns from the bp plugin) and hit.
*/
static RzStrBuf *bp_mock_sw_bp_multibits_opcode_at(ut64 addr, void *user) {
// this corresponds to the instruction of the program written into io in test_debug_sw_bp_multibits()
if (addr >= 0x58 && addr < 0x60) {
return rz_strbuf_new("st");
}
return rz_strbuf_new("STOP");
}
static size_t bp_mock_sw_bp_multibits_size_at(ut64 addr, void *user) {
// this corresponds to the instruction of the program written into io in test_debug_sw_bp_multibits()
return addr >= 0x58 && addr < 0x60 ? 2 : 4;
}
static bool test_debug_sw_bp_multibits(void) {
RzBreakpointContext bp_ctx = {
.is_mapped = bp_everything_is_mapped,
.get_sw_breakpoint_at = bp_mock_sw_bp_multibits_opcode_at,
.get_sw_breakpoint_size_at = bp_mock_sw_bp_multibits_size_at,
};
RzDebug *dbg;
RzIO *io;
SETUP_DEBUG(&dbg_mock_multibits_plugin, &bp_ctx);
rz_io_open_at(io, "malloc://0x1000", RZ_PERM_RW, 0644, 0x0, NULL);
// program is some non-thumb code with a chunk of thumb in between
rz_io_write_at(io, 0x50, (const ut8 *)"PRNT", 4);
rz_io_write_at(io, 0x54, (const ut8 *)"SWCH", 4);
rz_io_write_at(io, 0x58, (const ut8 *)"pr", 2);
rz_io_write_at(io, 0x5a, (const ut8 *)"pr", 2);
rz_io_write_at(io, 0x5e, (const ut8 *)"sw", 2);
rz_io_write_at(io, 0x60, (const ut8 *)"PRNT", 2);
const char full_code[] =
/* 0x4c */ "\x00\x00\x00\x00"
/* 0x50 */ "PRNT"
/* 0x54 */ "SWCH"
/* 0x58 */ "pr"
/* 0x5a */ "pr"
/* 0x5c */ "\x00\x00"
/* 0x5e */ "sw"
/* 0x60 */ "PRNT"
/* 0x64 */ "\x00\x00\x00\x00"
/* 0x68 */ "\x00\x00\x00\x00"
/* 0x6c */ "\x00\x00\x00\x00";
#define FULL_CODE_SIZE (0x70 - 0x4c)
mu_assert_eq(sizeof(full_code) - 1, FULL_CODE_SIZE, "code size");
rz_io_write_at(io, 0x4c, (const ut8 *)full_code, FULL_CODE_SIZE);
int r = rz_debug_attach(dbg, 42);
mu_assert_false(dbg_mock_failed, "global failure");
mu_assert_true(r, "attach");
rz_debug_reg_sync(dbg, RZ_REG_TYPE_ANY, false);
mu_assert_false(dbg_mock_failed, "global failure");
ut64 pc = rz_reg_get_value_by_role(dbg->reg, RZ_REG_NAME_PC);
mu_assert_eq(pc, 0x30, "initial pc");
RzBreakpointItem *b = rz_debug_bp_add(dbg, 0x50, 0, false, false, 0, NULL, 0);
mu_assert_false(dbg_mock_failed, "global failure");
mu_assert_notnull(b, "add bp in non-thumb");
mu_assert_eq(b->size, 4, "non-thumb bp size");
b = rz_debug_bp_add(dbg, 0x54, 0, false, false, 0, NULL, 0);
mu_assert_false(dbg_mock_failed, "global failure");
mu_assert_notnull(b, "add bp at the end of non-thumb");
mu_assert_eq(b->size, 4, "non-thumb bp size");
b = rz_debug_bp_add(dbg, 0x58, 0, false, false, 0, NULL, 0);
mu_assert_false(dbg_mock_failed, "global failure");
mu_assert_notnull(b, "add bp at the beginning of thumb");
mu_assert_eq(b->size, 2, "thumb bp size");
b = rz_debug_bp_add(dbg, 0x5a, 0, false, false, 0, NULL, 0);
mu_assert_false(dbg_mock_failed, "global failure");
mu_assert_notnull(b, "add bp in the middle of thumb");
mu_assert_eq(b->size, 2, "thumb bp size");
b = rz_debug_bp_add(dbg, 0x5e, 0, false, false, 0, NULL, 0);
mu_assert_false(dbg_mock_failed, "global failure");
mu_assert_notnull(b, "add bp at the end of thumb");
mu_assert_eq(b->size, 2, "thumb bp size");
b = rz_debug_bp_add(dbg, 0x68, 0, false, false, 0, NULL, 0);
mu_assert_false(dbg_mock_failed, "global failure");
mu_assert_notnull(b, "add bp after thumb");
mu_assert_eq(b->size, 4, "non-thumb bp size");
DebugMockCtx *ctx = dbg->plugin_data;
r = rz_debug_continue(dbg);
mu_assert_false(dbg_mock_failed, "global failure");
mu_assert_true(r, "continue");
rz_debug_reg_sync(dbg, RZ_REG_TYPE_ANY, false);
mu_assert_false(dbg_mock_failed, "global failure");
pc = rz_reg_get_value_by_role(dbg->reg, RZ_REG_NAME_PC);
mu_assert_eq(pc, 0x50, "pc recoiled after hitting sw breakpoint at 0x54");
mu_assert_streq(rz_strbuf_get(&ctx->output), "", "output");
ut8 data[FULL_CODE_SIZE];
rz_io_read_at(io, 0x4c, data, sizeof(data));
mu_assert_memeq(data, (const ut8 *)full_code, sizeof(data), "restored original bytes");
r = rz_debug_continue(dbg);
mu_assert_false(dbg_mock_failed, "global failure");
mu_assert_true(r, "continue");
rz_debug_reg_sync(dbg, RZ_REG_TYPE_ANY, false);
mu_assert_false(dbg_mock_failed, "global failure");
pc = rz_reg_get_value_by_role(dbg->reg, RZ_REG_NAME_PC);
mu_assert_eq(pc, 0x54, "pc recoiled after hitting sw breakpoint at 0x54");
mu_assert_streq(rz_strbuf_get(&ctx->output),
"PRNT with next pc = 0x54\n",
"output");
rz_io_read_at(io, 0x4c, data, sizeof(data));
mu_assert_memeq(data, (const ut8 *)full_code, sizeof(data), "restored original bytes");
r = rz_debug_continue(dbg);
mu_assert_false(dbg_mock_failed, "global failure");
mu_assert_true(r, "continue");
rz_debug_reg_sync(dbg, RZ_REG_TYPE_ANY, false);
mu_assert_false(dbg_mock_failed, "global failure");
pc = rz_reg_get_value_by_role(dbg->reg, RZ_REG_NAME_PC);
mu_assert_eq(pc, 0x58, "pc recoiled after hitting sw breakpoint at 0x54");
mu_assert_streq(rz_strbuf_get(&ctx->output),
"PRNT with next pc = 0x54\n",
"output");
rz_io_read_at(io, 0x4c, data, sizeof(data));
mu_assert_memeq(data, (const ut8 *)full_code, sizeof(data), "restored original bytes");
r = rz_debug_continue(dbg);
mu_assert_false(dbg_mock_failed, "global failure");
mu_assert_true(r, "continue");
rz_debug_reg_sync(dbg, RZ_REG_TYPE_ANY, false);
mu_assert_false(dbg_mock_failed, "global failure");
pc = rz_reg_get_value_by_role(dbg->reg, RZ_REG_NAME_PC);
mu_assert_eq(pc, 0x5a, "pc recoiled after hitting sw breakpoint at 0x54");
mu_assert_streq(rz_strbuf_get(&ctx->output),
"PRNT with next pc = 0x54\n"
"pr with next pc = 0x5a\n",
"output");
rz_io_read_at(io, 0x4c, data, sizeof(data));
mu_assert_memeq(data, (const ut8 *)full_code, sizeof(data), "restored original bytes");
r = rz_debug_continue(dbg);
mu_assert_false(dbg_mock_failed, "global failure");
mu_assert_true(r, "continue");
rz_debug_reg_sync(dbg, RZ_REG_TYPE_ANY, false);
mu_assert_false(dbg_mock_failed, "global failure");
pc = rz_reg_get_value_by_role(dbg->reg, RZ_REG_NAME_PC);
mu_assert_eq(pc, 0x5e, "pc recoiled after hitting sw breakpoint at 0x54");
mu_assert_streq(rz_strbuf_get(&ctx->output),
"PRNT with next pc = 0x54\n"
"pr with next pc = 0x5a\n"
"pr with next pc = 0x5c\n",
"output");
rz_io_read_at(io, 0x4c, data, sizeof(data));
mu_assert_memeq(data, (const ut8 *)full_code, sizeof(data), "restored original bytes");
r = rz_debug_continue(dbg);
mu_assert_false(dbg_mock_failed, "global failure");
mu_assert_true(r, "continue");
rz_debug_reg_sync(dbg, RZ_REG_TYPE_ANY, false);
mu_assert_false(dbg_mock_failed, "global failure");
pc = rz_reg_get_value_by_role(dbg->reg, RZ_REG_NAME_PC);
mu_assert_eq(pc, 0x68, "pc recoiled after hitting sw breakpoint at 0x54");
mu_assert_streq(rz_strbuf_get(&ctx->output),
"PRNT with next pc = 0x54\n"
"pr with next pc = 0x5a\n"
"pr with next pc = 0x5c\n"
"PRNT with next pc = 0x64\n",
"output");
rz_io_read_at(io, 0x4c, data, sizeof(data));
mu_assert_memeq(data, (const ut8 *)full_code, sizeof(data), "restored original bytes");
#undef FULL_CODE_SIZE
rz_debug_free(dbg);
rz_io_free(io);
mu_end;
}
/// @}
/**
* \brief Simple hw breakpoint test
* Start at 0x30, set breakpoint at 0x50 and 0x80
*/
static bool test_debug_hw_bp(void) {
RzDebug *dbg;
RzIO *io;
SETUP_DEBUG(&dbg_mock_plugin, &bp_ctx);
rz_io_open_at(io, "malloc://0x1000", RZ_PERM_RW, 0644, 0x0, NULL);
rz_io_write_at(io, 0x50, (const ut8 *)"PRNT", 4);
int r = rz_debug_attach(dbg, 42);
mu_assert_false(dbg_mock_failed, "global failure");
mu_assert_true(r, "attach");
rz_debug_reg_sync(dbg, RZ_REG_TYPE_ANY, false);
mu_assert_false(dbg_mock_failed, "global failure");
ut64 pc = rz_reg_get_value_by_role(dbg->reg, RZ_REG_NAME_PC);
mu_assert_eq(pc, 0x30, "initial pc");
RzBreakpointItem *b = rz_debug_bp_add(dbg, 0x50, 0, true, false, 0, NULL, 0);
mu_assert_false(dbg_mock_failed, "global failure");
mu_assert_notnull(b, "add bp");
mu_assert_true(b->hw, "bp is hardware");
b = rz_debug_bp_add(dbg, 0x60, 0, true, false, 0, NULL, 0);
mu_assert_false(dbg_mock_failed, "global failure");
mu_assert_notnull(b, "add bp");
mu_assert_true(b->hw, "bp is hardware");
r = rz_debug_continue(dbg);
mu_assert_false(dbg_mock_failed, "global failure");
mu_assert_true(r, "continue");
rz_debug_reg_sync(dbg, RZ_REG_TYPE_ANY, false);
mu_assert_false(dbg_mock_failed, "global failure");
pc = rz_reg_get_value_by_role(dbg->reg, RZ_REG_NAME_PC);
mu_assert_eq(pc, 0x50, "pc after hitting hw breakpoint at 0x54");
ut8 data[4];
rz_io_read_at(io, 0x50, data, sizeof(data));
mu_assert_memeq(data, (const ut8 *)"PRNT", 4, "original bytes");
DebugMockCtx *ctx = dbg->plugin_data;
mu_assert_streq(rz_strbuf_get(&ctx->output), "", "no print hit");
r = rz_debug_continue(dbg);
mu_assert_false(dbg_mock_failed, "global failure");
mu_assert_true(r, "continue");
rz_debug_reg_sync(dbg, RZ_REG_TYPE_ANY, false);
mu_assert_false(dbg_mock_failed, "global failure");
pc = rz_reg_get_value_by_role(dbg->reg, RZ_REG_NAME_PC);
mu_assert_eq(pc, 0x60, "pc after hitting hw breakpoint at 0x54");
mu_assert_streq(rz_strbuf_get(&ctx->output), "PRNT with next pc = 0x54\n", "print hit");
rz_debug_free(dbg);
rz_io_free(io);
mu_end;
}
/**
* \brief Simple hw watchpoint test
* Start at 0x30, set a watchpoint and run until hit
*/
static bool test_debug_hw_watch(void) {
RzDebug *dbg;
RzIO *io;
SETUP_DEBUG(&dbg_mock_plugin, &bp_ctx);
rz_io_open_at(io, "malloc://0x1000", RZ_PERM_RW, 0644, 0x0, NULL);
const char *code =
"PRNT" // 0x50
"L050" // 0x54
"PRNT" // 0x58
"S04f" // 0x5c
"L04f" // 0x60
"PRNT" // 0x64
"STOP"; // 0x68
rz_io_write_at(io, 0x50, (const ut8 *)code, strlen(code));
int r = rz_debug_attach(dbg, 42);
mu_assert_false(dbg_mock_failed, "global failure");
mu_assert_true(r, "attach");
RzBreakpointItem *b = rz_debug_bp_add(dbg, 0x4c, 4, true, true, RZ_PERM_R, NULL, 0);
mu_assert_false(dbg_mock_failed, "global failure");
mu_assert_notnull(b, "add wp");
mu_assert_true(b->hw, "bp is hardware");
r = rz_debug_continue(dbg);
mu_assert_false(dbg_mock_failed, "global failure");
mu_assert_true(r, "continue");
rz_debug_reg_sync(dbg, RZ_REG_TYPE_ANY, false);
mu_assert_false(dbg_mock_failed, "global failure");
ut64 pc = rz_reg_get_value_by_role(dbg->reg, RZ_REG_NAME_PC);
mu_assert_eq(pc, 0x64, "pc after hitting watchpoint");
DebugMockCtx *ctx = dbg->plugin_data;
mu_assert_streq(rz_strbuf_get(&ctx->output), "PRNT with next pc = 0x54\nPRNT with next pc = 0x5c\n",
"side effects at watchpoint hit");
mu_assert_eq(rz_reg_getv(dbg->reg, "a"), 'P', "acc side effects at watchpoint hit");
r = rz_debug_continue(dbg);
mu_assert_false(dbg_mock_failed, "global failure");
mu_assert_true(r, "continue");
mu_assert_streq(rz_strbuf_get(&ctx->output),
"PRNT with next pc = 0x54\nPRNT with next pc = 0x5c\nPRNT with next pc = 0x68\n",
"print hit");
rz_debug_free(dbg);
rz_io_free(io);
mu_end;
}
int all_tests() {
rz_cons_new(); // there is some windows-specific code in debug that accesses the cons singleton
mu_run_test(test_rz_debug_use);
mu_run_test(test_rz_debug_reg_offset);
mu_run_test(test_debug_sw_bp);
mu_run_test(test_debug_sw_bp_multibits);
mu_run_test(test_debug_hw_bp);
mu_run_test(test_debug_hw_watch);
rz_cons_free();
return tests_passed != tests_run;
}
mu_main(all_tests)