/* Chrysalide - Outil d'analyse de fichiers binaires
* stream.c - gestion des connexions aux serveurs 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 Foobar. If not, see .
*/
#include "stream.h"
#include
#include
#include
#include
#include
#include
#include "gdb-int.h"
#include "stream-int.h"
#include "utils.h"
#include "../../common/dllist.h"
#include "../../core/logs.h"
/* Initialise la classe des flux de communication avec GDB. */
static void g_gdb_stream_class_init(GGdbStreamClass *);
/* Initialise une instance de flux de communication avec GDB. */
static void g_gdb_stream_init(GGdbStream *);
/* Supprime toutes les références externes. */
static void g_gdb_stream_dispose(GGdbStream *);
/* Procède à la libération totale de la mémoire. */
static void g_gdb_stream_finalize(GGdbStream *);
/* Envoie un acquittement pour la dernière réception. */
static bool gdb_stream_ack(GGdbStream *);
/* Ecoute une connexion à un serveur GDB. */
static void *gdb_stream_thread(GGdbStream *);
/* Reste en alerte quant au changement de statut de l'exécution. */
static void *gdb_stream_status_thread(GGdbStream *);
/* Réceptionne un paquet d'un serveur GDB. */
static bool g_gdb_stream_read_packet(GGdbStream *, GGdbPacket *);
/* Indique le type défini pour un flux de communication avec un serveur GDB. */
G_DEFINE_TYPE(GGdbStream, g_gdb_stream, G_TYPE_OBJECT);
/******************************************************************************
* *
* Paramètres : klass = classe à initialiser. *
* *
* Description : Initialise la classe des flux de communication avec GDB. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void g_gdb_stream_class_init(GGdbStreamClass *klass)
{
GObjectClass *object; /* Autre version de la classe */
object = G_OBJECT_CLASS(klass);
object->dispose = (GObjectFinalizeFunc/* ! */)g_gdb_stream_dispose;
object->finalize = (GObjectFinalizeFunc)g_gdb_stream_finalize;
}
/******************************************************************************
* *
* Paramètres : stream = instance à initialiser. *
* *
* Description : Initialise une instance de flux de communication avec GDB. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void g_gdb_stream_init(GGdbStream *stream)
{
g_mutex_init(&stream->free_mutex);
g_cond_init(&stream->recv_cond);
g_mutex_init(&stream->recv_mutex);
g_cond_init(&stream->status_cond);
g_mutex_init(&stream->status_mutex);
stream->skip_ack = false;
stream->want_status = false;
}
/******************************************************************************
* *
* Paramètres : stream = instance d'objet GLib à traiter. *
* *
* Description : Supprime toutes les références externes. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void g_gdb_stream_dispose(GGdbStream *stream)
{
g_object_unref(G_OBJECT(stream->owner));
/* TODO... */
G_OBJECT_CLASS(g_gdb_stream_parent_class)->dispose(G_OBJECT(stream));
}
/******************************************************************************
* *
* Paramètres : stream = instance d'objet GLib à traiter. *
* *
* Description : Procède à la libération totale de la mémoire. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void g_gdb_stream_finalize(GGdbStream *stream)
{
/* TODO */
G_OBJECT_CLASS(g_gdb_stream_parent_class)->finalize(G_OBJECT(stream));
}
/******************************************************************************
* *
* Paramètres : stream = instance à modifier. *
* *
* Description : Ne participe plus aux acquitements de paquets. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
void g_gdb_stream_do_not_ack(GGdbStream *stream)
{
stream->skip_ack = true;
}
/******************************************************************************
* *
* Paramètres : stream = instance à réellement lancer. *
* *
* Description : Lance l'écoute d'un flux de communication avec GDB. *
* *
* Retour : Bilan de l'opération. *
* *
* Remarques : - *
* *
******************************************************************************/
bool g_gdb_stream_listen(GGdbStream *stream)
{
bool result; /* Bilan final à retourner */
result = true;
if (!g_thread_new("chrysalide_gdb_stream", (GThreadFunc)gdb_stream_thread, stream))
result = false;
if (!g_thread_new("chrysalide_gdb_status", (GThreadFunc)gdb_stream_status_thread, stream))
result = false;
return result;
}
/******************************************************************************
* *
* Paramètres : stream = encadrement associée à l'opération. *
* *
* Description : Envoie un acquittement pour la dernière réception. *
* *
* Retour : Bilan de l'opération. *
* *
* Remarques : - *
* *
******************************************************************************/
static bool gdb_stream_ack(GGdbStream *stream)
{
///
//return true;
bool result; /* Bilan à retourner */
GGdbPacket *packet; /* Paquet à envoyer */
packet = g_gdb_stream_get_free_packet(stream);
g_gdb_packet_start_new_command(packet);
g_gdb_packet_append(packet, "+");
result = g_gdb_stream_send_packet(stream, packet);
g_gdb_stream_mark_packet_as_free(stream, packet);
return result;
}
/******************************************************************************
* *
* Paramètres : stream = encadrement associée à l'opération. *
* *
* Description : Ecoute une connexion à un serveur GDB. *
* *
* Retour : ??? *
* *
* Remarques : - *
* *
******************************************************************************/
static void *gdb_stream_thread(GGdbStream *stream)
{
fd_set rfds; /* Liste des flux à surveiller */
int ret; /* Bilan d'un appel */
GGdbPacket *packet; /* Nouveau paquet reçu */
const char *data; /* Données reçues à analyser */
size_t len; /* Quantité de ces données */
while (1)
{
FD_ZERO(&rfds);
FD_SET(stream->fd, &rfds);
ret = select(stream->fd + 1, &rfds, NULL, NULL, NULL);
switch (ret)
{
case -1:
perror("select()");
break;
case 0:
break;
default:
packet = g_gdb_stream_get_free_packet(stream);
g_gdb_packet_start_new_command(packet);
if (g_gdb_stream_read_packet(stream, packet))
{
/* Acquittement ? */
if (!stream->skip_ack)
{
if (!gdb_stream_ack(stream)) goto bad_recv;
}
/* On conserve le résultat ? */
g_gdb_packet_get_data(packet, &data, &len, NULL);
//printf("---------------------------\n");
//printf(">> want status ? %d\n", stream->want_status);
//printf(">> got '%s'\n", data);
if (len >= 1)
{
if (stream->want_status)
stream->want_status = false;
else if (index("STWX", data[0]) != NULL)
{
g_mutex_lock(&stream->status_mutex);
g_gdb_packet_push(&stream->status_packets, packet);
g_mutex_unlock(&stream->status_mutex);
g_cond_signal(&stream->status_cond);
break;
}
// else message inconnu -> log_message() !
}
g_mutex_lock(&stream->recv_mutex);
g_gdb_packet_push(&stream->recv_packets, packet);
g_mutex_unlock(&stream->recv_mutex);
g_cond_signal(&stream->recv_cond);
}
else
g_gdb_stream_mark_packet_as_free(stream, packet);
break;
bad_recv:
printf("bad things happend...\n");
g_gdb_stream_mark_packet_as_free(stream, packet);
break;
}
}
printf("Oh noes....\n");
return NULL;
}
/******************************************************************************
* *
* Paramètres : stream = encadrement associée à l'opération. *
* *
* Description : Reste en alerte quant au changement de statut de l'exécution.*
* *
* Retour : ??? *
* *
* Remarques : - *
* *
******************************************************************************/
static void *gdb_stream_status_thread(GGdbStream *stream)
{
GGdbPacket *packet; /* Nouveau paquet reçu */
const char *data; /* Données reçues à analyser */
size_t len; /* Quantité de ces données */
bool malformed; /* Echec d'interprétation */
uint8_t byte; /* Valeur quelconque sur 8 bits*/
bool ret; /* Bilan d'un appel */
while (1)
{
/* Réception d'un nouveau paquet de statut */
g_mutex_lock(&stream->status_mutex);
if (dl_list_empty(stream->status_packets))
g_cond_wait(&stream->status_cond, &stream->status_mutex);
packet = g_gdb_packet_pop(&stream->status_packets);
g_mutex_unlock(&stream->status_mutex);
/* Traitement du paquet reçu */
g_gdb_packet_get_data(packet, &data, &len, NULL);
malformed = false;
switch (data[0])
{
case 'S':
ret = read_fixed_byte(data + 1, len - 1, &byte);
if (!ret)
{
malformed = true;
goto gsst_processed;
}
g_gdb_debugger_receive_signal_reply(stream->owner, byte);
break;
case 'T':
assert(false); // TODO
break;
case 'W':
ret = read_fixed_byte(data + 1, len - 1, &byte);
if (!ret)
{
malformed = true;
goto gsst_processed;
}
// TODO : ";process:pid"
printf("Program exited (status=%hhu)\n", byte);
g_gdb_debugger_receive_exit_reply(stream->owner, byte, -1);
// log_message en cas de mauvais format...
break;
default:
assert(false);
break;
}
gsst_processed:
if (malformed && true/* TODO : config->show_... */)
log_variadic_message(LMT_WARNING, "Malformed GDB status reply: '%s'", data);
g_gdb_stream_mark_packet_as_free(stream, packet);
}
return NULL;
}
/******************************************************************************
* *
* Paramètres : stream = flux de communication avec GDB à consulter. *
* *
* Description : Fournit un paquet prêt à emploi. *
* *
* Retour : Paquet prêt à emploi. *
* *
* Remarques : - *
* *
******************************************************************************/
GGdbPacket *g_gdb_stream_get_free_packet(GGdbStream *stream)
{
GGdbPacket *result; /* Paquet à retourner */
g_mutex_lock(&stream->free_mutex);
if (dl_list_empty(stream->free_packets))
result = g_gdb_packet_new();
else
result = g_gdb_packet_pop(&stream->free_packets);
g_mutex_unlock(&stream->free_mutex);
// ???
//g_gdb_packet_start_new_command(result);
return result;
}
/******************************************************************************
* *
* Paramètres : stream = flux de communication avec GDB à mettre à jour. *
* packet = paquet à considérer comme disponible. *
* *
* Description : Place un paquet en attente d'une future utilisation. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
void g_gdb_stream_mark_packet_as_free(GGdbStream *stream, GGdbPacket *packet)
{
//// Utile ?
g_gdb_packet_start_new_command(packet);
g_mutex_lock(&stream->free_mutex);
g_gdb_packet_push(&stream->free_packets, packet);
g_mutex_unlock(&stream->free_mutex);
}
/******************************************************************************
* *
* Paramètres : stream = flux ouvert en lecture à utiliser. *
* packet = données à recevoir. *
* *
* Description : Réceptionne un paquet d'un serveur GDB. *
* *
* Retour : Bilan de l'opération. *
* *
* Remarques : - *
* *
******************************************************************************/
static bool g_gdb_stream_read_packet(GGdbStream *stream, GGdbPacket *packet)
{
bool result; /* Bilan à renvoyer */
char tmp[3]; /* Tampon de réception */
uint8_t checksum; /* Contrôle d'intégrité */
do
{
result = stream->recv_byte(stream, tmp);
if (tmp[0] != '+') break;
}
while (0);
if (tmp[0] != '$') return false;
tmp[1] = '\0';
while ((result = stream->recv_byte(stream, tmp)))
{
//printf(" .. '%c'\n", tmp[0]);
if (tmp[0] == '#') break;
else g_gdb_packet_append(packet, tmp);
}
if (result)
{
result = stream->recv_byte(stream, &tmp[0]);
result &= stream->recv_byte(stream, &tmp[1]);
tmp[2] = 0;
checksum = strtol(tmp, NULL, 16);
}
if (result)
result = g_gdb_packet_verify_checksum(packet, checksum);
if (result)
result = g_gdb_packet_decode(packet);
return result;
}
/******************************************************************************
* *
* Paramètres : stream = flux ouvert en écriture à mettre à jour. *
* packet = données à transmettre. *
* *
* Description : Envoie un paquet à un serveur GDB. *
* *
* Retour : Bilan de l'opération. *
* *
* Remarques : - *
* *
******************************************************************************/
#include
bool g_gdb_stream_send_packet(GGdbStream *stream, GGdbPacket *packet)
{
bool result; /* Bilan à renvoyer */
const char *data; /* Données à envoyer */
size_t len; /* Quantité de ces données */
uint8_t checksum; /* Contrôle d'intégrité */
char tmp[3]; /* Impression du checksum */
g_gdb_packet_get_data(packet, &data, &len, NULL);
#if 1
/* Ack ? */
if (len == 1 && data[0] == '+')
result = stream->send_data(stream, "+", 1);
else
#endif
{
result = stream->send_data(stream, "$", 1);
//result = stream->send_data(stream, "+$", 2);
g_gdb_packet_compute_checksum(packet);
g_gdb_packet_get_data(packet, &data, &len, &checksum);
if (len == 1 && data[0] == '?')
stream->want_status = true;
/*
if (memcmp(data, "vCont;c", strlen("vCont;c")) == 0)
stream->want_status = true;
*/
result &= stream->send_data(stream, data, len);
result = stream->send_data(stream, "#", 1);
snprintf(tmp, 3, "%02hhx", checksum);
result &= stream->send_data(stream, tmp, 2);
}
return result;
}
/******************************************************************************
* *
* Paramètres : stream = flux de communication avec GDB à consulter. *
* *
* Description : Fournit un paquet reçu d'un serveur GDB. *
* *
* Retour : Paquet GDB. *
* *
* Remarques : - *
* *
******************************************************************************/
GGdbPacket *g_gdb_stream_recv_packet(GGdbStream *stream)
{
GGdbPacket *result; /* Paquet à retourner */
g_mutex_lock(&stream->recv_mutex);
if (dl_list_empty(stream->recv_packets))
g_cond_wait(&stream->recv_cond, &stream->recv_mutex);
result = g_gdb_packet_pop(&stream->recv_packets);
g_mutex_unlock(&stream->recv_mutex);
return result;
}