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

2015年8月8日 星期六

gold 和 GNU ld 行為差異造成的問題

原因

ToolChain/DSOLinking - Debian Wiki 介紹得很清楚, 主要的差異是處理 DT_NEEDED section 的行為不同。

對 program A 和 shared lib B 和 C 來說, 假設相依性如下:

  • A 用到 B 和 C
  • B 用到 C

照理說 link 的設定要如實反應出相依性。若 link 的設定如下:

  • A: link B
  • B: link C

linker 是 GNU ld (BFD-based linker) 的時候會成功, 但用 gold 的時候會失敗。原因是 linker 會在目標的 DT_NEEDED section 記錄用到的 shared lib, 然後 GNU ld 預設會將 DT_NEEDED section 的值一併「往上傳」。所以 A link B 的時候不只會在 DT_NEEDED section 加上 B 的記錄, 還會取得 B 的 DT_NEEDED section 內的值, 所以沒寫 link C 也會有 C 的記錄。

這造成一個問題: 日後 B 沒有用到 C 的時候, 會因為升級 B 而造成 A 的 link error, 錯誤原因是沒有 link C。

解法是用 GNU ld link 時也要下 --no-add-needed 避免自動取用 shared lib 內的 DT_NEEDED section 的內容。這樣開發的時候才會注意到漏加 shared lib。

這種情況容易發生嗎? 就我個人的經驗來說, 比我預期的容易。比方說寫 GTK+ 也會用到 glib, 但 GTK+ 自己也有用 glib, 所以 GTK+ 的 DT_NEEDED section 有含 glib, 然後自己就忘了加。若用 GNU ld link, 沒加 glib 也會成功。或是很多 lib 用到 pthread, 自己也用到或加入的 static lib 間接用到, 然後忘了 link pthread。

改用 gold 為預設 linker

gold link 的行為不但比較正確,速度比較快也比較省記憶體。可以的話, 改用 gold 作為系統預設 linker 比較省事。Mac OS X 已經用 gold 了, Ubuntu 12.04 要另裝 binutils-gold:

$ sudo apt-get install binutils-gold

然後檢查看看:

$ ls -l /usr/bin/ld
lrwxrwxrwx 1 root root 7 Apr  2 00:05 /usr/bin/ld -> ld.gold*

安裝前會是連到 GNU ld, 像這樣:

$ ls -l /usr/bin/ld
lrwxrwxrwx 1 root root 7 Apr  2 00:02 /usr/bin/ld -> ld.bfd*

若沒有成功, 可能要用 update-alternatives 切換預設 linker (Ubuntu 14.04 好像需要), 詳見這裡

其它

1. GNU ld 預設會加入所有指定要 link 的 shared lib 到 DT_NEEDED section, 即使沒有用到那些 lib 也一樣。若希望只加入用到的 lib, 要加上參數 --as-needed

2. 可以用 ldd 列出 executable 或 shared lib 相依的 lib, 像這樣:

$ ldd /bin/ls
        linux-vdso.so.1 =>  (0x00007fffe2351000)
        libselinux.so.1 => /lib/x86_64-linux-gnu/libselinux.so.1 (0x00007ff642698000)
        librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1 (0x00007ff642490000)
        libacl.so.1 => /lib/x86_64-linux-gnu/libacl.so.1 (0x00007ff642287000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ff641ec8000)
        libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007ff641cc4000)
        /lib64/ld-linux-x86-64.so.2 (0x00007ff6428ce000)
        libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007ff641aa6000)

2013年5月3日 星期五

使用 --start-group 和 --end-group 解決 circular dependencies

只是想備忘這篇: GCC: --start-group and --end-group command line options - Stack Overflow

關鍵的 gcc 指令:

-Wl,--start-group -lmy_lib -lyour_lib -lhis_lib -Wl,--end-group -ltheir_lib
  • gcc man page 說明 -Wl,X 傳遞參數 X 給 linker
  • ld man page 說明 --start-group archives --end-group

這個作法的好處是不用理會 static library 在參數裡的順序 (預設是假設 A 依賴 B 時, A 要放在 B 前面), 壞處是會加長 linking 時間。

Btw, 遇到和 linking 相關的問題時, 記得要同時從 compiler 和 linker 的角度看問題。

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

2012年11月17日 星期六

以使用 libsqlite 為例說明如何找到程式的進入點

一直對於如何善用 runtime 資訊找到程式進入點很有興趣, 終於有個不錯的小例子。

目標

假設要觀察的專案有用到 sqlite 儲存資料, 從使用方式知道一開始會先載入 sqlite 的內容, 現在想找到程式讀取第一筆資料的進入點。

第一步先觀察程式如何使用 sqlite, 是用 shared lib 或是直接包含在程式裡:

$ ldd PROG | grep sqlite

Case 1: 沒有結果, 表示 sqlite 的實作直接含在 PROG 裡面

找出和 sqlite 相關的 API:

$ nm PROG | grep sqlite | awk '{print $NF}' | xargs c++filt

或是查官網文件也成, 不過個人覺得 nm + c++filt 這招比較方便也比較酷。

觀察一下後, 得知開檔的 API 有: sqlite3_open, sqlite3_open16, sqlite3_open_v2, 之後用 cgdb 執行程式, 都設中斷點, 就結案了。

Case 2: 有 grep 結果, 表示在 shared lib 裡

若不想看官方文件, 也想來個動態搜集 API, 可用 ltrace:

$ ltrace -l /usr/lib/x86_64-linux-gnu/libsqlite3.so.0 PROG

其中 libsqlite3 的路徑是從 ldd PROG 得知的。

安裝需要的 debug symbol, 以做進一步觀察:

$ aptitude search sqlite | grep dbg # 找到 libsqlite3-0-dbg
$ sudo aptitude install libsqlite3-0-dbg

再來就用 cgdb 執行程式, 準備設中斷點, 結案。

相關參考資料:

以 abstract class 為例說明 C++ 編譯器和連結器的運作

ps. 經 Scott 提醒發覺內容有誤, 已更新內文。

Effective C++ item 7 提到, 若想使用 abstract class A, 建議這麼做:

// a.h
class A {
public:
  virtual ~A() = 0; // 注意只有宣告沒有實作, 是 pure virtual
};

但是還是得另外提供 ~A() 的實作:

// a.cpp
#include "a.h"
A::~A() {}

不然連結時會出錯

以 class B 繼承 A 為例, 假設程式如下:

// b.h
#include "a.h"
class B : public A {
};

// b.cpp
#include "b.h"
// Nothing.

先來看少了 a.cpp 的情況。由於編譯時只會看 header, 編譯器會阻止任何嘗試生成 A 的程式, 這是我們想要的好結果, 利用宣告含有至少一個 pure virtual 函式的作法, 得到 abstract class 的性質。用 destructor 是不錯的選擇, 不會因此增加不必要的 virtual method, 況且 「base class + 使用多型」的情況要有 virtual destructor, 和使用 abstract class 的目的相合。

然後, 來看編譯 b.cpp 得到的 b.o。不論是否有呼叫到 B() 或 ~B(), 編譯器都會產生 B() 和 ~B() 的 binary code。編譯器對照 a.h 和 b.h 覺得 access level 沒有問題, 不會有編譯錯誤, 其中 ~B() 會呼叫 ~A(), ~A() 的 symbol 還未定義, 等待連結時補上。

但是, 若少了在 a.cpp 內 ~A() 的實作的話, 連結時會發覺找不到 ~A() 的 symbol, 於是有 link error。就語法來說這結果挺怪異的, 宣告為 pure virtual 卻又得提供實作才行。反之, 從實作面來看 compiler 和 linker 怎麼運作, 就不會覺得奇怪。又一次讓我覺得要理解 C++ 的語法, 得從運作的方式來理解才行。只看語法的話, 不太容易理解和記憶。

關聯文章:

2012年1月29日 星期日

使用 RTLD_NEXT 實作 wrapper

先貼程式:

// RLTD_NEXT is only supported in _GNU_SOURCE.
#define _GNU_SOURCE

#include <stdio.h>
#include <dlfcn.h>

void *malloc(size_t size) {
    void* (*m)(size_t);
    // 「標準」取得 function pointer 的寫法. 見 TLPI 42.1.2 p863 的說明
    *(void**)(&m) = dlsym(RTLD_NEXT, "malloc");
    printf("local malloc\n");
    return m(size);
}

int main(void) {
    int *t = malloc(sizeof(int));
    *t = 3;
    printf("%d\n", *t);
    return 0;
}

編譯和執行結果:

$ gcc m.c -ldl -o m
$ nm m | grep malloc
0000000000400604 T malloc
$ ./m
local malloc
3

若去掉 malloc(), 加上 #include stdlib.h, 用 nm 看則是:

$ nm m | grep malloc
                 U malloc@@GLIBC_2.2.5

表示在 m 裡面沒有定義 malloc, 它存在 glibc 裡。malloc 後面的 "@@GLIBC_2.2.5" 是 version tag。當 shared library 裡有多個版本 malloc 時, 可用 version tag 來區別要用的是那一個 malloc, 詳情見 TLPI 42.3.2。

這裡的關鍵是 glibc 定義了擴充功能 — 假的 library handle: RTLD_DEFAULT 和 RTLD_NEXT。用前者取函式 (變數) 就和原本 ld.so 找 symbol 的方式一樣; 而後者則會找「下一個」, 這是針對實作 wrapper 的需求而定的。

由於是 glibc 的擴充, 編譯時要在開頭加上 #define _GNU_SOURCE 或在命令列加 -D_GNU_SOURCE, dlfcn.h 才會載入 RTLD_NEXT。關於 _GNU_SOURCE 的說明, 見《讀懂函式庫的 man page》

ld, ld.so 和 ldconfig 的行為

TLPI ch41 相當值得一看, 從開發者使用 library 的角度說明 library 的生成、靜態連結、動態連結 (載入) 的行為, 內容不多不少, 正好就是我想知道的, 省了看 linker、loader 的時間。

shared library 的名詞介紹

  • soname: 記錄在 shared library header 裡的名稱, 格式為 libX.so.MAJOR。要有同名檔案, 供之後程式載入 shared library 時使用
  • real name: shared library 的檔名, 格式為 libX.so.MAJOR.MINOR.NUMBER
  • linker name: 對 library X 來說, 就是 libX.so, 一般會是 symbolic link 指向最新的 major shared library

以 libjpeg 為例, 對應如下:

libjpeg.so -> libjpeg.so.62.0.0     # linker name
libjpeg.so.62 -> libjpeg.so.62.0.0  # soname
libjpeg.so.62.0.0                   # real name

這是我在 Ubuntu 裝好 package 後的樣子, 照理說 libjpeg.so 指向 libjpeg.so.62 應該會更彈性。

讀出 soname:

$ readelf -d libjpeg.so | grep SONAME
 0x000000000000000e (SONAME)             Library soname: [libjpeg.so.62]

static 和 dynamic linker

  • ld (ld.bfd) 是 static linker。Google 開發的 gold 是取代 ld.bfd 的 static linker。用 gcc 連結 shared library 或 executable 時就是呼叫 ld, 並將需要的參數傳給它。不論連結的是 static library 或 shared library, 都是 static linking。
  • ld 在連結 shared library 或 executable 時, 會將需要的 shared library 的 soname 寫入結果檔裡。注意, 只有 soname 而已, 沒有完整路徑。
  • ld-VERSION.so 是 dynamic (runtime) linker, 執行程式時, 由 runtime linker 載入 executable 開始。若 OS 用的 glibc 版本為 2.13, 就叫 ld-2.13.so。用 ldd 看所有執行檔, 都會找到它 (某個 symbolic link 連到 ld-2.13.so)。

以連結 libm.so 為例, 執行 gcc -lm prog.c -o prog 中間的部份行為如下:

  1. gcc 透過 -lm 的指示告知 ld 要連結 libm.so
  2. ld 會找到某處的 libm.so 指向 /lib/x86_64-linux-gnu/libm.so.6, 確認要用到的 symbol 都有, 沒有 link error
  3. ld 從 libm.so.6 的 header 讀出 soname "libm.so.6", 寫入 "libm.so.6" 到 prog 的 header。

執行 prog 時, ld-2.13.so 會從 prog 讀出 "libm.so.6", 再到預設的路徑上找檔名 "libm.so.6"。注意, static linking 時需要 libm.so, 但之後執行 prog 時用不到它, 因為記錄的 soname 為 "libm.so.6"。

關於 static linking 找檔名的順序, 可用 strace 觀察:

$ strace -e open,execve -f -o gcc.trace gcc -lm prog.c -o prog

在 gcc.trace 裡可看出一二。

ps.

  • 使用 execve 的目的是知道 child process 是那一個程式, 目標是看 ld 開敋的檔案
  • 可由 man 2 exec<TAB> 得知 system call 使用的 exec 函式為 execve。

ldconfig

執行 ldconfig 後, 它做的事如下:

  1. 讀出 /lib, /usr/lib, /etc/ld.so.conf 內的路徑之下的 shared library (ldconfig 會略過 symbolic link), 將結果寫入 /etc/ld.so.cache。之後 ld-2.13.so 會用 ld.so.cache 的記錄來找 shared library。
  2. ldconfig 會自動產生 symbolic link "libX.so.MAJOR" 指向最新版本的 shared library。例如 /lib/libfoo.so.2.0.1 的 soname 是 libbar.so.2, 執行 ldconfig 後, 它會產生 /lib/libbar.so.2 指向 /lib/libfoo.so.2.0.1。

之前困擾我許久的事就是第二步, 而 man ldconfig 裡沒提到這點。

結論是別隨便手動更新 soname 的檔案, 執行 ldconfig 後可能會出問題。裝套件後, 系統工具會自動跑 ldconfig 更新目錄, 可能會蓋掉自己手動更新的同檔名檔案。另外 ldconfig 沒有管 linker name, 若是自己編的 shared library, 要自己產生。

其它

若想連到舊的 major 版本 shared library, 得在 gcc 參數指定舊版檔名。還有可用 rpath 的參數寫入搜尋 shared library 的路徑到 shared library 或 executable 裡。關於這些細節, 還有 static linker 以及 dynamic linker 尋找 shared library 的完整順序, TLPI ch41 講得相當清楚。ch42 描述 dlopen, 之後再來翻翻。

2015-09-05 更新

加上 Scott 補充關於 ld-VERSION.so 和相關工具的說明:

fcamel: ld-2.21.so, ldd, ldconfig 屬於 glibc 是什麼意思啊? 我以為 glibc 是 standard C + POSIX lib

Scott: Linux 下動態連結器是 C Library 的一部分,例如 Android 不用 GLIBC 所以其 Dynamic Linker (/system/bin/linker64) 是 Google 自己寫出來、自己維護的 https://github.com/android/platform_bionic/blob/master/linker/Android.mk

這個知識可用來解釋 Android 動態連結器有許多「特異功能」,例如有註冊 SIGSEGV handler,所以 native code 記憶體存取錯誤會有 backtrace 等。 https://github.com/android/platform_bionic/blob/master/linker/debugger.cpp

至於動態連結器為何是 C Library 的一部分,從「實作 C library 的人」的角度看:凡不是 Kernel,但又是系統「執行期間」需要的低階 Library,就擺在 C Library 內了。

ldd 與 ldconfig 是 GLIBC 作者寫出 ld-2.21.so 動態連結器時「順便」寫的工具,是隨 GLIBC 一起安裝的。系統不用 GLIBC 的話,可能沒有 ldd 與 ldconfig。

2011年12月24日 星期六

加速 linking time

頻繁修改一點程式又要執行看結果時, 主要的時間都花在 linking。這時才體會到省下 linking time 也是很重要的事。

這篇提到兩個減少 linking time 的做法

  • 使用 ramdisk
  • gold

我本來就有用 SSD, 改將編譯結果全放到 ramdisk 後, 提昇的效果不怎麼明顯。到是用 gold 後減少了一半的 linking 時間。

不過 gold 也不是那麼完美, 這篇提到一些問題。我自己用的時候發覺滿常遇到 ld 可以編, 但 gold 不行。最後的解套方式是寫個小 script 切換 /usr/bin/ld 連到的程式。不常編的東西就暫時換回 ld 連結個一次就好。

另外 jserv 提到 gcc 有命令列參數可直接指定用那個 linker (ref.)。wens 則查到 Debian/Ubuntu changelog 裡有寫 下可用 -B/usr/lib/compat-ld-B/usr/lib/gold-ld for ld.gold

順便 google 一下看怎麼查 changelog, 似乎是看 /usr/share/doc/binutils/changelog.Debian.gz 或用 aptitude changelog PKG, 不過這兩個作法都只有最近的 changelog, 沒有完整記錄, 像查 binutils 時就沒有找到上面提的參數說明。

2011年12月21日 星期三

列出用到的 shared library

本文整理大家的建議和自己實際操作的心得。新手上路, 有錯還請指正。

static library 就是一包 object file, 沒什麼需要提的, static library 沒有記錄其它資訊。所以, 編 shared library 或 executable 時要自行處理好 static library 的相依性, 在前篇有提到一點資訊。

shared library 有兩種, 一種在 linking 時要指定好 shared library 用到的 undefined symbol 放在那些 shared library 裡, 待執行時再載入到記憶體使用; 另一種用 dlopen() 和 dlsym() 載入 (這兩個函式存在 libdl 內)。

前者比較單純, 可用 ldd 透過靜態分析了解用到那些 shared library, 且各自實際指到的檔案。ldd 本身是一個 shell script, 用到 ld.so 事先定義的一些機制 (LD_TRACE_LOADED_OBJECTS) 來讀取資料, man ld.so 裡有相關的說明。其中 LD_LIBRARY_PATH 和 LD_PRELOAD 相當實用, 無法在連結時解決問題時, 至少還有這招可在載入時處理。

若想分析透過 dlopen() 載入的動態函式庫, 有幾個做法

  • 在程式執行中觀察 /proc/PID/maps, 這個檔案記錄 process 用到的各區段記憶體為何, 可從對應到的檔案看出有載入的 shared library。必要時可配合 gdb 在想觀察的部份停住, 再從外部看 /proc/PID/maps這裡man proc 有相關說明。
  • strace 執行程式, 觀察開啟的檔案: strace -f -e open PROGRAM 2>&1 | grep "\.so"

就我自己小試的心得, 看 /proc/PID/maps 最穩, 且方便看各別 process、thread 載入的函式庫, 也不會拖慢觀察目標的執行程式。不過 strace 不需配合 gdb 停在該停的地方, 就「快篩」的角度來看, 也滿有用的, 加上 -f 後方便追蹤 multi-process、multi-thread, 不過執行速度好像有慢一些, 不太確定。之後再比較看看兩者適合的使用時機。

參考資料

2011-12-22 更新

  • 依 wens 和 Scott 的留言更新上面 strace 的例子。
  • strace -e 的參數看來頗有用的, 之後有需求時再來研究。

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...