動態庫與靜態庫的深入分析

關於動態與靜態鏈接的總結:

1)靜態庫由ar命令創建,動態庫由鏈接器創建,如果一個可執行文件只鏈接靜態庫,稱它爲靜態鏈接
2)靜態庫是目標文件的存檔,而動態庫本身就是一個目標文件,例如:gcc----->hello.o---->ar------>libhello.a與gcc----->hello.so
3)靜態庫是將部分或全部的目標代碼複製到可執行文件中,部分複製是指如果鏈接程序從存檔文件中讀取目標文件,如果只用到一個或兩個目標文件,則只會複製用到的目標文件
  動態庫不會複製任何目標代碼到可執行文件中.只會記錄有關的動態庫信息.
4)靜態鏈接的程序在運行期間不依賴於靜態庫
5)修復共享庫更容易,只需修復共享庫的錯誤,就能同時修復系統中所有使用該庫的程序,如果是靜態庫,就要重建所有使用該庫的程序.


以下有兩個小測試:
測試1)演示動態鏈接庫程序在運行時加載動態鏈接庫的過程

================================================
實驗:
/*新建程序hello.c*/
cat << EOF > hello.c
> #include <stdio.h>
> int main(){printf("Hello World\n");}
> EOF


/*新建一個空的程序empty.c*/
cat /dev/null > empty.c

/*採用共享庫的方式鏈接*/
gcc -shared -fpic -o empty.so empty.c

/*-fpic 使輸出的對象模塊是按照可重定位地址方式生成的,使生成的代碼是位置無關的,因爲重建共享目標庫需要位置無關,並且這類代碼支持大的偏移。*/
/*-share 指定生成動態庫*/

gcc -o hello hello.c empty.so

/*提示沒有找到emtpy.so動態庫*/
./hello
./hello: error while loading shared libraries: empty.so: cannot open shared object file: No such file or directory

/*指定LD_LIBRARY_PATH=.,並運行程序./hello*/
LD_LIBRARY_PATH=. ./hello
======================================================
最後可以運行了
解析:
1)程序在第一次運行失敗是因爲沒有找到動態庫文件empty.so.它是如何去找共享庫的呢.
首先跟據環境變量LD_LIBRARY_PATH查找動態鏈接庫文件empty.so,如果沒有找到empty.so,它會到ld.so.cache中尋找empty.so
最後到 /lib和/usr/lib兩個目錄下尋找
如果沒有找到empty.so,打印輸出error信息,並退出
這裏我們執行export LD_LIBRARAY_PATH=/etc,然後執行strace跟蹤執行流程:
execve("./hello", ["./hello"], [/* 38 vars */]) = 0
uname({sys="Linux", node="testdb", ...}) = 0
brk(0)                                  = 0x8361000
open("/etc/ld.so.preload", O_RDONLY)    = -1 ENOENT (No such file or directory)
open("/etc/tls/i686/mmx/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/etc/tls/i686/mmx", 0xbfff9c10) = -1 ENOENT (No such file or directory)
open("/etc/tls/i686/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/etc/tls/i686", 0xbfff9c10)     = -1 ENOENT (No such file or directory)
open("/etc/tls/mmx/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/etc/tls/mmx", 0xbfff9c10)      = -1 ENOENT (No such file or directory)
open("/etc/tls/libempty.so", O_RDONLY)  = -1 ENOENT (No such file or directory)
stat64("/etc/tls", 0xbfff9c10)          = -1 ENOENT (No such file or directory)
open("/etc/i686/mmx/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/etc/i686/mmx", 0xbfff9c10)     = -1 ENOENT (No such file or directory)
open("/etc/i686/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/etc/i686", 0xbfff9c10)         = -1 ENOENT (No such file or directory)
open("/etc/mmx/libempty.so", O_RDONLY)  = -1 ENOENT (No such file or directory)
stat64("/etc/mmx", 0xbfff9c10)          = -1 ENOENT (No such file or directory)
open("/etc/libempty.so", O_RDONLY)      = -1 ENOENT (No such file or directory)
stat64("/etc", {st_mode=S_IFDIR|0755, st_size=8192, ...}) = 0
open("/etc/ld.so.cache", O_RDONLY)      = -1 ENOENT (No such file or directory)
open("/lib/tls/i686/mmx/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/lib/tls/i686/mmx", 0xbfff9c10) = -1 ENOENT (No such file or directory)
open("/lib/tls/i686/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/lib/tls/i686", 0xbfff9c10)     = -1 ENOENT (No such file or directory)
open("/lib/tls/mmx/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/lib/tls/mmx", 0xbfff9c10)      = -1 ENOENT (No such file or directory)
open("/lib/tls/libempty.so", O_RDONLY)  = -1 ENOENT (No such file or directory)
stat64("/lib/tls", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
open("/lib/i686/mmx/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/lib/i686/mmx", 0xbfff9c10)     = -1 ENOENT (No such file or directory)
open("/lib/i686/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/lib/i686", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
open("/lib/mmx/libempty.so", O_RDONLY)  = -1 ENOENT (No such file or directory)
stat64("/lib/mmx", 0xbfff9c10)          = -1 ENOENT (No such file or directory)
open("/lib/libempty.so", O_RDONLY)      = -1 ENOENT (No such file or directory)
stat64("/lib", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
open("/usr/lib/tls/i686/mmx/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/usr/lib/tls/i686/mmx", 0xbfff9c10) = -1 ENOENT (No such file or directory)
open("/usr/lib/tls/i686/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/usr/lib/tls/i686", 0xbfff9c10) = -1 ENOENT (No such file or directory)
open("/usr/lib/tls/mmx/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/usr/lib/tls/mmx", 0xbfff9c10)  = -1 ENOENT (No such file or directory)
open("/usr/lib/tls/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/usr/lib/tls", 0xbfff9c10)      = -1 ENOENT (No such file or directory)
open("/usr/lib/i686/mmx/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/usr/lib/i686/mmx", 0xbfff9c10) = -1 ENOENT (No such file or directory)
open("/usr/lib/i686/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/usr/lib/i686", 0xbfff9c10)     = -1 ENOENT (No such file or directory)
open("/usr/lib/mmx/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/usr/lib/mmx", 0xbfff9c10)      = -1 ENOENT (No such file or directory)
open("/usr/lib/libempty.so", O_RDONLY)  = -1 ENOENT (No such file or directory)
stat64("/usr/lib", {st_mode=S_IFDIR|0755, st_size=53248, ...}) = 0
writev(2, [{"./hello", 7}, {": ", 2}, {"error while loading shared libra"..., 36}, {": ", 2}, {"libempty.so", 11}, {": ", 2}, {"cannot open shared object file", 30}, {": ", 2}, {"No such file or directory", 25}, {"\n", 1}], 10./hello: error while loading shared libraries: libempty.so: cannot open shared object file: No such file or directory
) = 118
exit_group(127)                         = ?


結果表明:hello程序會先到/etc目標下找,
然後打開ld.so.cache,尋找它需要的共享庫
open("/etc/ld.so.cache", O_RDONLY)      = -1 ENOENT (No such file or directory)
最後到/lib和/usr/lib下尋找它的共享庫
它的共享庫包括:
ldd hello
 libempty.so => not found
 libc.so.6 => /lib/tls/libc.so.6 (0x00e8e000)
 /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x004bf000)

OK,這裏需要說明的是採用gcc -o hello hello.c empty.so進行編譯的程序,ld.so.cache是不會幫助程序找到empty.so共享庫的.
因爲是ldconfig只會搜索以lib開頭以.so爲後綴的共享庫文件.如果採用empty.so爲共享庫文件.在ld.so.conf中指定其路徑,ldconfig也不會將其寫到ld.so.cache中
=========================================
實驗:
export LD_LIBRARY_PATH=.
./hello
=========================================
這裏執行成功後,在ld.so.cache中也看不到empty.so的身影.
=========================================
實驗:
strings /etc/ld.so.cache|grep empty.so
=========================================
將empty改名爲libempty.so,再重新進行編譯鏈接
=========================================
實驗:
unset LD_LIBRARY_PATH
mv empty.so libempty.so
pwd >> /etc/ld.so.conf
ldconfig
gcc -o hello hello.c libempty.so
strings /etc/ld.so.cache|grep empty.so
=========================================

更好的用法是使用-l選項.
例如:gcc -o hello hello.c -L . -lempty
其中-L 是指定搜索的目標,否則指示找不到libempty.so

指定'-l'選項和指定文件名的唯一區別是,-l 選項用'lib'和'.so'或'.a'把 library 包裹起來,而且搜索一些目錄.
這裏指的搜索一些目錄是-L指定的目錄

現在搜索的順序是
1)變量LD_LIBRARY_PATH指定的路徑
2)ld.so.cache中包含的lib庫文件名及路徑
3)到/lib和/usr/lib/中搜尋

那麼在ld.so.cache在什麼時候加載了libempty.so文件呢?
答案是用產生libempty.so文件後,再執行ldconfig,最後生成的
產生libempty.so產生的方式可以是拷貝或是gcc -shared等方式,而ldconfig命令是跟據/etc/ld.so.conf中的內容生成ld.so.cache
ldconfig會遍列ld.so.conf指定的所有路徑,查找到以lib開頭並以.so爲後綴的ELF文件
如果libempty.so被刪除,再執行ldconfig,就會將失效的記錄刪除掉


結論:
1)LD_LIBRARY_PATH和ld.so.conf及ld.so.cache毫無關係.
2)只有採用以lib開頭以.so爲後綴的共享庫纔會被ld.so.cache使用,用-l 選項和直接用文件的區別是-l選項會用'lib'和'.so'把 library 包裹起來,
而直接用文件則要明顯的指定文件開頭爲lib和後綴爲.so的共享庫文件.如果不指定,ldconfig是不會利用這個共享庫的.
3)ld.so.cache是用ldconfig產生,而ldconfig會根據ld.so.conf搜索所有的lib庫
4)搜索共享庫的順序是,先找LD_LIBRARY_PATH指定的路徑,然後找ld.so.cache指定的路徑,最後到/lib和/usr/lib下找
5)搜索/lib/和/usr/lib中的共享庫文件,並不是只要在這兩個目錄下就可以,它是搜索固定的子目錄,比如/usr/lib/i686/mmx/等等,最後再搜索/lib/和/usr/lib
6)產生新的hello文件後,可以移動 libempty.so共享庫文件,但前提是隻能把它移動到hello程序可以找到的地方.
也就是上面三個搜索目標(LD_LIBRARY_PATH,ld.so.cache,lib和/usr/lib)
7)ldconfig只會搜索以lib開頭以.so爲後綴的共享庫文件,而不會搜索以lib開頭,以.a爲後綴的靜態庫文件.

 

測試2:
用下面三種鏈接方式進行編譯分別進行測試:
第一種把它作爲兩個目標鏈接起來,這個方法最簡單.
第二種方法是把目標方文件以靜態庫的方式進行鏈接.
第三種方法是以共享目標文件的方式進行鏈接.

 

1)新建一個名爲piggy.c的共享目標文件,它什麼都不做,但會佔用內存空間,[0x10000]聲明瞭1MB的全局緩衝區
實驗:
=========================================
cat << EOF > piggy.c
>char bank[0x10000];
>EOF

cat << EOF > hello.c
> #include <unistd.h>
> int main(){pause();}
> EOF
=========================================


1.1編譯鏈接:
=========================================
實驗:
gcc -o hello hello.c piggy.c
=========================================


1.2)查看內存的佔用情況:
=========================================
實驗:
size ./hello
   text    data     bss     dec     hex filename
   1136     520   65568   67224   10698 ./hello
=========================================


1.3)用nm命令顯示已存在的bank數據,顯示佔用了1MB的內存空間
=========================================
實驗:
nm -S hello|grep bank
080495a0 00100000 B bank
=========================================

 

2)用靜態鏈接庫的方法,進行鏈接
2.1)生成.o的目標文件
=========================================
實驗:
gcc -c piggy.c
=========================================


2.2)用ar命令創建目標文件的存檔libpig.a
=========================================
實驗:
ar clq libpig.a piggy.o
=========================================


2.3)鏈接靜態庫,生成可執行文件hello
=========================================
實驗:
gcc -o hello hello.c -L ./ -lpig --static
=========================================


2.4)查看內存的佔用情況:
=========================================
實驗:
size ./hello
   text    data     bss     dec     hex filename
 656930    3488   12568  672986   a44da ./hello
=========================================


2.5)用nm命令顯示是否包括bank數組
=========================================
實驗:
nm -S hello|grep bank
=========================================
用靜態庫鏈接的可執行程序,可執行文件的大小有了明顯的增加.但是沒有包括數組bank.

 


3)用動態鏈接庫的方法,進行鏈接

3.1)生成動態函數庫 piggy.so
=========================================
實驗:
gcc -shared -o piggy.so piggy.c
=========================================


3.2)鏈接動態庫,生成可執行文件hello
=========================================
實驗:
gcc -o hello hello.c  ./piggy.so
=========================================


3.3)查看內存的佔用情況:
=========================================
實驗:
size ./hello
   text    data     bss     dec     hex filename
   1411     536      16    1963     7ab ./hello
=========================================


3.4)用nm命令顯示是否包括bank數組
=========================================
實驗:
nm -S hello|grep bank
=========================================
用動態庫鏈接的可執行程序,比靜態庫的可執行程序有了明顯的減小.並且沒有看到數組bank


3.5)最後用pmap命令來運行以查看進程內存的映射:
=========================================
實驗:
./hello &
 jobs -x pmap -q %1
4281:   ./hello
00111000   1176K r-x--  /lib/tls/libc-2.3.4.so
00237000      8K r-x--  /lib/tls/libc-2.3.4.so
00239000      8K rwx--  /lib/tls/libc-2.3.4.so
0023b000      8K rwx--    [ anon ]
009e7000      4K r-x--  /tmp/piggy.so (deleted)
009e8000      4K rwx--  /tmp/piggy.so (deleted)
009e9000   1024K rwx--    [ anon ]
00be9000     88K r-x--  /lib/ld-2.3.4.so
00bff000      4K r-x--  /lib/ld-2.3.4.so
00c00000      4K rwx--  /lib/ld-2.3.4.so
08048000      4K r-x--  /tmp/hello (deleted)
08049000      4K rw---  /tmp/hello (deleted)
b7ef1000      8K rw---    [ anon ]
bff78000    544K rw---    [ stack ]
ffffe000      4K -----    [ anon ]
==========================================
輸出結果表明有1MB的匿名映射,是給bank數組分配的存儲空間.


結論:
第一種方式會佔用更多的內存.例如piggy.c中定義1MB的數組,在運行時它會佔用1MB的內存空間,但由於採用的是動態鏈接方式,二進制文件會比輕小.
第二種方式則不會包括piggy.c中定義的1MB數組,鏈接器能夠確定沒有引用piggy.o,所以不會把它鏈接進來,但由於採用的是靜態鏈接,二進制文件會比較大.
第三種方式程序的bss最小,不會加載1MB的數組,但在運行期間同樣會佔用1MB的內存空間.

這裏有一點要說明.編譯器默認會優先選擇動態鏈接,如果只想鏈接靜態目標,則必須在gcc/g++中寫明-static選項.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章