Add: Mark API (#5313)

* Add: Bookmark API

* Fix: missing licenses + project migration

* Fix: migration

* Fix: formatting

* Fix: migrate info in cmd/project test

* Rename: Bookmark to Mark

* Remove: bookmark

* Fix: function type comment

* Fix: code coverage

* Add: tests, docs, ownership type annotations

- Added test/db/cmd/cmd_mark test
- Improved API documentation with \brief, \param, \return and proper grammar
- Added RZ_BORROW / RZ_OWN annotations to relevant API functions

* Fix: missed annotation

* Fix: type annotations + update meson.build

* Fix: License in `cmd_mark.yaml`

* `serialize_mark.c`: add NULL checks
This commit is contained in:
Premade 2025-09-02 03:06:01 +05:00 committed by GitHub
parent a77afb0a29
commit 06061654db
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 2497 additions and 17 deletions

146
librz/core/cmark.c Normal file
View file

@ -0,0 +1,146 @@
// SPDX-FileCopyrightText: 2025 PremadeS <emadsohail001@gmail.com>
// SPDX-License-Identifier: LGPL-3.0-only
#include <rz_types_base.h>
#include <rz_util/rz_str.h>
#include <rz_cmd.h>
#include <rz_mark.h>
#include <rz_util.h>
#include <rz_cons.h>
struct print_mark_t {
RzMark *b;
PJ *pj;
RzTable *tbl;
bool in_range;
ut64 range_from;
ut64 range_to;
};
static bool print_mark_name(RzMarkItem *bm, void *user) {
struct print_mark_t *u = (struct print_mark_t *)user;
if (u->in_range &&
!(RZ_BETWEEN(u->range_from, bm->from, u->range_to) || RZ_BETWEEN(u->range_from, bm->to, u->range_to) ||
RZ_BETWEEN(bm->from, u->range_from, bm->to) || RZ_BETWEEN(bm->from, u->range_to, bm->to))) {
return true;
}
rz_cons_printf("%s\n", bm->name);
return true;
}
static bool print_mark_json(RzMarkItem *bm, void *user) {
struct print_mark_t *u = (struct print_mark_t *)user;
if (u->in_range &&
!(RZ_BETWEEN(u->range_from, bm->from, u->range_to) || RZ_BETWEEN(u->range_from, bm->to, u->range_to) ||
RZ_BETWEEN(bm->from, u->range_from, bm->to) || RZ_BETWEEN(bm->from, u->range_to, bm->to))) {
return true;
}
const char *realname = RZ_STR_ISEMPTY(bm->realname) ? bm->name : bm->realname;
pj_o(u->pj);
pj_kn(u->pj, "from", bm->from);
pj_kn(u->pj, "to", bm->to);
pj_ks(u->pj, "name", bm->name);
pj_ks(u->pj, "realname", realname);
pj_ki(u->pj, "size", (bm->to >= bm->from) ? (bm->to - bm->from) : 0);
if (bm->comment) {
pj_ks(u->pj, "comment", bm->comment);
}
pj_end(u->pj);
return true;
}
static bool print_mark_range_name(RzMarkItem *bm, void *user) {
struct print_mark_t *u = (struct print_mark_t *)user;
if (u->in_range &&
!(RZ_BETWEEN(u->range_from, bm->from, u->range_to) || RZ_BETWEEN(u->range_from, bm->to, u->range_to) ||
RZ_BETWEEN(bm->from, u->range_from, bm->to) || RZ_BETWEEN(bm->from, u->range_to, bm->to))) {
return true;
}
rz_cons_printf("[0x%08" PFMT64x " - 0x%08" PFMT64x "] %s\n",
bm->from, bm->to, bm->name);
return true;
}
static bool print_mark_rizin(RzMarkItem *bm, void *user) {
struct print_mark_t *u = (struct print_mark_t *)user;
if (u->in_range &&
!(RZ_BETWEEN(u->range_from, bm->from, u->range_to) || RZ_BETWEEN(u->range_from, bm->to, u->range_to) ||
RZ_BETWEEN(bm->from, u->range_from, bm->to) || RZ_BETWEEN(bm->from, u->range_to, bm->to))) {
return true;
}
char *comment_b64 = NULL, *tmp = NULL;
if (RZ_STR_ISNOTEMPTY(bm->comment)) {
comment_b64 = rz_base64_encode_dyn((const ut8 *)bm->comment, strlen(bm->comment));
// prefix the armored string with "base64:"
if (comment_b64) {
tmp = rz_str_newf("base64:%s", comment_b64);
free(comment_b64);
comment_b64 = tmp;
}
}
ut64 size = (bm->to >= bm->from) ? (bm->to - bm->from) : 0;
rz_cons_printf("[0x%08" PFMT64x " - 0x%08" PFMT64x "] %s %" PFMT64u " %s\n",
bm->from, bm->to, bm->name, size, rz_str_get_null(comment_b64));
return true;
}
static bool print_mark_table(RzMarkItem *bm, void *user) {
struct print_mark_t *u = (struct print_mark_t *)user;
if (u->in_range &&
!(RZ_BETWEEN(u->range_from, bm->from, u->range_to) || RZ_BETWEEN(u->range_from, bm->to, u->range_to) ||
RZ_BETWEEN(bm->from, u->range_from, bm->to) || RZ_BETWEEN(bm->from, u->range_to, bm->to))) {
return true;
}
if (!RZ_STR_ISEMPTY(bm->name)) {
const char *realname = RZ_STR_ISEMPTY(bm->realname) ? bm->name : bm->realname;
rz_table_add_rowf(u->tbl, "XXss", bm->from, bm->to, bm->name, realname);
}
return true;
}
static void mark_print(RzMark *b, RzCmdStateOutput *state,
ut64 range_from, ut64 range_to, bool in_range) {
rz_return_if_fail(b);
struct print_mark_t u = {
.b = b,
.in_range = in_range,
.range_from = range_from,
.range_to = range_to
};
switch (state->mode) {
case RZ_OUTPUT_MODE_QUIET:
rz_mark_foreach(b, print_mark_name, &u);
break;
case RZ_OUTPUT_MODE_STANDARD:
rz_mark_foreach(b, print_mark_range_name, &u);
break;
case RZ_OUTPUT_MODE_JSON:
u.pj = state->d.pj;
pj_a(state->d.pj);
rz_mark_foreach(b, print_mark_json, &u);
pj_end(state->d.pj);
break;
case RZ_OUTPUT_MODE_RIZIN:
rz_mark_foreach(b, print_mark_rizin, &u);
break;
case RZ_OUTPUT_MODE_TABLE:
u.tbl = state->d.t;
rz_cmd_state_output_set_columnsf(state, "XXss", "from", "to", "name", "realname");
rz_mark_foreach(b, print_mark_table, &u);
break;
default:
rz_warn_if_reached();
break;
}
}
RZ_IPI void rz_core_mark_print(RzMark *b, RzCmdStateOutput *state) {
mark_print(b, state, UT64_MAX, UT64_MAX, false);
}
RZ_IPI void rz_core_mark_range_print(RzMark *b, RzCmdStateOutput *state,
ut64 range_from, ut64 range_to) {
mark_print(b, state, range_from, range_to, true);
}

336
librz/core/cmd/cmd_mark.c Normal file
View file

@ -0,0 +1,336 @@
// SPDX-FileCopyrightText: 2009-2025 PremadeS <emadsohail001@gmail.com>
// SPDX-License-Identifier: LGPL-3.0-only
#include <stddef.h>
#include <rz_cons.h>
#include <rz_core.h>
#include <../core_private.h>
#include <rz_mark.h>
#include <rz_cmd.h>
#include <rz_flag.h>
#include <rz_util/rz_log.h>
static bool mark_set_comment(RzMarkItem *item, const char *comment) {
if (!strncmp(comment, "base64:", 7)) {
const char *b64str = comment + 7;
char *dec = (char *)rz_base64_decode_dyn(b64str, -1);
if (!dec) {
RZ_LOG_ERROR("Failed to decode base64-encoded string: \"%s\"\n", b64str);
return false;
}
rz_mark_item_set_comment(item, dec);
free(dec);
} else {
rz_mark_item_set_comment(item, comment);
}
return true;
}
struct mark_to_mark_t {
ut64 next;
ut64 offset;
};
static bool mark_to_mark_foreach(RzMarkItem *bi, void *user) {
struct mark_to_mark_t *u = (struct mark_to_mark_t *)user;
if (bi->from < u->next && bi->from > u->offset) {
u->next = bi->from;
}
return true;
}
static int mark_to_mark(RzCore *core, const char *glob) {
rz_return_val_if_fail(core, 0);
rz_return_val_if_fail(glob, 0);
glob = rz_str_trim_head_ro(glob);
struct mark_to_mark_t u = { .next = UT64_MAX, .offset = core->offset };
rz_mark_foreach_glob(core->marks, glob, mark_to_mark_foreach, &u);
if (u.next != UT64_MAX && u.next > core->offset) {
return u.next - core->offset;
}
return 0;
}
static void mark_add_flag(RzFlag *f, RzMarkItem *bi) {
if (!f || !bi) {
return;
}
RzSpace *prev_space = rz_flag_space_cur(f);
RzSpace *mark_space = rz_flag_space_get(f, "marks");
if (!mark_space) {
mark_space = rz_flag_space_set(f, "marks");
} else {
rz_flag_space_set(f, "marks");
}
rz_flag_set(f, bi->name, bi->from, (ut32)(bi->to - bi->from));
if (prev_space) {
rz_flag_space_set(f, prev_space->name);
}
}
RZ_IPI RzCmdStatus rz_mark_add_handler(RzCore *core, int argc, const char **argv) {
RzMarkItem *item;
bool addMark = true;
ut64 end = rz_num_math(core->num, argv[2]);
if ((item = rz_mark_get_at(core->marks, core->offset))) {
RZ_LOG_ERROR("Cannot create mark \"%s\" from 0x%" PFMT64x " to 0x%" PFMT64x " because there is already \"%s\" mark\n", argv[1], core->offset, end, item->name);
addMark = false;
}
if (end < core->offset) {
RZ_LOG_ERROR("Ending offset (0x%" PFMT64x ") must be greater than or equal to starting offset (0x%" PFMT64x ")\n",
end, core->offset);
return RZ_CMD_STATUS_ERROR;
}
if (addMark) {
item = rz_mark_set(core->marks, argv[1], core->offset, end);
if (item) {
mark_add_flag(core->flags, item);
}
}
if (!item) {
RZ_LOG_ERROR(
"Cannot create mark \"%s\" from 0x%" PFMT64x " to 0x%" PFMT64x "\n", argv[1], core->offset, end);
return RZ_CMD_STATUS_ERROR;
}
if (argc > 3) {
return bool2status(mark_set_comment(item, argv[3]));
}
return RZ_CMD_STATUS_OK;
}
RZ_IPI RzCmdStatus rz_mark_append_handler(RzCore *core, int argc, const char **argv) {
ut64 end = rz_num_math(core->num, argv[2]);
RzMarkItem *item = rz_mark_set(core->marks, argv[1], core->offset, end);
if (!item) {
RZ_LOG_ERROR(
"Cannot create mark \"%s\" from 0x%" PFMT64x " to 0x%" PFMT64x "\n", argv[1], core->offset, end);
return RZ_CMD_STATUS_ERROR;
}
mark_add_flag(core->flags, item);
if (argc > 3) {
return bool2status(mark_set_comment(item, argv[3]));
}
return RZ_CMD_STATUS_OK;
}
RZ_IPI RzCmdStatus rz_mark_remove_handler(RzCore *core, int argc, const char **argv) {
if (argc < 2) {
RzList *list = rz_mark_get_all_off(core->marks, core->offset);
RzListIter *it;
RzMarkItem *bi;
rz_list_foreach (list, it, bi) {
RzFlagItem *fi = rz_flag_get(core->flags, bi->name);
rz_flag_unset(core->flags, fi);
}
rz_mark_unset_all_off(core->marks, core->offset);
return RZ_CMD_STATUS_OK;
}
if (rz_mark_unset_glob(core->marks, argv[1]) < 0) {
return RZ_CMD_STATUS_ERROR;
}
RzFlagItem *fi = rz_flag_get(core->flags, argv[1]);
if (fi) {
rz_flag_unset(core->flags, fi);
}
return RZ_CMD_STATUS_OK;
}
RZ_IPI RzCmdStatus rz_mark_list_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
rz_core_mark_print(core->marks, state);
return RZ_CMD_STATUS_OK;
}
RZ_IPI RzCmdStatus rz_mark_remove_all_handler(RzCore *core, int argc, const char **argv) {
RzList *list = rz_mark_all_list(core->marks);
RzListIter *it;
RzMarkItem *bi;
rz_list_foreach (list, it, bi) {
RzFlagItem *fi = rz_flag_get(core->flags, bi->name);
if (fi) {
rz_flag_unset(core->flags, fi);
}
}
rz_list_free(list);
rz_mark_unset_all(core->marks);
return RZ_CMD_STATUS_OK;
}
RZ_IPI RzCmdStatus rz_mark_color_handler(RzCore *core, int argc, const char **argv) {
RzMarkItem *bi = rz_mark_get(core->marks, argv[1]);
if (!bi) {
RZ_LOG_ERROR("Cannot find the mark '%s'\n", argv[1]);
return RZ_CMD_STATUS_ERROR;
}
if (argc < 3) {
if (bi->color) {
rz_cons_println(bi->color);
}
return RZ_CMD_STATUS_OK;
}
const char *ret = rz_mark_item_set_color(bi, argv[2]);
if (ret) {
rz_cons_println(ret);
}
return RZ_CMD_STATUS_OK;
}
RZ_IPI RzCmdStatus rz_mark_list_at_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
rz_core_mark_range_print(core->marks, state, core->offset, core->offset + 1);
return RZ_CMD_STATUS_OK;
}
RZ_IPI RzCmdStatus rz_mark_comment_handler(RzCore *core, int argc, const char **argv) {
RzMarkItem *item;
if (argc > 2) {
item = rz_mark_get(core->marks, argv[1]);
if (!item) {
RZ_LOG_ERROR("Cannot find mark with name '%s'\n", argv[1]);
return RZ_CMD_STATUS_ERROR;
}
return bool2status(mark_set_comment(item, argv[2]));
} else {
item = rz_mark_get(core->marks, argv[1]);
if (!item) {
RZ_LOG_ERROR("Cannot find mark\n");
return RZ_CMD_STATUS_ERROR;
} else if (item->comment) {
rz_cons_println(item->comment);
}
}
return RZ_CMD_STATUS_OK;
}
RZ_IPI RzCmdStatus rz_mark_rename_handler(RzCore *core, int argc, const char **argv) {
RzMarkItem *item = rz_mark_get(core->marks, argv[1]);
if (!item) {
RZ_LOG_ERROR("Cannot find matching mark\n");
return RZ_CMD_STATUS_ERROR;
}
if (!rz_mark_rename(core->marks, item, argv[2])) {
RZ_LOG_ERROR("Invalid new mark name\n");
return RZ_CMD_STATUS_ERROR;
}
RzFlagItem *fi = rz_flag_get(core->flags, argv[1]);
rz_flag_rename(core->flags, fi, argv[2]);
return RZ_CMD_STATUS_OK;
}
RZ_IPI RzCmdStatus rz_mark_realname_handler(RzCore *core, int argc, const char **argv) {
RzMarkItem *item = rz_mark_get(core->marks, argv[1]);
if (!item) {
RZ_LOG_ERROR("Cannot find mark\n");
return RZ_CMD_STATUS_ERROR;
}
if (argc < 3) {
rz_cons_printf("%s\n", item->realname);
} else {
rz_mark_item_set_realname(item, argv[2]);
RzFlagItem *fi = rz_flag_get(core->flags, item->name);
rz_flag_item_set_realname(fi, argv[2]);
}
return RZ_CMD_STATUS_OK;
}
RZ_IPI RzCmdStatus rz_mark_move_handler(RzCore *core, int argc, const char **argv) {
RzMarkItem *item = rz_mark_get(core->marks, argv[1]);
if (!item) {
RZ_LOG_ERROR("Cannot find matching mark\n");
return RZ_CMD_STATUS_OK;
}
ut64 new_start = rz_num_math(core->num, argv[2]);
ut64 new_end = rz_num_math(core->num, argv[3]);
RzMarkItem *new_item = rz_mark_set(core->marks, item->name, new_start, new_end);
if (!new_item) {
RZ_LOG_ERROR(
"Cannot move mark \"%s\" to 0x%" PFMT64x " - 0x%" PFMT64x "\n", argv[1], new_start, new_end);
return RZ_CMD_STATUS_ERROR;
}
rz_flag_set(core->flags, item->name, new_start, (ut32)(new_end - new_start));
return RZ_CMD_STATUS_OK;
}
RZ_IPI RzCmdStatus rz_mark_distance_handler(RzCore *core, int argc, const char **argv) {
rz_cons_printf("%d\n", mark_to_mark(core, argv[1]));
return RZ_CMD_STATUS_OK;
}
RZ_IPI RzCmdStatus rz_core_mark_describe(RzCore *core, ut64 addr, RzMarkItem *b, RzCmdStateOutput *state) {
ut64 size = (b->to >= b->from) ? (b->to - b->from) : 0;
PJ *pj = state->d.pj;
rz_cmd_state_output_set_columnsf(state, "ssXXds", "name", "realname",
"from", "to", "size", "comment");
switch (state->mode) {
case RZ_OUTPUT_MODE_JSON:
pj_o(pj);
pj_ks(pj, "name", b->name);
pj_kn(pj, "from", b->from);
pj_kn(pj, "to", b->to);
pj_kn(pj, "size", size);
if (b->comment) {
pj_ks(pj, "comment", b->comment);
}
if (b->realname) {
pj_ks(pj, "realname", b->realname);
}
pj_end(pj);
break;
case RZ_OUTPUT_MODE_TABLE: {
ut64 size = (b->to >= b->from) ? (b->to - b->from) : 0;
const char *name = b->realname ? b->realname : b->name;
if (addr >= b->from && addr <= b->to) {
rz_table_add_rowf(state->d.t, "ssXXds", b->name, name, b->from, b->to, size, b->comment);
} else if (addr < b->from) {
const char *descr_name = rz_str_newf("%s - %d", name, (int)(b->from - addr));
rz_table_add_rowf(state->d.t, "ssXXds", b->name, descr_name, b->from, b->to, size, b->comment);
} else {
const char *descr_name = rz_str_newf("%s + %d", name, (int)(addr - b->to));
rz_table_add_rowf(state->d.t, "ssXXds", b->name, descr_name, b->from, b->to, size, b->comment);
}
break;
}
case RZ_OUTPUT_MODE_STANDARD: {
const char *name = b->realname ? b->realname : b->name;
if (addr >= b->from && addr <= b->to) {
rz_cons_printf("[0x%08" PFMT64x " - 0x%08" PFMT64x "] %s\n", b->from, b->to, name);
} else if (addr < b->from) {
rz_cons_printf("[0x%08" PFMT64x " - 0x%08" PFMT64x "] %s - %d\n", b->from, b->to, name, (int)(b->from - addr));
} else {
rz_cons_printf("[0x%08" PFMT64x " - 0x%08" PFMT64x "] %s + %d\n", b->from, b->to, name, (int)(addr - b->to));
}
break;
}
default:
rz_warn_if_reached();
break;
}
return RZ_CMD_STATUS_OK;
}
RZ_IPI RzCmdStatus rz_mark_describe_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
RzMarkItem *b;
if (argc > 1) {
b = rz_mark_get(core->marks, argv[1]);
} else {
b = rz_mark_get_at(core->marks, core->offset);
}
if (!b) {
RZ_LOG_ERROR("Cannot find mark\n");
return RZ_CMD_STATUS_ERROR;
}
rz_core_mark_describe(core, core->offset, b, state);
return RZ_CMD_STATUS_OK;
}
RZ_IPI RzCmdStatus rz_mark_range_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
if (argc > 1) {
ut64 size = rz_num_math(core->num, argv[1]);
rz_core_mark_range_print(core->marks, state, core->offset, core->offset + size);
} else {
rz_core_mark_range_print(core->marks, state, core->offset, core->offset + core->blocksize);
}
return RZ_CMD_STATUS_OK;
}

View file

@ -72,6 +72,7 @@ static const RzCmdDescDetail cmd_debug_inject_opcode_details[2];
static const RzCmdDescDetail cmd_debug_inject_assembly_details[2];
static const RzCmdDescDetail cmd_debug_inject_syscall_details[2];
static const RzCmdDescDetail eval_getset_details[2];
static const RzCmdDescDetail mark_describe_details[2];
static const RzCmdDescDetail egg_config_details[2];
static const RzCmdDescDetail history_list_or_exec_details[2];
static const RzCmdDescDetail cmd_info_query_details[2];
@ -583,6 +584,17 @@ static const RzCmdDescArg flag_tag_search_args[2];
static const RzCmdDescArg flag_zone_add_args[2];
static const RzCmdDescArg flag_zone_remove_args[2];
static const RzCmdDescArg flag_hexdump_args[2];
static const RzCmdDescArg mark_add_args[4];
static const RzCmdDescArg mark_append_args[4];
static const RzCmdDescArg mark_remove_args[2];
static const RzCmdDescArg mark_color_args[3];
static const RzCmdDescArg mark_comment_args[3];
static const RzCmdDescArg mark_rename_args[3];
static const RzCmdDescArg mark_realname_args[3];
static const RzCmdDescArg mark_move_args[4];
static const RzCmdDescArg mark_distance_args[2];
static const RzCmdDescArg mark_describe_args[2];
static const RzCmdDescArg mark_range_args[2];
static const RzCmdDescArg flirt_create_args[2];
static const RzCmdDescArg flirt_dump_args[2];
static const RzCmdDescArg flirt_scan_args[2];
@ -12457,6 +12469,262 @@ static const RzCmdDescHelp flag_hexdump_help = {
.args = flag_hexdump_args,
};
static const RzCmdDescHelp m_help = {
.summary = "Manage marks",
};
static const RzCmdDescArg mark_add_args[] = {
{
.name = "name",
.type = RZ_CMD_ARG_TYPE_STRING,
},
{
.name = "end",
.type = RZ_CMD_ARG_TYPE_NUM,
},
{
.name = "comment",
.type = RZ_CMD_ARG_TYPE_STRING,
.flags = RZ_CMD_ARG_FLAG_LAST,
.optional = true,
},
{ 0 },
};
static const RzCmdDescHelp mark_add_help = {
.summary = "Adds a mark if there are no existing marks.",
.description = "Adds the mark to the current offset only if no mark covers this offset already",
.args = mark_add_args,
};
static const RzCmdDescArg mark_append_args[] = {
{
.name = "name",
.type = RZ_CMD_ARG_TYPE_STRING,
},
{
.name = "end",
.type = RZ_CMD_ARG_TYPE_NUM,
},
{
.name = "comment",
.type = RZ_CMD_ARG_TYPE_STRING,
.flags = RZ_CMD_ARG_FLAG_LAST,
.optional = true,
},
{ 0 },
};
static const RzCmdDescHelp mark_append_help = {
.summary = "Add mark.",
.description = "Adds a mark at the current offset, replacing any existing mark with the same name.",
.args = mark_append_args,
};
static const RzCmdDescArg mark_remove_args[] = {
{
.name = "regex_pattern",
.type = RZ_CMD_ARG_TYPE_STRING,
.flags = RZ_CMD_ARG_FLAG_LAST,
.optional = true,
},
{ 0 },
};
static const RzCmdDescHelp mark_remove_help = {
.summary = "Remove mark.",
.description = "Removes marks by name. If <regex_pattern> is given, only marks whose name matches the regex are removed. Otherwise, all marks covering the current offset are removed.",
.args = mark_remove_args,
};
static const RzCmdDescArg mark_remove_all_args[] = {
{ 0 },
};
static const RzCmdDescHelp mark_remove_all_help = {
.summary = "Remove all marks.",
.args = mark_remove_all_args,
};
static const RzCmdDescArg mark_list_args[] = {
{ 0 },
};
static const RzCmdDescHelp mark_list_help = {
.summary = "List all marks.",
.args = mark_list_args,
};
static const RzCmdDescArg mark_list_at_args[] = {
{ 0 },
};
static const RzCmdDescHelp mark_list_at_help = {
.summary = "List all marks containing the current offset.",
.args = mark_list_at_args,
};
static const RzCmdDescArg mark_color_args[] = {
{
.name = "name",
.type = RZ_CMD_ARG_TYPE_FLAG,
},
{
.name = "color",
.type = RZ_CMD_ARG_TYPE_STRING,
.flags = RZ_CMD_ARG_FLAG_LAST,
.optional = true,
},
{ 0 },
};
static const RzCmdDescHelp mark_color_help = {
.summary = "Set a color for the given mark / Show the color for the given mark.",
.args = mark_color_args,
};
static const RzCmdDescArg mark_comment_args[] = {
{
.name = "name",
.type = RZ_CMD_ARG_TYPE_FLAG,
},
{
.name = "comment",
.type = RZ_CMD_ARG_TYPE_STRING,
.flags = RZ_CMD_ARG_FLAG_LAST,
.optional = true,
},
{ 0 },
};
static const RzCmdDescHelp mark_comment_help = {
.summary = "Set a comment for the given mark. If no comment is given, it shows the current one for the mark.",
.args = mark_comment_args,
};
static const RzCmdDescArg mark_rename_args[] = {
{
.name = "old",
.type = RZ_CMD_ARG_TYPE_FLAG,
},
{
.name = "new",
.type = RZ_CMD_ARG_TYPE_STRING,
.flags = RZ_CMD_ARG_FLAG_LAST,
},
{ 0 },
};
static const RzCmdDescHelp mark_rename_help = {
.summary = "Rename mark.",
.args = mark_rename_args,
};
static const RzCmdDescArg mark_realname_args[] = {
{
.name = "name",
.type = RZ_CMD_ARG_TYPE_FLAG,
},
{
.name = "realname",
.type = RZ_CMD_ARG_TYPE_STRING,
.flags = RZ_CMD_ARG_FLAG_LAST,
.optional = true,
},
{ 0 },
};
static const RzCmdDescHelp mark_realname_help = {
.summary = "Show the realname of the mark / Set the realname of the mark.",
.description = "Each mark has two identifiers. <name> is the unique, escaped identifier used internally by Rizin. <realname> is the original, unescaped name, kept as given by the user.",
.args = mark_realname_args,
};
static const RzCmdDescArg mark_move_args[] = {
{
.name = "name",
.type = RZ_CMD_ARG_TYPE_FLAG,
},
{
.name = "new_start",
.type = RZ_CMD_ARG_TYPE_RZNUM,
},
{
.name = "new_end",
.type = RZ_CMD_ARG_TYPE_RZNUM,
.flags = RZ_CMD_ARG_FLAG_LAST,
},
{ 0 },
};
static const RzCmdDescHelp mark_move_help = {
.summary = "Move a mark to new a location.",
.args = mark_move_args,
};
static const RzCmdDescArg mark_distance_args[] = {
{
.name = "regex_pattern",
.type = RZ_CMD_ARG_TYPE_STRING,
.flags = RZ_CMD_ARG_FLAG_LAST,
},
{ 0 },
};
static const RzCmdDescHelp mark_distance_help = {
.summary = "Distance in bytes to reach the starting of mark from the current offset.",
.args = mark_distance_args,
};
static const RzCmdDescHelp md_help = {
.summary = "Describe mark.",
};
static const RzCmdDescDetailEntry mark_describe_Usage_space_example_detail_entries[] = {
{ .text = "md", .arg_str = NULL, .comment = "Describe all marks containing the current offset" },
{ .text = "md foo", .arg_str = NULL, .comment = "Describe the mark named 'foo' and show distance from current offset to reach 'foo' in bytes" },
{ 0 },
};
static const RzCmdDescDetail mark_describe_details[] = {
{ .name = "Usage example", .entries = mark_describe_Usage_space_example_detail_entries },
{ 0 },
};
static const RzCmdDescArg mark_describe_args[] = {
{
.name = "name",
.type = RZ_CMD_ARG_TYPE_FLAG,
.optional = true,
},
{ 0 },
};
static const RzCmdDescHelp mark_describe_help = {
.summary = "Describe marks at the current offset or a named mark with distance from the offset.",
.description = "Displays information about marks. Without arguments, it describes all marks that contain the current offset. With a <name> argument, it shows details of that mark and the distance in bytes from the current offset to reach the mark. If the mark begins after the current offset, it shows <name> - <bytes> (distance from current offset to start of the mark). If the mark ends before the current offset, it shows <name> + <bytes> (distance from current offset to the end of the mark).",
.details = mark_describe_details,
.args = mark_describe_args,
};
static const RzCmdDescArg mark_range_args[] = {
{
.name = "size",
.type = RZ_CMD_ARG_TYPE_RZNUM,
.flags = RZ_CMD_ARG_FLAG_LAST,
.optional = true,
},
{ 0 },
};
static const RzCmdDescHelp mark_range_help = {
.summary = "Show marks in the current block or in the next <size> bytes.",
.args = mark_range_args,
};
static const RzCmdDescHelp F_help = {
.summary = "FLIRT signature management",
};
@ -23368,6 +23636,47 @@ RZ_IPI void rzshell_cmddescs_init(RzCore *core) {
RzCmdDesc *flag_hexdump_cd = rz_cmd_desc_argv_new(core->rcmd, f_cd, "fx", rz_flag_hexdump_handler, &flag_hexdump_help);
rz_warn_if_fail(flag_hexdump_cd);
RzCmdDesc *m_cd = rz_cmd_desc_group_new(core->rcmd, root_cd, "m", rz_mark_add_handler, &mark_add_help, &m_help);
rz_warn_if_fail(m_cd);
RzCmdDesc *mark_append_cd = rz_cmd_desc_argv_new(core->rcmd, m_cd, "m+", rz_mark_append_handler, &mark_append_help);
rz_warn_if_fail(mark_append_cd);
RzCmdDesc *mark_remove_cd = rz_cmd_desc_argv_new(core->rcmd, m_cd, "m-", rz_mark_remove_handler, &mark_remove_help);
rz_warn_if_fail(mark_remove_cd);
RzCmdDesc *mark_remove_all_cd = rz_cmd_desc_argv_new(core->rcmd, m_cd, "m-*", rz_mark_remove_all_handler, &mark_remove_all_help);
rz_warn_if_fail(mark_remove_all_cd);
RzCmdDesc *mark_list_cd = rz_cmd_desc_argv_state_new(core->rcmd, m_cd, "ml", RZ_OUTPUT_MODE_STANDARD | RZ_OUTPUT_MODE_RIZIN | RZ_OUTPUT_MODE_QUIET | RZ_OUTPUT_MODE_JSON | RZ_OUTPUT_MODE_TABLE, rz_mark_list_handler, &mark_list_help);
rz_warn_if_fail(mark_list_cd);
RzCmdDesc *mark_list_at_cd = rz_cmd_desc_argv_state_new(core->rcmd, m_cd, "ml.", RZ_OUTPUT_MODE_STANDARD | RZ_OUTPUT_MODE_RIZIN | RZ_OUTPUT_MODE_QUIET | RZ_OUTPUT_MODE_JSON | RZ_OUTPUT_MODE_TABLE, rz_mark_list_at_handler, &mark_list_at_help);
rz_warn_if_fail(mark_list_at_cd);
RzCmdDesc *mark_color_cd = rz_cmd_desc_argv_new(core->rcmd, m_cd, "mc", rz_mark_color_handler, &mark_color_help);
rz_warn_if_fail(mark_color_cd);
RzCmdDesc *mark_comment_cd = rz_cmd_desc_argv_new(core->rcmd, m_cd, "mC", rz_mark_comment_handler, &mark_comment_help);
rz_warn_if_fail(mark_comment_cd);
RzCmdDesc *mark_rename_cd = rz_cmd_desc_argv_new(core->rcmd, m_cd, "mr", rz_mark_rename_handler, &mark_rename_help);
rz_warn_if_fail(mark_rename_cd);
RzCmdDesc *mark_realname_cd = rz_cmd_desc_argv_new(core->rcmd, m_cd, "mN", rz_mark_realname_handler, &mark_realname_help);
rz_warn_if_fail(mark_realname_cd);
RzCmdDesc *mark_move_cd = rz_cmd_desc_argv_new(core->rcmd, m_cd, "mm", rz_mark_move_handler, &mark_move_help);
rz_warn_if_fail(mark_move_cd);
RzCmdDesc *mark_distance_cd = rz_cmd_desc_argv_new(core->rcmd, m_cd, "mf", rz_mark_distance_handler, &mark_distance_help);
rz_warn_if_fail(mark_distance_cd);
RzCmdDesc *md_cd = rz_cmd_desc_group_state_new(core->rcmd, m_cd, "md", RZ_OUTPUT_MODE_STANDARD | RZ_OUTPUT_MODE_TABLE | RZ_OUTPUT_MODE_JSON, rz_mark_describe_handler, &mark_describe_help, &md_help);
rz_warn_if_fail(md_cd);
RzCmdDesc *mark_range_cd = rz_cmd_desc_argv_state_new(core->rcmd, m_cd, "mi", RZ_OUTPUT_MODE_STANDARD | RZ_OUTPUT_MODE_RIZIN | RZ_OUTPUT_MODE_QUIET | RZ_OUTPUT_MODE_JSON | RZ_OUTPUT_MODE_TABLE, rz_mark_range_handler, &mark_range_help);
rz_warn_if_fail(mark_range_cd);
RzCmdDesc *F_cd = rz_cmd_desc_group_new(core->rcmd, root_cd, "F", NULL, NULL, &F_help);
rz_warn_if_fail(F_cd);
RzCmdDesc *flirt_create_cd = rz_cmd_desc_argv_new(core->rcmd, F_cd, "Fc", rz_flirt_create_handler, &flirt_create_help);

View file

@ -1603,6 +1603,34 @@ RZ_IPI RzCmdStatus rz_flag_zone_around_handler(RzCore *core, int argc, const cha
RZ_IPI RzCmdStatus rz_flag_zone_list_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state);
// "fx"
RZ_IPI RzCmdStatus rz_flag_hexdump_handler(RzCore *core, int argc, const char **argv);
// "m"
RZ_IPI RzCmdStatus rz_mark_add_handler(RzCore *core, int argc, const char **argv);
// "m+"
RZ_IPI RzCmdStatus rz_mark_append_handler(RzCore *core, int argc, const char **argv);
// "m-"
RZ_IPI RzCmdStatus rz_mark_remove_handler(RzCore *core, int argc, const char **argv);
// "m-*"
RZ_IPI RzCmdStatus rz_mark_remove_all_handler(RzCore *core, int argc, const char **argv);
// "ml"
RZ_IPI RzCmdStatus rz_mark_list_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state);
// "ml."
RZ_IPI RzCmdStatus rz_mark_list_at_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state);
// "mc"
RZ_IPI RzCmdStatus rz_mark_color_handler(RzCore *core, int argc, const char **argv);
// "mC"
RZ_IPI RzCmdStatus rz_mark_comment_handler(RzCore *core, int argc, const char **argv);
// "mr"
RZ_IPI RzCmdStatus rz_mark_rename_handler(RzCore *core, int argc, const char **argv);
// "mN"
RZ_IPI RzCmdStatus rz_mark_realname_handler(RzCore *core, int argc, const char **argv);
// "mm"
RZ_IPI RzCmdStatus rz_mark_move_handler(RzCore *core, int argc, const char **argv);
// "mf"
RZ_IPI RzCmdStatus rz_mark_distance_handler(RzCore *core, int argc, const char **argv);
// "md"
RZ_IPI RzCmdStatus rz_mark_describe_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state);
// "mi"
RZ_IPI RzCmdStatus rz_mark_range_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state);
// "Fc"
RZ_IPI RzCmdStatus rz_flirt_create_handler(RzCore *core, int argc, const char **argv);
// "Fd"

View file

@ -235,6 +235,9 @@ commands:
- name: f
summary: Manage flags
subcommands: cmd_flag
- name: m
summary: Manage marks
subcommands: cmd_mark
- name: F
summary: FLIRT signature management
subcommands: cmd_flirt

View file

@ -0,0 +1,168 @@
# SPDX-FileCopyrightText: 2025 PremadeS <emadsohail001@gmail.com>
# SPDX-License-Identifier: LGPL-3.0-only
---
name: cmd_mark
commands:
- name: m
cname: mark_add
summary: Adds a mark if there are no existing marks.
description: >
Adds the mark to the current offset only if
no mark covers this offset already
args:
- name: name
type: RZ_CMD_ARG_TYPE_STRING
- name: end
type: RZ_CMD_ARG_TYPE_NUM
- name: comment
type: RZ_CMD_ARG_TYPE_STRING
optional: true
- name: m+
cname: mark_append
summary: Add mark.
description: >
Adds a mark at the current offset, replacing any existing mark with the same name.
args:
- name: name
type: RZ_CMD_ARG_TYPE_STRING
- name: end
type: RZ_CMD_ARG_TYPE_NUM
- name: comment
type: RZ_CMD_ARG_TYPE_STRING
optional: true
- name: m-
cname: mark_remove
summary: Remove mark.
description: >
Removes marks by name.
If <regex_pattern> is given, only marks whose name matches the regex
are removed. Otherwise, all marks covering the current offset are removed.
args:
- name: regex_pattern
type: RZ_CMD_ARG_TYPE_STRING
optional: true
- name: m-*
cname: mark_remove_all
summary: Remove all marks.
args: []
- name: ml
cname: mark_list
summary: List all marks.
type: RZ_CMD_DESC_TYPE_ARGV_STATE
modes:
- RZ_OUTPUT_MODE_STANDARD
- RZ_OUTPUT_MODE_RIZIN
- RZ_OUTPUT_MODE_QUIET
- RZ_OUTPUT_MODE_JSON
- RZ_OUTPUT_MODE_TABLE
args: []
- name: ml.
cname: mark_list_at
summary: List all marks containing the current offset.
type: RZ_CMD_DESC_TYPE_ARGV_STATE
modes:
- RZ_OUTPUT_MODE_STANDARD
- RZ_OUTPUT_MODE_RIZIN
- RZ_OUTPUT_MODE_QUIET
- RZ_OUTPUT_MODE_JSON
- RZ_OUTPUT_MODE_TABLE
args: []
- name: mc
cname: mark_color
summary: Set a color for the given mark / Show the color for the given mark.
args:
- name: name
type: RZ_CMD_ARG_TYPE_FLAG
- name: color
type: RZ_CMD_ARG_TYPE_STRING
optional: true
- name: mC
cname: mark_comment
summary: Set a comment for the given mark. If no comment is given, it shows the current one for the mark.
args:
- name: name
type: RZ_CMD_ARG_TYPE_FLAG
- name: comment
type: RZ_CMD_ARG_TYPE_STRING
optional: true
- name: mr
cname: mark_rename
summary: Rename mark.
args:
- name: old
type: RZ_CMD_ARG_TYPE_FLAG
- name: new
type: RZ_CMD_ARG_TYPE_STRING
- name: mN
cname: mark_realname
summary: Show the realname of the mark / Set the realname of the mark.
description: >
Each mark has two identifiers.
<name> is the unique, escaped identifier used internally by Rizin.
<realname> is the original, unescaped name, kept as given by the user.
args:
- name: name
type: RZ_CMD_ARG_TYPE_FLAG
- name: realname
type: RZ_CMD_ARG_TYPE_STRING
optional: true
- name: mm
cname: mark_move
summary: Move a mark to new a location.
args:
- name: name
type: RZ_CMD_ARG_TYPE_FLAG
- name: new_start
type: RZ_CMD_ARG_TYPE_RZNUM
- name: new_end
type: RZ_CMD_ARG_TYPE_RZNUM
- name: mf
cname: mark_distance
summary: Distance in bytes to reach the starting of mark from the current offset.
args:
- name: regex_pattern
type: RZ_CMD_ARG_TYPE_STRING
- name: md
summary: Describe mark.
subcommands:
- name: md
cname: mark_describe
summary: Describe marks at the current offset or a named mark with distance from the offset.
description: >
Displays information about marks.
Without arguments, it describes all marks that contain the current offset.
With a <name> argument, it shows details of that mark and the distance in bytes
from the current offset to reach the mark. If the mark begins after the
current offset, it shows <name> - <bytes> (distance from current offset to
start of the mark). If the mark ends before the current offset, it shows
<name> + <bytes> (distance from current offset to the end of the mark).
type: RZ_CMD_DESC_TYPE_ARGV_STATE
modes:
- RZ_OUTPUT_MODE_STANDARD
- RZ_OUTPUT_MODE_TABLE
- RZ_OUTPUT_MODE_JSON
args:
- name: name
type: RZ_CMD_ARG_TYPE_FLAG
optional: true
details:
- name: Usage example
entries:
- text: "md"
comment: "Describe all marks containing the current offset"
- text: "md foo"
comment: "Describe the mark named 'foo' and show distance from current offset to reach 'foo' in bytes"
- name: mi
cname: mark_range
summary: Show marks in the current block or in the next <size> bytes.
type: RZ_CMD_DESC_TYPE_ARGV_STATE
modes:
- RZ_OUTPUT_MODE_STANDARD
- RZ_OUTPUT_MODE_RIZIN
- RZ_OUTPUT_MODE_QUIET
- RZ_OUTPUT_MODE_JSON
- RZ_OUTPUT_MODE_TABLE
args:
- name: size
type: RZ_CMD_ARG_TYPE_RZNUM
optional: true

View file

@ -3,6 +3,7 @@ cmd_descs_yaml = files(
'cmd_alias.yaml',
'cmd_analysis.yaml',
'cmd_block.yaml',
'cmd_mark.yaml',
'cmd_cmp.yaml',
'cmd_debug.yaml',
'cmd_descs.yaml',

View file

@ -1,6 +1,8 @@
// SPDX-FileCopyrightText: 2009-2020 pancake <pancake@nopcode.org>
// SPDX-License-Identifier: LGPL-3.0-only
#include <rz_mark.h>
#include <rz_util/rz_str.h>
#include <rz_config.h>
#include <rz_util/ht_sp.h>
#include <rz_util/rz_regex.h>
@ -955,10 +957,39 @@ RZ_API int rz_core_fgets(char *buf, int len, void *user) {
return rz_str_ncpy(buf, ptr, len - 1);
}
static void append_marks(RzStrBuf *sb, RzList /*<RzMarkItem *>*/ *list, ut64 addr, bool is_start) {
RzListIter *it;
RzMarkItem *bi;
bool first = (rz_strbuf_length(sb) == 0);
rz_list_foreach (list, it, bi) {
if ((is_start && addr == bi->from) || (!is_start && addr == bi->to)) {
if (!first)
rz_strbuf_append(sb, " ; ");
rz_strbuf_appendf(sb, is_start ? "[s] %s [0x%" PFMT64x "-0x%" PFMT64x "]" : "[e] %s [0x%" PFMT64x "-0x%" PFMT64x "]",
bi->name, bi->from, bi->to);
first = false;
}
}
}
static const char *rz_core_print_offname(void *p, ut64 addr) {
RzCore *c = (RzCore *)p;
RzFlagItem *item = rz_flag_get_i(c->flags, addr);
return item ? item->name : NULL;
RzStrBuf *sb = rz_strbuf_new("");
RzList *all = rz_mark_get_all_off(c->marks, addr);
append_marks(sb, all, addr, true);
append_marks(sb, all, addr, false);
rz_list_free(all);
if (rz_strbuf_length(sb) == 0) {
RzFlagItem *item = rz_flag_get_i(c->flags, addr);
if (item)
rz_strbuf_set(sb, item->name);
}
return rz_strbuf_drain(sb);
}
static int rz_core_print_offsize(void *p, ut64 addr) {
@ -1692,6 +1723,7 @@ RZ_API bool rz_core_init(RzCore *core) {
core->io->ff = 1;
core->search = rz_search_new(RZ_SEARCH_KEYWORD);
core->flags = rz_flag_new();
core->marks = rz_mark_new();
core->graph = rz_agraph_new(rz_cons_canvas_new(1, 1));
core->graph->need_reload_nodes = false;
core->asmqjmps_size = RZ_CORE_ASMQJMPS_NUM;

View file

@ -145,6 +145,10 @@ RZ_IPI void rz_core_debug_signal_print(RzDebug *dbg, RzCmdStateOutput *state);
RZ_IPI RzCoreIOMapInfo *rz_core_io_map_info_new(RzCoreFile *cf, int perm_orig);
RZ_IPI void rz_core_io_map_info_free(RzCoreIOMapInfo *info);
/* cmark.c */
RZ_IPI void rz_core_mark_print(RzMark *b, RzCmdStateOutput *state);
RZ_IPI void rz_core_mark_range_print(RzMark *b, RzCmdStateOutput *state, ut64 range_from, ut64 range_to);
/* cflag.c */
RZ_IPI void rz_core_flag_print(RzFlag *f, RzCmdStateOutput *state);
RZ_IPI void rz_core_flag_real_name_print(RzFlag *f, RzCmdStateOutput *state);

View file

@ -24,6 +24,7 @@ rz_core_sources = [
'casm.c',
'cautocmpl.c',
'cbin.c',
'cmark.c',
'cbounds.c',
'cconfig.c',
'ccrypto.c',
@ -75,6 +76,7 @@ rz_core_sources = [
#'cmd/cmd_analysis.c',
'cmd/cmd_api.c',
'cmd/cmd_block.c',
'cmd/cmd_mark.c',
'cmd/cmd_cmp.c',
#'cmd/cmd_debug.c',
'cmd/cmd_egg.c',
@ -162,6 +164,7 @@ rz_core_deps = [
rz_debug_dep,
rz_config_dep,
rz_bin_dep,
rz_mark_dep,
platform_deps,
dependency('rzgdb'),
dependency('rzheap'),
@ -203,6 +206,7 @@ modules += { 'rz_core': {
'rz_lang',
'rz_hash',
'rz_flag',
'rz_mark',
'rz_egg',
'rz_debug',
'rz_crypto',

View file

@ -701,6 +701,23 @@ RZ_API bool rz_project_migrate_v18_v19(RzProject *prj, RzSerializeResultInfo *re
return true;
}
// --
// Migration 19 -> 20
//
// Changes from c6a7acf8492881938188fc97d782b6bf2957cce7:
// Introduced /core/marks sub-db (Rzmark API)
// This prepares the project schema for the new mark system.
RZ_API bool rz_project_migrate_v19_v20(RzProject *prj, RzSerializeResultInfo *res) {
Sdb *core_db;
RZ_SERIALIZE_SUB(prj, core_db, res, "core", return false;);
Sdb *marks_db = sdb_ns(core_db, "marks", 1);
if (!marks_db) {
return false;
}
return true;
}
static bool (*const migrations[])(RzProject *prj, RzSerializeResultInfo *res) = {
rz_project_migrate_v1_v2,
rz_project_migrate_v2_v3,
@ -720,6 +737,7 @@ static bool (*const migrations[])(RzProject *prj, RzSerializeResultInfo *res) =
rz_project_migrate_v16_v17,
rz_project_migrate_v17_v18,
rz_project_migrate_v18_v19,
rz_project_migrate_v19_v20,
};
/// Migrate the given project to the current version in-place

View file

@ -10,6 +10,7 @@
* /
* /config => see config.c
* /flags => see flag.c
* /marks => see mark.c
* /analysis => see analysis.c
* /file => see below
* /seek => see serialize_core_seek.c
@ -25,6 +26,7 @@ RZ_API void rz_serialize_core_save(RZ_NONNULL Sdb *db, RZ_NONNULL RzCore *core,
file_save(sdb_ns(db, "file", true), core, prj_file);
rz_serialize_config_save(sdb_ns(db, "config", true), core->config);
rz_serialize_flag_save(sdb_ns(db, "flags", true), core->flags);
rz_serialize_mark_save(sdb_ns(db, "marks", true), core->marks);
rz_serialize_analysis_save(sdb_ns(db, "analysis", true), core->analysis);
rz_serialize_debug_save(sdb_ns(db, "debug", true), core->dbg);
rz_serialize_core_seek_save(sdb_ns(db, "seek", true), core);
@ -79,6 +81,7 @@ RZ_API bool rz_serialize_core_load(RZ_NONNULL Sdb *db, RZ_NONNULL RzCore *core,
}
SUB("config", rz_serialize_config_load(subdb, core->config, config_exclude, res));
SUB("flags", rz_serialize_flag_load(subdb, core->flags, res));
SUB("marks", rz_serialize_mark_load(subdb, core->marks, res));
SUB("analysis", rz_serialize_analysis_load(subdb, core->analysis, res));
SUB("debug", rz_serialize_debug_load(subdb, core->dbg, res));
SUB("seek", rz_serialize_core_seek_load(subdb, core, res));

View file

@ -6,23 +6,23 @@
RZ_IPI void rz_core_visual_mark_reset(RzCore *core) {
size_t i;
for (i = 0; i < UT8_MAX; i++) {
core->marks[i] = UT64_MAX;
core->vmarks[i] = UT64_MAX;
}
core->marks_init = true;
core->vmarks_init = true;
}
RZ_IPI bool rz_core_visual_mark_dump(RzCore *core) {
size_t i;
if (!core->marks_init) {
if (!core->vmarks_init) {
return false;
}
bool res = false;
for (i = 0; i < UT8_MAX; i++) {
if (core->marks[i] != UT64_MAX) {
if (core->vmarks[i] != UT64_MAX) {
if (i > ASCII_MAX) {
rz_cons_printf("fV %zu 0x%" PFMT64x "\n", i - ASCII_MAX - 1, core->marks[i]);
rz_cons_printf("fV %zu 0x%" PFMT64x "\n", i - ASCII_MAX - 1, core->vmarks[i]);
} else {
rz_cons_printf("fV %c 0x%" PFMT64x "\n", (char)i, core->marks[i]);
rz_cons_printf("fV %c 0x%" PFMT64x "\n", (char)i, core->vmarks[i]);
}
res = true;
}
@ -31,17 +31,17 @@ RZ_IPI bool rz_core_visual_mark_dump(RzCore *core) {
}
RZ_IPI void rz_core_visual_mark_set(RzCore *core, ut8 ch, ut64 addr) {
if (!core->marks_init) {
if (!core->vmarks_init) {
rz_core_visual_mark_reset(core);
}
core->marks[ch] = addr;
core->vmarks[ch] = addr;
}
RZ_IPI void rz_core_visual_mark_del(RzCore *core, ut8 ch) {
if (!core->marks_init) {
if (!core->vmarks_init) {
return;
}
core->marks[ch] = UT64_MAX;
core->vmarks[ch] = UT64_MAX;
}
RZ_IPI void rz_core_visual_mark(RzCore *core, ut8 ch) {
@ -52,7 +52,7 @@ RZ_IPI void rz_core_visual_mark(RzCore *core, ut8 ch) {
}
RZ_IPI void rz_core_visual_mark_seek(RzCore *core, ut8 ch) {
if (core->marks_init && core->marks[ch] != UT64_MAX) {
rz_core_seek(core, core->marks[ch], true);
if (core->vmarks_init && core->vmarks[ch] != UT64_MAX) {
rz_core_seek(core, core->vmarks[ch], true);
}
}

View file

@ -34,6 +34,7 @@ include_files = [
'rz_list.h',
'rz_magic.h',
'rz_main.h',
'rz_mark.h',
'rz_parse.h',
'rz_pdb.h',
'rz_platform.h',

View file

@ -34,6 +34,7 @@
#include <rz_util/rz_annotated_code.h>
#include <rz_heap_glibc.h>
#include <rz_windows_heap.h>
#include <rz_mark.h>
#ifdef __cplusplus
extern "C" {
@ -282,6 +283,7 @@ struct rz_core_t {
RzLang *lang;
RzDebug *dbg;
RzFlag *flags;
RzMark *marks;
char *lastsearch; ///< Legacy search. Will be removed
RzSearch *search; ///< Legacy search. Will be removed
RzEgg *egg;
@ -341,8 +343,8 @@ struct rz_core_t {
RzList /*<char *>*/ *warnings_after;
RzPath *sys_path; ///< pointer to RzPath, contains path prefix of the system
bool marks_init;
ut64 marks[UT8_MAX + 1];
bool vmarks_init;
ut64 vmarks[UT8_MAX + 1];
RzMainCallback rz_main_rizin;
// int (*rz_main_rizin)(int argc, char **argv);

86
librz/include/rz_mark.h Normal file
View file

@ -0,0 +1,86 @@
#ifndef RZ_MARK_H
#define RZ_MARK_H
#include <rz_types.h>
#include <rz_list.h>
#include <rz_skiplist.h>
#include <rz_util/ht_sp.h>
#include <rz_util/rz_serialize.h>
#include <sdb.h>
#ifdef __cplusplus
extern "C" {
#endif
// NOTE: Marks are currently used for the Hex Widget only
// but the API is kept generic in case of future reuse
RZ_LIB_VERSION_HEADER(rz_mark);
#define RZ_MARK_NAME_SIZE 512
typedef struct rz_mark_item_t {
ut64 from; ///< inclusive starting address of mark
ut64 to; ///< inclusive ending address of mark
char *name; ///< unique name for each mark, escaped to avoid issues with rizin shell
char *realname; ///< real name, without any escaping
char *comment; ///< item comment
char *color; ///< item color
} RzMarkItem;
typedef struct rz_marks_at_offset_t {
ut64 off;
RzList /*<RzMarkItem *>*/ *marks;
} RzMarksAtOffset;
typedef struct rz_mark_t {
HtSP *ht_name; ///< name -> RzBookmarkItem*
RzList /*<RzBookmarkItem *>*/ *items; ///< All bookmarks
RzSkipList *by_off; ///< offset -> RzBookmarksAtOffset*
} RzMark;
typedef bool (*RzMarkItemCb)(RzMarkItem *item, void *user);
typedef enum {
RZ_MARK_MATCH_CONTAINS,
RZ_MARK_MATCH_START,
RZ_MARK_MATCH_END
} RzMarkMatchMode;
#ifdef RZ_API
RZ_API RZ_OWN RzMark *rz_mark_new(void);
RZ_API void rz_mark_free(RZ_OWN RzMark *b);
RZ_API RZ_OWN RzMarkItem *rz_mark_set(RZ_NONNULL RzMark *b, RZ_NONNULL const char *name, ut64 from, ut64 to);
RZ_API RZ_BORROW RzMarkItem *rz_mark_get(RZ_NONNULL RzMark *b, RZ_NONNULL const char *name);
RZ_API RZ_BORROW RzMarkItem *rz_mark_get_start(RZ_NONNULL RzMark *b, ut64 off);
RZ_API RZ_BORROW RzMarkItem *rz_mark_get_end(RZ_NONNULL RzMark *b, ut64 off);
RZ_API RZ_BORROW RzMarkItem *rz_mark_get_at(RZ_NONNULL RzMark *b, ut64 off);
RZ_API RZ_OWN RzList /*<RzMarkItem *>*/ *rz_mark_get_all_off(RZ_NONNULL RzMark *b, ut64 off);
RZ_API RZ_OWN RzList /*<RzMarkItem *>*/ *rz_mark_all_list(RZ_NONNULL RzMark *b);
RZ_API RZ_BORROW const RzList /*<RzMarkItem *>*/ *rz_mark_get_list(RZ_NONNULL RzMark *b, ut64 off);
RZ_API void rz_mark_item_free(RZ_OWN RzMarkItem *item);
RZ_API void rz_mark_item_set_comment(RZ_NONNULL RzMarkItem *item, RZ_NULLABLE const char *comment);
RZ_API void rz_mark_item_set_realname(RZ_NONNULL RzMarkItem *item, RZ_NONNULL const char *realname);
RZ_API RZ_NULLABLE const char *rz_mark_item_set_color(RZ_NONNULL RzMarkItem *item, RZ_NONNULL const char *color);
RZ_API int rz_mark_rename(RZ_NONNULL RzMark *b, RZ_NONNULL RzMarkItem *item, RZ_NONNULL const char *name);
RZ_API bool rz_mark_starts_or_ends(RZ_NONNULL RzMark *b, ut64 from, ut64 to);
RZ_API bool rz_mark_unset(RZ_NONNULL RzMark *b, RZ_NONNULL RzMarkItem *item);
RZ_API bool rz_mark_unset_all_off(RZ_NONNULL RzMark *b, ut64 off);
RZ_API void rz_mark_unset_all(RZ_NONNULL RzMark *b);
RZ_API int rz_mark_unset_glob(RZ_NONNULL RzMark *b, RZ_NONNULL const char *glob);
RZ_API int rz_mark_count(RZ_NONNULL RzMark *b, RZ_NONNULL const char *glob);
RZ_API void rz_mark_foreach(RZ_NONNULL RzMark *b, RZ_NONNULL RzMarkItemCb cb, RZ_NULLABLE void *user);
RZ_API void rz_mark_foreach_glob(RZ_NONNULL RzMark *b, RZ_NONNULL const char *glob, RZ_NONNULL RzMarkItemCb cb, RZ_NULLABLE void *user);
/* serialize */
RZ_API void rz_serialize_mark_save(RZ_NONNULL Sdb *db, RZ_NONNULL RzMark *bm);
RZ_API bool rz_serialize_mark_load(RZ_NONNULL Sdb *db, RZ_NONNULL RzMark *bm, RZ_NULLABLE RzSerializeResultInfo *res);
#endif // RZ_API
#ifdef __cplusplus
}
#endif
#endif // RZ_MARK_H

View file

@ -12,7 +12,7 @@
extern "C" {
#endif
#define RZ_PROJECT_VERSION 19
#define RZ_PROJECT_VERSION 20
typedef Sdb RzProject;

602
librz/mark/mark.c Normal file
View file

@ -0,0 +1,602 @@
// SPDX-FileCopyrightText: 2025 PremadeS <emadsohail001@gmail.com>
// SPDX-License-Identifier: LGPL-3.0-only
#include <rz_types_base.h>
#include <rz_util/rz_assert.h>
#include <rz_mark.h>
#include <rz_util.h>
#include <rz_cons.h>
RZ_LIB_VERSION(rz_mark);
#define STRDUP_OR_NULL(s) (!RZ_STR_ISEMPTY(s) ? strdup(s) : NULL)
static void mark_skiplist_free(void *data) {
RzMarksAtOffset *item = (RzMarksAtOffset *)data;
rz_list_free(item->marks);
free(data);
}
static int mark_skiplist_cmp(const void *va, const void *vb, void *user) {
const RzMarksAtOffset *a = (RzMarksAtOffset *)va, *b = (RzMarksAtOffset *)vb;
if (a->off == b->off) {
return 0;
}
return a->off < b->off ? -1 : 1;
}
static void free_item_name(RzMarkItem *item) {
if (item->name != item->realname) {
free(item->name);
}
}
static void free_item_realname(RzMarkItem *item) {
if (item->name != item->realname) {
free(item->realname);
}
}
static void set_name(RzMarkItem *item, char *name) {
free_item_name(item);
item->name = name;
free_item_realname(item);
item->realname = item->name;
}
/* return the list of marks at the nearest position.
dir == -1 -> result <= off
dir == 0 -> result == off
dir == 1 -> result >= off*/
static RzMarksAtOffset *rz_mark_get_nearest_list(RzMark *b, ut64 off, int dir) {
RzMarksAtOffset key = { .off = off };
RzMarksAtOffset *marks = (dir >= 0)
? rz_skiplist_get_geq(b->by_off, &key)
: rz_skiplist_get_leq(b->by_off, &key);
return (dir == 0 && marks && marks->off != off) ? NULL : marks;
}
RZ_API RZ_OWN RzMark *rz_mark_new(void) {
RzMark *b = RZ_NEW0(RzMark);
if (!b) {
return NULL;
}
b->ht_name = ht_sp_new(HT_STR_DUP, NULL, (HtSPFreeValue)rz_mark_item_free);
b->by_off = rz_skiplist_new(mark_skiplist_free, mark_skiplist_cmp);
return b;
}
static char *filter_item_name(const char *name) {
char *res = rz_str_dup(name);
if (!res) {
return NULL;
}
rz_str_trim(res);
rz_name_filter(res, 0, true);
return res;
}
static void remove_offsetmap(RzMark *b, RzMarkItem *item) {
rz_return_if_fail(b && item);
RzMarksAtOffset *marks = rz_mark_get_nearest_list(b, item->from, 0);
if (marks) {
rz_list_delete_data(marks->marks, item);
if (rz_list_empty(marks->marks)) {
rz_skiplist_delete(b->by_off, marks);
}
}
}
static RzMarksAtOffset *marks_at_offset(RzMark *b, ut64 off) {
RzMarksAtOffset *res = rz_mark_get_nearest_list(b, off, 0);
if (res) {
return res;
}
res = RZ_NEW(RzMarksAtOffset);
if (!res) {
return NULL;
}
res->marks = rz_list_new();
if (!res->marks) {
free(res);
return NULL;
}
res->off = off;
rz_skiplist_insert(b->by_off, res);
return res;
}
static bool update_mark_item_range(RzMark *b, RzMarkItem *item, ut64 new_from, ut64 new_to,
bool is_new, bool force) {
rz_return_val_if_fail(b && item, false);
if (item->from != new_from || item->to != new_to || force) {
if (!is_new) {
remove_offsetmap(b, item);
}
item->from = new_from;
item->to = new_to;
RzMarksAtOffset *marksAtOffset = marks_at_offset(b, new_from);
if (!marksAtOffset) {
return false;
}
rz_list_append(marksAtOffset->marks, item);
return true;
}
return false;
}
static bool update_mark_item_name(RzMark *b, RzMarkItem *item, const char *newname, bool force) {
if (!b || !item || !newname) {
return false;
}
if (!force && (item->name == newname || (RZ_STR_EQ(item->name, newname)))) {
return false;
}
char *fname = filter_item_name(newname);
if (!fname) {
return false;
}
bool res = (item->name)
? ht_sp_update_key(b->ht_name, item->name, fname)
: ht_sp_insert(b->ht_name, fname, item);
if (res) {
set_name(item, fname);
return true;
}
free(fname);
return false;
}
static bool mark_item_matches(RzMarkItem *bi, ut64 off, RzMarkMatchMode mode) {
switch (mode) {
case RZ_MARK_MATCH_CONTAINS:
return RZ_BETWEEN(bi->from, off, bi->to);
case RZ_MARK_MATCH_START:
return (off == bi->from);
case RZ_MARK_MATCH_END:
return (off == bi->to);
}
return false;
}
static void mark_iterate_matches(RzMark *b, ut64 off, RzMarkMatchMode mode,
bool stop_on_first, RzList /*<RzMarkItem *>*/ *out) {
RzMarksAtOffset *cur = rz_mark_get_nearest_list(b, off, -1);
while (cur) {
RzListIter *it;
RzMarkItem *bi;
rz_list_foreach (cur->marks, it, bi) {
if (!mark_item_matches(bi, off, mode)) {
continue;
}
rz_list_append(out, bi);
if (stop_on_first) {
return;
}
}
if (cur->off == 0) {
break;
}
cur = rz_mark_get_nearest_list(b, cur->off - 1, -1);
}
}
static RzMarkItem *mark_match(RzMark *b, ut64 off, RzMarkMatchMode mode) {
rz_return_val_if_fail(b, NULL);
RzList *results = rz_list_newf(NULL);
mark_iterate_matches(b, off, mode, true, results);
RzMarkItem *ret = results->length ? rz_list_get_n(results, 0) : NULL;
rz_list_free(results);
return ret;
}
static RzList /*<RzMarkItem *>*/ *mark_match_all(RzMark *b, ut64 off, RzMarkMatchMode mode) {
rz_return_val_if_fail(b, NULL);
RzList *results = rz_list_newf(NULL);
mark_iterate_matches(b, off, mode, false, results);
return results;
}
static bool mark_starts_or_ends(RzMark *b, ut64 from, ut64 to) {
if (!b || from > to) {
return false;
}
RzList *all = rz_mark_all_list(b);
if (!all) {
return false;
}
RzListIter *it;
RzMarkItem *bi;
rz_list_foreach (all, it, bi) {
if (RZ_BETWEEN(from, bi->from, to) ||
RZ_BETWEEN(from, bi->to, to)) {
rz_list_free(all);
return true;
}
}
rz_list_free(all);
return false;
}
static bool append_bi(RzMarkItem *item, void *user) {
rz_list_append((RzList *)user, item);
return true;
}
/**
* \brief Get all defined marks as a list.
*
* \param b The mark container.
* \return A newly allocated list of all mark items (\p RzMarkItem*).
*/
RZ_API RZ_OWN RzList /*<RzMarkItem *>*/ *rz_mark_all_list(RZ_NONNULL RzMark *b) {
RzList *ret = rz_list_new();
if (!b || !ret) {
return ret;
}
rz_mark_foreach(b, append_bi, ret);
return ret;
}
/**
* \brief Check whether any mark starts or ends in the given range.
*
* \param b The mark container.
* \param from Start of the range.
* \param to End of the range.
* \return True if a mark starts or ends in the given range, False otherwise.
*/
RZ_API bool rz_mark_starts_or_ends(RZ_NONNULL RzMark *b, ut64 from, ut64 to) {
return mark_starts_or_ends(b, from, to);
}
/**
* \brief Get all mark items that contain a given offset.
*
* \param b The mark container.
* \param off The offset to query.
* \return A list of all matching mark items. NULL if none are found.
*/
RZ_API RZ_OWN RzList /*<RzMarkItem *>*/ *rz_mark_get_all_off(RZ_NONNULL RzMark *b, ut64 off) {
return mark_match_all(b, off, RZ_MARK_MATCH_CONTAINS);
}
/**
* \brief Get the last mark item that contains the given offset.
*
* \param b The mark container.
* \param off The offset to query.
* \return The matching mark item. NULL if none is found.
*/
RZ_API RZ_BORROW RzMarkItem *rz_mark_get_at(RZ_NONNULL RzMark *b, ut64 off) {
return mark_match(b, off, RZ_MARK_MATCH_CONTAINS);
}
/**
* \brief Get the mark item that starts at a given offset.
*
* \param b The mark container.
* \param off The offset to query.
* \return The mark item starting at \p off. NULL if none is found.
*/
RZ_API RZ_BORROW RzMarkItem *rz_mark_get_start(RZ_NONNULL RzMark *b, ut64 off) {
return mark_match(b, off, RZ_MARK_MATCH_START);
}
/**
* \brief Get the mark item that ends at a given offset.
*
* \param b The mark container.
* \param off The offset to query.
* \return The mark item ending at \p off. NULL if none is found.
*/
RZ_API RZ_BORROW RzMarkItem *rz_mark_get_end(RZ_NONNULL RzMark *b, ut64 off) {
return mark_match(b, off, RZ_MARK_MATCH_END);
}
/**
* \brief Get the mark item with the given name.
*
* \param b The mark container.
* \param name The name of the mark to retrieve.
* \return The matching mark item. NULL if not found.
*/
RZ_API RZ_BORROW RzMarkItem *rz_mark_get(RZ_NONNULL RzMark *b, RZ_NONNULL const char *name) {
rz_return_val_if_fail(b, NULL);
RzMarkItem *r = ht_sp_find(b->ht_name, name, NULL);
return r;
}
/**
* \brief Get all mark items containing with a given offset.
*
* \param b The mark container.
* \param off The offset to query.
* \return A list of mark items. NULL if none are found.
*/
RZ_API RZ_BORROW const RzList /*<RzMarkItem *>*/ *rz_mark_get_list(RZ_NONNULL RzMark *b, ut64 off) {
const RzMarksAtOffset *item = rz_mark_get_nearest_list(b, off, 0);
return item ? item->marks : NULL;
}
/**
* \brief Create or update a mark item.
*
* If a mark with the given \p name already exists, it is updated.
* Otherwise, a new one is created.
*
* \param b The mark container.
* \param name The name of the mark.
* \param from Start offset.
* \param to End offset.
* \return The created or updated mark item. NULL on error.
*/
RZ_API RZ_OWN RzMarkItem *rz_mark_set(RZ_NONNULL RzMark *b, RZ_NONNULL const char *name, ut64 from, ut64 to) {
rz_return_val_if_fail(b && name && *name, NULL);
bool is_new = false;
char *itemname = filter_item_name(name);
if (!itemname) {
return NULL;
}
RzMarkItem *item = rz_mark_get(b, itemname);
free(itemname);
if (item && item->from == from && item->to == to) {
return item;
}
if (!item) {
item = RZ_NEW0(RzMarkItem);
if (!item) {
goto err;
}
is_new = true;
}
update_mark_item_range(b, item, from, to, is_new, true);
update_mark_item_name(b, item, name, true);
return item;
err:
rz_mark_item_free(item);
return NULL;
}
/**
* \brief Free a mark container and all its associated data.
*
* \param b The mark container to free.
*/
RZ_API void rz_mark_free(RZ_OWN RzMark *b) {
rz_warn_if_fail(b);
rz_skiplist_free(b->by_off);
ht_sp_free(b->ht_name);
free(b);
}
/**
* \brief Free a mark item.
*
* \param item The mark item to free.
*/
RZ_API void rz_mark_item_free(RZ_OWN RzMarkItem *item) {
if (!item) {
return;
}
free(item->color);
free(item->comment);
free_item_name(item);
free(item->realname);
free(item);
}
#define FOREACH_BODY(condition) \
RzSkipListNode *it, *tmp; \
RzMarksAtOffset *marks_at; \
RzListIter *it2, *tmp2; \
RzMarkItem *bi; \
rz_skiplist_foreach_safe(b->by_off, it, tmp, marks_at) { \
if (marks_at) { \
rz_list_foreach_safe (marks_at->marks, it2, tmp2, bi) { \
if (condition) { \
if (!cb(bi, user)) { \
return; \
} \
} \
} \
} \
}
/**
* \brief Iterate through all mark items.
*
* \param b The mark container.
* \param cb Callback function to invoke for each mark item.
* \param user User-provided data passed to the callback.
*/
RZ_API void rz_mark_foreach(RZ_NONNULL RzMark *b, RZ_NONNULL RzMarkItemCb cb, RZ_NULLABLE void *user) {
FOREACH_BODY(true);
}
/**
* \brief Iterate through all mark items matching a glob pattern.
*
* \param b The mark container.
* \param glob Glob/Regex pattern to filter mark names.
* \param cb Callback function to invoke for each matching mark item.
* \param user User-provided data passed to the callback.
*/
RZ_API void rz_mark_foreach_glob(RZ_NONNULL RzMark *b, RZ_NONNULL const char *glob, RZ_NONNULL RzMarkItemCb cb, RZ_NULLABLE void *user) {
FOREACH_BODY(!glob || rz_str_glob(bi->name, glob));
}
struct unset_off_foreach_t {
RzMark *b;
ut64 offset;
};
/**
* \brief Unset (remove) a specific mark item.
*
* The item is freed.
*
* \param b The mark container.
* \param item The mark item to unset.
* \return True if successfully unset. False otherwise.
*/
RZ_API bool rz_mark_unset(RZ_NONNULL RzMark *b, RZ_NONNULL RzMarkItem *item) {
rz_return_val_if_fail(b && item, false);
remove_offsetmap(b, item);
ht_sp_delete(b->ht_name, item->name);
return true;
}
static bool unset_off_foreach(void *user, const char *k, const void *v) {
struct unset_off_foreach_t *u = (struct unset_off_foreach_t *)user;
RzMarkItem *bi = (RzMarkItem *)v;
// check if any marked range contains the current offset
if (RZ_BETWEEN(bi->from, u->offset, bi->to)) {
rz_mark_unset(u->b, bi);
}
return true;
}
/**
* \brief Unset all marks found at a specific offset.
*
* \param b The mark container.
* \param off The offset to clear marks from.
* \return True if at least one mark was unset. False otherwise.
*/
RZ_API bool rz_mark_unset_all_off(RZ_NONNULL RzMark *b, ut64 off) {
rz_return_val_if_fail(b, false);
struct unset_off_foreach_t u = { b, off };
ht_sp_foreach(b->ht_name, unset_off_foreach, &u);
return true;
}
struct unset_foreach_t {
RzMark *b;
int n;
};
static bool unset_foreach(RzMarkItem *bi, void *user) {
struct unset_foreach_t *u = (struct unset_foreach_t *)user;
rz_mark_unset(u->b, bi);
u->n++;
return true;
}
/**
* \brief Unset all marks matching a glob pattern.
*
* \param b The mark container.
* \param glob Glob/Regex pattern to filter mark names.
* \return The number of marks unset. -1 on error.
*/
RZ_API int rz_mark_unset_glob(RZ_NONNULL RzMark *b, RZ_NONNULL const char *glob) {
rz_return_val_if_fail(b, -1);
struct unset_foreach_t u = { .b = b, .n = 0 };
rz_mark_foreach_glob(b, glob, unset_foreach, &u);
return u.n;
}
/**
* \brief Unset all marks in the container.
*
* \param b The mark container.
*/
RZ_API void rz_mark_unset_all(RZ_NONNULL RzMark *b) {
rz_return_if_fail(b);
ht_sp_free(b->ht_name);
b->ht_name = ht_sp_new(HT_STR_DUP, NULL, (HtSPFreeValue)rz_mark_item_free);
rz_skiplist_purge(b->by_off);
}
/**
* \brief Set or remove the color of a mark item.
*
* \param item The mark item.
* \param color The new color string, or NULL to remove.
* \return The updated color string. NULL if unset.
*/
RZ_API RZ_NULLABLE const char *rz_mark_item_set_color(RZ_NONNULL RzMarkItem *item, RZ_NONNULL const char *color) {
rz_return_val_if_fail(item, NULL);
free(item->color);
item->color = STRDUP_OR_NULL(color);
return item->color;
}
/**
* \brief Set or remove the comment of a mark item.
*
* \param item The mark item.
* \param comment The new comment string.
*/
RZ_API void rz_mark_item_set_comment(RZ_NONNULL RzMarkItem *item, RZ_NULLABLE const char *comment) {
rz_return_if_fail(item);
free(item->comment);
item->comment = RZ_STR_ISEMPTY(comment) ? NULL : rz_str_dup(comment);
}
/**
* \brief Rename a mark item.
*
* \param b The mark container.
* \param item The mark item to rename.
* \param name The new name.
* \return True if renamed successfully. False otherwise.
*/
RZ_API int rz_mark_rename(RZ_NONNULL RzMark *b, RZ_NONNULL RzMarkItem *item, RZ_NONNULL const char *name) {
rz_return_val_if_fail(b && item && name && *name, false);
return update_mark_item_name(b, item, name, false);
}
/**
* \brief Set or remove the real name of a mark item.
*
* \param item The mark item.
* \param realname The new real name.
*/
RZ_API void rz_mark_item_set_realname(RZ_NONNULL RzMarkItem *item, RZ_NONNULL const char *realname) {
rz_return_if_fail(item);
free_item_realname(item);
item->realname = RZ_STR_ISEMPTY(realname) ? NULL : rz_str_dup(realname);
}
static bool mark_count_foreach(RzMarkItem *fi, void *user) {
int *count = (int *)user;
(*count)++;
return true;
}
/**
* \brief Count mark items matching a glob pattern.
*
* \param b The mark container.
* \param glob Glob/Regex pattern to filter mark names.
* \return The number of matching marks. -1 on error.
*/
RZ_API int rz_mark_count(RZ_NONNULL RzMark *b, RZ_NONNULL const char *glob) {
int count = 0;
rz_return_val_if_fail(b, -1);
rz_mark_foreach_glob(b, glob, mark_count_foreach, &count);
return count;
}

30
librz/mark/meson.build Normal file
View file

@ -0,0 +1,30 @@
mark_sources = [
'mark.c',
'serialize_mark.c'
]
rz_mark = library('rz_mark', mark_sources,
include_directories: [platform_inc],
dependencies: [
rz_util_dep
],
install: true,
implicit_include_directories: false,
install_rpath: rpath_lib,
soversion: rizin_libversion,
version: rizin_version,
name_suffix: lib_name_suffix,
name_prefix: lib_name_prefix,
)
rz_mark_dep = declare_dependency(
link_with: rz_mark,
include_directories: [platform_inc]
)
meson.override_dependency('rz_mark', rz_mark_dep)
modules += { 'rz_mark': {
'target': rz_mark,
'dependencies': ['rz_util']
}}

186
librz/mark/serialize_mark.c Normal file
View file

@ -0,0 +1,186 @@
// SPDX-FileCopyrightText: 2025 PremadeS <emadsohail001@gmail.com>
// SPDX-License-Identifier: LGPL-3.0-only
#include <rz_util/rz_assert.h>
#include <rz_types.h>
#include <rz_util.h>
#include <rz_mark.h>
/*
* SDB Format:
*
* /marks
* <mark name>={"realname":<str>,"from":<uint>,"to":<uint>,"color":<str>,"comment":<str>,}
*/
static bool mark_save_cb(RzMarkItem *bm, void *user) {
Sdb *db = user;
PJ *j = pj_new();
if (!j) {
return false;
}
pj_o(j);
if (bm->realname) {
pj_ks(j, "realname", bm->realname);
}
pj_kn(j, "from", bm->from);
pj_kn(j, "to", bm->to);
if (bm->color) {
pj_ks(j, "color", bm->color);
}
if (bm->comment) {
pj_ks(j, "comment", bm->comment);
}
pj_end(j);
sdb_set(db, bm->name, pj_string(j));
pj_free(j);
return true;
}
/**
* \brief Save marks to a database.
*
* Serializes all marks from the given container \p bm into the namespace
* "marks" within the database \p db.
*
* \param db Database to save into.
* \param bm Mark container to serialize.
*/
RZ_API void rz_serialize_mark_save(RZ_NONNULL Sdb *db, RZ_NONNULL RzMark *bm) {
rz_return_if_fail(db && bm);
rz_mark_foreach(bm, mark_save_cb, sdb_ns(db, "marks", true));
}
typedef enum {
BOOKMARK_FIELD_REALNAME,
BOOKMARK_FIELD_FROM,
BOOKMARK_FIELD_TO,
BOOKMARK_FIELD_COLOR,
BOOKMARK_FIELD_COMMENT,
} MarkField;
typedef struct {
RzMark *mark;
RzKeyParser *parser;
} MarkLoadCtx;
static bool marks_load_cb(void *user, const SdbKv *kv) {
MarkLoadCtx *ctx = user;
char *json_str = sdbkv_dup_value(kv);
if (!json_str) {
return true;
}
RzJson *json = rz_json_parse(json_str);
if (!json || json->type != RZ_JSON_OBJECT) {
free(json_str);
return false;
}
RzMarkItem proto = { 0 };
bool offset_set = false;
bool size_set = false;
RZ_KEY_PARSER_JSON(ctx->parser, json, child, {
case BOOKMARK_FIELD_REALNAME:
if (child->type != RZ_JSON_STRING) {
break;
}
proto.realname = (char *)child->str_value;
break;
case BOOKMARK_FIELD_FROM:
if (child->type != RZ_JSON_INTEGER) {
break;
}
proto.from = child->num.u_value;
offset_set = true;
break;
case BOOKMARK_FIELD_TO:
if (child->type != RZ_JSON_INTEGER) {
break;
}
proto.to = child->num.u_value;
size_set = true;
break;
case BOOKMARK_FIELD_COLOR:
if (child->type != RZ_JSON_STRING) {
break;
}
proto.color = (char *)child->str_value;
break;
case BOOKMARK_FIELD_COMMENT:
if (child->type != RZ_JSON_STRING) {
break;
}
proto.comment = (char *)child->str_value;
break;
default:
break;
});
bool res = true;
if (!offset_set || !size_set) {
res = false;
goto beach;
}
RzMarkItem *item = rz_mark_set(ctx->mark, sdbkv_key(kv), proto.from, proto.to);
if (proto.realname) {
rz_mark_item_set_realname(item, proto.realname);
}
if (proto.color) {
rz_mark_item_set_color(item, proto.color);
}
if (proto.comment) {
rz_mark_item_set_comment(item, proto.comment);
}
beach:
rz_json_free(json);
free(json_str);
return res;
}
static bool load_marks(RZ_NONNULL Sdb *marks_db, RZ_NONNULL RzMark *bm) {
MarkLoadCtx ctx = { bm, rz_key_parser_new() };
if (!ctx.parser) {
return false;
}
rz_key_parser_add(ctx.parser, "realname", BOOKMARK_FIELD_REALNAME);
rz_key_parser_add(ctx.parser, "from", BOOKMARK_FIELD_FROM);
rz_key_parser_add(ctx.parser, "to", BOOKMARK_FIELD_TO);
rz_key_parser_add(ctx.parser, "color", BOOKMARK_FIELD_COLOR);
rz_key_parser_add(ctx.parser, "comment", BOOKMARK_FIELD_COMMENT);
bool r = sdb_foreach(marks_db, marks_load_cb, &ctx);
rz_key_parser_free(ctx.parser);
return r;
}
/**
* \brief Load marks from a database.
*
* Clears all existing marks in \p bm and loads marks from the namespace
* "marks" within the database \p db.
*
* \param db Database to load from.
* \param bm Mark container to populate.
* \param res Optional result info structure for reporting errors, may be NULL.
* \return True if loading succeeded. False if an error occurred.
*
* \note If no "marks" namespace exists, this is treated as an empty set
* of marks (not an error).
*/
RZ_API bool rz_serialize_mark_load(RZ_NONNULL Sdb *db, RZ_NONNULL RzMark *bm, RZ_NULLABLE RzSerializeResultInfo *res) {
rz_return_val_if_fail(db && bm, false);
rz_mark_unset_all(bm);
Sdb *marks_db = sdb_ns(db, "marks", false);
if (!marks_db) {
// Old project, no marks saved -> treat as empty, not an error
return true;
}
if (!load_marks(marks_db, bm)) {
RZ_SERIALIZE_ERR(res, "failed to parse mark json");
return false;
}
return true;
}

View file

@ -16,6 +16,7 @@ subdir('syscall')
subdir('magic')
subdir('search')
subdir('flag')
subdir('mark')
subdir('reg')
subdir('type')
subdir('bin')

View file

@ -751,6 +751,7 @@ if host_machine.system() == 'windows'
build_root / 'librz' / 'debug',
build_root / 'librz' / 'egg',
build_root / 'librz' / 'flag',
build_root / 'librz' / 'mark',
build_root / 'librz' / 'hash',
build_root / 'librz' / 'io',
build_root / 'librz' / 'lang',

255
test/db/cmd/cmd_mark Normal file
View file

@ -0,0 +1,255 @@
NAME=m command with space
FILE==
CMDS=<<EOF
m foobar 0x2000 @ 0x1000
ml
EOF
EXPECT=<<EOF
[0x00001000 - 0x00002000] foobar
EOF
RUN
NAME=no marks
FILE==
CMDS=m
EXPECT=
RUN
NAME=mr
FILE=malloc://1024
CMDS=<<EOF
m foo 0x20
mr foo bar
ml
EOF
EXPECT=<<EOF
[0x00000000 - 0x00000020] bar
EOF
RUN
NAME=mm
FILE=malloc://1024
CMDS=<<EOF
m foo 0x20
mm foo 0x10 0x30
ml
EOF
EXPECT=<<EOF
[0x00000010 - 0x00000030] foo
EOF
RUN
NAME=mlj
FILE=malloc://1024
CMDS=<<EOF
m foo 0x0000000c @ 0x0000000b
m bar 0x00000003 @ 0x00000002
mlj
EOF
EXPECT=<<EOF
[{"from":2,"to":3,"name":"bar","realname":"bar","size":1},{"from":11,"to":12,"name":"foo","realname":"foo","size":1}]
EOF
RUN
NAME=md
FILE=malloc://1024
CMDS=<<EOF
m a 0x20
md a @ 0x100
EOF
EXPECT=<<EOF
[0x00000000 - 0x00000020] a + 224
EOF
RUN
NAME=mC
FILE=malloc://1024
CMDS=<<EOF
m a 0x20
mC a foo
mC a
EOF
EXPECT=<<EOF
foo
EOF
RUN
NAME=m-*
FILE=malloc://1024
CMDS=<<EOF
m a 0x20
m-*
ml
EOF
EXPECT=
RUN
NAME=Set/get
FILE==
CMDS=<<EOF
m south 0x20
m+ alpine 0x30
m rain 800 @ 666
m+ air 900 @ 666
m+ es 20 @ 12
ml
EOF
EXPECT=<<EOF
[0x00000000 - 0x00000020] south
[0x00000000 - 0x00000030] alpine
[0x0000000c - 0x00000014] es
[0x0000029a - 0x00000320] rain
[0x0000029a - 0x00000384] air
EOF
RUN
NAME=marks (JSON)
FILE==
CMDS=<<EOF
# Add marks.
m south 0x20
m+ alpine 0x32
m rain 932 @ 666
m+ air 032 @ 666
m+ es 100 @ 12
mlj
EOF
EXPECT=<<EOF
[{"from":0,"to":32,"name":"south","realname":"south","size":32},{"from":0,"to":50,"name":"alpine","realname":"alpine","size":50},{"from":12,"to":100,"name":"es","realname":"es","size":88},{"from":666,"to":932,"name":"rain","realname":"rain","size":266},{"from":666,"to":26,"name":"air","realname":"air","size":0}]
EOF
RUN
NAME=m and m+ behavior
FILE==
CMDS=<<EOF
m foo 10
m bar 20
m cow 10
m low 20
ml~?
m-*
m+ foo 10
m+ bar 20
m+ cow 10
m+ low 20
ml~?
m-*
m foo 15 @ 10
m bar 25 @ 20
m cow 30 @ 10
m low 40 @ 20
ml~?
m-*
m+ foo 15 @ 10
m+ bar 25 @ 20
m+ cow 30 @ 10
m+ low 40 @ 20
ml~?
EOF
EXPECT=<<EOF
1
4
2
4
EOF
RUN
NAME=mark renaming and reusing the old name for something new
FILE=malloc://1024
CMDS=<<EOF
m foo 0x150 @ 0x100
mr foo bar
m foo 0x250 @ 0x200
ml
EOF
EXPECT=<<EOF
[0x00000100 - 0x00000150] bar
[0x00000200 - 0x00000250] foo
EOF
RUN
NAME=base64 armored marks
FILE=malloc://1024
CMDS=<<EOF
m direct1 1 verbatim @ 0
m direct2 21 base64:ImJhc2U2NCBhcm1vcmVkIg== @ 16
m indirect 40 @ 32
mC indirect base64:d29ya3MgdG9v
ml*
mlj
EOF
EXPECT=<<EOF
[0x00000000 - 0x00000001] direct1 1 base64:dmVyYmF0aW0=
[0x00000010 - 0x00000015] direct2 5 base64:ImJhc2U2NCBhcm1vcmVkIg==
[0x00000020 - 0x00000028] indirect 8 base64:d29ya3MgdG9v
[{"from":0,"to":1,"name":"direct1","realname":"direct1","size":1,"comment":"verbatim"},{"from":16,"to":21,"name":"direct2","realname":"direct2","size":5,"comment":"\"base64 armored\""},{"from":32,"to":40,"name":"indirect","realname":"indirect","size":8,"comment":"works too"}]
EOF
RUN
NAME=m base64:NotB64=100
FILE=malloc://1024
CMDS=<<EOF
m base64:NotB64 200 @ 100
ml
EOF
EXPECT=<<EOF
[0x00000064 - 0x000000c8] base64:NotB64
EOF
RUN
NAME=mark list at
FILE==
CMDS=<<EOF
m foo 0x0000000c @ 0x0000000b
m bar 0x00000003 @ 0x00000002
ml. @ 0x0000000b
ml.j @ 0x0000000b
ml.t @ 0x0000000b
ml.q @ 0x0000000b
ml. @ 0x00000002
ml.j @ 0x00000002
ml.t @ 0x00000002
ml.q @ 0x00000002
EOF
EXPECT=<<EOF
[0x0000000b - 0x0000000c] foo
[{"from":11,"to":12,"name":"foo","realname":"foo","size":1}]
from to name realname
------------------------------------
0x0000000b 0x0000000c foo foo
foo
[0x00000002 - 0x00000003] bar
[{"from":2,"to":3,"name":"bar","realname":"bar","size":1}]
from to name realname
------------------------------------
0x00000002 0x00000003 bar bar
bar
EOF
RUN
NAME=pxc shows the start and end of mark with name and range
FILE=malloc://1024
CMDS=<<EOF
m foo 0x20
pxc
EOF
EXPECT=<<EOF
- offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF comment
0x00000000 0000 0000 0000 0000 0000 0000 0000 0000 ................ ; [s] foo [0x0-0x20]
0x00000010 0000 0000 0000 0000 0000 0000 0000 0000 ................
0x00000020 0000 0000 0000 0000 0000 0000 0000 0000 ................ ; [e] foo [0x0-0x20]
0x00000030 0000 0000 0000 0000 0000 0000 0000 0000 ................
0x00000040 0000 0000 0000 0000 0000 0000 0000 0000 ................
0x00000050 0000 0000 0000 0000 0000 0000 0000 0000 ................
0x00000060 0000 0000 0000 0000 0000 0000 0000 0000 ................
0x00000070 0000 0000 0000 0000 0000 0000 0000 0000 ................
0x00000080 0000 0000 0000 0000 0000 0000 0000 0000 ................
0x00000090 0000 0000 0000 0000 0000 0000 0000 0000 ................
0x000000a0 0000 0000 0000 0000 0000 0000 0000 0000 ................
0x000000b0 0000 0000 0000 0000 0000 0000 0000 0000 ................
0x000000c0 0000 0000 0000 0000 0000 0000 0000 0000 ................
0x000000d0 0000 0000 0000 0000 0000 0000 0000 0000 ................
0x000000e0 0000 0000 0000 0000 0000 0000 0000 0000 ................
0x000000f0 0000 0000 0000 0000 0000 0000 0000 0000 ................
EOF
RUN

View file

@ -377,6 +377,7 @@ Detailed project load info:
project migrated from version 16 to 17.
project migrated from version 17 to 18.
project migrated from version 18 to 19.
project migrated from version 19 to 20.
EOF
RUN

View file

@ -33,6 +33,7 @@ if get_option('enable_tests')
'bin_mach0',
'bitvector',
'bits',
'marks',
'buf',
'cmd',
'compare',
@ -94,6 +95,7 @@ if get_option('enable_tests')
'sdb_sdb',
'sdb_util',
'serialize_analysis',
'serialize_mark',
'serialize_config',
'serialize_debug',
'serialize_flag',
@ -152,6 +154,7 @@ if get_option('enable_tests')
rz_crypto_dep,
rz_magic_dep,
rz_il_dep,
rz_mark_dep,
lrt,
],
install: false,

149
test/unit/test_marks.c Normal file
View file

@ -0,0 +1,149 @@
// SPDX-FileCopyrightText: 2025 PremadeS <emadsohail001@gmail.com>
// SPDX-License-Identifier: LGPL-3.0-only
#include <rz_mark.h>
#include "minunit.h"
bool test_rz_mark_get_at() {
RzMark *mark = rz_mark_new();
RzMarkItem *foo = rz_mark_set(mark, "foo", 1024, 2048);
RzMarkItem *bi;
bi = rz_mark_get_start(mark, 1024);
mu_assert_ptreq(bi, foo, "mark start at exact");
bi = rz_mark_get_start(mark, 1023);
mu_assert_null(bi, "no mark start at -1");
bi = rz_mark_get_start(mark, 1025);
mu_assert_null(bi, "no mark start at +1");
bi = rz_mark_get_end(mark, 2048);
mu_assert_ptreq(bi, foo, "mark end at exact");
bi = rz_mark_get_start(mark, 2047);
mu_assert_null(bi, "no mark end at -1");
bi = rz_mark_get_start(mark, 2049);
mu_assert_null(bi, "no mark end at +1");
rz_mark_free(mark);
mu_end;
}
bool test_rz_mark_get_all_off() {
RzMark *mark = rz_mark_new();
RzMarkItem *a = rz_mark_set(mark, "a", 0x100, 0x120);
RzMarkItem *b = rz_mark_set(mark, "b", 0x110, 0x130);
RzList *list = rz_mark_get_all_off(mark, 0x110);
mu_assert_eq(rz_list_length(list), 2, "2 marks overlap 0x110");
RzMarkItem *item;
RzListIter *it;
int found = 0;
rz_list_foreach (list, it, item) {
if (item == a || item == b)
found++;
}
mu_assert_eq(found, 2, "both overlapping items found");
rz_mark_free(mark);
mu_end;
}
bool test_rz_mark_all_list() {
RzMark *mark = rz_mark_new();
rz_mark_set(mark, "a", 0x100, 0x120);
rz_mark_set(mark, "b", 0x110, 0x130);
rz_mark_set(mark, "c", 0x120, 0x140);
RzList *all = rz_mark_all_list(mark);
mu_assert_eq(rz_list_length(all), 3, "all marks listed");
rz_mark_free(mark);
mu_end;
}
bool test_rz_mark_unset() {
RzMark *mark = rz_mark_new();
RzMarkItem *a = rz_mark_set(mark, "a", 0, 10);
rz_mark_set(mark, "b", 5, 15);
bool res = rz_mark_unset(mark, a);
mu_assert_true(res, "unset one mark");
mu_assert_null(rz_mark_get(mark, "a"), "a no longer exists");
bool res2 = rz_mark_unset_all_off(mark, 6);
mu_assert_true(res2, "unset all at offset");
mu_assert_null(rz_mark_get(mark, "b"), "b no longer exists");
rz_mark_free(mark);
mu_end;
}
bool test_rz_mark_rename() {
RzMark *mark = rz_mark_new();
RzMarkItem *item = rz_mark_set(mark, "foo", 100, 200);
mu_assert_notnull(item, "mark added");
rz_mark_rename(mark, item, "bar");
mu_assert_null(rz_mark_get(mark, "foo"), "old name no longer exists");
RzMarkItem *renamed = rz_mark_get(mark, "bar");
mu_assert_notnull(renamed, "new name exists");
mu_assert_streq(renamed->name, "bar", "renamed mark name matches");
mu_assert_true(item == renamed, "renamed item is same object");
rz_mark_free(mark);
mu_end;
}
bool test_rz_mark_set_properties() {
RzMark *mark = rz_mark_new();
RzMarkItem *item = rz_mark_set(mark, "foo", 100, 200);
mu_assert_notnull(item, "mark added");
rz_mark_item_set_comment(item, "test comment");
mu_assert_true(item->comment, "set comment works");
mu_assert_streq(item->comment, "test comment", "comment matches");
rz_mark_item_set_color(item, "0xFF00FF");
mu_assert_true(item->color, "set color works");
mu_assert_streq(item->color, "0xFF00FF", "color matches");
rz_mark_item_set_realname(item, "real_foo");
mu_assert_true(item->realname, "set realname works");
mu_assert_streq(item->realname, "real_foo", "realname matches");
rz_mark_free(mark);
mu_end;
}
bool test_rz_mark_count_and_starts_or_ends() {
RzMark *mark = rz_mark_new();
rz_mark_set(mark, "a", 0, 10);
rz_mark_set(mark, "b", 5, 15);
int c = rz_mark_count(mark, "*");
mu_assert_eq(c, 2, "2 marks counted");
bool so = rz_mark_starts_or_ends(mark, 0, 10);
mu_assert_true(so, "a starts/ends at 0/10");
rz_mark_free(mark);
mu_end;
}
int all_tests(void) {
mu_run_test(test_rz_mark_get_at);
mu_run_test(test_rz_mark_get_all_off);
mu_run_test(test_rz_mark_all_list);
mu_run_test(test_rz_mark_unset);
mu_run_test(test_rz_mark_set_properties);
mu_run_test(test_rz_mark_rename);
mu_run_test(test_rz_mark_count_and_starts_or_ends);
return tests_passed != tests_run;
}
mu_main(all_tests)

View file

@ -0,0 +1,111 @@
// SPDX-FileCopyrightText: 2025 PremadeS <emadsohail001@gmail.com>
// SPDX-License-Identifier: LGPL-3.0-only
#include <rz_mark.h>
#include <rz_flag.h>
#include "minunit.h"
#include "test_sdb.h"
Sdb *ref_0_db() {
Sdb *db = sdb_new0();
Sdb *marks_db = sdb_ns(db, "marks", true);
sdb_set(marks_db, "foobars", "{\"realname\":\"Foobars\",\"from\":4000,\"to\":5000,\"color\":\"white\",\"comment\":\"windowpane\"}");
sdb_set(marks_db, "f00b4r5", "{\"realname\":\"f00b4r5\",\"from\":5000,\"to\":6213}");
sdb_set(marks_db, "deliverance", "{\"realname\":\"deliverance\",\"from\":1403,\"to\":1500}");
return db;
}
RzMark *ref_0_mark() {
RzMark *mark = rz_mark_new();
rz_mark_set(mark, "deliverance", 0x578 + 3, 1500);
rz_mark_set(mark, "f00b4r5", 5000, 6213);
RzMarkItem *foobars = rz_mark_set(mark, "foobars", 4000, 5000);
rz_mark_item_set_realname(foobars, "Foobars");
rz_mark_item_set_color(foobars, "white");
rz_mark_item_set_comment(foobars, "windowpane");
return mark;
}
RzMark *ref_1_mark() {
RzMark *mark = rz_mark_new();
return mark;
}
static bool test_save(RzMark *mark, Sdb *ref) {
Sdb *db = sdb_new0();
rz_serialize_mark_save(db, mark);
assert_sdb_eq(db, ref, "save");
sdb_free(db);
sdb_free(ref);
rz_mark_free(mark);
return true;
}
typedef struct {
bool equal;
RzMark *other;
} markCmpCtx;
static bool mark_cmp(RzMarkItem *actual, RzMarkItem *expected) {
mu_assert_notnull(expected, "mark");
assert_streq_null(actual->realname, expected->realname, "mark realname");
mu_assert_eq_fmt(actual->from, expected->from, "mark from", "0x%" PFMT64x);
mu_assert_eq_fmt(actual->to, expected->to, "mark to", "0x%" PFMT64x);
assert_streq_null(actual->color, expected->color, "mark color");
assert_streq_null(actual->comment, expected->comment, "mark comment");
return true;
}
static bool mark_cmp_cb(RzMarkItem *fi, void *user) {
markCmpCtx *ctx = user;
RzMarkItem *fo = rz_mark_get(ctx->other, fi->name);
if (!mark_cmp(fi, fo)) {
ctx->equal = false;
return false;
}
return true;
}
static bool test_load(Sdb *db, RzMark *ref) {
RzMark *mark = rz_mark_new();
bool loaded = rz_serialize_mark_load(db, mark, NULL);
sdb_free(db);
mu_assert("load success", loaded);
size_t actual_count = rz_mark_count(mark, NULL);
size_t expected_count = rz_mark_count(ref, NULL);
mu_assert_eq(actual_count, expected_count, "marks count");
markCmpCtx cmp_ctx = { true, ref };
rz_mark_foreach(mark, mark_cmp_cb, &cmp_ctx);
mu_assert("marks equal", cmp_ctx.equal);
rz_mark_free(mark);
rz_mark_free(ref);
return true;
}
#define TEST_CALL(name, call) \
bool name() { \
if (!(call)) { \
return false; \
} \
mu_end; \
}
TEST_CALL(test_mark_0_save, test_save(ref_0_mark(), ref_0_db()));
TEST_CALL(test_mark_0_load, test_load(ref_0_db(), ref_0_mark()));
int all_tests() {
mu_run_test(test_mark_0_save);
mu_run_test(test_mark_0_load);
return tests_passed != tests_run;
}
mu_main(all_tests)