簡體   English   中英

當將可執行文件與a鏈接時,為什么ld需要-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更進一步。 鏈接testld還會檢查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.solibb.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

盡管readelfliba.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.solibb.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.confld.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指定的內容:

  1. -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(將libbliba鏈接時) liba 在哪里 -只是說它是一個依賴項。 快速的ldd libb.so將向您顯示它找不到liba

由於這些庫可能不在鏈接器搜索路徑中,因此在鏈接可執行文件時會出現鏈接器錯誤。 請記住,當您鏈接liba本身時,libb中的函數仍未解析,但是ld的默認行為是在鏈接最終的可執行文件之前,不關心DSO中的未解析符號。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM