後知後覺-ELF

原文鏈接:https://blog.csdn.net/love_gaohz/article/details/41310597

在計算機科學中,是一種用於二進制文件、可執行文件、目標代碼、共享庫和核心轉儲的格式文件。

是UNIX系統實驗室爲應用程序二進制接口(ABI)而開發和發佈的,也是Linux的主要可執行文件格式。

1999年,被86open項目選爲x86架構上的類Unix操作系統的二進制文件標準格式,用來取代COFF。因其可擴展性與靈活性,也可應用在其它處理器、計算機系統架構的操作系統上。 [1] 

ELF文件組成部分:

  • ELF頭
  • 程序頭表
  • 節頭表

實際上,一個文件中不一定包含全部內容,而且它們的位置也未必如同所示這樣安排,只有ELF頭的位置是固定的,奇遇各部分的位置、大小等信息由ELF頭中的各項值來決定。


ELF文件的三種類型:

  • .o可重定位文件
  • 可執行文件
  • 共享庫

三種格式基本上從結構你是一樣的,只是具體到每一個結構不同。

下面我們就從整體上看看這3種格式從文件內容上存儲的方式,spec上有張圖是比較經典的:如上圖:
 其實從文件存儲的格式來說,上面的兩種view實際上是一樣的,Segment實際上就是由section組成的,將相應的一些section映射到一起就叫segment了,就是說segment是由0個或多個section組成的,實際上本質都是section。在這裏我們首先來仔細瞭解一下section和segment的概念:section就是相同或者相似信息的集合,比如我們比較熟悉的.text .data  .bss section,.text是可執行指令的集合,.data是初始化後數據的集合,.bss是未初始化數據的集合。實際上我們也可以將一個程序的所有內容都放在一起,就像dos一樣,但是將可執行程序分成多個section是很有好處的,比如說我們可以將.text section放在memory的只讀空間內,將可變的.data section放在memory的可寫空間內。
 

從可執行文件的角度來講,如果一個數據未被初始化那就不需要爲其分配空間,所以.data和.bss一個重要的區別就是.bss並不佔用可執行文件的大小,它只是記載需要多少空間來存儲這些未初始化數據,而不分配實際的空間。

可以通過命令 $ readelf -l a.out 查看文件的格式和組成。

 

二。

站在彙編語言的角度,一個程序分爲:

數據段--DS

堆棧段--SS

代碼段--CS

擴展段--ES

站在高級語言的角度,根據APUE,一個程序分爲如下段:

text

data(initialized)

bss

stack

heap

1.一般情況下,一個可執行二進制程序(更確切的說,在Linux操作系統下爲一個進程單元,在UC/OSII中被稱爲任務)在存儲(沒有調入到內存運行)時擁有3個部分,分別是代碼段(text)、數據段(data)和BSS段。這3個部分一起組成了該可執行程序的文件。

可執行的二進制程序=代碼段(text)+數據段(data)+BSS段

2.當程序被加載到內存單元時,則需要另外兩個域:堆域和棧域。圖示爲可執行代碼存儲態和運行態的結構對照圖。

一個正在運行的C程序佔用的內存單元爲代碼段、初始化數據段、未初始化數據段(BSS)、堆、棧。

3.在將應用程序加載到內存空間執行時,操作系統負責代碼段、數據段和BSS段的加載,並將在內存中爲這些段分配空間。棧亦由操作系統分配和管理而不需要程序員顯式地管理;堆段由程序員自己管理,即顯式地申請和釋放空間。

4.動態分配與靜態分配,二者最大的區別在於:1.直到Run-Time的時候,執行動態分配,而在Compile-Time的時候,就已經決定好了分配多少Text+Data+BSS+Stack.2.通過malloc()動態分配內存的時候,需要程序員手動調用free()釋放內存,否則容易導致內存泄露,而靜態分配的內存則在進程執行結束後系統釋放(Text,Data),但Stac段中的數據很短暫,函數退出立即被銷燬。

從可執行文件a.out的角度來講,如果一個數據未被初始化那就不需要爲其分配空間,所以.data和.bss一個重要的區別就是.bss並不佔用可執行文件的大小,它只是記載需要多少空間來存儲這些未初始化數據,而不分配實際的控件。

 

代碼段 --text(code segment/text segment)
text段在內存中被映射爲只讀,但.data和.bss是可寫的。
text段是程序代碼段,在AT91庫中是表示程序段的大小,它是由編譯器在編譯連接時自動計算的,當你在鏈接定位文件中將該符號放置在代碼段後,那麼該符號表示的值就是代碼段大小,編譯連接時,該符號所代表的值會自動代入到源程序中。

數據段 -- data
data包含靜態初始化的數據,所以有初值的全局變量和static變量在data區。段的起始位置也是由連接定位文件所確定,大小在編譯連接時自動分配,它和你的程序大小沒有關係,但和程序使用到的全局變量,常量數量相關。數據段屬於靜態內存分配。 

bss段--bss
bss是英文Block Started by Symbol的簡稱,通常是指用來存放程序中未初始化的全局變量的一塊內存區域,在程序載入時由內核清0。BSS段屬於靜態內存分配。它的初始值也是由用戶自己定義的連接定位文件所確定,用戶應該將它定義在可讀寫的RAM區內,源程序中使用malloc分配的內存就是這一塊,它不是根據data大小確定,主要由程序中同時分配內存最大值所確定,不過如果超出了範圍,也就是分配失敗,可以等空間釋放之後再分配。BSS段屬於靜態內存分配。

stack:
棧(stack)保存函數的局部變量(但不包括static聲明的變量, static 意味着 在數據段中 存放變量),參數以及返回值。是一種“後進先出”(Last In First Out,LIFO)的數據結構,這意味着最後放到棧上的數據,將會是第一個從棧上移走的數據。對於哪些暫時存貯的信息,和不需要長時間保存的信息來說,LIFO這種數據結構非常理想。在調用函數或過程後,系統通常會清除棧上保存的局部變量、函數調用信息及其它的信息。棧另外一個重要的特徵是,它的地址空間“向下減少”,即當棧上保存的數據越多,棧的地址就越低。棧(stack)的頂部在可讀寫的RAM區的最後。

heap:
堆(heap)保存函數內部動態分配內存,是另外一種用來保存程序信息的數據結構,更準確的說是保存程序的動態變量。堆是“先進先出”(First In first Out,FIFO)數據結構。它只允許在堆的一端插入數據,在另一端移走數據。堆的地址空間“向上增加”,即當堆上保存的數據越多,堆的地址就越高。

一、C語言可執行代碼結構

 名稱 內容
代碼段  可執行代碼、字符串常量
數據段  已初始化全局變量、已初始化全局靜態變量、局部靜態變量、常量數據
BSS段  未初始化全局變量,未初始化全局靜態變量
 局部變量、函數參數
 動態內存分配

 

 

 


        一般情況下,一個可執行二進制程序(更確切的說,在Linux操作系統下爲一個進程單元,在UC/OSII中被稱爲任務)在存儲(沒有調入到內存運行)時擁有3個部分,分別是代碼段(text)、數據段(data)和BSS段。這3個部分一起組成了該可執行程序的文件。

        (1)代碼段(text segment):存放CPU執行的機器指令。通常代碼段是可共享的,這使得需要頻繁被執行的程序只需要在內存中擁有一份拷貝即可。代碼段也通常是隻讀的,這樣可以防止其他程序意外地修改其指令。另外,代碼段還規劃了局部數據所申請的內存空間信息。

        代碼段(code segment/text segment)通常是指用來存放程序執行代碼的一塊內存區域。這部分區域的大小在程序運行前就已經確定,並且內存區域通常屬於只讀, 某些架構也允許代碼段爲可寫,即允許修改程序。在代碼段中,也有可能包含一些只讀的常數變量,例如字符串常量等。

        (2)數據段(data segment):或稱全局初始化數據段/靜態數據段(initialized data segment/data segment)。該段包含了在程序中明確被初始化的全局變量、靜態變量(包括全局靜態變量和局部靜態變量)和常量數據。

        (3)未初始化數據段:亦稱BSS(Block Started by Symbol)。該段存入的是全局未初始化變量、靜態未初始化變量。

BSS端的特點是:可讀寫的,在程序執行之前BSS段會自動清0。所以,未初始的全局變量在程序執行之前已經成0了。

        而當程序被加載到內存單元時,則需要另外兩個域:堆域和棧域。圖1-1所示爲可執行代碼存儲態和運行態的結構對照圖。一個正在運行的C程序佔用的內存區域分爲代碼段、初始化數據段、未初始化數據段(BSS)、堆、棧5個部分。

 

C語言中的5個段作用 - myswirl - 漩渦的窩

圖1-1  C語言可執行代碼結構

      (4)棧段(stack):存放函數的參數值、局部變量的值,以及在進行任務切換時存放當前任務的上下文內容。

      (5)堆段(heap):用於動態內存分配,即使用malloc/free系列函數來管理的內存空間。

     在將應用程序加載到內存空間執行時,操作系統負責代碼段、數據段和BSS段的加載,並將在內存中爲這些段分配空間。棧段亦由操作系統分配和管理,而不需要程序員顯示地管理;堆段由程序員自己管理,即顯示地申請和釋放空間。

    另外,可執行程序在運行時具有相應的程序屬性。在有操作系統支持時,這些屬性頁由操作系統管理和維護。

二、例子演示,代碼段、數據段和BSS段存儲變量類型

#include <stdio.h>
const int    g_A       = 10;            //代碼段
int            g_B       = 20;            //數據段
static int    g_C       = 30;            //數據段
static int    g_D;                    //BSS段
int            g_E;                    //BSS段
char        *p1;                    //BSS段

void main( )
{
    int            local_A;            //棧
    static int    local_C = 0;        //數據段
    static int    local_D;            //數據段
    
    char        *p3 = "123456";     //123456在代碼段,p3在棧上

    p1 = (char *)malloc( 10 );      //堆,分配得來得10字節的區域在堆區
    strcpy( p1, "123456" );         //123456{post.content}放在常量區,編譯器可能會將它與p3所指向 的"123456"優化成一塊
    printf("\n");
    printf( "代碼段,全局初始化變量, 只讀const,  g_A,     addr:0x%08x\n", &g_A);
    printf("\n");
    printf( "數據段,全局變量,       初始化      g_B,     addr:0x%08x\n", &g_B);
    printf( "數據段,靜態全局變量,   初始化,     g_C,     addr:0x%08x\n", &g_C);
    printf("\n");
    printf( "BSS段, 全局變量,       未初始化    g_E,     addr:0x%08x\n", &g_E, g_E );    
    printf( "BSS段, 靜態全局變量,   未初始化,   g_D,     addr:0x%08x\n", &g_D );
    printf( "BSS段, 靜態局部變量,   初始化,     local_C, addr:0x%08x\n", &local_C);
    printf( "BSS段, 靜態局部變量,   未初始化,   local_D, addr:0x%08x\n", &local_D);
    printf("\n");
    printf( "棧,    局部變量,                   local_A, addr:0x%08x\n", &local_A );
    printf("\n");
    printf( "堆,    malloc分配內存,             p1,      addr:0x%08x\n", p1 );
}

}

C程序內存區域分配(5個段作用) - myswirl - 漩渦的窩
注意:
編譯時需要-g選項,這樣纔可以看elf信息;
readelf -a MemoryAssign > 1.txt
可執行程序MemoryAssign的信息導出到文本文件1.txt中,查看1.txt 

C程序內存區域分配(5個段作用) - myswirl - 漩渦的窩

 

C程序內存區域分配(5個段作用) - myswirl - 漩渦的窩

 

問題1:可執行文件大小由什麼決定?

可執行文件在存儲時分爲代碼段、數據段和BSS段三個部分。

【例一】
程序1:
int ar[30000];
void main()
{
    ......

程序2:
int ar[300000] =  {1, 2, 3, 4, 5, 6 };
void main()
{
    ......

發現程序2編譯之後所得的.exe文件比程序1的要大得多。當下甚爲不解,於是手工編譯了一下,並使用了/FAs編譯選項來查看了一下其各自的.asm,發現在程序1.asm中ar的定義如下:
_BSS SEGMENT
     ?ar@@3PAHA DD 0493e0H DUP (?)    ; ar
_BSS ENDS 
而在程序2.asm中,ar被定義爲:
_DATA SEGMENT
     ?ar@@3PAHA DD 01H     ; ar
                DD 02H
                DD 03H
                ORG $+1199988
_DATA ENDS 
區別很明顯,一個位於.bss段,而另一個位於.data段,兩者的區別在於:全局的未初始化變量存在於.bss段中,具體體現爲一個佔位符;全局的已初始化變量存於.data段中;而函數內的自動變量都在棧上分配空間。

.bss是不佔用.exe文件空間的,其內容由操作系統初始化(清零);而.data卻需要佔用,其內容由程序初始化,因此造成了上述情況。

C程序內存區域分配(5個段作用) - myswirl - 漩渦的窩
可以看到可執行文件“2”大小爲122K,可執行文件“1”大小爲4.8K,用size命令查看二進制可執行文件結構情況。

 

參考:https://www.cnblogs.com/bigbigtree/archive/2012/11/23/2784137.html

https://www.cnblogs.com/xmphoenix/archive/2011/10/23/2221879.html

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