/* Chrysalide - Outil d'analyse de fichiers binaires * loop.c - détection des boucles dans du code machine * * Copyright (C) 2013-2019 Cyrille Bagard * * This file is part of Chrysalide. * * Chrysalide 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. * * Chrysalide 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 Chrysalide. If not, see . */ #include "loop.h" #include #include #include "block.h" /** * Adaptation de l'algorithme : "A New Algorithm for Identifying Loops in Decompilation". */ /* Informations associées à un bloc */ typedef struct _bblock_info_t { bool traversed; /* Etat du traitement */ unsigned int dfsp_pos; /* Indice dans un chemin */ struct _bblock_info_t *iloop_header; /* Pointeur de bloc d'entête */ bool irreducible; /* Détection d'irreducible */ } bblock_info_t; /* Informations associées à un lien */ typedef struct _bblock_link_t { GCodeBlock *linked; /* Destination du bloc visé */ InstructionLinkType type; /* Type de liaison */ bblock_info_t *info; /* Informations sur ce bloc */ } bblock_link_t; /* Détermine les liens vers des blocs successeurs d'un bloc. */ static bblock_link_t *get_block_links(GCodeBlock *, bblock_info_t *, bool, size_t *); #define get_block_predecessors(b, i, c) \ get_block_links(b, i, false, c) #define get_block_successors(b, i, c) \ get_block_links(b, i, true, c) /* Libère de la mémoire les liens vers des blocs successeurs. */ static void delete_block_links(bblock_link_t *, size_t); /* Marque un bloc comme étant un entête de boucle. */ static void tag_loop_head(bblock_info_t *, bblock_info_t *); /* Parcourt une arborescence de blocs à la recherche de boucles. */ static bblock_info_t *traverse_basic_blocks_dfs(bblock_info_t *, GBlockList *, bblock_info_t *, unsigned int); /* Indique si une boucle doit être définie. */ static bool _should_be_natural_loop_link(bblock_info_t *, bblock_info_t *); /* Indique si une boucle doit être définie. */ static bool should_be_natural_loop_link(bblock_info_t *, bblock_info_t *); /* Définit les boucles entre un ensemble de blocs basiques. */ static void define_basic_blocks_loops(GBlockList *list, bblock_info_t *); /****************************************************************************** * * * Paramètres : block = bloc de code, basique, à considérer. * * info = informations complémentaires quant aux blocs. * * succ = true si les successeurs sont visés, false sinon. * * count = nombre de liens effectivement utiles. [OUT] * * * * Description : Détermine les liens vers des blocs successeurs d'un bloc. * * * * Retour : Liste de liens retrouvés ou NULL si vraiment aucun. * * * * Remarques : - * * * ******************************************************************************/ static bblock_link_t *get_block_links(GCodeBlock *block, bblock_info_t *info, bool succ, size_t *count) { bblock_link_t *result; /* Liste à retourner */ size_t allocated; /* Nombre d'éléments au maximum*/ block_link_t *links; /* Liens associés au bloc */ size_t i; /* Boucle de parcours */ bblock_link_t *new; /* Mémorisation de ce lien */ if (succ) links = g_code_block_get_destinations(block, &allocated); else links = g_code_block_get_sources(block, &allocated); result = malloc(allocated * sizeof(bblock_link_t)); *count = 0; for (i = 0; i < allocated; i++) { switch (links[i].type) { case ILT_EXEC_FLOW: case ILT_JUMP: case ILT_CASE_JUMP: case ILT_JUMP_IF_TRUE: case ILT_JUMP_IF_FALSE: new = &result[(*count)++]; new->linked = links[i].linked; g_object_ref(G_OBJECT(new->linked)); new->type = links[i].type; new->info = info + g_code_block_get_index(new->linked); break; default: break; } unref_block_link(&links[i]); } if (links != NULL) free(links); return result; } /****************************************************************************** * * * Paramètres : links = liste de liens retrouvés ou NULL si vraiment aucun. * * count = nombre de liens effectivement utiles. * * * * Description : Libère de la mémoire les liens vers des blocs successeurs. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void delete_block_links(bblock_link_t *links, size_t count) { size_t i; /* Boucle de parcours */ for (i = 0; i < count; i++) g_object_unref(G_OBJECT(links[i].linked)); if (links != NULL) free(links); } /****************************************************************************** * * * Paramètres : blk = bloc à mettre à jour. * * hdr = bloc d'entête à mémoriser. * * * * Description : Marque un bloc comme étant un entête de boucle. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void tag_loop_head(bblock_info_t *blk, bblock_info_t *hdr) { bblock_info_t *cur1; /* Boucle de parcours #1 */ bblock_info_t *cur2; /* Boucle de parcours #2 */ bblock_info_t *ih; /* Boucle de parcours #3 */ if (blk == hdr || hdr == NULL) goto done; cur1 = blk; cur2 = hdr; while (cur1->iloop_header != NULL) { ih = cur1->iloop_header; if (ih == cur2) goto done; if (ih->dfsp_pos < cur2->dfsp_pos) { cur1->iloop_header = cur2; cur1 = cur2; cur2 = ih; } else cur1 = ih; } cur1->iloop_header = cur2; done: ; } /****************************************************************************** * * * Paramètres : root = élément racine à considérer. * * list = liste de blocs de code à consulter. * * info = informations complémentaires quant aux blocs. * * pos = position dans le chemin courant. * * * * Description : Parcourt une arborescence de blocs à la recherche de boucles.* * * * Retour : Entête de boucle trouvée ou NULL. * * * * Remarques : - * * * ******************************************************************************/ static bblock_info_t *traverse_basic_blocks_dfs(bblock_info_t *root, GBlockList *list, bblock_info_t *info, unsigned int pos) { GCodeBlock *block; /* Bloc basique courant */ bblock_link_t *links; /* Liste de successeurs */ size_t count; /* Taille de cette liste */ size_t i; /* Boucle de parcours */ bblock_info_t *succ; /* Successeur à traiter */ bblock_info_t *nh; /* Nouvel entête de boucle */ bblock_info_t *h; /* Entête de boucle */ root->traversed = true; root->dfsp_pos = pos; block = g_block_list_get_block(list, root - info); links = get_block_successors(block, info, &count); for (i = 0; i < count; i++) { succ = links[i].info; /* Cas A : bloc jamais traversé */ if (!succ->traversed) { nh = traverse_basic_blocks_dfs(succ, list, info, pos + 1); tag_loop_head(root, nh); } else { /* Le successeur est dans DFSP(root) */ if (succ->dfsp_pos > 0) { /* Cas B : on le marque en tant qu'entête de boucle */ tag_loop_head(root, succ); } /* Cas C : on ne fait rien */ else if (succ->iloop_header == NULL) { } else { h = succ->iloop_header; /* h est dans DFSP(root) */ if (h->dfsp_pos > 0) { /* Cas D */ tag_loop_head(root, h); } /* h n'est pas dans DFSP(root) */ else { /* Cas E : réentrance */ succ->irreducible = true; while (h->iloop_header != NULL) { h = h->iloop_header; /* h est dans DFSP(root) */ if (h->dfsp_pos > 0) { tag_loop_head(root, h); break; } } } } } } delete_block_links(links, count); g_object_unref(G_OBJECT(block)); /* Efface la position du bloc courant dans le chemin */ root->dfsp_pos = 0; return root->iloop_header; } /****************************************************************************** * * * Paramètres : dest = informations du bloc de destination. * * header = informations de l'entête de boucle. * * * * Description : Indique si une boucle doit être définie. * * * * Retour : true si une boucle naturelle est bien présente. * * * * Remarques : - * * * ******************************************************************************/ static bool _should_be_natural_loop_link(bblock_info_t *dest, bblock_info_t *header) { bool result; /* Conclusion à retourner */ result = (dest == header); if (!result && header != NULL) result = _should_be_natural_loop_link(dest, header->iloop_header); return result; } /****************************************************************************** * * * Paramètres : dest = informations du bloc de destination. * * header = informations de l'entête de boucle. * * * * Description : Indique si une boucle doit être définie. * * * * Retour : true si une boucle naturelle est bien présente. * * * * Remarques : - * * * ******************************************************************************/ static bool should_be_natural_loop_link(bblock_info_t *dest, bblock_info_t *header) { bool result; /* Conclusion à retourner */ result = _should_be_natural_loop_link(dest, header); if (!result && dest->iloop_header != NULL) result = should_be_natural_loop_link(dest->iloop_header, header); return result; } /****************************************************************************** * * * Paramètres : list = liste de blocs de code à consulter. * * info = informations complémentaires quant aux blocs. * * * * Description : Définit les boucles entre un ensemble de blocs basiques. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void define_basic_blocks_loops(GBlockList *list, bblock_info_t *info) { size_t available; /* Quantité de blocs présents */ size_t i; /* Boucle de parcours #1 */ bblock_info_t *iter; /* Boucle de parcours #2 */ GCodeBlock *block; /* Bloc basique courant */ bblock_link_t *links; /* Liste de successeurs */ size_t count; /* Taille de cette liste */ size_t k; /* Boucle de parcours #3 */ GArchInstruction *first; /* Instruction initiale de bloc*/ GArchInstruction *last; /* Instruction finale de bloc */ #ifndef NDEBUG bool status; /* Bilan du changement */ #endif available = g_block_list_count_blocks(list); for (i = 0, iter = info; i < available; i++, iter++) { block = g_block_list_get_block(list, i); if (iter->irreducible) { links = get_block_predecessors(block, info, &count); for (k = 0; k < count; k++) if (should_be_natural_loop_link(iter->iloop_header, links[k].info->iloop_header) /** * Il se peut qu'un bloc fasse référence à lui même ! * * Cf. tests/analysis/disass/jinit_color_converter.bin * * On évite ici une boucle sans fin en officialisant cette boucle. */ || links[k].info == iter->iloop_header) { g_basic_block_get_boundaries(G_BASIC_BLOCK(links[k].linked), NULL, &last); g_basic_block_get_boundaries(G_BASIC_BLOCK(block), &first, NULL); g_arch_instruction_lock_dest(last); #ifndef NDEBUG status = g_arch_instruction_change_link(last, first, links[k].type, ILT_LOOP); assert(status); #else g_arch_instruction_change_link(last, first, links[k].type, ILT_LOOP); #endif g_arch_instruction_unlock_dest(last); g_object_unref(G_OBJECT(first)); g_object_unref(G_OBJECT(last)); } } else { links = get_block_successors(block, info, &count); for (k = 0; k < count; k++) if (_should_be_natural_loop_link(links[k].info, iter->iloop_header) /** * Il se peut qu'un bloc fasse référence à lui même ! * * Cf. tests/analysis/disass/selfloop.c * * On évite ici une boucle sans fin en officialisant cette boucle. */ || links[k].info == iter) { g_basic_block_get_boundaries(G_BASIC_BLOCK(block), NULL, &last); g_basic_block_get_boundaries(G_BASIC_BLOCK(links[k].linked), &first, NULL); g_arch_instruction_lock_dest(last); #ifndef NDEBUG status = g_arch_instruction_change_link(last, first, links[k].type, ILT_LOOP); assert(status); #else g_arch_instruction_change_link(last, first, links[k].type, ILT_LOOP); #endif g_arch_instruction_unlock_dest(last); g_object_unref(G_OBJECT(first)); g_object_unref(G_OBJECT(last)); } } delete_block_links(links, count); g_object_unref(G_OBJECT(block)); } } /****************************************************************************** * * * Paramètres : list = liste de blocs de code à consulter. * * * * Description : Détecte les boucles dans du code machine. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ void detect_loops_in_basic_blocks(GBlockList *list) { size_t count; /* Quantité de blocs présents */ bblock_info_t *info; /* Informations supplémentaires*/ count = g_block_list_count_blocks(list); /** * Un premier jet consistait à filtrer sur le nombre de blocs : s'il n'y * en avait qu'un, il n'y avait à priori pas de raison de rechercher des * boucles ! * * Mais c'était sans compter une routine se résumant à une boucle infinie... * * C'est par exemple le cas avec la fonction operator new[] (_ZnajRKSt9nothrow_t) * de l'échantillon b6990fc6913d839809c72d1d482cb2c295c4840fc6a1f40f38923464e958ffae. */ info = calloc(count, sizeof(bblock_info_t)); traverse_basic_blocks_dfs(&info[0], list, info, 1); define_basic_blocks_loops(list, info); free(info); }