/* 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_get_flags(ref) & AIF_RETURN_POINT); #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... */ }