/* Copyright radare2 2014-2015 - Author: pancake */ #include #include static const char *mousemodes[] = { "canvas-y", "canvas-x", "node-y", "node-x", NULL }; static int mousemode = 0; #define BORDER 3 #define BORDER_WIDTH 4 #define BORDER_HEIGHT 3 #define MARGIN_TEXT_X 2 #define MARGIN_TEXT_Y 2 #define HORIZONTAL_NODE_SPACING 12 #define VERTICAL_NODE_SPACING 4 #define MIN_NODE_WIDTH 18 #define MIN_NODE_HEIGTH BORDER_HEIGHT #define INIT_HISTORY_CAPACITY 16 #define TITLE_LEN 128 #define DEFAULT_SPEED 1 #define SMALLNODE_TEXT "[____]" #define SMALLNODE_TEXT_CUR "[_@@_]" #define ZOOM_STEP 10 #define ZOOM_DEFAULT 100 #define history_push(stack, x) (r_stack_push (stack, (void *)(size_t)x)) #define history_pop(stack) ((RGraphNode *)r_stack_pop (stack)) #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)) #define hash_get_rlist(sdb,k) ((RList *)(size_t)hash_get (sdb, k)) #define hash_get_int(sdb,k) ((int)hash_get (sdb, k)) #define get_anode(gn) ((ANode *)gn->data) #define graph_foreach_anode(list, it, pos, anode) \ if (list) for (it = list->head; it && (pos = it->data) && (pos) && (anode = (ANode *)pos->data); it = it->n) struct len_pos_t { int len; int pos; }; struct dist_t { const RGraphNode *from; const RGraphNode *to; int dist; }; struct layer_t { int n_nodes; RGraphNode **nodes; int position; int height; }; typedef struct ascii_node { int x; int y; int w; int h; ut64 addr; int layer; int pos_in_layer; char *text; int is_dummy; int is_reversed; int class; } ANode; typedef struct ascii_graph { RCore *core; RConsCanvas *can; RAnalFunction *fcn; RGraph *graph; const RGraphNode *curnode; int is_callgraph; int is_instep; int is_simple_mode; int is_small_nodes; int zoom; int movspeed; RStack *history; ANode *update_seek_on; int need_reload_nodes; int need_set_layout; int need_update_dim; int force_update_seek; /* layout algorithm info */ RList *back_edges; RList *long_edges; struct layer_t *layers; int n_layers; RList *dists; /* RList */ } AGraph; struct agraph_refresh_data { AGraph *g; int fs; }; #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) static ANode *ascii_node_new (int is_dummy) { ANode *res = R_NEW0 (ANode); if (!res) return NULL; res->layer = -1; res->pos_in_layer = -1; res->is_dummy = is_dummy; res->is_reversed = R_FALSE; res->class = -1; return res; } static void update_node_dimension(const RGraph *g, int is_small, int zoom) { const RList *nodes = r_graph_get_nodes (g); RGraphNode *gn; RListIter *it; ANode *n; graph_foreach_anode (nodes, it, gn, n) { if (is_small) { n->h = 0; n->w = strlen (SMALLNODE_TEXT); } else { n->w = r_str_bounds (n->text, &n->h); n->w += BORDER_WIDTH; n->h += BORDER_HEIGHT; /* scale node by zoom */ n->w = R_MAX (MIN_NODE_WIDTH, (n->w * zoom) / 100); n->h = R_MAX (MIN_NODE_HEIGTH, (n->h * zoom) / 100); } } } static void small_ANode_print(const AGraph *g, const ANode *n, int cur) { char title[TITLE_LEN]; if (!G (n->x + 2, n->y - 1)) return; if (cur) { W(SMALLNODE_TEXT_CUR); (void)G (-g->can->sx, -g->can->sy + 2); snprintf (title, sizeof (title) - 1, "0x%08"PFMT64x":", n->addr); W (title); (void)G (-g->can->sx, -g->can->sy + 3); W (n->text); } else { W(SMALLNODE_TEXT); } return; } static void normal_ANode_print(const AGraph *g, const ANode *n, int cur) { unsigned int center_x = 0, center_y = 0; unsigned int delta_x = 0, delta_txt_x = 0; unsigned int delta_y = 0, delta_txt_y = 0; char title[TITLE_LEN]; char *text; int x, y; #if SHOW_OUT_OF_SCREEN_NODES x = n->x + g->can->sx; y = n->y + n->h + g->can->sy; if (x < 0 || x > g->can->w) return; if (y < 0 || y > g->can->h) return; #endif x = n->x + g->can->sx; y = n->y + g->can->sy; if (x + MARGIN_TEXT_X < 0) delta_x = -(x + MARGIN_TEXT_X); if (x + n->w < -MARGIN_TEXT_X) return; if (y < -1) delta_y = R_MIN (n->h - BORDER_HEIGHT - 1, -y - MARGIN_TEXT_Y); /* print the title */ if (cur) { snprintf (title, sizeof (title)-1, "[0x%08"PFMT64x"]", n->addr); } else { snprintf (title, sizeof (title)-1, " 0x%08"PFMT64x" ", n->addr); } if (delta_x < strlen(title) && G(n->x + MARGIN_TEXT_X + delta_x, n->y + 1)) W(title + delta_x); /* print the body */ if (g->zoom > ZOOM_DEFAULT) { center_x = (g->zoom - ZOOM_DEFAULT) / 20; center_y = (g->zoom - ZOOM_DEFAULT) / 30; delta_txt_x = R_MIN (delta_x, center_x); delta_txt_y = R_MIN (delta_y, center_y); } if (G(n->x + MARGIN_TEXT_X + delta_x + center_x - delta_txt_x, n->y + MARGIN_TEXT_Y + delta_y + center_y - delta_txt_y)) { unsigned int text_x = center_x >= delta_x ? 0 : delta_x - center_x; unsigned int text_y = center_y >= delta_y ? 0 : delta_y - center_y; unsigned int text_h = BORDER_HEIGHT >= n->h ? 0 : n->h - BORDER_HEIGHT; if (g->zoom < ZOOM_DEFAULT) text_h--; if (text_y <= text_h - 1) { text = r_str_crop (n->text, text_x, text_y, n->w - BORDER_WIDTH, text_h); if (text) { W (text); if (g->zoom < ZOOM_DEFAULT) W ("\n"); free (text); } else { W (n->text); } } /* print some dots when the text is cropped because of zoom */ if (text_y <= text_h && g->zoom < ZOOM_DEFAULT) { char *dots = "..."; if (delta_x < strlen(dots)) { dots += delta_x; W (dots); } } } // TODO: check if node is traced or not and hsow proper color // This info must be stored inside ANode* from RCore* if (cur) { B1 (n->x, n->y, n->w, n->h); } else { B (n->x, n->y, n->w, n->h); } } static int **get_crossing_matrix (const RGraph *g, const struct layer_t layers[], int maxlayer, int i, int from_up, 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 && from_up) { for (j = 0; j < layers[i - 1].n_nodes; ++j) { const 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) { const 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) { const 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 && !from_up) { for (j = 0; j < layers[i].n_nodes; ++j) { const RGraphNode *gj = layers[i].nodes[j]; const RList *neigh = r_graph_get_neighbours (g, gj); const ANode *ak, *aj = (ANode *)gj->data; RGraphNode *gk; RListIter *itk; graph_foreach_anode (neigh, itk, gk, ak) { int s; for (s = 0; s < layers[i].n_nodes; ++s) { const RGraphNode *gs = layers[i].nodes[s]; const RList *neigh_s; RGraphNode *gt; RListIter *itt; const ANode *at, *as = (ANode *)gs->data; if (gs == gj) continue; neigh_s = r_graph_get_neighbours (g, gs); graph_foreach_anode (neigh_s, itt, gt, at) { if (at->pos_in_layer < ak->pos_in_layer) m[aj->pos_in_layer][as->pos_in_layer]++; } } } } } 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 (const RGraph *g, const struct layer_t layers[], int maxlayer, int i, int from_up) { int **cross_matrix; RGraphNode *u, *v; const 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, from_up, &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, const RGraphVisitor *vis) { const 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, const RGraphNode *n) { int res = 0; while ((n = hash_get_rnode (path, n)) != NULL) { res++; } return res; } static void set_layer (const RGraphNode *from, const RGraphNode *to, const RGraphVisitor *vis) { Sdb *path = (Sdb *)vis->data; int bdepth, adepth; adepth = get_depth (path, from); bdepth = get_depth (path, to); if (adepth + 1 > bdepth) hash_set (path, to, from); } static void view_dummy (RGraphNode *from, RGraphNode *to, const RGraphVisitor *vis) { const ANode *a = (ANode *)from->data; const 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 = { NULL, NULL, NULL, NULL, NULL, NULL }; const RGraphEdge *e; const 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 (const AGraph *g) { RGraphVisitor layer_vis = { NULL, NULL, NULL, NULL, NULL, NULL }; Sdb *path_layers = sdb_new0 (); const RGraphNode *gn; const RListIter *it; ANode *n; 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); } sdb_free (path_layers); } static int find_edge (const RGraphEdge *a, const RGraphEdge *b) { return a->from == b->to && a->to == b->from ? 0 : 1; } static int is_reversed (const AGraph *g, const RGraphEdge *e) { return r_list_find (g->back_edges, e, (RListComparator)find_edge) ? R_TRUE : R_FALSE; } /* add dummy nodes when there are edges that span multiple layers */ static void create_dummy_nodes (AGraph *g) { RGraphVisitor dummy_vis = { NULL, NULL, NULL, NULL, NULL, NULL }; const RListIter *it; const 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) { const ANode *from = (ANode *)e->from->data; const 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 = ascii_node_new (R_TRUE); RGraphNode *dummy; if (!n) return; n->layer = from->layer + i; n->is_reversed = is_reversed (g, e); n->w = 1; 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) { const RList *nodes = r_graph_get_nodes (g->graph); RGraphNode *gn; const RListIter *it; ANode *n; int i; /* 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++; 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; } 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 (const 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, R_TRUE); } while (cross_changed); do { cross_changed = R_FALSE; for (i = g->n_layers - 1; i >= 0; --i) cross_changed |= layer_sweep (g->graph, g->layers, g->n_layers, i, R_FALSE); } while (cross_changed); } static int find_dist (const struct dist_t *a, const struct dist_t *b) { return a->from == b->from && a->to == b->to ? 0 : 1; } /* returns the distance between two nodes */ /* if the distance between two nodes were explicitly set, returns that; * otherwise calculate the distance of two nodes on the same layer */ static int dist_nodes (const AGraph *g, const RGraphNode *a, const RGraphNode *b) { struct dist_t d; const ANode *aa, *ab; RListIter *it; int res = 0; if (g->dists) { d.from = a; d.to = b; it = r_list_find (g->dists, &d, (RListComparator)find_dist); if (it) { struct dist_t *old = (struct dist_t *)r_list_iter_get_data (it); return old->dist; } } aa = (ANode *)a->data; ab = (ANode *)b->data; if (aa->layer == ab->layer) { int i; res = 0; for (i = aa->pos_in_layer; i < ab->pos_in_layer; ++i) { const RGraphNode *cur = g->layers[aa->layer].nodes[i]; const RGraphNode *next = g->layers[aa->layer].nodes[i + 1]; const ANode *anext = (ANode *)next->data; const ANode *acur = (ANode *)cur->data; int found = R_FALSE; if (g->dists) { d.from = cur; d.to = next; it = r_list_find (g->dists, &d, (RListComparator)find_dist); if (it) { struct dist_t *old = (struct dist_t *)r_list_iter_get_data (it); res += old->dist; found = R_TRUE; } } if (!found) { int space = acur->is_dummy && anext->is_dummy ? 1 : HORIZONTAL_NODE_SPACING; res += acur->w / 2 + anext->w / 2 + space; } } } return res; } /* explictly set the distance between two nodes on the same layer */ static void set_dist_nodes (const AGraph *g, int l, int cur, int next) { struct dist_t *d; const RGraphNode *vi, *vip; const ANode *avi, *avip; if (!g->dists) return; d = R_NEW (struct dist_t); vi = g->layers[l].nodes[cur]; vip = g->layers[l].nodes[next]; avi = (ANode *)vi->data; avip = (ANode *)vip->data; d->from = vi; d->to = vip; d->dist = avip->x - avi->x; r_list_push (g->dists, d); } static int is_valid_pos (const AGraph *g, int l, int pos) { return pos >= 0 && pos < g->layers[l].n_nodes; } /* computes the set of vertical classes in the graph */ /* if v is an original node, L(v) = { v } * if v is a dummy node, L(v) is the set of all the dummies node that belongs * to the same long edge */ static Sdb *compute_vertical_nodes (const AGraph *g) { Sdb *res = sdb_new0 (); int i, j; for (i = 0; i < g->n_layers; ++i) { for (j = 0; j < g->layers[i].n_nodes; ++j) { RGraphNode *gn = g->layers[i].nodes[j]; const RList *Ln = hash_get_rlist (res, gn); const ANode *an = (ANode *)gn->data; if (!Ln) { RList *vert = r_list_new (); hash_set (res, gn, vert); if (an->is_dummy) { RGraphNode *next = gn; const ANode *anext = (ANode *)next->data; while (next && anext->is_dummy) { r_list_append (vert, next); next = r_graph_nth_neighbour (g->graph, next, 0); if (!next) break; anext = (ANode *)next->data; } } else { r_list_append (vert, gn); } } } } return res; } /* computes left or right classes, used to place dummies node */ /* classes respect three properties: * - v E C * - w E C => L(v) is a subset of C * - w E C, the s+(w) exists and is not in any class yet => s+(w) E C */ static RList **compute_classes (const AGraph *g, Sdb *v_nodes, int is_left, int *n_classes) { int i, j, c; RList **res = R_NEWS0 (RList *, g->n_layers); RGraphNode *gn; const RListIter *it; ANode *n; graph_foreach_anode (r_graph_get_nodes (g->graph), it, gn, n) { n->class = -1; } for (i = 0; i < g->n_layers; ++i) { c = i; for (j = is_left ? 0 : g->layers[i].n_nodes - 1; (is_left && j < g->layers[i].n_nodes) || (!is_left && j >= 0); j = is_left ? j + 1 : j - 1) { const RGraphNode *gj = g->layers[i].nodes[j]; const ANode *aj = (ANode *)gj->data; if (aj->class == -1) { const RList *laj = hash_get_rlist (v_nodes, gj); if (!res[c]) res[c] = r_list_new (); graph_foreach_anode (laj, it, gn, n) { r_list_append (res[c], gn); n->class = c; } } else { c = aj->class; } } } if (n_classes) *n_classes = g->n_layers; return res; } static int cmp_dist (const size_t a, const size_t b) { return (int)a < (int)b; } static RGraphNode *get_sibling (const AGraph *g, const ANode *n, int is_left, int is_adjust_class) { RGraphNode *res = NULL; int pos; if ((is_left && is_adjust_class) || (!is_left && !is_adjust_class)) pos = n->pos_in_layer + 1; else pos = n->pos_in_layer - 1; if (is_valid_pos (g, n->layer, pos)) res = g->layers[n->layer].nodes[pos]; return res; } static int adjust_class_val (const AGraph *g, const RGraphNode *gn, const RGraphNode *sibl, Sdb *res, int is_left) { if (is_left) return hash_get_int (res, sibl) - hash_get_int (res, gn) - dist_nodes (g, gn, sibl); else return hash_get_int (res, gn) - hash_get_int (res, sibl) - dist_nodes (g, sibl, gn); } /* adjusts the position of previously placed left/right classes */ /* tries to place classes as close as possible */ static void adjust_class (const AGraph *g, int is_left, RList **classes, Sdb *res, int c) { const RGraphNode *gn; const RListIter *it; const ANode *an; int dist, v, is_first = R_TRUE; graph_foreach_anode (classes[c], it, gn, an) { const RGraphNode *sibling; const ANode *sibl_anode; sibling = get_sibling (g, an, is_left, R_TRUE); if (!sibling) continue; sibl_anode = (ANode *)sibling->data; if (sibl_anode->class == c) continue; v = adjust_class_val (g, gn, sibling, res, is_left); dist = is_first ? v : R_MIN (dist, v); is_first = R_FALSE; } if (is_first) { RList *heap = r_list_new (); int len; graph_foreach_anode (classes[c], it, gn, an) { const RList *neigh = r_graph_all_neighbours (g->graph, gn); const RGraphNode *gk; const RListIter *itk; const ANode *ak; graph_foreach_anode (neigh, itk, gk, ak) { if (ak->class < c) r_list_append (heap, (void *)(size_t)(ak->x - an->x)); } } len = r_list_length (heap); if (len == 0) { dist = 0; } else { r_list_sort (heap, (RListComparator)cmp_dist); dist = (int)(size_t)r_list_get_n (heap, len / 2); } r_list_free (heap); } graph_foreach_anode (classes[c], it, gn, an) { int old_val = hash_get_int (res, gn); int new_val = is_left ? old_val + dist : old_val - dist; hash_set (res, gn, new_val); } } static int place_nodes_val (const AGraph *g, const RGraphNode *gn, const RGraphNode *sibl, Sdb *res, int is_left) { if (is_left) return hash_get_int (res, sibl) + dist_nodes (g, sibl, gn); else return hash_get_int (res, sibl) - dist_nodes (g, gn, sibl); } static int place_nodes_sel_p (int newval, int oldval, int is_first, int is_left) { if (is_first) return newval; if (is_left) return R_MAX (oldval, newval); else return R_MIN (oldval, newval); } /* places left/right the nodes of a class */ static void place_nodes (const AGraph *g, const RGraphNode *gn, int is_left, Sdb *v_nodes, RList **classes, Sdb *res, Sdb *placed) { const RList *lv = hash_get_rlist (v_nodes, gn); int p, v, is_first = R_TRUE; const RGraphNode *gk; const RListIter *itk; const ANode *ak; graph_foreach_anode (lv, itk, gk, ak) { const RGraphNode *sibling; const ANode *sibl_anode; sibling = get_sibling (g, ak, is_left, R_FALSE); if (!sibling) continue; sibl_anode = (ANode *)sibling->data; if (ak->class == sibl_anode->class) { if (!hash_get (placed, sibling)) place_nodes (g, sibling, is_left, v_nodes, classes, res, placed); v = place_nodes_val (g, gk, sibling, res, is_left); p = place_nodes_sel_p (v, p, is_first, is_left); is_first = R_FALSE; } } if (is_first) p = is_left ? 0 : 50; graph_foreach_anode (lv, itk, gk, ak) { hash_set (res, gk, p); hash_set (placed, gk, R_TRUE); } } /* computes the position to the left/right of all the nodes */ static Sdb *compute_pos (const AGraph *g, int is_left, Sdb *v_nodes) { Sdb *res, *placed; RList **classes; int n_classes, i; classes = compute_classes (g, v_nodes, is_left, &n_classes); if (!classes) return NULL; res = sdb_new0 (); placed = sdb_new0 (); for (i = 0; i < n_classes; ++i) { const RGraphNode *gn; const RListIter *it; r_list_foreach (classes[i], it, gn) { if (!hash_get_rnode (placed, gn)) { place_nodes (g, gn, is_left, v_nodes, classes, res, placed); } } adjust_class (g, is_left, classes, res, i); } sdb_free (placed); for (i = 0; i < n_classes; ++i) { if (classes[i]) r_list_free (classes[i]); } free (classes); return res; } /* calculates position of all nodes, but in particular dummies nodes */ /* computes two different placements (called "left"/"right") and set the final * position of each node to the average of the values in the two placements */ static void place_dummies (const AGraph *g) { const RList *nodes; Sdb *xminus, *xplus, *vertical_nodes; const RGraphNode *gn; const RListIter *it; ANode *n; vertical_nodes = compute_vertical_nodes (g); if (!vertical_nodes) return; xminus = compute_pos (g, R_TRUE, vertical_nodes); if (!xminus) goto xminus_err; xplus = compute_pos (g, R_FALSE, vertical_nodes); if (!xplus) goto xplus_err; nodes = r_graph_get_nodes (g->graph); graph_foreach_anode (nodes, it, gn, n) { n->x = (hash_get_int (xminus, gn) + hash_get_int (xplus, gn)) / 2; } sdb_free (xplus); xplus_err: sdb_free (xminus); xminus_err: sdb_free (vertical_nodes); } static RGraphNode *get_right_dummy (const AGraph *g, const RGraphNode *n) { const ANode *an = (ANode *)n->data; int k, layer = an->layer; for (k = an->pos_in_layer + 1; k < g->layers[layer].n_nodes; ++k) { RGraphNode *gk = g->layers[layer].nodes[k]; const ANode *ak = (ANode *)gk->data; if (ak->is_dummy) return gk; } return NULL; } /* returns true if all nodes on the right of n are dummies and reversed */ static int all_reversed_right (const AGraph *g, const RGraphNode *n) { const ANode *an; int k, layer; if (!n) return R_FALSE; an = (ANode *)n->data; layer = an->layer; for (k = an->pos_in_layer; k < g->layers[layer].n_nodes; ++k) { const RGraphNode *gk = g->layers[layer].nodes[k]; const ANode *ak = (ANode *)gk->data; if (!ak->is_reversed) return R_FALSE; } return R_TRUE; } static void adjust_directions (const AGraph *g, int i, int from_up, Sdb *D, Sdb *P) { const RGraphNode *vm = NULL, *wm = NULL; const ANode *vma = NULL, *wma = NULL; int j, d = from_up ? 1 : -1; if (i + d < 0 || i + d >= g->n_layers) return; for (j = 0; j < g->layers[i + d].n_nodes; ++j) { const RGraphNode *wp, *vp = g->layers[i + d].nodes[j]; const ANode *wpa, *vpa = (ANode *)vp->data; if (!vpa->is_dummy) continue; if (from_up) wp = r_list_get_n (r_graph_innodes (g->graph, vp), 0); else wp = r_graph_nth_neighbour (g->graph, vp, 0); wpa = (ANode *)wp->data; if (!wpa->is_dummy) continue; if (vm) { int p = hash_get_int (P, wm); int k; for (k = wma->pos_in_layer + 1; k < wpa->pos_in_layer; ++k) { const RGraphNode *w = g->layers[wma->layer].nodes[k]; const ANode *aw = (ANode *)w->data; if (aw->is_dummy) p &= hash_get_int (P, w); } if (p) { hash_set (D, vm, from_up); for (k = vma->pos_in_layer + 1; k < vpa->pos_in_layer; ++k) { const RGraphNode *v = g->layers[vma->layer].nodes[k]; const ANode *av = (ANode *)v->data; if (av->is_dummy) hash_set (D, v, from_up); } } } vm = vp; wm = wp; vma = (ANode *)vm->data; wma = (ANode *)wm->data; } } /* find a placement for a single node */ static void place_single (const AGraph *g, int l, const RGraphNode *bm, const RGraphNode *bp, int from_up, int va) { const RGraphNode *gk, *v = g->layers[l].nodes[va]; const ANode *ak; ANode *av = (ANode *)v->data; const RList *neigh; const RListIter *itk; int len; if (from_up) neigh = r_graph_innodes (g->graph, v); else neigh = r_graph_get_neighbours (g->graph, v); len = r_list_length (neigh); if (len == 0) return; int sum_x = 0; graph_foreach_anode (neigh, itk, gk, ak) { if (ak->is_reversed) { len--; continue; } sum_x += ak->x; } if (len == 0) return; av->x = sum_x / len; if (bm) { const ANode *bma = (ANode *)bm->data; av->x = R_MAX (av->x, bma->x + dist_nodes (g, bm, v)); } if (bp && !all_reversed_right (g, bp)) { const ANode *bpa = (ANode *)bp->data; av->x = R_MIN (av->x, bpa->x - dist_nodes (g, v, bp)); } } static int RM_listcmp (const struct len_pos_t *a, const struct len_pos_t *b) { return a->pos < b->pos; } static int RP_listcmp (const struct len_pos_t *a, const struct len_pos_t *b) { return a->pos >= b->pos; } static void collect_changes (const AGraph *g, int l, const RGraphNode *b, int from_up, int s, int e, RList *list, int is_left) { const RGraphNode *vt = g->layers[l].nodes[e - 1]; const RGraphNode *vtp = g->layers[l].nodes[s]; RListComparator lcmp; struct len_pos_t *cx; int i; lcmp = is_left ? (RListComparator)RM_listcmp : (RListComparator)RP_listcmp; for (i = is_left ? s : e - 1; (is_left && i < e) || (!is_left && i >= s); i = is_left ? i + 1 : i - 1) { const RGraphNode *v, *vi = g->layers[l].nodes[i]; const ANode *av, *avi = (ANode *)vi->data; const RList *neigh; const RListIter *it; int c = 0; if (from_up) neigh = r_graph_innodes (g->graph, vi); else neigh = r_graph_get_neighbours (g->graph, vi); graph_foreach_anode (neigh, it, v, av) { if ((is_left && av->x >= avi->x) || (!is_left && av->x <= avi->x)) { c++; } else { cx = R_NEW (struct len_pos_t); c--; cx->len = 2; cx->pos = av->x; if (is_left) cx->pos += dist_nodes (g, vi, vt); else cx->pos -= dist_nodes (g, vtp, vi); r_list_add_sorted (list, cx, lcmp); } } cx = R_NEW (struct len_pos_t); cx->len = c; cx->pos = avi->x; if (is_left) cx->pos += dist_nodes (g, vi, vt); else cx->pos -= dist_nodes (g, vtp, vi); r_list_add_sorted (list, cx, lcmp); } if (b) { const ANode *ab = (ANode *)b->data; cx = R_NEW (struct len_pos_t); cx->len = is_left ? INT_MAX : INT_MIN; cx->pos = ab->x; if (is_left) cx->pos += dist_nodes (g, b, vt); else cx->pos -= dist_nodes (g, vtp, b); r_list_add_sorted (list, cx, lcmp); } } static void combine_sequences (const AGraph *g, int l, const RGraphNode *bm, const RGraphNode *bp, int from_up, int a, int r) { RList *Rm = r_list_new (), *Rp = r_list_new (); const RGraphNode *vt, *vtp; ANode *at, *atp; int rm, rp, t, m, i; t = (a + r) / 2; vt = g->layers[l].nodes[t - 1]; vtp = g->layers[l].nodes[t]; at = (ANode *)vt->data; atp = (ANode *)vtp->data; collect_changes (g, l, bm, from_up, a, t, Rm, R_TRUE); collect_changes (g, l, bp, from_up, t, r, Rp, R_FALSE); rm = rp = 0; m = dist_nodes (g, vt, vtp); while (atp->x - at->x < m) { if (rm < rp) { if (r_list_empty (Rm)) { at->x = atp->x - m; } else { struct len_pos_t *cx = (struct len_pos_t *)r_list_pop (Rm); rm = rm + cx->len; at->x = R_MAX (cx->pos, atp->x - m); free (cx); } } else { if (r_list_empty (Rp)) { atp->x = at->x + m; } else { struct len_pos_t *cx = (struct len_pos_t *)r_list_pop (Rp); rp = rp + cx->len; atp->x = R_MIN (cx->pos, at->x + m); free (cx); } } } for (i = t - 2; i >= a; --i) { const RGraphNode *gv = g->layers[l].nodes[i]; ANode *av = (ANode *)gv->data; av->x = R_MIN (av->x, at->x - dist_nodes (g, gv, vt)); } for (i = t + 1; i < r; ++i) { const RGraphNode *gv = g->layers[l].nodes[i]; ANode *av = (ANode *)gv->data; av->x = R_MAX (av->x, atp->x + dist_nodes (g, vtp, gv)); } } /* places a sequence of consecutive original nodes */ /* it tries to minimize the distance between each node in the sequence and its * neighbours in the "previous" layer. Those neighbours are considered as * "fixed". The previous layer depends on the direction used during the layers * traversal */ static void place_sequence (const AGraph *g, int l, const RGraphNode *bm, const RGraphNode *bp, int from_up, int va, int vr) { int vt; if (vr == va + 1) { place_single (g, l, bm, bp, from_up, va); } else if (vr > va + 1) { vt = (vr + va) / 2; place_sequence (g, l, bm, bp, from_up, va, vt); place_sequence (g, l, bm, bp, from_up, vt, vr); combine_sequences (g, l, bm, bp, from_up, va, vr); } } /* if all nodes to the right of right_pos are reversed, shift them to make the * placement feasible and larger */ static void shift_right_dummies (const AGraph *g, int l, int right_pos) { const RGraphNode *vr, *bp; const ANode *ar, *abp; if (!is_valid_pos (g, l, right_pos)) return; vr = g->layers[l].nodes[right_pos]; ar = (ANode *)vr->data; if (ar->is_dummy) return; if (!is_valid_pos (g, l, right_pos + 1)) return; bp = g->layers[l].nodes[right_pos + 1]; abp = (ANode *)bp->data; if (!abp->is_dummy) return; if (all_reversed_right (g, bp)) { int k; for (k = right_pos + 1; k < g->layers[l].n_nodes; ++k) { RGraphNode *vk = g->layers[l].nodes[k]; ANode *ak = (ANode *)vk->data; int newv = ar->x + dist_nodes (g, vr, vk); if (newv > ak->x) { const RGraphNode *vj = vk; ANode *aj = ak; while (vj && aj && aj->is_dummy && aj->is_reversed) { aj->x = newv; vj = r_list_get_n (r_graph_innodes (g->graph, vj), 0); aj = (ANode *)vj->data; } vj = vk; aj = ak; while (vj && aj && aj->is_dummy && aj->is_reversed) { aj->x = newv; vj = r_graph_nth_neighbour (g->graph, vj, 0); aj = (ANode *)vj->data; } } } } } /* finds the placements of nodes while traversing the graph in the given * direction */ /* places all the sequences of consecutive original nodes in each layer. */ static void original_traverse_l (const AGraph *g, Sdb *D, Sdb *P, int from_up) { int i, k, va, vr; for (i = from_up ? 0 : g->n_layers - 1; (from_up && i < g->n_layers) || (!from_up && i >= 0); i = from_up ? i + 1 : i - 1) { int j; const RGraphNode *bm = NULL; const ANode *bma = NULL; j = 0; while (j < g->layers[i].n_nodes && !bm) { const RGraphNode *gn = g->layers[i].nodes[j]; const ANode *an = (ANode *)gn->data; if (an->is_dummy) { va = 0; vr = j; bm = gn; bma = an; } j++; } if (!bm) { va = 0; vr = g->layers[i].n_nodes; } place_sequence (g, i, NULL, bm, from_up, va, vr); for (k = va; k < vr - 1; ++k) set_dist_nodes (g, i, k, k + 1); if (is_valid_pos (g, i, vr - 1) && bm) set_dist_nodes (g, i, vr - 1, bma->pos_in_layer); shift_right_dummies (g, i, vr - 1); while (bm) { const RGraphNode *bp = get_right_dummy (g, bm); const ANode *bpa = NULL; bma = (ANode *)bm->data; if (!bp) { va = bma->pos_in_layer + 1; vr = g->layers[bma->layer].n_nodes; place_sequence (g, i, bm, NULL, from_up, va, vr); for (k = va; k < vr - 1; ++k) set_dist_nodes (g, i, k, k + 1); if (is_valid_pos (g, i, va)) set_dist_nodes (g, i, bma->pos_in_layer, va); shift_right_dummies (g, i, vr - 1); } else if (hash_get_int (D, bm) == from_up) { bpa = (ANode *)bp->data; va = bma->pos_in_layer + 1; vr = bpa->pos_in_layer; place_sequence (g, i, bm, bp, from_up, va, vr); hash_set (P, bm, R_TRUE); shift_right_dummies (g, i, vr - 1); } bm = bp; } adjust_directions (g, i, from_up, D, P); } } /* computes a final position of original nodes, considering dummies nodes as * fixed */ /* set the node placements traversing the graph downward and then upward */ static void place_original (AGraph *g) { const RList *nodes = r_graph_get_nodes (g->graph); Sdb *D, *P; const RGraphNode *gn; const RListIter *itn; const ANode *an; D = sdb_new0 (); P = sdb_new0 (); g->dists = r_list_new (); g->dists->free = (RListFree)free; graph_foreach_anode (nodes, itn, gn, an) { if (!an->is_dummy) continue; const RGraphNode *right_v = get_right_dummy (g, gn); if (right_v) { const ANode *right = (ANode *)right_v->data; hash_set (D, gn, 0); int dt_eq = right->x - an->x == dist_nodes (g, gn, right_v); hash_set (P, gn, dt_eq); } } original_traverse_l (g, D, P, R_TRUE); original_traverse_l (g, D, P, R_FALSE); r_list_free (g->dists); g->dists = NULL; sdb_free (P); sdb_free (D); } static void restore_original_edges (const AGraph *g) { const RListIter *it; const RGraphEdge *e; 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 (const AGraph *g) { RGraphNode *gn; const RListIter *it; const ANode *n; RList *toremove = r_list_new (); graph_foreach_anode (r_graph_get_nodes (g->graph), it, gn, n) { if (n->is_dummy) { r_list_append (toremove, gn); } } r_list_foreach (toremove, it, gn) { r_graph_del_node (g->graph, gn); } r_list_free (toremove); } /* 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 * 6) restore the original graph, with long edges and cycles */ static void set_layout_bb(AGraph *g) { int i, j, k; 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++) { int rh = 0; for (j = 0; j < g->layers[i].n_nodes; ++j) { const ANode *n = (ANode *)(g->layers[i].nodes[j]->data); if (n->h > rh) rh = n->h; } g->layers[i].height = rh; } /* x-coordinate assignment: algorithm based on: * A Fast Layout Algorithm for k-Level Graphs * by C. Buchheim, M. Junger, S. Leipert */ place_dummies (g); place_original (g); /* 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; } } } /* finalize x coordinate */ 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->x -= n->w / 2; } } 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); } 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; int y = 5, x = 20; graph_foreach_anode (nodes, it, gn, n) { // wrap to width 'w' if (prev_n && n->x < prev_n->x) { y += 10; x = 0; } n->x = x; n->y = prev_n ? y : 2; x += 30; prev_n = n; } } /* build the RGraph inside the AGraph g, starting from the Basic Blocks */ static int get_bbnodes(AGraph *g) { RAnalBlock *bb; RListIter *iter; Sdb *g_nodes = sdb_new0 (); if (!g_nodes) return R_FALSE; r_list_foreach (g->fcn->bbs, iter, bb) { RGraphNode *gn; ANode *node; if (bb->addr == UT64_MAX) continue; node = ascii_node_new (R_FALSE); if (!node) { sdb_free (g_nodes); return R_FALSE; } if (g->is_simple_mode) { node->text = r_core_cmd_strf (g->core, "pI %d @ 0x%08"PFMT64x, bb->size, bb->addr); }else { node->text = r_core_cmd_strf (g->core, "pDi %d @ 0x%08"PFMT64x, bb->size, bb->addr); } node->addr = bb->addr; gn = r_graph_add_node (g->graph, node); if (!gn) { sdb_free (g_nodes); return R_FALSE; } hash_set (g_nodes, bb->addr, gn); } r_list_foreach (g->fcn->bbs, iter, bb) { RGraphNode *u, *v; if (bb->addr == UT64_MAX) continue; u = hash_get_rnode (g_nodes, bb->addr); if (bb->jump != UT64_MAX) { v = hash_get_rnode (g_nodes, bb->jump); r_graph_add_edge (g->graph, u, v); } if (bb->fail != UT64_MAX) { v = hash_get_rnode (g_nodes, bb->fail); r_graph_add_edge (g->graph, u, v); } } sdb_free (g_nodes); return R_TRUE; } /* build the RGraph inside the AGraph g, starting from the Call Graph * information */ static int get_cgnodes(AGraph *g) { #if FCN_OLD Sdb *g_nodes = sdb_new0 (); RGraphNode *fcn_gn; RListIter *iter; RAnalRef *ref; ANode *node; char *code; node = ascii_node_new (R_FALSE); if (!node) { sdb_free (g_nodes); return R_FALSE; } node->text = strdup (""); node->addr = g->fcn->addr; node->x = 10; node->y = 3; fcn_gn = r_graph_add_node (g->graph, node); if (!fcn_gn) { sdb_free (g_nodes); return R_FALSE; } hash_set (g_nodes, g->fcn->addr, fcn_gn); r_list_foreach (g->fcn->refs, iter, ref) { /* XXX: something is broken, why there are duplicated * nodes here?! goto check fcn->refs!! */ /* avoid dups wtf */ RGraphNode *gn; gn = hash_get_rnode (g_nodes, ref->addr); if (gn) continue; RFlagItem *fi = r_flag_get_at (g->core->flags, ref->addr); node = ascii_node_new (R_FALSE); if (!node) { sdb_free (g_nodes); return R_FALSE; } if (fi) { node->text = strdup (fi->name); node->text = r_str_concat (node->text, ":\n"); } else { node->text = strdup (""); } code = r_core_cmd_strf (g->core, "pi 4 @ 0x%08"PFMT64x, ref->addr); node->text = r_str_concat (node->text, code); node->text = r_str_concat (node->text, "...\n"); node->addr = ref->addr; node->x = 10; node->y = 10; free (code); gn = r_graph_add_node (g->graph, node); if (!gn) { sdb_free (g_nodes); return R_FALSE; } hash_set (g_nodes, ref->addr, gn); r_graph_add_edge (g->graph, fcn_gn, gn); } sdb_free (g_nodes); #else eprintf ("Must be sdbized\n"); #endif return R_TRUE; } static int reload_nodes(AGraph *g) { int ret; if (g->is_callgraph) { ret = get_cgnodes(g); if (!ret) return R_FALSE; } else { ret = get_bbnodes(g); if (!ret) return R_FALSE; } update_node_dimension(g->graph, g->is_small_nodes, g->zoom); return R_TRUE; } static void update_seek(RConsCanvas *can, ANode *n, int force) { 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); } } static int is_near (const ANode *n, int x, int y, int is_next) { if (is_next) return (n->y == y && n->x > x) || n->y > y; else return (n->y == y && n->x < x) || n->y < y; } static const RGraphNode *find_near_of (const AGraph *g, const RGraphNode *cur, int is_next) { /* XXX: it's slow */ const RList *nodes = r_graph_get_nodes (g->graph); const RListIter *it; const RGraphNode *gn, *resgn = NULL; const ANode *n, *acur = cur ? get_anode(cur) : NULL; int default_v = is_next ? INT_MIN : INT_MAX; int start_y = acur ? acur->y : default_v; int start_x = acur ? acur->x : default_v; graph_foreach_anode (nodes, it, gn, n) { if (is_near (n, start_x, start_y, is_next)) { const ANode *resn; if (!resgn) { resgn = gn; continue; } resn = get_anode (resgn); if ((is_next && resn->y > n->y) || (!is_next && resn->y < n->y)) resgn = gn; else if ((is_next && resn->y == n->y && resn->x > n->x) || (!is_next && resn->y == n->y && resn->x < n->x)) resgn = gn; } } if (!resgn && cur) resgn = find_near_of (g, NULL, is_next); return resgn; } static void agraph_set_layout(AGraph *g) { if (g->is_callgraph) set_layout_callgraph(g); else set_layout_bb(g); g->curnode = find_near_of (g, NULL, R_TRUE); } /* 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; g->force_update_seek = force; } static void agraph_free(AGraph *g) { r_graph_free (g->graph); r_stack_free (g->history); free(g); } static void agraph_print_node(AGraph *g, ANode *n) { const int cur = get_anode (g->curnode) == n; if (g->is_small_nodes) small_ANode_print(g, n, cur); else normal_ANode_print(g, n, cur); } static void agraph_print_nodes(AGraph *g) { const RList *nodes = r_graph_get_nodes (g->graph); RGraphNode *gn; RListIter *it; ANode *n; graph_foreach_anode (nodes, it, gn, n) { if (gn != g->curnode) agraph_print_node(g, n); } /* draw current node now to make it appear on top */ agraph_print_node (g, get_anode(g->curnode)); } /* 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) { 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; } if (nth > 1) nth = 1; 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; } } 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; graph_foreach_anode (nodes, it, gn, u) { const RList *neighbours = r_graph_get_neighbours (g->graph, gn); const int exit_edges = r_list_length (neighbours); int nth = 0; graph_foreach_anode (neighbours, itn, gv, v) { 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++; } } } static void agraph_toggle_small_nodes(AGraph *g) { g->is_small_nodes = !g->is_small_nodes; g->need_update_dim = R_TRUE; g->need_set_layout = R_TRUE; } static void agraph_toggle_simple_mode(AGraph *g) { g->is_simple_mode = !g->is_simple_mode; g->need_reload_nodes = R_TRUE; } static void agraph_toggle_callgraph(AGraph *g) { g->is_callgraph = !g->is_callgraph; g->need_reload_nodes = R_TRUE; } static void agraph_set_zoom (AGraph *g, int v) { g->is_small_nodes = v <= 0; g->zoom = R_MAX (0, v); g->need_update_dim = R_TRUE; g->need_set_layout = R_TRUE; } /* 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) { int ret; r_graph_reset (g->graph); ret = reload_nodes(g); if (!ret) return R_FALSE; agraph_set_layout(g); g->update_seek_on = get_anode(g->curnode); return R_TRUE; } static void follow_nth(AGraph *g, int nth) { const RGraphNode *cn = r_graph_nth_neighbour (g->graph, g->curnode, nth); if (cn) { history_push (g->history, g->curnode); g->curnode = cn; } } static void agraph_follow_true(AGraph *g) { follow_nth(g, 0); agraph_update_seek(g, get_anode(g->curnode), R_FALSE); } static void agraph_follow_false(AGraph *g) { follow_nth(g, 1); agraph_update_seek(g, get_anode(g->curnode), R_FALSE); } /* 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 = p; agraph_update_seek (g, p->data, R_FALSE); } } /* 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) { history_push (g->history, g->curnode); g->curnode = find_near_of (g, g->curnode, R_TRUE); agraph_update_seek (g, get_anode(g->curnode), R_FALSE); } /* 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) { history_push (g->history, g->curnode); g->curnode = find_near_of (g, g->curnode, R_FALSE); agraph_update_seek (g, get_anode(g->curnode), R_FALSE); } static int agraph_refresh(struct agraph_refresh_data *grd) { char title[TITLE_LEN]; AGraph *g = grd->g; const int fs = grd->fs; int h, w = r_cons_get_size (&h); int ret; /* allow to change the current function only during debugging */ if (g->is_instep && g->core->io->debug) { RAnalFunction *f; 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; g->need_reload_nodes = R_TRUE; } } /* look for any change in the state of the graph * and update what's necessary */ if (g->need_reload_nodes) { ret = agraph_reload_nodes(g); if (!ret) return R_FALSE; g->need_reload_nodes = R_FALSE; } if (g->need_update_dim) { update_node_dimension (g->graph, g->is_small_nodes, g->zoom); g->need_update_dim = R_FALSE; } if (g->need_set_layout) { agraph_set_layout (g); g->need_set_layout = R_FALSE; } if (g->update_seek_on) { update_seek(g->can, g->update_seek_on, g->force_update_seek); g->update_seek_on = NULL; g->force_update_seek = R_FALSE; } if (fs) { r_cons_clear00 (); } h = fs ? h : 1024; r_cons_canvas_resize (g->can, w, h); r_cons_canvas_clear (g->can); agraph_print_edges(g); agraph_print_nodes(g); if (fs) { (void)G (-g->can->sx, -g->can->sy); snprintf (title, sizeof (title)-1, "[0x%08"PFMT64x"]> %d VV @ %s (nodes %d edges %d zoom %d%%) %s mouse:%s movements-speed:%d", g->fcn->addr, r_stack_size (g->history), g->fcn->name, g->graph->n_nodes, g->graph->n_edges, g->zoom, g->is_callgraph?"CG":"BB", mousemodes[mousemode], g->movspeed); W (title); } 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); } } r_cons_flush_nonewline (); return R_TRUE; } static void agraph_toggle_speed (AGraph *g) { int alt = r_config_get_i (g->core->config, "graph.scroll"); g->movspeed = g->movspeed == DEFAULT_SPEED ? alt : DEFAULT_SPEED; } static void agraph_init(AGraph *g) { g->is_callgraph = R_FALSE; g->is_instep = R_FALSE; g->is_simple_mode = R_TRUE; g->is_small_nodes = R_FALSE; g->need_reload_nodes = R_TRUE; g->force_update_seek = R_TRUE; g->history = r_stack_new (INIT_HISTORY_CAPACITY); g->graph = r_graph_new (); g->zoom = ZOOM_DEFAULT; g->movspeed = DEFAULT_SPEED; } static AGraph *agraph_new(RCore *core, RConsCanvas *can, RAnalFunction *fcn) { AGraph *g = R_NEW0 (AGraph); if (!g) return NULL; g->core = core; g->can = can; g->fcn = fcn; agraph_init(g); return g; } R_API int r_core_visual_graph(RCore *core, RAnalFunction *_fcn, int is_interactive) { int exit_graph = R_FALSE, is_error = R_FALSE; struct agraph_refresh_data *grd; int okey, key, wheel; RAnalFunction *fcn; const char *key_s; RConsCanvas *can; AGraph *g; int wheelspeed; int w, h; int ret; fcn = _fcn? _fcn: r_anal_get_fcn_in (core->anal, core->offset, 0); if (!fcn) { eprintf ("No function in current seek\n"); return R_FALSE; } w = r_cons_get_size (&h); can = r_cons_canvas_new (w, h); if (!can) { eprintf ("Cannot create RCons.canvas context\n"); return R_FALSE; } can->linemode = 1; 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); g = agraph_new (core, can, fcn); if (!g) { is_error = R_TRUE; goto err_graph_new; } grd = R_NEW (struct agraph_refresh_data); grd->g = g; grd->fs = is_interactive; core->cons->event_data = grd; core->cons->event_resize = (RConsEvent)agraph_refresh; while (!exit_graph && !is_error) { w = r_cons_get_size (&h); ret = agraph_refresh (grd); if (!ret) { is_error = R_TRUE; break; } if (!is_interactive) { /* this is a non-interactive ascii-art graph, so exit the loop */ r_cons_printf (Color_RESET); break; } r_cons_show_cursor(R_FALSE); 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); wheelspeed = r_config_get_i (core->config, "scr.wheelspeed"); switch (key) { case '-': agraph_set_zoom (g, g->zoom - ZOOM_STEP); break; case '+': agraph_set_zoom (g, g->zoom + ZOOM_STEP); break; case '0': agraph_set_zoom (g, ZOOM_DEFAULT); agraph_update_seek (g, get_anode (g->curnode), R_TRUE); break; 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' g->is_instep = R_TRUE; if (r_config_get_i (core->config, "cfg.debug")) r_core_cmd0 (core, "dso;.dr*"); else r_core_cmd0 (core, "aeso;.dr*"); ret = agraph_reload_nodes(g); if (!ret) is_error = R_TRUE; } 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" " w - toggle between movements speed 1 and graph.scroll\n" " x/X - jump to xref/ref\n" " z/Z - step / step over\n" " +/-/0 - zoom in/out/default\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; } } else { get_anode(g->curnode)->y += g->movspeed; } 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; } } else { get_anode(g->curnode)->y -= g->movspeed; } 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 -= g->movspeed; break; case 'l': get_anode(g->curnode)->x += g->movspeed; break; case 'K': can->sy -= g->movspeed; break; case 'J': can->sy += g->movspeed; break; case 'H': can->sx -= g->movspeed; break; case 'L': can->sx += g->movspeed; break; case 'e': can->linemode = !!!can->linemode; break; case 'p': agraph_toggle_small_nodes (g); agraph_update_seek (g, get_anode (g->curnode), R_TRUE); break; case 'u': agraph_undo_node(g); break; case '.': 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 'w': agraph_toggle_speed (g); 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); } } break; default: break; } } free (grd); agraph_free(g); err_graph_new: r_config_set_i (core->config, "scr.color", can->color); free (can); return !is_error; }