Et un fichier exe pour les gouverner tous!
Par Jean-Seb le mardi 9 juin 2009, 00:07 - Coding - Lien permanent
La question du fichier ressources regroupant toutes les données est
récurrente.
Les solutions sont multiples et ont fait couler beaucoup
d'encre.
Voici une solution sans développement, ce qui est toujours
intéressant.
Inclure vos données dans un seul fichier
Principe
- Lorsque votre programme contient des données séparées, l'amour de l'esthétisme et un légitime souci d'exclusivité peuvent conduire à distribuer un minimum de fichiers.
Fichier ressources
- Une solution courante est de distribuer un fichier exécutable, et un fichier ressources qui contiendra l'ensemble des données.
- Le fichier ressources peut être une simple concaténation des
données, une archive ou une base de données.
- La concaténation de données nécessite de maintenir un index, et souvent de recréer l'ensemble du fichier à la moindre modification. C'est par contre la plus rapide à l'exécution.
- L' utilisation d'un fichier ressources est également rapide (moins les temps de décompression éventuelle), mais aura au moins une dépendance (si vous projetez d'écrire un nouveau packer/depacker... c'est une mauvaise idée). Par contre, vous aurez des possibilités proches d'un système de fichier pour la gestion de votre fichier ressources.
- L' utilisation d'une base de données est à réserver aux gros projets et sort largement du cadre de ce billet.
Les codeurs n'ont pas froid aux oreilles
- On peut faire encore plus fort, et tout coller dans le même fichier.
- Dans ce cas, on n'a plus trop le choix, et il s'agit bien sûr de l'exécutable.
Choix retenu
- Ici nous allons utiliser le fichier exe pour stocker nos données.
- avantages : un seul fichier executable à distribuer. On ne risque pas de se tromper quand on distribue les mises à jour.
- inconvénients : il faut tout redistribuer à chaque modification de l'exécutable. Les temps de linkage sont allongés.
Options techniques
xxd : solution la plus portable
xxd -i data.dat: génère un dump de data.dat au format C.- Nous n'étudierons pas cette possibilité.
- En deux mots, elle consiste à définir les datas dans le code source, et à compiler celui-ci.
- Son inconvénient principal est la lourdeur des codes sources générés.
Injection des données dans l'exécutable
- Utilisation de objdump et objcopy (ou ld en remplacement de objcopy).
- Nous allons utiliser les fichiers suivants:
- data.txt : données à insérer dans l'exécutable (ici, ça sera du texte).
- main.c : le source C de l'exécutable.
objcopy, objdump
objcopy
- Il s'agit tout d'abord de transformer le fichier data.txt en un fichier objet.
- Formats d'entrée et de sortie de objcopy
objcopy --info: informations sur les formats d'entrée/sortie pour objcopy.
- Linux et Windows:
objcopy -I binary -O elf32-i386 --binary-architecture i386 data.txt data.o
- Explication des flags
- -I : (attention il s'agit d'un
i
majuscule) input target - -O : output target
- rappel :
objcopy --infodonne une liste des input/output potentiels.
- -I : (attention il s'agit d'un
Parenthèse sur ld
- Une manipulation équivalente à la commande objcopy ci-dessus:
ld -r -b binary data.txt -o data.o data.txt
- Explication des flags
- -r : link incrémental. Le fichier généré en sortie par ld pourra reservir comme entrée à ld.
- -b : input format (et non pas binary, je sais, le raccourci est tentant).
objdump
- Permet d'obtenir toutes sortes d'informations sur vos objets.
- Nous l'utilisons pour avoir le nom des symboles du fichier objet.
objdump -s data.o- donne le format du fichier , et le contenu des sections
objdump -x data.o- montre tous les headers.
- Pour avoir uniquement la table des symboles, utiliser le flag -t
$ objdump.exe -t data.o data.o: file format pe-i386 SYMBOL TABLE: [ 0](sec 1)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x00000000 _binary_data_txt_start [ 1](sec -1)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x0000001c _binary_data_txt_size [ 2](sec 1)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x0000001c _binary_data_txt_end
- plus court, même chose:
$ nm data.o 0000001c D _binary_data_txt_end 0000001c A _binary_data_txt_size 00000000 D _binary_data_txt_start
Utilisation des symboles dans le source
- Maintenant que nous avons la liste des symboles, nous pouvons y faire référence dans le source en C.
- Les symboles seront traités comme de vulgaires buffers.
- Ils sont faciles à utiliser, voici un source d'exemple:
/* main.c */
#include <stdio.h>
extern int binary_data_txt_end;
extern int binary_data_txt_size;
extern int binary_data_txt_start;
int main(void)
{
int size = (int)&binary_data_txt_size;
char *data = (char *)&binary_data_txt_start;
char *end = (char *)&binary_data_txt_end;
printf("taille: %d\n", size);
printf("buffer: %s", data);
printf("start: 0x%p ; end: 0x%p\n",data,end);
return 0;
}
- Juste une petite remarque pour Windows.
- * Sous Windows, la décoration des noms n'est pas la même que sous Unix.
- Par exemple:
_extern int binary_stdout_txt_startdeviendraextern int binary_stdout_txt_start - Il faut enlever le premier underscore pour chaque symbole.
- Par exemple:
Et qu'est-ce qu'on fait maintenant ?
- Il faut simplement compiler le source, et ajouter au linkage notre fichier précédemment pré-linké.
- Cela va plus vite à écrire qu'à expliquer:
gcc -W -Wall main.c data.o
- Je vous laisse la joie d'exécuter tout ça, et vous épargne le screenshot.
Docteur Hacker et Monsieur Lamer
- Bien entendu, la contre-mesure est assez évidente pour qui a compris tout ce qui précède.
- Je ne doute pas que Docteur Hacker, qui me lit (et je j'en suis fort honoré) a déja deviné.
- Dans le cas où Monsieur Lamer me lirait également, je n'en dirai pas plus.