動態庫和靜態庫在C/C++開發中很常見,相比靜態庫直接被編譯到可執行程序,動態庫運行時加載使得可執行程序的體積更小,更新動態庫能夠不用從新編譯可執行程序等諸多好處。做者是一個Linux後臺開發,這些知識常常用到,因此整理了一下這方面的知識。靜態庫相對簡單,本文只關心Linux平臺下的動態庫。linux
這裏我把一個短小卻頗有用的哈希函數編譯成動態庫作爲示例,ELFhash用於對字符串作哈希,返回一個無符號整數。web
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
//elfhash.h
#include
unsigned
long
ELFhash(
const
char
* key);
//elfhash.c
#include "elfhash.h"
unsigned
long
ELFhash(
const
char
* key)
{
unsigned
long
h = 0, g;
while
( *key ) {
h = ( h > 24;
h &= ~g;
}
return
h;
}
|
接下來使用gcc編譯以上代碼,並用ld將編譯的目標文件連接成動態庫緩存
1
2
|
gcc -fPIC -c -Wall elfhash.c
ld -shared elfhash.o -o libelfhash.so
|
其中-fPIC
意思是生成位置無關的代碼(Position Independent Code),適用於動態庫,在多個進程中共享動態庫的同一份代碼。ld的-shared
選項告訴連接器建立的是動態庫。gcc也能夠間接調用ld生成動態庫app
1
|
gcc -fPIC -shared -Wall -o libelfhash.so elfhash.c
|
動態庫的使用方法有兩種一種是隱式使用,第二種是顯式使用。隱式使用的方法很簡單。函數
1
2
3
4
5
6
|
#include "elfhash.h"
int
main()
{
printf
(
"%ldn"
, ElfHash(
"key-for-test"
));
return
0;
}
|
顯式使用動態庫須要藉助如下幾個函數測試
1
2
3
4
5
|
#include
void
*dlopen(
const
char
*filename,
int
flag);
//flag能夠是RTLD_LAZY,執行共享庫中的代碼時解決未定義符號,RTLD_NOW則是dlopen返回前解決未定義符號。
char
*dlerror(
void
);
//當發生錯誤時,返回錯誤信息
void
*dlsym(
void
*handle,
const
char
*symbol);
//獲取符號
int
dlclose(
void
*handle);
//關閉
|
應用上面幾個函數,調用ELFhash實現跟隱式調用同樣的功能編碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
#include "elfhash.h"
#include
#include
int
main() {
void
*handle;
unsigned
long
(*hash)(
const
char
*);
char
*error;
handle = dlopen (
"./libelfhash.so"
, RTLD_LAZY);
if
(!handle) {
fputs
(dlerror(), stderr);
exit
(1);
}
hash = dlsym(handle,
"ElfHash"
);
if
((error = dlerror()) != NULL) {
fputs
(error, stderr);
exit
(1);
}
printf
(
"%ldn"
, (*hash)(
"key-for-test"
));
dlclose(handle);
}
|
至此瞭解以上的知識就能夠建立和使用動態庫了。 實際應用中咱們可能仍是會遇到一些問題。spa
動態庫建立那一節,我演示如何隱式使用動態庫,那麼編譯運行這段代碼試一下。code
1
2
3
4
|
gcc main.c -L./ -lelfhash
./a.out
//執行可執行程序
//如下是輸出結果
./a.out: error
while
loading shared libraries: libelfhash.so: cannot open shared object file: No such file or directory
|
結果運行時報錯,可執行程序找不到動態庫。 網上有一些說法是編譯時設置-L
選項,但在Linux上面證實是不行的(SunOS上可行),這個選項只能在編譯連接時有效, 可讓你使用-l
如上面的-lelfhash
。使用readelf -d a.out
能夠看到可執行文件依賴的動態庫信息。orm
1
|
0x0000000000000001 (NEEDED) Shared library: [libelfhash.so]
|
能夠看到這裏面並無包含動態庫的路徑信息。查閱一下動態連接器的文檔man ld-linux.so
能夠發現這樣一句話(有的沒有,版本問題)
If a slash is found, then the dependency string is interpreted as a (relative or absolute) pathname, and the library is loaded using that pathname
這段話太長,我只截取一部分,大體就是說,當依賴中有/
符號,那麼會被解析成動態庫加載的路徑,隱式使用的例子換一種編譯方法。
1
2
3
|
gcc main.c .
/libelfhash
.so
.
/a
.out
23621492
//
輸出正常
|
再用readelf -d a.out
查看會發現,依賴信息中有了一個路徑。
1
|
0x0000000000000001 (NEEDED) Shared library: [.
/libelfhash
.so]
|
這種方法雖然解決了問題,可是依賴中的路徑是硬編碼,不是很靈活。 動態連接器是如何查找的動態庫的須要進一步查閱文檔。關於查找的順序有點長,這裏就不直接引用了,大體是這樣:
(僅ELF文件) 使用可執行文件中
DT_RPATH區域設置的屬性,若是DT_RUNPATH被設置,那麼忽略DT_RPATH(在個人Linux對應的是RPATH和RUNPATH)。
使用環境變量
LD_LIBRARY_PATH,若是可執行文件中有set-user-id/set-group-id, 會被忽略。
(僅ELF文件) 使用可執行文件中DT_RUNPATH區域設置的屬性
從
/etc/ld.so.cache緩存文件中查找
從默認路徑
/lib, /usr/lib文件目錄中查找
咱們須要設置RPATH或者RUNPATH,能夠這樣作
1
|
gcc main.c -Wl,-rpath,
/home/xxx
,--
enable
-new-dtags -L./ -lelfhash
|
這裏的-Wl
選項告訴連接器ld
若是如何處理,接下來傳遞的-rpath
(或者使用-R
)告訴ld
動態庫的路徑信息(注意-Wl,
和後面選項之間不能有空格)。若是沒有--enable-new-dtags
那麼只會設置RPATH,反之,RPATH和RUNPATH會同時被設置。使用readelf -d a.out
查看結果:
1
2
|
0x000000000000000f (RPATH) Library rpath: [
/home/xxx
]
0x000000000000001d (RUNPATH) Library runpath: [
/home/xxx
]
|
若是使用環境變量LD_LIBRARY_PATH,那麼通常這樣用 export
1
|
export
LD_LIBRARY_PATH=
/home/xxx
;$LD_LIBRARY_PATH
|
RPATH和RUNPATH指定動態庫的路徑,用起來簡單,可是也缺少靈活性,LDLIBRARYPATH在臨時測試的也是頗有用的,可是在正式環境中,直接使用它也不是好的實踐,由於環境變量跟用戶的環境關係比較大。動態庫不只要考慮本身使用, 還有分發給別的用戶使用的狀況。
更通用的方法是使用ldconfig
,有幾種方法,先在/etc/ld.so.conf.d/
目錄下建立一個文件,而後把你的動態庫路徑寫進去。或者將你的動態庫放到/lib,/lib64(64位),/usr/lib,/usr/lib64(64位)
而後運行sudo ldconfig
重建/etc/ld.so.cache
文件。
一般在使用第三方給的動態庫的時候,都是帶有版本(文件命名),能夠在/usr/lib64
下看到不少這樣的動態庫。如今我從新編譯動態庫,此次加上版本信息。
1
|
gcc -fPIC -shared -Wall -Wl,-soname,libelfhash.so.0 -o libelfhash.so.0.0.0 elfhash.c
|
每一個動態庫都有一個名字,如這裏的libelfhash.so.0.0.0,叫real name
,命名規則跟簡單,一般是libxxx.so.MAJOR.MINOR.VERSION
(有的時候VERSION會被省略),若是動態庫在接口上的兼容性,好比刪除了接口或者修改了接口參數,MAJOR增長,若是接口兼容,只是作了更新或者bug修復那麼MINOR和VERSION增長。也就是說MAJOR相同的庫接口都是兼容的,反之不兼容,若是使用不兼容的動態庫須要從新編譯可執行程序。
編譯動態庫時,經過給ld
傳遞鏈接選項-soname
能夠指定一個soname
, 如這裏的libelfhash.so.0 只保留MAJOR,可執行程序運行加載動態庫時,會加載這個指定名字的庫。
動態庫還有一個名字是link name
,編譯可執行程序時,傳個連接器ld
的動態庫名字,一般是沒有版本號以.so結尾的文件名。 通常做法是對soname建立軟鏈。
按照這個規則來命名的動態庫能夠ldconfig
識別,咱們把libelfhash.so.0.0.0放到/usr/lib64
文件夾中,執行如下指令
1
2
|
$
sudo
ldconfig -
v
|
grep
libelfhash.so
libelfhash.so.0 -> libelfhash.so.0.0.0
|
能夠發現ldconfig
根據libelfhash.so.0.0.0的信息,建立了一個soname指向real name的軟鏈,當動態庫更新(MINOR,VERSION增長),拷貝新庫到相應的位置,再執行sudo ldconfig
會自動更新軟鏈指向最新的動態庫,動態庫更新就完成了。
OK,關於Linux動態庫知識整理就到這裏了,這些知識雖然說都是些基礎,少有涉及動態庫內部的一些原理,可是卻很經常使用。整理過程當中我帶着疑問去閱讀了ld
和ld-linux.so
的文檔,收穫頗豐。一樣,但願本文能幫你解釋遇到的部分問題或疑惑。
若是想深刻體驗LINUX系統的新手,也能夠先下載一個方德Linux軟件中心試用一下。
免費下載地址:http://www.nfs-cloud.cn:81/appCenter/open/softcenter