先前是由系統工具的角度說明 static 和 dynamic linker 的運作原理和如何使用它們:
這篇反過來,從使用情境出發,以使用 LevelDB 為例,說明每個步驟遇到的問題,和如何解決。
備註: 這篇是改寫 2017 用 Ubuntu 14.04 編譯 LevelDB 的記錄,現在編 LevelDB 的方式和產生檔案的位置有點不同,不過使用 LevelDB (或任何第三方函式庫) 會遇到的問題,是一樣的。
準備環境
抓好並編好 LevelDB 的 shared library,編譯方式見第三方函式庫附的說明。
編好後要找出 header 和 shared library 的位置。
$ ls include/ # header files
leveldb/
$ ls out-shared/libleveldb.so* # shared library
out-shared/libleveldb.so@ out-shared/libleveldb.so.1@ out-shared/libleveldb.so.1.20*
接下來在同樣目錄下寫 sample.cpp 使用 LevelDB。
下面的例子用 clang++
編譯,這裡用到的參數和 g++
一樣。
問題一:找不到 header (compilation error)
$ clang++ sample.cpp
sample.cpp:5:10: fatal error: 'leveldb/db.h' file not found
#include "leveldb/db.h"
^
1 error generated.
解法:用 -I 指定 header 位置,像是 clang++ sample.cpp -I include/
。
之後若希望 header 位置讓大家都方便使用,可以複製 LevelDB 的 include
到 /usr/local/include/leveldb/include
,指令改為 clang++ sample.cpp -I /usr/local/include/leveldb/include
。
問題二:找不到 shared library (linking error)
$ clang++ sample.cpp -I include/
/tmp/sample-2e7dd8.o: In function `main':
sample.cpp:(.text+0x1e): undefined reference to `leveldb::Options::Options()'
sample.cpp:(.text+0x6f): undefined reference to `leveldb::DB::Open(leveldb::Options const&, std::string const&, leveldb::DB**)'
sample.cpp:(.text+0x10c): undefined reference to `leveldb::Status::ToString() const'
sample.cpp:(.text+0x7d0): undefined reference to `leveldb::Status::ToString() const'
clang: error: linker command failed with exit code 1 (use -v to see invocation)
要求 linker 連結 libleveldb.so (linker 的參數由 clang++
/ g++
轉傳):
$ clang++ sample.cpp -I include/ -l leveldb
/usr/bin/ld: cannot find -lleveldb
clang: error: linker command failed with exit code 1 (use -v to see invocation)
但 compiler 說找不到要連結的 library
補上 libleveldb 的位置:
$ clang++ sample.cpp -I include/ -l leveldb -L out-shared/
問題三:執行時找不到 shared library
$ ./a.out
./a.out: error while loading shared libraries: libleveldb.so.1: cannot open shared object file: No such file or directory
編出 executable file 或 shared library 表示 static linker 成功,但執行時會用到 dynamic linker 載入函式庫。這錯誤訊息是dynamic linker 回報的。
用 ldd 可以檢查 shared library 的路徑是否正確:
$ ldd a.out | grep leveldb
libleveldb.so.1 => not found
幾種解法:
1. 用 LD_LIBRARY_PATH 指定位置
$ LD_LIBRARY_PATH=`pwd`/out-shared ./a.out
通常不建議這麼作,其它作法比較「乾淨」。
2. 將 library path 寫到 executable
$ clang++ sample.cpp -I include/ -l leveldb -L out-shared/ -Wl,-rpath,`pwd`/out-shared
$ objdump -p a.out | grep PATH # 確認有記錄
RPATH /home/fcamel/dev/study/leveldb/out-shared
$ ldd a.out | grep leveldb # 也可用 ldd 確認
libleveldb.so.1 => /home/fcamel/dev/study/leveldb/out-shared/libleveldb.so.1 (0x00007fc1f091e000)
這裡我用絕對路徑減少潛在的問題。若 libleveldb 和 a.out 一定會放在同樣位置,也可以用 -Wl,rpath,’$ORIGIN’/lib
。
3. 搬到系統函式庫
$ ldd a.out | grep leveldb
libleveldb.so.1 => not found
$ sudo su
$ cp out-shared/libleveldb.so* /usr/lib
$ ldd a.out | grep leveldb
libleveldb.so.1 => /usr/lib/libleveldb.so.1 (0x00007f1717026000)
但這樣和系統內建的混在一起,不好維護。改放到 /usr/local/lib/leveldb/ 下:
$ mkdir /usr/local/lib/leveldb
$ cp --preserve=links out-shared/libleveldb.so* /usr/local/lib/leveldb/
$ echo "/usr/local/lib/leveldb" > /etc/ld.so.conf.d/leveldb.conf
$ ldconfig # Update ldconfig's cache
$ ldd a.out | grep leveldb
libleveldb.so.1 => /usr/local/lib/leveldb/libleveldb.so.1 (0x00007f0314b32000)
ldconfig 會讀 /etc/ld.so.conf。我在 Ubuntu 14.04 看到的設定如下:
$ cat /etc/ld.so.conf
include /etc/ld.so.conf.d/*.conf
所以在 /etc/ld.so.conf.d/ 建新檔案寫入 /usr/local/lib/leveldb,然後更新 ldconfig cache 即可。
用 checkinstall 安裝
在 Ubuntu 上用 checkinstall 安裝更好,可以打包成 debian package,方便用系統工具列出安裝的內容和移除。