Linux動態庫依賴其它動態庫的問題

1 前言

這兩天在編寫一個插件系統Demo的時候,發現了個很奇怪的問題:插件加載器中已經鏈接了ld庫,但是應用程序在鏈接插件加載器的時候,卻還需要顯式的來鏈接ld庫。否則就會報:DSO missing from command line。這個報錯翻譯過來就是沒有在命令行中指定該動態庫
這個報錯就很搞事了,你說你明明知道需要哪個庫,爲什麼不直接幫我鏈接呢,非得我顯示的在命令行中指定呢?

2 現象描述

問題可以簡單描述爲:當鏈接可執行文件時,依賴於libA.so,而libA.so又依賴於libB.so,而且可執行文件中還直接調用了libB.so中的函數,那麼此時鏈接就會出現錯誤。

2.1 問題發生的前置條件

  • libA.so在編譯過程中顯式的鏈接了libB.so
  • 可執行文件中使用了libB.so的函數
  • binuntils版本 ≥ 2.22

2.2 Talk is cheap. Show me the code

話不多說,先看看可以復現改問題的代碼吧

libB.so的源碼:

#include <stdio.h>

int funB1(){

    printf("in funB1");

    return 0;
}

int funB2(){

    printf("in funB2");

    return 0;
}

這裏面有兩個函數:funB1funB2
其中funB1函數會被libA調用,而funB2會被可執行文件調用。

編譯libB.so:

$ gcc libB.cpp -fPIC -shared -o libB.so

libA.so的源碼:

#include <stdio.h>

int funB1();

int funA1(){

    printf("in funA1 \n");

    funB1();

    return 0;
}

該庫中只有一個函數funA1,該函數在內部調用了libB中的funB1函數。且該函數會被可執行文件調用。

編譯libA.so:

$ gcc libA.cpp -fPIC -shared -o libA.so -Wl,-rpath=./ -L./ -lB

main.cpp的源碼:

int funA1();
int funB2();

int main(){

    funA1();
    funB2();

    return 0;
}

編譯main.cpp:(復現錯誤的編譯方法)

gcc main.cpp -L./ -lA

當我們按照上面的指令編譯main.cpp的時候,便報錯了。

/usr/bin/ld: /tmp/ccDQXTKy.o: undefined reference to symbol '_Z5funB2v'
.//libB.so: error adding symbols: DSO missing from command line
collect2: error: ld returned 1 exit status

(問號.jpg)這,這GCC不是搞事嗎,你明明知道我需要連接libB.so爲啥就不幫我鏈接上去呢?難道我libA.so沒有指明要使用libB.so?我們使用下面的指令來看一下

$ ldd libA.so

得到如下信息:

	linux-vdso.so.1 =>  (0x00007ffd09def000)
	libB.so => ./libB.so (0x00007fc513d7d000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fc5139b3000)
	/lib64/ld-linux-x86-64.so.2 (0x00007fc514181000)

明明libA.so已經顯式的指明我要依賴libB.so了,那爲啥在編譯main.cpp的時候鏈接了libA.so,GCC卻還要我們顯式的鏈接libB.so呢?

3 答案

答案很簡單,那就是GCC就是想要你顯式鏈接唄。(你是編譯器,你牛好吧。)那這是爲啥呢?
官方一點的答案就是,自從binutils 2.22版本以後,如果你在程序中使用了你依賴的動態庫所依賴的動態庫中的函數時,你就必須顯式的指定你依賴的動態庫所依賴的動態庫。

說那麼多,我們更想知道的是,通過修改什麼參數可以解決這個問題呢?因爲你可能不想在編譯程序的時候要把動態庫所依賴的所有動態庫都顯示鏈接一遍。

4 究極答案

實際上,這是binutils在2.22版本以後,默認把--no-copy-dt-needed-entries這個選項打開了。當打開了這個選項的時候,編譯器在鏈接的時候是不會遞歸的去獲取依賴動態庫的依賴項的,於是就會出現上述的問題。關於該配置項的詳細說明如下:

   --copy-dt-needed-entries
   --no-copy-dt-needed-entries
       This option affects the treatment of dynamic libraries referred to by DT_NEEDED tags inside ELF dynamic libraries mentioned on the command line.  Normally the linker won't add a DT_NEEDED
       tag to the output binary for each library mentioned in a DT_NEEDED tag in an input dynamic library.  With --copy-dt-needed-entries specified on the command line however any dynamic
       libraries that follow it will have their DT_NEEDED entries added.  The default behaviour can be restored with --no-copy-dt-needed-entries.

       This option also has an effect on the resolution of symbols in dynamic libraries.  With --copy-dt-needed-entries dynamic libraries mentioned on the command line will be recursively
       searched, following their DT_NEEDED tags to other libraries, in order to resolve symbols required by the output binary.  With the default setting however the searching of dynamic
       libraries that follow it will stop with the dynamic library itself.  No DT_NEEDED links will be traversed to resolve symbols.

大概意思就是,跟在--no-copy-dt-needed-entries它後面的庫都不會遍歷其依賴項,使用--copy-dt-needed-entries則相反。也就是使用下面的指令來編譯mian.cpp就可以避免該問題了。

$ gcc main.cpp -L./ -Wl,--copy-dt-needed-entries -lA

題外話

在Linux的ELF文件中,如果依賴於其他的動態庫,那麼改ELF文件會存在一個.dynamic的段,這個段裏面會記錄其依賴的動態庫信息,其標誌位爲DT_NEEDED。

最近爲公司開發的開源超級輕量級插件系統(https://github.com/HSRobot/HPlugin),歡迎大家交流討論。

5 參考文檔

1,DSO missing from command line原因及解決辦法:https://segmentfault.com/a/1190000002462705
2,折騰gcc/g++鏈接時.o文件及庫的順序問題: https://www.cnblogs.com/OCaml/archive/2012/06/18/2554086.html#sec-1-4-1

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章