文章下方附學習資源 請自主領取
作者簡介:
偉林,中年碼農,從事過電信、手機、安全、芯片等行業,目前依舊從事Linux方向開發工作,個人愛好Linux相關知識分享。
原理概述為什么要研究鏈接和加載?寫一個小的main函數用戶態程序,或者是一個小的內核態驅動ko,都非常簡單。但是這一切都是在gcc和linux內核的封裝之上,你只是實現了別人提供的一個接口,至于程序怎樣啟動、怎樣運行、怎樣實現這些機制你都一無所知。接著你會對程序出現的一些異常情況束手無策,對內核代碼中的一些用法不能理解,對makefile中的一些實現不知所云。所以這就是我們要研究鏈接和加載的目的:明白程序的映像文件是怎么組織的,程序啟動是怎么實現的,相關的機制是怎么聯系在一起的。“你應當了解真相,真相會使你自由”。
鏈接和加載(linker and loader): linker即鏈接器,它負責將多個.c編譯生成的.o文件,鏈接成一個可執行文件或者是庫文件;loader即加載器,它原本的功能很單一只是將可執行文件的段拷貝到編譯確定的內存地址即可,但是有了動態鏈接庫以后,部分的外部庫引用符號在加載的時候才會得到解析,所以加載也要處理鏈接器的相同操作重定位。
這方面的資料乍一看起來非常晦澀難懂,其實根本的功能電腦非常簡單:鏈接和加載的最核心的內容就是重定位。鏈接器負責將多個.o文件鏈接重定位成一個大文件,而加載器再將這個大文件重定位到一個進程空間當中去。
在linux環境下,鏈接和加載的機制最終有一個載體來承擔,這個載體就是elf文件。所以從研究elf文件格式入手,是理解鏈接和加載原理的好方法。
本文檔描述的鏈接和加載主要針對用戶程序而言,在操作系統的鏈接和加載和這里有些不同,因為如果你編譯一個內核,在加載內核的時候又有誰來做動態加載呢?關于內核實現的不同以后再在專門文檔中描述。
重定位原理前面已經說過鏈接和加載的核心內容就是重定位,所以開篇先用通俗易懂的語言來闡明重定位的原理。
符號表(Symbol Table):符號表就是一張字符符號和地址的對應表,例如使用“nm file“、”readelf -s file “等命令可以讀出一個elf文件的符號表。符號表的作用就是一個助記符,用一個字符串來標示某些抽象的地址,它能標示的地址有代碼地址和數據地址,代碼地址包括函數名、跳轉標號,數據地址包括全局變量。
符號表的組織如下圖所示:
從以上描述中可以看出,符號表的作用就是將符號名稱和地址進行綁定。而綁定的根本目的就是方便對符號的引用,在符號值發生改變的時候,不需要去手工改動源代碼中對符號引用的地方,而這種改動是由鏈接程序在重新生成執行文件時自動完成的。
重定位表(Relocation):有了符號表,就需要有人對符號表進行引用,在程序的執行過程中對全局變量的引用、跳轉、調用函數,這些都涉及到相應的符號引用。符號和其引用是一對多的關系,一個符號可能被代碼中多處引用。因為符號值改變的時候,也需要對所有引用符號的地方的代碼進行修改,所以需要還有一張表來記錄符號表的引用關系,這就是重定位表:
從上圖可見,重定位表項用來記錄鏈接和加載的過程中需要重新定位的位置,在各個段位置發生改變而引起符號地址改變時,根據重定位表來修改符號引用的值。
GOT表(Global Offset Table):前面的符號表和重定位表已經滿足編譯和鏈接過程中的重定位需求。同樣加載的過程中還需要重定位操作,需要將外部鏈接庫中的函數和變量和本程序中的引用鏈接起來,但是由于加載過程中代碼已經處于運行狀態,使用鏈接過程中同樣的重定位手段有些不合適。鏈接的重定位是通過重定位表直接修改代碼來完成的,但是代碼在運行過程中再去修改代碼會帶來很多問題和風險。
所以加載過程中的重定位,使用了一種改良的重定位手段:即通過兩張間接訪問表來屏蔽掉重定位帶來的對代碼的修改,訪問外部數據使用GOT,訪問外部程序使用PLT。這樣可鏈接出位置無關代碼PIC(Position Independent Code),需要重定位時只需要修改GOT和PLT的值,而不需要去改動可執行代碼。
GOT表用來做數據重定位的原理如上圖所示。
嵌入式物聯網需要學的東西真的非常多,千萬不要學錯了路線和內容,導致工資要不上去!
無償分享大家一個資料包,差不多150多G。里面學習內容、面經、項目都比較新也比較全!某魚上買估計至少要好幾十。
點擊這里找小助理0元領取:嵌入式物聯網學習資料(頭條)
PLT表(Procedure Linkage Table):從上一節可知,加載過程中的重定位為了避免對代碼的修改,引入了GOT來屏蔽對數據的訪問,同理對外部代碼的訪問也是可以用GOT來訪問的。但是為了實現動態鏈接的特性,即使用的時候才鏈接,不使用時可以不用鏈接,對外部代碼的訪問引入了一個新的表項PLT。
elf文件相關背景Elf文件格式,是現有linux環境下最流行的可執行文件格式,在elf文件存儲的信息之上,實現了相應的鏈接和加載特性。 Linux環境下可執行文件格式的發展歷史是:a.out -> coff -> xcoff -> elf。 Windows環境下可執行文件格式的發展歷史是:dos com/exe -> pe-coff。
elf文件格式Linux環境下,三種類型的執行文件都可以使用elf格式來表示:可重定位文件(即編譯生成但是未連接的文件)、動態庫文件、可執行文件。
Elf文件提供了兩種文件解析的視角,鏈接視角和動態加載視角。鏈接視角使用section的概念來解析文件,主要關注鏈接過程的使用;動態加載視角使用segment的概念來解析文件,主要關注加載和動態鏈接的實現。
整個文件的組織框圖如上所示,ELF頭描述了section header table和program header table的起始位置、表項大小和個數。根據section header table來尋址相應的section,根據program header table來尋址相應的segment,可以看到一般是一個segment包含多個section。
Elf文件的原理已經在上一章中闡述,elf的具體文件格式詳電腦 細描述可以參考參考資料中的“Executable and Linking Format (ELF) Specification “。這里不再詳細描述,只是記一些Specification上沒有的概要和重點理解。
加載視角的“PT_LOAD “類型segment:表明可加載到內存中的段,一般程序都包含兩個此種類型的段.data、.text加載視角的“PT_INTERP“類型segment:指定動態加載程序,即我們用 “ldd“命令看到的動態加載器加載視角的“PT_DYNAMIC “類型segment:相當于動態加載的一個入口段,指定了動態加載和鏈接需要的各種數據段的地址和類型。DT_NEEDED、DT_SONAME、DT_RPATH表項承載的是編譯時指定的一些依賴庫和搜索路徑等等。相關工具Linux下可以操作elf文件的有以下工具:
a.readelf“readelf –a file“讀出elf文件的所有信息。b.nm“nm file“讀出elf文件的符號表信息。c.objdump“objdump –d 電腦file“反匯編出elf文件中包含可執行代碼的section,elf命令中功能最強大的一個。d.objcopy轉換elf文件為bin或者其他格式的文件,編譯內核的時候會使用到。e.strip去掉elf文件中符號表和調試信息,對elf文件進行減肥。f.addr2line將絕對地址,轉換成調試信息中的源文件行號。
原文作者:彭偉林
作品來源:人人極客社區
原文鏈接:https://mp.weixin.qq.com/s/W1jUTSnM8CCBAbj-psdAbw
電腦