radare2/libr/anal/xrefs.c

700 lines
18 KiB
C

/* radare - LGPL - Copyright 2009-2024 - pancake, nibble, defragger, ret2libc */
// R2R db/cmd/cmd_aflxj db/cmd/cmd_aflxv db/cmd/cmd_ax
#include <r_anal.h>
#include <r_cons.h>
#include <r_vec.h>
#include <sdb/cwisstable.h>
R_VEC_TYPE (RVecAnalRef, RAnalRef);
// xrefs are stored as an adjacency list (in both directions),
// as a hastable mapping at (from) to hashtables mapping addr (at) to a ref type.
CWISS_DECLARE_FLAT_HASHMAP_DEFAULT(Edges, ut64, RAnalRefType);
// TODO store Edges directly in other hashmap, but how to hash & compare the hashmap itself?
CWISS_DECLARE_FLAT_HASHMAP_DEFAULT(AdjacencyList, ut64, Edges*);
#define INITIAL_CAPACITY 0
#define R_HM_FOREACH(type, hm, entry) \
type##_CIter iter; \
for (iter = type##_citer ((hm)); (entry = type##_CIter_get (&iter)) != NULL; type##_CIter_next (&iter))
#define R_ADJACENCY_LIST_FOREACH(adj_list, entry) R_HM_FOREACH(AdjacencyList, adj_list, entry)
#define R_EDGES_FOREACH(edges, entry) R_HM_FOREACH(Edges, edges, entry)
// NOTE: this is heavy in memory usage, but needed due to performance reasons for large amounts of xrefs..
typedef struct r_ref_manager_t {
AdjacencyList refs; // forward refs
AdjacencyList xrefs; // backward refs
} RefManager;
static inline int compare_ref(const RAnalRef *a, const RAnalRef *b) {
if (a->at < b->at) {
return -1;
}
if (a->at > b->at) {
return 1;
}
if (a->addr < b->addr) {
return -1;
}
if (a->addr > b->addr) {
return 1;
}
return 0;
}
static RefManager *ref_manager_new(void) {
RefManager *rm = R_NEW0 (RefManager);
if (R_LIKELY (rm)) {
rm->refs = AdjacencyList_new (INITIAL_CAPACITY);
rm->xrefs = AdjacencyList_new (INITIAL_CAPACITY);
}
return rm;
}
static inline void adjacency_list_fini(AdjacencyList *adj_list) {
const AdjacencyList_Entry *entry;
R_ADJACENCY_LIST_FOREACH (adj_list, entry) {
Edges *edges = entry->val;
Edges_destroy (edges);
free (edges);
}
AdjacencyList_destroy (adj_list);
}
static void ref_manager_free(RefManager *rm) {
if (R_LIKELY (rm)) {
adjacency_list_fini (&rm->refs);
adjacency_list_fini (&rm->xrefs);
}
free (rm);
}
static void _add_ref(AdjacencyList *adj_list, ut64 from, ut64 to, RAnalRefType type) {
AdjacencyList_Iter iter = AdjacencyList_find (adj_list, &from);
AdjacencyList_Entry *entry = AdjacencyList_Iter_get (&iter);
Edges *edges = entry ? entry->val : NULL;
if (!edges) {
// optionally add a hashtable if missing
edges = R_NEW0 (Edges);
if (!edges) {
R_LOG_WARN ("failed to allocate hashtable for xrefs");
return;
}
*edges = Edges_new (INITIAL_CAPACITY);
AdjacencyList_Entry new_entry = { .key = from, .val = edges };
AdjacencyList_insert (adj_list, &new_entry); // adds the new (empty) hashtable
}
Edges_Entry edge_entry = { .key = to, .val = type };
Edges_Insert result = Edges_insert (edges, &edge_entry); // and adds the ref
if (!result.inserted) {
Edges_Entry *existing_entry = Edges_Iter_get (&result.iter);
existing_entry->val = type;
}
}
static void ref_manager_add_entry(RefManager *rm, ut64 from, ut64 to, RAnalRefType type) {
_add_ref (&rm->refs, from, to, type);
_add_ref (&rm->xrefs, to, from, type);
}
static void _delete_ref(AdjacencyList *adj_list, ut64 from, ut64 to) {
AdjacencyList_Iter iter = AdjacencyList_find (adj_list, &from);
AdjacencyList_Entry *entry = AdjacencyList_Iter_get (&iter);
Edges *edges = entry ? entry->val : NULL;
if (edges) {
if (Edges_size (edges) == 1) {
AdjacencyList_erase_at (iter); // delete rest of hashtable
} else {
Edges_erase (edges, &to); // delete only a reference
}
}
}
// TODO add extra R_API call for deleting all refs, can be implemented in a more performant way
static void ref_manager_remove_entry(RefManager *rm, ut64 from, ut64 to) {
_delete_ref (&rm->refs, from, to);
_delete_ref (&rm->xrefs, to, from);
}
static ut64 ref_manager_count_xrefs(RefManager *rm) {
r_return_val_if_fail (rm, 0);
ut64 count = 0;
const AdjacencyList_Entry *entry;
R_ADJACENCY_LIST_FOREACH (&rm->xrefs, entry) {
count += Edges_size (entry->val);
}
return count;
}
static ut64 ref_manager_count_xrefs_at(RefManager *rm, ut64 to) {
r_return_val_if_fail (rm, 0);
AdjacencyList_CIter iter = AdjacencyList_cfind (&rm->xrefs, &to);
const AdjacencyList_Entry *entry = AdjacencyList_CIter_get (&iter);
const Edges *edges = entry? entry->val: NULL;
return edges? Edges_size (edges): 0;
}
static RVecAnalRef *_collect_all_refs(RefManager *rm, const AdjacencyList *adj_list) {
RVecAnalRef *result = RVecAnalRef_new ();
if (R_UNLIKELY (!result)) {
return NULL;
}
const ut64 length = ref_manager_count_xrefs (rm);
if (!RVecAnalRef_reserve (result, length)) {
RVecAnalRef_free (result);
return NULL;
}
const AdjacencyList_Entry *entry;
R_ADJACENCY_LIST_FOREACH (adj_list, entry) {
const Edges_Entry *edge_entry;
R_EDGES_FOREACH (entry->val, edge_entry) {
RAnalRef *ref = RVecAnalRef_emplace_back (result);
if (R_UNLIKELY (!ref)) {
RVecAnalRef_free (result);
return false;
}
ref->at = entry->key;
ref->addr = edge_entry->key;
ref->type = edge_entry->val;
}
}
return result;
}
static RVecAnalRef *_collect_refs_from(const AdjacencyList *adj_list, ut64 from) {
// only finds entries with matching "from"
RVecAnalRef *result = RVecAnalRef_new ();
if (R_UNLIKELY (!result)) {
return NULL;
}
const Edges *edges = NULL;
{
AdjacencyList_CIter iter = AdjacencyList_cfind (adj_list, &from);
const AdjacencyList_Entry *entry = AdjacencyList_CIter_get (&iter);
edges = entry ? entry->val : NULL;
}
if (!edges) {
RVecAnalRef_free (result);
return NULL;
}
ut64 ref_count = Edges_size (edges);
if (!RVecAnalRef_reserve (result, ref_count)) {
RVecAnalRef_free (result);
return NULL;
}
const Edges_Entry *entry;
R_EDGES_FOREACH(edges, entry) {
RAnalRef *ref = RVecAnalRef_emplace_back (result);
if (R_UNLIKELY (!ref)) {
RVecAnalRef_free (result);
return NULL;
}
ref->at = from;
ref->addr = entry->key;
ref->type = entry->val;
}
return result;
}
static RVecAnalRef *_collect_refs(RefManager *rm, const AdjacencyList *adj_list, ut64 addr) {
return addr == UT64_MAX
? _collect_all_refs (rm, adj_list)
: _collect_refs_from (adj_list, addr);
}
static inline RVecAnalRef *ref_manager_get_refs(RefManager *rm, ut64 from) {
r_return_val_if_fail (rm, NULL);
return _collect_refs (rm, &rm->refs, from);
}
static inline RVecAnalRef *ref_manager_get_xrefs(RefManager *rm, ut64 to) {
r_return_val_if_fail (rm, NULL);
return _collect_refs (rm, &rm->xrefs, to);
}
R_API bool r_anal_xrefs_init(RAnal *anal) {
r_return_val_if_fail (anal, false);
r_anal_xrefs_free (anal);
anal->rm = ref_manager_new ();
return !!anal->rm;
}
R_API void r_anal_xrefs_free(RAnal *anal) {
r_return_if_fail (anal);
ref_manager_free (anal->rm);
}
// set a reference from FROM to TO and a cross-reference(xref) from TO to FROM.
R_API bool r_anal_xrefs_set(RAnal *anal, ut64 from, ut64 to, const RAnalRefType _type) {
r_return_val_if_fail (anal && anal->rm, false);
if (from == to || from == UT64_MAX || to == UT64_MAX) {
return false;
}
if (anal->iob.is_valid_offset) {
if (!anal->iob.is_valid_offset (anal->iob.io, from, 0)) {
return false;
}
if (!anal->iob.is_valid_offset (anal->iob.io, to, 0)) {
return false;
}
}
RAnalRefType type = _type;
if (!R_ANAL_REF_TYPE_PERM (type)) {
// type |= R_ANAL_REF_TYPE_READ;
switch (R_ANAL_REF_TYPE_MASK (type)) {
case R_ANAL_REF_TYPE_CODE:
case R_ANAL_REF_TYPE_CALL:
case R_ANAL_REF_TYPE_JUMP:
type |= R_ANAL_REF_TYPE_EXEC;
break;
default:
type |= R_ANAL_REF_TYPE_READ;
break;
}
}
ref_manager_add_entry (anal->rm, from, to, type);
R_DIRTY (anal);
return true;
}
R_API bool r_anal_xref_del(RAnal *anal, ut64 from, ut64 to) {
r_return_val_if_fail (anal, false);
ref_manager_remove_entry (anal->rm, from, to);
R_DIRTY (anal);
return true;
}
R_API RVecAnalRef *r_anal_refs_get(RAnal *anal, ut64 from) {
r_return_val_if_fail (anal && anal->rm, NULL);
RVecAnalRef *anal_refs = ref_manager_get_refs (anal->rm, from);
if (!anal_refs || RVecAnalRef_empty (anal_refs)) {
RVecAnalRef_free (anal_refs);
return NULL;
}
RVecAnalRef_sort (anal_refs, compare_ref); // XXX not needed?
return anal_refs;
}
R_API RVecAnalRef *r_anal_xrefs_get(RAnal *anal, ut64 to) {
r_return_val_if_fail (anal && anal->rm, NULL);
RVecAnalRef *anal_refs = ref_manager_get_xrefs (anal->rm, to);
if (!anal_refs || RVecAnalRef_empty (anal_refs)) {
RVecAnalRef_free (anal_refs);
return NULL;
}
RVecAnalRef_sort (anal_refs, compare_ref); // XXX not needed?
return anal_refs;
}
R_API RVecAnalRef *r_anal_xrefs_get_from(RAnal *anal, ut64 to) {
r_return_val_if_fail (anal && anal->rm, NULL);
RVecAnalRef *anal_refs = ref_manager_get_refs (anal->rm, to);
if (!anal_refs || RVecAnalRef_empty (anal_refs)) {
RVecAnalRef_free (anal_refs);
return NULL;
}
RVecAnalRef_sort (anal_refs, compare_ref); // XXX not needed?
return anal_refs;
}
R_API bool r_anal_xrefs_has_xrefs_at(RAnal *anal, ut64 at) {
r_return_val_if_fail (anal && anal->rm, NULL);
AdjacencyList_CIter iter = AdjacencyList_cfind (&anal->rm->xrefs, &at);
const AdjacencyList_Entry *entry = AdjacencyList_CIter_get (&iter);
return !!entry;
}
static void r_anal_xrefs_list_table(RAnal *anal, RVecAnalRef *anal_refs, const char *arg) {
RTable *table = r_table_new ("xrefs");
r_table_set_columnsf (table, "ddssss", "from", "to", "type", "perm", "fromname", "toname");
RAnalRef *ref;
R_VEC_FOREACH (anal_refs, ref) {
int t = R_ANAL_REF_TYPE_MASK (ref->type);
if (!t) {
t = ' ';
}
char *fromname = anal->coreb.getNameDelta (anal->coreb.core, ref->addr);
char *toname = anal->coreb.getNameDelta (anal->coreb.core, ref->at);
r_table_add_rowf (table, "xxnssss",
ref->at, ref->addr,
r_anal_ref_size (ref),
r_anal_ref_type_tostring (t),
r_anal_ref_perm_tostring (ref),
toname, fromname
);
free (fromname);
free (toname);
}
bool show_table = true;
if (R_STR_ISNOTEMPTY (arg)) {
show_table = r_table_query (table, arg);
}
if (show_table) {
char *s = r_table_tofancystring (table);
r_cons_println (s);
free (s);
}
r_table_free (table);
}
static void r_anal_xrefs_list_json(RAnal *anal, RVecAnalRef *anal_refs) {
PJ *pj = anal->coreb.pjWithEncoding (anal->coreb.core);
if (!pj) {
return;
}
pj_a (pj);
RAnalRef *ref;
R_VEC_FOREACH (anal_refs, ref) {
int t = R_ANAL_REF_TYPE_MASK (ref->type);
if (!t) {
t = ' ';
}
pj_o (pj);
char *name = anal->coreb.getNameDelta (anal->coreb.core, ref->at);
if (name) {
r_str_replace_ch (name, ' ', 0, true);
pj_ks (pj, "name", name);
free (name);
}
pj_kn (pj, "from", ref->at);
pj_ks (pj, "type", r_anal_ref_type_tostring (t));
pj_ks (pj, "perm", r_anal_ref_perm_tostring (ref));
pj_kn (pj, "addr", ref->addr);
name = anal->coreb.getNameDelta (anal->coreb.core, ref->addr);
if (name) {
r_str_replace_ch (name, ' ', 0, true);
pj_ks (pj, "refname", name);
free (name);
}
pj_end (pj);
}
pj_end (pj);
anal->cb_printf ("%s\n", pj_string (pj));
pj_free (pj);
}
static void r_anal_xrefs_list_hex(RAnal *anal, RVecAnalRef *anal_refs) {
RAnalRef *ref;
R_VEC_FOREACH (anal_refs, ref) {
int t = R_ANAL_REF_TYPE_MASK (ref->type);
if (!t) {
t = ' ';
}
// TODO: export/import the read-write-exec information
anal->cb_printf ("ax%c 0x%"PFMT64x" 0x%"PFMT64x"\n", t, ref->addr, ref->at);
}
}
static void r_anal_xrefs_list_mapping(RAnal *anal, RVecAnalRef *anal_refs) {
RAnalRef *ref;
R_VEC_FOREACH (anal_refs, ref) {
RAnalRefType t = R_ANAL_REF_TYPE_MASK (ref->type);
if (!t) {
t = ' ';
}
anal->cb_printf ("0x%08"PFMT64x" -> 0x%08"PFMT64x" %s:%s\n", ref->at, ref->addr,
r_anal_ref_type_tostring (t), r_anal_ref_perm_tostring (ref));
}
}
static void r_anal_xrefs_list_plaintext(RAnal *anal, RVecAnalRef *anal_refs) {
RAnalRef *ref;
R_VEC_FOREACH (anal_refs, ref) {
int t = R_ANAL_REF_TYPE_MASK (ref->type);
if (!t) {
t = ' ';
}
char *name = anal->coreb.getNameDelta (anal->coreb.core, ref->at);
if (name) {
r_str_replace_ch (name, ' ', 0, true);
anal->cb_printf ("%40s", name);
free (name);
} else {
anal->cb_printf ("%40s", "?");
}
anal->cb_printf (" 0x%"PFMT64x" > %4s:%s > 0x%"PFMT64x, ref->at,
r_anal_ref_type_tostring (t), r_anal_ref_perm_tostring (ref), ref->addr);
name = anal->coreb.getNameDelta (anal->coreb.core, ref->addr);
if (name) {
r_str_replace_ch (name, ' ', 0, true);
anal->cb_printf (" %s\n", name);
free (name);
} else {
anal->cb_printf ("\n");
}
}
}
R_API void r_anal_xrefs_list(RAnal *anal, int rad, const char *arg) {
r_return_if_fail (anal && anal->rm);
RVecAnalRef *anal_refs = ref_manager_get_refs (anal->rm, UT64_MAX);
if (!anal_refs) {
R_LOG_DEBUG ("Could not list xrefs");
return;
}
RVecAnalRef_sort (anal_refs, compare_ref); // XXX not needed?
switch (rad) {
case ',':
r_anal_xrefs_list_table (anal, anal_refs, arg);
break;
case 'j':
r_anal_xrefs_list_json (anal, anal_refs);
break;
case '*':
r_anal_xrefs_list_hex (anal, anal_refs);
break;
case 'q':
r_anal_xrefs_list_mapping (anal, anal_refs);
break;
case '\0':
r_anal_xrefs_list_plaintext (anal, anal_refs);
default:
R_LOG_DEBUG ("Unsupported xrefs list format: %c", rad);
break;
}
RVecAnalRef_free (anal_refs);
}
R_API ut64 r_anal_xrefs_count(RAnal *anal) {
r_return_val_if_fail (anal && anal->rm, 0);
return ref_manager_count_xrefs (anal->rm);
}
R_API ut64 r_anal_xrefs_count_at(RAnal *anal, ut64 to) {
r_return_val_if_fail (anal && anal->rm, 0);
return ref_manager_count_xrefs_at (anal->rm, to);
}
R_API RVecAnalRef *r_anal_function_get_xrefs(RAnalFunction *fcn) {
r_return_val_if_fail (fcn, NULL);
RefManager *rm = fcn->anal->rm;
// XXX assume first basic block is the entrypoint
RVecAnalRef *anal_refs = ref_manager_get_xrefs (rm, fcn->addr);
if (anal_refs) {
RVecAnalRef_sort (anal_refs, compare_ref); // XXX not needed?
}
return anal_refs;
}
typedef RVecAnalRef *(*CollectFn)(RefManager *rm, ut64 addr);
static RVecAnalRef *fcn_get_all_refs(RAnalFunction *fcn, RefManager *rm, CollectFn collect_refs) {
RVecAnalRef *anal_refs = RVecAnalRef_new ();
if (R_LIKELY (anal_refs)) {
RListIter *iter;
RAnalBlock *bb;
r_list_foreach (fcn->bbs, iter, bb) {
int i;
for (i = 0; i < bb->ninstr; i++) {
ut64 instr_addr = bb->addr + r_anal_bb_offset_inst (bb, i);
RVecAnalRef *refs = collect_refs (rm, instr_addr);
if (!refs) {
continue;
}
RVecAnalRef_append (anal_refs, refs, NULL);
RVecAnalRef_free (refs);
}
}
RVecAnalRef_sort (anal_refs, compare_ref);
}
return anal_refs;
}
// XXX rename to r_anal_function_get_all_refs?
R_API RVecAnalRef *r_anal_function_get_refs(RAnalFunction *fcn) {
r_return_val_if_fail (fcn, NULL);
return fcn_get_all_refs (fcn, fcn->anal->rm, ref_manager_get_refs);
}
R_API RVecAnalRef *r_anal_function_get_all_xrefs(RAnalFunction *fcn) {
r_return_val_if_fail (fcn, NULL);
return fcn_get_all_refs (fcn, fcn->anal->rm, ref_manager_get_xrefs);
}
R_API char r_anal_ref_perm_tochar(RAnalRef *ref) {
if (ref->type & R_ANAL_REF_TYPE_WRITE) {
return 'w';
}
if (ref->type & R_ANAL_REF_TYPE_READ) {
return 'r';
}
if (ref->type & R_ANAL_REF_TYPE_EXEC) {
return 'x';
}
switch (R_ANAL_REF_TYPE_MASK (ref->type)) {
case R_ANAL_REF_TYPE_CODE:
case R_ANAL_REF_TYPE_CALL:
case R_ANAL_REF_TYPE_JUMP:
return 'x';
}
return '-';
}
R_API const char *r_anal_ref_perm_tostring(RAnalRef *ref) {
int perm = R_ANAL_REF_TYPE_PERM (ref->type);
if (!perm) {
switch (R_ANAL_REF_TYPE_MASK (ref->type)) {
case R_ANAL_REF_TYPE_CODE:
case R_ANAL_REF_TYPE_CALL:
case R_ANAL_REF_TYPE_JUMP:
perm = R_ANAL_REF_TYPE_EXEC;
break;
}
}
return r_str_rwx_i (perm);
}
R_API int r_anal_ref_size(RAnalRef *ref) {
int size = R_ANAL_REF_TYPE_SIZE (ref->type);
if (size) {
return size;
}
switch (R_ANAL_REF_TYPE_MASK (ref->type)) {
case R_ANAL_REF_TYPE_ICOD:
return 4; // or 8?
case R_ANAL_REF_TYPE_DATA:
return 4; // or 8?
}
return 0;
}
R_API const char *r_anal_ref_type_tostring(RAnalRefType type) {
switch (R_ANAL_REF_TYPE_MASK (type)) {
case ' ':
case R_ANAL_REF_TYPE_NULL:
return "NULL";
case R_ANAL_REF_TYPE_ICOD:
return "ICOD";
case R_ANAL_REF_TYPE_CODE:
return "CODE";
case R_ANAL_REF_TYPE_CALL:
return "CALL";
case R_ANAL_REF_TYPE_JUMP:
return "JUMP";
case R_ANAL_REF_TYPE_DATA:
return "DATA";
case R_ANAL_REF_TYPE_STRN:
return "STRN";
default:
// R_LOG_ERROR("Invalid unknown ref type %c", R_ANAL_REF_TYPE_MASK (type));
return "UNKN";
}
}
R_API RAnalRefType r_anal_xrefs_type_from_string(const char *s) {
RAnalRefType rt = R_ANAL_REF_TYPE_NULL;
if (strchr (s, 'r')) {
rt |= (R_ANAL_REF_TYPE_DATA | R_ANAL_REF_TYPE_READ);
}
if (strchr (s, 'w')) {
rt |= (R_ANAL_REF_TYPE_DATA | R_ANAL_REF_TYPE_WRITE);
}
if (strchr (s, 'x')) {
rt |= R_ANAL_REF_TYPE_EXEC;
}
if (strchr (s, 'c')) {
rt |= R_ANAL_REF_TYPE_CODE;
}
if (strchr (s, 'C')) {
rt |= R_ANAL_REF_TYPE_CALL;
}
if (strchr (s, 'j')) {
rt |= R_ANAL_REF_TYPE_JUMP;
}
if (strchr (s, 'd')) {
rt |= R_ANAL_REF_TYPE_DATA;
}
if (strchr (s, 's')) {
rt |= R_ANAL_REF_TYPE_STRN;
}
return rt;
}
R_API int r_anal_ref_typemask(int x) {
const int maskedType = x & 0xff;
switch (maskedType) {
case R_ANAL_REF_TYPE_NULL:
case R_ANAL_REF_TYPE_CODE | R_ANAL_REF_TYPE_DATA: // 'g' // XXX R2_590 - this is a conflictive type
case R_ANAL_REF_TYPE_CODE: // 'c' // code ref
case R_ANAL_REF_TYPE_CALL: // 'C' // code ref (call)
case R_ANAL_REF_TYPE_JUMP: // 'j' // code ref (call)
case R_ANAL_REF_TYPE_DATA: // 'd' // mem ref
case R_ANAL_REF_TYPE_STRN: // 's' // string ref
case R_ANAL_REF_TYPE_ICOD: // 'i' // indirect cod reference
return maskedType;
case ' ':
return R_ANAL_REF_TYPE_NULL;
}
R_LOG_ERROR ("Invalid reftype mask '%c' (0x%02x)", x, x);
// SHOULD NEVER HAPPEN MAYBE WARN HERE
return 0;
}
// TODO: deprecate
R_API RAnalRefType r_anal_xrefs_type(char ch) {
switch (ch) {
case R_ANAL_REF_TYPE_CODE:
case R_ANAL_REF_TYPE_CALL:
case R_ANAL_REF_TYPE_DATA:
case R_ANAL_REF_TYPE_STRN:
case R_ANAL_REF_TYPE_ICOD:
case R_ANAL_REF_TYPE_NULL:
return (RAnalRefType)ch;
default:
return R_ANAL_REF_TYPE_NULL;
}
}