/* Chrysalide - Outil d'analyse de fichiers binaires
 * target.c - opérandes ciblant un symbole
 *
 * Copyright (C) 2014-2017 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 Foobar.  If not, see <http://www.gnu.org/licenses/>.
 */


#include "target.h"


#include <assert.h>
#include <inttypes.h>
#include <malloc.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>


#include "operand-int.h"
#include "sharing/manager.h"
#include "../analysis/routine.h"
#include "../common/extstr.h"
#include "../format/format.h"



/* ------------------------ GESTION DES OPERANDES DE CIBLAGE ------------------------ */


/* Définition d'un opérande ciblant idéalement un symbole connu (instance) */
struct _GTargetOperand
{
    GArchOperand parent;                    /* Instance parente            */

    MemoryDataSize size;                    /* Taille de l'opérande        */
    vmpa2t addr;                            /* Adresse de l'élément visé   */

    GBinSymbol *symbol;                     /* Eventuel symbole associé    */
    phys_t diff;                            /* Position dans le symbole    */

};


/* Définition d'un opérande ciblant idéalement un symbole connu (classe) */
struct _GTargetOperandClass
{
    GArchOperandClass parent;               /* Classe parente              */

};


/* Initialise la classe des opérandes ciblant des symboles. */
static void g_target_operand_class_init(GTargetOperandClass *);

/* Initialise la classe des opérandes ciblant des symboles. */
static void g_target_operand_init(GTargetOperand *);

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

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

/* Initialise un nouvel objet partagé avec des informations. */
static bool g_target_operand_apply_template(GTargetOperand *, const GTargetOperand *);

/* Réalise une copie minimale d'un contenu partagé. */
static void g_target_operand_define_template(const GTargetOperand *, GTargetOperand *);

/* Compare un opérande avec un autre. */
static int g_target_operand_compare(const GTargetOperand *, const GTargetOperand *);

/* Traduit un opérande en version humainement lisible. */
static void g_target_operand_print(const GTargetOperand *, GBufferLine *, AsmSyntax);

/* Construit un petit résumé concis de l'opérande. */
static char *g_target_operand_build_tooltip(const GTargetOperand *, const GLoadedBinary *);



/* -------------------------- PARTAGES DE CONTENUS UNIQUES -------------------------- */


/* Gestionnaire des partages d'instances */
static GShareManager *_target_operand_manager = NULL;


/* Fournit le gestionnaire de partages attribué à un type. */
static GShareManager *get_target_operand_share_manager(void);



/* ---------------------------------------------------------------------------------- */
/*                          GESTION DES OPERANDES DE CIBLAGE                          */
/* ---------------------------------------------------------------------------------- */


/* Indique le type défini pour un opérande de valeur numérique. */
G_DEFINE_TYPE(GTargetOperand, g_target_operand, G_TYPE_ARCH_OPERAND);



/******************************************************************************
*                                                                             *
*  Paramètres  : klass = classe à initialiser.                                *
*                                                                             *
*  Description : Initialise la classe des opérandes ciblant des symboles.     *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void g_target_operand_class_init(GTargetOperandClass *klass)
{
    GObjectClass *object;                   /* Autre version de la classe  */
    GArchOperandClass *operand;             /* Version de classe parente   */

    object = G_OBJECT_CLASS(klass);
    operand = G_ARCH_OPERAND_CLASS(klass);

    object->dispose = (GObjectFinalizeFunc/* ! */)g_target_operand_dispose;
    object->finalize = (GObjectFinalizeFunc)g_target_operand_finalize;

    operand->get_manager = (get_operand_manager_fc)get_target_operand_share_manager;

    operand->apply_template = (apply_operand_template_fc)g_target_operand_apply_template;
    operand->define_template = (define_operand_template_fc)g_target_operand_define_template;

    operand->compare = (operand_compare_fc)g_target_operand_compare;
    operand->print = (operand_print_fc)g_target_operand_print;
    operand->build_tooltip = (operand_build_tooltip_fc)g_target_operand_build_tooltip;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : operand = instance à initialiser.                            *
*                                                                             *
*  Description : Initialise la classe des opérandes ciblant des symboles.     *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void g_target_operand_init(GTargetOperand *operand)
{
    operand->size = MDS_UNDEFINED;
    init_vmpa(&operand->addr, VMPA_NO_PHYSICAL, VMPA_NO_VIRTUAL);

    operand->symbol = NULL;
    operand->diff = 0;

}


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

static void g_target_operand_dispose(GTargetOperand *operand)
{
    if (operand->symbol != NULL)
        g_object_unref(G_OBJECT(operand->symbol));

    G_OBJECT_CLASS(g_target_operand_parent_class)->dispose(G_OBJECT(operand));

}


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

static void g_target_operand_finalize(GTargetOperand *operand)
{
    G_OBJECT_CLASS(g_target_operand_parent_class)->finalize(G_OBJECT(operand));

}


/******************************************************************************
*                                                                             *
*  Paramètres  : operand  = objet partagé à initialiser.                      *
*                template = coquille vide contenant les infos à enregistrer.  *
*                                                                             *
*  Description : Initialise un nouvel objet partagé avec des informations.    *
*                                                                             *
*  Retour      : Bilan de l'opération.                                        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static bool g_target_operand_apply_template(GTargetOperand *operand, const GTargetOperand *template)
{
    g_target_operand_define_template(template, operand);

    if (operand->symbol != NULL)
        g_object_ref(G_OBJECT(operand->symbol));

    return true;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : operand  = objet partagé à consulter.                        *
*                template = informations à retrouver intégralement.           *
*                                                                             *
*  Description : Réalise une copie minimale d'un contenu partagé.             *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void g_target_operand_define_template(const GTargetOperand *operand, GTargetOperand *template)
{
    template->size = operand->size;
    copy_vmpa(&template->addr, &operand->addr);

    template->symbol = operand->symbol;
    template->diff = operand->diff;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : a = premier opérande à consulter.                            *
*                b = second opérande à consulter.                             *
*                                                                             *
*  Description : Compare un opérande avec un autre.                           *
*                                                                             *
*  Retour      : Bilan de la comparaison.                                     *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static int g_target_operand_compare(const GTargetOperand *a, const GTargetOperand *b)
{
    int result;                             /* Bilan à retourner           */

    result = cmp_vmpa(&a->addr, &b->addr);
    if (result != 0) goto gtoc_done;

    if (a->size < b->size)
    {
        result = -1;
        goto gtoc_done;
    }
    else if (a->size > b->size)
    {
        result = 1;
        goto gtoc_done;
    }

    if (a->symbol == NULL && b->symbol != NULL)
    {
        result = -1;
        goto gtoc_done;
    }
    else if (a->symbol != NULL && b->symbol == NULL)
    {
        result = 1;
        goto gtoc_done;
    }
    else if (a->symbol != NULL && b->symbol != NULL)
    {
        result = g_binary_symbol_cmp((const GBinSymbol * []) { a->symbol },
                                     (const GBinSymbol * []) { b->symbol });
        if (result != 0) goto gtoc_done;
    }

    if (a->diff < b->diff)
    {
        result = -1;
        goto gtoc_done;
    }
    else if (a->diff > b->diff)
    {
        result = 1;
        goto gtoc_done;
    }

    result = 0;

 gtoc_done:

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : operand = opérande à traiter.                                *
*                line    = ligne tampon où imprimer l'opérande donné.         *
*                syntax  = type de représentation demandée.                   *
*                                                                             *
*  Description : Traduit un opérande en version humainement lisible.          *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void g_target_operand_print(const GTargetOperand *operand, GBufferLine *line, AsmSyntax syntax)
{
    const char *label;                      /* Etiquette liée à un symbole */
    vmpa2t tmp;                             /* Coquille vide pour argument */
    VMPA_BUFFER(value);                     /* Adresse brute à imprimer    */
    size_t len;                             /* Taille de l'élément inséré  */

    if (operand->symbol != NULL /* FIXME */ && g_binary_symbol_get_label(operand->symbol) != NULL /* FIXME */)
    {
        if (operand->diff > 0)
            g_buffer_line_append_text(line, BLC_MAIN, "<", 1, RTT_LTGT, NULL);

        label = g_binary_symbol_get_label(operand->symbol);
        g_buffer_line_append_text(line, BLC_MAIN, label, strlen(label), RTT_LABEL, G_OBJECT(operand));

        if (operand->diff > 0)
        {
            g_buffer_line_append_text(line, BLC_MAIN, "+", 1, RTT_SIGNS, G_OBJECT(operand));

            init_vmpa(&tmp, operand->diff, VMPA_NO_VIRTUAL);
            vmpa2_phys_to_string(&tmp, MDS_4_BITS, value, &len);

            g_buffer_line_append_text(line, BLC_MAIN, value, len, RTT_LABEL, G_OBJECT(operand));

            g_buffer_line_append_text(line, BLC_MAIN, ">", 1, RTT_LTGT, NULL);

        }

    }
    else
    {
        vmpa2_to_string(&operand->addr, operand->size, value, &len);

        g_buffer_line_append_text(line, BLC_MAIN, value, len, RTT_LABEL, G_OBJECT(operand));

    }

}


/******************************************************************************
*                                                                             *
*  Paramètres  : size = taille des adresse mémoire virtuelles.                *
*                addr = localisation d'un élément à retrouver.                *
*                                                                             *
*  Description : Crée un opérande réprésentant une valeur numérique.          *
*                                                                             *
*  Retour      : Instruction mise en place.                                   *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

GArchOperand *g_target_operand_new(MemoryDataSize size, const vmpa2t *addr)
{
    GArchOperand *result;                   /* Opérande à retourner        */
    GTargetOperand fake;                    /* Transport d'informations    */

    g_target_operand_init(&fake);

    fake.size = size;
    copy_vmpa(&fake.addr, addr);

    result = G_ARCH_OPERAND(g_share_manager_build(_target_operand_manager, (GSharedInstance *)&fake));

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : operand = opérande à consulter.                              *
*                binary  = informations relatives au binaire chargé.          *
*                                                                             *
*  Description : Construit un petit résumé concis de l'opérande.              *
*                                                                             *
*  Retour      : Chaîne de caractères à libérer après usage ou NULL.          *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static char *g_target_operand_build_tooltip(const GTargetOperand *operand, const GLoadedBinary *binary)
{
    char *result;                           /* Description à retourner     */
    SymbolType stype;                       /* Type de symbole identifié   */
    const mrange_t *srange;                 /* Emplacement du symbole      */
    GBufferCache *cache;                    /* Tampon de désassemblage     */
    size_t index;                           /* Indice de ligne à traiter   */
    GBufferLine *line;                      /* Ligne présente à l'adresse  */

    result = NULL;

    if (operand->symbol != NULL && operand->diff == 0)
    {
        stype = g_binary_symbol_get_target_type(operand->symbol);

        switch (stype)
        {
            case STP_ROUTINE:
            case STP_ENTRY_POINT:
                result = g_binary_routine_build_tooltip(G_BIN_ROUTINE(operand->symbol), binary);
                break;

            case STP_STRING:
            case STP_RO_STRING:

                srange = g_binary_symbol_get_range(operand->symbol);

                cache = g_loaded_binary_get_disassembled_cache(binary);

                index = g_buffer_cache_find_index_by_addr(cache, get_mrange_addr(srange), true);

                index = g_buffer_cache_look_for_flag(cache, index, BLF_HAS_CODE);

                line = g_buffer_cache_find_line_by_index(cache, index);

                if (line != NULL)
                {
                    result = g_buffer_line_get_text(line, BLC_ASSEMBLY, BLC_COUNT, true);
                    g_object_unref(G_OBJECT(line));
                }

                g_object_unref(G_OBJECT(cache));

                break;

            default:
                break;

        }

    }

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : operand = structure dont le contenu est à consulter.         *
*                                                                             *
*  Description : Renseigne la taille de la valeur indiquée à la construction. *
*                                                                             *
*  Retour      : Taille de la valeur représentée en mémoire.                  *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

MemoryDataSize g_target_operand_get_size(const GTargetOperand *operand)
{
    return operand->size;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : operand = structure dont le contenu est à consulter.         *
*                addr    = localisation à renseigner. [OUT]                   *
*                                                                             *
*  Description : Fournit l'adresse en mémoire de l'élément visé.              *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

void g_target_operand_get_addr(const GTargetOperand *operand, vmpa2t *addr)
{
    copy_vmpa(addr, &operand->addr);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : operand   = opérande dont le contenu est à raffiner. [OUT]   *
*                format    = format du binaire d'origine à consulter.         *
*                strict    = indique la perfection attendue de la résolution. *
*                container = propriétaire d'origine à tenir au courant.       *
*                                                                             *
*  Description : Tente une résolution de symbole.                             *
*                                                                             *
*  Retour      : Bilan de l'opération.                                        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

bool g_target_operand_resolve(GTargetOperand **operand, GBinFormat *format, bool strict, GShareContainer *container)
{
    bool result;                            /* Bilan à retourner           */
    GSharedInstance *shared;                /* Instace de travail partagée */
    GTargetOperand template;                /* Transport d'informations    */
    GBinSymbol *symbol;                     /* Facilités d'accès au symbole*/
    SymbolType stype;                       /* Type de symbole trouvé      */
    const mrange_t *range;                  /* Couverture du symbole       */
    char *label;                            /* Désignation de la chaîne    */

    shared = G_SHARED_INSTANCE(*operand);

    g_shared_instance_define_template(shared, (GSharedInstance *)&template);

    result = g_binary_format_resolve_symbol(format, &template.addr, strict, &template.symbol, &template.diff);

    shared = g_share_manager_update(_target_operand_manager, shared, (GSharedInstance *)&template, container);

    if (template.symbol != NULL)
        g_object_unref(G_OBJECT(template.symbol));

    *operand = G_TARGET_OPERAND(shared);

    /**
     * Si plusieurs chaînes se suivent, la seconde et les suivantes bénéficient
     * d'une étiquette si et seulement si elles sont détachées des précédentes
     * par un octet nul.
     *
     * S'il y a juste un retour à la ligne ("\n"), alors aucune séparation n'est
     * considérée, et le bloc de chaînes est uniforme.
     *
     * Aussi, si une référence renvoie vers une ligne de ce bloc, alors on
     * attribue à cette ligne une étiquette propre.
     */

    if (result && (*operand)->diff == 0)
    {
        symbol = (*operand)->symbol;

        stype = g_binary_symbol_get_target_type(symbol);

        if (stype == STP_STRING || stype == STP_RO_STRING)
        {
            if (g_binary_symbol_get_label(symbol) == NULL)
            {
                range = g_binary_symbol_get_range(symbol);

                assert(cmp_vmpa(&(*operand)->addr, get_mrange_addr(range)) == 0);

                label = create_string_label(format, get_mrange_addr(range), get_mrange_length(range));

                g_binary_symbol_set_alt_label(symbol, label);

                free(label);

            }

        }

    }

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : operand = opérande dont le contenu est à raffiner.           *
*                diff    = décallage entre le symbole et l'adresse initiale.  *
*                                                                             *
*  Description : Fournit les indications concernant le symbole associé.       *
*                                                                             *
*  Retour      : Symbole résolu ou NULL si aucun.                             *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

GBinSymbol *g_target_operand_get_symbol(const GTargetOperand *operand, phys_t *diff)
{
    GBinSymbol *result;                     /* Symbole associé à retourner */

    if (diff != NULL)
        *diff = operand->diff;

    result = operand->symbol;

    if (result != NULL)
        g_object_ref(G_OBJECT(result));

    return result;

}



/* ---------------------------------------------------------------------------------- */
/*                            PARTAGES DE CONTENUS UNIQUES                            */
/* ---------------------------------------------------------------------------------- */


/******************************************************************************
*                                                                             *
*  Paramètres  : -                                                            *
*                                                                             *
*  Description : Fournit le gestionnaire de partages attribué à un type.      *
*                                                                             *
*  Retour      : Gestionnaire de partages en place.                           *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static GShareManager *get_target_operand_share_manager(void)
{
    return _target_operand_manager;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : -                                                            *
*                                                                             *
*  Description : Initialise les mécanismes de partage d'opérandes de ciblage. *
*                                                                             *
*  Retour      : Bilan de l'opération.                                        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

bool init_target_operand_sharing(void)
{
    _target_operand_manager = g_share_manager_new(G_TYPE_TARGET_OPERAND);

    return true;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : -                                                            *
*                                                                             *
*  Description : Imprime des statistiques quant aux partages dans l'archi.    *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/
#ifdef DEBUG_DUMP_STATS
void dump_target_operand_share_stats(void)
{
    g_share_manager_dump_stats(_target_operand_manager);

}
#endif


/******************************************************************************
*                                                                             *
*  Paramètres  : -                                                            *
*                                                                             *
*  Description : Supprime les mécanismes de partage des opérandes de ciblage. *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

void exit_target_operand_sharing(void)
{
    g_object_unref(G_OBJECT(_target_operand_manager));

}