/* Chrysalide - Outil d'analyse de fichiers binaires
 * gdb.c - débogage à l'aide de gdb.
 *
 * Copyright (C) 2009-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 Chrysalide.  If not, see <http://www.gnu.org/licenses/>.
 */


#include "gdb.h"



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


#include "gdb-int.h"
#include "helpers.h"
#include "helpers_arm.h"
#include "helpers_arm64.h"
#include "tcp.h"
#include "utils.h"
#include "../../common/cpp.h"
#include "../../format/format.h"








/* Initialise la classe du débogueur utilisant gdb. */
static void g_gdb_debugger_class_init(GGdbDebuggerClass *);

/* Procède à l'initialisation du débogueur utilisant gdb. */
static void g_gdb_debugger_init(GGdbDebugger *);

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

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


/* Met en marche le débogueur utilisant un serveur GDB. */
static bool g_gdb_debugger_run(GGdbDebugger *);

/* Remet en marche le débogueur utilisant un serveur GDB. */
//static bool g_gdb_debugger_resume(GGdbDebugger *);

/* Tue le débogueur utilisant un serveur GDB. */
static bool g_gdb_debugger_kill(GGdbDebugger *);






/* --------------------------- ENTREES / SORTIES BASIQUES --------------------------- */


/* Lit une valeur quelconque à une adresse arbitraire.  */
static bool g_gdb_debugger_read_memory(GGdbDebugger *, virt_t, size_t, ...);

/* Ecrit une valeur quelconque à une adresse arbitraire.  */
static bool g_gdb_debugger_write_memory(GGdbDebugger *, virt_t, size_t, ...);

/* Liste l'ensemble des registres appartenant à un groupe. */
static char **g_gdb_debugger_get_register_names(const GGdbDebugger *, const char *, size_t *);

/* Indique la taille associée à un registre donné. */
static unsigned int g_gdb_debugger_get_register_size(const GGdbDebugger *, const char *);

/* Effectue la lecture d'un registre donné. */
static bool g_gdb_debugger_read_register(GGdbDebugger *, const char *, size_t, ...);

/* Effectue l'écriture d'un registre donné. */
static bool g_gdb_debugger_write_register(GGdbDebugger *, const char *, size_t, ...);



/* ------------------------- MANIPULATION DE L'ETAT COURANT ------------------------- */


/* Détermine le point d'exécution courant. */
static bool g_gdb_debugger_get_current_pc(GGdbDebugger *, virt_t *);

/* Remonte la pile d'appels jusqu'au point courant. */
static bool g_gdb_debugger_compute_call_stack(GGdbDebugger *, virt_t **, size_t *);



/* --------------------------- GESTION DES POINTS D'ARRET --------------------------- */


/* Ajoute un point d'arrêt basique en mémoire. */
static gdb_breakpoint *g_gdb_debugger_enable_memory_breakpoint(GGdbDebugger *, virt_t);

/* Retire un point d'arrêt basique en mémoire. */
static bool g_gdb_debugger_disable_memory_breakpoint(GGdbDebugger *, gdb_breakpoint *);



/* -------------------------- CONTROLE DU FLOT D'EXECUTION -------------------------- */


/* Redémarre le processus de débogage lié à un serveur GDB. */
static bool g_gdb_debugger_restart(GGdbDebugger *);

/* Remet en marche le débogueur utilisant un serveur GDB. */
static bool g_gdb_debugger_resume(GGdbDebugger *);












/* Détermine l'identifiant du thread principal courant. */
static char *g_gdb_debugger_get_active_thread(GGdbDebugger *);






/* ------------------------ ACCUEIL D'EVENEMENTS ASYNCHRONES ------------------------ */








/* Indique le type défini par la GLib pour le débogueur gdb. */
G_DEFINE_TYPE(GGdbDebugger, g_gdb_debugger, G_TYPE_BINARY_DEBUGGER);


/******************************************************************************
*                                                                             *
*  Paramètres  : klass = classe de débogueur à initialiser.                   *
*                                                                             *
*  Description : Initialise la classe du débogueur utilisant gdb.             *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void g_gdb_debugger_class_init(GGdbDebuggerClass *klass)
{
    GObjectClass *object;                   /* Autre version de la classe  */
    GBinaryDebuggerClass *parent;           /* Version en classe parente   */

    object = G_OBJECT_CLASS(klass);

    object->dispose = (GObjectFinalizeFunc/* ! */)g_gdb_debugger_dispose;
    object->finalize = (GObjectFinalizeFunc)g_gdb_debugger_finalize;

    parent = G_BINARY_DEBUGGER_CLASS(klass);

    parent->read_mem = (read_mem_any_fc)g_gdb_debugger_read_memory;
    parent->write_mem = (write_mem_any_fc)g_gdb_debugger_write_memory;
    parent->get_reg_names = (get_reg_names_fc)g_gdb_debugger_get_register_names;
    parent->get_reg_size = (get_reg_size_fc)g_gdb_debugger_get_register_size;
    parent->read_reg = (read_write_reg_any_fc)g_gdb_debugger_read_register;
    parent->write_reg = (read_write_reg_any_fc)g_gdb_debugger_write_register;

    parent->get_current_pc = (get_current_pc_fc)g_gdb_debugger_get_current_pc;
    parent->get_call_stack = (get_call_stack_fc)g_gdb_debugger_compute_call_stack;

    parent->enable_bp = (enable_mem_bp_fc)g_gdb_debugger_enable_memory_breakpoint;
    parent->disable_bp = (disable_mem_bp_fc)g_gdb_debugger_disable_memory_breakpoint;

    parent->restart = (restart_debugger_fc)g_gdb_debugger_restart;
    parent->resume = (resume_debugger_fc)g_gdb_debugger_resume;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : debugger = instance de débogueur à préparer.                 *
*                                                                             *
*  Description : Procède à l'initialisation du débogueur utilisant gdb.       *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void g_gdb_debugger_init(GGdbDebugger *debugger)
{
    GBinaryDebugger *parent;                /* Instance parente            */

    parent = G_BINARY_DEBUGGER(debugger);

    parent->run = (basic_debugger_fc)g_gdb_debugger_run;
    //parent->resume = (resume_debugger_fc)g_gdb_debugger_resume;
    parent->kill = (basic_debugger_fc)g_gdb_debugger_kill;

    //parent->get_reg_values = (get_register_values_fc)get_register_values_using_gdb_debugger;

    //debugger->cond = g_cond_new();
    //debugger->mutex = g_mutex_new();


    // FIXME
    //debugger->compute_cstack = compute_call_stack_for_arm64;
    //debugger->fill_mem_bp = fill_memory_breakpoint_cmd_for_arm64;




}


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

static void g_gdb_debugger_dispose(GGdbDebugger *debugger)
{
    if (debugger->stream != NULL)
        g_object_unref(G_OBJECT(debugger->stream));

    if (debugger->support != NULL)
        g_object_unref(G_OBJECT(debugger->support));

    if (debugger->target != NULL)
        g_object_unref(G_OBJECT(debugger->target));

    G_OBJECT_CLASS(g_gdb_debugger_parent_class)->dispose(G_OBJECT(debugger));

}


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

static void g_gdb_debugger_finalize(GGdbDebugger *debugger)
{
    G_OBJECT_CLASS(g_gdb_debugger_parent_class)->finalize(G_OBJECT(debugger));

}


/******************************************************************************
*                                                                             *
*  Paramètres  : binary = binaire représenter à déboguer.                     *
*                server = nom ou adresse du serveur à contacter.              *
*                port   = port de connexion.                                  *
*                                                                             *
*  Description : Crée un débogueur utilisant un serveur GDB distant.          *
*                                                                             *
*  Retour      : Instance de débogueur mise en place ou NULL.                 *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

GBinaryDebugger *g_gdb_debugger_new(GLoadedBinary *binary, const char *server, unsigned short port)
{
    GGdbDebugger *result;                   /* Débogueur à retourner       */
    GExeFormat *format;                     /* Format du binaire chargé    */
    const char *arch;                       /* Architecture d'exécution    */
    GArchProcessor *proc;                   /* Processeur lié au binaire   */
    char service[sizeof(XSTR(UINT16_MAX)) + 1]; /* Conversion requise      */

    result = g_object_new(G_TYPE_GDB_DEBUGGER, NULL);

    G_BINARY_DEBUGGER(result)->binary = binary;
    g_object_ref(G_OBJECT(binary));

    /* Propriétés de la cible */

    format = g_loaded_binary_get_format(binary);

    result->endian = g_binary_format_get_endianness(G_BIN_FORMAT(format));

    arch = g_exe_format_get_target_machine(format);

    if (strcmp(arch, "armv7") == 0)
        result->ops = get_arm_operations();
    else
        result->ops = NULL;

    g_object_unref(G_OBJECT(format));

    if (result->ops == NULL)
        goto ggdn_error;

    proc = g_loaded_binary_get_processor(binary);

    result->msize = g_arch_processor_get_memory_size(proc);

    g_object_unref(G_OBJECT(proc));

    /* Mise en place des modules auxialiaires */

    snprintf(service, sizeof(service), "%hu", port);

    result->stream = g_gdb_tcp_client_new(server, service, result);
    if (result->stream == NULL) goto ggdn_error;

    result->support = g_gdb_support_new(result->stream);

    result->target = g_gdb_target_new(result->stream);
    if (result->target == NULL) goto ggdn_error;

    return G_BINARY_DEBUGGER(result);

 ggdn_error:

    g_object_unref(G_OBJECT(result));

    return NULL;

}








/******************************************************************************
*                                                                             *
*  Paramètres  : debugger = débogueur à lancer.                               *
*                                                                             *
*  Description : Met en marche le débogueur utilisant un serveur GDB.         *
*                                                                             *
*  Retour      : Bilan de l'opération.                                        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static bool g_gdb_debugger_run(GGdbDebugger *debugger)
{



    GGdbPacket *packet;

    bool test;

    const char *data;
    size_t len;


    int sig;
    vmpa_t addr;
    pid_t thread;


    debugger->stream = g_gdb_tcp_client_new("127.0.0.1", "6666", NULL);
    if (debugger->stream == NULL) return false;


    printf("Connection done !\n");



    packet = g_gdb_stream_get_free_packet(debugger->stream);

    g_gdb_packet_start_new_command(packet);
    g_gdb_packet_append(packet, "?");


    test = g_gdb_stream_send_packet(debugger->stream, packet);



    printf(" >> Paquet '%s' bien envoyé ? %s\n", "?", test ? "oui" : "non");



    g_gdb_stream_mark_packet_as_free(debugger->stream, packet);

    packet = g_gdb_stream_recv_packet(debugger->stream);

    g_gdb_packet_get_data(packet, &data, &len, NULL);

    printf(" << Réception de '%s'\n", data);





    get_stop_reply_sig_info(packet, &sig, &addr, &thread, SRE_LITTLE);

    g_signal_emit_by_name(debugger, "halted", sig, addr, thread);



    return true;

}




/******************************************************************************
*                                                                             *
*  Paramètres  : debugger = débogueur à relancer.                             *
*                                                                             *
*  Description : Tue le débogueur utilisant un serveur GDB.                   *
*                                                                             *
*  Retour      : Bilan de l'opération.                                        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static bool g_gdb_debugger_kill(GGdbDebugger *debugger)
{


#if 0
    int ret;                                /* Bilan de l'appel système    */

    ret = kill(debugger->child, SIGKILL);
    if (ret != 0) perror("kill");

    debugger->child = 0;

    g_mutex_lock(debugger->mutex);
    debugger->run_again = TRUE;
    g_cond_signal(debugger->cond);
    g_mutex_unlock(debugger->mutex);
#endif
    return true;

}









/******************************************************************************
*                                                                             *
*  Paramètres  : debugger = débogueur à consulter.                            *
*                                                                             *
*  Description : Détermine l'identifiant du thread principal courant.         *
*                                                                             *
*  Retour      : Identifiant du thread actif principal ou NULL en cas d'échec.*
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static char *g_gdb_debugger_get_active_thread(GGdbDebugger *debugger)
{
    char *result;                           /* Identifiant à renvoyer      */
    GGdbPacket *packet;                     /* Paquet de communication     */
    bool status;                            /* Bilan d'une communication   */
    const char *data;                       /* Données reçues à analyser   */
    const char *start;                      /* Début d'identification      */
    const char *end;                        /* Fin d'identification        */

    result = NULL;

    /* Envoi de la requête */

    packet = g_gdb_stream_get_free_packet(debugger->stream);

    g_gdb_packet_start_new_command(packet);
    g_gdb_packet_append(packet, "?");

    status = g_gdb_stream_send_packet(debugger->stream, packet);

    if (!status)
        goto ggdgat_exit;

    /* Réception de la réponse */

    packet = g_gdb_stream_recv_packet(debugger->stream);

    g_gdb_packet_get_data(packet, &data, NULL, NULL);

    start = strstr(data, "thread:");
    if (start == NULL) goto ggdgat_exit;

    start += sizeof("thread:") - 1 /* '\0' */;

    end = strstr(start, ";");
    if (end == NULL) goto ggdgat_exit;

    result = strndup(start, end - start);

 ggdgat_exit:

    g_gdb_stream_mark_packet_as_free(debugger->stream, packet);

    return result;

}




















/* ---------------------------------------------------------------------------------- */
/*                             ENTREES / SORTIES BASIQUES                             */
/* ---------------------------------------------------------------------------------- */


/******************************************************************************
*                                                                             *
*  Paramètres  : debugger = débogueur à consulter.                            *
*                addr     = emplacement en mémoire à venir consulter.         *
*                size     = taille des données mises en jeu.                  *
*                ...      = emplacement de la valeur lue à conserver. [OUT]   *
*                                                                             *
*  Description : Lit une valeur quelconque à une adresse arbitraire.          *
*                                                                             *
*  Retour      : Bilan de l'opération.                                        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static bool g_gdb_debugger_read_memory(GGdbDebugger *debugger, virt_t addr, size_t size, ...)
{
    bool result;                            /* Bilan d'opération à renvoyer*/
    char cmd[1 + VMPA_MAX_LEN + 3];         /* Commande à émettre          */
    GGdbPacket *packet;                     /* Paquet de communication     */
    const char *data;                       /* Données reçues à analyser   */
    size_t len;                             /* Quantité de données reçues  */
    va_list ap;                             /* Liste variable d'arguments  */
    uint8_t *val8;                          /* Valeur sur 8 bits           */
    uint16_t *val16;                        /* Valeur sur 16 bits          */
    uint16_t conv16;                        /* Valeur adaptée sur 16 bits  */
    uint32_t *val32;                        /* Valeur sur 32 bits          */
    uint32_t conv32;                        /* Valeur adaptée sur 32 bits  */
    uint64_t *val64;                        /* Valeur sur 64 bits          */
    uint64_t conv64;                        /* Valeur adaptée sur 64 bits  */

    /* Envoi de la requête */

    cmd[0] = 'm';

    result = translate_virt_to_hex(debugger, addr, &cmd[1]);

    switch (size)
    {
        case 8:
            strcat(cmd, ",1");
            break;

        case 16:
            strcat(cmd, ",2");
            break;

        case 32:
            strcat(cmd, ",4");
            break;

        case 64:
            strcat(cmd, ",8");
            break;

        default:
            assert(false);
            result = false;
            goto ggdrm_exit;
            break;

    }

    packet = g_gdb_stream_get_free_packet(debugger->stream);

    g_gdb_packet_start_new_command(packet);
    g_gdb_packet_append(packet, cmd);

    result = g_gdb_stream_send_packet(debugger->stream, packet);

    g_gdb_stream_mark_packet_as_free(debugger->stream, packet);

    if (!result)
        goto ggdrm_exit;

    /* Réception de la réponse */

    packet = g_gdb_stream_recv_packet(debugger->stream);

    g_gdb_packet_get_data(packet, &data, &len, NULL);

    if (is_error_code(data, len))
    {
        result = false;
        goto ggdrm_error;
    }

    va_start(ap, size);

    switch (size)
    {
        case 8:
            val8 = va_arg(ap, uint8_t *);
            result = hex_to_u8(data, val8);
            break;

        case 16:
            val16 = va_arg(ap, uint16_t *);
            result = hex_to_u16(data, &conv16);
            *val16 = from_u16(&conv16, debugger->endian);
            break;

        case 32:
            val32 = va_arg(ap, uint32_t *);
            result = hex_to_u32(data, &conv32);
            *val32 = from_u32(&conv32, debugger->endian);
            break;

        case 64:
            val64 = va_arg(ap, uint64_t *);
            result = hex_to_u64(data, &conv64);
            *val64 = from_u64(&conv64, debugger->endian);
            break;

        default:
            assert(false);
            result = false;
            break;

    }

    va_end(ap);

 ggdrm_error:

    g_gdb_stream_mark_packet_as_free(debugger->stream, packet);

 ggdrm_exit:

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : debugger = débogueur à manipuler.                            *
*                addr     = emplacement en mémoire à venir consulter.         *
*                size     = taille des données mises en jeu.                  *
*                ...      = emplacement de la valeur lue à conserver. [OUT]   *
*                                                                             *
*  Description : Lit une valeur quelconque à une adresse arbitraire.          *
*                                                                             *
*  Retour      : Bilan de l'opération.                                        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static bool g_gdb_debugger_write_memory(GGdbDebugger *debugger, virt_t addr, size_t size, ...)
{
    bool result;                            /* Bilan d'opération à renvoyer*/
    char cmd[1 + 3 * VMPA_MAX_LEN + 3];     /* Commande à émettre          */
    va_list ap;                             /* Liste variable d'arguments  */
    const uint8_t *val8;                    /* Valeur sur 8 bits           */
    const uint16_t *val16;                  /* Valeur sur 16 bits          */
    uint16_t conv16;                        /* Valeur adaptée sur 16 bits  */
    const uint32_t *val32;                  /* Valeur sur 32 bits          */
    uint32_t conv32;                        /* Valeur adaptée sur 32 bits  */
    const uint64_t *val64;                  /* Valeur sur 64 bits          */
    uint64_t conv64;                        /* Valeur adaptée sur 64 bits  */
    char hexval[17];                        /* Valeur sous forme hexa      */
    GGdbPacket *packet;                     /* Paquet de communication     */
    const char *data;                       /* Données reçues à analyser   */
    size_t len;                             /* Quantité de données reçues  */

    /* Envoi de la requête */

    cmd[0] = 'M';

    result = translate_virt_to_hex(debugger, addr, &cmd[1]);

    va_start(ap, size);

    switch (size)
    {
        case 8:
            val8 = va_arg(ap, uint8_t *);
            result = u8_to_hex(val8, hexval);

            strcat(cmd, ",1:");
            strcat(cmd, hexval);
            break;

        case 16:
            val16 = va_arg(ap, uint16_t *);
            conv16 = to_u16(val16, debugger->endian);
            result = u16_to_hex(&conv16, hexval);

            strcat(cmd, ",2:");
            strcat(cmd, hexval);
            break;

        case 32:
            val32 = va_arg(ap, uint32_t *);
            conv32 = to_u32(val32, debugger->endian);
            result = u32_to_hex(&conv32, hexval);

            strcat(cmd, ",4:");
            strcat(cmd, hexval);
            break;

        case 64:
            val64 = va_arg(ap, uint64_t *);
            conv64 = to_u64(val64, debugger->endian);
            result = u16_to_hex(&conv64, hexval);

            strcat(cmd, ",8:");
            strcat(cmd, hexval);
            break;

        default:
            assert(false);
            result = false;
            break;

    }

    if (!result)
        goto ggdwm_exit;

    packet = g_gdb_stream_get_free_packet(debugger->stream);

    g_gdb_packet_start_new_command(packet);
    g_gdb_packet_append(packet, cmd);

    result = g_gdb_stream_send_packet(debugger->stream, packet);

    g_gdb_stream_mark_packet_as_free(debugger->stream, packet);

    if (!result)
        goto ggdwm_exit;

    /* Réception de la réponse */

    packet = g_gdb_stream_recv_packet(debugger->stream);

    g_gdb_packet_get_data(packet, &data, &len, NULL);

    if (len == 3 && data[0] == 'E')
    {
        result = false;
        goto ggdrm_error;
    }

 ggdrm_error:

    g_gdb_stream_mark_packet_as_free(debugger->stream, packet);

 ggdwm_exit:

    va_end(ap);

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : debugger = débogueur à consulter.                            *
*                group    = éventuel groupe de registres ciblé ou NULL.       *
*                count    = nombre d'éléments dans la liste de noms. [OUT]    *
*                                                                             *
*  Description : Liste l'ensemble des registres appartenant à un groupe.      *
*                                                                             *
*  Retour      : Liste de noms à libérer de la mémoire après utilisation.     *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static char **g_gdb_debugger_get_register_names(const GGdbDebugger *debugger, const char *group, size_t *count)
{
    return g_gdb_target_get_register_names(debugger->target, group, count);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : debugger = débogueur à consulter.                            *
*                name     = désignation du registre visé.                     *
*                                                                             *
*  Description : Indique la taille associée à un registre donné.              *
*                                                                             *
*  Retour      : Taille en bits, ou 0 si le registre n'a pas été trouvé.      *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static unsigned int g_gdb_debugger_get_register_size(const GGdbDebugger *debugger, const char *name)
{
    return g_gdb_target_get_register_size(debugger->target, name);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : debugger = débogueur à consulter.                            *
*                reg      = désignation humaine du register à consulter.      *
*                size     = taille des données mises en jeu.                  *
*                ...      = emplacement de la valeur lue à conserver. [OUT]   *
*                                                                             *
*  Description : Effectue la lecture d'un registre donné.                     *
*                                                                             *
*  Retour      : Bilan de l'opération.                                        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static bool g_gdb_debugger_read_register(GGdbDebugger *debugger, const char *reg, size_t size, ...)
{
    bool result;                            /* Bilan d'opération à renvoyer*/
    va_list ap;                             /* Liste variable d'arguments  */
    uint8_t *val8;                          /* Valeur sur 8 bits           */
    uint16_t *val16;                        /* Valeur sur 16 bits          */
    uint32_t *val32;                        /* Valeur sur 32 bits          */
    uint64_t *val64;                        /* Valeur sur 64 bits          */

    va_start(ap, size);

    switch (size)
    {
        case 8:
            val8 = va_arg(ap, uint8_t *);
            result = g_gdb_target_read_register(debugger->target, debugger->stream, debugger->endian,
                                                reg, 8, val8);
            break;

        case 16:
            val16 = va_arg(ap, uint16_t *);
            result = g_gdb_target_read_register(debugger->target, debugger->stream, debugger->endian,
                                                reg, 16, val16);
            break;

        case 32:
            val32 = va_arg(ap, uint32_t *);
            result = g_gdb_target_read_register(debugger->target, debugger->stream, debugger->endian,
                                                reg, 32, val32);
            break;

        case 64:
            val64 = va_arg(ap, uint64_t *);
            result = g_gdb_target_read_register(debugger->target, debugger->stream, debugger->endian,
                                                reg, 64, val64);
            break;

        default:
            assert(false);
            result = false;
            break;

    }

    va_end(ap);

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : debugger = débogueur à manipuler.                            *
*                reg      = désignation humaine du register à consulter.      *
*                size     = taille des données mises en jeu.                  *
*                ...      = emplacement de la valeur à écrire.                *
*                                                                             *
*  Description : Effectue l'écriture d'un registre donné.                     *
*                                                                             *
*  Retour      : Bilan de l'opération.                                        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static bool g_gdb_debugger_write_register(GGdbDebugger *debugger, const char *reg, size_t size, ...)
{
    bool result;                            /* Bilan d'opération à renvoyer*/
    va_list ap;                             /* Liste variable d'arguments  */
    const uint8_t *val8;                    /* Valeur sur 8 bits           */
    const uint16_t *val16;                  /* Valeur sur 16 bits          */
    const uint32_t *val32;                  /* Valeur sur 32 bits          */
    const uint64_t *val64;                  /* Valeur sur 64 bits          */

    va_start(ap, size);

    switch (size)
    {
        case 8:
            val8 = va_arg(ap, const uint8_t *);
            result = g_gdb_target_write_register(debugger->target, debugger->stream, debugger->endian,
                                                 reg, 8, val8);
            break;

        case 16:
            val16 = va_arg(ap, const uint16_t *);
            result = g_gdb_target_write_register(debugger->target, debugger->stream, debugger->endian,
                                                 reg, 16, val16);
            break;

        case 32:
            val32 = va_arg(ap, const uint32_t *);
            result = g_gdb_target_write_register(debugger->target, debugger->stream, debugger->endian,
                                                 reg, 32, val32);
            break;

        case 64:
            val64 = va_arg(ap, const uint64_t *);
            result = g_gdb_target_write_register(debugger->target, debugger->stream, debugger->endian,
                                                 reg, 64, val64);
            break;

        default:
            assert(false);
            result = false;
            break;

    }

    va_end(ap);

    return result;

}



/* ---------------------------------------------------------------------------------- */
/*                           MANIPULATION DE L'ETAT COURANT                           */
/* ---------------------------------------------------------------------------------- */


/******************************************************************************
*                                                                             *
*  Paramètres  : debugger = débogueur à consulter.                            *
*                pc       = adresse de l'instruction courante. [OUT]          *
*                                                                             *
*  Description : Détermine le point d'exécution courant.                      *
*                                                                             *
*  Retour      : Bilan de la récupération.                                    *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static bool g_gdb_debugger_get_current_pc(GGdbDebugger *debugger, virt_t *pc)
{
    bool result;                            /* Bilan à retourner           */

    result = debugger->ops->get_pc(debugger, pc);

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : debugger  = débogueur à consulter.                           *
*                callstack = pile d'appels reconstituée. [OUT]                *
*                size      = taille de cette pile. [OUT]                      *
*                                                                             *
*  Description : Remonte la pile d'appels jusqu'au point courant.             *
*                                                                             *
*  Retour      : Bilan de l'opération.                                        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static bool g_gdb_debugger_compute_call_stack(GGdbDebugger *debugger, virt_t **callstack, size_t *size)
{
    bool result;                            /* Bilan global à retourner    */

    if (debugger->ops->compute_cstack != NULL)
        result = debugger->ops->compute_cstack(debugger, callstack, size);

    else
        result = false;

    return result;

}



/* ---------------------------------------------------------------------------------- */
/*                             GESTION DES POINTS D'ARRET                             */
/* ---------------------------------------------------------------------------------- */


/******************************************************************************
*                                                                             *
*  Paramètres  : debugger = débogueur à manipuler ici.                        *
*                addr     = emplacement du point mémoire à traiter.           *
*                                                                             *
*  Description : Ajoute un point d'arrêt basique en mémoire.                  *
*                                                                             *
*  Retour      : Structure de suivi mise en place pour l'occasion, voire NULL.*
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static gdb_breakpoint *g_gdb_debugger_enable_memory_breakpoint(GGdbDebugger *debugger, virt_t addr)
{
    gdb_breakpoint *result;                 /* Nouveau suivi à retourner   */
    char cmd[3 + VMPA_MAX_LEN + 3];         /* Commande à émettre          */
    bool status;                            /* Bilan d'une opération       */
    const char *kind;                       /* Taille spécifique du point  */
    GGdbPacket *packet;                     /* Paquet de communication     */
    const char *data;                       /* Données reçues à analyser   */
    size_t len;                             /* Quantité de données reçues  */
    GBinaryDebugger *dbg;                   /* Autre version du débogueur  */
    const uint8_t *bp;                      /* Données du point d'arrêt    */
    size_t bp_len;                          /* Quantité de ces données     */
    uint8_t memory[16];                     /* Sauvegarde de la mémoire    */

    result = NULL;

    /* Si l'utilisation de la commande dédiée est possible */
    if (1)  //////// TODO
    {
        /* Envoi de la requête */

        strcpy(cmd, "Z0,");

        status = translate_virt_to_hex(debugger, addr, &cmd[3]);

        if (!status)
            goto ggdemb_exit;

        kind = debugger->ops->get_bp_kind(debugger, addr);

        if (kind == NULL)
            goto ggdemb_exit;

        strcat(cmd, kind);

        packet = g_gdb_stream_get_free_packet(debugger->stream);

        g_gdb_packet_start_new_command(packet);
        g_gdb_packet_append(packet, cmd);

        status = g_gdb_stream_send_packet(debugger->stream, packet);

        g_gdb_stream_mark_packet_as_free(debugger->stream, packet);

        if (!status)
            goto ggdemb_exit;

        /* Réception de la réponse */

        packet = g_gdb_stream_recv_packet(debugger->stream);

        g_gdb_packet_get_data(packet, &data, &len, NULL);

        if (is_error_code(data, len))
        {
            g_gdb_stream_mark_packet_as_free(debugger->stream, packet);
            goto ggdemb_fallback;
        }

        if (strcmp(data, "OK") != 0)
        {
            g_gdb_stream_mark_packet_as_free(debugger->stream, packet);
            goto ggdemb_fallback;
        }

        g_gdb_stream_mark_packet_as_free(debugger->stream, packet);

        /* Constitution d'un dossier de suivi */

        result = (gdb_breakpoint *)malloc(sizeof(gdb_breakpoint));

        result->is_z = true;

        result->kind = kind;

    }

    else
    {

 ggdemb_fallback:

        dbg = G_BINARY_DEBUGGER(debugger);

        /* Détermination du point d'arrêt */

        bp = debugger->ops->get_bp_data(debugger, addr, &bp_len);

        assert(bp_len <= 16);

        /* Sauvegarde de la mémoire courante */

        status = g_binary_debugger_read_memory_data(dbg, addr, memory, bp_len);

        if (!status) goto ggdemb_exit;

        /* Application du point d'arrêt */

        status = g_binary_debugger_write_memory_data(dbg, addr, bp, bp_len);

        if (!status) goto ggdemb_exit;

        /* Constitution d'un dossier de suivi */

        result = (gdb_breakpoint *)malloc(sizeof(gdb_breakpoint));

        result->is_z = false;

        memcpy(result->memory, memory, bp_len);
        result->len = bp_len;

    }

    init_raw_breakpoint((raw_breakpoint *)result, addr);

 ggdemb_exit:

    return result;
}


/******************************************************************************
*                                                                             *
*  Paramètres  : debugger = débogueur à manipuler ici.                        *
*                bp       = point d'arrêt à traiter.                          *
*                                                                             *
*  Description : Retire un point d'arrêt basique de la mémoire ciblée.        *
*                                                                             *
*  Retour      : Bilan de l'opération.                                        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static bool g_gdb_debugger_disable_memory_breakpoint(GGdbDebugger *debugger, gdb_breakpoint *bp)
{
    bool result;                            /* Bilan à retourner           */
    char cmd[3 + VMPA_MAX_LEN + 3];         /* Commande à émettre          */
    bool status;                            /* Bilan d'une opération       */
    GGdbPacket *packet;                     /* Paquet de communication     */
    const char *data;                       /* Données reçues à analyser   */
    size_t len;                             /* Quantité de données reçues  */
    GBinaryDebugger *dbg;                   /* Autre version du débogueur  */

    result = false;

    /* Si l'utilisation de la commande dédiée est requise */
    if (bp->is_z)
    {
        /* Envoi de la requête */

        strcpy(cmd, "z0,");

        status = translate_virt_to_hex(debugger, bp->raw.addr, &cmd[3]);

        if (!status)
            goto ggddmb_exit;

        strcat(cmd, bp->kind);

        packet = g_gdb_stream_get_free_packet(debugger->stream);

        g_gdb_packet_start_new_command(packet);
        g_gdb_packet_append(packet, cmd);

        status = g_gdb_stream_send_packet(debugger->stream, packet);

        g_gdb_stream_mark_packet_as_free(debugger->stream, packet);

        if (!status)
            goto ggddmb_exit;

        /* Réception de la réponse */

        packet = g_gdb_stream_recv_packet(debugger->stream);

        g_gdb_packet_get_data(packet, &data, &len, NULL);

        if (is_error_code(data, len))
        {
            g_gdb_stream_mark_packet_as_free(debugger->stream, packet);
            goto ggddmb_exit;
        }

        if (strcmp(data, "OK") != 0)
        {
            g_gdb_stream_mark_packet_as_free(debugger->stream, packet);
            goto ggddmb_exit;
        }

        g_gdb_stream_mark_packet_as_free(debugger->stream, packet);

        result = true;

    }

    else
    {
        dbg = G_BINARY_DEBUGGER(debugger);

        result = g_binary_debugger_write_memory_data(dbg, bp->raw.addr, bp->memory, bp->len);

    }

 ggddmb_exit:

    return result;

}



/* ---------------------------------------------------------------------------------- */
/*                            CONTROLE DU FLOT D'EXECUTION                            */
/* ---------------------------------------------------------------------------------- */


/******************************************************************************
*                                                                             *
*  Paramètres  : debugger = débogueur à redémarrer.                           *
*                                                                             *
*  Description : Redémarre le processus de débogage lié à un serveur GDB.     *
*                                                                             *
*  Retour      : Bilan de l'opération.                                        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static bool g_gdb_debugger_restart(GGdbDebugger *debugger)
{
    bool result;                            /* Bilan à retourner           */
    GGdbPacket *packet;                     /* Paquet de communication     */
    return true;
    /* Envoi de la requête */

    packet = g_gdb_stream_get_free_packet(debugger->stream);

    g_gdb_packet_start_new_command(packet);
    g_gdb_packet_append(packet, "R00");

    result = g_gdb_stream_send_packet(debugger->stream, packet);

    g_gdb_stream_mark_packet_as_free(debugger->stream, packet);

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : debugger = débogueur à relancer.                             *
*                                                                             *
*  Description : Remet en marche le débogueur utilisant un serveur GDB.       *
*                                                                             *
*  Retour      : Bilan de l'opération.                                        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static bool g_gdb_debugger_resume(GGdbDebugger *debugger)
{
    bool result;                            /* Bilan à retourner           */
    //char *id;                               /* Identifiant de thread       */
    GGdbPacket *packet;                     /* Paquet de communication     */
    //const char *data;                       /* Données reçues à analyser   */

    static bool _twice = false;


    if (!_twice && 0)
    {

    packet = g_gdb_stream_get_free_packet(debugger->stream);

    g_gdb_packet_start_new_command(packet);
    g_gdb_packet_append(packet, "$QPassSignals:e;10;14;17;1a;1b;1c;21;24;25;2c;4c;");

    result = g_gdb_stream_send_packet(debugger->stream, packet);

    g_gdb_stream_mark_packet_as_free(debugger->stream, packet);

    if (!result)
        goto ggdhmb_exit;

    }






    /* Envoi de la requête */

    /*
    id = g_gdb_debugger_get_active_thread(debugger);
    if (id == NULL) return false;

    printf("ID : %s\n", id);
    */

    /*
    id = g_gdb_support_get_id(debugger->support);
    if (id == NULL) return false;

    printf("ID : %s\n", id);
    */

    packet = g_gdb_stream_get_free_packet(debugger->stream);

    g_gdb_packet_start_new_command(packet);
    g_gdb_packet_append(packet, "vCont;c:-1");
    //g_gdb_packet_append(packet, "vCont;c:p256f.-1");


    /*
    if (_twice)
    {
    g_gdb_packet_start_new_command(packet);
    g_gdb_packet_append(packet, "vCont;c:p");
    g_gdb_packet_append(packet, id);
    g_gdb_packet_append(packet, ".");
    g_gdb_packet_append(packet, id);
    }
    else
    {
        _twice = true;
    g_gdb_packet_start_new_command(packet);
    g_gdb_packet_append(packet, "vCont;c:p");
    g_gdb_packet_append(packet, id);
    g_gdb_packet_append(packet, ".-1");
    }
    */





    result = g_gdb_stream_send_packet(debugger->stream, packet);

    g_gdb_stream_mark_packet_as_free(debugger->stream, packet);

    if (!result)
        goto ggdhmb_exit;

    /* Réception de la réponse */
    /*
    packet = g_gdb_stream_recv_packet(debugger->stream);

    g_gdb_packet_get_data(packet, &data, NULL, NULL);

    printf("Ack cont...\n");

    //result = (strcmp(data, "OK") == 0);

    g_gdb_stream_mark_packet_as_free(debugger->stream, packet);
    */
 ggdhmb_exit:

    _twice = true;

    return result;

}














/* ---------------------------------------------------------------------------------- */
/*                          ACCUEIL D'EVENEMENTS ASYNCHRONES                          */
/* ---------------------------------------------------------------------------------- */


/******************************************************************************
*                                                                             *
*  Paramètres  : debugger = instance liée à un débogueur GDB à manipuler.     *
*                signum   = indentifiant du signal concerné.                  *
*                                                                             *
*  Description : Réagit à la réception d'un signal par le programme étudié.   *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

void g_gdb_debugger_receive_signal_reply(GGdbDebugger *debugger, int signum)
{
    virt_t pc;                              /* Position courante du CPU    */
    bool status;                            /* Bilan d'une opération       */
    GBinaryDebugger *base;                  /* Version basique du débogueur*/

    base = G_BINARY_DEBUGGER(debugger);

    status = g_binary_debugger_get_current_pc(base, &pc);

    if (!status)
        pc = VMPA_NO_VIRTUAL;

    on_binary_debugger_stopped(base, pc);

    g_signal_emit_by_name(debugger, "signaled", signum);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : debugger = instance liée à un débogueur GDB à manipuler.     *
*                status   = indication d'état à la sortie.                    *
*                pid      = éventuel identifiant de processus concerné ou -1. *
*                                                                             *
*  Description : Réagit à la sortie d'exécution d'un programme étudié.        *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

void g_gdb_debugger_receive_exit_reply(GGdbDebugger *debugger, int status, pid_t pid)
{
    GBinaryDebugger *base;                  /* Version basique du débogueur*/

    base = G_BINARY_DEBUGGER(debugger);

    on_binary_debugger_finished(base, pid);

    g_signal_emit_by_name(debugger, "exited", status, pid);

}