Приклад 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
розділу, який починається з 0x601040
8 байт.
Тоді стандарт ELF гарантує, що названий розділ .bss
повністю заповнений нулями:
.bss
У цьому розділі містяться неініціалізовані дані, що сприяють образу пам'яті програми. За визначенням, система ініціалізує дані з нулями, коли програма починає працювати. Розділ не займає файлового простору, як зазначено в типі розділу SHT_NOBITS
.
Крім того, тип SHT_NOBITS
ефективний і не займає місця у виконуваному файлі:
sh_size
Цей член надає розмір розділу в байтах. Якщо тип SHT_NOBITS
розділу не є , розділ займає sh_size
байти у файлі. Розділ типу SHT_NOBITS
може мати ненульовий розмір, але він не займає місця у файлі.
Тоді від ядра Linux доводиться з нуля викреслити цю область пам’яті при завантаженні програми в пам'ять при її запуску.