歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Unix知識 >> 關於Unix

Linux 動態函式庫解析

摘要 動態連結 VS 靜態聯結 By Wing 動態連結 VS 靜態聯結 在 Linux 中,執行檔我們可以編程成靜態聯結以及動態連結,以下我們舉一個簡短的程序作為例子: #include int main() printf( test); 若我們執行 : [root@hlchou /root]# gcc test.c -o test 所產生 摘要

動態連結 VS 靜態聯結

By Wing

動態連結 VS 靜態聯結

  在 Linux 中,執行檔我們可以編程成靜態聯結以及動態連結,以下我們舉一個簡短的程序作為例子:

#include  int main() { printf(" test"); } 

  若我們執行 :

[root@hlchou /root]# gcc test.c -o test 

  所產生出來的執行檔 test,預設為使用動態函式庫,所以我們可以用以下的指令 :

[root@hlchou /root]# ldd test libc.so.6 => /lib/libc.so.6 (0x40016000) /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000) 

  來得知目前該執行檔共用了哪些動態函式庫,以我們所舉的 test 執行檔來說,共用了兩個動態函式庫,分別為 libc.so.6 與 ld-linux.so.2。我們還可以透過下面的 file 指令,來得知該執行檔的相關屬性,如下

[root@hlchou /root]# file test test: ELF 32-bit LSB executable, Intel 80386, version 1, dynamically linked (use s shared libs), not stripped 

  not stripped 表示這個執行檔還沒有透過 strip 指令來把執行時用不到的符號、以及相關除錯的資訊刪除,舉個例子來說,目前這個test 執行檔大小約為 11694 bytes

[root@hlchou /root]# ls -l test -rwxr-xr-x 1 root root 11694 Oct 24 02:31 test 

  經過strip後,則變為 3004 bytes

[root@hlchou /root]# strip test [root@hlchou /root]# ls -l test -rwxr-xr-x 1 root root 3004 Oct 24 02:48 test 

  不過讀者必須注意到一點,經過 strip 過的執行檔,就無法透過其它的除錯軟件從裡面取得函式在編程時所附的相關資訊,這些資訊對我們在除錯軟件時,可以提供不少的幫助,各位在應用上請自行注意。

  相對於編程出來使用動態函式庫的執行檔 test,我們也可以做出靜態聯結的執行檔 test

[root@hlchou /root]# gcc -static test.c -o test 

  透過指令 ldd,我們可以確定執行檔 test 並沒有使用到動態函式庫

[root@hlchou /root]# ldd test not a dynamic executable 

  再透過指令 file,可以注意到 test 目前為 statically linked,且亦尚未經過 strip

[root@hlchou /root]# file test test: ELF 32-bit LSB executable, Intel 80386, version 1, statically linked, not stripped 

  相信大夥都會好奇,使用靜態聯結,且又沒有經過 strip 刪去不必要的符號的執行檔的大小會是多少,透過 ls -l來看,我們發現大小變成 932358 bytes 比起靜態聯結的執行檔大了相當多

[root@hlchou /root]# ls -l test -rwxr-xr-x 1 root root 932258 Oct 24 02:51 test 

  若再經過 strip,則檔案大小變為 215364 bytes

[root@hlchou /root]# strip test [root@hlchou /root]# ls -l test -rwxr-xr-x 1 root root 215364 Oct 24 02:55 test 

  與使用動態函式庫的執行檔 test 比較起來,大了約 70倍 (215364/3004)。因此,整體來說,在使用的環境中使用動態函式庫並且經過 strip 處理的話,可以讓整體的空間較為精簡。許多執行檔都會用到同一組的函式庫,像 libc 中的函式是每個執行檔都會使用到的,若是使用動態函式庫,則可以盡量減少同樣的函式庫內容重復存在系統中,進而達到節省空間的目的。

  筆者一年前曾寫過一個可以用來刪去動態函式庫中不必要函式的工具,針對這個只用到了 printf 的程序來產生新的 libc.so 的話,我們可以得到一個精簡過的 libc.so 大小約為 219068 bytes

[root@hlchoua lib]# ls -l libc.so* -rwxr-xr-x 1 root root 219068 Nov 2 04:47 libc.so lrwxrwxrwx 1 root root 7 Nov 1 03:40 libc.so.6 -> libc.so 

  與靜態聯結的執行檔大小 215364 bytes 比較起來,若是在這個環境中使用了動態函式庫的話成本約為 3004 + 219068 =222072 bytes,不過這是只有一個執行檔的情況下,使用動態函式庫的環境會小輸給使用靜態聯結的環境,在一個基本的 Linux 環境中,如果大量的使用動態函式庫的話,像是有 2 個以上的執行檔的話,那用動態函式庫的成本就大大的降低了,像如果兩個執行檔都只用到了 printf,那靜態聯結的成本為 215364 *2 =430728 bytes,而使用動態函式庫的成本為3004 *2 + 219068=225076 bytes,兩者相差約一倍。

  很明顯的,我們可以看到動態函式庫在 Linux 環境中所發揮的妙用,它大幅的降低了整體環境的持有成本,提高了環境空間的利用率。

  ld-linux.so.2 在 RedHat 6.1 中,我們可以在 /lib 或是 /usr/lib 目錄底下找到許多系統上所安裝的動態函式庫,在文章的這個部分,筆者將把整個函式庫大略的架構作一個說明。

  其實 Linux 跟 Windows 一樣,提供了一組很基本的動態函式庫,在 Windows 上面我們知道 kernel32.dll 提供了其它動態函式庫基本的函式呼叫,而在 Linux 上面則透過 ld-linux.so.2 提供了其它動態函式庫基本的函式,在筆者電腦的 RedHat6.1 上,ld-linux.so.2 是透過 link 到 ld-2.1.2.so(這部分需視各人所使用的 glibc 版本不同而定)

-rwxr-xr-x 1 root root 368878 Jan 20 14:28 ld-2.1.2.so lrwxrwxrwx 1 root root 11 Jan 20 14:28 ld-linux.so.2 -> ld-2.1.2.so 

  ld-linux.so 是屬於 Glibc (GNU C Library) 套件的一部分,只要是使用 Glibc 動態函式庫的環境,就可以見到 ld-linux.so 的蹤影。

  接下來,我們透過指令 ldd 來驗證出各個函式庫間的階層關系,首先如下圖我們執行了 ”ldd ls”、”ldd pwd” 與 “ldd vi”,可以看出各個執行檔呼叫了哪些動態函式庫,像執行檔 ls 呼叫了 /lib/libc.so.6 (0x40016000)與 /lib/ld-linux.so.2 (0x40000000),而括號內的數字為該函式庫載入記憶體的位置,在本文的稍後,會介紹到函式庫載入時的細節,到時讀者會有更深入的了解。

  其實我們不難發現,在 Linux 上使用動態函式庫的執行檔,幾乎都會去呼叫 libc.so.6 與 ld-linux.so.2 這兩個動態函式庫,筆者過去修改 Glibc 的套件時,也了解到在 Linux 中函式庫的關系,ld-linux.so.2 算是最底層的動態函式庫,它本身為靜態聯結,主要的工作是提供基本的函式給其他的函式庫,而我們最常會呼叫的 libc.so.6 則是以 ld-linux.so.2 為基礎的一個架構完成的動態函式庫,它幾乎負責了所有我們常用的標准 C 函式庫,像是我們在 Linux 下寫的 Socket 程序,其中的connect()、bind()、send() .....之類的函式,都是由 libc.so.6 所提供的。

  也因此,libc.so.6 的大小也是相當可觀的,在 RedHat 6.1 中經過 strip 後,大小約為 1052428 bytes。

[root@hlchoua /root]# ldd /bin/ls libc.so.6 => /lib/libc.so.6 (0x40016000) /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000) [root@hlchoua /root]# ldd /bin/pwd libc.so.6 => /lib/libc.so.6 (0x40016000) /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000) [root@hlchoua /root]# ldd /bin/vi libtermcap.so.2 => /lib/libtermcap.so.2 (0x40016000) libc.so.6 => /lib/libc.so.6 (0x4001b000) /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000) 

  如下,我們透過 ldd 驗證 vi 所用到的動態函式庫 /lib/libtermcap.so.2,它本身是呼叫了 libc.so.6 的函式所組成的。

[root@hlchoua /root]# ldd /lib/libtermcap.so.2 libc.so.6 => /lib/libc.so.6 (0x40007000) /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x80000000) 

  接下來,我們依序測試了 /lib/libc.so.6 與 /lib/ld-linux.so.2

[root@hlchoua /root]# ldd /lib/libc.so.6 /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000) [root@hlchoua /root]# ldd /lib/ld-linux.so.2 statically linked 

  我們可以整理以上的結論,畫成如下的一個架構圖

  

  在這個圖中,我們可以清楚的明白 ld-linux.so.2 負責了最基礎的函式,而 libc.so.6 再根據這些基本的函式架構了完整的 C 函式庫,供其它的動態函式庫或是應用程序來呼叫。

  透過筆者所寫的一個 ELF 工具程序(注二),我們也可以清楚的看到 libc.so.6呼叫了 ld-linux.so.2 哪些函式

[root@hlchoua /root]# /I-elf /lib/libc.so.6|more ======================================================== open_target_file:/lib/libc.so.6 ==>ld-linux.so.2 __register_frame_table cfsetispeed xdr_int32_t utmpname _dl_global_scope_alloc __strcasestr hdestroy_r rename __iswctype_l __sigaddset xdr_callmsg pthread_setcancelstate xdr_union __wcstoul_internal setttyent strrchr __sysv_signal ...┅(more) 

  其實,ldd 指令為一個 shell script 的檔案,它主要是透過呼叫 ”run-time dynamic linker” 的命令,並以 LD_TRACE_LOADED_OBJECTS 為參數來秀出這些結果的。

  如下,就是我們不透過 ldd 指令直接以 eval 搭配 LD_TRACE_LOADED_OBJECTS參數來檢視 libcrypt.So.1 與 libm.so.6這兩個動態函式庫的結果。

[root@hlchoua /root]# eval LD_TRACE_LOADED_OBJECTS=1 /lib/libcrypt-2.1.2.so libc.so.6 => /lib/libc.so.6 (0x40016000) /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000) [root@hlchoua /root]# eval LD_TRACE_LOADED_OBJECTS=1 /lib/libm.so.6 libc.so.6 => /lib/libc.so.6 (0x40016000) /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000) 

Copyright © Linux教程網 All Rights Reserved