![](/img/trans.png)
[英]/usr/bin/ld: warning: abc.so, needed by xyz.so not found (try using -rpath or -rpath-link)"
[英]Why does ld need -rpath-link when linking an executable against a so that needs another so?
我很好奇。 我創建了一個共享對象:
gcc -o liba.so -fPIC -shared liba.c
還有一個共享對象,它與前一個對象鏈接:
gcc -o libb.so -fPIC -shared libb.c liba.so
現在,當創建鏈接到libb.so
的可執行文件時,我將必須指定-rpath-link到ld,以便在發現libb.so
依賴於它時可以找到liba.so
:
gcc -o test -Wl,-rpath-link,./ test.c libb.so
否則ld會抱怨。
為什么,在鏈接test
時ld必須能夠找到liba.so
? 因為對我來說,除了確認liba.so
的存在之外,ld似乎並沒有做其他事情。 例如,運行readelf --dynamic ./test
僅在需要時列出libb.so
,因此我猜動態鏈接器必須libb.so -> liba.so
發現libb.so -> liba.so
依賴項,並自行搜索liba.so
。
我在x86-64 GNU / Linux平台上, test
的main()例程在libb.so
中調用了一個函數, liba.so
又在libb.so
中調用了一個函數。
為什么,在鏈接
test
時ld必須能夠找到liba.so
? 因為對我來說,除了確認liba.so
的存在之外,ld似乎並沒有做其他事情。 例如,運行readelf --dynamic ./test
僅在需要時列出libb.so
,因此我猜動態鏈接器必須libb.so -> liba.so
發現libb.so -> liba.so
依賴項,並自行搜索liba.so
。
好吧,如果我正確理解鏈接過程,則ld實際上甚至不需要找到libb.so
它可能會忽略test
所有未解析的引用,希望動態鏈接libb.so
在運行時加載libb.so
時能夠解析它們。 但是,如果ld以這種方式進行操作,則在鏈接時將不會檢測到許多“未定義的引用”錯誤,而是在嘗試在運行時加載test
時會發現它們。 因此, ld只是做額外的檢查,以確保可以在test
依賴的共享庫中真正找到在test
本身中未找到的所有符號。 因此,如果test
程序具有“未定義的引用”錯誤(某些變量或函數未在test
本身中找到,而在libb.so
均未libb.so
),則這在鏈接時變得明顯,而不僅僅是在運行時。 因此,這種行為只是額外的健全性檢查。
但是ld更進一步。 鏈接test
, ld還會檢查libb.so
中所有未解析的引用是否在libb.so
所依賴的共享庫中libb.so
(在我們的例子中libb.so
依賴liba.so
,因此需要liba.so
進行定位)在鏈接時)。 好吧,實際上ld在鏈接libb.so
時已經進行了此檢查。 為什么第二次執行此檢查...也許ld的開發人員發現,當您嘗試將程序鏈接到過時的庫(在鏈接時可能會加載)時,這種雙重檢查對於檢測損壞的依賴項很有用,但是現在它可以無法加載,因為它所依賴的庫已更新(例如, liba.so
后來被重做,並且其中的某些功能已從中刪除)。
UPD
只是做了很少的實驗。 似乎我的假設“實際上ld在鏈接libb.so
時已經完成了此檢查”,這是錯誤的。
讓我們假設liba.c
具有以下內容:
int liba_func(int i)
{
return i + 1;
}
libb.c
具有下一個:
int liba_func(int i);
int liba_nonexistent_func(int i);
int libb_func(int i)
{
return liba_func(i + 1) + liba_nonexistent_func(i + 2);
}
和test.c
#include <stdio.h>
int libb_func(int i);
int main(int argc, char *argv[])
{
fprintf(stdout, "%d\n", libb_func(argc));
return 0;
}
鏈接libb.so
:
gcc -o libb.so -fPIC -shared libb.c liba.so
鏈接器不會生成任何liba_nonexistent_func
錯誤消息,而是僅默默地生成損壞的共享庫libb.so
行為與使用ar創建靜態庫( libb.a
)相同,后者也不會解析生成的庫的符號。
但是當您嘗試鏈接test
:
gcc -o test -Wl,-rpath-link=./ test.c libb.so
您得到錯誤:
libb.so: undefined reference to `liba_nonexistent_func'
collect2: ld returned 1 exit status
如果ld不遞歸掃描所有共享庫,則無法檢測到此類錯誤。 因此,似乎該問題的答案與我上面講的相同: ld需要使用-rpath-link來確保可以稍后通過動態加載來加載鏈接的可執行文件。 只是一個健全的檢查。
UPD2
盡早檢查未解析的引用(鏈接libb.so
)是libb.so
,但是ld由於某些原因沒有這樣做。 這可能是為了允許對共享庫進行循環依賴。
liba.c
可以具有以下實現:
int libb_func(int i);
int liba_func(int i)
{
int (*func_ptr)(int) = libb_func;
return i + (int)func_ptr;
}
因此liba.so
使用libb.so
而libb.so
使用liba.so
(最好不要做這種事情)。 這樣可以成功編譯並運行:
$ gcc -o liba.so -fPIC -shared liba.c
$ gcc -o libb.so -fPIC -shared libb.c liba.so
$ gcc -o test test.c -Wl,-rpath=./ libb.so
$ ./test
-1217026998
盡管readelf說liba.so
不需要libb.so
:
$ readelf -d liba.so | grep NEEDED
0x00000001 (NEEDED) Shared library: [libc.so.6]
$ readelf -d libb.so | grep NEEDED
0x00000001 (NEEDED) Shared library: [liba.so]
0x00000001 (NEEDED) Shared library: [libc.so.6]
如果在共享庫的鏈接期間ld檢查了未解析的符號,則liba.so
的鏈接將無法進行。
請注意,我使用-rpath鍵而不是-rpath-link 。 區別在於, -rpath-link僅在鏈接時用於檢查最終可執行文件中的所有符號都可以解析,而-rpath實際上將您指定為參數的路徑嵌入到ELF中:
$ readelf -d test | grep RPATH
0x0000000f (RPATH) Library rpath: [./]
因此,現在可以test
共享庫( liba.so
和libb.so
)是否位於您當前的工作目錄( ./
)中。 如果僅使用-rpath-link,則test
ELF中將沒有此類條目,並且您必須將共享庫的路徑添加到/etc/ld.so.conf
文件或LD_LIBRARY_PATH
環境變量中。
UPD3
實際上可以在鏈接共享庫的過程中檢查未解析的符號,必須使用--no-undefined
選項:
$ gcc -Wl,--no-undefined -o libb.so -fPIC -shared libb.c liba.so
/tmp/cc1D6uiS.o: In function `libb_func':
libb.c:(.text+0x2d): undefined reference to `liba_nonexistent_func'
collect2: ld returned 1 exit status
此外,我還找到了一篇很好的文章,闡明了鏈接依賴於其他共享庫的共享庫的許多方面: 更好地理解示例的Linux次要依賴性解決方案 。
您的系統通過ld.so.conf
, ld.so.conf.d
以及系統環境LD_LIBRARY_PATH
等提供了系統范圍的庫搜索路徑,這些路徑由已安裝的庫通過pkg-config
信息和就像您針對標准庫進行構建時一樣。 當庫駐留在定義的搜索路徑中時,將自動遵循標准庫搜索路徑,以允許找到所有必需的庫。
對於您自己創建的自定義共享庫,沒有標准的運行時庫搜索路徑 。 在編譯和鏈接期間,您可以通過-L/path/to/lib
指定對庫的搜索路徑。 對於非標准位置的庫,可以選擇在編譯時將庫搜索路徑放置在可執行文件的標頭(ELF標頭)中,以便您的可執行文件可以找到所需的庫。
rpath
提供了一種將自定義運行時庫搜索路徑嵌入到ELF標頭中的方法,以便您也可以找到自定義庫,而不必每次使用時都指定搜索路徑。 這也適用於依賴庫的庫。 正如您所發現的,不僅在命令行上指定庫的順序很重要,而且還必須提供運行時庫搜索路徑(或rpath)以及要鏈接的每個從屬庫的信息,以便標頭包含運行所需的所有庫的位置。
評論的附錄
我的問題主要是為什么ld必須“自動嘗試查找共享庫”(liba.so)並“將其包含在鏈接中”。
這就是ld
工作方式。 摘自man ld
“-rpath選項在查找鏈接中顯式包含的共享對象所需的共享對象時也使用...如果在鏈接ELF可執行文件時未使用-rpath,則環境變量“ LD_RUN_PATH”的內容如果已定義,將使用它。” 在您的情況下, liba
不在LD_RUN_PATH
因此ld
將需要一種在可執行文件編譯期間定位liba
的方式,可以使用rpath
(如上所述),也可以為其提供顯式搜索路徑。
其次,“在鏈接中包含它”的真正含義。 在我看來,這似乎意味着:“確認它的存在”(liba.so),因為libb.so的ELF標頭沒有被修改(它們已經針對liba.so使用了NEEDED標記),而exec的標頭僅聲明了libb。因此需要。 為什么ld關心查找liba.so,它不能僅將任務留給運行時鏈接程序嗎?
不,回到ld
的語義。 為了產生“良好鏈接” , ld
必須能夠找到所有依賴的庫。 否則, ld
無法確保良好的鏈接。 運行時鏈接程序必須查找並加載 ,而不僅僅是查找程序所需的共享庫。 ld
不能保證會發生這種情況,除非在鏈接Progam時ld
本身可以找到所有需要的共享庫 。
我猜您需要知道何時使用-rpath
選項和-rpath-link
選項。 首先,我引用man ld
指定的內容:
- -rpath和-rpath-link之間的區別在於,-rpath選項指定的目錄包含在可執行文件中並在運行時使用,而-rpath-link選項僅在鏈接時有效。 只有使用--with-sysroot選項配置的本機鏈接程序和交叉鏈接程序才支持以這種方式搜索-rpath。
您必須區分鏈接時間和運行時。 根據您接受的anton_rh的回答,在編譯和鏈接共享庫或靜態庫時,不會啟用檢查未定義符號的功能,而在編譯和鏈接可執行文件時,則啟用ENABLED的功能。 (但是,請注意,有些文件是共享庫以及可執行文件,例如ld.so
鍵入man ld.so
進行探索,我不知道是否啟用了檢查未定義符號的功能。編譯這些“雙重”文件時)。
因此-rpath-link
用於鏈接時檢查,而-rpath
用於鏈接時和運行時,因為rpath
嵌入在ELF標頭中。 但是,請注意,如果同時指定了-rpath-link
選項和鏈接時,它們將覆蓋-rpath
選項。
但是,為什么還要使用-rpath-option
和-rpath
選項? 我認為它們用於消除“超鏈接”。 請參閱此示例,更好地了解Linux二級依賴性。 ,只需使用ctrl + F
導航到與“超鏈接”相關的內容。 你應該專注於為什么“overlinking”是壞的,因為該方法我們采用以避免“overlinking”,是否存在ld
選項-rpath-link
和-rpath
是合理的:我們故意忽略的命令一些庫和編譯鏈接以避免“過度鏈接”,並且由於省略,因此ld
需要使用-rpath-link
或-rpath
來定位這些省略的庫。
您實際上並沒有告訴ld(將libb
與liba
鏈接時) liba
在哪里 -只是說它是一個依賴項。 快速的ldd libb.so
將向您顯示它找不到liba
。
由於這些庫可能不在鏈接器搜索路徑中,因此在鏈接可執行文件時會出現鏈接器錯誤。 請記住,當您鏈接liba本身時,libb中的函數仍未解析,但是ld
的默認行為是在鏈接最終的可執行文件之前,不關心DSO中的未解析符號。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.