Sunday, February 24, 2008

Linux動態函式庫解析

前言
用 MS Windows 一段時間的讀者,應該都聽過動態函式庫這個名詞。在 Windows 9X/ME 或是 Windows NT/2000 中,常見到的動態函式庫為副檔名 「DLL」 (Dynamic Loading Library)的檔案。

而在 Linux 中,當然也有動態函式庫的機制存在。如此一來,所撰寫的程序便無需透過靜態連結(Static Link),而可以在編程時透過動態連結(Dynamic Link)產生我們所要的執行檔。

使用動態函式庫的好處有許多。首先,就是由於執行檔主要呼叫的函式都包含於動態函式庫中,所以檔案所佔的空間可以因而縮小。其次,當動態函式庫的函式內容有所改變時,呼叫該動態函式庫的程序,可以在最小修正甚至是不需重新編程的情況下,就可以叫用到新版本的函式庫服務。

對於發展 Embedded Linux 的業者來說,能夠儘可能減少應用程序執行環境所需空間的大小,便可以把日後成品所需的 Flash 容量降到最低,在整體成本以及所耗用的記憶體空間來說,都可以得到許多的好處,而在動態函式庫來著手所得到的效益也是相當可觀的,儘可能的刪去不必要的動 態函式庫,以及針對動態函式庫改寫來縮小或是透過工具刪去用不到的函式,都可以帶來許多的助益。

當然棉,動態函式庫的好處還不只這些,相信讀者們在文章中可以發現其它的妙用的。


檔案格式(ELF VS A.out)
首先,我們必須先確定目前所執行的 Linux Kernel 版本有開啟 ELF 與 A.out 執行檔案格式的支援(通常都會有)


Kernel support for a.out binaries (CONFIG_BINFMT_AOUT) [M/n/y/?]
Kernel support for ELF binaries (CONFIG_BINFMT_ELF) [Y/m/n/?]



舉個例子來說,若要執行 a.out 格式的執行檔時,我們必須確認 CONFIG_BINFMT_AOUT 為 Y,也就是由 Kernel 直接支援 a.out 檔案格式,或者 CONFIG_BINFMT_AOUT 為 M,也就是不把 a.out 的檔案格式支援編入 Kernel 中,改以 Module 的形式存在,一旦 Kernel 需要執行 a.out 格式的程序時,在動態的載入該 Module,來啟動具備執行 a.out 執行檔的能力。不過 a.out 執行檔的格式,是 Unix 上使用了相當久的的檔案格式,ELF 是目前較新的的檔案格式。a.out 檔案格式共有三個 Section,分別為.text, .data, 及 .bss,並還包括了一個文字表(String Table)與符號表(Symbol Table)。與ELF 檔案格式比較起來,a.out 相形之下顯得較為缺乏彈性,ELF檔案格式允許多個節區的存在,執行檔可以根據需求提供應用程序執行環境的節區,並且 ELF 檔支援了 32-bit 與 64-bit 的執行環境。其實,兩者之間還有其它規格上的不同,有興趣的讀者也可以自行找一些相關的資料來比較即可瞭解。

再來呢,我們就來討論動態函式庫的檔案格式。我們都知道在 Linux中有 a.out 與 ELF 兩種檔案的格式,其中目前我們最常見的便是 ELF 檔案格式。在 Linux 的函式庫目錄中,我們常常可以見到 「*.so」 的檔案,例如:「/lib/libc.so.6」 或是 「/lib/ld-linux.so.2」。這些便是在 Linux中所常見到的動態函式庫檔案。由下圖我們可以看到動態函式庫 libc.so.6 的 ELF Header:


libc.so.6 的 ELF Header

e_ident ->EI_MAG0:7fh
->EI_MAG1:E
->EI_MAG2:L
->EI_MAG3:F
->EI_CLASS:32-bit objects
->EI_DATA:ELFDATA2LSB
->EI_VERSION:1h
->EI_PAD:0h
->EI_NIDENT:3h

e_type: ET_DYN (Shared Obj File)

e_machine:Intel 80386
e_version:Current version
e_entry:182a8h
e_phoff:34h
e_shoff:3bbf8ch
e_flags:0h
e_ehsize:34h
e_phentsize:20h
e_phnum:5h
e_shentsize:28h
e_shnum:40h
e_shstrndx:3dh



由圖中,我們可以注意到 e_type: ET_DYN,e_type 是在ELF 檔案的格式中,用來描述目前該檔的檔案型態,我們所舉的例子為 libc.so.6 這個動態函式庫的檔案,所以 e_type 的屬性為 Shared Obj File。

當然棉,我們若再拿一個ELF執行檔來比較也是不錯的,所以如下圖


ls 的 ELF Header

e_ident ->EI_MAG0:7fh
->EI_MAG1:E
->EI_MAG2:L
->EI_MAG3:F
->EI_CLASS:32-bit objects
->EI_DATA:ELFDATA2LSB
->EI_VERSION:1h
->EI_PAD:0h
->EI_NIDENT:2h

e_type: ET_EXEC (Executable file)

e_machine:Intel 80386
e_version:Current version
e_entry:8049130h
e_phoff:34h
e_shoff:bea4h
e_flags:0h
e_ehsize:34h
e_phentsize:20h
e_phnum:6h
e_shentsize:28h
e_shnum:1ah
e_shstrndx:19h



我們可以注意到 e_type: ET_EXEC,這就是 ELF 檔中對於執行檔所定義的檔案屬性。


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


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



若我們執行 :


[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



與靜態聯結的執行檔大小

No comments: