各位看官們,大家好,上一回中咱們說的是C程序內存布局的例子,這一回咱們繼續說該例子。閒話休提,言歸正轉。讓我們一起talk C栗子吧!
看官們,我們在上一回中介紹了C程序在內存中的布局,並且給大家做了簡單的演示。上一回的例子比較簡單,只能說明程序中內存布局的大體輪廓。我們今天會通過具體的內存地址來清楚地介紹C程序在內存中的布局。下面是例子的程序源代碼,請大家參考:
int ga1;
int ga2 = 1;
int func()
{
int i = 0;
static static_la1;
static static_la2 = 3;
printf("func is running \n");
printf("Address of i: %p \n",&i);
printf("Address of static_la1 : %p \n",&static_la1);
printf("Address of static_la2 : %p \n",&static_la2);
while(i++<8)
sleep(1);
return 0;
}
int main()
{
int la1;
int la2 = 2;
int *p;
p = (int *) malloc(3*sizeof(int));
if(NULL == p)
printf("malloc failed \n");
printf("Address of ga1: %p \n",&ga1);
printf("Address of ga2: %p \n",&ga2);
printf("Address of la1: %p \n",&la1);
printf("Address of la2: %p \n",&la2);
printf("Address of p: %p \n",p);
func();
free(p);
p = NULL;
return 0;
}
在該代碼中,我們手動輸出各個變量的地址,這樣做的目的是為了通過變量的地址來判斷變量在內存中的位置,進而確認變量屬於內存布局中的哪個分區。那麼這些變量究竟是在哪個分區中呢?data分區還是bss分區?下面是程序的運行結果,請大家參考:
Address of ga1: 0x804a040
Address of ga2: 0x804a030
Address of la1: 0xbfc7e7b4
Address of la2: 0xbfc7e7b8
Address of p: 0x8ffc008
func is running
Address of i: 0xbfc7e78c
Address of static_la1 : 0x804a03c
Address of static_la2 : 0x804a034
從程序的運行結果中我們可以看到,各個變量在內存中的地址,不過,我們還是不知道這些變量屬於哪個內存分區。看官們莫急,我們通過readelf工具來查看該程序的內存布局,通過內存布局就可以看到各個內存分區的地址范圍,這些我就們就能依據變量的地址推斷出變量所在的內存分區。下面是詳細的結果:
readelf -S s //使用readelf 工具查看程序的內存布局
There are 30 section headers, starting at offset 0x1190:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .interp PROGBITS 08048154 000154 000013 00 A 0 0 1
[ 2] .note.ABI-tag NOTE 08048168 000168 000020 00 A 0 0 4
[ 3] .note.gnu.build-i NOTE 08048188 000188 000024 00 A 0 0 4
[ 4] .gnu.hash GNU_HASH 080481ac 0001ac 000020 04 A 5 0 4
[ 5] .dynsym DYNSYM 080481cc 0001cc 000090 10 A 6 1 4
[ 6] .dynstr STRTAB 0804825c 00025c 000063 00 A 0 0 1
[ 7] .gnu.version VERSYM 080482c0 0002c0 000012 02 A 5 0 2
[ 8] .gnu.version_r VERNEED 080482d4 0002d4 000020 00 A 6 1 4
[ 9] .rel.dyn REL 080482f4 0002f4 000008 08 A 5 0 4
[10] .rel.plt REL 080482fc 0002fc 000038 08 A 5 12 4
[11] .init PROGBITS 08048334 000334 000023 00 AX 0 0 4
[12] .plt PROGBITS 08048360 000360 000080 04 AX 0 0 16
[13] .text PROGBITS 080483e0 0003e0 0002a2 00 AX 0 0 16
[14] .fini PROGBITS 08048684 000684 000014 00 AX 0 0 4
[15] .rodata PROGBITS 08048698 000698 0000dc 00 A 0 0 4
[16] .eh_frame_hdr PROGBITS 08048774 000774 000034 00 A 0 0 4
[17] .eh_frame PROGBITS 080487a8 0007a8 0000d0 00 A 0 0 4
[18] .init_array INIT_ARRAY 08049f08 000f08 000004 00 WA 0 0 4
[19] .fini_array FINI_ARRAY 08049f0c 000f0c 000004 00 WA 0 0 4
[20] .jcr PROGBITS 08049f10 000f10 000004 00 WA 0 0 4
[21] .dynamic DYNAMIC 08049f14 000f14 0000e8 08 WA 6 0 4
[22] .got PROGBITS 08049ffc 000ffc 000004 04 WA 0 0 4
[23] .got.plt PROGBITS 0804a000 001000 000028 04 WA 0 0 4
[24] .data PROGBITS 0804a028 001028 000010 00 WA 0 0 4
[25] .bss NOBITS 0804a038 001038 00000c 00 WA 0 0 4
[26] .comment PROGBITS 00000000 001038 00004f 01 MS 0 0 1
[27] .shstrtab STRTAB 00000000 001087 000106 00 0 0 1
[28] .symtab SYMTAB 00000000 001640 0004c0 10 29 47 4
[29] .strtab STRTAB 00000000 001b00 0002be 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings)
I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
從運行結果中可以看到程序的
data分區從地址0x0804a028開始,大小為0x000010(16byte)。
bss分區從0x0804a038開始,大小為0x00000c(12byte)。
那麼我們結合程序中變量的地址來推斷一下變量所在的內存分區:
Address of ga1: 0x804a040 //變量的地址大於bss(0x0804a038),變量位於bss分區
Address of ga2: 0x804a030 //變量的地址大於data(0x0804a028),而且小於bss,變量屬於data分區
Address of static_la1 : 0x804a03c //變量的地址大於bss(0x0804a038),變量位於bss分區
Address of static_la2 : 0x804a034 //變量的地址大於data(0x0804a028),而且小於bss,變量屬於data分區
還有四個變量的地址不在bss和data分區范圍內,因此不能確定這些變量所在的分區:
Address of la1: 0xbfc7e7b4 //變量的地址大於bss(0x0804a038),但是超出了bss分區的范圍
Address of la2: 0xbfc7e7b8 //變量的地址大於bss(0x0804a038),但是超出了bss分區的范圍
Address of p: 0x8ffc008 //變量的地址大於bss(0x0804a038),但是超出了bss分區的范圍
Address of i: 0xbfc7e78c //變量的地址大於bss(0x0804a038),但是超出了bss分區的范圍
我們在前一章回中介紹過,重要的分區就四種,他們不在data和bss,那麼只能在堆區或者棧區了。現在我們只有變量的地址,要是有堆區或者棧區的地址范圍就好了。這樣我們就可以向剛才推斷bss和data分區中的變量一樣來推斷這些變量在內存中的分區。
接下我們一起查找一下程序的棧和堆分區。還記得我們在前面章回中介紹的proc虛擬文件系統嗎?我們可以借助它來獲得棧區和堆區的內存空間信息。
./s & //在後台運行編譯好的程序
[1] 2736 //程序在後台正常運行,同時顯示程序的PID
Address of ga1: 0x804a040 //程序運行,輸出程序中的內容
Address of ga2: 0x804a030
Address of la1: 0xbfe01cf4
Address of la2: 0xbfe01cf8
Address of p: 0x964d008
func is running
Address of i: 0xbfe01ccc
Address of static_la1 : 0x804a03c
Address of static_la2 : 0x804a034
cat /proc/2736/maps //查看proc中關於進程的信息
08048000-08049000 r-xp 00000000 08:13 22415696 /home/talk8/s
08049000-0804a000 r--p 00000000 08:13 22415696 /home/talk8/s
0804a000-0804b000 rw-p 00001000 08:13 22415696 /home/talk8/s
0964d000-0966e000 rw-p 00000000 00:00 0 [heap]
b75b7000-b75b8000 rw-p 00000000 00:00 0
b75b8000-b7761000 r-xp 00000000 08:16 6444 /lib/i386-linux-gnu/libc-2.19.so
b7761000-b7763000 r--p 001a9000 08:16 6444 /lib/i386-linux-gnu/libc-2.19.so
b7763000-b7764000 rw-p 001ab000 08:16 6444 /lib/i386-linux-gnu/libc-2.19.so
b7764000-b7767000 rw-p 00000000 00:00 0
b777e000-b7781000 rw-p 00000000 00:00 0
b7781000-b7782000 r-xp 00000000 00:00 0 [vdso]
b7782000-b77a2000 r-xp 00000000 08:16 6459 /lib/i386-linux-gnu/ld-2.19.so
b77a2000-b77a3000 r--p 0001f000 08:16 6459 /lib/i386-linux-gnu/ld-2.19.so
b77a3000-b77a4000 rw-p 00020000 08:16 6459 /lib/i386-linux-gnu/ld-2.19.so
bfde3000-bfe04000 rw-p 00000000 00:00 0 [stack]
從上面信息中我們可以看到,標記為[heap]和[stack]的分區就是我們要找的堆區和棧區,與其在同一行中的內容顯示了這兩個分區的內存地址空間。
堆區:0x0964d000-0x0966e000
棧區:0xbfde3000-0xbfe04000
現在,我們可以推斷剛才哪四個不知道自己所在分區的變量了,下面是我們的推斷結果:
Address of la1: 0xbfe01cf4 //變量的地址位於棧區地址空間內,該變量位於棧區
Address of la2: 0xbfe01cf8 //變量的地址位於棧區地址空間內,該變量位於棧區
Address of p: 0x964d008 //變量的地址位於堆區地址空間內,該變量位於堆區
Address of i: 0xbfe01ccc //變量的地址位於棧區地址空間內,該變量位於棧區
看官們,到目前為止, 我們通過具體的內存地址清楚地展示了C程序在內存中的布局。最後我們畫一個直觀的圖形給大家看,這樣大家就能直觀地看清楚C程序的內存模型了。
下載使用(CSDN又出問題了,所以暫時不能上傳程序,等CSDN修復好問題後,我會及時上傳程序,到時候大家可以到我們資源中下載程序)。因為每個人的電腦是不同的,而且所使用的編程環境也可能不相同,所以建議各位看官都下載程序到自己的電腦上運行,然後按照我們介紹的方法觀察程序在內存中的模型。
各位看官,關於C程序內存布局的例子咱們就說到這裡。欲知後面還有什麼例子,且聽下回分解 。