Приклад Ubuntu 15.10, ядро 4.2.0, x86-64, GCC 5.2.1
Досить стандартів, давайте подивимось на реалізацію :-)
Локальна змінна
Стандарти: невизначена поведінка.
Реалізація: програма виділяє стек простору і ніколи нічого не переміщує на цю адресу, тому все, що там було раніше, використовується.
#include <stdio.h>
int main() {
int i;
printf("%d\n", i);
}
скласти з:
gcc -O0 -std=c99 a.c
Виходи:
0
і декомпілюється за допомогою:
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
З наших знань про x86-64 виклики:
%rdiє першим аргументом printf, таким чином, рядок "%d\n"за адресою0x4005e4
%rsiє другим аргументом printf, таким чином i.
Він походить від -0x4(%rbp), що є першою 4-байтною локальною змінною.
На даний момент, rbpна першій сторінці стека було виділено ядро, тому, щоб зрозуміти це значення, ми б заглянули в код ядра і дізналися, для чого це встановлено.
TODO чи ядро встановлює цю пам'ять на щось перед тим, як використовувати її для інших процесів, коли процес гине? Якщо ні, новий процес міг би прочитати пам'ять інших готових програм, витікаючи дані. Див.: Чи неініціалізовані значення колись становлять небезпеку для безпеки?
Потім ми можемо також грати з нашими власними модифікаціями стека і писати цікаві речі, такі як:
#include <assert.h>
int f() {
int i = 13;
return i;
}
int g() {
int i;
return i;
}
int main() {
f();
assert(g() == 13);
}
Локальна змінна в -O3
Аналіз впровадження: Що значить <оптимізоване значення> у gdb?
Глобальні змінні
Стандарти: 0
Реалізація: .bssрозділ.
#include <stdio.h>
int i;
int main() {
printf("%d\n", i);
}
gcc -00 -std=c99 a.c
компілює до:
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>каже, що iза адресою 0x601044:
readelf -SW a.out
містить:
[25] .bss NOBITS 0000000000601040 001040 000008 00 WA 0 0 4
що говорить, що 0x601044знаходиться в середині .bssрозділу, який починається з 0x6010408 байт.
Тоді стандарт ELF гарантує, що названий розділ .bssповністю заповнений нулями:
.bssУ цьому розділі містяться неініціалізовані дані, що сприяють образу пам'яті програми. За визначенням, система ініціалізує дані з нулями, коли програма починає працювати. Розділ не займає файлового простору, як зазначено в типі розділу SHT_NOBITS.
Крім того, тип SHT_NOBITSефективний і не займає місця у виконуваному файлі:
sh_sizeЦей член надає розмір розділу в байтах. Якщо тип SHT_NOBITSрозділу не є , розділ займає sh_size
байти у файлі. Розділ типу SHT_NOBITSможе мати ненульовий розмір, але він не займає місця у файлі.
Тоді від ядра Linux доводиться з нуля викреслити цю область пам’яті при завантаженні програми в пам'ять при її запуску.