Les variables initialisées par la section .bss occupent-elles de l’espace dans le fichier elf?

Si je comprends bien, la section .bss fichiers ELF est utilisée pour allouer de l’espace pour les variables à initialisation nulle. Notre chaîne d’outils produit des fichiers ELF, d’où ma question: la section .bss doit-elle contenir tous ces zéros? Cela semble être un gaspillage d’espace si, par exemple, si j’alloue un tableau global de dix mégaoctets, il en résulte dix mégaoctets de zéros dans le fichier ELF. Qu’est-ce que je vois mal ici?

Cela fait un certain temps que je travaille avec ELF. Mais je pense que je me souviens encore de ces choses. Non, il ne contient pas physiquement ces zéros. Si vous examinez un en-tête de programme de fichier ELF, vous verrez que chaque en-tête a deux nombres: Le premier correspond à la taille du fichier. Et une autre est la taille de la section lorsqu’elle est allouée en mémoire virtuelle ( readelf -l ./a.out ):

 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align PHDR 0x000034 0x08048034 0x08048034 0x000e0 0x000e0 RE 0x4 INTERP 0x000114 0x08048114 0x08048114 0x00013 0x00013 R 0x1 [Requesting program interpreter: /lib/ld-linux.so.2] LOAD 0x000000 0x08048000 0x08048000 0x00454 0x00454 RE 0x1000 LOAD 0x000454 0x08049454 0x08049454 0x00104 0x61bac RW 0x1000 DYNAMIC 0x000468 0x08049468 0x08049468 0x000d0 0x000d0 RW 0x4 NOTE 0x000128 0x08048128 0x08048128 0x00020 0x00020 R 0x4 GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x4 

Les en-têtes de type LOAD sont ceux qui sont copiés dans la mémoire virtuelle lorsque le fichier est chargé pour exécution. D’autres en-têtes contiennent d’autres informations, comme les bibliothèques partagées nécessaires. Comme vous le voyez, le FileSize et le MemSiz diffèrent de manière significative pour l’en-tête qui contient la section bss (la deuxième LOAD ):

 0x00104 (file-size) 0x61bac (mem-size) 

Pour cet exemple de code:

 int a[100000]; int main() { } 

La spécification ELF indique que la partie d’un segment dont la taille est supérieure à la taille du fichier est simplement remplie de zéros dans la mémoire virtuelle. Le mappage segment à section du deuxième en-tête LOAD est comme ceci:

 03 .ctors .dtors .jcr .dynamic .got .got.plt .data .bss 

Il y a donc aussi d’autres sections. Pour constructeur / destructeur C ++. La même chose pour Java. Il contient ensuite une copie de la section .dynamic et d’autres éléments utiles pour la liaison dynamic (je crois que c’est l’endroit qui contient les bibliothèques partagées nécessaires, entre autres). Après cela, la section .data qui contient les globaux initialisés et les variables statiques locales. À la fin, la section .bss apparaît, remplie par des zéros au moment du chargement, car la taille du fichier ne le couvre pas.

En passant, vous pouvez voir dans quelle section de sortie un symbole particulier va être placé en utilisant l’option de l’éditeur de liens -M . Pour gcc, vous utilisez -Wl,-M pour placer l’option dans l’éditeur de liens. L’exemple ci-dessus montre qu’un .bss est alloué dans .bss . Cela peut vous aider à vérifier que vos objects non initialisés se retrouvent vraiment dans .bss et pas ailleurs:

 .bss 0x08049560 0x61aa0 [many input .o files...] *(COMMON) *fill* 0x08049568 0x18 00 COMMON 0x08049580 0x61a80 /tmp/cc2GT6nS.o 0x08049580 a 0x080ab000 . = ALIGN ((. != 0x0)?0x4:0x1) 0x080ab000 . = ALIGN (0x4) 0x080ab000 . = ALIGN (0x4) 0x080ab000 _end = . 

GCC conserve les globaux non initialisés dans une section COMMON par défaut, pour assurer la compatibilité avec les anciens compilateurs, ce qui permet de définir des globales deux fois dans un programme sans erreurs de définition multiples. Utilisez -fno-common pour que GCC utilise les sections .bss pour les fichiers objects (ne fait aucune différence pour le dernier exécutable lié car, comme vous le voyez, il va de toute façon entrer dans une section de sortie .bss contrôlée par l’ éditeur de liens) script : ld -verbose avec ld -verbose ). Mais cela ne devrait pas vous faire peur, c’est juste un détail interne. Voir la page de manuel de gcc.

La section .bss d’un fichier ELF est utilisée pour les données statiques qui ne sont pas initialisées par programme mais dont la valeur est garantie à l’exécution. Voici un petit exemple qui expliquera la différence.

 int main() { static int bss_test1[100]; static int bss_test2[100] = {0}; return 0; } 

Dans ce cas, bss_test1 est placé dans le .bss car il n’est pas initialisé. bss_test2 est cependant placé dans le segment .data avec un tas de zéros. Le chargeur d’exécution alloue essentiellement la quantité d’espace réservé au .bss et le met à zéro avant que le code utilisateur ne commence à s’exécuter.

Vous pouvez voir la différence en utilisant objdump , nm ou des utilitaires similaires:

 moozletoots$ objdump -t a.out | grep bss_test 08049780 l O .bss 00000190 bss_test1.3 080494c0 l O .data 00000190 bss_test2.4 

C’est généralement l’une des premières sursockets rencontrées par les développeurs embarqués … n’initialise jamais explicitement les statiques à zéro explicitement. Le chargeur d’exécution (généralement) prend soin de cela. Dès que vous initialisez quelque chose explicitement, vous indiquez au compilateur / éditeur de liens d’inclure les données dans l’image exécutable.

Une section .bss n’est pas stockée dans un fichier exécutable. Parmi les sections les plus courantes ( .text , .data , .bss ), seuls les .text (code actuel) et .data (données initialisées) sont présents dans un fichier ELF.

C’est correct, .bss n’est pas présent physiquement dans le fichier, mais les informations sur sa taille sont présentes pour que le chargeur dynamic puisse allouer la section .bss au programme d’application. En tant que règle de pouce seulement LOAD, le segment TLS obtient la mémoire pour le programme d’application, les autres sont utilisés pour le chargeur dynamic.

A propos du fichier exécutable statique, les sections bss ont également de l’espace dans l’exécutable

Application embarquée où il n’y a pas de chargeur, c’est courant.

Suman