/* Chrysalide - Outil d'analyse de fichiers binaires
 * instruction.c - gestion des instructions de la ARM
 *
 * Copyright (C) 2017-2018 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 "instruction.h"
#include 
#include 
#include 
#include 
#include 
#include "instruction-int.h"
/* Initialise la classe des instructions ARM. */
static void g_arm_instruction_class_init(GArmInstructionClass *);
/* Initialise une instance de instruction ARM. */
static void g_arm_instruction_init(GArmInstruction *);
/* Supprime toutes les références externes. */
static void g_arm_instruction_dispose(GArmInstruction *);
/* Procède à la libération totale de la mémoire. */
static void g_arm_instruction_finalize(GArmInstruction *);
/* -------------------- CONSERVATION SUR DISQUE DES INSTRUCTIONS -------------------- */
/* Charge une instruction depuis une mémoire tampon. */
static bool g_arm_instruction_unserialize(GArmInstruction *, GAsmStorage *, GBinFormat *, packed_buffer *);
/* Sauvegarde une instruction dans une mémoire tampon. */
static bool g_arm_instruction_serialize(GArmInstruction *, GAsmStorage *, packed_buffer *);
/* Indique le type défini pour une représentation d'une instruction ARM. */
G_DEFINE_TYPE(GArmInstruction, g_arm_instruction, G_TYPE_ARCH_INSTRUCTION);
/******************************************************************************
*                                                                             *
*  Paramètres  : klass = classe à initialiser.                                *
*                                                                             *
*  Description : Initialise la classe des instructions ARM.                   *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/
static void g_arm_instruction_class_init(GArmInstructionClass *klass)
{
    GObjectClass *object_class;             /* Autre version de la classe  */
    GArchInstructionClass *instr;           /* Encore une autre vision...  */
    object_class = G_OBJECT_CLASS(klass);
    object_class->dispose = (GObjectFinalizeFunc/* ! */)g_arm_instruction_dispose;
    object_class->finalize = (GObjectFinalizeFunc)g_arm_instruction_finalize;
    instr = G_ARCH_INSTRUCTION_CLASS(klass);
    instr->unserialize = (unserialize_instruction_fc)g_arm_instruction_unserialize;
    instr->serialize = (serialize_instruction_fc)g_arm_instruction_serialize;
}
/******************************************************************************
*                                                                             *
*  Paramètres  : instr = instance à initialiser.                              *
*                                                                             *
*  Description : Initialise une instance d'instruction ARM.                   *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/
static void g_arm_instruction_init(GArmInstruction *instr)
{
    instr->suffix = NULL;
    instr->cached_keyword = NULL;
    instr->cond = ACC_AL;
}
/******************************************************************************
*                                                                             *
*  Paramètres  : instr = instance d'objet GLib à traiter.                     *
*                                                                             *
*  Description : Supprime toutes les références externes.                     *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/
static void g_arm_instruction_dispose(GArmInstruction *instr)
{
    G_OBJECT_CLASS(g_arm_instruction_parent_class)->dispose(G_OBJECT(instr));
}
/******************************************************************************
*                                                                             *
*  Paramètres  : instr = instance d'objet GLib à traiter.                     *
*                                                                             *
*  Description : Procède à la libération totale de la mémoire.                *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/
static void g_arm_instruction_finalize(GArmInstruction *instr)
{
    if (instr->suffix != NULL)
        free(instr->suffix);
    if (instr->cached_keyword != NULL)
        free(instr->cached_keyword);
    G_OBJECT_CLASS(g_arm_instruction_parent_class)->finalize(G_OBJECT(instr));
}
/******************************************************************************
*                                                                             *
*  Paramètres  : instr  = instruction quelconque à modifier.                  *
*                suffix = chaîne de caractères fournie en complément.         *
*                                                                             *
*  Description : Etend la désignation d'un nom d'instruction.                 *
*                                                                             *
*  Retour      : true.                                                        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/
bool g_arm_instruction_extend_keyword(GArmInstruction *instr, const char *suffix)
{
    instr->suffix = stradd(instr->suffix, suffix);
    if (instr->cached_keyword != NULL)
    {
        free(instr->cached_keyword);
        instr->cached_keyword = NULL;
    }
    return true;
}
/******************************************************************************
*                                                                             *
*  Paramètres  : instr = instruction ARM à mettre à jour.                     *
*                cond  = condition d'exécution de l'instruction.              *
*                                                                             *
*  Description : Définit les conditions d'exécution d'une instruction ARM.    *
*                                                                             *
*  Retour      : Bilan de l'opération.                                        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/
bool g_arm_instruction_set_cond(GArmInstruction *instr, ArmCondCode cond)
{
    bool result;                            /* Bilan à retourner           */
    const char *suffix;                     /* Eventuelle marque à ajouter */
    instr->cond = cond;
    switch (cond)
    {
        case ACC_EQ: suffix = "eq"; break;
        case ACC_NE: suffix = "ne"; break;
        case ACC_HS: suffix = "hs"; break;
        case ACC_LO: suffix = "lo"; break;
        case ACC_MI: suffix = "mi"; break;
        case ACC_PL: suffix = "pl"; break;
        case ACC_VS: suffix = "vs"; break;
        case ACC_VC: suffix = "vc"; break;
        case ACC_HI: suffix = "hi"; break;
        case ACC_LS: suffix = "ls"; break;
        case ACC_GE: suffix = "ge"; break;
        case ACC_LT: suffix = "lt"; break;
        case ACC_GT: suffix = "gt"; break;
        case ACC_LE: suffix = "le"; break;
        case ACC_AL: suffix = NULL; break;
        case ACC_NV: suffix = "nv"; break;
        default:    /* Pour GCC... */
            assert(false);
            suffix = NULL;
            break;
    }
    if (suffix != NULL)
        result = g_arm_instruction_extend_keyword(instr, suffix);
    else
        result = true;
    return result;
}
/******************************************************************************
*                                                                             *
*  Paramètres  : instr = instruction ARM à consulter.                         *
*                                                                             *
*  Description : Indique les conditions d'exécution d'une instruction ARM.    *
*                                                                             *
*  Retour      : Condition d'exécution de l'instruction.                      *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/
ArmCondCode g_arm_instruction_get_cond(const GArmInstruction *instr)
{
    return instr->cond;
}
/* ---------------------------------------------------------------------------------- */
/*                      CONSERVATION SUR DISQUE DES INSTRUCTIONS                      */
/* ---------------------------------------------------------------------------------- */
/******************************************************************************
*                                                                             *
*  Paramètres  : instr   = instruction d'assemblage à consulter.              *
*                storage = mécanisme de sauvegarde à manipuler.               *
*                format  = format binaire chargé associé à l'architecture.    *
*                pbuf    = zone tampon à remplir.                             *
*                                                                             *
*  Description : Charge une instruction depuis une mémoire tampon.            *
*                                                                             *
*  Retour      : Bilan de l'opération.                                        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/
static bool g_arm_instruction_unserialize(GArmInstruction *instr, GAsmStorage *storage, GBinFormat *format, packed_buffer *pbuf)
{
    bool result;                            /* Bilan à retourner           */
    GArchInstructionClass *parent;          /* Classe parente à consulter  */
    unsigned char len;                      /* Taille de textes            */
    char *text;                             /* Texte reconstitué           */
    parent = G_ARCH_INSTRUCTION_CLASS(g_arm_instruction_parent_class);
    result = parent->unserialize(G_ARCH_INSTRUCTION(instr), storage, format, pbuf);
    if (result)
    {
        result = extract_packed_buffer(pbuf, &len, sizeof(unsigned char), false);
        if (result && len > 0)
        {
            text = (char *)malloc(len);
            if (result)
                result = extract_packed_buffer(pbuf, text, len, false);
            if (result)
                result = g_arm_instruction_extend_keyword(instr, text);
            free(text);
        }
    }
    if (result)
        result = extract_packed_buffer(pbuf, &instr->cond, sizeof(ArmCondCode), true);
    return result;
}
/******************************************************************************
*                                                                             *
*  Paramètres  : instr   = instruction d'assemblage à consulter.              *
*                storage = mécanisme de sauvegarde à manipuler.               *
*                pbuf    = zone tampon à remplir.                             *
*                                                                             *
*  Description : Sauvegarde une instruction dans une mémoire tampon.          *
*                                                                             *
*  Retour      : Bilan de l'opération.                                        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/
static bool g_arm_instruction_serialize(GArmInstruction *instr, GAsmStorage *storage, packed_buffer *pbuf)
{
    bool result;                            /* Bilan à retourner           */
    GArchInstructionClass *parent;          /* Classe parente à consulter  */
    size_t len;                             /* Taille de textes            */
    parent = G_ARCH_INSTRUCTION_CLASS(g_arm_instruction_parent_class);
    result = parent->serialize(G_ARCH_INSTRUCTION(instr), storage, pbuf);
    if (result)
    {
        if (instr->suffix == NULL)
            result = extend_packed_buffer(pbuf, (unsigned char []) { 0 }, sizeof(unsigned char), false);
        else
        {
            len = strlen(instr->suffix) + 1;
            assert(len > 1);
            if (len > (2 << (sizeof(unsigned char) * 8 - 1)))
            {
                log_variadic_message(LMT_ERROR, "ARM suffix too long: '%s' (%zu bytes)", instr->suffix, len);
                result = false;
            }
            else
                result = extend_packed_buffer(pbuf, (unsigned char []) { len }, sizeof(unsigned char), false);
            if (result)
                result = extend_packed_buffer(pbuf, instr->suffix, len, false);
        }
    }
    if (result)
        result = extend_packed_buffer(pbuf, &instr->cond, sizeof(ArmCondCode), true);
    return result;
}