庫的創建與使用
當一段代碼在一個程序中被多次使用的時候,我們可以把他寫成函數來調用,當一類代碼被多個程序重複使用的時候,我們就可以將其組建成一個庫,來實現對這類代碼的重複使用。
在對庫進行修改的時候應該考慮其兼容性,也就是說,依賴於就的庫的軟件在將庫更新後,這個軟件還是可以使用的,不能因爲庫的更新導致軟件的運行失敗,否則的話,如果這個庫非常的重要,那後果將會是災難性的。那就意味着軟件要重寫。聽別人說,linux的libc5過渡到libc6的時候是多麼的麻煩,因爲以來libc5的軟件不能運行在libc6的庫上,導致許多軟件需要重新編譯甚至重寫。但是,這不能說,庫不能被修改,而是說,在對庫的修改和升級的時候要注意兼容。
對於庫的命名,這裏有些約定,但很簡單,首先,所有的庫都要以lib開頭,gcc就依賴於這個約定,當你使用-l這個選項的時候,就會被默認的加上lib這個字符串。還有,以.a結尾表示靜態庫,以.so結尾的表示是共享庫。另外還有就是編號的約定,一般格式爲:庫名.主版本號.次版本號.補丁級別號。對於編號的增加,約定是:若庫達到了不能和前一個版本兼容了,就要升級主版本號,如果僅是增加了新的功能,但有和以前的版本兼容的話,那就是升級次版本號,若只是修正錯誤,則只需要升級補丁的級別。還有一類很特殊的庫,他們以_p或者_g來結尾,通常,以_g結尾的庫是調試庫,編入了特殊的符號和功能,能夠增加採用這個庫的程序的調試能力,_p表示代碼剖析庫,他們包含的代碼和符號能夠進行復雜的代碼剖析和性能分析。記住,當你使用這些特殊的庫完成了程序的調試的時候,就要用,正常的庫重新將軟件進行編譯。
對庫的操作:nm, ar, ldd, ldconfig
nm命令
nm命令用來列出目標文件或者二進制文件所有的符號,他可以用來查看程序到底調用了什麼函數,還能用來查看,某一個庫中有沒有我們所需要的函數。
nm [option(s)] [file(s)]
當你不指定文件的時候,nm就會尋找但前目錄現有沒有a.out文件,若有,則會將該文件作爲要查看的文件,否則他會提示
nm: 'a.out': No such file
例如:
#include <stdio.h>
int i = 5;
char s;
const C = '0';
void fun(void)
{
printf("hello");
}
gcc -c 1.c後將會生成一個1.o的文件。然後我們利用nm工具來查看。
nm 1.o
00000000 R C
00000000 T fun
00000000 D i /*有一點不明白就是如果i被初始化爲0,則他顯示的是B,爲初始化的*/
U printf
00000001 C s
其中的大寫字母表示符號的種類,下表給出了他們的意思:
A The symbol’s value is absolute, and will not be changed by further linking.
B The symbol is in the uninitialized data section (known as BSS)
C The symbol is common
D The symbol is in the initialized data section.
G The symbol is in an initialized data section for small objects.
I The symbol is an indirect reference to another symbol
N The symbol is a debugging symbol
R The symbol is in a read only data section
S The symbol is in an uninitialized data section for small objects
T The symbol is in the text (code) section
U The symbol is undefined.
V The symbol is a weak object.
W The symbol is a weak symbol that has not been specifically tagged as a weak object symbol.
- The symbol is a stabs symbol in an a.out object file
還有幾個比較常用的選項:
-A 列出符號名的同時列出他來自那個文件。
-a 列出所有符號,包括調試符號
-l 列出對應的行號
-n 根據符號的地址來排序,默認是按名稱來排的
-u 只列出未定義的符號(同--undefined-only,反--defined-only)
ar命令
ar命令使用來建立或修改備份文件,或是從備份文件中抽取文件,當然,也可以把他用在庫的創建與修改上,他可以把多個文件按照一定的組織結構組成一個備份文件,而且所有的文件即使在備份文件中,仍然保存着其原來的權限與屬性。組成他的文件稱爲他的member。
ar [emulation options] [-]{dmpqrstx}[abcfilNoPsSuvV] [member-name] [count] archive-file file...
ar -M [<mri-script]
常用的選項:
d 從庫中刪除模塊
m 在一個庫中移動成員
p 顯示庫中指定的成員到標準輸出中。
q 快速追加,增加模塊到庫的結尾處,並不見查是否替換
r 在庫中插入模塊
t 顯示庫的模塊的清單
x 提取庫中的成員,若不指定,則提取所有的成員
a 在庫中一個已存在的成員的後面添加一個文件
b 在庫中一個已存在的成員的前面添加一個文件
c 創建一個庫,不論庫是否已經存在
f 在庫中截斷指定的名字
i 類似b
N 與count參數一起使用,在庫中,有多個相同文件名的時候,指定提取或輸出的個數。
o 當提取成員時,保留成員的原始數據
P 進行文件明匹配時使用全路徑名
s 寫入一個目標文件索引到庫中或者更新一個存在的目標文件索引。
S 不創建目標文件索引
u ?
例如:我們有如下幾個程序:
/*File 1.c*/
#include "test.h"
#include <stdio.h>
void p1(void)
{
printf("We are in p1 function\n");
}
/*File 2.c*/
#include "test.h"
#include <stdio.h>
void p2(void)
{
printf("We are in p2 function\n");
}
/*File test.h*/
#ifndef _TEST_
#define _TEST_
void p1(void);
void p2(void);
#endif
/*File 3.c*/
#include "test.h"
#include <stdio.h>
int main(void)
{
printf("We are in main function\n");
p1();
p2();
return 0;
}
他們之間的關係是:3.c掉用了1.c和2.c中的p1,p2函數,首先要編譯1.c 2.c:
$gcc -c 1.c
$gcc -c 2.c
然後建立一個庫,將產生的1.o 2.o放到庫裏面去:
$ar -rcs libtest.a 1.o 2.o
這就行了,然後使用gcc編譯並連接3.c
$gcc 3.c -static -L. -l test
我們使用-static是爲了將libtest.a和他連接起來,執行:
$./a.out
We are in main function
We are in p1 function
We are in p2 function
很明顯,程序發的運行結果說明了,我們已經使用libtest.a這個庫中的p1,p2函數。
ldd命令
將會列出爲使程序正常運行所需要的共享庫。
ldd [options] file
常用的選項,
-d 執行重定位,並報告所有丟失的函數
-r 執行對函數和數據對象的重定位,並報告丟失的任何函數或數據
ldconfig命令:
他是一個動態連接庫的管理命令,能使動態連接庫爲系統所共享,他的工作是,在默認搜索目錄也就是/lib和/usr/lib還有動態庫配置文件/etc/ld.so.conf內所記錄的目錄中,搜索動態連接庫,來創建動態裝入程序ld.so所需的連接和緩存文件(/etc/ld.so.cache(保存已排序的動態連接庫名)),程序連接時首先在cache中找,之後在到ld.so.conf找詳細的路徑。
共享庫的編寫
創建共享庫的辦法,和創建靜態庫的辦法有點不同,我們仍然拿上面的程序當作例子:
1.編譯1.o 2.o,注意,這裏有不同的地方,再用gcc編譯是,要用上-fPIC的選項,這能產生無關的代碼,並能被加載到任何地址。
$gcc -fPIC -c 1.c
$gcc -fPIC -c 2.c
2.用ar創建庫,注意,這個時候的文件名有所改變
ar -rcs libtest.so 1.o 2.o
3.使用gcc的-shared的選項和-soname選項,用-Wl選項把參數傳遞連接器ld,使用-l選項顯示的鏈接C庫,以保證得到所需的啓動(startup)代碼,從而避免程序在使用不同的,可能不兼容版本的C庫的系統上不能運行。
$ gcc -g -shared -Wl,-soname,libtest.so -o libtest.so.1.0.0 1.o 2.o -lc
之後會產生一個libtest.so.1.0.0文件,
我們只是爲了測試並不想 把他安裝到系統上面,所以要爲其建立連接:(用於soname)
$ ln -s libtest.so.1.0.0 libtest.so.1
另一個是鏈接程序在使用-l test連接到libtest使用的:
$ ln -s libtest.so.1.0.0 libtest.so
爲了使用剛纔建立的共享庫,我們需要將libtest.so拷貝到/usr/lib的目錄下面:
$cp libtest.so /usr/lib
然夠編譯3.c:
$gcc 3.c -l test
$./a.out
We are in main function
We are in p1 function
We are in p2 function
還有一種方法是把他的路徑放到/etc/ld.so.conf文件中,然後以root的身份運行一下ldconfig命令,要說的是,ldconfig在/sbin中。
dl接口
加載共享對象:
void * dlopen (const char *filename, int flag);
他會以flag指定的模式加載filename指定的對象,若filename是NULL,則dlopen打開當前執行的文件,如果是一個絕對的路徑名,dlopen就會打開那個文件,如果僅僅是一個文件名,則dlopen會以下面給定的順序搜索下列目錄,查找文件$LD_ELF_LIBRARY_PATH:
$LD_LIBRARY_PATH, /etc/ld.so.cache, /usr/lib/, /lib
參數flag:
RTLD_LAZY : 來自被加載的對象的符號在被調用時解析
RTLD_NOW : 來自被加載的對象的所有符號在函數dlopen返回前解析
如果他們的其中一個 | RTDL_GLOBAL 就會導致導出所有的符號,就像他們被直接鏈接一樣;
另外,當dlopen成功返回時,返回的是一個句柄,否則返回NULL;
使用共享對象:
void *dlsym(void *handle, const char *symbol);
dlsym在handle中搜索symbol,失敗返回NULL;
錯誤檢查
char *dlerror(void);
返回描述最近的發生錯誤的字符串,沒錯時返回NULL;
卸載共享對象:
int dlclose(void *handle);
順便說一下,他們所在的頭文件是dlfcn.h
例如:
#include <stdio.h>
#include <dlfcn.h>
int main(void)
{
void * handle;
void (*pfun)(void);
char *err;
printf("we are in function main\n");
if (NULL == (handle = dlopen("./libtest.so",RTLD_NOW)))
{
printf("Failed open libteat\n");
return 1;
}
dlerror();
pfun = dlsym(handle,"p1");
printf("%s\n",((err = dlerror()) == NULL) ? "get p1" : "err");
(*pfun)();
pfun = dlsym(handle,"p2");
printf("%s\n",((err = dlerror()) == NULL) ? "get p2" : "err");
(*pfun)();
dlclose(handle);
return 0;
}
將其保存爲4.c後:
$gcc -Wall 4.c -l dl
$./a.out
we are in function main
get p1
We are in p1 function
get p2
We are in p2 function
在對庫進行修改的時候應該考慮其兼容性,也就是說,依賴於就的庫的軟件在將庫更新後,這個軟件還是可以使用的,不能因爲庫的更新導致軟件的運行失敗,否則的話,如果這個庫非常的重要,那後果將會是災難性的。那就意味着軟件要重寫。聽別人說,linux的libc5過渡到libc6的時候是多麼的麻煩,因爲以來libc5的軟件不能運行在libc6的庫上,導致許多軟件需要重新編譯甚至重寫。但是,這不能說,庫不能被修改,而是說,在對庫的修改和升級的時候要注意兼容。
對於庫的命名,這裏有些約定,但很簡單,首先,所有的庫都要以lib開頭,gcc就依賴於這個約定,當你使用-l這個選項的時候,就會被默認的加上lib這個字符串。還有,以.a結尾表示靜態庫,以.so結尾的表示是共享庫。另外還有就是編號的約定,一般格式爲:庫名.主版本號.次版本號.補丁級別號。對於編號的增加,約定是:若庫達到了不能和前一個版本兼容了,就要升級主版本號,如果僅是增加了新的功能,但有和以前的版本兼容的話,那就是升級次版本號,若只是修正錯誤,則只需要升級補丁的級別。還有一類很特殊的庫,他們以_p或者_g來結尾,通常,以_g結尾的庫是調試庫,編入了特殊的符號和功能,能夠增加採用這個庫的程序的調試能力,_p表示代碼剖析庫,他們包含的代碼和符號能夠進行復雜的代碼剖析和性能分析。記住,當你使用這些特殊的庫完成了程序的調試的時候,就要用,正常的庫重新將軟件進行編譯。
對庫的操作:nm, ar, ldd, ldconfig
nm命令
nm命令用來列出目標文件或者二進制文件所有的符號,他可以用來查看程序到底調用了什麼函數,還能用來查看,某一個庫中有沒有我們所需要的函數。
nm [option(s)] [file(s)]
當你不指定文件的時候,nm就會尋找但前目錄現有沒有a.out文件,若有,則會將該文件作爲要查看的文件,否則他會提示
nm: 'a.out': No such file
例如:
#include <stdio.h>
int i = 5;
char s;
const C = '0';
void fun(void)
{
printf("hello");
}
gcc -c 1.c後將會生成一個1.o的文件。然後我們利用nm工具來查看。
nm 1.o
00000000 R C
00000000 T fun
00000000 D i /*有一點不明白就是如果i被初始化爲0,則他顯示的是B,爲初始化的*/
U printf
00000001 C s
其中的大寫字母表示符號的種類,下表給出了他們的意思:
A The symbol’s value is absolute, and will not be changed by further linking.
B The symbol is in the uninitialized data section (known as BSS)
C The symbol is common
D The symbol is in the initialized data section.
G The symbol is in an initialized data section for small objects.
I The symbol is an indirect reference to another symbol
N The symbol is a debugging symbol
R The symbol is in a read only data section
S The symbol is in an uninitialized data section for small objects
T The symbol is in the text (code) section
U The symbol is undefined.
V The symbol is a weak object.
W The symbol is a weak symbol that has not been specifically tagged as a weak object symbol.
- The symbol is a stabs symbol in an a.out object file
還有幾個比較常用的選項:
-A 列出符號名的同時列出他來自那個文件。
-a 列出所有符號,包括調試符號
-l 列出對應的行號
-n 根據符號的地址來排序,默認是按名稱來排的
-u 只列出未定義的符號(同--undefined-only,反--defined-only)
ar命令
ar命令使用來建立或修改備份文件,或是從備份文件中抽取文件,當然,也可以把他用在庫的創建與修改上,他可以把多個文件按照一定的組織結構組成一個備份文件,而且所有的文件即使在備份文件中,仍然保存着其原來的權限與屬性。組成他的文件稱爲他的member。
ar [emulation options] [-]{dmpqrstx}[abcfilNoPsSuvV] [member-name] [count] archive-file file...
ar -M [<mri-script]
常用的選項:
d 從庫中刪除模塊
m 在一個庫中移動成員
p 顯示庫中指定的成員到標準輸出中。
q 快速追加,增加模塊到庫的結尾處,並不見查是否替換
r 在庫中插入模塊
t 顯示庫的模塊的清單
x 提取庫中的成員,若不指定,則提取所有的成員
a 在庫中一個已存在的成員的後面添加一個文件
b 在庫中一個已存在的成員的前面添加一個文件
c 創建一個庫,不論庫是否已經存在
f 在庫中截斷指定的名字
i 類似b
N 與count參數一起使用,在庫中,有多個相同文件名的時候,指定提取或輸出的個數。
o 當提取成員時,保留成員的原始數據
P 進行文件明匹配時使用全路徑名
s 寫入一個目標文件索引到庫中或者更新一個存在的目標文件索引。
S 不創建目標文件索引
u ?
例如:我們有如下幾個程序:
/*File 1.c*/
#include "test.h"
#include <stdio.h>
void p1(void)
{
printf("We are in p1 function\n");
}
/*File 2.c*/
#include "test.h"
#include <stdio.h>
void p2(void)
{
printf("We are in p2 function\n");
}
/*File test.h*/
#ifndef _TEST_
#define _TEST_
void p1(void);
void p2(void);
#endif
/*File 3.c*/
#include "test.h"
#include <stdio.h>
int main(void)
{
printf("We are in main function\n");
p1();
p2();
return 0;
}
他們之間的關係是:3.c掉用了1.c和2.c中的p1,p2函數,首先要編譯1.c 2.c:
$gcc -c 1.c
$gcc -c 2.c
然後建立一個庫,將產生的1.o 2.o放到庫裏面去:
$ar -rcs libtest.a 1.o 2.o
這就行了,然後使用gcc編譯並連接3.c
$gcc 3.c -static -L. -l test
我們使用-static是爲了將libtest.a和他連接起來,執行:
$./a.out
We are in main function
We are in p1 function
We are in p2 function
很明顯,程序發的運行結果說明了,我們已經使用libtest.a這個庫中的p1,p2函數。
ldd命令
將會列出爲使程序正常運行所需要的共享庫。
ldd [options] file
常用的選項,
-d 執行重定位,並報告所有丟失的函數
-r 執行對函數和數據對象的重定位,並報告丟失的任何函數或數據
ldconfig命令:
他是一個動態連接庫的管理命令,能使動態連接庫爲系統所共享,他的工作是,在默認搜索目錄也就是/lib和/usr/lib還有動態庫配置文件/etc/ld.so.conf內所記錄的目錄中,搜索動態連接庫,來創建動態裝入程序ld.so所需的連接和緩存文件(/etc/ld.so.cache(保存已排序的動態連接庫名)),程序連接時首先在cache中找,之後在到ld.so.conf找詳細的路徑。
共享庫的編寫
創建共享庫的辦法,和創建靜態庫的辦法有點不同,我們仍然拿上面的程序當作例子:
1.編譯1.o 2.o,注意,這裏有不同的地方,再用gcc編譯是,要用上-fPIC的選項,這能產生無關的代碼,並能被加載到任何地址。
$gcc -fPIC -c 1.c
$gcc -fPIC -c 2.c
2.用ar創建庫,注意,這個時候的文件名有所改變
ar -rcs libtest.so 1.o 2.o
3.使用gcc的-shared的選項和-soname選項,用-Wl選項把參數傳遞連接器ld,使用-l選項顯示的鏈接C庫,以保證得到所需的啓動(startup)代碼,從而避免程序在使用不同的,可能不兼容版本的C庫的系統上不能運行。
$ gcc -g -shared -Wl,-soname,libtest.so -o libtest.so.1.0.0 1.o 2.o -lc
之後會產生一個libtest.so.1.0.0文件,
我們只是爲了測試並不想 把他安裝到系統上面,所以要爲其建立連接:(用於soname)
$ ln -s libtest.so.1.0.0 libtest.so.1
另一個是鏈接程序在使用-l test連接到libtest使用的:
$ ln -s libtest.so.1.0.0 libtest.so
爲了使用剛纔建立的共享庫,我們需要將libtest.so拷貝到/usr/lib的目錄下面:
$cp libtest.so /usr/lib
然夠編譯3.c:
$gcc 3.c -l test
$./a.out
We are in main function
We are in p1 function
We are in p2 function
還有一種方法是把他的路徑放到/etc/ld.so.conf文件中,然後以root的身份運行一下ldconfig命令,要說的是,ldconfig在/sbin中。
dl接口
加載共享對象:
void * dlopen (const char *filename, int flag);
他會以flag指定的模式加載filename指定的對象,若filename是NULL,則dlopen打開當前執行的文件,如果是一個絕對的路徑名,dlopen就會打開那個文件,如果僅僅是一個文件名,則dlopen會以下面給定的順序搜索下列目錄,查找文件$LD_ELF_LIBRARY_PATH:
$LD_LIBRARY_PATH, /etc/ld.so.cache, /usr/lib/, /lib
參數flag:
RTLD_LAZY : 來自被加載的對象的符號在被調用時解析
RTLD_NOW : 來自被加載的對象的所有符號在函數dlopen返回前解析
如果他們的其中一個 | RTDL_GLOBAL 就會導致導出所有的符號,就像他們被直接鏈接一樣;
另外,當dlopen成功返回時,返回的是一個句柄,否則返回NULL;
使用共享對象:
void *dlsym(void *handle, const char *symbol);
dlsym在handle中搜索symbol,失敗返回NULL;
錯誤檢查
char *dlerror(void);
返回描述最近的發生錯誤的字符串,沒錯時返回NULL;
卸載共享對象:
int dlclose(void *handle);
順便說一下,他們所在的頭文件是dlfcn.h
例如:
#include <stdio.h>
#include <dlfcn.h>
int main(void)
{
void * handle;
void (*pfun)(void);
char *err;
printf("we are in function main\n");
if (NULL == (handle = dlopen("./libtest.so",RTLD_NOW)))
{
printf("Failed open libteat\n");
return 1;
}
dlerror();
pfun = dlsym(handle,"p1");
printf("%s\n",((err = dlerror()) == NULL) ? "get p1" : "err");
(*pfun)();
pfun = dlsym(handle,"p2");
printf("%s\n",((err = dlerror()) == NULL) ? "get p2" : "err");
(*pfun)();
dlclose(handle);
return 0;
}
將其保存爲4.c後:
$gcc -Wall 4.c -l dl
$./a.out
we are in function main
get p1
We are in p1 function
get p2
We are in p2 function
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.