/* Chrysalide - Outil d'analyse de fichiers binaires * target.c - gestion des éléments propres à l'architecture reconnue par GDB * * Copyright (C) 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 "target.h" #include #include #include #include #include #include "utils.h" #include "../../common/cpp.h" #include "../../common/extstr.h" #include "../../common/xml.h" /* Définitions de registres */ typedef struct _arch_register_t { char *name; /* Nom de registre */ unsigned int size; /* Taille en bits */ } arch_register_t; typedef struct _target_cpu_t { char *label; /* Désignation de l'ensemble */ arch_register_t *regs; /* Définition des registres */ unsigned int count; /* Quantité de ces définitions */ } target_cpu_t; /* Indications quant à l'interfaçage client/serveur GDB (instance) */ struct _GGdbTarget { GObject parent; /* A laisser en premier */ target_cpu_t **defs; /* Liste de définitions */ size_t count; /* Taille de cette même liste */ bool read_single_register; /* Lecture spécifique permise ?*/ bool write_single_register; /* Ecriture spécifique valide ?*/ }; /* Indications quant à l'interfaçage client/serveur GDB (classe) */ struct _GGdbTargetClass { GObjectClass parent; /* A laisser en premier */ }; /* Initialise la classe des détails d'interfaçage GDB. */ static void g_gdb_target_class_init(GGdbTargetClass *); /* Procède à l'initialisation des détails d'interfaçage GDB. */ static void g_gdb_target_init(GGdbTarget *); /* Supprime toutes les références externes. */ static void g_gdb_target_dispose(GGdbTarget *); /* Procède à la libération totale de la mémoire. */ static void g_gdb_target_finalize(GGdbTarget *); /* Charge la définition d'un groupe de registres. */ static bool g_gdb_target_load_register_definition(GGdbTarget *, GGdbStream *, const char *); /* Recherche l'indice correspondant à un registre donné. */ static bool g_gdb_target_find_register_index(const GGdbTarget *, const char *, unsigned int *); /* Recherche la position correspondant à un registre donné. */ static bool g_gdb_target_find_register_offset(const GGdbTarget *, unsigned int, size_t *); /* Indique le type défini par la GLib pour les détails d'interfaçage GDB. */ G_DEFINE_TYPE(GGdbTarget, g_gdb_target, G_TYPE_OBJECT); /****************************************************************************** * * * Paramètres : klass = classe de débogueur à initialiser. * * * * Description : Initialise la classe des détails d'interfaçage GDB. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_gdb_target_class_init(GGdbTargetClass *klass) { GObjectClass *object; /* Autre version de la classe */ object = G_OBJECT_CLASS(klass); object->dispose = (GObjectFinalizeFunc/* ! */)g_gdb_target_dispose; object->finalize = (GObjectFinalizeFunc)g_gdb_target_finalize; } /****************************************************************************** * * * Paramètres : target = instance de débogueur à préparer. * * * * Description : Procède à l'initialisation des détails d'interfaçage GDB. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_gdb_target_init(GGdbTarget *target) { target->defs = NULL; target->count = 0; target->read_single_register = true; target->write_single_register = true; } /****************************************************************************** * * * Paramètres : target = instance d'objet GLib à traiter. * * * * Description : Supprime toutes les références externes. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_gdb_target_dispose(GGdbTarget *target) { G_OBJECT_CLASS(g_gdb_target_parent_class)->dispose(G_OBJECT(target)); } /****************************************************************************** * * * Paramètres : target = instance d'objet GLib à traiter. * * * * Description : Procède à la libération totale de la mémoire. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_gdb_target_finalize(GGdbTarget *target) { G_OBJECT_CLASS(g_gdb_target_parent_class)->finalize(G_OBJECT(target)); } /****************************************************************************** * * * Paramètres : stream = flux de communication ouvert avec le débogueur. * * * * Description : Crée une définition des détails d'interfaçage GDB. * * * * Retour : Instance de détails mise en place ou NULL. * * * * Remarques : - * * * ******************************************************************************/ GGdbTarget *g_gdb_target_new(GGdbStream *stream) { GGdbTarget *result; /* Débogueur à retourner */ GGdbPacket *packet; /* Paquet de communication GDB */ bool status; /* Bilan d'une communication */ const char *data; /* Données reçues du serveur */ size_t len; /* Quantité de ces données */ char *xmldata; /* Données modifiables */ xmlDocPtr xdoc; /* Document XML récupéré */ xmlXPathContextPtr context; /* Contexte d'analyse associé */ xmlXPathObjectPtr xobject; /* Cible d'une recherche */ unsigned int i; /* Boucle de parcours */ char *access; /* Chemin d'accès à un élément */ char *xmlref; /* Référence de définitions */ result = NULL; //goto end; //goto skip; packet = g_gdb_stream_get_free_packet(stream); g_gdb_packet_start_new_command(packet); //g_gdb_packet_append(packet, "qTargeted:multiprocess+;xmlRegisters"); g_gdb_packet_append(packet, "qXfer:features:read:target.xml:0,3fff"); //g_gdb_packet_append(packet, "qTargeted:multiprocess+;swbreak+;hwbreak+;qRelocInsn+;fork-events+;vfork-events+;exec-events+;vContTargeted+;QThreadEvents+;no-resumed+"); status = g_gdb_stream_send_packet(stream, packet); if (!status) goto ggtn_failed; g_gdb_stream_mark_packet_as_free(stream, packet); packet = g_gdb_stream_recv_packet(stream); g_gdb_packet_get_data(packet, &data, &len, NULL); printf(" << Réception de '%s'\n", data); /* Marqueur de fin placé au début ?! */ if (data[0] != 'l') goto ggtn_failed; xmldata = strdup(data + 1); /** * On cherche à éviter la déconvenue suivante avec la libxml2 : * * noname.xml:12: namespace error : Namespace prefix xi on include is not defined * */ xmldata = strrpl(xmldata, "xi:include", "include"); if (!load_xml_from_memory(xmldata, len - 1, &xdoc, &context)) goto ggtn_failed; result = g_object_new(G_TYPE_GDB_TARGET, NULL); xobject = get_node_xpath_object(context, "/target/include"); for (i = 0; i < XPATH_OBJ_NODES_COUNT(xobject); i++) { asprintf(&access, "/target/include[position()=%u]", i + 1); xmlref = get_node_prop_value(context, access, "href"); free(access); if (xmlref != NULL) { printf("REF>> %s\n", xmlref); /*static bool */g_gdb_target_load_register_definition(result, stream, xmlref); free(xmlref); } } if(xobject != NULL) xmlXPathFreeObject(xobject); close_xml_file(xdoc, context); free(xmldata); //result = g_object_new(G_TYPE_GDB_TARGET, NULL); ggtn_failed: g_gdb_stream_mark_packet_as_free(stream, packet); return result; } /****************************************************************************** * * * Paramètres : target = ensemble d'informations liées à l'architecture. * * stream = flux de communication ouvert avec le débogueur. * * name = désignation des définitions de registres à charger. * * * * Description : Charge la définition d'un groupe de registres. * * * * Retour : Bilan de l'opération. * * * * Remarques : - * * * ******************************************************************************/ static bool g_gdb_target_load_register_definition(GGdbTarget *target, GGdbStream *stream, const char *name) { bool result; /* Bilan à retourner */ GGdbPacket *packet; /* Paquet de communication GDB */ bool status; /* Bilan d'une communication */ const char *data; /* Données reçues du serveur */ size_t len; /* Quantité de ces données */ xmlDocPtr xdoc; /* Document XML récupéré */ xmlXPathContextPtr context; /* Contexte d'analyse associé */ xmlXPathObjectPtr xobject; /* Cible d'une recherche */ target_cpu_t *def; /* Nouvelle définition à lire */ unsigned int i; /* Boucle de parcours */ char *access; /* Chemin d'accès à un élément */ char *type; /* Espèce de définition */ result = false; /* Envoi de la requête */ packet = g_gdb_stream_get_free_packet(stream); g_gdb_packet_start_new_command(packet); g_gdb_packet_append(packet, "qXfer:features:read:"); g_gdb_packet_append(packet, name); g_gdb_packet_append(packet, ":0,3fff"); status = g_gdb_stream_send_packet(stream, packet); if (!status) goto ggtlrd_failed; g_gdb_stream_mark_packet_as_free(stream, packet); /* Réception de la réponse */ packet = g_gdb_stream_recv_packet(stream); g_gdb_packet_get_data(packet, &data, &len, NULL); //printf(">>>> '%s'\n", data); /* Marqueur de fin placé au début ?! */ if (data[0] != 'l') goto ggtlrd_failed; if (!load_xml_from_memory(data + 1, len - 1, &xdoc, &context)) goto ggtlrd_failed; /* Chargement des définitions */ xobject = get_node_xpath_object(context, "/feature/*"); def = (target_cpu_t *)calloc(1, sizeof(target_cpu_t)); def->count = XPATH_OBJ_NODES_COUNT(xobject); def->regs = (arch_register_t *)calloc(def->count, sizeof(arch_register_t)); for (i = 0; i < XPATH_OBJ_NODES_COUNT(xobject); i++) { asprintf(&access, "/feature/*[position()=%u]", i + 1); type = get_node_name(context, access); if (strcmp(type, "reg") == 0) { def->regs[i].name = get_node_prop_value(context, access, "name"); def->regs[i].size = atoi(get_node_prop_value(context, access, "bitsize")); //printf("load reg '%s' (%u)\n", def->regs[i].name, def->regs[i].size); } free(type); free(access); } if(xobject != NULL) xmlXPathFreeObject(xobject); close_xml_file(xdoc, context); /* Intégration finale */ target->defs = (target_cpu_t **)realloc(target->defs, ++target->count * sizeof(target_cpu_t *)); target->defs[target->count - 1] = def; ggtlrd_failed: g_gdb_stream_mark_packet_as_free(stream, packet); return result; } /****************************************************************************** * * * Paramètres : target = ensemble d'informations liées à l'architecture. * * 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 : - * * * ******************************************************************************/ char **g_gdb_target_get_register_names(const GGdbTarget *target, const char *group, size_t *count) { char **result; /* Désignations à retourner */ unsigned int i; /* Boucle de parcours #1 */ const target_cpu_t *rgrp; /* Groupe de registres */ unsigned int j; /* Boucle de parcours #2 */ result = NULL; for (i = 0; i < target->count && result == NULL; i++) { rgrp = target->defs[i]; if (group != NULL) { if (strcmp(rgrp->label, group) != 0) continue; } *count = rgrp->count; result = (char **)calloc(*count, sizeof(char *)); for (j = 0; j < *count; j++) result[j] = strdup(rgrp->regs[j].name); } return result; } /****************************************************************************** * * * Paramètres : target = ensemble d'informations liées à l'architecture. * * 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 : - * * * ******************************************************************************/ unsigned int g_gdb_target_get_register_size(const GGdbTarget *target, const char *name) { unsigned int result; /* Taille en bits à retourner */ unsigned int i; /* Boucle de parcours #1 */ const target_cpu_t *rgrp; /* Groupe de registres */ unsigned int j; /* Boucle de parcours #2 */ result = 0; for (i = 0; i < target->count && result == 0; i++) { rgrp = target->defs[i]; for (j = 0; j < rgrp->count; j++) if (strcmp(rgrp->regs[j].name, name) == 0) result = rgrp->regs[j].size; } return result; } /****************************************************************************** * * * Paramètres : target = ensemble d'informations liées à l'architecture. * * reg = désignation humaine du register à consulter. * * index = indice correspondant au registre pour GDB. [OUT] * * * * Description : Recherche l'indice correspondant à un registre donné. * * * * Retour : Bilan de l'opération : trouvaille ou échec ? * * * * Remarques : - * * * ******************************************************************************/ static bool g_gdb_target_find_register_index(const GGdbTarget *target, const char *reg, unsigned int *index) { bool result; /* Bilan à retourner */ unsigned int i; /* Boucle de parcours #1 */ unsigned int j; /* Boucle de parcours #2 */ result = false; *index = 0; for (i = 0; i < target->count && !result; i++) for (j = 0; j < target->defs[i]->count && !result; j++) { if (strcmp(target->defs[i]->regs[j].name, reg) == 0) result = true; else (*index)++; } return result; } /****************************************************************************** * * * Paramètres : target = ensemble d'informations liées à l'architecture. * * index = indice correspondant au registre pour GDB. * * offset = position de valeur du registre dans du texte. [OUT] * * * * Description : Recherche la position correspondant à un registre donné. * * * * Retour : Bilan de l'opération : trouvaille ou échec ? * * * * Remarques : - * * * ******************************************************************************/ static bool g_gdb_target_find_register_offset(const GGdbTarget *target, unsigned int index, size_t *offset) { unsigned int i; /* Boucle de parcours #1 */ unsigned int j; /* Boucle de parcours #2 */ *offset = 0; for (i = 0; i < target->count && index > 0; i++) for (j = 0; j < target->defs[i]->count && index > 0; j++) { assert(target->defs[i]->regs[j].size % 4 == 0); *offset += target->defs[i]->regs[j].size / 4; index--; } return (index == 0); } /****************************************************************************** * * * Paramètres : target = ensemble d'informations liées à l'architecture. * * stream = flux de communication ouvert avec le débogueur. * * endian = boutisme de la cible. * * 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 : - * * * ******************************************************************************/ bool g_gdb_target_read_register(GGdbTarget *target, GGdbStream *stream, SourceEndian endian, const char *reg, size_t size, ...) { bool result; /* Bilan à retourner */ unsigned int index; /* Indice du registre ciblé */ GGdbPacket *packet; /* Paquet de communication */ char cmd[sizeof(XSTR(UINT_MAX)) + 1]; /* Elément de requête */ const char *data; /* Données reçues à analyser */ size_t len; /* Quantité de ces données */ const char *raw; /* Début de zone à relire */ size_t offset; /* Position dans la masse */ 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 */ result = g_gdb_target_find_register_index(target, reg, &index); if (!result) goto ggtrr_error; /** * Essai avec la méthode précise. */ if (!target->read_single_register) goto read_all_register_fallback; packet = g_gdb_stream_get_free_packet(stream); g_gdb_packet_start_new_command(packet); g_gdb_packet_append(packet, "p"); snprintf(cmd, sizeof(cmd), "%x", index); g_gdb_packet_append(packet, cmd); result = g_gdb_stream_send_packet(stream, packet); g_gdb_stream_mark_packet_as_free(stream, packet); if (!result) goto ggtrr_error; /* Réception de la réponse */ packet = g_gdb_stream_recv_packet(stream); g_gdb_packet_get_data(packet, &data, &len, NULL); if (len != 0 && !is_error_code(data, len)) raw = data; else { target->read_single_register = false; g_gdb_stream_mark_packet_as_free(stream, packet); read_all_register_fallback: /** * Utilisation de la méthode de masse au besoin... */ packet = g_gdb_stream_get_free_packet(stream); g_gdb_packet_start_new_command(packet); g_gdb_packet_append(packet, "g"); result = g_gdb_stream_send_packet(stream, packet); g_gdb_stream_mark_packet_as_free(stream, packet); if (!result) goto ggtrr_error; /* Réception de la réponse */ packet = g_gdb_stream_recv_packet(stream); g_gdb_packet_get_data(packet, &data, &len, NULL); result = g_gdb_target_find_register_offset(target, index, &offset); if (!result || offset > len) goto ggtrr_exit; raw = data + offset; len -= offset; } /* Lecture finale de la valeur recherchée */ va_start(ap, size); switch (size) { case 8: val8 = va_arg(ap, uint8_t *); result = hex_to_u8(raw, val8); break; case 16: val16 = va_arg(ap, uint16_t *); result = hex_to_u16(raw, &conv16); *val16 = from_u16(&conv16, endian); break; case 32: val32 = va_arg(ap, uint32_t *); result = hex_to_u32(raw, &conv32); *val32 = from_u32(&conv32, endian); break; case 64: val64 = va_arg(ap, uint64_t *); result = hex_to_u64(raw, &conv64); *val64 = from_u64(&conv64, endian); break; default: assert(false); result = false; break; } va_end(ap); ggtrr_exit: g_gdb_stream_mark_packet_as_free(stream, packet); ggtrr_error: return result; } /****************************************************************************** * * * Paramètres : target = ensemble d'informations liées à l'architecture. * * stream = flux de communication ouvert avec le débogueur. * * endian = boutisme de la cible. * * 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 : - * * * ******************************************************************************/ bool g_gdb_target_write_register(GGdbTarget *target, GGdbStream *stream, SourceEndian endian, 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 */ 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 */ unsigned int index; /* Indice du registre ciblé */ GGdbPacket *packet; /* Paquet de communication */ char cmd[sizeof(XSTR(UINT_MAX)) + 1]; /* Elément de requête */ const char *data; /* Données reçues à analyser */ size_t len; /* Quantité de ces données */ char *new; /* Nouvelles valeurs générales */ size_t offset; /* Position dans la masse */ /* Tronc commun : récupération de la valeur */ va_start(ap, size); switch (size) { case 8: val8 = va_arg(ap, uint8_t *); result = u8_to_hex(val8, hexval); break; case 16: val16 = va_arg(ap, uint16_t *); conv16 = to_u16(val16, endian); result = u16_to_hex(&conv16, hexval); break; case 32: val32 = va_arg(ap, uint32_t *); conv32 = to_u32(val32, endian); result = u32_to_hex(&conv32, hexval); break; case 64: val64 = va_arg(ap, uint64_t *); conv64 = to_u64(val64, endian); result = u16_to_hex(&conv64, hexval); break; default: assert(false); result = false; break; } va_end(ap); if (!result) goto ggtwr_error; /* Préparation de la suite */ result = g_gdb_target_find_register_index(target, reg, &index); if (!result) goto ggtwr_error; /** * Essai avec la méthode précise. */ if (!target->write_single_register) goto write_all_register_fallback; packet = g_gdb_stream_get_free_packet(stream); g_gdb_packet_start_new_command(packet); g_gdb_packet_append(packet, "P"); snprintf(cmd, sizeof(cmd), "%x", index); g_gdb_packet_append(packet, cmd); g_gdb_packet_append(packet, "="); g_gdb_packet_append(packet, hexval); result = g_gdb_stream_send_packet(stream, packet); g_gdb_stream_mark_packet_as_free(stream, packet); if (!result) goto ggtwr_error; /* Réception de la réponse */ packet = g_gdb_stream_recv_packet(stream); g_gdb_packet_get_data(packet, &data, &len, NULL); if (is_error_code(data, len) || strcmp(data, "OK") != 0) { target->write_single_register = false; g_gdb_stream_mark_packet_as_free(stream, packet); write_all_register_fallback: /** * Utilisation de la méthode de masse au besoin... */ /* Lecture de l'ensemble des registres */ packet = g_gdb_stream_get_free_packet(stream); g_gdb_packet_start_new_command(packet); g_gdb_packet_append(packet, "g"); result = g_gdb_stream_send_packet(stream, packet); g_gdb_stream_mark_packet_as_free(stream, packet); if (!result) goto ggtwr_error; /* Réception de la réponse et mise à jour */ packet = g_gdb_stream_recv_packet(stream); g_gdb_packet_get_data(packet, &data, &len, NULL); result = g_gdb_target_find_register_offset(target, index, &offset); if (!result || offset > len) goto ggtwr_exit; new = (char *)malloc(len); memcpy(new, data, len); memcpy(new + offset, hexval, strlen(hexval)); g_gdb_stream_mark_packet_as_free(stream, packet); /* Ecrasement de tous les registres */ packet = g_gdb_stream_get_free_packet(stream); g_gdb_packet_start_new_command(packet); g_gdb_packet_append(packet, "G"); g_gdb_packet_append(packet, new); free(new); result = g_gdb_stream_send_packet(stream, packet); g_gdb_stream_mark_packet_as_free(stream, packet); if (!result) goto ggtwr_error; /* Réception de la réponse */ packet = g_gdb_stream_recv_packet(stream); g_gdb_packet_get_data(packet, &data, &len, NULL); result = (!is_error_code(data, len) && strcmp(data, "OK") == 0); } ggtwr_exit: g_gdb_stream_mark_packet_as_free(stream, packet); ggtwr_error: return result; }