Les variables initialisées à zéro section .bss occupent-elles de l'espace dans le fichier elf?
Question
Si je comprends bien, la section .bss
des fichiers ELF est utilisée pour allouer de l'espace aux variables initialisées à zéro. Notre chaîne d’outils génère des fichiers ELF, d’où ma question: la section .bss
doit-elle contenir tous ces zéros? Cela semble être un tel gaspillage d'espaces que lorsque, par exemple, j'alloue un tableau global de dix mégaoctets, le fichier ELF contient dix mégaoctets de zéros. Qu'est-ce que je vois mal ici?
La solution
Cela fait quelque temps que je travaille avec ELF. Mais je pense que je me souviens encore de ce genre de choses. Non, il ne contient pas physiquement ces zéros. Si vous regardez dans un en-tête de programme de fichier ELF, vous verrez que chaque en-tête a deux chiffres: l'un est la taille du fichier. Et un autre est la taille de la section lorsqu'elle est allouée dans la mémoire virtuelle ( readelf -l ./a.out
):
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
PHDR 0x000034 0x08048034 0x08048034 0x000e0 0x000e0 R E 0x4
INTERP 0x000114 0x08048114 0x08048114 0x00013 0x00013 R 0x1
[Requesting program interpreter: /lib/ld-linux.so.2]
LOAD 0x000000 0x08048000 0x08048000 0x00454 0x00454 R E 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, telles que les bibliothèques partagées nécessaires. Comme vous le voyez, les éléments FileSize
et MemSiz
diffèrent considérablement pour l'en-tête qui contient la section bss
(le deuxième LOAD
un):
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 de mémoire est supérieure à la taille de fichier est simplement renseignée avec des zéros dans la mémoire virtuelle. Le mappage segment à section du deuxième en-tête LOAD
est le suivant:
03 .ctors .dtors .jcr .dynamic .got .got.plt .data .bss
Donc, il y a aussi d'autres sections. Pour les constructeurs / destructeurs C ++. La même chose pour Java. Ensuite, il contient une copie de la section .dynamic
et d'autres éléments utiles pour la liaison dynamique (je pense que c'est l'emplacement contenant les bibliothèques partagées nécessaires, entre autres éléments). Après cela, la section .data ??code> qui contient les globales initialisées et les variables statiques locales. À la fin, la section
.bss
apparaît. Elle est 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 transmettre l’option à l’éditeur de liens. L'exemple ci-dessus montre que un
est alloué dans .bss
. Cela peut vous aider à vérifier que vos objets 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 par défaut les éléments globaux non initialisés dans une section COMMON, par souci de compatibilité avec les anciens compilateurs, ce qui permet de définir les éléments globaux deux fois dans un programme sans erreurs de définition multiples. Utilisez -fno-common
pour que GCC utilise les sections .bss pour les fichiers objet (ne fait aucune différence pour l'exécutable lié final, car, comme vous le voyez, il sera de toute façon inséré dans une section de sortie .bss Ceci est contrôlé par le script de l'éditeur de liens . Affichez-le avec ld -verbose
). Mais cela ne devrait pas vous effrayer, c'est juste un détail interne. Voir la page de manuel de gcc.
Autres conseils
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 garantie est de définir la valeur zéro lors de 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
puisqu'il n'est pas initialisé. bss_test2
est toutefois placé dans le segment .data ??code> avec un groupe de zéros. Le chargeur d’exécution alloue en principe la quantité d’espace réservé au
.bss
et le met à zéro avant l’exécution de tout code utilisateur.
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 surprises auxquelles se heurtent les développeurs intégrés: ne jamais initialiser la statique de manière explicite. Le chargeur d'exécution (généralement) s'en occupe. Dès que vous initialisez quoi que ce soit explicitement, vous demandez au compilateur / lieur 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 ??code>,
.bss
), seul le .text
(code réel ) et .data ??code> (données initialisées) sont présents dans un fichier ELF.
C'est exact. Le fichier .bss n'est pas présent physiquement dans le fichier. Seules les informations sur sa taille sont présentes pour que le chargeur dynamique attribue la section .bss au programme d'application. La règle thumb étant uniquement LOAD, TLS Segment récupère la mémoire du programme d’application, les autres sont utilisés pour le chargeur dynamique.
À propos des fichiers exécutables statiques, les sections bss se voient également attribuer un espace dans l'exécutable
Application intégrée où il n'y a pas de chargeur, c'est courant.
Suman