Forum Programmation.c autoextractible : théorie

Posté par (page perso) .
Tags : aucun
0
20
avr.
2005
Bonsoir,

j'ai eu une idée de projet tout récemment qui nécéssite la création de binaires auto-extractibles.

J'avais déjà réalisé ce type de binaire, mais mon algorithme est limité aux petits fichiers (en fait je créé un code source C à la volée, avec dedans tous le nécéssaire pour extraire octet par octet un des fichiers archivés, et une fois le code généré je compile tout via gcc)

problème : bien que lors de l'extraction ce soit quasi-instantanné, lors de la compilation ca peut devenir très vite bougrement long car j'ai des fichiers de 20Mo à inclure dans le binaire et mon code a compiler fait donc lui aussi 20Mo (une succession de fwrite sur 15km :)

Donc, je voulais repenser mon algo... Quelqu'un a des idées ?
Comment font les installshield, wise, & co ? sous linux c'est généralement des scripts shell de la mort, on les réalise comment ?


Merci
  • # Dégrossissons le problème

    Posté par . Évalué à 2.

    Dans un script shell, je sais vraiment pas comment on fait.

    Dans un programme auto-extractible binaire, je suppose que le problème se situe dans le fait d'inclure un bloc de données arbitraire et arbitrairement grand, la taille de ce bloc de données, et le moyen d'accéder à ces données.

    A priori, je me contenterais de définir deux variables importées dans le fichier C qui contient la fonction main() et l'algorithme de décompression :

    extern unsigned int code_size;
    extern void* code;

    Ainsi, l'algorithme de décompression n'a qu'à se reposer dessus et le fichier compile sans accroc.
    Reste à créer un fichier .o qui exporte ces variables avec le contenu correct.
    Pour ça je ne peux que suggérer de lire la doc expliquant le format de fichier ELF ( http://en.wikipedia.org/wiki/Executable_and_Linkable_Format ), et d'apprendre à utiliser libelf ( http://www.mr511.de/software/english.html ).

    Il n'y a pas besoin de savoir faire grand-chose : juste créer un fichier ELF relocalisable (.o) qui contient des données en lecture seule, exportées sous les noms "code_size" et "code".

    Une fois un tel fichier créé, il se lie sans problème avec le fichier qui contient l'algo de décompression, et si l'algo est correct, ça devrait marcher sans problème.

    Je suggère de faire des essais avec des problèmes triviaux d'abord.
    • [^] # Re: Dégrossissons le problème

      Posté par (page perso) . Évalué à 2.

      le probleme avec ca c'est que les long et les int c'est limité, et idem pour la taille d'un tableau, donc je ne vois trop pas comment "stocker" mon fichier dans une variable C (j'y arrive, mais pas avec des fichiers aussi gros : size array is too large)

      Sinon c'est interressant la libelf, j'vais p'tetre approfondir là dedans.

      Merci.
      • [^] # Re: Dégrossissons le problème

        Posté par . Évalué à 2.

        Bah les limitations... Avec un unsigned int pour indiquer la taille des données ça peut tout de même aller jusqu'à 4Go, c'est pas si mal. Sinon, il y a peut-être une structure permettant de définir un offset dans un fichier de plus de 4Go, ou au pire on peut toujours utiliser 2 unsigned int au lieu d'un. Mais bon, de toute façon, à plus de 512 Mo, la méthode que j'indique devient limitée par la mémoire de pas mal de machines, et il faut trouver autre chose.

        Concernant la taille d'un tableau, certes c'est limité, mais je ne suggère pas d'utiliser un tableau, je suggère un pointeur.
        • [^] # Re: Dégrossissons le problème

          Posté par (page perso) . Évalué à 2.

          Concernant la taille d'un tableau, certes c'est limité, mais je ne suggère pas d'utiliser un tableau, je suggère un pointeur.

          oui désolé j'avais pas noté. ta solution me semble la plus pertinente pour faire ce que je veux, faut juste que je me documente sur cette fameuse libelf et que je saisisse bien le principe : le .o contenant le fichier de 15Mo, on va le créé a la volée via libelf, sans passer par le compilateur, et il aura le même résultat que si j'avais ecris un code à la main, contenant les 2 fameuses variables, et que j'aurai ensuite compiler ? mais ce code que j'aurai pu ecrire à la main, il ressemblerai à quoi ?

          un truc genre :
          int taille=30;
          unsigned char*="ceci est un fichier autogénéré";

          (et plus généralement avec le char* contenant le fichier en base64 par exemple ?)

          Enfin bon, j'verrai ca ce soir de toute facon, mais j'pense que tu m'as mis sur une bonne piste :)

          Merci.
          • [^] # Re: Dégrossissons le problème

            Posté par . Évalué à 1.

            Oui, tu as bien compris ce que je voulais dire, bien que je ne vois pas pourquoi il faudrait coder le fichier en base64 : autant le mettre tel quel.

            L'équivalent en source serait quelque chose du genre :

            unsigned int taille = 15*1024*1024;
            char * contenu =
            {
            '\044', '\353', '\150', '\220', '\000', '\274', '\175', '\574',
            '\003', '\520', '\017', '\520', '\137', '\374', '\076', '\033',
            ... // Et ainsi de suite pour représenter 15 Mo
            };

            C'est tout de même assez lourd à faire en source, et je suis même pas certain que gcc l'accepte (tiens, je vais essayer :).)
            • [^] # Re: Dégrossissons le problème

              Posté par . Évalué à 1.

              Marche pas, ma notation.

              Ce serait plutôt

              char * contenu =
              "\044\353\150\220\000\274\175\574"
              "\003\520\017\520\137\374\076\033"
              ...
              ;

              ce qui génère un octet nul à la fin, mais voilà, quoi.
              • [^] # Re: Dégrossissons le problème

                Posté par (page perso) . Évalué à 2.

                C'est bon mon ptit bout de code marche.
                Avec un fichier de 15ko ca génère un code C autonome qui se compile en 0.50 secondes et qui une fois executé recréé le fichier d'origine. Parcontre pour celui de 15Mo là c'est meeeggggaaaaaa long et ca tue mon processeur !

                Donc j'aimerai bien le compiler une fois pour toute en .o et ensuite le lier quand j'en ai besoin a un code C qui accéderait aux variables taille et contenu, c'est possible ca ? (j'suis pas très calé sur certains points du C, notamment sur ces histoires de linkage)

                J'ai essayé vite fait, mais gcc me dit que j'ai pas de main dans mon .o :(
                • [^] # Re: Dégrossissons le problème

                  Posté par . Évalué à 1.

                  C'est normal que ça soit long de transformer un code source de 15 Mo en fichier .o, surtout si le source est composé presque uniquement d'une chaîne de caractère : le compilateur n'est pas du tout prévu pour ça et est très peu efficace (reconnaître la chaîne de caractère, la stocker, l'écrire dans un .S, la reconnaître dans le .S, la stocker, l'écrire dans un .o).

                  Le mieux est de réaliser un petit programme qui, à partir d'un fichier qui contient les données à inclure, fabrique directement un .o contenant ces données et un symbole exporté de pointeur vers ces données. Autrement dit qui fabrique le .o équivalent à ton fichier source géant mais sans avoir à générer ce fichier source et à le faire transformer en .o par un compilateur.

                  Donc j'aimerai bien le compiler une fois pour toute en .o et ensuite le lier quand j'en ai besoin a un code C qui accéderait aux variables taille et contenu, c'est possible ca ?

                  Moi je te dis de laisser tomber cette méthode, mais c'est possible, oui. En admettant que ton fichier .c géant se nomme donnees.c, tu les compiles avec :

                  gcc -c donnees.c

                  ce qui crée le fichier donnees.o qui exporte les symboles voulus et peut être lié à tout code qui en a besoin (ou pas). En admettant que ton code qui se base sur la taille et le pointeur vers les données s'appelle main.c, il suffira de faire :

                  gcc donnees.o main.c -o main

                  pour créer un fichier exécutable "main" complet. Voilà voilà.
    • [^] # Re: Dégrossissons le problème

      Posté par (page perso) . Évalué à 2.

      Et pourquoi ne pas faire la chose suivante :

      Dans le prog final :
      - Une section elf ".embedded_prog" avec des symboles _b_embed et _e_embed pour la localiser
      - Dans cette section, le binaire du programme a charger /decompresser
      - Le programme de chargement decompression

      Le programme de chargemement/decompression faisant les choses suivantes :
      - ecriture des donnees entre les symboles _b_embed et _e_embed dans un fichier temporaire (decompression a la volee ou a posteriori)
      - fork() puis exec() du fichier temporaire

      Et voila ! Pour ce qui est de comment inclure un fichier dans une section, man objcopy ou voir SOS dans le linux magazine de Mars dernier (tiens ca me fait penser que le code de n'est pas encore en ligne).
      • [^] # Re: Dégrossissons le problème

        Posté par (page perso) . Évalué à 2.

        Ta technique à l'air fort interressante, mais je n'ai pas tout compris :)

        D'après ce que j'ai compris, avec objcopy on peut inserer dans un binaire existant un fichier quelquonque, que l'on balisera avec par exemple _debut_prog et _fin_prog.

        Mais comment dit-on, en C, que notre (nos) fichier(s) se trouve(nt) dans soi-même, a l'adresse _debut_prog et se termine à _fin_prog ?

        Et supposons que j'ai une fichier plop.bin et une image plop.jpg, je stock plop.jpg dans plop.bin via objcopy. A quoi ressemblerai la ligne de commande ? (oui désolé j'ai pas linuxmag de mars et le man page est pas super clair vu d'ici (j'suis au taf, jpeux rien tester, pas de gcc, snif...)

        Merci
        • [^] # Re: Dégrossissons le problème

          Posté par . Évalué à 1.

          Mais comment dit-on, en C, que notre (nos) fichier(s) se trouve(nt) dans soi-même, a l'adresse _debut_prog et se termine à _fin_prog ?

          L'idée, c'est que _debug_prog et _fin_prog soient des symboles exportés. Autrement dit, en lexique C, des variables externes.

          Il suffirait de déclarer dans le fichier C :
          extern void * _debut_prog, * _fin_prog;

          Puis de s'en servir tel quel. Je n'ai pas suggéré cette méthode parce que je ne connaissais pas objcopy (et ne sais toujours pas comment m'en servir.)
        • [^] # Re: Dégrossissons le problème

          Posté par (page perso) . Évalué à 2.

          Exemple de manip:
          On veut caser toute un tar.gz dans un executable, le decompresser a la volee dans un repertoire temporaire, et executer le programme nommé "run" contenu dans cette archive.

          Pour ca, on commence a fabriquer son tar.gz avec son executable "run" a faire demarrer une fois que la decompression sera faite : binaire ou script shell. Ensuite on fabrique un .o qui contient cette archive dans la section .rodata, et on repere les donnees ainsi enfouies par les symboles "__embed" et "_e_embed". Enfin, on lie ce .o avec un autre .o qui contient le code de decompression.

          Bon, finalement c'est peut-etre plus simple avec le code sous les yeux.

          - Le fichier qui decompresse l'archive integree dans le binaire et qui lance le programme "run" de cette archive :

          #include <stdio.h>
          #include <unistd.h>
          #include <stdlib.h>

          extern char _b_embed, _e_embed;

          int main ()
          {
          FILE * pipe;
          char dirname[] = "/tmp/.embedXXXXXX";
          char * contents;

          mkdtemp(dirname);
          if (! dirname)
          return -1;

          if (chdir(dirname))
          return -1;

          pipe = popen("tar xzf -", "w");
          if (! pipe)
          return -1;

          for (contents = & _b_embed ; contents < & _e_embed ; contents ++)
          {
          if (fwrite(contents, 1, 1, pipe) != 1)
          return -1;
          }

          if (pclose(pipe))
          return -1;

          execl("./run", "./run", NULL);

          return -1;
          }


          - Le Makefile qui fait l'archive a compresser avec le fichier run (un script de base) a demarrer une fois que tout sera decompresse. On en profite pour mettre d'autres trucs bidon dans l'archive. Au final, le make construit le binaire avec l'archive enfouie :

          all: demo

          demo: unembed.o embedded_archive.o
          $(CC) -o $@ unembed.o embedded_archive.o

          embedded_archive.o: archive.tgz
          touch container.c && $(CC) -c container.c
          objcopy --add-section .embed=$< container.o
          echo "SECTIONS { .rodata . : { _b_embed = .; container.o(.embed); _e_embed = .; }}" \
          > script.lds
          ld -r -o $@ script.lds

          archive.tgz:
          echo "#! /bin/sh" > run
          echo 'echo "######### Hello World ! #########"' >> run
          echo 'echo Cur dir=`pwd`' >> run
          echo "echo Dir contents:" >> run
          echo "ls -la" >> run
          # echo "make && ./demo" >> run
          chmod 755 run
          tar czf $@ run Makefile unembed.c

          clean:
          $(RM) unembed.o* demo archive.tgz embedded_archive.o


          Et voila ! Maintenant je mets l'executable "demo" n'importe ou, je l'execute, et, dans le /tmp/.embedXYZTUV, ce decompressera tout et ca lancera le script "run" !

          Note : le "XYZTUV" est genere par mkdtemp, le script "run" s'occupera de l'afficher.

          Note 2 : le code a l'air de marcher, il y a peut-etre quelques optimisations a faire (regarder du cote de fwrite).
  • # Voir les sharutils...

    Posté par (page perso) . Évalué à 5.

    $ man shar
    • [^] # Re: Voir les sharutils...

      Posté par . Évalué à 1.

      Hum, question ?

      Les autoextratibles d'ID software comme celui d'enemy-territory sont-ils réalisés avec cette technique ?
    • [^] # Re: Voir les sharutils...

      Posté par (page perso) . Évalué à 2.

      pertinent, je préfererai une approche C (plus pédagogique dans mon cas) que shell, mais la commande à l'air fort pratique quand même.

      Merci
  • # Pour les scripts,

    Posté par . Évalué à 2.

    dans mes lointains souvenirs, ça se règle souvent à grands coups de
    "here doc".

    Exemple (sans test, hein, à adapter) :
    Tu as un répertoire X avec 3 fichiers A, B et C
    Tu fais un "tar cf Temp.tar X", suivi d'un "compress Temp.tar".
    Tu as maintenant un fichier "Temp.tar.Z" qui contient ton répertoire compressé.
    Tu fais un script Install.sh contenant quelquechose comme

    "cat << LA_FIN | uncompress | tar xf - "

    Puis tu fais un "cat Temp.tar.Z >> Install.sh
    Puis tu ajoutes en fin de script, sur la première ligne après les données binaires venant de ton Temp.tar.Z "LA_FIN"

    Il faut sans doute utiliser d'autres commandes au passage pour des problèmes d'encodage, ton script devant être de l'ascii...

    Evidemment, compress / uncompress et tar peuvent être remplacés par du zip, bzip, ..., et "LA_FIN" est un mot clé dont le choix est libre, mais qui ne doit pas pouvoir se trouver dans les données que tu veux extraire.

    Bonne chance.
    • [^] # Re: Pour les scripts,

      Posté par (page perso) . Évalué à 2.

      Hum le here script j'ai déjà eu l'occasion d'en faire il est vrai, mais pas dans ce context. Vu sous cet angle je vais tester la méthode ce soir en rentrant :)

      Merci.

      P.S : Autrement, comme dit précédemment, je serai quand même intérressé par la méthode C pour stocker un fichier volumineux dans le code source, donc si quelqu'un a une idée, le thread n'est pas clos :)

Suivre le flux des commentaires

Note : les commentaires appartiennent à ceux qui les ont postés. Nous n'en sommes pas responsables.