/* Chrysalide - Outil d'analyse de fichiers binaires
 * context.c - mise en place d'un contexte de décompilation
 *
 * Copyright (C) 2010-2013 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 <http://www.gnu.org/licenses/>.
 */


#include "context.h"


#include <malloc.h>


#include "context-int.h"
#include "instruction-int.h"
#include "../arch/operand.h"



/* Initialise la classe des contextes de décompilation. */
static void g_dec_context_class_init(GDecContextClass *);

/* Initialise une instance de contexte de décompilation. */
static void g_dec_context_init(GDecContext *);

/* Supprime toutes les références externes. */
static void g_dec_context_dispose(GDecContext *);

/* Procède à la libération totale de la mémoire. */
static void g_dec_context_finalize(GDecContext *);



/* Indique le type défini pour un contexte de décompilation. */
G_DEFINE_TYPE(GDecContext, g_dec_context, G_TYPE_OBJECT);


/******************************************************************************
*                                                                             *
*  Paramètres  : class = classe à initialiser.                                *
*                                                                             *
*  Description : Initialise la classe des contextes de décompilation.         *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void g_dec_context_class_init(GDecContextClass *class)
{
    GObjectClass *object;                   /* Autre version de la classe  */

    object = G_OBJECT_CLASS(class);

    object->dispose = (GObjectFinalizeFunc/* ! */)g_dec_context_dispose;
    object->finalize = (GObjectFinalizeFunc)g_dec_context_finalize;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : ctx = instance à initialiser.                                *
*                                                                             *
*  Description : Initialise une instance de contexte de décompilation.        *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void g_dec_context_init(GDecContext *ctx)
{
    ctx->awaited = g_raccess_list_new();

    ctx->shared = g_hash_table_new_full((GHashFunc)g_arch_register_hash,
                                        (GEqualFunc)g_arch_register_equal,
                                        g_object_unref, g_object_unref);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : ctx = instance d'objet GLib à traiter.                       *
*                                                                             *
*  Description : Supprime toutes les références externes.                     *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void g_dec_context_dispose(GDecContext *ctx)
{
    if (ctx->format != NULL)
        g_object_unref(G_OBJECT(ctx->format));

    if (ctx->routine != NULL)
        g_object_unref(G_OBJECT(ctx->routine));

    g_object_unref(G_OBJECT(ctx->awaited));
    g_hash_table_unref(ctx->shared);

    G_OBJECT_CLASS(g_dec_context_parent_class)->dispose(G_OBJECT(ctx));

}


/******************************************************************************
*                                                                             *
*  Paramètres  : ctx = instance d'objet GLib à traiter.                       *
*                                                                             *
*  Description : Procède à la libération totale de la mémoire.                *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void g_dec_context_finalize(GDecContext *ctx)
{
    G_OBJECT_CLASS(g_dec_context_parent_class)->finalize(G_OBJECT(ctx));

}


/******************************************************************************
*                                                                             *
*  Paramètres  : dest = contexte de compilation à définir.                    *
*                src  = contexte de compilation à copier.                     *
*                                                                             *
*  Description : Duplique partiellement un contexte de compilation.           *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

void _g_dec_context_dup(GDecContext *dest, GDecContext *src)
{
    if (src->routine != NULL)
        g_object_ref(G_OBJECT(src->routine));
    if (src->format != NULL)
        g_object_ref(G_OBJECT(src->format));

    dest->routine = src->routine;
    dest->format = src->format;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : orig = contexte de compilation à copier.                     *
*                                                                             *
*  Description : Duplique un contexte de compilation.                         *
*                                                                             *
*  Retour      : Contexte de décompilation prêt à emploi.                     *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

GDecContext *g_dec_context_dup(GDecContext *orig)
{
    GDecContext *result;                    /* Instance à retourner        */

    result = orig->dup(orig);

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : ctx     = contexte de décompilation à compléter.             *
*                routine = routine visée par l'opération.                     *
*                format  = format du fichier binaire associé.                 *
*                                                                             *
*  Description : Définit les informations essentielles à la décompilation.    *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

void g_dec_context_set_info(GDecContext *ctx, GBinRoutine *routine, GExeFormat *format)
{
    g_object_ref(G_OBJECT(routine));
    g_object_ref(G_OBJECT(format));

    ctx->routine = routine;
    ctx->format = format;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : ctx = instance à consulter.                                  *
*                                                                             *
*  Description : Fournit le format binaire associé au contexte.               *
*                                                                             *
*  Retour      : Format du fichier binaire décompilé.                         *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

GExeFormat *g_dec_context_get_format(const GDecContext *ctx)
{
    return ctx->format;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : ctx     = instance à mettre à jour.                          *
*                awaited = liste des registres impliqués dans plusieurs blocs.*
*                                                                             *
*  Description : Définit la liste des registrés utilisés dans plusieurs blocs.*
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

void g_dec_context_set_awaited(GDecContext *ctx, GRAccessList *awaited)
{
    g_object_unref(G_OBJECT(ctx->awaited));

    g_object_ref(G_OBJECT(awaited));
    ctx->awaited = awaited;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : ctx = instance à consulter.                                  *
*                                                                             *
*  Description : Fournit la liste des registrés utilisés dans plusieurs blocs.*
*                                                                             *
*  Retour      : Liste des registres impliqués dans plusieurs blocs.          *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

const GRAccessList *g_dec_context_get_awaited(const GDecContext *ctx)
{
    return ctx->awaited;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : ctx    = instance à mettre à jour.                           *
*                shared = liste des allocations passées de registres attendus.*
*                                                                             *
*  Description : Définit la liste des registrés déjà alloués et attendus.     *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

void g_dec_context_set_shared_allocs(GDecContext *ctx, GHashTable *shared)
{
    g_hash_table_unref(ctx->shared);

    g_hash_table_ref(shared);
    ctx->shared = shared;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : ctx = instance à consulter.                                  *
*                                                                             *
*  Description : Fournit la liste des registrés déjà alloués et attendus.     *
*                                                                             *
*  Retour      : Liste des allocations passées de registres attendus.         *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

GHashTable *g_dec_context_get_shared_allocs(const GDecContext *ctx)
{
    return ctx->shared;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : parent = instance à éventuellement compléter.                *
*                child  = instance à venir consulter.                         *
*                                                                             *
*  Description : Propage les registres alloués et attendus par la suite.      *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

void g_dec_context_spread_allocated_shared_regs(GDecContext *parent, GDecContext *child)
{
    GHashTableIter iter;                    /* Boucle de parcours          */
    GArchRegister *reg;                     /* Registre converti           */
    GDecInstruction *dinstr;                /* Expression décompilée       */

    g_hash_table_iter_init(&iter, child->shared);

    while (g_hash_table_iter_next(&iter, (gpointer *)&reg, (gpointer *)&dinstr))
    {
        /**
         * La liste des allocations attendues du bloc virtuel parent, et donc de
         * son contexte associé, englobe les attentes des blocs contenus.
         * Même si ce bloc parent n'est pas responsable directement de ces allocations,
         * on fait remonter ces dernières pour la propagation dans les blocs suivants
         * le bloc parent.
         */
        if (g_raccess_list_find(parent->awaited, reg) != NULL)
        {
            g_object_ref(G_OBJECT(reg));
            g_object_ref(G_OBJECT(dinstr));

            g_hash_table_insert(parent->shared, reg, dinstr);

        }

        parent->spread(parent, reg, dinstr);

    }

}


/******************************************************************************
*                                                                             *
*  Paramètres  : ctx = instance à consulter.                                  *
*                                                                             *
*  Description : Fournit le premier élément de la liste des instructions.     *
*                                                                             *
*  Retour      : Première instruction décompilée pour le contexte.            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

GDecInstruction *g_dec_context_get_decomp_instrs(const GDecContext *ctx)
{
    return ctx->list;


}


/******************************************************************************
*                                                                             *
*  Paramètres  : ctx   = instance à mettre à jour.                            *
*                instr = première instruction décompilée pour le contexte.    *
*                                                                             *
*  Description : Met à jour le premier élément de la liste des instructions.  *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

void g_dec_context_set_decomp_instrs(GDecContext *ctx, GDecInstruction *instr)
{
    ctx->list = instr;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : ctx     = instance à consulter, voire mettre à jour.         *
*                operand = opérande représentant un registre quelconque.      *
*                assign  = précise le sort prochain du registre.              *
*                addr    = adresse de l'instruction décompilée.               *
*                                                                             *
*  Description : Convertit un registre machine en un pseudo-registre.         *
*                                                                             *
*  Retour      : Pseudo-registre, existant ou non, prêt à emploi.             *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

GDecInstruction *g_dec_context_convert_register(GDecContext *ctx, gpointer operand, bool assign, vmpa_t addr)
{
    return ctx->convert_reg(ctx, operand, assign, addr);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : ctx    = instance à consulter, voire mettre à jour.          *
*                reg    = registre à l'origine de l'allocation.               *
*                dinstr = élément décompilé résultant.                        *
*                addr   = adresse de l'instruction décompilée.                *
*                                                                             *
*  Description : Sauvegarde une conversion de registre si elle est attendue.  *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

void g_dec_context_notify_reg_alloc(GDecContext *ctx, GArchRegister *reg, GDecInstruction *dinstr, vmpa_t addr)
{
    reg_access *access;                     /* Attente rélle si existante  */

    access = g_raccess_list_find(ctx->awaited, reg);

    if (access != NULL && access->last_write == addr)
    {
        g_object_ref(G_OBJECT(reg));
        g_object_ref(G_OBJECT(dinstr));

        g_hash_table_insert(ctx->shared, reg, dinstr);

    }

}


/******************************************************************************
*                                                                             *
*  Paramètres  : ctx  = instance à consulter, voire mettre à jour.            *
*                reg  = registre à convertir prochainement.                   *
*                addr = adresse de l'instruction décompilée.                  *
*                                                                             *
*  Description : Fournit une conversiond de registre déjà faite et attendue.  *
*                                                                             *
*  Retour      : Elément déjà décompilé dans une autre branche.               *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

GDecInstruction *g_dec_context_get_awaited_alloc(GDecContext *ctx, GArchRegister *reg, vmpa_t addr)
{
    reg_access *access;                     /* Attente rélle si existante  */

    access = g_raccess_list_find(ctx->awaited, reg);
    if (access == NULL) return NULL;

    if (access->last_write != addr) return NULL;

    return G_DEC_INSTRUCTION(g_hash_table_lookup(ctx->shared, reg));

}