rizin/librz/reg/profile.c
NOT XVilka 155ead6822
librz/reg: derive CC with more than four argument registers (#6600)
rz_reg_profile_to_cc() only emitted the first four argument registers
(A0-A3), so architectures that pass more arguments in registers -- the
C6000 EABI uses ten, and x86-64/riscv/ppc all declare more than four --
got a truncated convention. Walk the whole A0-A9 role range, stopping at
the first role the profile leaves undefined, and build the cc string with
RzStrBuf. Covered by a new test_reg unit test.

Co-authored-by agent: Claude/claude-opus-4-8

Co-authored-by: Anton Kochkov <anton.kochkov@gmail.com>
2026-07-06 03:35:41 +08:00

691 lines
19 KiB
C

// SPDX-FileCopyrightText: 2009-2020 pancake <pancake@nopcode.org>
// SPDX-License-Identifier: LGPL-3.0-only
#include <rz_list.h>
#include <rz_util/rz_log.h>
#include <rz_util/rz_str.h>
#include <rz_reg.h>
#include <rz_util.h>
#include <rz_util/rz_assert.h>
#include <rz_lib.h>
#include <string.h>
#define GDB_NAME_SZ 16
#define GDB_TYPE_SZ 16
#define GDB_GROUPS_SZ 128
static void rz_reg_profile_def_free(RzRegProfileDef *def) {
if (!def) {
return;
}
free(def->name);
free(def->comment);
free(def->flags);
free(def);
}
static void rz_reg_profile_alias_free(RzRegProfileAlias *alias) {
if (!alias) {
return;
}
free(alias->reg_name);
free(alias->alias);
free(alias);
}
/**
* Like rz_reg_type_by_name, but warn if non-existent
*/
static int expect_reg_type_by_name(const char *str) {
int r = rz_reg_type_by_name(str);
if (r < 0) {
RZ_LOG_WARN("No register type for type abbreviation \"%s\".\n", str);
}
return r;
}
/**
* \brief Parses a register type string.
*
* The type string must be of the following form:
*
* <sub-type>@<main-type>
*
* "<sub-type>@" is optional.
*
* \param item Register item whichs types are set.
* \param type_str The type string.
* \return true On success.
* \return false On failure.
*/
static bool parse_type(RZ_OUT RzRegProfileDef *def, const char *type_str) {
rz_return_val_if_fail(def && type_str, false);
char *s = rz_str_dup(type_str);
char *at = strchr(s, '@');
if (at) {
// This register has a secondary type e.g. xmm@fpu
def->arena_type = expect_reg_type_by_name(at + 1);
s[at - s] = '\0';
def->type = expect_reg_type_by_name(s);
} else {
def->type = expect_reg_type_by_name(s);
def->arena_type = def->type;
if (def->type == RZ_REG_TYPE_FLG) { // Hack to put flgs into gpr arena.
def->arena_type = RZ_REG_TYPE_GPR;
} else {
def->arena_type = def->type;
}
}
bool res = true;
if (def->type < 0 || def->arena_type < 0) {
RZ_LOG_ERROR("Illegal type abbreviation \"%s\"\n", s);
res = false;
}
free(s);
return res;
}
/**
* \brief Parses the size of a register definition.
* Sizes with . in front are in bits. Otherwise in bytes.
*
* \param s Size string.
* \return ut32 The size as integer or UT64_MAX if it fails.
*/
static ut32 parse_size(char *s) {
rz_return_val_if_fail(s, UT32_MAX);
if (s[0] == '.') {
return strtoul(s + 1, NULL, 0);
} else {
return strtoul(s, NULL, 0) * 8;
}
}
/**
* \brief Parses the offset of a register defintion and sets the offset in \p def->offset.
*
* Offset is of the form: <byte>.<bit>
* .<bit> is optional.
*
* \param s Offset string.
* \param def The defintion item to store the offset into \p def->offset in bits.
* \return false On failure (sets def->offset = UT32_MAX).
* \return true On success.
*/
static bool parse_offset(const char *s, RZ_OUT RzRegProfileDef *def) {
rz_return_val_if_fail(s && def, false);
if (s[0] == '?') {
def->offset = UT32_MAX;
return true;
} else if (s[0] == '.') {
def->offset = strtoul(s + 1, NULL, 0);
return true;
}
def->offset = strtoul(s, NULL, 0) * 8;
const char *bi = strchr(s, '.');
if (!bi) {
// No bit offset given.
return true;
}
ut8 bit_offset = strtoul(bi + 1, NULL, 0);
def->offset += bit_offset;
return true;
}
/**
* \brief Parses a register alias.
*
* The alias is of the form:
* "=<alias> <reg name>"
*
* \param reg The RzReg struct with the register profile.
* \param tokens A list with both tokens of the alias string.
* \return true On success.
* \return false On Failure.
*/
static bool parse_alias(RZ_OUT RzList /*<RzRegProfileAlias *>*/ *alias_list, RZ_BORROW RzList /*<char *>*/ *tokens) {
rz_return_val_if_fail(alias_list && tokens, false);
RzRegProfileAlias *pa = RZ_NEW0(RzRegProfileAlias);
if (!pa) {
RZ_LOG_WARN("Unable to allocate memory.\n");
return false;
}
const char *real_name = rz_list_get_n(tokens, 1);
const char *alias = rz_list_get_n(tokens, 0);
if (!alias) {
RZ_LOG_WARN("Failed to get alias name from token.\n");
free(pa);
return false;
}
RzRegisterId role = rz_reg_get_name_idx(alias + 1);
if (!(role >= 0 && role < RZ_REG_NAME_LAST)) {
RZ_LOG_WARN("Invalid alias\n");
free(pa);
return false;
}
pa->alias = rz_str_dup(alias);
pa->reg_name = rz_str_dup(real_name);
pa->role = role;
rz_list_append(alias_list, pa);
return true;
}
/**
* \brief Parses a register definition.
*
* \param reg Register struct with the register profile.
* \param tokens List of strings of a single register definition.
* \return false On failure.
* \return true On success.
*/
static bool parse_def(RZ_OUT RzList /*<RzRegProfileDef *>*/ *def_list, RZ_BORROW RzList /*<char *>*/ *tokens) {
rz_return_val_if_fail(def_list && tokens, false);
RzRegProfileDef *def = RZ_NEW0(RzRegProfileDef);
if (!def) {
RZ_LOG_WARN("Unable to allocate memory.\n");
return false;
}
const char *name = rz_list_get_n(tokens, 1);
if (!name) {
goto reg_parse_error;
}
def->name = rz_str_dup(name);
if (!parse_type(def, rz_list_get_n(tokens, 0))) {
RZ_LOG_WARN("Invalid register type.\n");
goto reg_parse_error;
}
def->size = parse_size(rz_list_get_n(tokens, 2));
if (def->size == UT32_MAX || def->size == 0) {
RZ_LOG_WARN("Invalid register size.\n");
goto reg_parse_error;
}
def->packed = parse_size(rz_list_get_n(tokens, 4));
if (def->packed == UT32_MAX) {
RZ_LOG_WARN("Invalid register packed size.\n");
goto reg_parse_error;
}
if (!parse_offset(rz_list_get_n(tokens, 3), def)) {
RZ_LOG_WARN("Invalid register offset.\n");
goto reg_parse_error;
}
// Comments and flags are optional
if (rz_list_length(tokens) == 6) {
const char *comment_flag = rz_list_get_n(tokens, 5);
if (!comment_flag) {
goto reg_parse_error;
}
if (comment_flag[0] == '#') {
// Remove # from the comment
def->comment = rz_str_dup(comment_flag + 1);
} else {
def->flags = rz_str_dup(comment_flag);
}
}
RZ_LOG_DEBUG("profile: register def: %s %d %d %s\n", def->name, def->size, def->offset, def->flags);
rz_list_append(def_list, def);
return true;
reg_parse_error:
rz_reg_profile_def_free(def);
return false;
}
/**
* \brief Parses a register profile string. Each line is either a register alias or a register definiton.
*
* A register alias string is of the following form:
* "=<alias> <name>\n"
*
* A register definition string is of the following form:
* "(<sub-type>@)main-type <name> .<size> <byte offset>(.<bit offset>) <packed> (# <comment> OR <flags>)\n"
*
* Elements in "()" are optional.
* Each "<...>" token is separated by tab or space characters.
*
* * alias: Register alias (e.g. PC, A1 etc.)
* * name: Register name.
* * size: Register size in bits.
* * main-type: Register type: gpr, fpr, ctr, flg etc.
* * sub-type: The second register type (e.g. xmm@fpu : xmm is sub-type of fpu)
* * byte offset: Offset into register profile in bytes.
* * bit offset: Offset into the byte offset in bits.
* * packed: Packed size of the register in bytes.
* * comment: A comment about the register.
* * Flags this register holds.
*
* \param reg Register struct which holds all register items.
* \param profile Register profile string.
* \return false On failure.
* \return true On success.
*/
static bool parse_reg_profile_str(RZ_OUT RzList /*<RzRegProfileAlias *>*/ *alias_list, RZ_OUT RzList /*<RzRegProfileDef *>*/ *def_list, const char *profile_str) {
rz_return_val_if_fail(alias_list && def_list && profile_str, false);
RzList *def_lines = rz_str_split_duplist_n(profile_str, "\n", 0, true);
rz_return_val_if_fail(def_lines, false);
st32 l = 0; // Line number
const char *line;
bool is_alias = false;
RzListIter *it;
RzList *toks = NULL;
rz_list_foreach (def_lines, it, line) {
++l;
if (RZ_STR_ISEMPTY(line)) {
continue;
}
if (rz_str_strchr(line, "#")) {
RzList *line_and_cmt = rz_str_split_duplist_n_regex(line, "\\#", 0, true);
char *raw_comment = rz_str_dup(rz_list_last_val(line_and_cmt));
if (!raw_comment) {
RZ_LOG_WARN("Comment could not be split from register definition. Line: \"%s\"\n", line);
continue;
}
char *comment = rz_str_prepend(raw_comment, "#");
if (!comment) {
RZ_LOG_WARN("Could not prepend # to comment. Line: \"%s\".\n", line);
continue;
}
toks = rz_str_split_duplist_n_regex(rz_list_first_val(line_and_cmt), "\\s+", 0, true);
rz_list_append(toks, comment);
rz_list_free(line_and_cmt);
} else {
toks = rz_str_split_duplist_n_regex(line, "\\s+", 0, true);
}
ut32 toks_len = rz_list_length(toks);
if (rz_list_empty(toks)) {
goto free_toks_continue;
}
const char *first_tok = rz_list_get_n(toks, 0);
if (first_tok[0] == '#') { // Comment line
goto free_toks_continue;
} else if (first_tok[0] == '=') { // Alias
if (toks_len != 2) {
RZ_LOG_WARN("Invalid number of %d columns in alias \"%s\" at line %d. 2 needed.\n", toks_len, line, l);
goto free_toks_continue;
}
is_alias = true;
} else if (isalpha(first_tok[0])) {
if (toks_len != 5 && toks_len != 6) {
RZ_LOG_WARN("Invalid number of %d columns in definition \"%s\" at line %d. 5 or 6 needed.\n", toks_len, line, l);
goto free_toks_continue;
}
} else {
RZ_LOG_WARN("Invalid line \"%s\" at register profiles line %d.\n", line, l);
goto free_toks_continue;
}
bool success = is_alias
? parse_alias(alias_list, toks)
: parse_def(def_list, toks);
if (!success) {
RZ_LOG_WARN("Parsing error in \"%s\" at line %d.\n", line, l);
rz_list_free(toks);
rz_list_free(def_lines);
return false;
}
is_alias = false;
free_toks_continue:
rz_list_free(toks);
}
rz_list_free(def_lines);
return true;
}
static void add_item_to_regset(RZ_BORROW RzReg *reg, RZ_BORROW RzRegItem *item) {
rz_return_if_fail(reg && item);
RzRegisterType t = item->arena;
if (!reg->regset[t].regs) {
reg->regset[t].regs = rz_list_newf((RzListFree)rz_reg_item_free);
}
if (!reg->regset[t].ht_regs) {
reg->regset[t].ht_regs = ht_sp_new(HT_STR_DUP, NULL, NULL);
}
// Dynamically update the list of supported bit sizes
reg->bits |= item->size;
rz_list_append(reg->regset[t].regs, item);
ht_sp_insert(reg->regset[t].ht_regs, item->name, item);
// Update the overall type of registers into a regset
if (item->type == RZ_REG_TYPE_ANY) {
reg->regset[t].maskregstype = UT32_MAX;
return;
}
reg->regset[t].maskregstype |= ((int)1 << item->type);
}
/**
* \brief Fills \p reg->regset with the definitions and alias of the register profile.
*
* \param reg The RzReg struct which holds the register profile and an empty \p reg->regset
* \return false On failure.
* \return true On success.
*/
RZ_API bool rz_reg_set_reg_profile(RZ_BORROW RzReg *reg) {
rz_return_val_if_fail(reg, false);
rz_return_val_if_fail(reg->reg_profile.alias && reg->reg_profile.defs, false);
RzListIter *it;
RzRegProfileDef *def;
rz_list_foreach (reg->reg_profile.defs, it, def) {
RzRegItem *item = RZ_NEW0(RzRegItem);
if (!item) {
RZ_LOG_WARN("Unable to allocate memory.\n");
return false;
}
item->name = rz_str_dup(def->name);
item->type = def->type;
item->arena = def->arena_type;
item->size = def->size;
item->offset = def->offset;
// Update the overall profile size
if (item->offset + item->size > reg->size) {
reg->size = item->offset + item->size;
}
item->packed_size = def->packed;
if (def->comment) {
item->comment = rz_str_dup(def->comment);
}
if (def->flags) {
item->flags = rz_str_dup(def->flags);
}
add_item_to_regset(reg, item);
}
RzRegProfileAlias *alias;
rz_list_foreach (reg->reg_profile.alias, it, alias) {
RzRegItem *item = rz_reg_get(reg, alias->reg_name, RZ_REG_TYPE_ANY);
if (!item) {
RZ_LOG_WARN("Invalid alias given in register profile: %s.\n", alias->reg_name);
}
reg->by_role[alias->role] = item;
}
return true;
}
/**
* \brief Parses a register profile string and sets up all registers accordingly in \p reg.
*
* \param reg The RzReg struct which should hold the register data.
* \param profile The register profile string.
* \return false On failure;
* \return true On success.
*/
RZ_API bool rz_reg_set_profile_string(RZ_NONNULL RzReg *reg, RZ_NONNULL const char *profile_str) {
rz_return_val_if_fail(reg && profile_str, false);
// Same profile, no need to change
if (reg->reg_profile_str && !strcmp(reg->reg_profile_str, profile_str)) {
return true;
}
// we should reset all the arenas before setting the new reg profile
rz_reg_arena_pop(reg);
// Purge the old registers
rz_reg_free_internal(reg, true);
rz_reg_arena_shrink(reg);
// Cache the profile string
reg->reg_profile_str = rz_str_dup(profile_str);
reg->reg_profile.defs = rz_list_newf((RzListFree)rz_reg_profile_def_free);
reg->reg_profile.alias = rz_list_newf((RzListFree)rz_reg_profile_alias_free);
rz_return_val_if_fail(reg->reg_profile.defs && reg->reg_profile.alias, true);
if (!parse_reg_profile_str(reg->reg_profile.alias, reg->reg_profile.defs, profile_str)) {
RZ_LOG_WARN("Could not parse register profile string.\n");
rz_reg_free_internal(reg, false);
return false;
}
// Check for duplicates
RzListIter *it, *tmp;
RzRegProfileDef *def;
rz_list_foreach_safe (reg->reg_profile.defs, it, tmp, def) {
if (rz_reg_get(reg, def->name, RZ_REG_TYPE_ANY)) {
RZ_LOG_WARN("Ignoring duplicated register definition '%s'.\n", def->name);
rz_list_delete(reg->reg_profile.defs, it);
}
}
if (!rz_reg_set_reg_profile(reg)) {
RZ_LOG_WARN("Could not set reg profile.\n");
return false;
}
reg->size = 0;
for (ut32 i = 0; i < RZ_REG_TYPE_LAST; i++) {
RzRegSet *rs = &reg->regset[i];
if (rs && rs->arena) {
reg->size += rs->arena->size; // Sums minimum arena size.
}
}
rz_reg_fit_arena(reg);
// dup the last arena to allow regdiffing
rz_reg_arena_push(reg);
rz_reg_reindex(reg);
return true;
}
RZ_API bool rz_reg_set_profile(RzReg *reg, const char *profile) {
rz_return_val_if_fail(reg && profile, false);
char *base, *file;
char *str = rz_file_slurp(profile, NULL);
if (!str) {
base = rz_sys_getenv(RZ_LIB_ENV);
if (base) {
file = rz_str_append(base, profile);
str = rz_file_slurp(file, NULL);
free(file);
}
}
if (!str) {
eprintf("rz_reg_set_profile: Cannot find '%s'\n", profile);
return false;
}
bool ret = rz_reg_set_profile_string(reg, str);
free(str);
return ret;
}
static char *gdb_to_rz_profile(const char *gdb) {
rz_return_val_if_fail(gdb, NULL);
RzStrBuf *sb = rz_strbuf_new("");
if (!sb) {
return NULL;
}
char *copy = rz_str_dup(gdb);
char *ptr1, *gptr, *gptr1;
char name[GDB_NAME_SZ + 1], groups[GDB_GROUPS_SZ + 1], type[GDB_TYPE_SZ + 1];
const int all = 1, gpr = 2, save = 4, restore = 8, float_ = 16,
sse = 32, vector = 64, system = 128, mmx = 256;
int number, rel, offset, size, type_bits, ret;
// Every line is -
// Name Number Rel Offset Size Type Groups
char *ptr = (char *)rz_str_trim_head_ro(copy);
// It's possible someone includes the heading line too. Skip it
if (rz_str_startswith(ptr, "Name")) {
if (!(ptr = strchr(ptr, '\n'))) {
rz_strbuf_free(sb);
free(copy);
return NULL;
}
ptr++;
}
for (;;) {
// Skip whitespace at beginning of line and empty lines
while (isspace((ut8)*ptr)) {
ptr++;
}
if (!*ptr) {
break;
}
if ((ptr1 = strchr(ptr, '\n'))) {
*ptr1 = '\0';
} else {
eprintf("Could not parse line: %s (missing \\n)\n", ptr);
rz_strbuf_free(sb);
free(copy);
return false;
}
ret = sscanf(ptr, " %" RZ_STR_DEF(GDB_NAME_SZ) "s %d %d %d %d %" RZ_STR_DEF(GDB_TYPE_SZ) "s %" RZ_STR_DEF(GDB_GROUPS_SZ) "s",
name, &number, &rel, &offset, &size, type, groups);
// Groups is optional, others not
if (ret < 6) {
if (*ptr != '*') {
eprintf("Could not parse line: %s\n", ptr);
rz_strbuf_free(sb);
free(copy);
return NULL;
}
ptr = ptr1 + 1;
continue;
}
// If name is '', then skip
if (rz_str_startswith(name, "''")) {
if (!ptr1) {
break;
}
ptr = ptr1 + 1;
continue;
}
// If size is 0, skip
if (size == 0) {
if (!ptr1) {
break;
}
ptr = ptr1 + 1;
continue;
}
type_bits = 0;
// Parse group
if (ret >= 7) {
gptr = groups;
while (1) {
if ((gptr1 = strchr(gptr, ','))) {
*gptr1 = '\0';
}
if (rz_str_startswith(gptr, "general")) {
type_bits |= gpr;
} else if (rz_str_startswith(gptr, "all")) {
type_bits |= all;
} else if (rz_str_startswith(gptr, "save")) {
type_bits |= save;
} else if (rz_str_startswith(gptr, "restore")) {
type_bits |= restore;
} else if (rz_str_startswith(gptr, "float")) {
type_bits |= float_;
} else if (rz_str_startswith(gptr, "sse")) {
type_bits |= sse;
} else if (rz_str_startswith(gptr, "mmx")) {
type_bits |= mmx;
} else if (rz_str_startswith(gptr, "vector")) {
type_bits |= vector;
} else if (rz_str_startswith(gptr, "system")) {
type_bits |= system;
}
if (!gptr1) {
break;
}
gptr = gptr1 + 1;
}
}
// If type is not defined, skip
if (!*type) {
if (!ptr1) {
break;
}
ptr = ptr1 + 1;
continue;
}
// TODO: More mappings between gdb and rizin reg groups. For now, either fpu or gpr
if (!(type_bits & sse) && !(type_bits & float_)) {
type_bits |= gpr;
}
// Print line
rz_strbuf_appendf(sb, "%s\t%s\t.%d\t%d\t0\n",
// Ref: Comment above about more register type mappings
((type_bits & mmx) || (type_bits & float_) || (type_bits & sse)) ? "fpu" : "gpr",
name, size * 8, offset);
// Go to next line
if (!ptr1) {
break;
}
ptr = ptr1 + 1;
}
free(copy);
return rz_strbuf_drain(sb);
}
RZ_API char *rz_reg_parse_gdb_profile(const char *profile_file) {
char *str = NULL;
if (!(str = rz_file_slurp(profile_file, NULL))) {
char *base = rz_sys_getenv(RZ_LIB_ENV);
if (base) {
char *file = rz_str_appendf(base, RZ_SYS_DIR "%s", profile_file);
if (file) {
str = rz_file_slurp(file, NULL);
free(file);
}
}
}
if (str) {
char *ret = gdb_to_rz_profile(str);
free(str);
return ret;
}
eprintf("rz_reg_parse_gdb_profile: Cannot find '%s'\n", profile_file);
return NULL;
}
RZ_API char *rz_reg_profile_to_cc(RzReg *reg) {
const char *r0 = rz_reg_get_name_by_type(reg, "R0");
const char *a0 = rz_reg_get_name_by_type(reg, "A0");
if (!a0) {
RZ_LOG_WARN("It is mandatory to have at least one argument register defined in the register profile.\n");
return NULL;
}
if (!r0) {
r0 = a0;
}
// Gather every consecutive argument register the profile declares (A0..A9),
// stopping at the first undefined role, so an arch that passes more than four
// arguments in registers gets a complete convention rather than a truncated
// one.
static const char *arg_roles[] = { "A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9" };
RzStrBuf sb;
rz_strbuf_init(&sb);
rz_strbuf_appendf(&sb, "%s reg(%s", r0, a0);
for (size_t i = 0; i < RZ_ARRAY_SIZE(arg_roles); i++) {
const char *an = rz_reg_get_name_by_type(reg, arg_roles[i]);
if (!an) {
break;
}
rz_strbuf_appendf(&sb, ", %s", an);
}
rz_strbuf_append(&sb, ")");
return rz_strbuf_drain_nofree(&sb);
}