碼迷,mamicode.com
首頁 > 系統相關 > 詳細

linux 目標文件(*.o) bss,data,text,rodata,堆,棧 以及程序加載運行理解(轉)

時間:2021-07-21 17:43:05      閱讀:0      評論:0      收藏:0      [點我收藏+]

標簽:tar   etc   數據讀取   那是   第一個   模塊   data   全局變量   i386   

一、編譯及加載

C語言的編譯鏈接過程要把我們編寫的一個c程序(源代碼)轉換成可以在硬件上運行的程序(可執行代碼),需要進行編譯和鏈接。編譯就是把文本形式源代碼翻譯為機器語言形式的目標文件的過程。鏈接是把目標文件、操作系統的啟動代碼和用到的庫文件進行組織形成最終生成可加載、可執行代碼的過程

程序運行時會將編譯好的文件從外存中加載到內存中,而后進行運行

過程圖解如下: 

技術圖片

  1. 預處理器:將.c 文件轉化成 .i文件,使用的gcc命令是:gcc –E,對應于預處理命令cpp;
  2. 編譯器:將.c/.h文件轉換成.s文件,使用的gcc命令是:gcc –S,對應于編譯命令 cc –S;
  3. 匯編器:將.s 文件轉化成 .o文件,使用的gcc 命令是:gcc –c,對應于匯編命令是 as;
  4. 鏈接器:將.o文件轉化成可執行程序,使用的gcc 命令是: gcc,對應于鏈接命令是 ld;
  5. 加載器:將可執行程序加載到內存并進行執行,loader和ld-linux.so。

二、編譯過程

編譯過程又可以分成兩個階段:編譯和匯編。

2.1編譯

編譯是指編譯器讀取源程序(字符流),對之進行詞法和語法的分析,將高級語言指令轉換為功能等效的匯編代碼。

源文件的編譯過程包含兩個主要階段:

第一個階段是預處理階段,在正式的編譯階段之前進行。預處理階段將根據已放置在文件中的預處理指令來修改源文件的內容。

主要是以下幾方面的處理:

  1. 宏定義指令,如 #define a b 對于這種偽指令,預編譯所要做的是將程序中的所有a用b替換,但作為字符串常量的 a則不被替換。還有 #undef,則將取消對某個宏的定義,使以后該串的出現不再被替換。
  2. 條件編譯指令,如#ifdef,#ifndef,#else,#elif,#endif等。 這些偽指令的引入使得程序員可以通過定義不同的宏來決定編譯程序對哪些代碼進行處理。預編譯程序將根據有關的文件,將那些不必要的代碼過濾掉
  3. 頭文件包含指令,如#include "FileName"或者#include 等。 該指令將頭文件中的定義統統都加入到它所產生的輸出文件中,以供編譯程序對之進行處理。
  4. 特殊符號,預編譯程序可以識別一些特殊的符號。 例如在源程序中出現的LINE標識將被解釋為當前行號(十進制數),FILE則被解釋為當前被編譯的C源程序的名稱。預編譯程序對于在源程序中出現的這些串將用合適的值進行替換。

頭文件的目的主要是為了使某些定義可以供多個不同的C源程序使用,這涉及到頭文件的定位即搜索路徑問題。頭文件搜索規則如下:

  1. 所有header file的搜尋會從-I開始
  2. 然后找環境變量 C_INCLUDE_PATH,CPLUS_INCLUDE_PATH,OBJC_INCLUDE_PATH指定的路徑
  3. 再找默認目錄(/usr/include、/usr/local/include、/usr/lib/gcc-lib/i386-linux/2.95.2/include......)

 

第二個階段編譯、優化階段,編譯程序所要作得工作就是通過詞法分析和語法分析,在確認所有的指令都符合語法規則之后,將其翻譯成等價的中間代碼表示或匯編代碼。 

 

2.2匯編

匯編實際上指匯編器(as)把匯編語言代碼翻譯成目標機器指令的過程。目標文件中所存放的也就是與源程序等效的目標的機器語言代碼。目標文件由段組成。通常一個目標文件中至少有兩個段:

  • 代碼段:該段中所包含的主要是程序的指令。該段一般是可讀和可執行的,但一般卻不可寫。
  • 數據段:主要存放程序中要用到的各種全局變量或靜態的數據。一般數據段都是可讀,可寫,可執行的。

 

2.3目標文件(Executable and Linkable Format)

  1. 可重定位(Relocatable)文件:由編譯器和匯編器生成,可以與其他可重定位目標文件合并創建一個可執行或共享的目標文件;
  2. 共享(Shared)目標文件:一類特殊的可重定位目標文件,可以在鏈接(靜態共享庫)時加入目標文件或加載時或運行時(動態共享庫)被動態的加載到內存并執行;
  3. 可執行(Executable)文件:由鏈接器生成,可以直接通過加載器加載到內存中充當進程執行的文件。

技術圖片

 

2.4 靜態庫與動態庫

靜態庫(static library)就是將相關的目標模塊打包形成的單獨的文件。使用ar命令。

靜態庫的優點在于:

  • 程序員不需要顯式的指定所有需要鏈接的目標模塊,因為指定是一個耗時且容易出錯的過程;
  • 鏈接時,連接程序只從靜態庫中拷貝被程序引用的目標模塊,這樣就減小了可執行文件在磁盤和內存中的大小。

動態庫(dynamic library)是一種特殊的目標模塊,它可以在運行時被加載到任意的內存地址,或者是與任意的程序進行鏈接。

動態庫的優點在于:

  • 更新動態庫,無需重新鏈接;對于大系統,重新鏈接是一個非常耗時的過程;
  • 運行中可供多個程序使用,內存中只需要有一份,節省內存。

 

三、鏈接過程

鏈接器主要是將有關的目標文件彼此相連接生成可加載、可執行的目標文件。鏈接器的核心工作就是符號表解析和重定位。

3.1 鏈接的時機:

  1. 編譯時,就是源代碼被編譯成機器代碼時(靜態鏈接器負責);
  2. 加載時,也就是程序被加載到內存時(加載器負責);
  3. 運行時,由應用程序來實施(動態鏈接器負責)。

3.2 鏈接的作用(軟件復用):

  1. 使得分離編譯成為可能;
  2. 動態綁定(binding):使定義、實現、使用分離

3.3 靜態庫搜索路徑(由靜態鏈接器負責)

  1. gcc先從-L尋找;
  2. 再找環境變量LIBRARY_PATH指定的搜索路徑;
  3. 再找內定目錄 /lib /usr/lib /usr/local/lib 這是當初compile gcc時寫在程序內的。

3.4 動態庫搜索路徑(由動態鏈接器負責)

  1. 編譯目標代碼時指定的動態庫搜索路徑-L;
  2. 環境變量LD_LIBRARY_PATH指定的動態庫搜索路徑;
  3. 配置文件/etc/ld.so.conf中指定的動態庫搜索路徑;
  4. 默認的動態庫搜索路徑/lib /usr/lib/ /usr/local/lib

3.5 靜態鏈接(編譯時)

鏈接器將函數的代碼從其所在地(目標文件或靜態鏈接庫中)拷貝到最終的可執行程序中。這樣該程序在被執行時這些代碼將被裝入到該進程的虛擬地址空間中。靜態鏈接庫實際上是一個目標文件的集合,其中的每個文件含有庫中的一個或者一組相關函數的代碼。

為創建可執行文件,鏈接器必須要完成的主要任務:

  1. 符號解析:把目標文件中符號的定義和引用聯系起來;
  2. 重定位:把符號定義和內存地址對應起來,然后修改所有對符號的引用。

關于符號表和符號解析以及重定位的分析后續學習。

3.6 動態鏈接(加載、運行時)

在此種方式下,函數的定義在動態鏈接庫或共享對象的目標文件中。在編譯的鏈接階段,動態鏈接庫只提供符號表和其他少量信息用于保證所有符號引用都有定義,保證編譯順利通過。動態鏈接器(ld-linux.so)鏈接程序在運行過程中根據記錄的共享對象的符號定義來動態加載共享庫,然后完成重定位。在此可執行文件被執行時,動態鏈接庫的全部內容將被映射到運行時相應進程的虛地址空間。動態鏈接程序將根據可執行程序中記錄的信息找到相應的函數代碼。  

 

四、加載過程

加載器把可執行文件從外存加載到內存并進行執行。 Linux中進程運行時的內存映像如下:

技術圖片

 

 加載過程如下:

加載器首先創建如上圖所示的內存映像,然后根據段頭部表,把目標文件拷貝到內存的數據和代碼段中。然后,加載器跳轉到程序入口點(即符號_start 的地址),執行啟動代碼(startup code),啟動代碼的調用順序如所示:

技術圖片

 

五、處理目標的常用工具

UNIX系統提供了一系列工具幫助理解和處理目標文件。GNUbinutils 包也提供了很多幫助。這些工具包括:

  • AR :創建靜態庫,插入、刪除、列出和提取成員;
  • STRINGS :列出目標文件中所有可以打印的字符串;
  • STRIP :從目標文件中刪除符號表信息;
  • NM :列出目標文件符號表中定義的符號;
  • SIZE :列出目標文件中節的名字和大??;
  • READELF :顯示一個目標文件的完整結構,包括ELF 頭中編碼的所有信息。
  • OBJDUMP :顯示目標文件的所有信息,最有用的功能是反匯編.text節中的二進制指令。
  • LDD :列出可執行文件在運行時需要的共享庫。

六、編譯文件詳解

一個簡單的程序被編譯成目標文件后的結構如下:

 

技術圖片

 

 

從圖可以看出,已初始化的全局變量和局部靜態變量保存在 .data段中,未初始化的全局變量和未初始化的局部靜態變量保存在 .bss段中。

 

目標文件各個段在文件中的布局如下:

技術圖片

 

 

各個段介紹:

init段:

程序初始化入口代碼,在main() 之前運行。

bss段:

BSS段屬于靜態內存分配。通常是指用來存放程序中未初始化的全局變量和未初始化的局部靜態變量。未初始化的全局變量和未初始化的局部靜態變量默認值是0,本來這些變量也可以放到data段的,但是因為他們都是0,所以為他們在data段分配空間并且存放數據0是沒有必要的。

程序在運行時,才會給BSS段里面的變量分配內存空間。

在目標文件(*.o)和可執行文件中,BSS段只是為未初始化的全局變量和未初始化的局部靜態變量預留位置而已,它并沒有內容,所以它不占據空間。

section table中保存了BSS段(未初始化的全局變量和未初始化的局部靜態變量)內存空間大小總和。 (objdump -h *.o 命令可以看到)

data段:

數據段(datasegment)通常是指用來存放程序中已初始化的全局變量和已初始化的靜態變量的一塊內存區域。數據段屬于靜態內存分配。

text段:

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

rodata段:

存放的是只讀數據,比如字符串常量,全局const變量 和 #define定義的常量。例如: char*p="123456", "123456"就存放在rodata段中。

strtab段:

存儲的是變量名,函數名等。例如: char* szPath="/root",void func() 變量名szPath 和函數名func 存儲在strtab段里。

shstrtab段:

bss,text,data等段名也存儲在這里。

rel.text段:

針對 text段的重定位表,還有 rel.data(針對data段的重定位表)

heap堆:

堆是用于存放進程運行中被動態分配的內存段,它的大小并不固定,可動態擴張或縮減。當進程調用malloc等函數分配內存時,新分配的內存就被動態添加到堆上(堆被擴張);當利用free等函數釋放內存時,被釋放的內存從堆中被剔除(堆被縮減)

stack棧:

是用戶存放程序臨時創建的局部變量,也就是說我們函數括弧“{}”中定義的變量(但不包括static聲明的變量,static意味著在數據段中存放變量)。除此以外,在函數被調用時,其參數也會被壓入發起調用的進程棧中,并且待到調用結束后,函數的返回值也會被存放回棧中。由于棧的先進先出特點,所以棧特別方便用來保存/恢復調用現場。從這個意義上講,我們可以把堆??闯梢粋€寄存、交換臨時數據的內存區。


驗證BSS內存空間

程序1:

int ar[30000];
void main()
{

......

}

 

程序2:

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

......

}

結論是:程序2編譯之后所得的.exe文件比程序1的要大得多。 為什么?

區別很明顯,一個位于.bss段,而另一個位于.data段,兩者的區別在于:

l 全局的未初始化變量存在于.bss段中,具體體現為一個占位符;全局的已初始化變量存于.data段中;

l 而函數內的自動變量都在棧上分配空間。

l .bss是不占用.exe文件空間的,其內容由操作系統初始化(清零);

l 而.data卻需要占用,其內容由程序初始化,因此造成了上述情況。

 

注意:

1. bss段(未手動初始化的數據)并不給該段的數據分配空間. 程序運行后,系統分配內存空間并由系統初始化,默認內存空間的值都為0. section table中保存了BSS段(未初始化的全局變量和未初始化的局部靜態變量)內存空間大小總和,所以程序運行后,系統知道該分配多少內存給BSS段。

2. data(已手動初始化的數據)段則為數據分配空間,數據保存在目標文件中。

 

這里有個疑問: data段是變量的內存空間,那是如何區分哪幾個字節是變量a的? 哪幾個字節是變量b的? 因為變量a,b的內存空間是在一起的,如果你不告訴他們的類型,我們的確是不知道變量a有幾個字節,變量b有幾個字節。

那么哪里保存了 變量a,b的類型了? 查資料發現,text代碼段中調用a的匯編代碼,是會告訴我們變量a的類型的,這樣我們就知道讀取哪幾個字節的值了。 程序運行起來后,BSS段中變量內存數據讀取原理類似。

 

轉自:

https://blog.csdn.net/weixin_41042404/article/details/81239416
https://blog.csdn.net/sunny04/article/details/40627311

linux 目標文件(*.o) bss,data,text,rodata,堆,棧 以及程序加載運行理解(轉)

標簽:tar   etc   數據讀取   那是   第一個   模塊   data   全局變量   i386   

原文地址:https://www.cnblogs.com/zl1991/p/15039949.html

(0)
(0)
   
舉報
評論 一句話評論(0
登錄后才能評論!
? 2014 mamicode.com 版權所有  聯系我們:gaon5@hotmail.com
迷上了代碼!
4399在线看MV_久久99精品久久久久久久久久_成人又黄又爽又刺激视频_能收黄台的app不收费