不知道大家平時(shí)編程過(guò)程中使用動(dòng)態(tài)鏈接庫(kù)的情況多不多,如果一個(gè)程序引用了無(wú)數(shù)個(gè)動(dòng)態(tài)鏈接庫(kù),那就有可能引入符號(hào)沖突的問(wèn)題,問(wèn)題如下:
想象中
實(shí)際上
下面,我們嘗試解決它。
最開(kāi)始介紹下g++基本命令參數(shù):
g++-c
先來(lái)看一段代碼:
#include
再定義一個(gè)簡(jiǎn)單的main.cc程序:
#include
編譯這兩個(gè)文件,并分別打包成靜態(tài)庫(kù):
g++ -c work.cc -o work.oar rc libwork.a work.og++ -c main.cc -o main.oar rc libmain.a main.o
現(xiàn)在將這兩個(gè)靜態(tài)庫(kù)鏈接成一個(gè)可執(zhí)行文件,注意鏈接器如果發(fā)現(xiàn)當(dāng)前庫(kù)中使用了沒(méi)有被定義的符號(hào),它只會(huì)向后查找,因此最低級(jí)別沒(méi)有其它依賴(lài)的庫(kù)應(yīng)該放在最右邊,如果出現(xiàn)了符號(hào)沖突問(wèn)題,鏈接器會(huì)使用最左邊的符號(hào)。
如果這樣進(jìn)行鏈接:
$ g++ -s -L. -o main.exe -lwork -lmain./libmain.a(main.o): In function `main':main.cc undefined reference to `DoThing()'collect2: error: ld returned 1 exit status
鏈接失敗,因?yàn)閙ain庫(kù)里的DoThing符號(hào)沒(méi)有被定義,鏈接器向后查找,沒(méi)有找到對(duì)應(yīng)的符號(hào)定義,這里更改下鏈接庫(kù)的順序:
g++-s-L.-omain.exe-lmain-lwork$./main.exestartworkfinished
鏈接成功。
現(xiàn)在寫(xiě)一個(gè)簡(jiǎn)單的容易產(chǎn)生符號(hào)沖突的文件conflict.cc:
#include
編譯并打包成靜態(tài)庫(kù):
g++-cconflict.cc-oconflict.oar rc libconflict.a conflict.o
如果按這樣的順序鏈接成一個(gè)可執(zhí)行程序:
$g++-s-L.-omain.exe-lmain-lwork-lconflict$./main.exestartworkfinished
如果稍微更改一下鏈接的順序:
$g++-s-L.-omain.exe-lmain-lconflict-lwork$ ./main.exestartconflictfinished
這里發(fā)現(xiàn)順序的不同導(dǎo)致了程序輸出內(nèi)容不同,究其原因就是那潛在的符號(hào)沖突。
現(xiàn)在再試試動(dòng)態(tài)庫(kù),先介紹如何使用動(dòng)態(tài)庫(kù):
$rmlibconflict.a$g++-sharedconflict.o-olibconflict.so$g++-s-L.-omain.exe-lmain-lconflict$LD_LIBRARY_PATH=../main.exestartconflictfinished
現(xiàn)在再引用一個(gè)中間層在動(dòng)態(tài)鏈接庫(kù)中調(diào)用conflict的文件layer.cc
#include
并把layer和conflict打包成一個(gè)動(dòng)態(tài)鏈接庫(kù):
$g++-clayer.cc-olayer.o$ g++ -shared layer.o conflict.o -o libconflict.so
然后更新main.c程序,main里面調(diào)用layer,layer里調(diào)用conflict:
#include
編譯鏈接執(zhí)行:
$g++-cmain.cc-omain.o$arrclibmain.amain.o$g++-s-L.-omain.exe-lmain-lconflict$LD_LIBRARY_PATH=../main.exestartlayerconflictfinished
正常輸出,沒(méi)啥問(wèn)題,現(xiàn)在再把之前的work.cc也塞到main.cc中,觀察下沖突:
#include
把work.o和main.o打包成一個(gè)庫(kù),之后和conflict鏈接成一個(gè)可執(zhí)行程序,運(yùn)行:
$g++-cmain.cc-omain.o$arrclibmain.amain.owork.o$g++-s-L.-omain.exe-lmain-lconflict$LD_LIBRARY_PATH=../main.exestartworklayerworkfinished
這里輸出了兩個(gè)work,正常情況下第二個(gè)work應(yīng)該輸出conflict,怎么解決呢?
可以考慮使用-fvisibility=hidden來(lái)隱藏內(nèi)部的符號(hào),鏈接庫(kù)內(nèi)部使用的符號(hào)把它隱藏掉,不讓它被導(dǎo)出,外部也不會(huì)改變它的調(diào)用路徑。
先使用nm看一下libconflict.so里面的符號(hào):
$nm-CDlibconflict.sow_ITM_deregisterTMCloneTablew_ITM_registerTMCloneTable000000000000065aTDoLayer()0000000000000672TDoThing()0000000000201030B__bss_startw__cxa_finalizew__gmon_start__0000000000201030D_edata0000000000201038B_end0000000000000688T_fini0000000000000528T_init U puts
如果把符號(hào)隱藏掉:
$g++-fvisibility=hidden-clayer.cc-olayer.o$g++-fvisibility=hidden-cconflict.cc-oconflict.o$g++-sharedlayer.oconflict.o-olibconflict.so再使用nm看一下libconflict.so里面的符號(hào):$nm-CDlibconflict.sow_ITM_deregisterTMCloneTablew_ITM_registerTMCloneTable0000000000201028B__bss_startw__cxa_finalizew__gmon_start__0000000000201028D_edata0000000000201030B_end0000000000000618T_fini00000000000004c0T_init U puts
這樣的話(huà)main函數(shù)肯定不能調(diào)用DoLayer啦,因?yàn)镈oLayer符號(hào)沒(méi)有暴露出來(lái):
$g++-s-L.-omain.exe-lmain-lconflict./libmain.a(main.o):Infunction`main':main.ccundefinedreferenceto`DoLayer()'collect2: error: ld returned 1 exit statu
那怎么暴露出來(lái)特定符號(hào)呢,直接看代碼,改動(dòng)了layer.cc:
#include
再編譯鏈接運(yùn)行看看結(jié)果:
$g++-fvisibility=hidden-clayer.cxx-olayer.o$g++-sharedlayer.oconflict.o-olibconflict.so$g++-s-L.-omain.exe-lmain-lconflict$LD_LIBRARY_PATH=../main.exestartworklayerconflictfinished
發(fā)現(xiàn)已經(jīng)是我們期待的結(jié)果啦,符號(hào)沖突的問(wèn)題因此被解決。
是不是感覺(jué)很麻煩,難道每個(gè)要暴露的符號(hào)都要加上__attribute__這種修飾嗎,這里其實(shí)可以寫(xiě)一個(gè)export文件,告訴編譯器要導(dǎo)出的所有符號(hào)有哪些。
export.txt {global:*DoLayer*;local:*;};g++ -Wl,--version-script=export.txt -s -shared layer.o conflict.o -o libconflict.so
但這種方式只有在gcc中才可以被使用,我在clang中嘗試使用但是失敗啦,所以為了兼容性不建議使用這種方式,還是消停的使用__attribute__來(lái)解決符號(hào)沖突問(wèn)題吧。
Tips
通過(guò)隱藏符號(hào)可以減小可執(zhí)行程序的大小,還可以解決符號(hào)沖突問(wèn)題,但有個(gè)缺點(diǎn),因?yàn)殡[藏了符號(hào),線(xiàn)上程序運(yùn)行時(shí)如果出現(xiàn)crash,通過(guò)堆棧信息我們看不到具體函數(shù)調(diào)用路徑,給定位問(wèn)題帶來(lái)了困難。所以,是否需要使用這種辦法,還應(yīng)根據(jù)實(shí)際情況具體抉擇。
原文標(biāo)題:原來(lái)編譯鏈接還有這么多套路……
文章出處:【微信公眾號(hào):嵌入式ARM】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
責(zé)任編輯:haq
-
嵌入式
+關(guān)注
關(guān)注
5060文章
18975瀏覽量
302094 -
編程
+關(guān)注
關(guān)注
88文章
3566瀏覽量
93539
原文標(biāo)題:原來(lái)編譯鏈接還有這么多套路……
文章出處:【微信號(hào):gh_c472c2199c88,微信公眾號(hào):嵌入式微處理器】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論