Écrit par Programind, le 8 Mars 2024
Il existe plusieurs façons d'embarquer des fichiers dans un code source C. La méthode suivante a l'avantage d'être indépendante du compilateur utilisé et donc plus portable.
L'archive complète est disponible à l'adresse embed_binaries_c.tar.zst.
//emb.c #include <stdio.h> int main(void) { printf("Hello World !\n"); return 0; }
//embb.h unsigned char emb[] = { 0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x3e, 0x00, 0x01, 0x00, 0x00, 0x00, 0x50, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ... }; unsigned int emb_len = 15952;
//main.c #define _GNU_SOURCE #include <unistd.h> #include <stdio.h> #include <errno.h> #include <sys/mman.h> #include "embb.h" int main(void) { int fd = memfd_create("embb", 0); if (fd == -1) fprintf(stderr, "fd error %d\n", errno); int bw = write(fd, emb, emb_len); if (bw != emb_len) fprintf(stderr, "bytes write %d\n", bw); char *newargv[] = { "emb", NULL }; char *newenvp[] = { NULL }; fexecve(fd, newargv, newenvp); perror("fexecve"); fprintf(stderr, "fexecve error %d\n", errno); return 0; }
make embb main
On peut alors tester les exécutables produits avec ./emb
et ./main
dans le terminal. Les exécutables donnent alors la même sortie Hello World !
, cela signifie que tout s'est déroulé comme prévu.
Du côté du fichier emb.c
il n'y a pas grand chose à dire, le programme se contente d'afficher Hello World !
.
Dans le fichier embb.h
il y a un tableau de type unsigned char
rempli de valeur hexadécimales et une variable de type unsigned int
. À bien regarder, la valeur de cette variable correspond à la taille du fichier emb
en octets. C'est aussi la taille du tableau. En fait embb.h
est un hex dump formaté dans le format C de l'exécutable emb
. Chaque valeur hexadécimal du tableau est un octet du fichier emb
. Ainsi le fichier est stocké tel quel dans un format accessible via du code C. Pour cela l'outil xxd¹ est utilisé avec la commande xxd -i emb > embb.h
.
Enfin le fichier main.c
est le plus complexe. Le cœur du programme est constitué de trois parties :
#define _GNU_SOURCE #include <sys/mman.h> #include <stdio.h> #include <errno.h> int fd = memfd_create("embb", 0); if (fd == -1) fprintf(stderr, "fd error %d\n", errno);
memfd_create
² va créer un fichier anonyme dans la mémoire RAM et renvoyer un file descriptor du fichier qui permet d'y accéder. Attention un file descriptor est de type int
, c'est une abstraction des fichiers différente d'un file pointer de type FILE *
. Les deux dernières lignes s'assurent que l'appel système s'est déroulé sans encombres.
#include <unistd.h> #include <stdio.h> #include "embb.h" int bw = write(fd, emb, emb_len); if (bw != emb_len) fprintf(stderr, "bytes write %d\n", bw);
write
³ va écrire le contenu du tableau contenu dans le fichier embb.h
dans notre fichier anonyme nouvellement créé et ainsi recréer le fichier emb
d'origine dans la mémoire RAM. Là encore les deux dernières lignes nous signalent les éventuelles erreurs.
#include <unistd.h> #include <stdio.h> #include <errno.h> char *newargv[] = { "emb", NULL }; char *newenvp[] = { NULL }; fexecve(fd, newargv, newenvp); perror("fexecve"); fprintf(stderr, "fexecve error %d\n", errno);
fexecve
⁴ va à enfin exécuter notre binaire, qui se trouve dans la mémoire RAM, en lui donnant des arguments et un environnement spécifiés respectivement par newargv
et newenvp
. Les deux tableaux de chaînes de caractère doivent se finir par la valeur NULL
et en général, le premier élément des arguments est le nom de l'exécutable appelé, sinon sa valeur doit être NULL
. Si l'appel de l'exécutable fonctionne correctement, le programme appelant ne se termine jamais et se transforme pour laisser la place à l'exécutable appelé. Dans le cas d'une erreur lors de l'appel, le programme continue et les deux dernières lignes affichent un message d'erreur correspondant au problème. Pour éviter que le programme appelant ne soit remplacé par le programme appelé, il est possible d'utiliser l'appel système fork
⁵.
¹. https://linux.die.net/man/1/xxd
². https://www.man7.org/linux/man-pages/man2/memfd_create.2.html
³. https://www.man7.org/linux/man-pages/man2/write.2.html