60 votes

Comment compiler et exécuter directement depuis la mémoire?

Est-il possible de compiler un programme C ++ (ou similaire) sans générer le fichier exécutable mais en l'écrivant et en l'exécutant directement à partir de la mémoire?

Par exemple avec GCC et clang , quelque chose qui a un effet similaire à:

 c++ hello.cpp -o hello.x && ./hello.x $@ && rm -f hello.x
 

dans la ligne de commande.

Mais sans la charge d'écrire un exécutable sur le disque pour le charger / l'exécuter à nouveau immédiatement.

(Si possible, la procédure peut ne pas utiliser d'espace disque.)

51voto

Lothar_K Points 500

Possible? Pas la façon dont vous semblez vouloir. La tâche est en deux parties:

1) Comment obtenir le binaire dans la mémoire

Quand nous indiquons /dev/stdout comme fichier de sortie dans Linux, nous pouvons alors tuyau dans notre programme x0 qui lit un exécutable à partir de stdin et l'exécute:

  gcc -pipe YourFiles1.cpp YourFile2.cpp -o/dev/stdout -Wall | ./x0

En x0 il nous suffit de les lire depuis l'entrée standard jusqu'à atteindre la fin du fichier:

int main(int argc, const char ** argv)
{
    const int stdin = 0;
    size_t ntotal = 0;
    char * buf = 0;
    while(true)
    {
        /* increasing buffer size dynamically since we do not know how many bytes to read */
        buf = (char*)realloc(buf, ntotal+4096*sizeof(char));
        int nread = read(stdin, buf+ntotal, 4096); 
        if (nread<0) break;
        ntotal += nread;
    }
    memexec(buf, ntotal, argv); 
}

Il serait également possible pour x0 directement exécuter le compilateur et de lire la sortie. Cette question a été posée ici: Redirection exec sortie d'un tampon ou d'un fichier

Mise en garde: je viens de comprendre que pour une raison étrange, cela ne fonctionne pas lorsque j'utilise pipe | mais fonctionne quand j'utilise l' x0 < foo.

Remarque: Si vous êtes prêt à modifier votre compilateur ou vous ne JIT comme LLVM, clang et d'autres cadres de vous pourrait générer directement le code de l'exécutable. Cependant, pour le reste de cette discussion, je suppose que vous voulez utiliser un compilateur.

Remarque: l'Exécution via fichier temporaire

D'autres programmes tels que UPX obtenir un comportement similaire par l'exécution d'un fichier temporaire, il est plus facile et plus portable que l'approche décrite ci-dessous. Sur les systèmes où l' /tmp est associé à un disque RAM pour l'exemple typique des serveurs, le fichier temporaire sera basé en mémoire de toute façon.

int memexec(void * exe, size_t exe_size, const char * argv)
{
    /* random temporary file name in /tmp */
    char name[15] = "/tmp/fooXXXXXX"; 
    /* creates temporary file, returns writeable file descriptor */
    int fd_wr = mkostemp(name,  O_WRONLY);
    /* makes file executable and readonly */
    chmod(name, S_IRUSR | S_IXUSR);
    /* creates read-only file descriptor before deleting the file */
    int fd_ro = open(name, O_RDONLY);
    /* removes file from file system, kernel buffers content in memory until all fd closed */
    unlink(name);
    /* writes executable to file */
    write(fd_wr, exe, exe_size);
    /* fexecve will not work as long as there in a open writeable file descriptor */
    close(fd_wr);
    char *const newenviron[] = { NULL };
    /* -fpermissive */
    fexecve(fd_ro, argv, newenviron);
    perror("failed");
}

Mise en garde: la gestion des Erreurs est laissée de côté pour les clartés du saké. Comprend pour des raisons de concision.

Remarque: En combinant l'étape main() et memexec() en une seule fonction et l'utilisation de splice(2) pour copier directement entre stdin et fd_wr le programme pourrait être considérablement optimisé.

2) l'Exécution directement à partir de la mémoire

On ne se contente pas de charger et d'exécuter un ELFE binaire à partir de la mémoire. Un peu de préparation, pour la plupart liés à la liaison dynamique, qui doit arriver. Il y a beaucoup de matériel à expliquer les différentes étapes de l'ELFE processus de liaison et d'étudier ce qui me fait croire que théoriquement possible. Voir, par exemple, de cette étroitement liée à la question sur SI toutefois il ne semble pas exister une solution de travail.

Mise à jour UserModeExec semble venir de très près.

La rédaction d'un travail de mise en œuvre serait très coûteuse en temps, et sûrement soulever quelques questions intéressantes dans son propre droit. J'aime à croire que c'est voulu par la conception: pour la plupart des applications, il est fortement souhaitable d' (accidentellement) l'exécution de ses données d'entrée, car elle permet l'injection de code.

Ce qui se passe exactement quand un ELFE est exécuté? Normalement le noyau reçoit un nom de fichier, puis crée un processus, des charges et des cartes différentes sections de l'exécutable en mémoire, effectue beaucoup de vérifications et le marque comme exécutable avant de passer le contrôle et un nom de fichier dos au moment de l'exécution linker ld-linux.so (une partie de la libc). La prend soin de la délocalisation de fonctions de manipulation des bibliothèques supplémentaires, la configuration des objets globaux et de sauter à la exécutables point d'entrée. AIU ce gros du travail est fait par dl_main() (mis en œuvre dans la libc/elfe/rtld.c).

Même fexecve est mis en œuvre à l'aide d'un fichier dans /proc , et c'est ce besoin d'un nom de fichier qui nous amène à ré-écrire certaines parties de ce processus de liaison.

Les bibliothèques

  • UserModeExec
  • libelf -- lire, modifier, créer des fichiers ELF
  • eresi -- jouer avec les elfes
  • OSKit (semble être un projet mort tout de même)

La lecture

Questions liées au SI

Il semble donc possible, à vous de décider si est aussi pratique.

24voto

delnan Points 52260

Oui, à condition de le faire correctement nécessite la conception des pièces importantes du compilateur avec cela à l'esprit. La LLVM les gars ont fait cela, d'abord avec un peu séparer, JIT, et plus tard avec le MC de sous-projet. Je ne pense pas qu'il y a un ready-made outil de le faire. Mais en principe, c'est juste une question de la liaison à grand bruit de ferraille et llvm, en passant de la source de bruit, et en passant à l'IR, il crée à MCJIT. Peut-être une démo est-ce (j'ai vaguement souvenir d'un de base, C interprète qui a travaillé comme ceci, mais je pense que c'était basé sur l'héritage JIT).

Edit: Trouvé la démo je l'ai rappelé. Aussi, il y a accrocher, ce qui semble faire fondamentalement, ce que j'ai décrit, mais en mieux.

22voto

Matt Kline Points 3962

Linux peut créer des systèmes de fichiers en RAM à l'aide de tempfs. Par exemple, j'ai mon tmp répertoire dans mon système de fichiers le tableau comme suit:

tmpfs       /tmp    tmpfs   nodev,nosuid    0   0

L'utilisation de ce, tous les fichiers que je mets en /tmp sont stockés dans ma RAM.

Windows ne semble pas avoir de "officielle" de la façon de faire cela, mais il a de nombreux à des tiers.

Sans cette "RAM disk" concept, il faudrait vraisemblablement fortement modifier un compilateur et l'éditeur de liens pour fonctionner entièrement en mémoire.

9voto

Basile Starynkevitch Points 67055

Si vous ne sont pas spécifiquement liées à C++, vous pouvez également envisager d'autres JIT de solutions basées sur:

  • en Common Lisp SBCL est capable de générer du code machine à la volée
  • vous pouvez utiliser TinyCC et son libtcc.a qui émet rapidement des pauvres (c'est à dire unoptimized) machine de code C code en mémoire.
  • pensez également toute JITing de la bibliothèque, par exemple libjit, GNU Foudre, LLVM, asmjit
  • bien sûr, émettant un code C++ sur certains tmpfs et de le compiler...

Mais si vous voulez du bon code machine, vous aurez besoin d'être optimisé, et qui n'est pas rapide (si le temps d'écrire à un système de fichiers est négligeable).

Si vous êtes attaché à code C++ généré, vous avez besoin d'une bonne C++ compilateur d'optimisation (par exemple, g++ ou clang++); ils prennent beaucoup de temps à compiler du code C++ à l'optimisation de la binaire, de sorte que vous devez générer à certaines fichier foo.cc (peut-être dans une mémoire RAM du système de fichiers à l'instar de certains tmpfs, mais qui donnerait un mineur de gain, puisque la plupart du temps est consacré à l'intérieur d' g++ ou clang++ passes d'optimisation, pas de lecture du disque), puis compilez qu' foo.cc de foo.so (en utilisant peut-être make, ou d'au moins un fork g++ -Wall -shared -O2 foo.cc -o foo.so, peut-être avec d'autres bibliothèques). Enfin votre programme principal dlopen qui a généré foo.so. FWIW, faire FONDRE est exactement ce que fait.

Sinon, générer une source de programme foobar.cc, le compiler un exécutable foobarbin par exemple, avec g++ -O2 foobar.cc -o foobarbin et de les exécuter avec execve que foobarbin binaire exécutable

Lors de la génération de code C++, vous pouvez éviter de produire de minuscules fichiers source C++ (par exemple, une dizaine de lignes; si possible, de générer des fichiers C++ d'une centaine de lignes au moins). Par exemple, essayez si possible de mettre plusieurs généré des fonctions C++ dans le même généré un fichier C++ (mais éviter d'avoir de très gros généré des fonctions C++, par exemple 10KLOC dans une seule fonction; ils prennent beaucoup de temps à être compilé par GCC). Vous pourriez envisager, si applicable, de n'avoir qu'un seul #include en qui a généré fichier C++, et de pré-compilation que généralement inclus l'en-tête.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X