簡體   English   中英

為什么g ++在動態鏈接時檢測未定義的引用

[英]Why does g++ detect undefined reference when dynamically linking

我可能錯誤地認為動態鏈接是如何工作的,因為我無法弄清楚這一點。 據我所知,當動態鏈接庫時,它的符號在運行時被解析。 這個答案:

當您動態鏈接時,指向要鏈接的文件的指針(例如,文件的文件名)包含在可執行文件中,並且鏈接時不包括所述文件的內容。 只有當您稍后運行可執行文件時才會購買這些動態鏈接文件,並且它們只會被購買到可執行文件的內存中副本,而不是磁盤上的副本。

[...]

在動態情況下,主程序與C運行時導入庫鏈接(某些東西聲明了動態庫中的內容但實際上沒有定義它)。 即使實際代碼丟失,這也允許鏈接器鏈接。

然后,在運行時,操作系統加載程序執行主程序與C運行時DLL(動態鏈接庫或共享庫或其他命名法)的后期鏈接。

我很困惑為什么g++似乎期望共享對象在動態鏈接時存在。 當然,我希望庫的名稱是必要的,以便它可以在運行時加載,但為什么在這個階段需要.so 此外, g++在鏈接庫時會抱怨未定義的引用。

我的問題是:

  1. 為什么g++似乎在動態鏈接時需要共享對象,如果只在運行時加載庫? 我理解如何指定共享對象的名稱以便可以在運行時加載-l標志,但我認為必須在鏈接時( -L )提供.so的路徑或.so本身。
  2. 為什么g++在動態鏈接時會嘗試解析符號? 沒有什么能阻止我在鏈接時擁有一個完整的.so ,然后在運行時提供一個不同的(不完整的) .so ,這會導致程序在嘗試使用未定義的符號時崩潰。

我做了一個可重復的例子:

目錄結構:

.
├── main.cpp
└── test
    ├── usertest.cpp
    └── usertest.h

文件內容:

測試/ usertest.h

#ifndef USERTEST_H_4AD3C656_8109_11E8_BED5_5BE6E678B346
#define USERTEST_H_4AD3C656_8109_11E8_BED5_5BE6E678B346

namespace usertest
{
    void helloWorld();

    // This method is not defined anywhere
    void byeWorld();
};

#endif /* USERTEST_H_4AD3C656_8109_11E8_BED5_5BE6E678B346 */

測試/ usertest.cpp

#include "usertest.h"
#include <iostream>

void usertest::helloWorld()
{
    std::cout << "Hello, world\n";
}

main.cpp中

#include "test/usertest.h"

int main()
{
    usertest::helloWorld();
    usertest::byeWorld();
}

用法

$ cd test
$ g++ -c -fPIC usertest.cpp
$ g++ usertest.o -shared -o libusertest.so
$ cd ..
$ g++ main.cpp -L test/ -lusertest
$ LD_LIBRARY_PATH="test" ./a.out

預期的行為

我試圖在嘗試啟動a.out時崩潰,因為它無法在libusertest.so找到必要的符號。

實際行為

a.out的構建在鏈接時失敗,因為它無法找到byeWorld()

/tmp/ccVNcRRY.o: In function `main':
main.cpp:(.text+0xa): undefined reference to `usertest::byeWorld()'
collect2: error: ld returned 1 exit status

對於ELF格式,確實不必知道哪些符號屬於哪個庫,因為在執行程序時會發生實際的符號解析。 按照慣例,雖然ld仍然會在生成二進制文件時解析符號。 這是為了您的方便,以便您在缺少符號時立即獲得反饋,因為在這種情況下,您的程序無法正常工作。

使用--warn-unresolved-symbols標志,您可以在這種情況下將ld行為從錯誤更改為警告:

$ g++ -Wl,--warn-unresolved-symbols main.cpp -lusertest

應該發出警告但仍然創建可執行文件。 請注意,您仍然需要提供庫名稱,否則ld將不知道在哪里查找所需的符號。

在Windows上,鏈接器需要確切地知道哪個符號屬於哪個庫,以便生成必要的導入表。 因此,無法使用未解析的符號構建PE二進制文件。

可執行文件的代碼段始終是只讀的安全措施,因此您不能擁有在運行時修改自己的代碼的程序。 正如其他人所提到的,鏈接器正在做的是生成每個庫提供的符號列表。

您建議將此過程延遲到運行時,但這意味着如果您在鏈接時提供的庫列表不完整,則每次啟動它時二進制文件都可能崩潰。 當你可以在鏈接時簡單地檢查一下,為什么你會冒風險? 將符號解析延遲到運行時意味着每次運行程序時,它將對所有未解析的符號執行相同的搜索。 此外,如果您不必在鏈接時提供庫列表,則意味着它必須在運行時嘗試所有可能的庫。 您如何解析由多個庫定義的符號?

據我所知(以非常簡單的方式),動態鏈接器在運行時所做的是保留一個哈希表,在將其映射到程序的地址空間后,將這些符號轉換為動態鏈接庫中的地址(函數指針)。 在您的可執行文件中,鏈接器需要知道哪個庫提供了每個符號(函數,變量等)來執行此解析。

所以,在這個非常簡化的解釋中 ,你調用usertest::helloWorld(); 被翻譯成類似dynamic_resolve("usertest::helloWorld", "libusertest.so")(); with dynamic_resolve接收符號名稱和庫名稱,並返回一個函數指針。 在內部, dynamic_resolve (虛構名稱)正在做的是加載庫“libusertest.so”,檢索庫中函數的地址,在哈希表中緩存它,然后返回函數指針。 它可能正在使用這些系統調用。 在第一次調用之后,由於結果緩存在哈希表中並且庫已經加載,所以后續調用都要便宜得多。

暫無
暫無

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

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