2015-01-11 20:01:44 +01:00
|
|
|
/* Copyright radare2 2014-2015 - Author: pancake */
|
2014-04-29 03:53:48 +02:00
|
|
|
|
|
|
|
#include <r_core.h>
|
2015-02-17 01:28:51 +01:00
|
|
|
static const char *mousemodes[] = { "canvas-y", "canvas-x", "node-y", "node-x", NULL };
|
2015-03-05 02:07:53 +01:00
|
|
|
static int mousemode = 0;
|
2015-06-29 17:00:12 +02:00
|
|
|
static int zoom = 100;
|
2015-05-30 01:36:40 +02:00
|
|
|
|
|
|
|
#define BORDER 3
|
|
|
|
#define BORDER_WIDTH 4
|
|
|
|
#define BORDER_HEIGHT 3
|
2015-05-30 18:04:03 +02:00
|
|
|
#define MARGIN_TEXT_X 2
|
|
|
|
#define MARGIN_TEXT_Y 2
|
2015-06-12 11:08:05 +02:00
|
|
|
#define HORIZONTAL_NODE_SPACING 12
|
|
|
|
#define VERTICAL_NODE_SPACING 4
|
2015-05-30 01:36:40 +02:00
|
|
|
#define MAX_NODE_WIDTH 18
|
2015-06-12 11:08:05 +02:00
|
|
|
#define INIT_HISTORY_CAPACITY 16
|
|
|
|
#define TITLE_LEN 128
|
2015-05-30 01:36:40 +02:00
|
|
|
|
2015-06-12 11:08:05 +02:00
|
|
|
#define history_push(stack, x) (r_stack_push (stack, (void *)(size_t)x))
|
|
|
|
#define history_pop(stack) ((RGraphNode *)r_stack_pop (stack))
|
|
|
|
|
2015-06-17 11:50:48 +02:00
|
|
|
#define hash_set(sdb,k,v) (sdb_num_set (sdb, sdb_fmt (0, "%"PFMT64u, (ut64)(size_t)k), (ut64)(size_t)v, 0))
|
|
|
|
#define hash_get(sdb,k) (sdb_num_get (sdb, sdb_fmt (0, "%"PFMT64u, (ut64)(size_t)k), NULL))
|
|
|
|
#define hash_get_rnode(sdb,k) ((RGraphNode *)(size_t)hash_get (sdb, k))
|
2015-06-12 11:08:05 +02:00
|
|
|
|
|
|
|
#define get_gn(iter) ((RGraphNode *)r_list_iter_get_data(iter))
|
|
|
|
#define get_anode(iter) ((ANode *)get_gn(iter)->data)
|
|
|
|
|
2015-06-16 00:20:39 +02:00
|
|
|
#define graph_foreach_anode(list, it, pos, anode) \
|
2015-06-12 11:08:05 +02:00
|
|
|
if (list) for (it = list->head; it && (pos = it->data) && (pos) && (anode = (ANode *)pos->data); it = it->n)
|
2014-05-05 03:15:28 +02:00
|
|
|
|
2015-06-16 00:20:39 +02:00
|
|
|
struct layer_t {
|
|
|
|
int n_nodes;
|
|
|
|
RGraphNode **nodes;
|
|
|
|
int position;
|
|
|
|
int height;
|
|
|
|
};
|
|
|
|
|
2015-06-12 11:08:05 +02:00
|
|
|
typedef struct ascii_node {
|
2014-04-29 03:53:48 +02:00
|
|
|
int x;
|
|
|
|
int y;
|
|
|
|
int w;
|
|
|
|
int h;
|
|
|
|
ut64 addr;
|
2015-06-16 00:20:39 +02:00
|
|
|
int layer;
|
|
|
|
int pos_in_layer;
|
2014-07-09 05:05:22 +02:00
|
|
|
char *text;
|
2015-06-16 00:20:39 +02:00
|
|
|
int is_dummy;
|
2015-06-12 11:08:05 +02:00
|
|
|
} ANode;
|
2014-04-29 03:53:48 +02:00
|
|
|
|
2015-06-12 11:08:05 +02:00
|
|
|
typedef struct ascii_graph {
|
2015-06-03 00:18:33 +02:00
|
|
|
RCore *core;
|
|
|
|
RConsCanvas *can;
|
|
|
|
RAnalFunction *fcn;
|
2015-06-12 11:08:05 +02:00
|
|
|
RGraph *graph;
|
|
|
|
RListIter *curnode;
|
|
|
|
|
2015-06-03 00:18:33 +02:00
|
|
|
int is_callgraph;
|
|
|
|
int is_instep;
|
|
|
|
int is_simple_mode;
|
|
|
|
int is_small_nodes;
|
|
|
|
|
2015-06-12 11:08:05 +02:00
|
|
|
RStack *history;
|
|
|
|
ANode *update_seek_on;
|
2015-06-03 15:51:08 +02:00
|
|
|
int need_reload_nodes;
|
|
|
|
int force_update_seek;
|
2015-06-16 00:20:39 +02:00
|
|
|
|
|
|
|
/* layout algorithm info */
|
|
|
|
RList *back_edges;
|
|
|
|
RList *long_edges;
|
|
|
|
struct layer_t *layers;
|
|
|
|
int n_layers;
|
2015-06-12 11:08:05 +02:00
|
|
|
} AGraph;
|
2014-04-29 03:53:48 +02:00
|
|
|
|
2015-06-12 11:08:05 +02:00
|
|
|
struct agraph_refresh_data {
|
|
|
|
AGraph *g;
|
2015-06-11 12:23:30 +02:00
|
|
|
int fs;
|
2014-04-29 03:53:48 +02:00
|
|
|
};
|
|
|
|
|
2015-06-03 00:18:33 +02:00
|
|
|
#define G(x,y) r_cons_canvas_gotoxy (g->can, x, y)
|
|
|
|
#define W(x) r_cons_canvas_write (g->can, x)
|
|
|
|
#define B(x,y,w,h) r_cons_canvas_box(g->can, x,y,w,h,NULL)
|
|
|
|
#define B1(x,y,w,h) r_cons_canvas_box(g->can, x,y,w,h,Color_BLUE)
|
|
|
|
#define B2(x,y,w,h) r_cons_canvas_box(g->can, x,y,w,h,Color_MAGENTA)
|
|
|
|
#define L(x,y,x2,y2) r_cons_canvas_line(g->can, x,y,x2,y2,0)
|
|
|
|
#define L1(x,y,x2,y2) r_cons_canvas_line(g->can, x,y,x2,y2,1)
|
|
|
|
#define L2(x,y,x2,y2) r_cons_canvas_line(g->can, x,y,x2,y2,2)
|
|
|
|
#define F(x,y,x2,y2,c) r_cons_canvas_fill(g->can, x,y,x2,y2,c,0)
|
|
|
|
|
2015-06-12 11:08:05 +02:00
|
|
|
static void update_node_dimension(RGraph *g, int is_small) {
|
|
|
|
const RList *nodes = r_graph_get_nodes (g);
|
|
|
|
RGraphNode *gn;
|
|
|
|
RListIter *it;
|
|
|
|
ANode *n;
|
2015-05-30 01:21:48 +02:00
|
|
|
|
2015-06-16 00:20:39 +02:00
|
|
|
graph_foreach_anode (nodes, it, gn, n) {
|
2015-06-03 14:47:34 +02:00
|
|
|
if (is_small) {
|
|
|
|
n->w = n->h = 0;
|
|
|
|
} else {
|
|
|
|
n->w = r_str_bounds (n->text, &n->h);
|
|
|
|
n->w += BORDER_WIDTH;
|
|
|
|
n->h += BORDER_HEIGHT;
|
|
|
|
n->w = R_MAX (MAX_NODE_WIDTH, n->w);
|
2015-06-29 17:00:12 +02:00
|
|
|
/* scale node by zoom */
|
|
|
|
n->w = (n->w * zoom) / 100;
|
|
|
|
n->h = (n->h * zoom) / 100;
|
2015-06-03 14:47:34 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-12 11:08:05 +02:00
|
|
|
static void small_ANode_print(AGraph *g, ANode *n, int cur) {
|
|
|
|
char title[TITLE_LEN];
|
2014-04-29 03:53:48 +02:00
|
|
|
|
2015-05-30 12:37:42 +02:00
|
|
|
if (!G (n->x + 2, n->y - 1))
|
2015-05-30 12:16:31 +02:00
|
|
|
return;
|
2015-05-30 12:37:42 +02:00
|
|
|
if (cur) {
|
|
|
|
W("[_@@_]");
|
2015-06-03 00:18:33 +02:00
|
|
|
(void)G (-g->can->sx, -g->can->sy + 2);
|
2015-05-30 12:37:42 +02:00
|
|
|
snprintf (title, sizeof (title) - 1,
|
2015-01-09 11:38:00 +01:00
|
|
|
"0x%08"PFMT64x":", n->addr);
|
2015-05-30 12:37:42 +02:00
|
|
|
W (title);
|
2015-06-03 00:18:33 +02:00
|
|
|
(void)G (-g->can->sx, -g->can->sy + 3);
|
2015-05-30 12:37:42 +02:00
|
|
|
W (n->text);
|
|
|
|
} else {
|
|
|
|
W("[____]");
|
2015-01-09 11:38:00 +01:00
|
|
|
}
|
2015-05-30 12:37:42 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-06-12 11:08:05 +02:00
|
|
|
static void normal_ANode_print(AGraph *g, ANode *n, int cur) {
|
|
|
|
char title[TITLE_LEN];
|
2015-05-30 12:37:42 +02:00
|
|
|
char *text;
|
|
|
|
int delta_x = 0;
|
|
|
|
int delta_y = 0;
|
2015-05-31 23:46:32 +02:00
|
|
|
int x, y;
|
2015-01-09 11:38:00 +01:00
|
|
|
|
2015-01-11 01:48:20 +01:00
|
|
|
#if SHOW_OUT_OF_SCREEN_NODES
|
2015-06-03 00:18:33 +02:00
|
|
|
x = n->x + g->can->sx;
|
|
|
|
y = n->y + n->h + g->can->sy;
|
|
|
|
if (x < 0 || x > g->can->w)
|
2015-05-30 01:36:40 +02:00
|
|
|
return;
|
2015-06-03 00:18:33 +02:00
|
|
|
if (y < 0 || y > g->can->h)
|
2015-05-30 01:36:40 +02:00
|
|
|
return;
|
2015-01-11 01:48:20 +01:00
|
|
|
#endif
|
2015-06-03 00:18:33 +02:00
|
|
|
x = n->x + g->can->sx;
|
|
|
|
y = n->y + g->can->sy;
|
2015-05-30 18:04:03 +02:00
|
|
|
if (x < -MARGIN_TEXT_X)
|
|
|
|
delta_x = -x - MARGIN_TEXT_X;
|
|
|
|
if (x + n->w < -MARGIN_TEXT_X)
|
2015-05-30 01:36:40 +02:00
|
|
|
return;
|
|
|
|
if (y < -1)
|
2015-05-30 18:04:03 +02:00
|
|
|
delta_y = -y - MARGIN_TEXT_Y;
|
2015-05-30 01:36:40 +02:00
|
|
|
|
2014-04-29 03:53:48 +02:00
|
|
|
if (cur) {
|
2014-05-18 03:41:39 +02:00
|
|
|
//F (n->x,n->y, n->w, n->h, '.');
|
2014-04-29 03:53:48 +02:00
|
|
|
snprintf (title, sizeof (title)-1,
|
2015-06-12 02:19:58 +02:00
|
|
|
"[0x%08"PFMT64x"]", n->addr);
|
2014-04-29 03:53:48 +02:00
|
|
|
} else {
|
|
|
|
snprintf (title, sizeof (title)-1,
|
2015-06-12 02:19:58 +02:00
|
|
|
" 0x%08"PFMT64x" ", n->addr);
|
2014-04-29 03:53:48 +02:00
|
|
|
}
|
2015-05-30 18:16:03 +02:00
|
|
|
if (delta_x < strlen(title) && G(n->x + MARGIN_TEXT_X + delta_x, n->y + 1))
|
|
|
|
W(title + delta_x);
|
2015-06-29 17:00:12 +02:00
|
|
|
int center_x = 0;
|
|
|
|
int center_y = 0;
|
|
|
|
if (zoom>100) {
|
|
|
|
center_x += ((zoom-100)/20);
|
|
|
|
center_y += ((zoom-100)/30);
|
|
|
|
}
|
2015-05-30 12:37:42 +02:00
|
|
|
|
2015-06-29 17:00:12 +02:00
|
|
|
if (G(n->x + MARGIN_TEXT_X + delta_x + center_x,
|
|
|
|
n->y + MARGIN_TEXT_Y + delta_y + center_y)) {
|
|
|
|
text = r_str_crop (n->text,
|
|
|
|
delta_x, delta_y,
|
|
|
|
n->w - BORDER_WIDTH,
|
|
|
|
n->h - BORDER_WIDTH + 1);
|
2015-05-31 22:27:47 +02:00
|
|
|
if (text) {
|
|
|
|
W (text);
|
|
|
|
free (text);
|
|
|
|
} else {
|
|
|
|
W (n->text);
|
|
|
|
}
|
2015-01-11 02:28:59 +01:00
|
|
|
}
|
2015-05-30 12:37:42 +02:00
|
|
|
|
2015-02-15 21:48:46 +01:00
|
|
|
// TODO: check if node is traced or not and hsow proper color
|
2015-06-12 11:08:05 +02:00
|
|
|
// This info must be stored inside ANode* from RCore*
|
2015-02-15 21:48:46 +01:00
|
|
|
if (cur) {
|
|
|
|
B1 (n->x, n->y, n->w, n->h);
|
|
|
|
} else {
|
|
|
|
B (n->x, n->y, n->w, n->h);
|
|
|
|
}
|
2014-04-29 03:53:48 +02:00
|
|
|
}
|
|
|
|
|
2015-06-16 00:20:39 +02:00
|
|
|
static int **get_crossing_matrix (const RGraph *g, struct layer_t layers[],
|
|
|
|
int maxlayer, int i, int *n_rows) {
|
|
|
|
int len = layers[i].n_nodes;
|
|
|
|
int **m;
|
|
|
|
int j;
|
|
|
|
|
|
|
|
m = R_NEWS0 (int *, len);
|
|
|
|
if (!m)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
for (j = 0; j < len; ++j) {
|
|
|
|
m[j] = R_NEWS0 (int, len);
|
|
|
|
if (!m[j])
|
|
|
|
goto err_row;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* calculate crossings between layer i and layer i-1 */
|
|
|
|
/* consider the crossings generated by each pair of edges */
|
|
|
|
if (i > 0) {
|
|
|
|
int prev_len = layers[i - 1].n_nodes;
|
|
|
|
for (j = 0; j < prev_len; ++j) {
|
|
|
|
RGraphNode *gj = layers[i - 1].nodes[j];
|
|
|
|
const RList *neigh = r_graph_get_neighbours (g, gj);
|
|
|
|
RGraphNode *gk;
|
|
|
|
RListIter *itk;
|
|
|
|
|
|
|
|
r_list_foreach (neigh, itk, gk) {
|
|
|
|
int s;
|
|
|
|
for (s = 0; s < j; ++s) {
|
|
|
|
RGraphNode *gs = layers[i - 1].nodes[s];
|
|
|
|
const RList *neigh_s = r_graph_get_neighbours (g, gs);
|
|
|
|
RGraphNode *gt;
|
|
|
|
RListIter *itt;
|
|
|
|
|
|
|
|
r_list_foreach (neigh_s, itt, gt) {
|
|
|
|
ANode *ak, *at; /* k and t should be "indexes" on layer i */
|
|
|
|
|
|
|
|
if (gt == gk) continue;
|
|
|
|
ak = (ANode *)gk->data;
|
|
|
|
at = (ANode *)gt->data;
|
|
|
|
if (ak->layer != i || at->layer != i) {
|
|
|
|
eprintf("%llx or %llx are not on the right layer (%d)\n", ak->addr, at->addr, i);
|
|
|
|
eprintf("edge from %llx to %llx is wrong\n", ((ANode*)(gj->data))->addr, ak->addr);
|
|
|
|
eprintf("edge from %llx to %llx is wrong\n\n", ((ANode*)(gs->data))->addr, at->addr);
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
m[ak->pos_in_layer][at->pos_in_layer]++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* calculate crossings between layer i and layer i+1 */
|
|
|
|
if (i < maxlayer - 1) {
|
|
|
|
/* XXX: implement */
|
|
|
|
}
|
|
|
|
|
|
|
|
if (n_rows)
|
|
|
|
*n_rows = len;
|
|
|
|
return m;
|
|
|
|
|
|
|
|
err_row:
|
|
|
|
for (i = 0; i < len; ++i) {
|
|
|
|
if (m[i])
|
|
|
|
free (m[i]);
|
|
|
|
}
|
|
|
|
free (m);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int layer_sweep (RGraph *g, struct layer_t layers[], int maxlayer, int i) {
|
|
|
|
int **cross_matrix;
|
|
|
|
RGraphNode *u, *v;
|
|
|
|
ANode *au, *av;
|
|
|
|
int n_rows, j, changed = R_FALSE;
|
|
|
|
int len = layers[i].n_nodes;
|
|
|
|
|
|
|
|
cross_matrix = get_crossing_matrix (g, layers, maxlayer, i, &n_rows);
|
|
|
|
if (!cross_matrix) return R_FALSE;
|
|
|
|
|
|
|
|
for (j = 0; j < len - 1; ++j) {
|
|
|
|
int auidx, avidx;
|
|
|
|
|
|
|
|
u = layers[i].nodes[j];
|
|
|
|
v = layers[i].nodes[j + 1];
|
|
|
|
au = (ANode *)u->data;
|
|
|
|
av = (ANode *)v->data;
|
|
|
|
auidx = au->pos_in_layer;
|
|
|
|
avidx = av->pos_in_layer;
|
|
|
|
|
|
|
|
if (cross_matrix[auidx][avidx] > cross_matrix[avidx][auidx]) {
|
|
|
|
/* swap elements */
|
|
|
|
layers[i].nodes[j] = v;
|
|
|
|
layers[i].nodes[j + 1] = u;
|
|
|
|
changed = R_TRUE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* update position in the layer of each node. During the swap of some
|
|
|
|
* elements we didn't swap also the pos_in_layer because the cross_matrix
|
|
|
|
* is indexed by it, so do it now! */
|
|
|
|
for (j = 0; j < layers[i].n_nodes; ++j) {
|
|
|
|
ANode *n = (ANode *)layers[i].nodes[j]->data;
|
|
|
|
n->pos_in_layer = j;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (j = 0; j < n_rows; ++j)
|
|
|
|
free (cross_matrix[j]);
|
|
|
|
free (cross_matrix);
|
|
|
|
return changed;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void view_cyclic_edge (RGraphNode *from, RGraphNode *to, RGraphVisitor *vis) {
|
|
|
|
AGraph *g = (AGraph *)vis->data;
|
|
|
|
RGraphEdge *e = R_NEW (RGraphEdge);
|
|
|
|
|
|
|
|
e->from = from;
|
|
|
|
e->to = to;
|
|
|
|
r_list_append (g->back_edges, e);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int get_depth (Sdb *path, RGraphNode *n) {
|
|
|
|
int res = 0;
|
2015-06-17 11:50:48 +02:00
|
|
|
while ((n = hash_get_rnode (path, n)) != NULL) {
|
2015-06-16 00:20:39 +02:00
|
|
|
res++;
|
|
|
|
}
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void set_layer (RGraphNode *from, RGraphNode *to, RGraphVisitor *vis) {
|
|
|
|
Sdb *path = (Sdb *)vis->data;
|
|
|
|
int bdepth, adepth;
|
|
|
|
|
|
|
|
adepth = get_depth (path, from);
|
|
|
|
bdepth = get_depth (path, to);
|
|
|
|
|
|
|
|
if (adepth + 1 > bdepth)
|
2015-06-17 11:50:48 +02:00
|
|
|
hash_set (path, to, from);
|
2015-06-16 00:20:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static void view_dummy (RGraphNode *from, RGraphNode *to, RGraphVisitor *vis) {
|
|
|
|
ANode *a = (ANode *)from->data;
|
|
|
|
ANode *b = (ANode *)to->data;
|
|
|
|
RList *long_edges = (RList *)vis->data;
|
|
|
|
|
|
|
|
if (R_ABS (a->layer - b->layer) > 1) {
|
|
|
|
RGraphEdge *e = R_NEW (RGraphEdge);
|
|
|
|
e->from = from;
|
|
|
|
e->to = to;
|
|
|
|
r_list_append (long_edges, e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* find a set of edges that, removed, makes the graph acyclic */
|
|
|
|
/* invert the edges identified in the previous step */
|
|
|
|
static void remove_cycles (AGraph *g) {
|
|
|
|
RGraphVisitor cyclic_vis = { 0 };
|
|
|
|
RGraphEdge *e;
|
|
|
|
RListIter *it;
|
|
|
|
|
|
|
|
g->back_edges = r_list_new();
|
|
|
|
cyclic_vis.back_edge = (RGraphEdgeCallback)view_cyclic_edge;
|
|
|
|
cyclic_vis.data = g;
|
|
|
|
r_graph_dfs (g->graph, &cyclic_vis);
|
|
|
|
|
|
|
|
r_list_foreach (g->back_edges, it, e) {
|
|
|
|
r_graph_del_edge (g->graph, e->from, e->to);
|
|
|
|
r_graph_add_edge (g->graph, e->to, e->from);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* assign a layer to each node of the graph */
|
|
|
|
static void assign_layers (AGraph *g) {
|
|
|
|
RGraphVisitor layer_vis = { 0 };
|
|
|
|
Sdb *path_layers = sdb_new0 ();
|
|
|
|
RGraphNode *gn;
|
|
|
|
RListIter *it;
|
2015-06-12 11:08:05 +02:00
|
|
|
ANode *n;
|
2015-06-03 00:18:33 +02:00
|
|
|
|
2015-06-16 00:20:39 +02:00
|
|
|
layer_vis.data = path_layers;
|
|
|
|
layer_vis.tree_edge = (RGraphEdgeCallback)set_layer;
|
|
|
|
layer_vis.fcross_edge = (RGraphEdgeCallback)set_layer;
|
|
|
|
r_graph_dfs (g->graph, &layer_vis);
|
|
|
|
|
|
|
|
graph_foreach_anode (r_graph_get_nodes (g->graph), it, gn, n) {
|
|
|
|
n->layer = get_depth (path_layers, gn);
|
|
|
|
}
|
2015-06-03 00:18:33 +02:00
|
|
|
|
2015-06-16 00:20:39 +02:00
|
|
|
sdb_free (path_layers);
|
2014-05-05 03:15:28 +02:00
|
|
|
}
|
|
|
|
|
2015-06-16 00:20:39 +02:00
|
|
|
/* add dummy nodes when there are edges that span multiple layers */
|
|
|
|
static void create_dummy_nodes (AGraph *g) {
|
|
|
|
RGraphVisitor dummy_vis = { 0 };
|
|
|
|
RListIter *it;
|
|
|
|
RGraphEdge *e;
|
|
|
|
|
|
|
|
g->long_edges = r_list_new ();
|
|
|
|
dummy_vis.data = g->long_edges;
|
|
|
|
dummy_vis.tree_edge = (RGraphEdgeCallback)view_dummy;
|
|
|
|
dummy_vis.fcross_edge = (RGraphEdgeCallback)view_dummy;
|
|
|
|
r_graph_dfs (g->graph, &dummy_vis);
|
|
|
|
|
|
|
|
r_list_foreach (g->long_edges, it, e) {
|
|
|
|
ANode *from = (ANode *)e->from->data;
|
|
|
|
ANode *to = (ANode *)e->to->data;
|
|
|
|
int diff_layer = R_ABS (from->layer - to->layer);
|
|
|
|
RGraphNode *prev = e->from;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
r_graph_del_edge (g->graph, e->from, e->to);
|
|
|
|
for (i = 1; i < diff_layer; ++i) {
|
|
|
|
ANode *n = R_NEW0 (ANode);
|
|
|
|
n->layer = from->layer + i;
|
|
|
|
n->pos_in_layer = -1;
|
|
|
|
n->is_dummy = R_TRUE;
|
|
|
|
RGraphNode *dummy = r_graph_add_node (g->graph, n);
|
|
|
|
r_graph_add_edge (g->graph, prev, dummy);
|
|
|
|
prev = dummy;
|
|
|
|
}
|
|
|
|
r_graph_add_edge (g->graph, prev, e->to);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* create layers and assign an initial ordering of the nodes into them */
|
|
|
|
static void create_layers (AGraph *g) {
|
2015-06-12 11:08:05 +02:00
|
|
|
const RList *nodes = r_graph_get_nodes (g->graph);
|
|
|
|
RGraphNode *gn;
|
|
|
|
RListIter *it;
|
|
|
|
ANode *n;
|
2015-06-16 00:20:39 +02:00
|
|
|
int i;
|
2014-05-05 03:15:28 +02:00
|
|
|
|
2015-06-16 00:20:39 +02:00
|
|
|
/* identify max layer */
|
|
|
|
g->n_layers = 0;
|
|
|
|
graph_foreach_anode (nodes, it, gn, n) {
|
|
|
|
if (n->layer > g->n_layers)
|
|
|
|
g->n_layers = n->layer;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* create a starting ordering of nodes for each layer */
|
|
|
|
g->n_layers++;
|
|
|
|
g->layers = R_NEWS0 (struct layer_t, g->n_layers);
|
|
|
|
|
|
|
|
graph_foreach_anode (nodes, it, gn, n)
|
|
|
|
g->layers[n->layer].n_nodes++;
|
2014-05-06 01:17:02 +04:00
|
|
|
|
2015-06-16 00:20:39 +02:00
|
|
|
for (i = 0; i < g->n_layers; ++i) {
|
|
|
|
g->layers[i].nodes = R_NEWS (RGraphNode *, g->layers[i].n_nodes);
|
|
|
|
g->layers[i].position = 0;
|
2014-05-05 03:15:28 +02:00
|
|
|
}
|
2015-06-16 00:20:39 +02:00
|
|
|
graph_foreach_anode (nodes, it, gn, n) {
|
|
|
|
n->pos_in_layer = g->layers[n->layer].position;
|
|
|
|
g->layers[n->layer].nodes[g->layers[n->layer].position++] = gn;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* layer-by-layer sweep */
|
|
|
|
/* it permutes each layer, trying to find the best ordering for each layer
|
|
|
|
* to minimize the number of crossing edges */
|
|
|
|
static void minimize_crossings (AGraph *g) {
|
|
|
|
int i, cross_changed;
|
|
|
|
|
|
|
|
do {
|
|
|
|
cross_changed = R_FALSE;
|
|
|
|
|
|
|
|
for (i = 0; i < g->n_layers; ++i)
|
|
|
|
cross_changed |= layer_sweep (g->graph, g->layers, g->n_layers, i);
|
|
|
|
} while (cross_changed);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void restore_original_edges (AGraph *g) {
|
|
|
|
RListIter *it;
|
|
|
|
RGraphEdge *e;
|
2015-06-12 11:08:05 +02:00
|
|
|
|
2015-06-16 00:20:39 +02:00
|
|
|
r_list_foreach (g->long_edges, it, e) {
|
|
|
|
r_graph_add_edge (g->graph, e->from, e->to);
|
|
|
|
}
|
|
|
|
|
|
|
|
r_list_foreach (g->back_edges, it, e) {
|
|
|
|
r_graph_del_edge (g->graph, e->to, e->from);
|
|
|
|
r_graph_add_edge (g->graph, e->from, e->to);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void remove_dummy_nodes (AGraph *g) {
|
|
|
|
RGraphNode *gn;
|
|
|
|
RListIter *it;
|
|
|
|
ANode *n;
|
2015-06-17 13:46:03 +02:00
|
|
|
RList *toremove = r_list_new ();
|
2015-06-16 00:20:39 +02:00
|
|
|
|
|
|
|
graph_foreach_anode (r_graph_get_nodes (g->graph), it, gn, n) {
|
|
|
|
if (n->is_dummy) {
|
2015-06-17 13:46:03 +02:00
|
|
|
r_list_append (toremove, gn);
|
2015-06-16 00:20:39 +02:00
|
|
|
}
|
|
|
|
}
|
2015-06-17 13:46:03 +02:00
|
|
|
|
|
|
|
r_list_foreach (toremove, it, gn) {
|
|
|
|
r_graph_del_node (g->graph, gn);
|
|
|
|
}
|
|
|
|
|
|
|
|
r_list_free (toremove);
|
2015-06-16 00:20:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* 1) trasform the graph into a DAG
|
|
|
|
* 2) partition the nodes in layers
|
|
|
|
* 3) split long edges that traverse multiple layers
|
|
|
|
* 4) reorder nodes in each layer to reduce the number of edge crossing
|
|
|
|
* 5) assign x and y coordinates to each node (TODO)
|
|
|
|
* 6) restore the original graph, with long edges and cycles */
|
|
|
|
static void set_layout_bb(AGraph *g) {
|
|
|
|
int i, j, k, rh, nx;
|
|
|
|
|
|
|
|
remove_cycles (g);
|
|
|
|
assign_layers (g);
|
|
|
|
create_dummy_nodes (g);
|
|
|
|
create_layers (g);
|
|
|
|
minimize_crossings (g);
|
|
|
|
|
|
|
|
/* identify row height */
|
|
|
|
for (i = 0; i < g->n_layers; i++) {
|
2014-05-05 03:15:28 +02:00
|
|
|
rh = 0;
|
2015-06-16 00:20:39 +02:00
|
|
|
for (j = 0; j < g->layers[i].n_nodes; ++j) {
|
|
|
|
ANode *n = (ANode *)(g->layers[i].nodes[j]->data);
|
|
|
|
if (n->h > rh)
|
|
|
|
rh = n->h;
|
2014-05-05 03:15:28 +02:00
|
|
|
}
|
2015-06-16 00:20:39 +02:00
|
|
|
g->layers[i].height = rh;
|
2014-05-05 03:15:28 +02:00
|
|
|
}
|
|
|
|
|
2015-06-16 00:20:39 +02:00
|
|
|
/* vertical align */
|
|
|
|
for (i = 0; i < g->n_layers; ++i) {
|
|
|
|
for (j = 0; j < g->layers[i].n_nodes; ++j) {
|
|
|
|
ANode *n = (ANode *)(g->layers[i].nodes[j]->data);
|
|
|
|
n->y = 1;
|
|
|
|
for (k = 0; k < n->layer; ++k) {
|
|
|
|
n->y += g->layers[k].height + VERTICAL_NODE_SPACING;
|
|
|
|
}
|
|
|
|
}
|
2014-05-05 03:15:28 +02:00
|
|
|
}
|
2015-06-16 00:20:39 +02:00
|
|
|
|
|
|
|
/* TODO: x-coordinate assignment algorithm */
|
|
|
|
/* horizontal align */
|
|
|
|
for (i = 0; i < g->n_layers; i++) {
|
2015-06-03 00:18:33 +02:00
|
|
|
nx = (i % 2) * 10;
|
2015-06-16 00:20:39 +02:00
|
|
|
for (j = 0; j < g->layers[i].n_nodes; ++j) {
|
|
|
|
ANode *n = (ANode *)(g->layers[i].nodes[j]->data);
|
|
|
|
n->x = nx;
|
|
|
|
nx += n->w + HORIZONTAL_NODE_SPACING;
|
2014-05-05 03:15:28 +02:00
|
|
|
}
|
|
|
|
}
|
2015-06-16 00:20:39 +02:00
|
|
|
|
|
|
|
restore_original_edges (g);
|
|
|
|
remove_dummy_nodes (g);
|
|
|
|
|
|
|
|
/* free all temporary structures used during layout */
|
|
|
|
for (i = 0; i < g->n_layers; ++i)
|
|
|
|
free (g->layers[i].nodes);
|
|
|
|
free (g->layers);
|
|
|
|
r_list_free (g->long_edges);
|
|
|
|
r_list_free (g->back_edges);
|
2014-05-05 03:15:28 +02:00
|
|
|
}
|
|
|
|
|
2015-06-12 11:08:05 +02:00
|
|
|
static void set_layout_callgraph(AGraph *g) {
|
|
|
|
const RList *nodes = r_graph_get_nodes (g->graph);
|
|
|
|
RGraphNode *gn;
|
|
|
|
RListIter *it;
|
|
|
|
ANode *prev_n = NULL, *n;
|
2015-06-03 14:59:53 +02:00
|
|
|
int y = 5, x = 20;
|
|
|
|
|
2015-06-16 00:20:39 +02:00
|
|
|
graph_foreach_anode (nodes, it, gn, n) {
|
2015-06-03 14:59:53 +02:00
|
|
|
// wrap to width 'w'
|
2015-06-12 11:08:05 +02:00
|
|
|
if (prev_n && n->x < prev_n->x) {
|
|
|
|
y += 10;
|
|
|
|
x = 0;
|
2015-06-03 14:59:53 +02:00
|
|
|
}
|
2015-06-12 11:08:05 +02:00
|
|
|
n->x = x;
|
|
|
|
n->y = prev_n ? y : 2;
|
2015-06-03 14:59:53 +02:00
|
|
|
x += 30;
|
2015-06-12 11:08:05 +02:00
|
|
|
prev_n = n;
|
2015-06-03 14:59:53 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-12 11:08:05 +02:00
|
|
|
/* build the RGraph inside the AGraph g, starting from the Basic Blocks */
|
|
|
|
static int get_bbnodes(AGraph *g) {
|
2014-04-29 03:53:48 +02:00
|
|
|
RAnalBlock *bb;
|
2015-06-03 00:18:33 +02:00
|
|
|
RListIter *iter;
|
2015-06-12 11:08:05 +02:00
|
|
|
Sdb *g_nodes = sdb_new0 ();
|
|
|
|
if (!g_nodes) return R_FALSE;
|
2015-06-03 00:18:33 +02:00
|
|
|
|
|
|
|
r_list_foreach (g->fcn->bbs, iter, bb) {
|
2015-06-12 11:08:05 +02:00
|
|
|
RGraphNode *gn;
|
|
|
|
ANode *node;
|
|
|
|
|
2015-06-03 00:18:33 +02:00
|
|
|
if (bb->addr == UT64_MAX)
|
|
|
|
continue;
|
|
|
|
|
2015-06-12 11:08:05 +02:00
|
|
|
node = R_NEW0 (ANode);
|
2015-06-15 21:15:57 +03:00
|
|
|
if (!node) {
|
|
|
|
sdb_free (g_nodes);
|
|
|
|
return R_FALSE;
|
|
|
|
}
|
2015-06-12 11:08:05 +02:00
|
|
|
|
2015-06-03 00:18:33 +02:00
|
|
|
if (g->is_simple_mode) {
|
2015-06-12 11:08:05 +02:00
|
|
|
node->text = r_core_cmd_strf (g->core,
|
2015-06-03 00:18:33 +02:00
|
|
|
"pI %d @ 0x%08"PFMT64x, bb->size, bb->addr);
|
|
|
|
}else {
|
2015-06-12 11:08:05 +02:00
|
|
|
node->text = r_core_cmd_strf (g->core,
|
2015-06-03 00:18:33 +02:00
|
|
|
"pDi %d @ 0x%08"PFMT64x, bb->size, bb->addr);
|
2014-07-09 05:05:22 +02:00
|
|
|
}
|
2015-06-12 11:08:05 +02:00
|
|
|
node->addr = bb->addr;
|
2015-06-16 00:20:39 +02:00
|
|
|
node->layer = -1;
|
|
|
|
node->pos_in_layer = -1;
|
|
|
|
node->is_dummy = R_FALSE;
|
2015-06-12 11:08:05 +02:00
|
|
|
node->x = 0;
|
|
|
|
node->y = 0;
|
|
|
|
node->w = 0;
|
|
|
|
node->h = 0;
|
|
|
|
|
|
|
|
gn = r_graph_add_node (g->graph, node);
|
2015-06-15 21:15:57 +03:00
|
|
|
if (!gn) {
|
|
|
|
sdb_free (g_nodes);
|
|
|
|
return R_FALSE;
|
|
|
|
}
|
2015-06-17 11:50:48 +02:00
|
|
|
hash_set (g_nodes, bb->addr, gn);
|
2015-06-12 11:08:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
r_list_foreach (g->fcn->bbs, iter, bb) {
|
|
|
|
RGraphNode *u, *v;
|
|
|
|
if (bb->addr == UT64_MAX)
|
|
|
|
continue;
|
|
|
|
|
2015-06-17 11:50:48 +02:00
|
|
|
u = hash_get_rnode (g_nodes, bb->addr);
|
2015-06-12 11:08:05 +02:00
|
|
|
if (bb->jump != UT64_MAX) {
|
2015-06-17 11:50:48 +02:00
|
|
|
v = hash_get_rnode (g_nodes, bb->jump);
|
2015-06-12 11:08:05 +02:00
|
|
|
r_graph_add_edge (g->graph, u, v);
|
|
|
|
}
|
|
|
|
if (bb->fail != UT64_MAX) {
|
2015-06-17 11:50:48 +02:00
|
|
|
v = hash_get_rnode (g_nodes, bb->fail);
|
2015-06-12 11:08:05 +02:00
|
|
|
r_graph_add_edge (g->graph, u, v);
|
|
|
|
}
|
2014-07-09 05:05:22 +02:00
|
|
|
}
|
2015-06-03 00:18:33 +02:00
|
|
|
|
2015-06-12 11:08:05 +02:00
|
|
|
g->curnode = r_list_iterator (r_graph_get_nodes(g->graph));
|
|
|
|
sdb_free (g_nodes);
|
2015-06-03 00:18:33 +02:00
|
|
|
return R_TRUE;
|
2014-07-09 05:05:22 +02:00
|
|
|
}
|
2014-04-29 03:53:48 +02:00
|
|
|
|
2015-06-12 11:08:05 +02:00
|
|
|
/* build the RGraph inside the AGraph g, starting from the Call Graph
|
|
|
|
* information */
|
|
|
|
static int get_cgnodes(AGraph *g) {
|
2014-09-23 00:40:35 +02:00
|
|
|
#if FCN_OLD
|
2015-06-12 11:08:05 +02:00
|
|
|
Sdb *g_nodes = sdb_new0 ();
|
|
|
|
RGraphNode *fcn_gn;
|
2014-07-09 05:05:22 +02:00
|
|
|
RListIter *iter;
|
2015-06-12 11:08:05 +02:00
|
|
|
RAnalRef *ref;
|
|
|
|
ANode *node;
|
|
|
|
char *code;
|
2015-06-03 00:18:33 +02:00
|
|
|
|
2015-06-12 11:08:05 +02:00
|
|
|
node = R_NEW0 (ANode);
|
2015-06-15 21:15:57 +03:00
|
|
|
if (!node) {
|
|
|
|
sdb_free (g_nodes);
|
|
|
|
return R_FALSE;
|
|
|
|
}
|
2015-06-12 11:08:05 +02:00
|
|
|
node->text = strdup ("");
|
|
|
|
node->addr = g->fcn->addr;
|
2015-06-16 00:20:39 +02:00
|
|
|
node->layer = -1;
|
|
|
|
node->pos_in_layer = -1;
|
|
|
|
node->is_dummy = R_FALSE;
|
2015-06-12 11:08:05 +02:00
|
|
|
node->x = 10;
|
|
|
|
node->y = 3;
|
|
|
|
node->w = 0;
|
|
|
|
node->h = 0;
|
|
|
|
fcn_gn = r_graph_add_node (g->graph, node);
|
2015-06-15 21:15:57 +03:00
|
|
|
if (!fcn_gn) {
|
|
|
|
sdb_free (g_nodes);
|
|
|
|
return R_FALSE;
|
|
|
|
}
|
2015-06-17 11:50:48 +02:00
|
|
|
hash_set (g_nodes, g->fcn->addr, fcn_gn);
|
2015-06-03 12:44:40 +02:00
|
|
|
|
2015-06-03 00:18:33 +02:00
|
|
|
r_list_foreach (g->fcn->refs, iter, ref) {
|
2015-06-03 12:44:40 +02:00
|
|
|
/* XXX: something is broken, why there are duplicated
|
|
|
|
* nodes here?! goto check fcn->refs!! */
|
2014-07-09 05:05:22 +02:00
|
|
|
/* avoid dups wtf */
|
2015-06-12 11:08:05 +02:00
|
|
|
RGraphNode *gn;
|
2015-06-17 11:50:48 +02:00
|
|
|
gn = hash_get_rnode (g_nodes, ref->addr);
|
2015-06-12 11:08:05 +02:00
|
|
|
if (gn) continue;
|
|
|
|
|
2015-06-03 00:18:33 +02:00
|
|
|
RFlagItem *fi = r_flag_get_at (g->core->flags, ref->addr);
|
2015-06-12 11:08:05 +02:00
|
|
|
node = R_NEW0 (ANode);
|
2015-06-16 23:00:15 +02:00
|
|
|
if (!node) {
|
|
|
|
sdb_free (g_nodes);
|
|
|
|
return R_FALSE;
|
|
|
|
}
|
2014-07-09 05:05:22 +02:00
|
|
|
if (fi) {
|
2015-06-12 11:08:05 +02:00
|
|
|
node->text = strdup (fi->name);
|
|
|
|
node->text = r_str_concat (node->text, ":\n");
|
2014-07-09 05:05:22 +02:00
|
|
|
} else {
|
2015-06-12 11:08:05 +02:00
|
|
|
node->text = strdup ("");
|
2014-07-09 05:05:22 +02:00
|
|
|
}
|
2015-06-03 00:18:33 +02:00
|
|
|
code = r_core_cmd_strf (g->core,
|
2014-07-09 05:05:22 +02:00
|
|
|
"pi 4 @ 0x%08"PFMT64x, ref->addr);
|
2015-06-12 11:08:05 +02:00
|
|
|
node->text = r_str_concat (node->text, code);
|
|
|
|
node->text = r_str_concat (node->text, "...\n");
|
|
|
|
node->addr = ref->addr;
|
2015-06-16 00:20:39 +02:00
|
|
|
node->layer = -1;
|
|
|
|
node->pos_in_layer = -1;
|
|
|
|
node->is_dummy = R_FALSE;
|
2015-06-12 11:08:05 +02:00
|
|
|
node->x = 10;
|
|
|
|
node->y = 10;
|
|
|
|
node->w = 0;
|
|
|
|
node->h = 0;
|
2014-07-09 05:05:22 +02:00
|
|
|
free (code);
|
2015-06-12 11:08:05 +02:00
|
|
|
gn = r_graph_add_node (g->graph, node);
|
2015-06-15 21:15:57 +03:00
|
|
|
if (!gn) {
|
|
|
|
sdb_free (g_nodes);
|
|
|
|
return R_FALSE;
|
|
|
|
}
|
2015-06-17 11:50:48 +02:00
|
|
|
hash_set (g_nodes, ref->addr, gn);
|
2015-06-03 00:18:33 +02:00
|
|
|
|
2015-06-12 11:08:05 +02:00
|
|
|
r_graph_add_edge (g->graph, fcn_gn, gn);
|
2015-06-03 00:18:33 +02:00
|
|
|
}
|
|
|
|
|
2015-06-12 11:08:05 +02:00
|
|
|
g->curnode = r_list_iterator (r_graph_get_nodes (g->graph));
|
|
|
|
sdb_free (g_nodes);
|
2014-09-23 00:40:35 +02:00
|
|
|
#else
|
2015-06-12 11:08:05 +02:00
|
|
|
eprintf ("Must be sdbized\n");
|
2014-09-23 00:40:35 +02:00
|
|
|
#endif
|
2015-06-03 00:18:33 +02:00
|
|
|
|
|
|
|
return R_TRUE;
|
2014-07-09 05:05:22 +02:00
|
|
|
}
|
|
|
|
|
2015-06-12 11:08:05 +02:00
|
|
|
static int reload_nodes(AGraph *g) {
|
2015-06-03 14:59:53 +02:00
|
|
|
int ret;
|
2015-06-03 00:18:33 +02:00
|
|
|
|
2015-06-03 11:38:31 +02:00
|
|
|
if (g->is_callgraph) {
|
2015-06-03 15:25:05 +02:00
|
|
|
ret = get_cgnodes(g);
|
2015-06-03 11:38:31 +02:00
|
|
|
if (!ret)
|
|
|
|
return R_FALSE;
|
|
|
|
} else {
|
2015-06-03 15:25:05 +02:00
|
|
|
ret = get_bbnodes(g);
|
2015-06-03 11:38:31 +02:00
|
|
|
if (!ret)
|
|
|
|
return R_FALSE;
|
2014-04-29 03:53:48 +02:00
|
|
|
}
|
2015-06-03 11:38:31 +02:00
|
|
|
|
2015-06-12 11:08:05 +02:00
|
|
|
update_node_dimension(g->graph, g->is_small_nodes);
|
2015-06-03 14:59:53 +02:00
|
|
|
return R_TRUE;
|
|
|
|
}
|
|
|
|
|
2015-06-12 11:08:05 +02:00
|
|
|
static void update_seek(RConsCanvas *can, ANode *n, int force) {
|
2015-06-03 15:25:05 +02:00
|
|
|
int x, y, w, h;
|
|
|
|
int doscroll = R_FALSE;
|
|
|
|
|
|
|
|
if (!n) return;
|
|
|
|
|
|
|
|
x = n->x + can->sx;
|
|
|
|
y = n->y + can->sy;
|
|
|
|
w = can->w;
|
|
|
|
h = can->h;
|
|
|
|
|
|
|
|
doscroll = force || y < 0 || y + 5 > h || x + 5 > w || x + n->w + 5 < 0;
|
|
|
|
|
|
|
|
if (doscroll) {
|
|
|
|
// top-left
|
|
|
|
can->sy = -n->y + BORDER;
|
|
|
|
can->sx = -n->x + BORDER;
|
|
|
|
// center
|
|
|
|
can->sy = -n->y + BORDER + (h / 8);
|
|
|
|
can->sx = -n->x + BORDER + (w / 4);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-12 11:08:05 +02:00
|
|
|
static void agraph_set_layout(AGraph *g) {
|
2015-06-03 15:51:08 +02:00
|
|
|
if (g->is_callgraph)
|
|
|
|
set_layout_callgraph(g);
|
|
|
|
else
|
|
|
|
set_layout_bb(g);
|
|
|
|
}
|
|
|
|
|
2015-06-12 11:08:05 +02:00
|
|
|
/* set the willing to center the screen on a particular node */
|
|
|
|
static void agraph_update_seek(AGraph *g, ANode *n, int force) {
|
|
|
|
g->update_seek_on = n;
|
2015-06-03 15:51:08 +02:00
|
|
|
g->force_update_seek = force;
|
|
|
|
}
|
|
|
|
|
2015-06-12 11:08:05 +02:00
|
|
|
static void agraph_free(AGraph *g) {
|
|
|
|
r_graph_free (g->graph);
|
|
|
|
r_stack_free (g->history);
|
2015-06-03 15:25:05 +02:00
|
|
|
free(g);
|
|
|
|
}
|
|
|
|
|
2015-06-12 11:08:05 +02:00
|
|
|
static void agraph_print_node(AGraph *g, ANode *n) {
|
|
|
|
const int cur = get_anode (g->curnode) == n;
|
2015-06-03 15:25:05 +02:00
|
|
|
|
|
|
|
if (g->is_small_nodes)
|
2015-06-12 11:08:05 +02:00
|
|
|
small_ANode_print(g, n, cur);
|
2015-06-03 15:25:05 +02:00
|
|
|
else
|
2015-06-12 11:08:05 +02:00
|
|
|
normal_ANode_print(g, n, cur);
|
2015-06-03 15:25:05 +02:00
|
|
|
}
|
|
|
|
|
2015-06-12 11:08:05 +02:00
|
|
|
static void agraph_print_nodes(AGraph *g) {
|
|
|
|
const RList *nodes = r_graph_get_nodes (g->graph);
|
|
|
|
RGraphNode *gn;
|
|
|
|
RListIter *it;
|
|
|
|
ANode *n;
|
|
|
|
|
2015-06-16 00:20:39 +02:00
|
|
|
graph_foreach_anode (nodes, it, gn, n) {
|
2015-06-12 11:08:05 +02:00
|
|
|
if (gn != get_gn (g->curnode))
|
|
|
|
agraph_print_node(g, n);
|
|
|
|
}
|
2015-06-03 15:25:05 +02:00
|
|
|
|
|
|
|
/* draw current node now to make it appear on top */
|
2015-06-12 11:08:05 +02:00
|
|
|
agraph_print_node (g, get_anode(g->curnode));
|
2015-06-03 15:25:05 +02:00
|
|
|
}
|
|
|
|
|
2015-06-12 11:08:05 +02:00
|
|
|
/* print an edge between two nodes.
|
|
|
|
* nth: specifies if the edge is the true(1)/false(2) branch or if it's the
|
|
|
|
* only edge for that node(0), so that a different style will be applied
|
|
|
|
* to the drawn line */
|
|
|
|
static void agraph_print_edge(AGraph *g, ANode *a, ANode *b, int nth) {
|
2015-06-03 15:25:05 +02:00
|
|
|
int x, y, x2, y2;
|
|
|
|
int xinc = 3 + 2 * (nth + 1);
|
|
|
|
x = a->x + xinc;
|
|
|
|
y = a->y + a->h;
|
|
|
|
x2 = b->x + xinc;
|
|
|
|
y2 = b->y;
|
|
|
|
if (a == b) {
|
|
|
|
x2 = a->x;
|
|
|
|
y2 = y - 3;
|
|
|
|
}
|
2015-06-16 00:20:39 +02:00
|
|
|
if (nth > 1)
|
|
|
|
nth = 1;
|
2015-06-03 15:25:05 +02:00
|
|
|
switch (nth) {
|
|
|
|
case 0: L1 (x, y, x2, y2); break;
|
|
|
|
case 1: L2 (x, y, x2, y2); break;
|
|
|
|
case -1: L (x, y, x2, y2); break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-12 11:08:05 +02:00
|
|
|
static void agraph_print_edges(AGraph *g) {
|
|
|
|
const RList *nodes = r_graph_get_nodes (g->graph);
|
|
|
|
RGraphNode *gn, *gv;
|
|
|
|
RListIter *it, *itn;
|
|
|
|
ANode *u, *v;
|
2015-06-03 15:25:05 +02:00
|
|
|
|
2015-06-16 00:20:39 +02:00
|
|
|
graph_foreach_anode (nodes, it, gn, u) {
|
2015-06-12 11:08:05 +02:00
|
|
|
const RList *neighbours = r_graph_get_neighbours (g->graph, gn);
|
|
|
|
const int exit_edges = r_list_length (neighbours);
|
|
|
|
int nth = 0;
|
2015-06-03 15:25:05 +02:00
|
|
|
|
2015-06-16 00:20:39 +02:00
|
|
|
graph_foreach_anode (neighbours, itn, gv, v) {
|
2015-06-12 11:08:05 +02:00
|
|
|
int cur_nth = nth;
|
|
|
|
if (g->is_callgraph) {
|
|
|
|
/* hack: we don't support more than two exit edges from a node
|
|
|
|
* yet, so set nth to zero, to make every edge appears as the
|
|
|
|
* "true" edge of the node */
|
|
|
|
cur_nth = 0;
|
|
|
|
} else if (exit_edges == 1) {
|
|
|
|
cur_nth = -1;
|
|
|
|
}
|
|
|
|
agraph_print_edge (g, u, v, cur_nth);
|
|
|
|
nth++;
|
2015-06-03 15:25:05 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-12 11:08:05 +02:00
|
|
|
static void agraph_toggle_small_nodes(AGraph *g) {
|
2015-06-03 15:25:05 +02:00
|
|
|
g->is_small_nodes = !g->is_small_nodes;
|
2015-06-03 15:51:08 +02:00
|
|
|
g->need_reload_nodes = R_TRUE;
|
|
|
|
}
|
|
|
|
|
2015-06-12 11:08:05 +02:00
|
|
|
static void agraph_toggle_simple_mode(AGraph *g) {
|
2015-06-03 15:51:08 +02:00
|
|
|
g->is_simple_mode = !g->is_simple_mode;
|
|
|
|
g->need_reload_nodes = R_TRUE;
|
|
|
|
}
|
|
|
|
|
2015-06-12 11:08:05 +02:00
|
|
|
static void agraph_toggle_callgraph(AGraph *g) {
|
2015-06-03 15:51:08 +02:00
|
|
|
g->is_callgraph = !g->is_callgraph;
|
|
|
|
g->need_reload_nodes = R_TRUE;
|
2015-06-03 15:25:05 +02:00
|
|
|
}
|
|
|
|
|
2015-06-12 11:08:05 +02:00
|
|
|
/* reload all the info in the nodes, depending on the type of the graph
|
|
|
|
* (callgraph, CFG, etc.), set the default layout for these nodes and center
|
|
|
|
* the screen on the selected one */
|
|
|
|
static int agraph_reload_nodes(AGraph *g) {
|
2015-06-03 14:59:53 +02:00
|
|
|
int ret;
|
|
|
|
|
2015-06-12 11:08:05 +02:00
|
|
|
r_graph_reset (g->graph);
|
2015-06-03 14:59:53 +02:00
|
|
|
ret = reload_nodes(g);
|
|
|
|
if (!ret)
|
|
|
|
return R_FALSE;
|
2015-06-12 11:08:05 +02:00
|
|
|
agraph_set_layout(g);
|
|
|
|
g->update_seek_on = get_anode(g->curnode);
|
2015-06-03 00:18:33 +02:00
|
|
|
return R_TRUE;
|
2014-07-09 05:05:22 +02:00
|
|
|
}
|
2014-04-29 03:53:48 +02:00
|
|
|
|
2015-06-12 11:08:05 +02:00
|
|
|
static void follow_nth(AGraph *g, int nth) {
|
|
|
|
const RGraphNode *cn = r_graph_nth_neighbour (g->graph, get_gn(g->curnode), nth);
|
|
|
|
if (cn) {
|
|
|
|
history_push (g->history, get_gn (g->curnode));
|
|
|
|
g->curnode = r_graph_node_iter (g->graph, cn->idx);
|
2015-06-03 00:18:33 +02:00
|
|
|
}
|
2015-05-30 01:21:48 +02:00
|
|
|
}
|
|
|
|
|
2015-06-12 11:08:05 +02:00
|
|
|
static void agraph_follow_true(AGraph *g) {
|
2015-06-03 00:18:33 +02:00
|
|
|
follow_nth(g, 0);
|
2015-06-12 11:08:05 +02:00
|
|
|
agraph_update_seek(g, get_anode(g->curnode), R_FALSE);
|
2015-06-03 00:18:33 +02:00
|
|
|
}
|
|
|
|
|
2015-06-12 11:08:05 +02:00
|
|
|
static void agraph_follow_false(AGraph *g) {
|
2015-06-03 00:18:33 +02:00
|
|
|
follow_nth(g, 1);
|
2015-06-12 11:08:05 +02:00
|
|
|
agraph_update_seek(g, get_anode(g->curnode), R_FALSE);
|
2015-02-14 05:10:03 +01:00
|
|
|
}
|
|
|
|
|
2015-06-12 11:08:05 +02:00
|
|
|
/* go back in the history of selected nodes, if we can */
|
|
|
|
static void agraph_undo_node(AGraph *g) {
|
|
|
|
const RGraphNode *p = history_pop (g->history);
|
|
|
|
if (p) {
|
|
|
|
g->curnode = r_graph_node_iter (g->graph, p->idx);
|
|
|
|
agraph_update_seek (g, p->data, R_FALSE);
|
|
|
|
}
|
2015-06-03 00:18:33 +02:00
|
|
|
}
|
|
|
|
|
2015-06-12 11:08:05 +02:00
|
|
|
/* pushes the current node in the history and makes g->curnode the next node in
|
|
|
|
* the order given by r_graph_get_nodes */
|
|
|
|
static void agraph_next_node(AGraph *g) {
|
|
|
|
if (!g->curnode->n) return;
|
|
|
|
history_push (g->history, get_gn(g->curnode));
|
|
|
|
g->curnode = g->curnode->n;
|
|
|
|
agraph_update_seek (g, get_anode(g->curnode), R_FALSE);
|
2015-06-03 11:38:31 +02:00
|
|
|
}
|
|
|
|
|
2015-06-12 11:08:05 +02:00
|
|
|
/* pushes the current node in the history and makes g->curnode the prev node in
|
|
|
|
* the order given by r_graph_get_nodes */
|
|
|
|
static void agraph_prev_node(AGraph *g) {
|
|
|
|
if (!g->curnode->p) return;
|
|
|
|
history_push (g->history, get_gn(g->curnode));
|
|
|
|
g->curnode = g->curnode->p;
|
|
|
|
agraph_update_seek (g, get_anode(g->curnode), R_FALSE);
|
2015-06-03 11:38:31 +02:00
|
|
|
}
|
|
|
|
|
2015-06-12 11:08:05 +02:00
|
|
|
static int agraph_refresh(struct agraph_refresh_data *grd) {
|
|
|
|
char title[TITLE_LEN];
|
|
|
|
AGraph *g = grd->g;
|
|
|
|
const int fs = grd->fs;
|
2015-06-03 14:20:03 +02:00
|
|
|
int h, w = r_cons_get_size (&h);
|
2015-06-03 15:51:08 +02:00
|
|
|
int ret;
|
2015-06-03 12:24:42 +02:00
|
|
|
|
2015-06-12 11:08:05 +02:00
|
|
|
/* allow to change the current function only during debugging */
|
2015-06-03 00:18:33 +02:00
|
|
|
if (g->is_instep && g->core->io->debug) {
|
2015-05-30 01:36:40 +02:00
|
|
|
RAnalFunction *f;
|
2015-06-03 00:18:33 +02:00
|
|
|
r_core_cmd0 (g->core, "sr pc");
|
|
|
|
f = r_anal_get_fcn_in (g->core->anal, g->core->offset, 0);
|
|
|
|
if (f && f != g->fcn) {
|
|
|
|
g->fcn = f;
|
2015-06-03 15:51:08 +02:00
|
|
|
g->need_reload_nodes = R_TRUE;
|
2014-10-23 03:29:05 +02:00
|
|
|
}
|
|
|
|
}
|
2015-06-03 15:51:08 +02:00
|
|
|
|
|
|
|
/* look for any change in the state of the graph
|
|
|
|
* and update what's necessary */
|
|
|
|
if (g->need_reload_nodes) {
|
2015-06-12 11:08:05 +02:00
|
|
|
ret = agraph_reload_nodes(g);
|
2015-06-03 15:51:08 +02:00
|
|
|
if (!ret)
|
|
|
|
return R_FALSE;
|
|
|
|
|
|
|
|
g->need_reload_nodes = R_FALSE;
|
|
|
|
}
|
2015-06-12 11:08:05 +02:00
|
|
|
if (g->update_seek_on) {
|
|
|
|
update_seek(g->can, g->update_seek_on, g->force_update_seek);
|
|
|
|
g->update_seek_on = NULL;
|
2015-06-03 15:51:08 +02:00
|
|
|
g->force_update_seek = R_FALSE;
|
|
|
|
}
|
|
|
|
|
2015-06-11 03:55:15 +02:00
|
|
|
if (fs) {
|
|
|
|
r_cons_clear00 ();
|
|
|
|
}
|
2015-05-30 01:36:40 +02:00
|
|
|
|
2015-06-11 12:23:30 +02:00
|
|
|
h = fs ? h : 1024;
|
|
|
|
r_cons_canvas_resize (g->can, w, h);
|
2015-06-03 00:18:33 +02:00
|
|
|
r_cons_canvas_clear (g->can);
|
2014-08-04 01:29:53 +02:00
|
|
|
|
2015-06-12 11:08:05 +02:00
|
|
|
agraph_print_edges(g);
|
|
|
|
agraph_print_nodes(g);
|
2014-08-04 01:29:53 +02:00
|
|
|
|
2015-06-11 03:55:15 +02:00
|
|
|
if (fs) {
|
|
|
|
(void)G (-g->can->sx, -g->can->sy);
|
|
|
|
snprintf (title, sizeof (title)-1,
|
2015-06-29 17:00:12 +02:00
|
|
|
"[0x%08"PFMT64x"]> %d VV @ %s (nodes %d edges %d zoom %d%%) %s mouse:%s",
|
2015-06-12 11:08:05 +02:00
|
|
|
g->fcn->addr, r_stack_size (g->history), g->fcn->name,
|
2015-06-29 17:00:12 +02:00
|
|
|
g->graph->n_nodes, g->graph->n_edges, zoom, g->is_callgraph?"CG":"BB",
|
2015-06-11 03:55:15 +02:00
|
|
|
mousemodes[mousemode]);
|
|
|
|
W (title);
|
|
|
|
}
|
2014-08-04 01:29:53 +02:00
|
|
|
|
2015-06-11 03:55:15 +02:00
|
|
|
if (fs) {
|
|
|
|
r_cons_canvas_print (g->can);
|
|
|
|
} else {
|
|
|
|
r_cons_canvas_print_region (g->can);
|
|
|
|
}
|
|
|
|
if (fs) {
|
|
|
|
const char *cmdv = r_config_get (g->core->config, "cmd.gprompt");
|
|
|
|
if (cmdv && *cmdv) {
|
|
|
|
r_cons_gotoxy (0, 1);
|
|
|
|
r_core_cmd0 (g->core, cmdv);
|
|
|
|
}
|
2014-10-23 03:29:05 +02:00
|
|
|
}
|
2015-06-04 01:30:33 +02:00
|
|
|
r_cons_flush_nonewline ();
|
2015-06-03 00:18:33 +02:00
|
|
|
return R_TRUE;
|
2014-08-04 01:29:53 +02:00
|
|
|
}
|
2014-09-26 15:40:17 +02:00
|
|
|
|
2015-06-12 11:08:05 +02:00
|
|
|
static void agraph_init(AGraph *g) {
|
2015-06-03 00:18:33 +02:00
|
|
|
g->is_callgraph = R_FALSE;
|
|
|
|
g->is_instep = R_FALSE;
|
|
|
|
g->is_simple_mode = R_TRUE;
|
|
|
|
g->is_small_nodes = R_FALSE;
|
2015-06-03 15:51:08 +02:00
|
|
|
g->need_reload_nodes = R_TRUE;
|
2015-06-12 11:08:05 +02:00
|
|
|
g->curnode = NULL;
|
|
|
|
g->update_seek_on = NULL;
|
2015-06-03 15:51:08 +02:00
|
|
|
g->force_update_seek = R_TRUE;
|
2015-06-12 11:08:05 +02:00
|
|
|
g->history = r_stack_new (INIT_HISTORY_CAPACITY);
|
|
|
|
g->graph = r_graph_new ();
|
2015-06-16 00:20:39 +02:00
|
|
|
g->back_edges = NULL;
|
2014-10-23 03:29:05 +02:00
|
|
|
}
|
|
|
|
|
2015-06-12 11:08:05 +02:00
|
|
|
static AGraph *agraph_new(RCore *core, RConsCanvas *can, RAnalFunction *fcn) {
|
2015-06-29 17:00:12 +02:00
|
|
|
AGraph *g = R_NEW0 (AGraph);
|
|
|
|
if (!g) return NULL;
|
2015-04-11 02:41:02 +03:00
|
|
|
|
2015-06-03 00:18:33 +02:00
|
|
|
g->core = core;
|
|
|
|
g->can = can;
|
|
|
|
g->fcn = fcn;
|
|
|
|
|
2015-06-12 11:08:05 +02:00
|
|
|
agraph_init(g);
|
2015-06-03 00:18:33 +02:00
|
|
|
return g;
|
2014-10-23 02:13:25 +02:00
|
|
|
}
|
|
|
|
|
2015-06-11 12:23:30 +02:00
|
|
|
R_API int r_core_visual_graph(RCore *core, RAnalFunction *_fcn, int is_interactive) {
|
2015-06-12 02:19:58 +02:00
|
|
|
int exit_graph = R_FALSE, is_error = R_FALSE;
|
2015-06-12 11:08:05 +02:00
|
|
|
struct agraph_refresh_data *grd;
|
2015-06-12 02:19:58 +02:00
|
|
|
int okey, key, wheel;
|
2015-06-03 00:18:33 +02:00
|
|
|
RAnalFunction *fcn;
|
2015-06-12 02:19:58 +02:00
|
|
|
const char *key_s;
|
2015-06-03 00:18:33 +02:00
|
|
|
RConsCanvas *can;
|
2015-06-12 11:08:05 +02:00
|
|
|
AGraph *g;
|
2015-02-17 01:28:51 +01:00
|
|
|
int wheelspeed;
|
2015-06-03 00:18:33 +02:00
|
|
|
int w, h;
|
2015-06-12 02:19:58 +02:00
|
|
|
int ret;
|
2015-06-03 00:18:33 +02:00
|
|
|
|
2014-09-26 18:10:33 +02:00
|
|
|
fcn = _fcn? _fcn: r_anal_get_fcn_in (core->anal, core->offset, 0);
|
2014-07-09 05:05:22 +02:00
|
|
|
if (!fcn) {
|
|
|
|
eprintf ("No function in current seek\n");
|
|
|
|
return R_FALSE;
|
2014-04-29 03:53:48 +02:00
|
|
|
}
|
2014-07-09 05:05:22 +02:00
|
|
|
w = r_cons_get_size (&h);
|
2015-06-11 12:23:30 +02:00
|
|
|
can = r_cons_canvas_new (w, h);
|
2015-06-02 23:50:02 +02:00
|
|
|
if (!can) {
|
|
|
|
eprintf ("Cannot create RCons.canvas context\n");
|
|
|
|
return R_FALSE;
|
|
|
|
}
|
2015-02-15 21:34:12 +01:00
|
|
|
can->linemode = 1;
|
2015-02-14 04:50:29 +01:00
|
|
|
can->color = r_config_get_i (core->config, "scr.color");
|
|
|
|
// disable colors in disasm because canvas doesnt supports ansi text yet
|
|
|
|
r_config_set_i (core->config, "scr.color", 0);
|
2015-06-03 00:18:33 +02:00
|
|
|
|
2015-06-12 11:08:05 +02:00
|
|
|
g = agraph_new (core, can, fcn);
|
2015-06-03 00:18:33 +02:00
|
|
|
if (!g) {
|
|
|
|
is_error = R_TRUE;
|
|
|
|
goto err_graph_new;
|
2014-08-03 14:43:44 +02:00
|
|
|
}
|
2014-05-05 03:15:28 +02:00
|
|
|
|
2015-06-12 11:08:05 +02:00
|
|
|
grd = (struct agraph_refresh_data *)malloc (sizeof(*grd));
|
2015-06-11 12:23:30 +02:00
|
|
|
grd->g = g;
|
|
|
|
grd->fs = is_interactive;
|
|
|
|
|
|
|
|
core->cons->event_data = grd;
|
2015-06-12 11:08:05 +02:00
|
|
|
core->cons->event_resize = (RConsEvent)agraph_refresh;
|
2015-06-03 00:18:33 +02:00
|
|
|
|
|
|
|
while (!exit_graph && !is_error) {
|
2015-05-30 01:36:40 +02:00
|
|
|
w = r_cons_get_size (&h);
|
2015-06-12 11:08:05 +02:00
|
|
|
ret = agraph_refresh (grd);
|
2015-06-03 00:18:33 +02:00
|
|
|
if (!ret) {
|
|
|
|
is_error = R_TRUE;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2015-06-11 12:23:30 +02:00
|
|
|
if (!is_interactive) {
|
|
|
|
/* this is a non-interactive ascii-art graph, so exit the loop */
|
|
|
|
r_cons_printf (Color_RESET);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2015-06-04 01:30:33 +02:00
|
|
|
r_cons_show_cursor(R_FALSE);
|
2015-05-30 01:36:40 +02:00
|
|
|
wheel = r_config_get_i (core->config, "scr.wheel");
|
|
|
|
if (wheel)
|
|
|
|
r_cons_enable_mouse (R_TRUE);
|
|
|
|
|
|
|
|
// r_core_graph_inputhandle()
|
|
|
|
okey = r_cons_readchar ();
|
|
|
|
key = r_cons_arrow_to_hjkl (okey);
|
2015-06-28 02:23:36 +02:00
|
|
|
wheelspeed = r_config_get_i (core->config, "scr.wheelspeed");
|
2015-05-30 01:36:40 +02:00
|
|
|
|
|
|
|
switch (key) {
|
2015-06-29 17:00:12 +02:00
|
|
|
case '-':
|
|
|
|
zoom-=10;
|
|
|
|
if (zoom<0) zoom = 0;
|
|
|
|
update_node_dimension(g->graph, g->is_small_nodes);
|
|
|
|
agraph_set_layout (g);
|
|
|
|
// agraph_update_seek (g, get_anode(g->curnode), R_TRUE);
|
|
|
|
break;
|
|
|
|
case '+':
|
|
|
|
zoom+=10;
|
|
|
|
update_node_dimension (g->graph, g->is_small_nodes);
|
|
|
|
agraph_set_layout (g);
|
|
|
|
// agraph_update_seek (g, get_anode(g->curnode), R_TRUE);
|
|
|
|
// g->is_instep = R_TRUE;
|
|
|
|
break;
|
|
|
|
case '=':
|
|
|
|
case '|':
|
|
|
|
{ // TODO: edit
|
|
|
|
const char *buf = NULL;
|
|
|
|
const char *cmd = r_config_get (core->config, "cmd.gprompt");
|
|
|
|
r_line_set_prompt ("cmd.gprompt> ");
|
|
|
|
core->cons->line->contents = strdup (cmd);
|
|
|
|
buf = r_line_readline ();
|
|
|
|
core->cons->line->contents = NULL;
|
|
|
|
r_config_set (core->config, "cmd.gprompt", buf);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'O':
|
|
|
|
agraph_toggle_simple_mode(g);
|
|
|
|
break;
|
|
|
|
case 'V':
|
|
|
|
agraph_toggle_callgraph(g);
|
|
|
|
break;
|
|
|
|
case 'z':
|
|
|
|
g->is_instep = R_TRUE;
|
|
|
|
key_s = r_config_get (core->config, "key.s");
|
|
|
|
if (key_s && *key_s) {
|
|
|
|
r_core_cmd0 (core, key_s);
|
|
|
|
} else {
|
|
|
|
if (r_config_get_i (core->config, "cfg.debug"))
|
|
|
|
r_core_cmd0 (core, "ds;.dr*");
|
|
|
|
else
|
|
|
|
r_core_cmd0 (core, "aes;.dr*");
|
|
|
|
}
|
|
|
|
ret = agraph_reload_nodes(g);
|
|
|
|
if (!ret)
|
|
|
|
is_error = R_TRUE;
|
|
|
|
break;
|
|
|
|
case 'Z':
|
|
|
|
if (okey == 27) {
|
|
|
|
agraph_prev_node(g);
|
|
|
|
} else {
|
|
|
|
// 'Z'
|
2015-06-03 00:18:33 +02:00
|
|
|
g->is_instep = R_TRUE;
|
2015-06-29 17:00:12 +02:00
|
|
|
if (r_config_get_i (core->config, "cfg.debug"))
|
|
|
|
r_core_cmd0 (core, "dso;.dr*");
|
|
|
|
else
|
|
|
|
r_core_cmd0 (core, "aeso;.dr*");
|
|
|
|
|
2015-06-12 11:08:05 +02:00
|
|
|
ret = agraph_reload_nodes(g);
|
2015-06-03 00:18:33 +02:00
|
|
|
if (!ret)
|
|
|
|
is_error = R_TRUE;
|
2015-06-29 17:00:12 +02:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'x':
|
|
|
|
if (r_core_visual_xrefs_x (core))
|
|
|
|
exit_graph = R_TRUE;
|
|
|
|
break;
|
|
|
|
case 'X':
|
|
|
|
if (r_core_visual_xrefs_X (core))
|
|
|
|
exit_graph = R_TRUE;
|
|
|
|
break;
|
|
|
|
case 9: // tab
|
|
|
|
agraph_next_node(g);
|
|
|
|
break;
|
|
|
|
case '?':
|
|
|
|
r_cons_clear00 ();
|
|
|
|
r_cons_printf ("Visual Ascii Art graph keybindings:\n"
|
|
|
|
" . - center graph to the current node\n"
|
|
|
|
" C - toggle scr.color\n"
|
|
|
|
" hjkl - move node\n"
|
|
|
|
" HJKL - scroll canvas\n"
|
|
|
|
" tab - select next node\n"
|
|
|
|
" TAB - select previous node\n"
|
|
|
|
" t/f - follow true/false edges\n"
|
|
|
|
" e - toggle edge-lines style (diagonal/square)\n"
|
|
|
|
" O - toggle disasm mode\n"
|
|
|
|
" p - toggle mini-graph\n"
|
|
|
|
" u - select previous node\n"
|
|
|
|
" V - toggle basicblock / call graphs\n"
|
|
|
|
" x/X - jump to xref/ref\n"
|
|
|
|
" z/Z - step / step over\n"
|
|
|
|
" +/- - zoom in/out\n"
|
|
|
|
" R - relayout\n");
|
|
|
|
r_cons_flush ();
|
|
|
|
r_cons_any_key (NULL);
|
|
|
|
break;
|
|
|
|
case 'R':
|
|
|
|
case 'r':
|
|
|
|
agraph_set_layout (g);
|
|
|
|
break;
|
|
|
|
case 'j':
|
|
|
|
if (r_cons_singleton()->mouse_event) {
|
|
|
|
switch (mousemode) {
|
|
|
|
case 0: // canvas-y
|
|
|
|
can->sy += wheelspeed;
|
|
|
|
break;
|
|
|
|
case 1: // canvas-x
|
|
|
|
can->sx += wheelspeed;
|
|
|
|
break;
|
|
|
|
case 2: // node-y
|
|
|
|
get_anode(g->curnode)->y += wheelspeed;
|
|
|
|
break;
|
|
|
|
case 3: // node-x
|
|
|
|
get_anode(g->curnode)->x += wheelspeed;
|
|
|
|
break;
|
2015-06-03 00:18:33 +02:00
|
|
|
}
|
2015-06-29 17:00:12 +02:00
|
|
|
} else {
|
|
|
|
get_anode(g->curnode)->y++;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'k':
|
|
|
|
if (r_cons_singleton()->mouse_event) {
|
|
|
|
switch (mousemode) {
|
|
|
|
case 0: // canvas-y
|
|
|
|
can->sy -= wheelspeed;
|
|
|
|
break;
|
|
|
|
case 1: // canvas-x
|
|
|
|
can->sx -= wheelspeed;
|
|
|
|
break;
|
|
|
|
case 2: // node-y
|
|
|
|
get_anode(g->curnode)->y -= wheelspeed;
|
|
|
|
break;
|
|
|
|
case 3: // node-x
|
|
|
|
get_anode(g->curnode)->x -= wheelspeed;
|
|
|
|
break;
|
2015-06-03 00:18:33 +02:00
|
|
|
}
|
2015-06-29 17:00:12 +02:00
|
|
|
} else {
|
|
|
|
get_anode(g->curnode)->y--;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'm':
|
|
|
|
mousemode++;
|
|
|
|
if (!mousemodes[mousemode])
|
|
|
|
mousemode = 0;
|
|
|
|
break;
|
|
|
|
case 'M':
|
|
|
|
mousemode--;
|
|
|
|
if (mousemode<0)
|
|
|
|
mousemode = 3;
|
|
|
|
break;
|
|
|
|
case 'h': get_anode(g->curnode)->x--; break;
|
|
|
|
case 'l': get_anode(g->curnode)->x++; break;
|
|
|
|
|
|
|
|
case '0': can->sx = can->sy = 0; break;
|
|
|
|
case 'K': can->sy -= wheelspeed; break;
|
|
|
|
case 'J': can->sy += wheelspeed; break;
|
|
|
|
case 'H': can->sx -= wheelspeed; break;
|
|
|
|
case 'L': can->sx += wheelspeed; break;
|
|
|
|
|
|
|
|
case 'e':
|
|
|
|
can->linemode = !!!can->linemode;
|
|
|
|
break;
|
|
|
|
case 'p':
|
|
|
|
agraph_toggle_small_nodes(g);
|
|
|
|
break;
|
|
|
|
case 'u':
|
|
|
|
agraph_undo_node(g);
|
|
|
|
break;
|
|
|
|
case '.':
|
|
|
|
zoom = 100;
|
|
|
|
update_node_dimension (g->graph, g->is_small_nodes);
|
|
|
|
agraph_set_layout (g);
|
|
|
|
//agraph_update_seek (g, get_anode (g->curnode), R_TRUE);
|
|
|
|
g->is_instep = R_TRUE;
|
|
|
|
break;
|
|
|
|
case 't':
|
|
|
|
agraph_follow_true (g);
|
|
|
|
break;
|
|
|
|
case 'f':
|
|
|
|
agraph_follow_false (g);
|
|
|
|
break;
|
|
|
|
case '/':
|
|
|
|
r_core_cmd0 (core, "?i highlight;e scr.highlight=`?y`");
|
|
|
|
break;
|
|
|
|
case ':':
|
|
|
|
core->vmode = R_FALSE;
|
|
|
|
r_core_visual_prompt_input (core);
|
|
|
|
core->vmode = R_TRUE;
|
|
|
|
break;
|
|
|
|
case 'C':
|
|
|
|
can->color = !!!can->color;
|
|
|
|
//r_config_swap (core->config, "scr.color");
|
|
|
|
// refresh graph
|
|
|
|
break;
|
|
|
|
case -1: // EOF
|
|
|
|
case 'q':
|
|
|
|
exit_graph = R_TRUE;
|
|
|
|
break;
|
|
|
|
case 27: // ESC
|
|
|
|
if (r_cons_readchar () == 91) {
|
|
|
|
if (r_cons_readchar () == 90) {
|
|
|
|
agraph_prev_node (g);
|
2015-06-03 00:18:33 +02:00
|
|
|
}
|
2015-06-29 17:00:12 +02:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
eprintf ("Key %d\n", key);
|
|
|
|
//sleep (1);
|
|
|
|
break;
|
2014-04-29 03:53:48 +02:00
|
|
|
}
|
|
|
|
}
|
2015-05-30 01:36:40 +02:00
|
|
|
|
2015-06-12 11:08:05 +02:00
|
|
|
agraph_free(g);
|
2015-06-03 00:18:33 +02:00
|
|
|
err_graph_new:
|
2015-06-11 03:55:15 +02:00
|
|
|
r_config_set_i (core->config, "scr.color", can->color);
|
2015-06-15 20:44:37 +03:00
|
|
|
free (can);
|
2015-06-03 00:18:33 +02:00
|
|
|
return !is_error;
|
2014-04-29 03:53:48 +02:00
|
|
|
}
|