dll和so檔案區別與構成

查志強發表於2014-06-20

【原文:http://blog.csdn.net/educast/article/details/9304015

動態連結,在可執行檔案裝載時或執行時,由作業系統的裝載程式載入庫。大多數作業系統將解析外部引用(比如庫)作為載入過程的一部分。在這些系統上,可執行檔案包含一個叫做import   directory的表,該表的每一項包含一個庫的名字。根據表中記錄的名字,裝載程式在硬碟上搜尋需要的庫,然後將其載入到記憶體中預先不確定的位置,之後根據載入庫後確定的庫的地址更新可執行程式。可執行程式根據更新後的庫資訊呼叫庫中的函式或引用庫中的資料。這種型別的動態載入成為裝載時載入   ,被包括Windows和Linux的大多數系統採用。裝載程式在載入應用軟體時要完成的最複雜的工作之一就是載入時連結。  
   
  其他作業系統可能在執行時解析引用。在這些系統上,可執行程式呼叫作業系統API,將庫的名字,函式在庫中的編號和函式引數一同傳遞。作業系統負責立即解析然後代表應用呼叫合適的函式。這種動態連結叫做執行時連結   。因為每個呼叫都會有系統開銷,執行時連結要慢得多,對應用的效能有負面影響。現代作業系統已經很少使用執行時連結。  
   
  可以動態連結的庫,在Windows上是dynamic   link   library   (DLL),在UNIX或Linux上是Shared   Library。庫檔案是預先編譯連結好的可執行檔案,儲存在計算機的硬碟上。大多數情況下,同一時間多個應用可以使用一個庫的同一份拷貝,作業系統不需要載入這個庫的多個例項。  
   
  Windows   和   Linux   的載入時連結是由作業系統來完成的,格式在不同的系統下有不同的區別,但是原理還是一樣的。
linux下檔案的型別是不依賴於其字尾名的,但一般來講:
.o,是目標檔案,相當於windows中的.obj檔案
.so 為共享庫,是shared object,用於動態連線的,和dll差不多
.a為靜態庫,是好多個.o合在一起,用於靜態連線
.la為libtool自動生成的一些共享庫,vi編輯檢視,主要記錄了一些配置資訊。可以用如下命令檢視*.la檔案的格式   $file *.la
      *.la: ASCII English text
所以可以用vi來檢視其內容。
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
建立.a庫檔案和.o庫檔案:
[yufei@localhost perl_c2]$ pwd
/home/yufei/perl_c2
[yufei@localhost perl_c2]$ cat mylib.c
#include <stdio.h>
#include <string.h>
void hello(){
        printf("success call from perl to c library\n");
}
[yufei@localhost perl_c2]$ cat mylib.h
extern void hello();
[yufei@localhost perl_c2]$ gcc -c mylib.c
[yufei@localhost perl_c2]$ dir
mylib.c  mylib.h  mylib.o
[yufei@localhost perl_c2]$ ar -r mylib.a mylib.o
ar: 正在建立 mylib.a
[yufei@localhost 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 loaded libraries)。
  靜態函式庫是在程式執行前就加入到目標程式中去了;而共享函式庫則是在程式啟動的時候載入到程式中,它可以被 不同的程式共享;動態載入函式庫則可以在程式執行的任何時候動態的載入。實際上,動態函式庫並非另外一種庫函式格式,區別是動態載入函式庫是如何被程式設計師 使用的。後面我們將舉例說明。
  本文件主要參考Program Library HOWTO,作者是luster(hwang@ustc.edu),任何非商業目的的再次發行本文件都是允許的,但是請保留作者資訊和本版權宣告。本文件首先在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”和“real name”之間的區別和它們是如何相互作用的。你同樣還要知道你應該把這些庫函式放在你檔案系統的什麼位置等等。下面我們具體看看這些問題。
  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格式的二進位制可執行檔案會自動啟動和執行一個program loader。對於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這個函式如果特殊的功能需要的話。因此,GNU loader通常限制或者忽略使用者對這些變數使用setuid和setgid。如果loader通過判斷程式的相關環境變數判斷程式的是否使用了 setuid或者setgid,如果uid和euid不同,或者gid和egid部一樣,那麼loader就假定程式已經使用了setuid或者 setgid,然後就大大的限制器控制這個老連結的許可權。如果閱讀GNU glibc的庫函式原始碼,就可以清楚地看到這一點,特別的我們可以看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 Authentication Modules(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的意思是resolve undefined symbols as code from the dynamic library is executed,而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();
s = (actual_type) dlsym(handle, symbol_being_searched_for);
if ((err = dlerror()) != NULL)
{
}
else
{
}
  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/local/libGROUP ( /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來建立和安裝這個函式庫。GNU libtool是一個函式庫支援的典型的指令碼。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
#include
void hello(void)
{
printf("Hello, library world.
");
}
  6.2. File libhello.h
void hello(void);
  6.3. File demo_use.c
#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
#include
#include
#include
typedef void (*simple_demo_function)(void);
int main(void)
{
const char *error;
void *module;
simple_demo_function demo_function;
module = dlopen("libhello.so", RTLD_LAZY);
if (!module)
{
  fprintf(stderr, "Couldn't open libhello.so: %s
",dlerror());
  exit(1);
}
dlerror();
demo_function = dlsym(module, "hello");
if ((error = dlerror()))
{
  fprintf(stderr, "Couldn't find hello: %s
", error);
  exit(1);
}
(*demo_function)();
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

相關文章