2012年1月29日 星期日

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。

5 則留言:

  1. soname 要在link的時候自己指定!
    除非你用的是libtool, 他就自己幫你處理了,
    但你要給他major, minor version

    回覆刪除
  2. 1. 有一處 ld.bfd 誤拼成 "ld.bfs" 了

    2. 可加一節總結,註明靜態連結器 ld{,.bfd,.gold} 屬於 binutils,而動態連結器 /lib64/ld-2.21.so 與相關的 ldd、ldconfig 等屬於 glibc。 Android 不用 glibc 所以後者不同。

    3. C library 不是用 glibc 所以沒有 ldd 可用時,用 "readelf -d XXX.so | grep NEEDED" 取代

    回覆刪除
    回覆
    1. 1. 修正 ld.bfs 了

      2. ld-2.21.so, ldd, ldconfig 屬於 glibc 是什麼意思啊? 我以為 glibc 是 standard C lib + POSIX C lib (ref. http://stackoverflow.com/a/11461093/278456 )

      怎麼會忽然想到要來回這篇啊?

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

      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。

      > 怎麼會忽然想到要來回這篇啊?

      FB 上有位朋友貼你這篇,我就重讀了一次。

      刪除
    3. 原來如此, 難怪 Mac OS X 下沒有這些東西, 用起來不太習慣, 又懶得再學一套 Mac OS X 怎麼作這些事

      我直接複製貼上你的留言到內文裡好了 XD

      刪除

在 Fedora 下裝 id-utils

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