顯示具有 build 標籤的文章。 顯示所有文章
顯示具有 build 標籤的文章。 顯示所有文章

2017年4月29日 星期六

(C/C++ ) 如何在 Linux 上使用自行編譯的第三方函式庫

以使用 LevelDB 為例。

抓好並編好相關檔案,編譯方式見第三方函式庫附的說明:

$ 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*

下面的例子用 clang++ 編譯,這裡用到的參數和 g++ 一樣。

問題一:找不到 header

$ clang++ sample.cpp
sample.cpp:5:10: fatal error: 'leveldb/db.h' file not found
#include "leveldb/db.h"
         ^
1 error generated.

解法:用 -I 指定 header 位置

問題二:找不到 shared library

$ 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 指定位置 (man ld.so 查看細節)

$ LD_LIBRARY_PATH=`pwd`/out-shared ./a.out

若 out-shared 的位置有固定的話,可以在 /.bashrc 加上

export LD_LIBRARY_PATH=/path/to/out-shared 

2. 將 library path 寫到 executable 裡 (man ld.sh 查看細節):

$ 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)

這裡我用絕對路徑減少潛在的問題。

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)

由 man ldconfig 得知 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 即可。

參考資料

  1. The Linux Programming Interface ch41: Fundamentals of Shared Libraries
  2. man ld.so
  3. man ldconfig

2012年11月22日 星期四

善用 shared library visibility 減少程式之間的衝突

關於 static library 和 shared library 的基本知識:

static library 沒有特別的, 但是 shared library 有不少神奇的功能可用。

想像一個情境, 有四個獨立的專案 A, B, X, Y 四者, 其中 A 用到 B, X 用到 Y。若 A 也想用到 X, 但 B 和 Y 有重覆的 symbol, 得在編譯和連結時動點手腳, 才能過關。

上面情境的示意圖:

A -> B
|
v
X -> Y

假設編譯 B, X, Y 時產生 static library, 在產生執行檔 A 時, ld 會抱怨某些 symbol 衝突 (在 B 和 Y 裡面)。

static library 只是一堆 object file 的集合體, 就像 tar 包覆一堆檔案一般, 只是 static library 裝的是 binary 並且可以加入 index 檔。也因此 static library 通常比 shared library 大包, 因為 static library 不管 (也無法知道) 每個 symbol 最後是否會被用到, 總之就先留著它。

shared library 多了不少功能可用, 其中一個實用的功能是 visibility。可透過編譯 (非連結) 時下參數隱藏不需要的 symbol。以上面的例子來說, 若 A 只用到 X 且不會用到 Y, 那麼, 改用 shared library 的 visibility, 可以避開 B 和 Y 衝突的 symbol, 作法如下所述。

為簡化描述, 以下用單一檔案表示一個專案:

1. 產生 libXY.so

$ g++ -c Y.cpp -fPIC -fvisibility=hidden
$ g++ -c X.cpp -fPIC
$ g++ -shared -o libXY.so X.o Y.o

注意: 編譯 Y.cpp 時多了 -fvisibility=hidden, 表示除非程式內有用 g++ 特有的語法指定 visibility (__attribute__((__visibility__("default"))), 不然預設行為變成:

  • 無論原本 C++ 語意的 scope 為何, shared library 外看不見此 object file 的 symbol
  • shared library 內仍採用 C++ 的語意

對部份平台來說, -fPIC 是編譯 shared library 的必要條件, 先當作編譯 shared library 時需要在編譯時加上 -fPIC 吧。

2. 產生 libB.a

$ g++ -c B.cpp
$ ar rvs libB.a B.o

shared 或 static library 在此無關緊要

3. 產生執行檔 A

$ g++ -c A.cpp
$ g++ A.o libB.a libXY.so -o A

備註:

2011-11-24 更新

Scott 補充有關 PIC 的說明, 我就直接備忘在這裡啦。

相關 key word: "text relocation" [1] "text" 在此指機械碼,跟可執行檔中擺機械碼的 section 稱為 .text section 一樣。"relocation" 指『有參考到某 symbol,故連結器需一併修改的地方』。有加 *-fPIC*,compiler 產出的機械碼就不會有 text relocation,dynamic linker 才願意在執行時載入。

在幾乎全部平台上, -fPIC 或 -fpic 都是必要的。10 年前 i386 Linux 上不加也可以,但現在連 i386 預設也會被 security policy 拒絕。*elfutils* 中有個 eu-findtexrel 可找出 shard library 中沒加 "-fPIC" 編譯的 .o。

運作原理上,若沒加 -fPIC ,compiler 產出的機械碼中每個用到全域變數與函式的地方都會嵌有該 symbol 的位址。即 shared library .text section 中會散佈很多 memory reference。但那些位址在執行期間需被 dynamic linker 修改。加了 -fPIC ,產生出來的機械碼 reference 全域變數與函式的方法就不一樣了 [2]。

在安全性上不希望一頁記憶體既可寫又可執行;從多 process 共用 shared library .text section 的 physical memory pages 角度來看,希望 .text section 保持不變且 read only。 所以後來 dynamic linker 遇到有 text relocation 的 shared library 就拒絕載入了。

[1]: http://www.akkadia.org/drepper/textrelocs.html [2]: http://www.iecc.com/linker/linker10.html

GNU Makefile 雜項語法備忘

一般的 tutorial 教得都差不多卻少了一些我想知道的語法, 以下是自己備忘用的語法, 對於讀別人的 Makefile 時有幫助

$ cat Makefile
var ?= xxx         # assign var = xxx if var is not assigned
.PHONY: all        # tell make that "all" is not a file
all:               # first target is the default target
    @echo make-all # @ means do not display the cmd
    @echo all: x $(x)

all: b             # all depends on b

b:
    @echo make-b   # must use TAB to indent actions
    @echo b: var $(var)


all: a             # now all depends on a and b

a: x:= 3           # set x = 3 only in this context
a:
    @echo make-a
    @echo a: x $(x)

reverse = $(2) $(1)  # define a function
c:
    @echo c: x y
    @echo c: $(call reverse, x, y)  # use "call" to use
                                    # defined functions

範例輸出

$ make
make-a
a: x 3
make-b
b: var xxx
make-all
all: x
$ var=ooo make b  # override var
make-b
b: var ooo
$ x=9 make  # set x = 9 globally
make-a
a: x 3   # note that x is still 3
make-b
b: var xxx
make-all
all: x 9
$ make c
c: x y
c: y x

注意 Makefile 自己有套變數, 想改變 g++ 的參數時, Makefile 必須寫成傳遞變數作為 g++ 參數, 比方說慣例上會用 CFLAGS, CXXFLAGS, LDFLAGS 之類的。若 Makefile 沒這麼定, 想改變 g++ 用的參數時, 就只能直接修改 Makefile 了。

2012-11-25 更新

Scott 提醒, 補上有用的參考資料:

2014-01-20 更新

更新連結, 還有補上 Scott 在留言裡補充的說明:

用『=』定義的變數像是建立一個數學關係式,每次用到該變數時會重新解譯、計算出新的值。

2012年11月15日 星期四

編譯或連結錯誤的檢錯流程 (初版)

開發環境是 Ubuntu。先寫篇草稿, 日後慢慢補完。

找不到 xxx.h

檢查是否有 xxx.h

$ sudo updatedb && locate xxx.h

若 OS 內沒有的話, 看看要裝什麼套件才有

$ sudo apt-file update && apt-file search xxx.h

確定有檔案後, 檢查使用 libxxx 需要用的編譯參數為何

$ pkg-config --cflags xxx

若不確定 pkg-config 參數的名稱, 使用 apt-file search 查到的 package 名稱 "PKG-X", 查詢 PKG-X 包含的檔案

$ dpkg -L PKG-X | grep pkgconfig

比對編譯時用的參數, 是否有含到正確的 include path (參數 -I), 沒有的話, 可能是 makefile 出錯, 檢查產生 makefile 的設定檔是否正確

若 OS 內沒有 xxx.h, 也沒有任何一個套件含有 xxx.h, 可能目前 OS 太舊, 用 Ubuntu Packages Search 查詢 xxx.h, 確認是否新版的 OS 才有 xxx.h

相關文章

2012年1月18日 星期三

自行編譯含 debug symbol 的套件 (package)

對函式庫 X 來說

  • X-dev 表示讓開發者用的, 有裝 header、文件之類的
  • X-dbg 內容類似 X, 不過有留 debug symbol

比方說 libjpeg, 三種的描述如下:

  • libjpeg62 - The Independent JPEG Group's JPEG runtime library
  • libjpeg62-dbg - Development files for the IJG JPEG library
  • libjpeg62-dev - Development files for the IJG JPEG library

裝了 X-dbg 後什麼事也不用做, gdb 自己會優先用含 debug symbol 的版本。不過若官方沒提供供 X-dbg 的話, 就得自己編。參考官方文件《HowToGetABacktrace - Debian Wiki》, 做法如下:

  • $ apt-get install build-essential fakeroot gdb
  • $ apt-get build-dep X
  • $ DEB_BUILD_OPTIONS="nostrip noopt" fakeroot apt-get -b source X
  • $ dpkg -i X.deb

針對上述指令, 補充幾點

  • 編好套件後, 可用 dpkg -c X.deb 先看裡面裝了什麼
  • 用 objdump --source FILE 查看裡面有沒有含程式碼, 有的話才表示確實有含 debug symbol。這個作法比用 file FILE 看是否有 strip 更確實。二進位檔有可能沒含 debug symbol 也沒 strip。

2016/02/16 更新

遇到 "trying to overwrite ..." 的錯誤 時,可以用 sudo dpkg -i --force-overwrite X.deb 強制安裝。

2011年12月19日 星期一

解決 undefined symbol / reference

C++ 新手上路, 有錯還請幫忙指正。

基本觀念

相較於 script language 或 Java 來說, C/C++ 有完整的「編譯 -> 連結 -> 執行」三個階段, 各階段都可能發生 undefined symbol。在解決惱人的 undefined symbol 前, 得先明白整個編譯流程:

  1. 編譯 .c / .cpp 為 .o (object file) 時, 需要提供 header 檔 (用到 gcc 參數 -I)。事實上, 在編譯單一檔案時, gcc/g++ 根本不在意真正的 symbol 是否存在, 反正有宣告它就信了, 所以有引對 header 即可。這也是可分散編譯的原因 (如 distcc ), 程式之間在編譯成 .o 檔時, 並沒有相依性。
  2. 用 linker (ld 或 gold) 將 *.o 連結成 dynamic library 或執行檔時, 需要提供要連結的 library (用到 gcc 參數 -L 指定目錄位置, 用 -l 指定要連什麼函式庫)。不同於前一步, 此時 symbol 一定要在。
  3. 執行的時候, 會再動態開啟 shared library 讀出 symbol。換句話說, 前一個步驟只是檢查是否有。檢查通過也連結成 executable 或 shared library 後, 若執行時對應的檔案不見了, 仍會在執行期間找不到 symbol。若位置沒設好, 可能需要用 LIB_LIBRARY_PATH 指定動態函式的位置, 但不建議這麼做, 最好在執行 linker 時就指定好位置。原因見《Why LD_LIBRARY_PATH is bad》

明白這點後, 就看 undefined symbol 發生在那個階段, 若是編 object file 時發生, 就是沒和編譯器說 header 檔在那, 記得用 -I 告訴它。若在 linking 時發生, 就要同時設好 -L 和 -l。不過難就難在要去那找 undefined symbol 的出處。

解決問題的流程

首先是判斷 symbol 是不是自己用到的原始碼裡, 可配合 id-utils 找看看 (我是用 gj, 比較方便一點)。或是看有沒有 man page, 有 man page 的話, 裡面會記錄用到的 header 和該怎麼下連結參數。若在專案裡找不到, 再用 Google 搜看看 symbol, 運氣好可能會找到套件名稱, 運氣不好.....目前還不知怎麼處理較好, 目前是四處亂翻看看。如果是網路上找來的程式碼, 別人已附好正確的 include 了, 這時用 apt-file search HEADER_PATH 就能找到套件名稱 ( 記得先跑 apt-file update 更新資料庫 ), 比方說: apt-file search openssl/rsa.h 會得到 libssl-dev: /usr/include/openssl/rsa.h。

在 Ubuntu 上, 通常需要裝 X-dev 以取得 header 檔。若是已經裝好套件了, 可用 dpkg --searchlocate 或是 dpkg -L PKG_NAME 找出 header 位置。

若編譯過但 linking 時出錯, 要做進一步分析, 先看是那一個程式用到 undefined symbol。不管是自己的程式出錯, 或是用到的函式庫出錯, 都可從對應的原始碼找到編譯時用的 header X.h。

  • 先看有沒有 man page, 有的話, 裡面會寫該下什麼參數連結。像 man sqrt 會看到說要 "Link with -lm" (記得裝 manpages-dev)
  • 若 X.h 是自己的, 就在附近找看看原始碼在那, 有沒有編譯到。
  • 若 X.h 放在系統目錄裡, 可用 apt-file search X.h 找出 library 的可能出處 ( 記得先跑 apt-file update 更新資料庫 )。接著可用下列方式之一找出函式庫的可能位置:
    • dpkg --search SUBSTRING_OF_LIBRARY_NAME
    • dpkg -L PKG_NAME | grep lib
    • locate SUBSTRING_OF_LIBRARY_NAME # 記得先跑 updatedb

若知道函式庫的確切名稱, 且有 pkg-config 的資訊的話, 可用 pkg-config --libs LIBRARY_NAME 直接找出 gcc/g++ linking 時該下的參數 (附帶一提, 用 --cflags 找出編譯時用到的參數, 像是 -I 接的)。不然, 用其它方式找到函式庫位置後, 要依 -L-l 的規則寫下參數。記得 -l 後接的名稱不用加 "lib", 像 libm.so 是用 -lm。

實際寫較具規模的專案時, 可能不會用手刻 makefile, 要視自己用的整合工具, 將找到的資訊加入整合工具中。

其它相關資訊

  • 可配合 nm LIBRARY 查看 symbol, man nm 有各狀態說明, U 表示 undefined。若該函式應該要出自該函式庫, 卻標為 U, 表示該函式庫一開始就沒編好, 要重編該函式庫。反之, 若該函式定義在外部函式庫, 則是連結時出錯。
  • nm 只適用 static library 或未 strip 前的 shared library。strip 後的 shared lib 得用 readelf -Ws 來看, 這個情境下沒 nm 簡單易讀。(2014-10-27 更新: 也可用 nm -D)
  • 函式庫有 U 通常是正常的, 編執行檔或 dynamic library 時才要指定連結的位置。換句話說, 若執行檔 X 用到 static library A, 而 A 用到 library B。則編 X 時, 要加上 -lA 和 -lB 的參數。編 X 的部份要知道它用到的函式庫有那些相依性, 而不是 A 自己會搞定自己的相依性, 這點不太直覺 (ref.)。
  • static library 只是一堆 object file 的集合體。之所以會用 ar 和 ranlib 編 static library, 目的是減少連結的檔案以方便管理。在用 readelf -Ws 讀 static library 時, 會列出各個 object file 的內容。讀 dynamic library 時就沒這樣列了 (ref.)。
  • 在 Linux 下 linking 時要注意函式庫的順序, 摘錄 gcc manpage 關於 -l 的說明:
    It makes a difference where in the command you write this option; the linker searches and processes libraries and object files in the order they are specified. Thus, foo.o -lz bar.o searches library z after file foo.o but before bar.o. If bar.o refers to functions in z, those functions may not be loaded.
  • 當 libm.so 和 libm.a 同時存在時, -lm 會連到 libm.so, 官方說明見 man ld--library=namespec 該段 (ref.)。感謝 cmtsij 的說明。
  • 可用 ldd 找出 dynamic library 實際連到的檔案。

參考資料

在 Fedora 下裝 id-utils

Fedora 似乎因為執行檔撞名,而沒有提供 id-utils 的套件 ,但這是使用 gj 的必要套件,只好自己編。從官網抓好 tarball ,解開來編譯 (./configure && make)就是了。 但編譯後會遇到錯誤: ./stdio.h:10...