C编译器、链接器、加载器详解
一、概述
C語(yǔ)言的編譯鏈接過(guò)程要把我們編寫(xiě)的一個(gè)c程序(源代碼)轉(zhuǎn)換成可以在硬件上運(yùn)行的程序(可執(zhí)行代碼),需要進(jìn)行編譯和鏈接。編譯就是把文本形式源代碼翻譯為機(jī)器語(yǔ)言形式的目標(biāo)文件的過(guò)程。鏈接是把目標(biāo)文件、操作系統(tǒng)的啟動(dòng)代碼和用到的庫(kù)文件進(jìn)行組織形成最終生成可加載、可執(zhí)行代碼的過(guò)程。過(guò)程圖解如下:?
?
二、編譯過(guò)程
編譯過(guò)程又可以分成兩個(gè)階段:編譯和匯編。
2.1編譯
編譯是指編譯器讀取源程序(字符流),對(duì)之進(jìn)行詞法和語(yǔ)法的分析,將高級(jí)語(yǔ)言指令轉(zhuǎn)換為功能等效的匯編代碼。
源文件的編譯過(guò)程包含兩個(gè)主要階段:
第一個(gè)階段是預(yù)處理階段,在正式的編譯階段之前進(jìn)行。預(yù)處理階段將根據(jù)已放置在文件中的預(yù)處理指令來(lái)修改源文件的內(nèi)容。
主要是以下幾方面的處理:
頭文件的目的主要是為了使某些定義可以供多個(gè)不同的C源程序使用,這涉及到頭文件的定位即搜索路徑問(wèn)題。頭文件搜索規(guī)則如下:
?
第二個(gè)階段編譯、優(yōu)化階段,編譯程序所要作得工作就是通過(guò)詞法分析和語(yǔ)法分析,在確認(rèn)所有的指令都符合語(yǔ)法規(guī)則之后,將其翻譯成等價(jià)的中間代碼表示或匯編代碼。?
?
2.2匯編
匯編實(shí)際上指匯編器(as)把匯編語(yǔ)言代碼翻譯成目標(biāo)機(jī)器指令的過(guò)程。目標(biāo)文件中所存放的也就是與源程序等效的目標(biāo)的機(jī)器語(yǔ)言代碼。目標(biāo)文件由段組成。通常一個(gè)目標(biāo)文件中至少有兩個(gè)段:
- 代碼段:該段中所包含的主要是程序的指令。該段一般是可讀和可執(zhí)行的,但一般卻不可寫(xiě)。
- 數(shù)據(jù)段:主要存放程序中要用到的各種全局變量或靜態(tài)的數(shù)據(jù)。一般數(shù)據(jù)段都是可讀,可寫(xiě),可執(zhí)行的。
?
2.3目標(biāo)文件(Executable and Linkable Format)
?
2.4 靜態(tài)庫(kù)與動(dòng)態(tài)庫(kù)
靜態(tài)庫(kù)(static library)就是將相關(guān)的目標(biāo)模塊打包形成的單獨(dú)的文件。使用ar命令。
靜態(tài)庫(kù)的優(yōu)點(diǎn)在于:
- 程序員不需要顯式的指定所有需要鏈接的目標(biāo)模塊,因?yàn)橹付ㄊ且粋€(gè)耗時(shí)且容易出錯(cuò)的過(guò)程;
- 鏈接時(shí),連接程序只從靜態(tài)庫(kù)中拷貝被程序引用的目標(biāo)模塊,這樣就減小了可執(zhí)行文件在磁盤(pán)和內(nèi)存中的大小。
動(dòng)態(tài)庫(kù)(dynamic library)是一種特殊的目標(biāo)模塊,它可以在運(yùn)行時(shí)被加載到任意的內(nèi)存地址,或者是與任意的程序進(jìn)行鏈接。
動(dòng)態(tài)庫(kù)的優(yōu)點(diǎn)在于:
- 更新動(dòng)態(tài)庫(kù),無(wú)需重新鏈接;對(duì)于大系統(tǒng),重新鏈接是一個(gè)非常耗時(shí)的過(guò)程;
- 運(yùn)行中可供多個(gè)程序使用,內(nèi)存中只需要有一份,節(jié)省內(nèi)存。
?
三、鏈接過(guò)程
鏈接器主要是將有關(guān)的目標(biāo)文件彼此相連接生成可加載、可執(zhí)行的目標(biāo)文件。鏈接器的核心工作就是符號(hào)表解析和重定位。
3.1 鏈接的時(shí)機(jī):
3.2 鏈接的作用(軟件復(fù)用):
3.3 靜態(tài)庫(kù)搜索路徑(由靜態(tài)鏈接器負(fù)責(zé))
3.4 動(dòng)態(tài)庫(kù)搜索路徑(由動(dòng)態(tài)鏈接器負(fù)責(zé))
3.5 靜態(tài)鏈接(編譯時(shí))
鏈接器將函數(shù)的代碼從其所在地(目標(biāo)文件或靜態(tài)鏈接庫(kù)中)拷貝到最終的可執(zhí)行程序中。這樣該程序在被執(zhí)行時(shí)這些代碼將被裝入到該進(jìn)程的虛擬地址空間中。靜態(tài)鏈接庫(kù)實(shí)際上是一個(gè)目標(biāo)文件的集合,其中的每個(gè)文件含有庫(kù)中的一個(gè)或者一組相關(guān)函數(shù)的代碼。
為創(chuàng)建可執(zhí)行文件,鏈接器必須要完成的主要任務(wù):
關(guān)于符號(hào)表和符號(hào)解析以及重定位的分析后續(xù)學(xué)習(xí)。
3.6 動(dòng)態(tài)鏈接(加載、運(yùn)行時(shí))
在此種方式下,函數(shù)的定義在動(dòng)態(tài)鏈接庫(kù)或共享對(duì)象的目標(biāo)文件中。在編譯的鏈接階段,動(dòng)態(tài)鏈接庫(kù)只提供符號(hào)表和其他少量信息用于保證所有符號(hào)引用都有定義,保證編譯順利通過(guò)。動(dòng)態(tài)鏈接器(ld-linux.so)鏈接程序在運(yùn)行過(guò)程中根據(jù)記錄的共享對(duì)象的符號(hào)定義來(lái)動(dòng)態(tài)加載共享庫(kù),然后完成重定位。在此可執(zhí)行文件被執(zhí)行時(shí),動(dòng)態(tài)鏈接庫(kù)的全部?jī)?nèi)容將被映射到運(yùn)行時(shí)相應(yīng)進(jìn)程的虛地址空間。動(dòng)態(tài)鏈接程序?qū)⒏鶕?jù)可執(zhí)行程序中記錄的信息找到相應(yīng)的函數(shù)代碼。 ?
四、加載過(guò)程
加載器把可執(zhí)行文件從外存加載到內(nèi)存并進(jìn)行執(zhí)行。 Linux中進(jìn)程運(yùn)行時(shí)的內(nèi)存映像如下:
?
?加載過(guò)程如下:
加載器首先創(chuàng)建如上圖所示的內(nèi)存映像,然后根據(jù)段頭部表,把目標(biāo)文件拷貝到內(nèi)存的數(shù)據(jù)和代碼段中。然后,加載器跳轉(zhuǎn)到程序入口點(diǎn)(即符號(hào)_start 的地址),執(zhí)行啟動(dòng)代碼(startup code),啟動(dòng)代碼的調(diào)用順序如所示:
?
五、處理目標(biāo)的常用工具
UNIX系統(tǒng)提供了一系列工具幫助理解和處理目標(biāo)文件。GNUbinutils 包也提供了很多幫助。這些工具包括:
- AR :創(chuàng)建靜態(tài)庫(kù),插入、刪除、列出和提取成員;
- STRINGS :列出目標(biāo)文件中所有可以打印的字符串;
- STRIP :從目標(biāo)文件中刪除符號(hào)表信息;
- NM :列出目標(biāo)文件符號(hào)表中定義的符號(hào);
- SIZE :列出目標(biāo)文件中節(jié)的名字和大小;
- READELF :顯示一個(gè)目標(biāo)文件的完整結(jié)構(gòu),包括ELF 頭中編碼的所有信息。
- OBJDUMP :顯示目標(biāo)文件的所有信息,最有用的功能是反匯編.text節(jié)中的二進(jìn)制指令。
- LDD :列出可執(zhí)行文件在運(yùn)行時(shí)需要的共享庫(kù)。
總結(jié)
以上是生活随笔為你收集整理的C编译器、链接器、加载器详解的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 快速理解Docker - 容器级虚拟化解
- 下一篇: 基于 Bochs 的操作系统内核实现