堆棧、linux c程序存儲空間佈局的總結

棧主要用來存放局部變量, 傳遞參數, 存放函數的返回地址.esp 始終指向棧頂, 棧中的數據越多, esp的值越小.
堆用於存放動態分配的對象, 當你使用 malloc , new 等進行分配時,所得到的空間就在堆中. 動態分配得到的內存附帶有分配信息, 所以你能夠 realloc 和 free調它們.
全局,靜態和常量是分配在數據區中的。數據區包括bss和初始化區。
堆向高內存地址生長
棧向低內存地址生長
堆和棧相向而生,堆和棧之間有個臨界點,稱爲stkbrk
CODE:
進程在內存中的影像.
      我們假設現在有一個程序, 它的函數調用順序如下.
      main(...) ->; func_1(...) ->; func_2(...) ->; func_3(...)
      即: 主函數main調用函數func_1; 函數func_1調用函數func_2; 函數func_2調用函數func_3
      當程序被操作系統調入內存運行, 其相對應的進程在內存中的影像如下圖所示.
        (內存高址)
        +--------------------------------------+
        |             ......                   | ... 省略了一些我們不需要關心的區
        +--------------------------------------+
        | env strings (環境變量字串)          | /
        +--------------------------------------+ /
        | argv strings (命令行字串)           |   /
        +--------------------------------------+    /
        | env pointers (環境變量指針)         |    SHELL的環境變量和命令行參數保存區
        +--------------------------------------+    /
        | argv pointers (命令行參數指針)      |   /
        +--------------------------------------+ /
        | argc (命令行參數個數)               | /
        +--------------------------------------+
        |            main 函數的棧幀           | /
        +--------------------------------------+ /
        |            func_1 函數的棧幀         |   /
        +--------------------------------------+    /
        |            func_2 函數的棧幀         |     /
        +--------------------------------------+      /
        |            func_3 函數的棧幀         |      Stack (棧)
        +......................................+      /
        |                                      |     /
                      ......                        /
        |                                      |   /
        +......................................+ /
        |            Heap (堆)                 | /
        +--------------------------------------+
        |        Uninitialised (BSS) data      | 非初始化數據(BSS)區
        +--------------------------------------+
        |        Initialised data              | 初始化數據區
        +--------------------------------------+
        |        Text                          | 文本區
        +--------------------------------------+
        (內存低址)
        這裏需要說明的是:
        i)   隨着函數調用層數的增加, 函數棧幀是一塊塊地向內存低地址方向延伸的.
             隨着進程中函數調用層數的減少, 即各函數調用的返回, 棧幀會一塊塊地
             被遺棄而向內存的高址方向回縮.
             各函數的棧幀大小隨着函數的性質的不同而不等, 由函數的局部變量的數目決定.
        ii) 進程對內存的動態申請是發生在Heap(堆)裏的. 也就是說, 隨着系統動態分
             配給進程的內存數量的增加, Heap(堆)有可能向高址或低址延伸, 依賴於不
             同CPU的實現. 但一般來說是向內存的高地址方向增長的.
        iii) 在BSS數據或者Stack(棧)的增長耗盡了系統分配給進程的自由內存的情況下,
             進程將會被阻塞, 重新被操作系統用更大的內存模塊來調度運行.
             (雖然和exploit沒有關係, 但是知道一下還是有好處的)
        iv) 函數的棧幀裏包含了函數的參數(至於被調用函數的參數是放在調用函數的棧
             幀還是被調用函數棧幀, 則依賴於不同系統的實現),
             它的局部變量以及恢復調用該函數的函數的棧幀(也就是前一個棧幀)所需要的
             數據, 其中包含了調用函數的下一條執行指令的地址.
        v)   非初始化數據(BSS)區用於存放程序的靜態變量, 這部分內存都是被初始化爲零的.
             初始化數據區用於存放可執行文件裏的初始化數據.
             這兩個區統稱爲數據區.
        vi) Text(文本區)是個只讀區, 任何嘗試對該區的寫操作會導致段違法出錯. 文本區
             是被多個運行該可執行文件的進程所共享的. 文本區存放了程序的代碼.
    2) 函數的棧幀.
       函數調用時所建立的棧幀包含了下面的信息:
       i)   函數的返回地址. 返回地址是存放在調用函數的棧幀還是被調用函數的棧幀裏,
            取決於不同系統的實現.
       ii) 調用函數的棧幀信息, 即棧頂和棧底.
       iii) 爲函數的局部變量分配的空間
       iv) 爲被調用函數的參數分配的空間--取決於不同系統的實現.

另外:
返回值即使放在棧中也未必不行。因爲每個進程擁有自己的棧空間,只要在其它函數運行之前,把返回值取出來就行。棧中的數據一般不會被自動銷燬,棧指針動了一下而已,數據還在那裏。
返回值如何實現取於編譯器和採用的編譯規則, 其中並沒有通用的標準. 比如要從寄存器返回, 但不同體系的機器,其寄存器是不同的, 無法統一. Stroustrup 說有些系統中c++是解析的, 那麼這些c++的解析實現採用的返回方式與編譯實現採用的返回方式可能也不同. 所以我們只能討論範圍限制在某種特定機器上的某個編譯器上.在這裏,我們約定是x86/gcc3 (linux)
當返回值能容納在一個寄存器中時, 通常都用一個寄存器返回.這是沒有問題的. 當返回值足夠小能容納在兩個寄存器中, 比如 edx:eax時, 通過這兩個寄存器返回. 當要求返回的對象比較大時, 比如 x = foo (), 而 sizeof (x) 比較大, 則調用者將 x 的地址通過棧傳遞給被調用函數 foo, foo 把返回值寫到 x 中. 有點象這樣:
將 x = foo ();
轉化爲 (void) foobar (&x);
再強調一次, 如何返回並不是c的一個組成部分. c標準是抽象的, 並不關心"實際如何返回". 實際如何返回, 是編譯器的事.
視情況的不同, 調用者在調用前, 也許要爲被調用者在堆棧中提供一些空間,供被調用者使用.

bss段(未手動初始化的數據)並不給該段的數據分配空間,只是記錄數據所需空間的大小。
data(已手動初始化的數據)段則爲數據分配空間,數據保存在目標文件中。

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