簡體   English   中英

靜態鏈接 libstdc++:有什么陷阱嗎?

[英]Linking libstdc++ statically: any gotchas?

我需要將一個基於 Ubuntu 12.10 和 GCC 4.7 的 libstdc++ 構建的 C++ 應用程序部署到運行 Ubuntu 10.04 的系統,它帶有相當舊版本的 libstdc++。

目前,我正在使用-static-libstdc++ -static-libgcc編譯,正如這篇博文所建議的: 靜態鏈接 libstdc++ 作者警告在靜態編譯 libstdc++ 時不要使用任何動態加載的 C++ 代碼,這是我尚未檢查的內容。 盡管如此,到目前為止一切似乎都進展順利:我可以在 Ubuntu 10.04 上使用 C++11 功能,這正是我所追求的。

我注意到這篇文章是從 2005 年開始的,也許從那以后發生了很大的變化。 它的建議是否仍然有效? 有什么我應該注意的潛在問題嗎?

該博客文章非常不准確。

據我所知,GCC 的每個主要版本都引入了 C++ ABI 更改(即那些具有不同的第一或第二版本號組件的版本)。

不對。 自 GCC 3.4 以來引入的唯一 C++ ABI 更改是向后兼容的,這意味着 C++ ABI 已經穩定了將近九年。

更糟糕的是,大多數主要的 Linux 發行版都使用 GCC 快照和/或修補它們的 GCC 版本,這使得在分發二進制文件時幾乎不可能確切知道您可能正在處理的 GCC 版本。

發行版的 GCC 補丁版本之間的差異很小,ABI 沒有變化,例如 Fedora 的 4.6.3 20120306(Red Hat 4.6.3-2)的 ABI 與上游 FSF 4.6.x 版本兼容,幾乎可以肯定與任何 4.6 版本兼容。 x 來自任何其他發行版。

在 GNU/Linux 上,GCC 的運行時庫使用 ELF 符號版本控制,因此很容易檢查對象和庫所需的符號版本,如果你有一個libstdc++.so提供這些符號,它就可以工作,即使稍微有點問題也沒關系與您的發行版的另一個版本不同的補丁版本。

但是如果要工作,則不能動態鏈接 C++ 代碼(或任何使用 C++ 運行時支持的代碼)。

這也不是真的。

也就是說,靜態鏈接到libstdc++.a是您的一種選擇。

如果您動態加載一個庫(使用dlopen ),它可能不起作用的原因是當您(靜態)鏈接它時,您的應用程序可能不需要它所依賴的 libstdc++ 符號,因此這些符號將不會出現在您的可執行文件中。 這可以通過將共享庫動態鏈接到libstdc++.so來解決(如果它依賴於它,無論如何這是正確的做法。)ELF 符號插入意味着共享庫將使用可執行文件中存在的符號,但在您的可執行文件中不存在的其他人將在它鏈接到的任何libstdc++.so中找到。 如果您的應用程序不使用dlopen ,則無需關心它。

另一種選擇(也是我更喜歡的選擇)是將更新的libstdc++.so與您的應用程序一起部署,並確保它在默認系統libstdc++.so之前找到,這可以通過強制動態鏈接器在正確的位置查找來完成,或者在運行時使用$LD_LIBRARY_PATH環境變量,或在鏈接時在可執行文件中設置RPATH 我更喜歡使用RPATH ,因為它不依賴於為應用程序工作而正確設置的環境。 如果您使用'-Wl,-rpath,$ORIGIN'鏈接您的應用程序(請注意單引號以防止 shell 嘗試擴展$ORIGIN ),那么可執行文件將具有$ORIGINRPATH ,它告訴動態鏈接器查找共享與可執行文件本身位於同一目錄中的庫。 如果將較新的libstdc++.so放在與可執行文件相同的目錄中,它將在運行時找到,問題就解決了。 (另一種選擇是將可執行文件放在/some/path/bin/中,將更新的 libstdc++.so 放在/some/path/lib/中,並與'-Wl,-rpath,$ORIGIN/../lib'或任何鏈接相對於可執行文件的其他固定位置,並設置 RPATH 相對於$ORIGIN

Jonathan Wakely 的出色回答的一個補充,為什么 dlopen() 有問題:

由於 GCC 5 中的新異常處理池(請參閱PR 64535PR 65434 ),如果您 dlopen 和 dlclose 靜態鏈接到 libstdc++ 的庫,您每次都會發生(池對象的)內存泄漏。 因此,如果您有機會使用 dlopen,靜態鏈接 libstdc++ 似乎是一個非常糟糕的主意。 請注意,這是一個真正的泄漏,而不是PR 65434中提到的良性泄漏。

Jonathan Wakely 關於 RPATH 的回答的補充:

RPATH 只有在所討論的 RPATH 是正在運行的應用程序的 RPATH 時才有效。 如果你有一個庫通過它自己的 RPATH 動態鏈接到任何庫,庫的 RPATH 將被加載它的應用程序的 RPATH 覆蓋。 當您不能保證應用程序的 RPATH 與庫的 RPATH 相同時,這是一個問題,例如,如果您希望依賴項位於特定目錄中,但該目錄不是應用程序 RPATH 的一部分。

例如,假設您有一個應用程序 App.exe,它動態鏈接依賴於 GCC 4.9 的 libstdc++.so.x。 App.exe 通過 RPATH 解決了這種依賴關系,即

App.exe (RPATH=.:./gcc4_9/libstdc++.so.x)

現在假設還有另一個庫 Dependency.so,它對 GCC 5.5 的 libstdc++.so.y 有一個動態鏈接的依賴。 這里的依賴是通過庫的RPATH來解決的,即

Dependency.so (RPATH=.:./gcc5_5/libstdc++.so.y)

當 App.exe 加載 Dependency.so 時,它既不追加也不預先添加庫的 RPATH 它根本不咨詢它。 唯一被考慮的 RPATH 將是正在運行的應用程序的 RPATH,或本例中的 App.exe。 這意味着如果庫依賴於 gcc5_5/libstdc++.so.y 中但不在 gcc4_9/libstdc++.so.x 中的符號,則庫將無法加載。

這只是一個警告,因為我自己過去也遇到過這些問題。 RPATH 是一個非常有用的工具,但它的實現仍然存在一些問題。

您可能還需要確保不依賴於動態 glibc。 在生成的可執行文件上運行ldd並記下任何動態依賴項(libc/libm/libpthread 是常見的可疑對象)。

額外的練習是使用這種方法構建一堆涉及的 C++11 示例,並在真實的 10.04 系統上實際嘗試生成的二進制文件。 在大多數情況下,除非你對動態加載做了一些奇怪的事情,否則你會立即知道程序是運行還是崩潰。

我想在 Jonathan Wakely 的回答中添加以下內容。

在 linux 上玩-static-libstdc++時,我遇到了dlclose()的問題。 假設我們有一個靜態鏈接到libstdc++的應用程序“A”,它在運行時動態加載鏈接到libstdc++插件“P”。 沒關系。 但是當'A'卸載'P'時,發生分段錯誤。 我的假設是卸載libstdc++.so后,'A' 不再可以使用與libstdc++相關的符號。 請注意,如果“A”和“P”都靜態鏈接到libstdc++ ,或者如果“A”動態鏈接而“P”靜態鏈接,則不會出現問題。

摘要:如果您的應用程序加載/卸載可能動態鏈接到libstdc++的插件,則該應用程序也必須動態鏈接到它。 這只是我的觀察,我想聽聽您的意見。

暫無
暫無

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

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