進程地址空間分佈

轉載請註明出處:http://blog.csdn.net/wangxiaolong_china

 

對於一個進程,其空間分佈如下圖所示:


C程序一般分爲:

1.程序段:程序段爲程序代碼在內存中的映射.一個程序可以在內存中多有個副本.

2.初始化過的數據:在程序運行值初已經對變量進行初始化的

3.未初始化過的數據:在程序運行初未對變量進行初始化的數據

4.堆(stack):存儲局部,臨時變量,在程序塊開始時自動分配內存,結束時自動釋放內存.存儲函數的返回指針.

5.棧(heap):存儲動態內存分配,需要程序員手工分配,手工釋放.

多任務操作系統中的每一個進程都運行在一個屬於它自己的內存沙盤中,這個沙盤就是虛擬地址空間(virtual address space),在32位模式下,它總是一個4GB的內存地址塊。這些虛擬地址通過頁表(page table)映射到物理內存,頁表由操作系統維護並被處理器引用。每個進程都擁有一套屬於它自己的頁表,但是還有一個隱情,只要虛擬地址被使能,那麼它將會作用於這臺機器上運行的所有軟件,包括內核本身,因此,有一部分虛擬地址必須保留給內核使用。進程內存空間分佈如下圖所示:


但是這並不意味着內核使用了這麼多的物理內存,僅表示它可以支配這麼大的地址空間。可根據內核需要,將其映射到物理內存。內核空間在頁表中擁有較高的特權級(ring2或以下),因此,只要用戶態的程序試圖訪問這些頁,就會導致一個頁錯誤(page fault)。在Linux中,內核空間是持續存在的,並且在所有進程中都映射到同樣的物理內存,內核代碼和數據總是可尋址的,隨時準備處理中斷和系統調用。與之相反,用戶模式地址空間的映射隨着進程切換的發生而不斷的變化,如下圖所示:


上圖中藍色區域表示映射到物理內存的虛擬地址,而白色區域表示未映射的部分。可以看出,Firefox使用了相當多的虛擬地址空間,因爲它佔用內存較多。

Linux進程標準的內存段佈局,如下圖所示,地址空間中的各個條帶對應於不同的內存段(memory segment),如:堆、棧之類的。記住,這些段只是簡單的虛擬內存地址空間範圍,與Intel處理器的段沒有任何關係。


幾乎每個進程的虛擬地址空間中各段的分佈都與上圖完全一致。

這就給遠程發掘程序漏洞的人打開了方便之門。一個發掘過程往往需要引用絕對內存地址:棧地址,庫函數地址等。遠程攻擊者必須依賴地址空間分佈的一致性,來探索出這些地址。如果讓他們猜個正着,那麼有人就會被整了。因此,地址空間的隨機排布方式便逐漸流行起來,Linux通過對棧、內存映射段、堆的起始地址加上隨機的偏移量來打亂佈局。但不幸的是,32位地址空間相當緊湊,這給隨機化所留下的空間不大,削弱了這種技巧的效果。

進程地址空間中最頂部的段是棧,大多數編程語言將之用於存儲函數參數和局部變量。調用一個方法或函數會將一個新的棧幀(stack frame)壓入到棧中,這個棧幀會在函數返回時被清理掉。由於棧中數據嚴格的遵守LIFO的順序,這個簡單的設計意味着不必使用複雜的數據結構來追蹤棧中的內容,只需要一個簡單的指針指向棧的頂端即可,因此壓棧(pushing)和退棧(popping)過程非常迅速、準確。進程中的每一個線程都有屬於自己的棧。

通過不斷向棧中壓入數據,超出其容量就會耗盡棧所對應的內存區域,這將觸發一個頁故障(page fault),而被Linux的expand_stack()處理,它會調用acct_stack_growth()來檢查是否還有合適的地方用於棧的增長。如果棧的大小低於RLIMIT_STACK(通常爲8MB),那麼一般情況下棧會被加長,程序繼續執行,感覺不到發生了什麼事情。這是一種將棧擴展到所需大小的常規機制。然而,如果達到了最大棧空間的大小,就會棧溢出(stack overflow),程序收到一個段錯誤(segmentation fault)。

動態棧增長是唯一一種訪問未映射內存區域而被允許的情形,其他任何對未映射內存區域的訪問都會觸發頁錯誤,從而導致段錯誤。一些被映射的區域是隻讀的,因此企圖寫這些區域也會導致段錯誤。

在棧的下方,是我們的內存映射段。內核將文件的內容直接映射到內存。任何應用程序都可以通過Linux的mmap()系統調用或者Windows的CreateFileMapping()/MapViewOfFile()請求這種映射。內存映射是一種方便高效的文件I/O方式,所以它被用來加載動態庫。創建一個不對應於任何文件的匿名內存映射也是可能的,此方法用於存放程序的數據。在Linux中,如果你通過malloc()請求一大塊內存,C運行庫將會創建這樣一個匿名映射而不是使用堆內存。“大塊”意味着比MMAP_THRESHOLD還大,缺省128KB,可以通過mallocp()調整。

接下來的一塊內存空間是堆。與棧一樣,堆用於運行時內存分配;但不同的是,堆用於存儲那些生存期與函數調用無關的數據。大部分語言都提供了堆管理功能。在C語言中,堆分配的接口是malloc()函數。如果堆中有足夠的空間來滿足內存請求,它就可以被語言運行時庫處理而不需要內核參與,否則,堆會被擴大,通過brk()系統調用來分配請求所需的內存塊。堆管理是很複雜的,需要精細的算法來應付我們程序中雜亂的分配模式,優化速度和內存使用效率。處理一個堆請求所需的時間會大幅度的變動。實時系統通過特殊目的分配器來解決這個問題。堆在分配過程中可能會變得零零碎碎,如下圖所示:


最後,我們看看底部的內存段:BSS,數據段,代碼段。

在C語言中,BSS和數據段保存的都是靜態(全局)變量的內容。區別在於BSS保存的是未被初始化的靜態變量內容,他們的值不是直接在程序的源碼中設定的。BSS內存區域是匿名的,它不映射到任何文件。如果你寫static intcntActiveUsers,則cntActiveUsers的內容就會保存到BSS中去。而數據段則保存在源代碼中已經初始化的靜態變量的內容。數據段不是匿名的,它映射了一部分的程序二進制鏡像,也就是源代碼中指定了初始值的靜態變量。所以,如果你寫static int cntActiveUsers=10,則cntActiveUsers的內容就保存在了數據段中,而且初始值是10。儘管數據段映射了一個文件,但它是一個私有內存映射,這意味着更改此處的內存不會影響被映射的文件。

你可以通過閱讀文件/proc/pid_of_process/maps來檢驗一個Linux進程中的內存區域。記住:一個段可能包含許多區域。比如,每個內存映射文件在mmap段中都有屬於自己的區域,動態庫擁有類似BSS和數據段的額外區域。有時人們提到“數據段”,指的是全部的數據段+BSS+堆。

你還可以通過nm和objdump命令來察看二進制鏡像,打印其中的符號,它們的地址,段等信息。最後需要指出的是,前文描述的虛擬地址佈局在linux中是一種“靈活佈局”,而且作爲默認方式已經有些年頭了,它假設我們有值RLIMT_STACK。但是,當沒有該值得限制時,Linux退回到“經典佈局”,如下圖所示:


C語言程序實例分析如下所示:

#include<stdio.h>
#include <malloc.h>

void print(char *,int);
int main()
{
    char *s1 = "abcde";
    char *s2 = "abcde";
    char s3[] = "abcd";
    long int *s4[100];
    char *s5 = "abcde";
    int a = 5;
    int b =6;//a,b在棧上,&a>&b地址反向增長

    printf("variables address in main function:n
            s1=%ps2=%p s3=%p s4=%p s5=%p a=%p b=%pnn", 
            s1,s2,s3,s4,s5,&a,&b);
    printf("variables address in processcall:n");
    print("ddddddddd",5);//參數入棧從右至左進行,p先進棧,str後進 &p>&str
    printf("nmain=%p print=%pn",main,print);
    //打印代碼段中主函數和子函數的地址,編譯時先編譯的地址低,後編譯的地址高main<print
}
void print(char *str,intp)
{
    char *s1 = "abcde";//abcde在常量區,s1在棧上
    char *s2 = "abcde";//abcde在常量區,s2在棧上 s2-s1=6可能等於0,編譯器優化了相同的常量,只在內存保存一份
    //而&s1>&s2
    char s3[] = "abcdeee";//abcdeee在常量區,s3在棧上,數組保存的內容爲abcdeee的一份拷貝
    long int *s4[100];
    char *s5 = "abcde";
    int a = 5;
    int b =6;
    int c;
    int d;//a,b,c,d均在棧上,&a>&b>&c>&d地址反向增長
    char *q=str;//
    int m=p;//
    char *r=(char *)malloc(1);
    char *w=(char *)malloc(1);// r<w 堆正向增長

    printf("s1=%p s2=%p s3=%p s4=%p s5=%p na=%p b=%pc=%p d=%pn 
            str=%pq=%p p=%p m=%p r=%p w=%pn",
            s1,s2,s3,s4,s5,&a,&b,&c,&d,&str,q,&p,&m,r,w);
}
/* 棧和堆是在程序運行時候動態分配的,局部變量均在棧上分配。棧是反向增長的,地址遞減;malloc等分配的內存空間在堆空間。堆是正向增長的,地址遞增。
r,w變量在棧上(則&r>&w),r,w所指內容在堆中(即r<w)。*/



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章