1. 程式人生 > >gcc 生成動態鏈接庫

gcc 生成動態鏈接庫

工具使用 3.0 rom purposes so庫 file out foo 版權

http://blog.csdn.net/ngvjai/article/details/8520840

Linux下文件的類型是不依賴於其後綴名的,但一般來講:
.o,是目標文件,相當於windows中的.obj文件
.so 為共享庫,是shared object,用於動態連接的,和dll差不多
.a為靜態庫,是好多個.o合在一起,用於靜態連接
.la為libtool自動生成的一些共享庫,vi編輯查看,主要記錄了一些配置信息。可以用如下命令查看*.la文件的格式 $file *.la
*.la: ASCII English text
所以可以用vi來查看其內容。
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

創建.a庫文件和.o庫文件:
[[email protected] perl_c2]$ pwd
/home/yufei/perl_c2

[[email protected] perl_c2]$ cat mylib.c
#include <stdio.h>
#include <string.h>
void hello(){
printf("success call from perl to c library/n");
}
[[email protected] perl_c2]$ cat mylib.h
extern void hello();


[[email protected] perl_c2]$ gcc -c mylib.c
[[email protected] perl_c2]$ dir
mylib.c mylib.h mylib.o
[[email protected] perl_c2]$ ar -r mylib.a mylib.o
ar: 正在創建 mylib.a
[[email protected] perl_c2]$ dir
mylib.a mylib.c mylib.h mylib.o


@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
動態鏈接庫*.so的編譯與使用- -


動態庫*.so在linux下用c和c++編程時經常會碰到,最近在網站找了幾篇文章介紹動態庫的編譯和鏈接,總算搞懂了這個之前一直不太了解得東東,這裏做個筆記,也為其它正為動態庫鏈接庫而苦惱的兄弟們提供一點幫助。
1、動態庫的編譯

下面通過一個例子來介紹如何生成一個動態庫。這裏有一個頭文件:so_test.h,三個.c文件:test_a.c、test_b.c、test_c.c,我們將這幾個文件編譯成一個動態庫:libtest.so。

so_test.h:

#include <stdio.h>
#include <stdlib.h>
void test_a();
void test_b();
void test_c();

test_a.c:

#include "so_test.h"
void test_a()
{
printf("this is in test_a.../n");
}

test_b.c:

#include "so_test.h"
void test_b()
{
printf("this is in test_b.../n");
}

test_c.c:

#include "so_test.h"
void test_c()
{
printf("this is in test_c.../n");
}

將這幾個文件編譯成一個動態庫:libtest.so
$ gcc test_a.c test_b.c test_c.c -fPIC -shared -o libtest.so

2、動態庫的鏈接

在1、中,我們已經成功生成了一個自己的動態鏈接庫libtest.so,下面我們通過一個程序來調用這個庫裏的函數。程序的源文件為:test.c。
test.c:

#include "so_test.h"
int main()
{
test_a();
test_b();
test_c();
return 0;

}
l 將test.c與動態庫libtest.so鏈接生成執行文件test:
$ gcc test.c -L. -ltest -o test

l 測試是否動態連接,如果列出libtest.so,那麽應該是連接正常了

$ ldd test
l 執行test,可以看到它是如何調用動態庫中的函數的。
3、編譯參數解析
最主要的是GCC命令行的一個選項:
-shared 該選項指定生成動態連接庫(讓連接器生成T類型的導出符號表,有時候也生成弱連接W類型的導出符號),不用該標誌外部程序無法連接。相當於一個可執行文件

l -fPIC:表示編譯為位置獨立的代碼,不用此選項的話編譯後的代碼是位置相關的所以動態載入時是通過代碼拷貝的方式來滿足不同進程的需要,而不能達到真正代碼段共享的目的。

l -L.:表示要連接的庫在當前目錄中

l -ltest:編譯器查找動態連接庫時有隱含的命名規則,即在給出的名字前面加上lib,後面加上.so來確定庫的名稱

l LD_LIBRARY_PATH:這個環境變量指示動態連接器可以裝載動態庫的路徑。

l 當然如果有root權限的話,可以修改/etc/ld.so.conf文件,然後調用 /sbin/ldconfig來達到同樣的目的,不過如果沒有root權限,那麽只能采用輸出LD_LIBRARY_PATH的方法了。
4、註意
調用動態庫的時候有幾個問題會經常碰到,有時,明明已經將庫的頭文件所在目錄 通過 “-I” include進來了,庫所在文件通過“-L”參數引導,並指定了“-l”的庫名,但通過ldd命令察看時,就是死活找不到你指定鏈接的so文件,這時你要作的就是通過修改LD_LIBRARY_PATH或者/etc/ld.so.conf文件來指定動態庫的目錄。通常這樣做就可以解決庫無法鏈接的問題了。

makefile裏面怎麽正確的編譯和連接生成.so庫文件,然後又是在其他程序的makefile裏面如何編譯和連接才能調用這個庫文件的函數????
答:
你需要告訴動態鏈接器、加載器ld.so在哪裏才能找到這個共享庫,可以設置環境變量把庫的路徑添加到庫目錄/lib和/usr/lib,LD_LIBRARY_PATH=$(pwd),這種方法采用命令行方法不太方便,一種替代方法
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^註釋^^^^^^^^^^^^^^^^^^^^^^^^^^^^
LD_LIBRARY_PATH可以在/etc/profile還是 ~/.profile還是 ./bash_profile裏設置,或者.bashrc裏,

改完後運行source /etc/profile或 . /etc/profile
更好的辦法是添入/etc/ld.so.conf, 然後執行 /sbin/ldconfig


^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^註釋^^^^^^^^^^^^^^^^^^^^^^^^^^^^
是把庫路徑添加到/etc/ld.so.conf,然後以root身份運行ldconfig
也可以在連接的時候指定文件路徑和名稱 -I -L.

GCC=gcc
CFLAGS=-Wall -ggdb -fPIC
#CFLAGS=
all: libfunc test
libfunc:func.o func1.o
$(GCC) -shared -Wl,-soname,libfunc.so.1 -o libfunc.so.1.1 $<
ln -sf libfunc.so.1.1 libfunc.so.1
ln -sf libfunc.so.1 libfunc.so
***********************************************註釋************************************************
ln -s是用來創建軟鏈接,也就相當於windows中的快捷方式,在當前目錄中創建上一級目錄中的文件ttt的命名為ttt2軟鏈接的命令是ln -s ../ttt ttt2,如果原文件也就是ttt文件刪除的話,ttt2也變成了空文件。
ln -d是用來創建硬鏈接,也就相當於windows中文件的副本,當原文件刪除的時候,並不影響“副本”的內容。

編譯目標文件時使用gcc的-fPIC選項,產生與位置無關的代碼並能被加載到任何地址:
gcc –fPIC –g –c liberr.c –o liberr.o

使用gcc的-shared和-soname選項;
使用gcc的-Wl選項把參數傳遞給連接器ld;
使用gcc的-l選項顯示的連接C庫,以保證可以得到所需的啟動(startup)代碼,從而避免程序在使用不同的,可能不兼容版本的C庫的系統上不能啟動執行。
gcc –g –shared –Wl,-soname,liberr.so –o liberr.so.1.0.0 liberr.o –lc

建立相應的符號連接:
ln –s liberr.so.1.0.0 liberr.so.1;
ln –s liberr.so.1.0.0 liberr.so;

在MAKEFILE中:
$@
表示規則中的目標文件集。在模式規則中,如果有多個目標,那麽,"$@"就是匹配於目標中模式定義的集合。
$%
僅當目標是函數庫文件中,表示規則中的目標成員名。例如,如果一個目標是"foo.a(bar.o)",那麽,"$%"就是"bar.o","$@"就是"foo.a"。如果目標不是函數庫文件(Unix下是[.a],Windows下是[.lib]),那麽,其值為空。
$<
依賴目標中的第一個目標名字。如果依賴目標是以模式(即"%")定義的,那麽"$<"將是符合模式的一系列的文件集。註意,其是一個一個取出來的。
$?
所有比目標新的依賴目標的集合。以空格分隔。
$^
所有的依賴目標的集合。以空格分隔。如果在依賴目標中有多個重復的,那個這個變量會去除重復的依賴目標,只保留一份。
*********************************************註釋***********************************************************************
test: test.o libfunc
$(GCC) -o test test.o -L. -lfunc
%.o:%.c
$(GCC) -c $(CFLAGS) -o $@ $<
clean:
rm -fr *.o
rm -fr *.so*
rm -fr test

要生成.so文件,cc要帶-shared 參數;要調用.so的文件,比如libfunc.so,可以在cc命令最後加上-lfunc,還要視情況加上-L/usr/xxx 指出libfunc.so的路徑;這樣,在你要編譯的源文件中就可以調用libfunc.so這個庫文件的函數.
前面的都說的差不多了,最後提醒一下最好提供一個接口頭文件
動態加載,用dlopen,dlclose,dlsym



ref:http://niefei.blog.ccidnet.com/blog/ccid/do_showone/tid_42855.html
1. 介紹

  使用GNU的工具我們如何在Linux下創建自己的程序函數庫?一個“程序 函數庫”簡單的說就是一個文件包含了一些編譯好的代碼和數據,這些編譯好的代碼和數據可以在事後供其他的程序使用。程序函數庫可以使整個程序更加模塊化,更容易重新編譯,而且更方便升級。程序函數庫可分為3種類型:靜態函數庫(static libraries)、共享函數庫(shared libraries)和動態加載函數庫(dynamically loadedlibraries)。

  靜態函數庫是在程序執行前就加入到目標程序中去了;而共享函數庫則是在程序啟動的時候加載到程序中,它可以被不同的程序共享;動態加載函數庫則可以在程序運行的任何時候動態的加載。實際上,動態函數庫並非另外一種庫函數格式,區別是動態加載函數庫是如何被程序員使用的。後面我們將舉例說明。

  本文檔主要參考Program Library HOWTO,作者是luster([email protected]),任何非商業目的的再次發行本文檔都是允許的,但是請保留作者信息和本版權聲明。本文檔首先在www.linuxaid.com.cn發布。

  2. 靜態函數庫

  靜態函數庫實際上就是簡單的一個普通的目標文件的集合,一般來說習慣用“.a”作為文件的後綴。可以用ar這個程序來產生靜態函數庫文件。Ar是archiver的縮寫。靜態函數庫現在已經不在像以前用得那麽多了,主要是共享函數庫與之相比較有很多的優勢的原因。慢慢地,大家都喜歡使用共享函數庫了。不過,在一些場所靜態函數庫仍然在使用,一來是保持一些與以前某些程序的兼容,二來它描述起來也比較簡單。

  靜態庫函數允許程序員把程序link起來而不用重新編譯代碼,節省了重新編譯代碼的時間。不過,在今天這麽快速的計算機面前,一般的程序的重新編譯也花費不了多少時間,所以這個優勢已經不是像它以前那麽明顯了。靜態函數庫對開發者來說還是很有用的,例如你想把自己提供的函數給別人使用,但是又想對函數的源代碼進行保密,你就可以給別人提供一個靜態函數庫文件。理論上說,使用ELF格式的靜態庫函數生成的代碼可以比使用共享函數庫(或者動態函數庫)的程序運行速度上快一些,大概1-5%。

  創建一個靜態函數庫文件,或者往一個已經存在地靜態函數庫文件添加新的目標代碼,可以用下面的命令:



ar rcs my_library.a file1.o file2.o

  這個例子中是把目標代碼file1.o和file2.o加入到my_library.a這個函數庫文件中,如果my_library.a不存在則創建一個新的文件。在用ar命令創建靜態庫函數的時候,還有其他一些可以選擇的參數,可以參加ar的使用幫助。這裏不再贅述。

  一旦你創建了一個靜態函數庫,你可以使用它了。你可以把它作為你編譯和連接過程中的一部分用來生成你的可執行代碼。如果你用gcc來編譯產生可執行代碼的話,你可以用“-l”參數來指定這個庫函數。你也可以用ld來做,使用它的“-l”和“-L”參數選項。具體用法,可以參考info:gcc。

 3. 共享函數庫

  共享函數庫中的函數是在當一個可執行程序在啟動的時候被加載。如果一個共享函數庫正常安裝,所有的程序在重新運行的時候都可以自動加載最新的函數庫中的函數。對於Linux系統還有更多的可以實現的功能:

o 升級了函數庫但是仍然允許程序使用老版本的函數庫。 o 當執行某個特定程序的時候可以覆蓋某個特定的庫或者庫中指定的函數。 o 可以在庫函數被使用的過程中修改這些函數庫。

  3.1. 一些約定

  如果你要編寫的共享函數庫支持所有有用的特性,你在編寫的過程中必須遵循一系列約定。你必須理解庫的不同的名字間的區別,例如它的“soname”和“realname”之間的區別和它們是如何相互作用的。你同樣還要知道你應該把這些庫函數放在你文件系統的什麽位置等等。下面我們具體看看這些問題。

  3.1.1. 共享庫的命名

  每個共享函數庫都有個特殊的名字,稱作“soname”。Soname名字命名必須以“lib”作為前綴,然後是函數庫的名字,然後是“.so”,最後是版本號信息。不過有個特例,就是非常底層的C庫函數都不是以lib開頭這樣命名的。

  每個共享函數庫都有一個真正的名字(“real name”),它是包含真正庫函數代碼的文件。真名有一個主版本號,和一個發行版本號。最後一個發行版本號是可選的,可以沒有。主版本號和發行版本號使你可以知道你到底是安裝了什麽版本的庫函數。

另外,還有一個名字是編譯器編譯的時候需要的函數庫的名字,這個名字就是簡單的soname名字,而不包含任何版本號信息。

  管理共享函數庫的關鍵是區分好這些名字。當可執行程序需要在自己的程序中列出這些他們需要的共享庫函數的時候,它只要用soname就可以了;反過來,當你要創建一個新的共享函數庫的時候,你要指定一個特定的文件名,其中包含很細節的版本信息。當你安裝一個新版本的函數庫的時候,你只要先將這些函數庫文件拷貝到一些特定的目錄中,運行ldconfig這個實用就可以。Ldconfig檢查已經存在的庫文件,然後創建soname的符號鏈接到真正的函數庫,同時設置/etc/ld.so.cache這個緩沖文件。這個我們稍後再討論。

  Ldconfig並不設置鏈接的名字,通常的做法是在安裝過程中完成這個鏈接名字的建立,一般來說這個符號鏈接就簡單的指向最新的soname或者最新版本的函數庫文件。最好把這個符號鏈接指向soname,因為通常當你升級你的庫函數的後,你就可以自動使用新版本的函數庫勒。

  我們來舉例看看:

  /usr/lib/libreadline.so.3是一個完全的完整的soname,ldconfig可以設置一個符號鏈接到其他某個真正的函數庫文件,例如是/usr/lib/libreadline.so.3.0。同時還必須有一個鏈接名字,例如/usr/lib/libreadline.so就是一個符號鏈接指向/usr/lib/libreadline.so.3。

3.1.2. 文件系統中函數庫文件的位置

  共享函數庫文件必須放在一些特定的目錄裏,這樣通過系統的環境變量設置,應用程序才能正確的使用這些函數庫。大部分的源碼開發的程序都遵循GNU的一些標準,我們可以看info幫助文件獲得相信的說明,info信息的位置是:info:standards#Directory_Variables。GNU標準建議所有的函數庫文件都放在/usr/local/lib目錄下,而且建議命令可執行程序都放在/usr/local/bin目錄下。這都是一些習慣問題,可以改變的。

  文件系統層次化標準FHS(Filesystem Hierarchy Standard)(http://www.pathname.com/fhs)規定了在一個發行包中大部分的函數庫文件應該安裝到/usr/lib目錄 下,但是如果某些庫是在系統啟動的時候要加載的,則放到/lib目錄下,而那些不是系統本身一部分的庫則放到/usr/local/lib下面。

  上面兩個路徑的不同並沒有本質的沖突。GNU提出的標準主要對於開發者開發源碼的,而FHS的建議則是針對發行版本的路徑的。具體的位置信息可以看/etc/ld.so.conf裏面的配置信息。

  3.2. 這些函數庫如何使用

  在基於GNU glibc的系統裏,包括所有的linux系統,啟動一個ELF格式的二進制可執行文件會自動啟動和運行一個programloader。對於Linux系統,這個loader的名字是/lib/ld-linux.so.X(X是版本號)。這個loader啟動後,反過來就會load所有的其他本程序要使用的共享函數庫。

  到底在哪些目錄裏查找共享函數庫呢?這些定義缺省的是放在/etc/ld.so.conf文件裏面,我們可以修改這個文件,加入我們自己的一些特殊的路徑要求。大多數RedHat系列的發行包的/etc/ld.so.conf文件裏面不包括/usr/local/lib這個目錄,如果沒有這個目錄的話,我們可以修改/etc/ld.so.conf,自己手動加上這個條目。

  如果你想覆蓋某個庫中的一些函數,用自己的函數替換它們,同時保留該庫中其他的函數的話,你可以在/etc/ld.so.preload中加入你想要替換的庫(.o結尾的文件),這些preloading的庫函數將有優先加載的權利。

  當程序啟動的時候搜索所有的目錄顯然會效率很低,於是Linux系統實際上用的是一個高速緩沖的做法。Ldconfig缺省情況下讀出/etc/ld.so.conf相關信息,然後設置適當地符號鏈接,然後寫一個cache到/etc/ld.so.cache這個文件中,而這個/etc/ld.so.cache則可以被其他程序有效的使用了。這樣的做法可以大大提高訪問函數庫的速度。這就要求每次新增加一個動態加載的函數庫的時候,就要運行ldconfig來更新這個cache,如果要刪除某個函數庫,或者某個函數庫的路徑修改了,都要重新運行ldconfig來更新這個cache。通常的一些包管理器在安裝一個新的函數庫的時候就要運行ldconfig。

  另外,FreeBSD使用cache的文件不一樣。FreeBSD的ELF cache是/var/run/ld-elf.so.hints,而a.out的cache責是/var/run/ld.so.hints。它們同樣是通過ldconfig來更新。

  3.3. 環境變量

  各種各樣的環境變量控制著一些關鍵的過程。例如你可以臨時為你特定的程序的一次執行指定一個不同的函數庫。Linux系統中,通常變量LD_LIBRARY_PATH就是可以用來指定函數庫查找路徑的,而且這個路徑通常是在查找標準的路徑之前查找。這個是很有用的,特別是在調試一個新的函數庫的時候,或者在特殊的場合使用一個肥標準的函數庫的時候。環境變量LD_PRELOAD列出了所有共享函數庫中需要優先加載的庫文件,功能和/etc/ld.so.preload類似。這些都是有/lib/ld-linux.so這個loader來實現的。值得一提的是,LD_LIBRARY_PATH可以在大部分的UNIX-linke系統下正常起作用,但是並非所有的系統下都可以使用,例如HP-UX系統下,就是用SHLIB_PATH這個變量,而在AIX下則使用LIBPATH這個變量。

  LD_LIBRARY_PATH在開發和調試過程中經常大量使用,但是不應該被一個普通用戶在安裝過程中被安裝程序修改,大家可以去參考 http://www.visi.com/~barr/ldpath.html,這裏有一個文檔專門介紹為什麽不使用LD_LIBRARY_PATH這個 變量。

  事實上還有更多的環境變量影響著程序的調入過程,它們的名字通常就是以LD_或者RTLD_打頭。大部分這些環境變量的使用的文檔都是不全,通常搞得人頭昏眼花的,如果要真正弄清楚它們的用法,最好去讀loader的源碼(也就是gcc的一部分)。

  允許用戶控制動態鏈接函數庫將涉及到setuid/setgid這個函數如果特殊的功能需要的話。因此,GNUloader通常限制或者忽略用戶對這些變量使用setuid和setgid。如果loader通過判斷程序的相關環境變量判斷程序的是否使用了setuid或者setgid,如果uid和euid不同,或者gid和egid部一樣,那麽loader就假定程序已經使用了setuid或者setgid,然後就大大的限制器控制這個老鏈接的權限。如果閱讀GNUglibc的庫函數源碼,就可以清楚地看到這一點,特別的我們可以看elf/rtld.c和sysdeps/generic/dl-sysdep.c這兩個文件。這就意味著如果你使得uid和gid與euid和egid分別相等,然後調用一個程序,那麽這些變量就可以完全起效。

3.4. 創建一個共享函數庫

  現在我們開始學習如何創建一個共享函數庫。其實創建一個共享函數庫非常容易。首先創建object文件,這個文件將加入通過gcc –fPIC參數命令加入到共享函數庫裏面。PIC的意思是“位置無關代碼”(Position Independent Code)。下面是一個標準的格式:



gcc -shared -Wl,-soname,your_soname -o library_name file_list library_list

  下面再給一個例子,它創建兩個object文件(a.o和b.o),然後創建一個包含a.o和b.o的共享函數庫。例子中”-g”和“-Wall”參數不是必須的。



gcc -fPIC -g -c -Wall a.cgcc -fPIC -g -c -Wall b.cgcc -shared -Wl,

-soname,liblusterstuff.so.1 -o liblusterstuff.so.1.0.1 a.o b.o -lc

  下面是一些需要註意的地方:

·不用使用-fomit-frame-pointer這個編譯參數除非你不得不這樣。雖然使用了這個參數獲得的函數庫仍然可以使用,但是這使得調試程序幾乎沒有用,無法跟蹤調試。 · 使用-fPIC來產生代碼,而不是-fpic。 · 某些情況下,使用gcc來生成object文件,需要使用“-Wl,-export-dynamic”這個選項參數。通常,動態函數庫的符號表裏面包含了這些動態的對象的符號。這個選項在創建ELF格式的文件時候,會將所有的符號加入到動態符號表中。可以參考ld的幫助獲得更詳細的說明。

  3.5. 安裝和使用共享函數庫

  一旦你了一個共享函數庫,你還需要安裝它。其實簡單的方法就是拷貝你的庫文件到指定的標準的目錄(例如/usr/lib),然後運行ldconfig。

  如果你沒有權限去做這件事情,例如你不能修改/usr/lib目錄,那麽你就只好通過修改你的環境變量來實現這些函數庫的使用了。首先,你需要創建這些共享函數庫;然後,設置一些必須得符號鏈接,特別是從soname到真正的函數庫文件的符號鏈接,簡單的方法就是運行ldconfig:



ldconfig -n directory_with_shared_libraries

  然後你就可以設置你的LD_LIBRARY_PATH這個環境變量,它是一個以逗號分隔的路徑的集合,這個可以用來指明共享函數庫的搜索路徑。例如,使用bash,就可以這樣來啟動一個程序my_program:



LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH my_program

  如果你需要的是重載部分函數,則你就需要創建一個包含需要重載的函數的object文件,然後設置LD_PRELOAD環境變量。通常你可以很方便的升級你的函數庫,如果某個API改變了,創建庫的程序會改變soname。然而,如果一個函數升級了某個函數庫而保持了原來的soname,你可以強行將老版本的函數庫拷貝到某個位置,然後重新命名這個文件(例如使用原來的名字,然後後面加.orig後綴),然後創建一個小的“wrapper”腳本來設置這個庫函數和相關的東西。例如下面的例子:



#!/bin/sh export LD_LIBRARY_PATH=/usr/local/my_lib:$LD_LIBRARY_PATH exec

/usr/bin/my_program.orig $*

  我們可以通過運行ldd來看某個程序使用的共享函數庫。例如你可以看ls這個實用工具使用的函數庫:



ldd /bin/ls

libtermcap.so.2 => /lib/libtermcap.so.2 (0x4001c000)

libc.so.6 => /lib/libc.so.6 (0x40020000)

/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)

  通常我麽可以看到一個soname的列表,包括路徑。在所有的情況下,你都至少可以看到兩個庫:



· /lib/ld-linux.so.N(N是1或者更大,一般至少2)。

這是這個用力加載其他所有的共享庫的庫。

· libc.so.N(N應該大於或者等於6)。這是C語言函數庫。

  值得一提的是,不要在對你不信任的程序運行ldd命令。在ldd的manual裏面寫得很清楚,ldd是通過設置某些特殊的環境變量(例如,對於ELF對象,設置LD_TRACE_LOADED_OBJECTS),然後運行這個程序。這樣就有可能使得某地程序可能使得ldd來執行某些意想不到的代碼,而產生不安全的隱患。

3.6. 不兼容的函數庫

  如果一個新版的函數庫要和老版本的二進制的庫不兼容,則soname需要改變。對於c語言,一共有4個基本的理由使得它們在二進制代碼上很難兼容:

  o. 一個函數的行文改變了,這樣它就可能與最開始的定義不相符合。

  o. 輸出的數據項改變了。

  o. 某些輸出的函數刪除了。

  o. 某些輸出函數的接口改變了。

  如果你能避免這些地方,你就可以保持你的函數庫在二進制代碼上的兼容,或者說,你可以使得你的程序的應用二進制接口(ABI:Application Binary Interface)上兼容。

  4. 動態加載的函數庫Dynamically Loaded (DL) Libraries

  動態加載的函數庫Dynamically loaded (DL)libraries是一類函數庫,它可以在程序運行過程中的任何時間加載。它們特別適合在函數中加載一些模塊和plugin擴展模塊的場合,因為它可以在當程序需要某個plugin模塊時才動態的加載。例如,Pluggable AuthenticationModules(PAM)系統就是用動態加載函數庫來使得管理員可以配置和重新配置身份驗證信息。

  Linux系統下,DL函數庫與其他函數庫在格式上沒有特殊的區別,我們前面提到過,它們創建的時候是標準的object格式。主要的區別就是這些函數庫不是在程序鏈接的時候或者啟動的時候加載,而是通過一個API來打開一個函數庫,尋找符號表,處理錯誤和關閉函數庫。通常C語言環境下,需要包含這個頭文件。

  Linux中使用的函數和Solaris中一樣,都是dlpoen()API。當時不是所有的平臺都使用同樣的接口,例如HP-UX使用shl_load()機制,而Windows平臺用另外的其他的調用接口。如果你的目的是使得你的代碼有很強的移植性,你應該使用一些wrapping函數庫,這樣的wrapping函數庫隱藏不同的平臺的接口區別。一種方法是使用glibc函數庫中的對動態加載模塊的支持,它使用一些潛在的動態加載函數庫界面使得它們可以誇平臺使用。具體可以參考http://developer.gnome.org/doc/API/glib/glib-dynamic-loading-of-modules.html.另外一個方法是使用libltdl,是GNU libtool的一部分,可以進一步參考CORBA相關資料。

  4.1. dlopen()

  dlopen函數打開一個函數庫然後為後面的使用做準備。C語言原形是:



void * dlopen(const char *filename, int flag);

  如果文件名filename是以“/”開頭,也就是使用絕對路徑,那麽dlopne就直接使用它,而不去查找某些環境變量或者系統設置的函數庫所在的目錄了。否則dlopen()

  就會按照下面的次序查找函數庫文件:

1. 環境變量LD_LIBRARY指明的路徑。 2. /etc/ld.so.cache中的函數庫列表。 3. /lib目錄,然後/usr/lib。不過一些很老的a.out的loader則是采用相反的次序,也就是先查/usr/lib,然後是/lib。

  Dlopen()函數中,參數flag的值必須是RTLD_LAZY或者RTLD_NOW,RTLD_LAZY的意思是resolveundefined symbols as code from the dynamic library isexecuted,而RTLD_NOW的含義是resolve all undefined symbols before dlopen()returns and fail if this cannot be done‘。

  如果有好幾個函數庫,它們之間有一些依賴關系的話,例如X依賴Y,那麽你就要先加載那些被依賴的函數。例如先加載Y,然後加載X。

  dlopen()函數的返回值是一個句柄,然後後面的函數就通過使用這個句柄來做進一步的操作。如果打開失敗dlopen()就返回一個NULL。如果一個函數庫被多次打開,它會返回同樣的句柄。

  如果一個函數庫裏面有一個輸出的函數名字為_init,那麽_init就會在dlopen()這個函數返回前被執行。我們可以利用這個函數在我的函數庫裏面做一些初始化的工作。我們後面會繼續討論這個問題的。

  4.2. dlerror()

  通過調用dlerror()函數,我們可以獲得最後一次調用dlopen(),dlsym(),或者dlclose()的錯誤信息。

4.3. dlsym()

  如果你加載了一個DL函數庫而不去使用當然是不可能的了,使用一個DL函數庫的最主要的一個函數就是dlsym(),這個函數在一個已經打開的函數庫裏面查找給定的符號。這個函數如下定義:



void * dlsym(void *handle, char *symbol);

  函數中的參數handle就是由dlopen打開後返回的句柄,symbol是一個以NIL結尾的字符串。

  如果dlsym()函數沒有找到需要查找的symbol,則返回NULL。如果你知道某個symbol的值不可能是NULL或者0,那麽就很好,你就可以根據這個返回結果判斷查找的symbol是否存在了;不過,如果某個symbol的值就是NULL,那麽這個判斷就有問題了。標準的判斷方法是先調用dlerror(),清除以前可能存在的錯誤,然後調用dlsym()來訪問一個symbol,然後再調用dlerror()來判斷是否出現了錯誤。一個典型的過程如下:



dlerror(); /* clear error code */

s = (actual_type) dlsym(handle, symbol_being_searched_for);

if ((err = dlerror()) != NULL)

{

/* handle error, the symbol wasn‘t found */

}

else

{

/* symbol found, its value is in s */

}

  4.4. dlclose()

  dlopen()函數的反過程就是dlclose()函數,dlclose()函數用力關閉一個DL函數庫。Dl函數庫維持一個資源利用的計數器,當調用dlclose的時候,就把這個計數器的計數減一,如果計數器為0,則真正的釋放掉。真正釋放的時候,如果函數庫裏面有_fini()這個函數,則自動調用_fini()這個函數,做一些必要的處理。Dlclose()返回0表示成功,其他非0值表示錯誤。

  4.5. DL Library Example

  下面是一個例子。例子中調入math函數庫,然後打印2.0的余弦函數值。例子中每次都檢查是否出錯。應該是個不錯的範例:



#include

#include

#include

int main(int argc, char **argv)

{

void *handle;

double (*cosine)(double);

char *error;

handle = dlopen ("/lib/libm.so.6", RTLD_LAZY);

if (!handle) {

fputs (dlerror(), stderr);

exit(1);

}

cosine = dlsym(handle, "cos");

if ((error = dlerror()) != NULL)

{

fputs(error, stderr);

exit(1);

}

printf ("%f ", (*cosine)(2.0));

dlclose(handle);

}

  如果這個程序名字叫foo.c,那麽用下面的命令來編譯:



gcc -o foo foo.c -ldl



5. 其他

  5.1. nm命令

  nm命令可以列出一個函數庫文件中的符號表。它對於靜態的函數庫和共享的函數庫都起作用。對於一個給定的函數庫,nm命令可以列出函數庫中定義的所有符號,包括每個符號的值和類型。還可以給出在原程序中這個函數(符號)是在多少行定義的,不過這必須要求編譯該函數庫的時候加“-l”選項。

  關於符號的類型,這裏我們再多討論一下。符號的類型是以一個字母的形式顯示的,小寫字母表示這個符號是本地(local)的,而大寫字母則表示這個符號是全局的(global,externel)。一般來說,類型有一下幾種:T、D、B、U、W。各自的含義如下:T表示在代碼段中定義的一般變量符號;D表示時初始化過的數據段;B表示初始化的數據段;U表示沒有定義的,在這個庫裏面使用了,但是在其他庫中定義的符號;W,weak的縮寫,表示如果其他函數庫中也有對這個符號的定義,則其他符號的定義可以覆蓋這個定義。

  如果你知道一個函數的名字,但是你不知道這個函數在什麽庫中定義的,那麽可以用mn的“-o”選項和grep命令來查找庫的名字。-o選項使得顯示的每一行都有這個函數庫文件名。例如,你要查找“cos”這個是在什麽地方定義的,大致可以用下面的命令:



nm -o /lib/* /usr/lib/* /usr/lib/*/* /usr/local/lib/* 2> /dev/null

| grep ‘cos$‘

  關於nm的更詳細的用法我們可以參考info文檔,位置是info:binutils#nm。

  5.2. 特殊函數_init和_fini

  函數庫裏面有兩個特殊的函數,_init和_fini,這個我們在前面已經說過了。主要是分別用來初始化函數庫和關閉的時候做一些必要的處理,我們可以把自己認為需要的代碼放到這兩個函數裏面,它們分別在函數庫被加載和釋放的時候被執行。具體說,如果一個函數庫裏面有一個名字為“_init”的函數輸出,那麽在第一次通過dlopen()函數打開這個函數庫,或者只是簡單的作為共享函數庫被打開的時候,_init函數被自動調用執行。與之相對應的就是_fini函數,當一個程序調用dlclose()去釋放對這個函數庫的引用的時候,如果該函數庫的被引用計數器為0了,或者這個函數庫是作為一般的共享函數庫被使用而使用它的程序正常退出的時候,_fini就會被調用執行。C語言定義它們的原型如下:



void _init(void); void _fini(void);

  當用gcc編譯源程序為“.o”文件的時候,需要加一個“-nostartfiles”選項。這個選項使得C編譯器不鏈接系統的啟動函數庫裏面的啟動函數。否則,就會得到一個“multiple-definition”的錯誤。

  5.3. 共享函數庫也可以使腳本(Scripts)

  GNU的loader允許使用特殊格式的腳本語言來寫一個函數庫。這對於那些需要間接包含其他函數庫的情況還是有用的。例如,下面是一個/usr/lib/libc.so的例子:



/* GNU ld script Use the shared library, but some functions are only in

the static library, so try that secondarily. */GROUP ( /lib/libc.so.6

/usr/lib/libc_nonshared.a )

  更多的信息可以參考texinfo文檔中關於ld鏈接的腳本部分。一般的信息還可以參考: info:ld#Options 和info:ld#Commands,也可以參考info:ld#Option Commands。

  5.4. GNU libtool

  如果你正在編譯的系統相很方便的移植到其他操作系統下,你可以使用GNU libtool來創建和安裝這個函數庫。GNUlibtool是一個函數庫支持的典型的腳本。Libtool隱藏了使用一個可移植的函數庫的負責性。Libtool提供了一個可以移植的界面來創建object文件,鏈接函數庫(靜態或者共享的),並且安裝這些庫。它還包含了libltdl,一個可移植的動態函數庫調入程序的wrapper。更多的詳細討論,可以在http://www.gnu.org/software/libtool/manual.html看到。

  5.5. 刪除一些符號

  在一個生產的文件中很多符號都是為了debug而包含的,占用了不少空間。如果空間不夠,而且這些符號也許不再需要,就可以將其中一些刪除。

  最好的方法就是先正常的生成你需要的object文件,然後debug和測試你需要的一些東西。一旦你完全測試完畢了,就可以用strip去刪除一些不需要的符號了。Strip命令可以使你很方便的控制刪除什麽符號,而保留什麽符號。Strip的具體用法可以參考其幫助文件。

  另外的方法就是使用GNU ld的選項“-S”和“-s”;“-S”會刪除一些debugger的符號,而“-s”則是將所有的符號信息都刪除。通常我們可以在gcc中加這樣的參數“-Wl,-S”和“-Wl,-s”來達到這個目的。

摘要

下面是一些例子,例子中我們會使用三種函數庫(靜態的、共享的和動態加載的函數庫)。文件libhello.c是一個函數庫,libhello.h是它的頭文件;demo_use.c則是一個使用了libhello函數庫的。Script_static和script_dynamic分別演示如何以靜態和共享方式使用函數庫,而後面的demo_dynamic.c和script_dynamic則表示演示如何以動態加載函數庫的方式來使用它。

(2002-08-25 17:38:37)

By Wing

  6. 更多的例子

  下面是一些例子,例子中我們會使用三種函數庫(靜態的、共享的和動態加載的函數庫)。文件libhello.c是一個函數庫,libhello.h是它的頭文件;demo_use.c則是一個使用了libhello函數庫的。Script_static和script_dynamic分別演示如何以靜態和共享方式使用函數庫,而後面的demo_dynamic.c和script_dynamic則表示演示如何以動態加載函數庫的方式來使用它。

  6.1. File libhello.c



/* libhello.c - demonstrate library use. */

#include

void hello(void)

{

printf("Hello, library world.

");

}

  6.2. File libhello.h



/* libhello.h - demonstrate library use. */

void hello(void);

  6.3. File demo_use.c



/* demo_use.c -- demonstrate direct use of the "hello" routine */

#include "libhello.h"

int main(void)

{

hello();

return 0;

}

  6.4. File script_static



#!/bin/sh

# Static library demo

# Create static library‘s object file, libhello-static.o.

# I‘m using the name libhello-static to clearly

# differentiate the static library from the

# dynamic library examples, but you don‘t need to use

# "-static" in the names of your

# object files or static libraries.gcc -Wall -g -c -o libhello-static.o

libhello.c

# Create static library.ar rcs libhello-static.a libhello-static.o

# At this point we could just copy libhello-static.a

# somewhere else to use it.

# For demo purposes, we‘ll just keep the library

# in the current directory.

# Compile demo_use program file.gcc -Wall -g -c demo_use.c -o demo_use.o

# Create demo_use program; -L. causes "." to be searched during

# creation of the program. Note that this command causes

# the relevant object file in libhello-static.a to be

# incorporated into file demo_use_static.gcc -g -o demo_use_static

demo_use.o -L. -lhello-static

# Execute the program../demo_use_static

  6.5. File script_shared



#!/bin/sh

# Shared library demo

# Create shared library‘s object file, libhello.o.gcc -fPIC -Wall

-g -c libhello.c

# Create shared library.

# Use -lc to link it against C library, since libhello

# depends on the C library.gcc -g -shared -Wl,-soname,libhello.so.0 -o

libhello.so.0.0 libhello.o -lc# At this point we could just copy

libhello.so.0.0 into

# some directory, say /usr/local/lib.

# Now we need to call ldconfig to fix up the symbolic links.

# Set up the soname. We could just execute:

# ln -sf libhello.so.0.0 libhello.so.0

# but let‘s let ldconfig figure it out./sbin/ldconfig -n .

# Set up the linker name.

# In a more sophisticated setting, we‘d need to make

# sure that if there was an existing linker name,

# and if so, check if it should stay or not.ln -sf libhello.so.0

libhello.so

# Compile demo_use program file.gcc -Wall -g -c demo_use.c -o

demo_use.o

# Create program demo_use.

# The -L. causes "." to be searched during creation

# of the program; note that this does NOT mean that "."

# will be searched when the program is executed.gcc -g -o demo_use

demo_use.o -L. -lhello

# Execute the program. Note that we need to tell the program

# where the shared library is,

using LD_LIBRARY_PATH.LD_LIBRARY_PATH="." ./demo_use

  6.6. File demo_dynamic.c



/* demo_dynamic.c -- demonstrate dynamic loading and

use of the "hello" routine */

/* Need dlfcn.h for the routines to

dynamically load libraries */

#include

#include

#include

/* Note that we don‘t have to include "libhello.h".

However, we do need to specify something related;

we need to specify a type that will hold the value

we‘re going to get from dlsym(). */

/* The type "simple_demo_function" describes a function that

takes no arguments, and returns no value: */

typedef void (*simple_demo_function)(void);

int main(void)

{

const char *error;

void *module;

simple_demo_function demo_function;

/* Load dynamically loaded library */

module = dlopen("libhello.so", RTLD_LAZY);

if (!module)

{

fprintf(stderr, "Couldn‘t open libhello.so: %s

",dlerror());

exit(1);

}

/* Get symbol */

dlerror();

demo_function = dlsym(module, "hello");

if ((error = dlerror()))

{

fprintf(stderr, "Couldn‘t find hello: %s

", error);

exit(1);

}

/* Now call the function in the DL library */

(*demo_function)();

/* All done, close things cleanly */

dlclose(module);

return 0;

}

  6.7. File script_dynamic



#!/bin/sh

# Dynamically loaded library demo

# Presume that libhello.so and friends have

# been created (see dynamic example).

# Compile demo_dynamic program file into an object file.gcc

-Wall -g -c demo_dynamic.c

# Create program demo_use.

# Note that we don‘t have to tell it where to search

for DL libraries,

# since the only special library this program uses won‘t be

# loaded until after the program starts up.

# However, we DO need the option -ldl to include the library

# that loads the DL libraries.gcc -g -o demo_dynamic

demo_dynamic.o -ldl

# Execute the program. Note that we need to tell the
# program where get the dynamically loaded library,
# using LD_LIBRARY_PATH.LD_LIBRARY_PATH="." ./demo_dynamic


轉載: 原作者發表文章的具體地址沒找到,從別人轉載的blog中轉載而來! 感謝原作者的分享!

gcc 生成動態鏈接庫