Exemple Ubuntu 15.10, Kernel 4.2.0, x86-64, GCC 5.2.1
Assez de normes, regardons une implémentation :-)
Variable locale
Normes: comportement indéfini.
Implémentation: le programme alloue de l'espace de pile, et ne déplace jamais rien vers cette adresse, donc tout ce qui s'y trouvait auparavant est utilisé.
#include <stdio.h>
int main() {
int i;
printf("%d\n", i);
}
compiler avec:
gcc -O0 -std=c99 a.c
les sorties:
0
et décompile avec:
objdump -dr a.out
à:
0000000000400536 <main>:
400536: 55 push %rbp
400537: 48 89 e5 mov %rsp,%rbp
40053a: 48 83 ec 10 sub $0x10,%rsp
40053e: 8b 45 fc mov -0x4(%rbp),%eax
400541: 89 c6 mov %eax,%esi
400543: bf e4 05 40 00 mov $0x4005e4,%edi
400548: b8 00 00 00 00 mov $0x0,%eax
40054d: e8 be fe ff ff callq 400410 <printf@plt>
400552: b8 00 00 00 00 mov $0x0,%eax
400557: c9 leaveq
400558: c3 retq
D'après notre connaissance des conventions d'appel x86-64:
%rdiest le premier argument printf, donc la chaîne "%d\n"à l'adresse0x4005e4
%rsiest donc le deuxième argument printf i.
Il provient de -0x4(%rbp), qui est la première variable locale de 4 octets.
À ce stade, rbpest dans la première page de la pile a été allouée par le noyau, donc pour comprendre cette valeur, nous devrions examiner le code du noyau et découvrir ce qu'il définit.
TODO est-ce que le noyau définit cette mémoire sur quelque chose avant de la réutiliser pour d'autres processus lorsqu'un processus meurt? Sinon, le nouveau processus serait capable de lire la mémoire d'autres programmes terminés, ce qui fuirait des données. Voir: Les valeurs non initialisées représentent-elles un risque de sécurité?
Nous pouvons alors également jouer avec nos propres modifications de pile et écrire des choses amusantes comme:
#include <assert.h>
int f() {
int i = 13;
return i;
}
int g() {
int i;
return i;
}
int main() {
f();
assert(g() == 13);
}
Variable locale dans -O3
Analyse d'implémentation à: Que signifie <value optimized out> dans gdb?
Variables globales
Normes: 0
Mise en œuvre: .bsssection.
#include <stdio.h>
int i;
int main() {
printf("%d\n", i);
}
gcc -00 -std=c99 a.c
compile en:
0000000000400536 <main>:
400536: 55 push %rbp
400537: 48 89 e5 mov %rsp,%rbp
40053a: 8b 05 04 0b 20 00 mov 0x200b04(%rip),%eax # 601044 <i>
400540: 89 c6 mov %eax,%esi
400542: bf e4 05 40 00 mov $0x4005e4,%edi
400547: b8 00 00 00 00 mov $0x0,%eax
40054c: e8 bf fe ff ff callq 400410 <printf@plt>
400551: b8 00 00 00 00 mov $0x0,%eax
400556: 5d pop %rbp
400557: c3 retq
400558: 0f 1f 84 00 00 00 00 nopl 0x0(%rax,%rax,1)
40055f: 00
# 601044 <i>dit que ic'est à l'adresse 0x601044et:
readelf -SW a.out
contient:
[25] .bss NOBITS 0000000000601040 001040 000008 00 WA 0 0 4
qui dit 0x601044est en plein milieu de la .bsssection, qui commence à 0x601040et fait 8 octets de long.
La norme ELF garantit alors que la section nommée .bssest complètement remplie de zéros:
.bssCette section contient des données non initialisées qui contribuent à l'image mémoire du programme. Par définition, le système initialise les données avec des zéros lorsque le programme commence à s'exécuter. La section des tartes oc- pas d' espace de fichier, comme indiqué par le type de section, SHT_NOBITS.
De plus, le type SHT_NOBITSest efficace et n'occupe aucun espace sur le fichier exécutable:
sh_sizeCe membre donne la taille de la section en octets. Sauf si le type de SHT_NOBITSsection est , la section occupe des sh_size
octets dans le fichier. Une section de type SHT_NOBITSpeut avoir une taille différente de zéro, mais elle n'occupe aucun espace dans le fichier.
Ensuite, c'est au noyau Linux de remettre à zéro cette région mémoire lors du chargement du programme en mémoire au démarrage.