在編寫linux驅動程式時,時常會發現鏈結出錯,當時往往不知道錯誤在哪。現在了解到鏈結器的工作原理之後,明白當時為什麼出錯了。對於以後有效率地編寫驅動程式有很大幫助。
乙個c語言程式,經過諸如gcc之類的編譯器編譯成可執行檔案一般會經歷4個處理過程,這個大部分的linux入門書籍都有講到過,如果沒有扔掉它,:)!
ascii原始檔.c----》ascii中間檔案.i(由cpp處理後生成)------》組合語言檔案.s(由cl處理後生成)-----》可重定位目標檔案.o(由as處理後生成)-----》可執行目標檔案(由ld處理後生成)。
說到可執行目標檔案和可重定位目標檔案不得不講講elf(可執行可鏈結格式),但elf在大部分書籍都有講到,且網上資料相當多。
elf定義了可重定位和可執行目標檔案構成的大致框架。可重定位目標檔案中,有很多的節,每個節都是固定大小的條目。這些條目儲存了**、符號、引用、變數和除錯等等相關資訊。
乙個點c檔案會引用其他檔案的函式或者是全域性變數,那麼經過上面那一套走下來,會生成點o檔案也就是可重定位目標檔案。在可重定位目標檔案中也會儲存引用的函式或全域性變數資訊,而這些引用的函式和全域性變數在鏈結生成可執行程式時都要進行重定位。
典型地,.rel.text:**段需要重定位的資訊。主要是一些引用的函式;.rel.data:資料段需要重定位的資訊。主要是本模組引用的全域性變數等。
那麼,當我們將所有的可重定位目標檔案和靜態庫生成完成之後,輸入命令:
gcc ex1.c ex2.o ex3.o ex4.a會發生什麼呢?
可以很簡單的說,就是ld鏈結器會將它們連線生成可執行檔案。具體有兩個步驟:1是符號解析,2是重定位。
那麼符號到底是如何解析的呢?
還是上面的例子:
gcc ex1.c ex2.o ex3.o ex4.a
這其中ex4.a包含ex5.o和ex6.o
不失一般性,我們假設有:
ex1.c引用了ex2.o中的符號2.a,ex3.o中的3.a,ex4.a裡面的ex5.o中5.a,ex7.o中的7.a;
ex2.o則是ex3.o中的3.b
ex3.o則是ex2.o中的2.b,
當然此處假設ex2.o定義了2.a和2.b符號,ex3.o和ex5.o 、ex6.o與此類似。
在符號解析階段鏈結器從左往右按照命令列的順序來掃瞄可重定位目標檔案和靜態庫。它維持了三個集合e、u和d。
e是可重定位目標檔案的集合
u是未解析符號的集合
d為已定義的符號的集合
步驟1步驟2
步驟3步驟4
eex1.o
ex1.o ex2.o
ex1.o ex2.o ex3.o
ex1.o ex2.o ex3.o ex5.o
u2.a 3.a 5.a
3.a 5.a 3.b
5.a空
d2.a 2.b
2.a 2.b 3.a3.b
2.a 2.b 3.a 3.b 5.a
步驟1,未解析的符號有2.a3.a 4.a三個;
步驟2,由於ex2.o的加入,更新e集合,由於ex2.o本身定義了2.a 2.b兩個符號,所以u集合中的2.a 2.b引用可以被解析。解析後的符號被放入到d集合中
步驟3同步驟2;
步驟4中,靜態庫包含了ex5.o和ex6.o兩個可重定位目標檔案,那麼ld會逐個拿u集合中未解析的符號和靜態庫的可重定位目標檔案中定義的符號匹配,如果有匹配的,這將其加入到e中,如果沒有則進行下個可重定位目標檔案的匹配過程。本例中,由於ex5.o中定義的符號5.a正好和u集合中的5.a匹配,那麼ex5.o自身就被放入到e集合中了。像ex6.o定義個符號和u匹配不上,就沒有被放入到e集合中。從這點來看,ld不會將那麼沒有引用的可重定位目標檔案鏈結到可執行檔案中。
最後ld發現,掃瞄完所有的目標檔案和靜態庫了且u集合為空,那麼就會合併和重定義e集合中的目標檔案,從而構建出可執行檔案了。
若將上面命令改為如下命令:
gcc ex1.c ex2.o ex3.o ex4.a ex7.o
ex7.o引用了ex4.a中ex6.o中的6.a,那麼會怎麼樣呢?
答案是ld會輸出錯誤,並終止。
考慮到步驟4之後,掃瞄ex7.o的過程。
步驟4之後,還有ex7.o要掃瞄,那麼ld繼續掃瞄ex7.o。ex7.o被加入到e中,
將6.a加入到u中。
這樣掃瞄完了所有的目標檔案和靜態庫了,但是且u集合不為空。ld就會輸出錯誤並終止。
所以一般書籍給的忠告是,將靜態庫盡量放在命令的最後,也可以反覆輸入靜態庫。
由此,為了避免上面的錯誤,我們可以這樣:
gcc ex1.c ex2.o ex3.o ex7.o ex4.a
或是這樣
gcc ex1.c ex2.o ex3.o ex4.a ex7.o ex4.a
從上面不難看出,ld只會把引用了的可重定位目標檔案鏈結到可執行檔案中,那些沒有引用的則不會被包含進去。
以上只是說靜態庫和可重定位目標檔案有先後順序之分,順序不當會造成鏈結錯誤。如果全部都是可重定位目標檔案,那麼就沒有先後順序之分了。
《c專家程式設計》c5以及《computer systems a programmer's perspective》c7
鏈結器符號解析演算法小解以及靜態庫鏈結順序等等問題
一 編譯鏈結 在編寫linux驅動程式時,時常會發現鏈結出錯,當時往往不知道錯誤在哪。現在了解到鏈結器的工作原理之後,明白當時為什麼出錯了。對於以後有效率地編寫驅動程式有很大幫助。乙個c語言程式,經過諸如gcc之類的編譯器編譯成可執行檔案一般會經歷4個處理過程,這個大部分的linux入門書籍都有講到...
鏈結器,符號解析與重定位 概念
符號解析。將每個符號引用剛好和乙個符號定義聯絡起來。符號分為四類 匯出符號 export,本地符號 匯入符號 import,外部符號 靜態符號 本地符號 區域性符號 本地符號,不出現在符號表中 匯出符號,在本模組定義,能夠被其他模組引用的符號。非static全域性函式,非static全域性變數。匯入...
鏈結器,符號解析與重定位 概念
符號分為四類 匯出符號 export,本地符號 匯入符號 import,外部符號 靜態符號 本地符號 區域性符號 本地符號,不出現在符號表中 匯出符號,在本模組定義,能夠被其他模組引用的符號。非static全域性函式,非static全域性變數。匯入符號,在其他模組定義,被本模組引用的符號。exter...