Linux 動態庫 undefined symbol 原因定位與解決方法

VE視訊引擎發表於2021-07-12

在使用動態庫開發部署時,遇到最多的問題可能就是 undefined symbol 了,導致這個出現這個問題的原因有多種多樣,快速找到原因,採用對應的方法解決是本文寫作的目的。

可能的原因

  1. 依賴庫未找到
    這是最常見的原因,一般是沒有指定查詢目錄,或者沒有安裝到系統查詢目錄裡
  2. 連結的依賴庫不一致
    編譯的時候使用了高版本,然後不同機器使用時連結的卻是低版本,低版本可能缺失某些 api
  3. 符號被隱藏
    如果動態庫編譯時被預設隱藏,外部程式碼使用了某個被隱藏的符號。
  4. c++ abi 版本不一致
    最典型的例子就是 gcc 4.x 到 gcc 5.x 版本之間的問題,在 4.x 編輯的動態庫,不能在 5.x 中連結使用。

解決方法

  1. 依賴庫未找到
  • 使用 ldd -r , 確定系統庫中是否存在所依賴的庫
  • 執行 ldconfig 命令更新 ld 快取
  • 執行 ldconfig -p | grep {SO_NAME} 檢視是否能找到對應的庫
  • 檢查 LD_LIBRATY_PATH 是否設定了有效的路徑
  1. 連結的庫版本不一致
    如果系統中之前有安裝過相同的庫,或者存在多個庫,就需要確定連結的具體是哪個庫

有一個特殊場景需要注意下,.so 檔案中有個預設 rpath 路徑,用於搜尋被依賴的庫,這個路徑優先於系統目錄和LD_LIBRARY_PATH。假如 rpath 存在相同名字的 .so 檔案,會優先載入這個路徑的檔案。

在遇到 undefined symbol 問題時,使用 readelf -d | grep rpath 檢視:

$ readelf -d libSXVideoEngineJni.so | grep rpath
 0x000000000000000f (RPATH)              Library rpath: 
 [/home/slayer/workspace/SXVideoEngine-Core/Render/cmake-build-
 debug:/home/slayer/workspace/SXVideoEngine-Core/Render/../../SXVideoEngine-Core-Lib/blend2d/linux/lib]

如果存在的路徑中有相應的庫,可以先重新命名檔案再測試確認。

關於連線時的順序可以檢視文件: http://man7.org/linux/man-pages/man8/ld.so.8.html

    If a shared object dependency does not contain a slash, then it is
   searched for in the following order:

   o  Using the directories specified in the DT_RPATH dynamic section
      attribute of the binary if present and DT_RUNPATH attribute does
      not exist.  Use of DT_RPATH is deprecated.

   o  Using the environment variable LD_LIBRARY_PATH, unless the
      executable is being run in secure-execution mode (see below), in
      which case this variable is ignored.

   o  Using the directories specified in the DT_RUNPATH dynamic section
      attribute of the binary if present.  Such directories are searched
      only to find those objects required by DT_NEEDED (direct
      dependencies) entries and do not apply to those objects' children,
      which must themselves have their own DT_RUNPATH entries.  This is
      unlike DT_RPATH, which is applied to searches for all children in
      the dependency tree.

   o  From the cache file /etc/ld.so.cache, which contains a compiled
      list of candidate shared objects previously found in the augmented
      library path.  If, however, the binary was linked with the -z
      nodeflib linker option, shared objects in the default paths are
      skipped.  Shared objects installed in hardware capability
      directories (see below) are preferred to other shared objects.

   o  In the default path /lib, and then /usr/lib.  (On some 64-bit
      architectures, the default paths for 64-bit shared objects are
      /lib64, and then /usr/lib64.)  If the binary was linked with the
      -z nodeflib linker option, this step is skipped.

符號被隱藏

第三方已經編譯好的庫,在引入了對應的標頭檔案,使用了其中的某個方法,最終連結的時候出現 undefined symbol,這種情況有可能是庫的開發者並沒有匯出這個方法的符號。

# 使用 nm 命令檢視匯出的函式符號, 這裡檢視 License 相關的函式
$ nm -gDC libSXVideoEngineJni.so | grep -i license
0000000000008110 T __ZN13SXVideoEngine6Public7License10SetLicenseEPKc
0000000000008130 T __ZN13SXVideoEngine6Public7License13LicenseStatusEv
0000000000008190 T __ZN13SXVideoEngine6Public7License19IsVideoCutSupportedEv
0000000000008170 T __ZN13SXVideoEngine6Public7License26IsDynamicTemplateSupportedEv
0000000000008150 T __ZN13SXVideoEngine6Public7License26IsStadardTemplateSupportedEv

# nm 返回的並不是原始函式名,通過 c++filt 獲取原始名稱
$ c++filt __ZN13SXVideoEngine6Public7License10SetLicenseEPKc
SXVideoEngine::Public::License::SetLicense(char const*)

c++ Abi 版本不一致

Gcc 對 c++ 的新特性是一步一步的增加的,如果實現了新的特性,就可能會修改 c++ 的 abi,並且會升級 glibc 的版本。

Abi 連結最常見的錯誤是 std::string 和 std::list 的在gcc 4.x 和 gcc 5.x 的不同實現引起的。在gcc 4.x 時,gcc 對標準 string 的實現就放在 std 名稱空間下,編譯時展開為 std::basic_string 。但是 gcc 5.x 開始,對 string 的實現就放在了 std::__cxx11空間裡,編譯後展開為 std::__cxx11::basic_string 。這就會導致在 gcc 4.x 編譯的動態庫,假如有的函式使用了 string 作為引數或者返回值,這時匯出的函式引數為 std::basic_string 型別。 無法在 gcc 5.x 下編譯連線使用。
錯誤類似:

undefined symbol:  "std::__cxx11 ***"

這種情況有一個折中辦法就是在gcc 5.x 或以上 編譯時,增加 -D_GLIBCXX_USE_CXX11_ABI=0 禁用 c++11 abi。
當然最好的做法就是保證編譯器大版本基本一致。在新開發的程式如果用到了 c++ 的新特性,升級 gcc 版本和 glibc 是十分必要的。

實用命令總結

  1. ldd 命令,用於查詢某個動態庫所依賴的庫是否存在
# ldd -r <lib/excutable file> 
# 找不到的庫會出現 not found
$ ldd -r libSXVideoEngine.so
        linux-vdso.so.1 =>  (0x00007ffc337d2000)
        libz.so.1 => /lib64/libz.so.1 (0x00007f061cf41000)
        libX11.so.6 => /lib64/libX11.so.6 (0x00007f061cc03000)
        libEGL.so.1 => /lib64/libEGL.so.1 (0x00007f061c9ef000)
        libGLESv2.so.2 => /lib64/libGLESv2.so.2 (0x00007f061c7dd000)
        libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f061c5c1000)
        libblend2d.so => /home/seeshion/workspace/SXVideoEngine-Core/Render/../../SXVideoEngine-Core-Lib/blend2d/linux/lib/libblend2d.so (0x00007f061c187000)
        libfreeimage.so.3 => /lib/libfreeimage.so.3 (0x00007f061b8ac000)
        libavcodec.so.58 => /lib/libavcodec.so.58 (0x00007f06198b6000)
        libavformat.so.58 => /lib/libavformat.so.58 (0x00007f06193e1000)
        libavutil.so.56 => /lib/libavutil.so.56 (0x00007f06190bd000)
        ...
        
  1. nm 命令,用於讀取庫被匯出的符號
$ nm -gDC libSXVideoEngineJni.so | grep -i license
0000000000008110 T __ZN13SXVideoEngine6Public7License10SetLicenseEPKc
0000000000008130 T __ZN13SXVideoEngine6Public7License13LicenseStatusEv
0000000000008190 T __ZN13SXVideoEngine6Public7License19IsVideoCutSupportedEv
0000000000008170 T __ZN13SXVideoEngine6Public7License26IsDynamicTemplateSupportedEv
0000000000008150 T __ZN13SXVideoEngine6Public7License26IsStadardTemplateSupportedEv

  1. readelf 用於讀取 elf 檔案的相關資訊
$ readelf -d libSXVideoEngineJni.so | grep rpath
 0x000000000000000f (RPATH)              Library rpath: 
 [/home/slayer/workspace/SXVideoEngine-Core/Render/cmake-build-
 debug:/home/slayer/workspace/SXVideoEngine-Core/Render/../../SXVideoEngine-Core-Lib/blend2d/linux/lib]
  1. c++filt 用於獲取符號的原始名
$ c++filt __ZN13SXVideoEngine6Public7License10SetLicenseEPKc
SXVideoEngine::Public::License::SetLicense(char const*)

相關文章