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:
%rdi
est le premier argument printf, donc la chaîne "%d\n"
à l'adresse0x4005e4
%rsi
est donc le deuxième argument printf i
.
Il provient de -0x4(%rbp)
, qui est la première variable locale de 4 octets.
À ce stade, rbp
est 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: .bss
section.
#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 i
c'est à l'adresse 0x601044
et:
readelf -SW a.out
contient:
[25] .bss NOBITS 0000000000601040 001040 000008 00 WA 0 0 4
qui dit 0x601044
est en plein milieu de la .bss
section, qui commence à 0x601040
et fait 8 octets de long.
La norme ELF garantit alors que la section nommée .bss
est complètement remplie de zéros:
.bss
Cette 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_NOBITS
est efficace et n'occupe aucun espace sur le fichier exécutable:
sh_size
Ce membre donne la taille de la section en octets. Sauf si le type de SHT_NOBITS
section est , la section occupe des sh_size
octets dans le fichier. Une section de type SHT_NOBITS
peut 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.