欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C語(yǔ)言目標(biāo)文件的詳細(xì)講解

 更新時(shí)間:2023年01月17日 14:35:09   作者:叫我小秦就好了  
最近正在閱讀關(guān)于C語(yǔ)言的庫(kù),但是我還沒(méi)有find關(guān)于目標(biāo)文件的解釋,這篇文章主要給大家介紹了C語(yǔ)言目標(biāo)文件的詳細(xì)講解,文中介紹的非常詳細(xì),需要的朋友可以參考下

前言

一個(gè) C 語(yǔ)言程序經(jīng)編譯器和匯編器生成可重定位目標(biāo)文件,再經(jīng)鏈接器生成可執(zhí)行目標(biāo)文件。那么目標(biāo)文件中存放的是什么?我們的源代碼在經(jīng)編譯以后又是怎么存儲(chǔ)的?

文章為 《深入理解計(jì)算機(jī)系統(tǒng)》的讀書(shū)筆記,更為詳細(xì)的內(nèi)容可以閱讀原書(shū)。

目標(biāo)文件分類

目標(biāo)文件有三種形式:

  • 可重定位目標(biāo)文件
    包含二進(jìn)制代碼和數(shù)據(jù),其形式可以在編譯時(shí)與其他可重定位目標(biāo)文件合并,創(chuàng)建一個(gè)可執(zhí)行目標(biāo)文件
  • 可執(zhí)行目標(biāo)文件
    包含二進(jìn)制代碼和數(shù)據(jù),其形式可以被直接復(fù)制到內(nèi)存并執(zhí)行
  • 共享目標(biāo)文件
    一種特殊可重定位目標(biāo)文件,可以在加載或運(yùn)行時(shí)被動(dòng)態(tài)地加載進(jìn)內(nèi)存并鏈接

可重定位目標(biāo)文件

下圖為一個(gè)典型的 ELF 可重定位目標(biāo)文件的格式。

ELF object file

ELF 頭以一個(gè) 16 字節(jié)的序列開(kāi)始,這個(gè)序列包含了:生成該文件的系統(tǒng)的字的大小和字節(jié)順序、目標(biāo)文件的類型、機(jī)器類型、節(jié)頭部表(也稱段表)的文件偏移,以及節(jié)頭部表中條目的大小和數(shù)量等。

節(jié)頭部表是由描述文件中各個(gè)節(jié)的條目(entry)組成的數(shù)組。節(jié)頭部表描述了文件中各個(gè)節(jié)在文件中的偏移位置及節(jié)的屬性等,從節(jié)頭部表里面可以得到每個(gè)節(jié)的所有信息。

  • .text:已編譯程序的機(jī)器代碼
  • .rodata:只讀數(shù)據(jù)
    • 比如 printf 語(yǔ)句中的格式串(%d\n)
  • .data:已初始化的全局和靜態(tài) C 變量
    • 局部 C 變量在運(yùn)行時(shí)被保存在棧中,既不在 .data 節(jié)中,也不在 .bss 節(jié)中
  • .bss:未初始化的全局和靜態(tài) C 變量,以及所有被初始化為 0 的全局或靜態(tài)變量
    • 目標(biāo)文件格式區(qū)分已初始化和未初始化變量是為了空間效率:
      • 未初始化變量不需要占據(jù)任何實(shí)際的磁盤空間,因?yàn)闆](méi)有初始化,值沒(méi)有意義,也就不必表示每個(gè)值
      • .bss 段只是為未初始化全部變量和局部靜態(tài)變量預(yù)留位置,它并沒(méi)有內(nèi)容,也不占據(jù)實(shí)際的空間,僅僅是個(gè)占位符
      • .bss 段的大小存放在節(jié)頭部表中
        • 可以使用 readelf -S test 來(lái)查看 test 可執(zhí)行程序節(jié)頭部表
      • 運(yùn)行時(shí),在內(nèi)存中分配這些變量,并初始化為 0
  • .symtab:一個(gè)符號(hào)表,它存放在程序中定義和引用的函數(shù)和全局變量的信息
    • 包含局部靜態(tài)變量
    • 不包含局部非靜態(tài)變量,這些符號(hào)在運(yùn)行時(shí)在棧中被管理,鏈接器不關(guān)心這些
  • .rel.text:一個(gè) .text 節(jié)中位置的列表,當(dāng)鏈接器把這個(gè)目標(biāo)文件和其他文件組合時(shí),需要修改這些位置
  • .rel.data:被模塊引用或定義的所有全局變量的重定位信息
  • .debug:一個(gè)調(diào)試符號(hào)表,其條目是程序中定義的局部變量和類型定義,程序中定義和引用的全局變量,以及原始的 C 源文件
    • 只有在編譯時(shí)加入 -g 選項(xiàng)才會(huì)得到這張表
  • .line:原始 C 源程序中的行號(hào)和 .text 節(jié)中機(jī)器指令之間的映射
    • 只有在編譯時(shí)加入 -g 選項(xiàng)才會(huì)得到這張表
  • .strtab:一個(gè)字符串表,其中包含 .symtab 和 .debug 節(jié)中的符號(hào)表,以及節(jié)頭部中的節(jié)名字
    • 字符串表就是以 NULL 結(jié)尾的字符串序列

分段的優(yōu)點(diǎn)

為什么要這么麻煩,把程序的指令和數(shù)據(jù)分開(kāi)存放?

  • 程序被裝載進(jìn)內(nèi)存后,數(shù)據(jù)和指令分別被映射到兩個(gè)虛擬區(qū)域
    • 由于數(shù)據(jù)是可讀寫的,指令是只讀的,權(quán)限可以分別設(shè)置成可讀寫和只讀
    • 可以防止程序指令被改寫
  • 對(duì)現(xiàn)代 CPU 而言緩存是極其重要的
    • 數(shù)據(jù)區(qū)和指令區(qū)分離有利于提高程序的局部性,從而提高緩存命中率
  • 啟動(dòng)多個(gè)相同進(jìn)程時(shí)
    • 可以共享一份指令,節(jié)省內(nèi)存

符號(hào)和符號(hào)表

每個(gè)可重定位目標(biāo)模塊 m 都有一個(gè)符號(hào)表,它包含 m 定義和引用的符號(hào)的信息。在鏈接器的上下文中,有三種不同的符號(hào):

  • 由模塊 m 定義并能被其他模塊引用的全局符號(hào)
    • 全局鏈接器符號(hào)對(duì)應(yīng)于非靜態(tài)的 C 函數(shù)和全局變量
  • 由其他模塊定義并被模塊 m 引用的全局符號(hào)
    • 這些符號(hào)稱為外部符號(hào),對(duì)應(yīng)于在其他模塊中定義的非靜態(tài) C 函數(shù)和全局變量
  • 只被模塊 m 定義和引用的符號(hào)
    • 它們對(duì)應(yīng)于帶 static 屬性的 C 函數(shù)和全局變量,這些符號(hào)在模塊 m 中任何位置都可見(jiàn),但是不能被其他模塊引用

符號(hào)表是由匯編器用編譯器輸出到匯編語(yǔ)言 .s 文件中的符號(hào)構(gòu)造的。.symtab 節(jié)中包含 ELF 符號(hào)表,這張符號(hào)表包含一個(gè)條目的數(shù)組。下面是 ELF 符號(hào)表?xiàng)l目格式:

typedef struct {
    int	  name;		// String table offset
    char  type:4,	// Function or data (4 bits)
    	  binding:4;// Local or global (4 bits)
    char  reserved;	// Unused
    short section;	// Section header index
    long  value;	// Section offset or absolute address
    long  size;		// Object size in bytes
} Elf64_Symbol;

name 是字符串表中的字節(jié)偏移,指向符號(hào)的字符串名字。value 是符號(hào)的位置。對(duì)于可重定位目標(biāo)文件,value 是距定義目標(biāo)的起始位置的偏移。對(duì)于可執(zhí)行目標(biāo)文件,該值是一個(gè)絕對(duì)運(yùn)行時(shí)地址。size 是目標(biāo)的大小(以字節(jié)為單位)。

每個(gè)符號(hào)都被分配到目標(biāo)文件的某個(gè)節(jié),由 section 字段表示,該字段是一個(gè)到節(jié)頭部表的索引。有三個(gè)特殊的的偽節(jié),它們?cè)诠?jié)頭部表中是沒(méi)有條目的:

  • ABS:代表不該被重定位的符號(hào)
  • UNDEF:代表未定義的符號(hào),也就是在本目標(biāo)模塊中引用,但定義在其他地方的符號(hào)
  • COMMON:表示還未被分配位置的未初始化的數(shù)據(jù)目標(biāo)
    • 對(duì)于該類型符號(hào),value 字段給出對(duì)齊要求,size 給出最小的大小

只有可重定位目標(biāo)文件中才有偽節(jié),可執(zhí)行目標(biāo)文件中是沒(méi)有的。

GCC 將可重定位目標(biāo)文件中的符號(hào)分配到 COMMON 和 .bss 的規(guī)則:

  • COMMON:未初始化的全局變量
  • .bss:未初始化的靜態(tài)變量,以及初始化為 0 的全局或靜態(tài)變量

符號(hào)解析

對(duì)于局部符號(hào)的解析是非常簡(jiǎn)單的,因?yàn)榫幾g器只允許每個(gè)模塊中每個(gè)局部符號(hào)有一個(gè)定義。不過(guò),對(duì)全局符號(hào)的引用解析就麻煩的多。當(dāng)編譯器遇到一個(gè)不是在當(dāng)前模塊中定義的符號(hào)(變量或函數(shù)名)時(shí),會(huì)假設(shè)該符號(hào)是在其他某個(gè)模塊中定義的,生成一個(gè)鏈接器符號(hào)表?xiàng)l目,并把它交給鏈接器處理。

如果多個(gè)模塊定義同名的全局符號(hào),會(huì)發(fā)生什么呢?下面是 Linux 編譯系統(tǒng)采用的方法。

在編譯時(shí),編譯器向匯編器輸出每個(gè)全局符號(hào),或者是強(qiáng)或者是弱,匯編器把這個(gè)信息編碼在可重定位目標(biāo)文件的符號(hào)表里。函數(shù)和已初始化的全局變量是強(qiáng)符號(hào),未初始化的全局變量是弱符號(hào)。

根據(jù)強(qiáng)弱符號(hào)的定義,Linux 鏈接器使用如下規(guī)則來(lái)處理多重定義的符號(hào)名:

  • 不允許有多個(gè)同名強(qiáng)符號(hào)
  • 如果有一個(gè)強(qiáng)符號(hào)和多個(gè)弱符號(hào)同名,選擇強(qiáng)符號(hào)
  • 如果有多個(gè)弱符號(hào)同名,從弱符號(hào)中任意選擇一個(gè)

重定位

當(dāng)匯編器生成一個(gè)目標(biāo)模塊時(shí),它并不知道數(shù)據(jù)和代碼最終將放在內(nèi)存中的什么位置。它也不知道這個(gè)模塊引用的任何外部定義的函數(shù)或者全局變量的位置。所以,無(wú)論何時(shí)匯編器遇到對(duì)最終位置位置的目標(biāo)引用,它就會(huì)生成一個(gè)重定位條目,告訴鏈接器在將目標(biāo)文件合并成可執(zhí)行文件時(shí)如何修改這個(gè)引用。代碼的重定位條目放在 .rel.text 中,已初始化數(shù)據(jù)的重定位條目放在 .rel.data 中。

下圖為 ELF 重定位條目的格式:

elf object

符號(hào)解析完成后,代碼中的每個(gè)符號(hào)引用和正好一個(gè)符號(hào)定義關(guān)聯(lián)起來(lái)。此時(shí),就可以開(kāi)始重定位了。在重定位中,將合并輸入模塊,并為每個(gè)符號(hào)分配運(yùn)行時(shí)地址。重定位由兩步組成:

  • 重定位節(jié)和符號(hào)定義
    • 鏈接器將所有相同類型的節(jié)合并為同一類型的新的聚合節(jié)
    • 鏈接器將運(yùn)行時(shí)內(nèi)存地址賦給新的聚合節(jié),賦給輸入模塊定義的每個(gè)節(jié),以及賦給輸入模塊定義的每個(gè)符號(hào)

merge secssion

  • 重定位節(jié)中的符號(hào)引用
    • 鏈接器修改代碼節(jié)和數(shù)據(jù)節(jié)中對(duì)每個(gè)符號(hào)的引用,使得它們指向正確的運(yùn)行時(shí)地址

可執(zhí)行目標(biāo)文件

下圖為一個(gè)典型的 ELF 可執(zhí)行文件:

elf can

可執(zhí)行文件加載到內(nèi)存:

elf load

總結(jié) 

到此這篇關(guān)于C語(yǔ)言目標(biāo)文件的文章就介紹到這了,更多相關(guān)C語(yǔ)言目標(biāo)文件內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • C++17 使用 std::string_view避免字符串拷貝優(yōu)化程序性能

    C++17 使用 std::string_view避免字符串拷貝優(yōu)化程序性能

    這篇文章主要介紹了C++17 使用 std::string_view避免字符串拷貝優(yōu)化程序性能,幫助大家提高程序運(yùn)行速度,感興趣的朋友可以了解下
    2020-10-10
  • 深入了解c++數(shù)組與指針

    深入了解c++數(shù)組與指針

    這篇文章主要介紹了c++數(shù)組與指針的相關(guān)資料,幫助大家更好的理解和學(xué)習(xí)c++,感興趣的朋友可以了解下
    2020-08-08
  • C語(yǔ)言編程之掃雷小游戲空白展開(kāi)算法優(yōu)化

    C語(yǔ)言編程之掃雷小游戲空白展開(kāi)算法優(yōu)化

    掃雷是電腦上很經(jīng)典的游戲,特意去網(wǎng)上玩了一會(huì),幾次調(diào)試之后,發(fā)現(xiàn)這個(gè)比三子棋要復(fù)雜一些,尤其是空白展開(kāi)算法上和堵截玩家有的一拼,與實(shí)際游戲差別較大,不能使用光標(biāo),下面來(lái)詳解每一步分析
    2021-09-09
  • 簡(jiǎn)單分析C++指針的操作和運(yùn)算

    簡(jiǎn)單分析C++指針的操作和運(yùn)算

    這篇文章主要介紹了簡(jiǎn)單分析C++指針的操作和運(yùn)算的相關(guān)資料,需要的朋友可以參考下
    2015-07-07
  • C/C++實(shí)現(xiàn)的MD5哈希校驗(yàn)的示例代碼

    C/C++實(shí)現(xiàn)的MD5哈希校驗(yàn)的示例代碼

    MD5算法是一種廣泛使用的 Hash 算法,常用于確保信息傳輸?shù)耐暾耘c一致性,本文主要介紹了C/C++實(shí)現(xiàn)的MD5哈希校驗(yàn)的示例代碼,具有一定的參考價(jià)值,感興趣的可以了解一下
    2023-10-10
  • C程序和C++程序之間的互相調(diào)用圖文教程

    C程序和C++程序之間的互相調(diào)用圖文教程

    這篇文章主要給大家介紹了關(guān)于C程序和C++程序之間互相調(diào)用的相關(guān)資料,我們平常在刷題的時(shí)候,難免遇到實(shí)現(xiàn)多組輸入這樣的問(wèn)題,文中通過(guò)圖文介紹的非常詳細(xì),需要的朋友可以參考下
    2023-07-07
  • 一文帶你了解C++中的右值引用與移動(dòng)語(yǔ)義

    一文帶你了解C++中的右值引用與移動(dòng)語(yǔ)義

    本篇文章主要為大家詳細(xì)介紹了C++中的右值引用與移動(dòng)語(yǔ)義的相關(guān)知識(shí),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2023-03-03
  • 基于ROS 服務(wù)通信模式詳解

    基于ROS 服務(wù)通信模式詳解

    今天小編就為大家分享一篇基于ROS 服務(wù)通信模式詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2019-12-12
  • C語(yǔ)言模擬實(shí)現(xiàn)掃雷游戲

    C語(yǔ)言模擬實(shí)現(xiàn)掃雷游戲

    這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言模擬實(shí)現(xiàn)掃雷游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-01-01
  • C語(yǔ)言小程序 如何判斷三角型類型

    C語(yǔ)言小程序 如何判斷三角型類型

    第一個(gè)判斷三角形的類型,兩個(gè)浮點(diǎn)型數(shù)據(jù)不能直接判斷相等,為了輸入方便一些,自己設(shè)置的精度比較低,10^(-3)
    2013-07-07

最新評(píng)論