Ví dụ Ubuntu 15.10, Kernel 4.2.0, x86-64, GCC 5.2.1
Đủ tiêu chuẩn, chúng ta hãy nhìn vào một triển khai :-)
Biến cục bộ
Tiêu chuẩn: hành vi không xác định.
Triển khai: chương trình phân bổ không gian ngăn xếp và không bao giờ di chuyển bất cứ thứ gì đến địa chỉ đó, vì vậy bất cứ thứ gì trước đó đều được sử dụng.
#include <stdio.h>
int main() {
int i;
printf("%d\n", i);
}
biên dịch với:
gcc -O0 -std=c99 a.c
đầu ra:
0
và dịch ngược với:
objdump -dr a.out
đến:
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
Từ kiến thức của chúng tôi về các quy ước gọi x86-64:
%rdi
là đối số printf đầu tiên, do đó, chuỗi "%d\n"
tại địa chỉ0x4005e4
%rsi
là đối số printf thứ hai, do đó i
.
Nó đến từ -0x4(%rbp)
, là biến cục bộ 4 byte đầu tiên.
Tại thời điểm này, rbp
trong trang đầu tiên của ngăn xếp đã được cấp phát bởi kernel, vì vậy để hiểu giá trị đó, chúng ta sẽ xem xét mã hạt nhân và tìm hiểu xem nó đặt cái gì.
TODO kernel có đặt bộ nhớ đó thành một cái gì đó trước khi sử dụng lại nó cho các tiến trình khác khi một tiến trình chết không? Nếu không, quá trình mới sẽ có thể đọc bộ nhớ của các chương trình đã hoàn thành khác, rò rỉ dữ liệu. Xem: Các giá trị chưa được khởi tạo có bao giờ là rủi ro bảo mật không?
Sau đó chúng ta cũng có thể chơi với các sửa đổi ngăn xếp của riêng mình và viết những điều thú vị như:
#include <assert.h>
int f() {
int i = 13;
return i;
}
int g() {
int i;
return i;
}
int main() {
f();
assert(g() == 13);
}
Biến cục bộ trong -O3
Phân tích triển khai tại: <value được tối ưu hóa> có nghĩa là gì trong gdb?
Biến toàn cầu
Tiêu chuẩn: 0
Thực hiện: .bss
phần.
#include <stdio.h>
int i;
int main() {
printf("%d\n", i);
}
gcc -00 -std=c99 a.c
biên dịch thành:
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>
nói rằng đó i
là tại địa chỉ 0x601044
và:
readelf -SW a.out
chứa đựng:
[25] .bss NOBITS 0000000000601040 001040 000008 00 WA 0 0 4
trong đó nói 0x601044
là ở giữa .bss
phần, bắt đầu từ 0x601040
và dài 8 byte.
Các tiêu chuẩn ELF sau đó đảm bảo rằng phần tên .bss
là hoàn toàn đầy các zeros:
.bss
Phần này chứa dữ liệu chưa được khởi tạo góp phần vào hình ảnh bộ nhớ của chương trình. Theo định nghĩa, hệ thống khởi tạo dữ liệu bằng số không khi chương trình bắt đầu chạy. Phần này không có không gian tệp, như được chỉ định bởi loại phần , SHT_NOBITS
.
Hơn nữa, loại SHT_NOBITS
này là hiệu quả và không chiếm dung lượng trên tệp thực thi:
sh_size
Thành viên này cung cấp kích thước của phần tính bằng byte. Trừ khi loại thứ hai là SHT_NOBITS
, phần chiếm sh_size
byte trong tệp. Một phần của loại SHT_NOBITS
có thể có kích thước khác không, nhưng nó không chiếm không gian trong tệp.
Sau đó, hạt nhân Linux sẽ loại bỏ vùng nhớ đó khi tải chương trình vào bộ nhớ khi khởi động.