天天看點

【圖解】Linux下C程式程序位址空間布局 .

我們在學習C程式開發時經常會遇到一些概念:代碼段、資料段、BSS段(Block Started by Symbol) 、堆(heap)和棧(stack)。先看一張教材上的示意圖(來源,《UNIX環境進階程式設計》一書),顯示了程序位址空間中典型的存儲區域配置設定情況。

【圖解】Linux下C程式程式位址空間布局 .

從圖中可以看出:

從低位址到高位址分别為:代碼段、(初始化)資料段、(未初始化)資料段(BSS)、堆、棧、指令行參數和環境變量

堆向高記憶體位址生長

棧向低記憶體位址生長

還經常看到下面這個圖(來源,不詳):

【圖解】Linux下C程式程式位址空間布局 .

先看一段程式。

下面是輸出結果。

【圖解】Linux下C程式程式位址空間布局 .

先仔細分析一下上面的輸出結果,看看能得出什麼結論。貌似很難分析出來什麼結果。好了我們繼續往下看吧。

接下來,通過檢視proc檔案系統下的檔案,看一下這個程序的真實記憶體配置設定情況。(我們需要在程式結束前加一個死循環,不讓程序結束,以便我們進一步分析)。

      在return 0前,增加 while(1); 語句

重新編譯後,運作程式,程式将進入死循環。

【圖解】Linux下C程式程式位址空間布局 .

使用ps指令檢視一下程序的pid

  #ps -aux | grep a.out

【圖解】Linux下C程式程式位址空間布局 .

檢視/proc/2699/maps檔案,這個檔案顯示了程序在記憶體空間中各個區域的配置設定情況。

  #cat  /proc/2699/maps

【圖解】Linux下C程式程式位址空間布局 .

上面紅顔色标出的幾個區間是我們感興趣的區間:

08048000-08049000  r-xp  貌似是代碼段

08049000-0804a000 r--p   暫時不清楚,看不出來

0804a000-0804b000 rw-p  貌似為資料段

08a7e000-08a9f000  rw-p  堆

bff73000-bff88000     rw-p   棧   

我們把這些資料與剛才的程式運作結果進行比較,看看有什麼結論。

                &global_init_a=0x804a018       全局初始化:資料段              global_init_a=1

            &global_uninit_a=0x804a04c      全局未初始化:資料段          global_uninit_a=0

     &static_global_init_a=0x804a01c      全局靜态初始化:資料段      static_global_init_a=1

&static_global_uninit_a=0x804a038      全局靜态未初始化:資料段     static_global_uninit_a=0

             &const_global_a=0x80487c0     全局隻讀變量: 代碼段        const_global_a=1

                 &global_init_b=0x804a020       全局初始化:資料段      global_init_b=1

            &global_uninit_b=0x804a048        全局未初始化:資料段      global_uninit_b=0

     &static_global_init_b=0x804a024        全局靜态初始化:資料段    static_global_init_b=1

&static_global_uninit_b=0x804a03c        全局靜态未初始化:資料段   static_global_uninit_b=0

            &const_global_b=0x80487c4        全局隻讀變量: 代碼段             const_global_b=1

                 &local_init_a=0xbff8600c          局部初始化:棧                     local_init_a=1

             &local_uninit_a=0xbff86008         局部未初始化:棧                 local_uninit_a=134514459

     &static_local_init_a=0x804a028         局部靜态初始化:資料段      static_local_init_a=1

 &static_local_uninit_a=0x804a040        局部靜态未初始化:資料段     static_local_uninit_a=0

             &const_local_a=0xbff86004        局部隻讀變量:棧     const_local_a=1

                  &local_init_b=0xbff86000        局部初始化:棧          local_init_b=1

                &local_uninit_b=0xbff85ffc         局部未初始化:棧        local_uninit_b=-1074241512

      &static_local_init_b=0x804a02c        局部靜态初始化:資料段      static_local_init_b=1

 &static_local_uninit_b=0x804a044        局部靜态未初始化:資料段      static_local_uninit_b=0

                &const_local_b=0xbff85ff8        局部隻讀變量:棧        const_local_b=1

                           p_chars=0x80487c8        字元串常量:代碼段          p_chars=abcdef

                    malloc_p_a=0x8a7e008        malloc動态配置設定:堆        *malloc_p_a=0

通過以上分析我們暫時可以得到的結論如下,在程序的位址空間中:

資料段中存放:全局變量(初始化以及未初始化的)、靜态變量(全局的和局部的、初始化的以及未初始化的)

代碼段中存放:全局隻讀變量(const)、字元串常量

堆中存放:動态配置設定的區域

棧中存放:局部變量(初始化以及未初始化的,但不包含靜态變量)、局部隻讀變量(const)

這裡我們沒有發現BSS段,但是我們将未初始化的資料按照位址進行排序看一下,可以發現一個規律。

    &static_global_init_a=0x804a01c      全局靜态初始化:資料段      static_global_init_a=1

                &global_init_b=0x804a020       全局初始化:資料段      global_init_b=1

    &static_global_init_b=0x804a024        全局靜态初始化:資料段    static_global_init_b=1

       &static_local_init_a=0x804a028         局部靜态初始化:資料段      static_local_init_a=1

       &static_local_init_b=0x804a02c        局部靜态初始化:資料段      static_local_init_b=1

  &static_local_uninit_a=0x804a040        局部靜态未初始化:資料段     static_local_uninit_a=0

  &static_local_uninit_b=0x804a044        局部靜态未初始化:資料段      static_local_uninit_b=0

           &global_uninit_b=0x804a048        全局未初始化:資料段      global_uninit_b=0

    這裡可以發現,初始化的和未初始化的資料好像是分開存放的,是以我們可以猜測BSS段是存在的,隻不過資料段是分為初始化和未初始化(即BSS段)的兩部分,他們在加載到程序位址空間時是合并為資料段了,在程序位址空間中沒有單獨分為一個區域。

    還有一個問題,靜态資料與非靜态資料是否是分開存放的呢?請讀者自行分析一下。

 接下來我們從程式的角度看一下,這寫存儲區域是如何配置設定的。首先我們先介紹一下ELF檔案格式。

ELF(Executable and Linkable Format )檔案格式是一個開放标準,各種UNIX系統的可執行檔案都采用ELF格式,它有三種不同的類型:

–可重定位的目标檔案(Relocatable,或者Object File)

–可執行檔案(Executable)

–共享庫(Shared Object,或者Shared Library)

下圖為ELF檔案的結構示意圖(來源,不詳):

【圖解】Linux下C程式程式位址空間布局 .

一個程式編譯生成目标代碼檔案(ELF檔案)的過程如下,此圖引自《程式員的自我修養》一書的一個圖:

【圖解】Linux下C程式程式位址空間布局 .

可以通過readelf指令檢視EFL檔案的相關資訊,例如 readelf  -a  a.out  ,我們隻關心各個段的配置設定情況,是以我們使用以下指令:

    # readelf -S a.out

【圖解】Linux下C程式程式位址空間布局 .

 将這裡的記憶體布局與之前看到的程式的運作結果進行分析:

            &global_uninit_a=0x804a04c      全局未初始化:BSS段          global_uninit_a=0

&static_global_uninit_a=0x804a038      全局靜态未初始化:BSS段     static_global_uninit_a=0

             &const_global_a=0x80487c0     全局隻讀變量: 隻讀資料段        const_global_a=1

            &global_uninit_b=0x804a048        全局未初始化:BSS段      global_uninit_b=0

&static_global_uninit_b=0x804a03c        全局靜态未初始化:BSS段   static_global_uninit_b=0

            &const_global_b=0x80487c4        全局隻讀變量: 隻讀資料段             const_global_b=1

 &static_local_uninit_a=0x804a040        局部靜态未初始化:BSS段     static_local_uninit_a=0

      &static_local_init_b=0x804a02c        局部靜态初始化:資料段      static_local_init_b=1

 &static_local_uninit_b=0x804a044        局部靜态未初始化:BSS段      static_local_uninit_b=0

                           p_chars=0x80487c8        字元串常量:隻讀資料段          p_chars=abcdef

ELF 檔案一般包含以下幾個段 :

.text section:主要是編譯後的源碼指令,是隻讀字段。

.data section :初始化後的非const的全局變量、局部static變量。

.bss:未初始化後的非const全局變量、局部static變量。

.rodata字段  是存放隻讀資料 

分析到這以後,我們在和之前分析的結果對比一下,會發現确實存在BSS段,位址為0804a030 ,大小為0x20,之前我們的程式中未初始化的的确存放在這個位址區間中了,隻不過執行exec系統調用時,将這部分的資料初始化為0後,放到了程序位址空間的資料段中了,在程序位址空間中就沒有必要存在BSS段了,是以都稱做資料段。同理,.rodata字段也是與text段放在一起了。

在ELF檔案中,找不到局部非靜态變量和動态配置設定的内容。

以上有很多地方的分析,我在網上基本找不到很明确的結論,很多教材上也沒有描述,隻是我通過程式分析得出的結論,如有不妥之處,請指出,歡迎交流。

繼續閱讀