/* Chrysalide - Outil d'analyse de fichiers binaires
* gtkgraphview.c - affichage de morceaux de code sous forme graphique
*
* Copyright (C) 2009-2014 Cyrille Bagard
*
* This file is part of Chrysalide.
*
* OpenIDA is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* OpenIDA is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Foobar. If not, see .
*/
#include "gtkgraphview.h"
#include
#include "gtkblockview.h"
#include "gtkbufferview.h"
#include "gtkviewpanel-int.h"
#include "graph/layout.h"
#include "../analysis/blocks/flow.h"
#include "../gui/editem.h"
/* Composant d'affichage sous forme graphique (instance) */
struct _GtkGraphView
{
GtkViewPanel parent; /* A laisser en premier */
GtkWidget *support; /* Support des vues en bloc */
GBinRoutine *routine; /* Routine en cours d'affichage*/
segcnt_list *highlighted; /* Segments mis en évidence */
GtkBufferView **children; /* Liste des sous-blocs */
GtkAllocation *allocs; /* Emplacements prévisibles */
size_t children_count; /* Taille de cette liste */
GGraphLayout *layout; /* Disposition en graphique */
};
/* Composant d'affichage sous forme graphique (classe) */
struct _GtkGraphViewClass
{
GtkViewPanelClass parent; /* A laisser en premier */
};
/* Initialise la classe générique des graphiques de code. */
static void gtk_graph_view_class_init(GtkGraphViewClass *);
/* Initialise une instance d'afficheur de code en graphique. */
static void gtk_graph_view_init(GtkGraphView *);
/* Indique les dimensions de travail du composant d'affichage. */
static void gtk_graph_view_compute_requested_size(GtkGraphView *, gint *, gint *);
/* Réagit à un défilement chez une barre associée au composant. */
static void gtk_graph_view_adjust_scroll_value(GtkGraphView *, GtkAdjustment *, GtkOrientation);
/* Met à jour l'affichage de la vue sous forme graphique. */
static gboolean gtk_graph_view_draw(GtkWidget *, cairo_t *, GtkGraphView *);
/* Actualise les besoins internes avant un redimensionnement. */
static void gtk_graph_view_prepare_resize(GtkGraphView *);
/* Réagit à la sélection externe d'une adresse. */
static void gtk_graph_view_define_main_address(GtkGraphView *, const vmpa2t *);
/* Indique la position d'affichage d'une adresse donnée. */
static bool gtk_graph_view_get_address_coordinates(const GtkGraphView *, const vmpa2t *addr, gint *x, gint *y, ScrollPositionTweak tweak);
/* Déplace le curseur à un emplacement défini. */
static bool gtk_graph_view_move_caret_to(GtkGraphView *, gint, gint);
/* Place en cache un rendu destiné à l'aperçu graphique rapide. */
static void gtk_graph_view_cache_glance(GtkGraphView *, cairo_t *, const GtkAllocation *, double);
/* Supprime tout contenu de l'afficheur de code en graphique. */
static void gtk_graph_view_reset(GtkGraphView *);
/* Définit la liste complète des éléments du futur graphique. */
static GtkBufferView **gtk_graph_view_load_nodes(GtkGraphView *, GLoadedBinary *, const GBinRoutine *);
/* Notifie un changement de surbrillance au sein d'un noeud. */
static void gtk_graph_view_changed_highlights(GtkBlockView *, GtkGraphView *);
/* Notifie une incapacité de déplacement au sein d'un noeud. */
static void gtk_graph_view_reach_caret_limit(GtkBufferView *, GdkScrollDirection, GtkGraphView *);
/* Détermine le type du composant d'affichage en graphique. */
G_DEFINE_TYPE(GtkGraphView, gtk_graph_view, GTK_TYPE_VIEW_PANEL)
/******************************************************************************
* *
* Paramètres : class = classe GTK à initialiser. *
* *
* Description : Initialise la classe générique des graphiques de code. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void gtk_graph_view_class_init(GtkGraphViewClass *class)
{
GtkViewPanelClass *panel_class; /* Classe parente */
panel_class = GTK_VIEW_PANEL_CLASS(class);
panel_class->compute_size = (compute_requested_size_fc)gtk_graph_view_compute_requested_size;
panel_class->adjust = (adjust_scroll_value_fc)gtk_graph_view_adjust_scroll_value;
panel_class->define = (define_address_fc)gtk_graph_view_define_main_address;
panel_class->get_coordinates = (get_addr_coordinates_fc)gtk_graph_view_get_address_coordinates;
panel_class->move_caret_to = (move_caret_to_fc)gtk_graph_view_move_caret_to;
panel_class->cache_glance = (cache_glance_fc)gtk_graph_view_cache_glance;
}
/******************************************************************************
* *
* Paramètres : view = instance GTK à initialiser. *
* *
* Description : Initialise une instance d'afficheur de code en graphique. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void gtk_graph_view_init(GtkGraphView *view)
{
GtkViewPanel *viewpanel; /* Instance parente #1 */
//GtkBinView *binview; /* Instance parente #2 */
viewpanel = GTK_VIEW_PANEL(view);
viewpanel->resize = (prepare_resize_fc)gtk_graph_view_prepare_resize;
////////viewpanel->get_coordinates = (get_addr_coordinates_fc)gtk_graph_view_get_address_coordinates;
//binview = GTK_BIN_VIEW(view);
//binview->set_lines = (set_rendering_lines_fc)gtk_graph_view_set_rendering_lines;
//binview->define_address = (define_main_address_fc)gtk_graph_view_define_main_address;
//binview->get_coordinates = (get_addr_coordinates_fc)gtk_graph_view_get_address_coordinates;
view->support = gtk_fixed_new();
gtk_widget_set_has_window(view->support, TRUE);
gtk_widget_set_can_focus(view->support, TRUE);
g_signal_connect(G_OBJECT(view->support), "draw",
G_CALLBACK(gtk_graph_view_draw), view);
gtk_widget_show(view->support);
gtk_fixed_put(GTK_FIXED(view), view->support, 0, 0);
//view->mutex = g_mutex_new();
//view->cond = g_cond_new();
}
/******************************************************************************
* *
* Paramètres : view = composant GTK à consulter. *
* width = largeur requise à renseigner ou NULL. [OUT] *
* height = hauteur requise à renseigner ou NULL. [OUT] *
* *
* Description : Indique les dimensions de travail du composant d'affichage. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void gtk_graph_view_compute_requested_size(GtkGraphView *view, gint *width, gint *height)
{
GtkRequisition requisition; /* Taille requise */
if (width != NULL && view->layout != NULL)
{
g_graph_layout_size_request(view->layout, &requisition);
*width = requisition.width;
}
if (height != NULL && view->layout != NULL)
{
g_graph_layout_size_request(view->layout, &requisition);
*height = requisition.height;
}
}
/******************************************************************************
* *
* Paramètres : view = panneau d'affichage concerné. *
* adj = défilement dont une valeur a changé. *
* orientation = indication sur le défilement à traiter. *
* *
* Description : Réagit à un défilement chez une barre associée au composant. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void gtk_graph_view_adjust_scroll_value(GtkGraphView *view, GtkAdjustment *adj, GtkOrientation orientation)
{
gint fake_x; /* Abscisse virtuelle */
gint fake_y; /* Ordonnée virtuelle */
fake_x = 0;
fake_y = 0;
gtk_view_panel_compute_fake_coord(GTK_VIEW_PANEL(view), &fake_x, &fake_y);
gtk_fixed_move(GTK_FIXED(view), view->support, fake_x, -fake_y);
}
/******************************************************************************
* *
* Paramètres : widget = composant GTK à redessiner. *
* cr = contexte graphique associé à l'événement. *
* view = support maître à consulter. *
* *
* Description : Met à jour l'affichage de la vue sous forme graphique. *
* *
* Retour : FALSE pour poursuivre la propagation de l'événement. *
* *
* Remarques : - *
* *
******************************************************************************/
static gboolean gtk_graph_view_draw(GtkWidget *widget, cairo_t *cr, GtkGraphView *view)
{
if (view->layout != NULL)
g_graph_layout_draw(view->layout, cr, true);
return FALSE;
}
/******************************************************************************
* *
* Paramètres : view = composant GTK à mettre à jour. *
* *
* Description : Actualise les besoins internes avant un redimensionnement. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void gtk_graph_view_prepare_resize(GtkGraphView *view)
{
size_t i; /* Boucle de parcours */
if (view->children_count > 0)
{
for (i = 0; i < view->children_count; i++)
gtk_widget_queue_resize(GTK_WIDGET(view->children[i]));
g_graph_layout_refresh(view->layout);
g_graph_layout_place(view->layout, view);
change_editor_items_current_view_content(GTK_VIEW_PANEL(view));
}
}
/******************************************************************************
* *
* Paramètres : view = composant GTK à mettre à jour. *
* addr = adresse sélectionnée de manière externe. *
* *
* Description : Réagit à la sélection externe d'une adresse. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void gtk_graph_view_define_main_address(GtkGraphView *view, const vmpa2t *addr)
{
bool need_update; /* Mise à jour du contenu ? */
const mrange_t *range; /* Couverture courante */
GExeFormat *format; /* Type de fichier chargé */
GBinRoutine **routines; /* Liste des routines trouvées */
size_t routines_count; /* Nombre de ces routines */
size_t i; /* Boucle de parcours */
gint width; /* Largeur idéale du composant */
gint height; /* Hauteur idéale du composant */
if (view->routine == NULL)
need_update = true;
else
{
range = g_binary_routine_get_range(view->routine);
need_update = !mrange_contains_addr(range, addr);
}
if (need_update)
{
gtk_graph_view_reset(view);
format = g_loaded_binary_get_format(GTK_VIEW_PANEL(view)->binary);
routines = g_binary_format_get_routines(G_BIN_FORMAT(format), &routines_count);
for (i = 0; i < routines_count; i++)
{
range = g_binary_routine_get_range(routines[i]);
if (mrange_contains_addr(range, addr))
{
view->routine = routines[i];
g_object_ref(G_OBJECT(view->routine));
view->highlighted = init_segment_content_list();
view->children = gtk_graph_view_load_nodes(view, GTK_VIEW_PANEL(view)->binary,
routines[i]);
view->allocs = (GtkAllocation *)calloc(view->children_count,
sizeof(GtkAllocation));
view->layout = g_graph_layout_new(g_binary_routine_get_basic_blocks(view->routine),
view->children, view->children_count);
g_graph_layout_place(view->layout, view);
break;
}
}
gtk_graph_view_compute_requested_size(view, &width, &height);
gtk_widget_size_allocate(GTK_WIDGET(view), (GtkAllocation []){ { 0, 0, width, height } });
gtk_widget_size_allocate(view->support, (GtkAllocation []){ { 0, 0, width, height } });
change_editor_items_current_view_content(GTK_VIEW_PANEL(view));
}
}
/******************************************************************************
* *
* Paramètres : view = composant GTK à consulter. *
* addr = adresse à présenter à l'écran. *
* x = position horizontale au sein du composant. [OUT] *
* y = position verticale au sein du composant. [OUT] *
* tweak = adaptation finale à effectuer. *
* *
* Description : Indique la position d'affichage d'une adresse donnée. *
* *
* Retour : true si l'adresse fait partie du composant, false sinon. *
* *
* Remarques : - *
* *
******************************************************************************/
static bool gtk_graph_view_get_address_coordinates(const GtkGraphView *view, const vmpa2t *addr, gint *x, gint *y, ScrollPositionTweak tweak)
{
/* TODO */
return false;
}
/******************************************************************************
* *
* Paramètres : view = composant GTK à manipuler. *
* x = abscisse proposée pour le nouvel emplacement. *
* y = ordonnée proposée pour le nouvel emplacement. *
* *
* Description : Déplace le curseur à un emplacement défini. *
* *
* Retour : true si un traitement a été effectué, false sinon. *
* *
* Remarques : - *
* *
******************************************************************************/
static bool gtk_graph_view_move_caret_to(GtkGraphView *view, gint x, gint y)
{
bool result; /* Bilan à retourner */
size_t i; /* Boucle de parcours */
GtkViewPanel *pview; /* Autre vision d'enfance */
gint sub_x; /* Abscisse relative à l'enfant*/
gint sub_y; /* Ordonnée relative à l'enfant*/
result = false;
for (i = 0; i < view->children_count; i++)
{
if (x < view->allocs[i].x || x >= (view->allocs[i].x + view->allocs[i].width)) continue;
if (y < view->allocs[i].y || y >= (view->allocs[i].y + view->allocs[i].height)) continue;
pview = GTK_VIEW_PANEL(view->children[i]);
sub_x = x - view->allocs[i].x;
sub_y = y - view->allocs[i].y;
result = GTK_VIEW_PANEL_GET_CLASS(pview)->move_caret_to(pview, sub_x, sub_y);
break;
}
return result;
}
/******************************************************************************
* *
* Paramètres : view = composant GTK à manipuler. *
* cairo = assistant pour la création de rendus. *
* area = taille de la surface réduite à disposition. *
* scale = échelle vis à vis de la taille réelle. *
* *
* Description : Place en cache un rendu destiné à l'aperçu graphique rapide. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void gtk_graph_view_cache_glance(GtkGraphView *view, cairo_t *cairo, const GtkAllocation *area, double scale)
{
size_t i; /* Boucle de parcours */
GtkAllocation sub_area; /* Emplacement réservé */
for (i = 0; i < view->children_count; i++)
{
sub_area.x = view->allocs[i].x * scale;
sub_area.y = view->allocs[i].y * scale;
sub_area.width = view->allocs[i].width * scale + 1;
sub_area.height = view->allocs[i].height * scale + 1;
gtk_view_panel_cache_glance(GTK_VIEW_PANEL(view->children[i]), cairo, &sub_area, scale);
}
cairo_scale(cairo, scale, scale);
if (view->layout != NULL)
g_graph_layout_draw(view->layout, cairo, false);
}
/******************************************************************************
* *
* Paramètres : - *
* *
* Description : Crée un nouveau composant pour l'affichage en graphique. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
GtkWidget *gtk_graph_view_new(void)
{
return g_object_new(GTK_TYPE_GRAPH_VIEW, NULL);
}
/******************************************************************************
* *
* Paramètres : view = composant GTK à mettre à jour. *
* widget = composant GTK à insérer. *
* x = abscisse du point d'insertion. *
* y = ordonnée du point d'insertion. *
* *
* Description : Place une vue sous forme de bloc dans le graphique. *
* *
* Retour : Plutôt que de redéfinir *toutes* les méthodes de *
* GtkContainer, on étend ! *
* *
* Remarques : - *
* *
******************************************************************************/
void gtk_graph_view_put(GtkGraphView *view, GtkWidget *widget, const GtkAllocation *alloc)
{
size_t i; /* Boucle de parcours */
GtkWidget *parent; /* Parent en cas de réajustemt.*/
for (i = 0; i < view->children_count; i++)
if (GTK_WIDGET(view->children[i]) == widget)
{
view->allocs[i] = *alloc;
break;
}
parent = gtk_widget_get_parent(widget);
if (parent != NULL)
{
g_object_ref(G_OBJECT(widget));
gtk_container_remove(GTK_CONTAINER(parent), widget);
}
gtk_fixed_put(GTK_FIXED(view->support), widget, alloc->x, alloc->y);
if (parent != NULL)
g_object_unref(G_OBJECT(widget));
}
/******************************************************************************
* *
* Paramètres : view = instance GTK à réinitialiser. *
* *
* Description : Supprime tout contenu de l'afficheur de code en graphique. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void gtk_graph_view_reset(GtkGraphView *view)
{
size_t i; /* Boucle de parcours */
/*
for (i = 0; i < view->links_count; i++)
gtk_object_destroy(GTK_OBJECT(view->links[i]));
if (view->links_count > 0)
{
free(view->links);
view->links = NULL;
view->links_count = 0;
}
*/
if (view->highlighted)
exit_segment_content_list(view->highlighted);
for (i = 0; i < view->children_count; i++)
{
g_signal_handlers_disconnect_by_func(view->children[i], gtk_graph_view_reach_caret_limit, view);
gtk_widget_destroy(GTK_WIDGET(view->children[i]));
}
if (view->children_count > 0)
{
free(view->children);
view->children = NULL;
free(view->allocs);
view->allocs = NULL;
view->children_count = 0;
}
}
/******************************************************************************
* *
* Paramètres : view = composant d'affichage GTK à mettre à jour. *
* routine = routine à présenter via ledit composant. *
* *
* Description : Définit la liste complète des éléments du futur graphique. *
* *
* Retour : Liste d'éléments du graphique à placer. *
* *
* Remarques : - *
* *
******************************************************************************/
static GtkBufferView **gtk_graph_view_load_nodes(GtkGraphView *view, GLoadedBinary *binary, const GBinRoutine *routine)
{
GtkBufferView **result; /* Liste à retourner */
GCodeBuffer *buffer; /* Tampon brut à découper */
size_t *count; /* Nombre d'éléments créés. */
GInstrBlock *main_block; /* Premier bloc rattaché */
GInstrBlock **blocks; /* Liste des blocs basiques */
size_t i; /* Boucle de parcours */
vmpa2t first; /* Début d'un groupe de lignes */
vmpa2t last; /* Fin d'un groupe de lignes */
GBufferView *subview; /* Partie affichée du tampon */
buffer = g_loaded_binary_get_disassembled_buffer(binary);
count = &view->children_count;
main_block = g_binary_routine_get_basic_blocks(routine);
blocks = NULL;
*count = 0;
g_instr_block_list_all_blocks(main_block, &blocks, count);
result = (GtkBufferView **)calloc(*count, sizeof(GtkBufferView *));
for (i = 0; i < *count; i++)
{
result[i] = GTK_BUFFER_VIEW(gtk_block_view_new());
g_signal_connect(result[i], "reach-limit", G_CALLBACK(gtk_graph_view_reach_caret_limit), view);
g_signal_connect(result[i], "highlight-changed", G_CALLBACK(gtk_graph_view_changed_highlights), view);
gtk_widget_show(GTK_WIDGET(result[i]));
gtk_view_panel_attach_binary(GTK_VIEW_PANEL(result[i]), binary, BVW_GRAPH);
gtk_view_panel_show_border(GTK_VIEW_PANEL(result[i]), true);
g_flow_block_get_boundary_addresses(G_FLOW_BLOCK(blocks[i]), &first, &last);
subview = g_buffer_view_new(buffer, view->highlighted);
g_buffer_view_restrict(subview, &first, &last);
gtk_buffer_view_attach_buffer(result[i], subview);
}
return result;
}
/******************************************************************************
* *
* Paramètres : node = composant d'affichage GTK impliqué dans la procédure. *
* view = support graphique de tous les noeuds. *
* *
* Description : Notifie un changement de surbrillance au sein d'un noeud. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void gtk_graph_view_changed_highlights(GtkBlockView *node, GtkGraphView *view)
{
size_t i; /* Boucle de parcours */
for (i = 0; i < view->children_count; i++)
{
if (view->children[i] == GTK_BUFFER_VIEW(node))
continue;
gtk_widget_queue_draw(GTK_WIDGET(view->children[i]));
}
}
/******************************************************************************
* *
* Paramètres : node = composant d'affichage GTK impliqué dans la procédure. *
* dir = direction du déplacement souhaité et impossible. *
* view = support graphique de tous les noeuds. *
* *
* Description : Notifie une incapacité de déplacement au sein d'un noeud. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void gtk_graph_view_reach_caret_limit(GtkBufferView *node, GdkScrollDirection dir, GtkGraphView *view)
{
GBufferView *bview; /* Vue d'un tampon global */
vmpa2t first; /* Début d'un groupe de lignes */
vmpa2t last; /* Fin d'un groupe de lignes */
const mrange_t *range; /* Couverture courante */
GArchProcessor *proc; /* Processeur pour instructions*/
GArchInstruction *ref; /* Point de référence */
#ifndef NDEBUG
bool is_return; /* Est-ce une instruc. finale ?*/
#endif
vmpa2t iaddr; /* Position de l'instructin */
size_t i; /* Boucle de parcours */
bool updated; /* Besoin d'une mise à jour ? */
/* Détermination de l'instruction à cibler */
bview = gtk_buffer_view_get_buffer(node);
g_buffer_view_get_restrictions(bview, &first, &last);
range = g_binary_routine_get_range(view->routine);
proc = g_loaded_binary_get_processor(GTK_VIEW_PANEL(view)->binary);
ref = NULL;
#ifndef NDEBUG
is_return = false;
#endif
switch (dir)
{
case GDK_SCROLL_LEFT:
case GDK_SCROLL_UP:
if (cmp_vmpa(get_mrange_addr(range), &first) != 0)
{
ref = g_arch_processor_find_instr_by_address(proc, &first);
if (ref != NULL)
ref = g_arch_processor_get_prev_instr(proc, ref);
/* TODO : boucler si !HAS_CODE */
if (ref != NULL)
copy_vmpa(&iaddr, get_mrange_addr(g_arch_instruction_get_range(ref)));
}
break;
case GDK_SCROLL_RIGHT:
case GDK_SCROLL_DOWN:
ref = g_arch_processor_find_instr_by_address(proc, &last);
#ifndef NDEBUG
if (ref != NULL)
is_return = g_arch_instruction_is_return(ref);
#endif
if (ref != NULL)
ref = g_arch_processor_get_next_instr(proc, ref);
/* TODO : boucler si !HAS_CODE */
if (ref != NULL)
{
copy_vmpa(&iaddr, get_mrange_addr(g_arch_instruction_get_range(ref)));
if (!mrange_contains_addr(range, &iaddr))
ref = NULL;
}
break;
case GDK_SCROLL_SMOOTH:
assert(0); /* Argument jamais généré */
break;
}
g_object_unref(G_OBJECT(proc));
/* Recherche du bloc parent */
if (ref == NULL)
return;
for (i = 0; i < view->children_count; i++)
{
bview = gtk_buffer_view_get_buffer(view->children[i]);
g_buffer_view_get_restrictions(bview, &first, &last);
if (cmp_vmpa(&first, &iaddr) <= 0 && cmp_vmpa(&iaddr, &last) <= 0)
{
assert(node != view->children[i]);
break;
}
}
assert(i < view->children_count || is_return);
/* Affichage du nouveau curseur */
/**
* Il se peut qu'aucune adresse suivante ne soit disponible : c'est typiquement
* le cas sous ARM, avec les valeurs brutes référencées dans le code. Ces valeurs
* sont incluses dans la surface couverte par la routine concernée, mais ne sont
* pas intégrées dans les blocs basiques associés.
*/
if (i == view->children_count)
return;
gtk_widget_grab_focus(GTK_WIDGET(view->children[i]));
switch (dir)
{
case GDK_SCROLL_LEFT:
updated = gtk_buffer_view_move_caret_to(view->children[i], false, NULL);
break;
case GDK_SCROLL_UP:
break;
case GDK_SCROLL_RIGHT:
updated = gtk_buffer_view_move_caret_to(view->children[i], true, NULL);
break;
case GDK_SCROLL_DOWN:
break;
case GDK_SCROLL_SMOOTH:
assert(0); /* Argument jamais généré */
break;
}
/* TODO : scrolling... */
}