LINUX下動態庫及版本號控制


針對同一動態組件的不同版本鏈接和加載。

一、概念         
         DLL HELL字面意思是DLL"災難",是由於com組件(動態庫)升級引起的程序不能運行的情況。
        原因
         有三種可能的原因導致了DLL Hell的發生:
                一是由使用舊版本的DLL替代原來一個新版本的DLL而引起的。這個原因最普遍,是Windows 9X用戶通常遇到的DLL錯誤之一。
                二是由新版DLL中的函數無意發生改變而引起。盡管在設計DLL時候應該向下兼容,然而要保證DLL完全向下兼容卻是不能的。
                三是由新版DLL的安裝引入一個新的Bug。

二、linux下的解決方案——命名規范       
       Linux 上的Dll ,叫sharedlibrary。Linux 系統面臨和Window一樣的問題,如何控制動態庫的多個版本問題。為解決這個問題,Linux 為解決這個問題,引入了一套命名機制,如果遵守這個機制來做,就可以避免這個問題。但是這只事一個約定,不是強制的。但是建議遵守這個約定,否則同樣也會出現 Linux 版的Dll hell 問題。

Real Name        
          首先是共享庫本身的文件名:共享庫的命名必須如 libname.so.x.y.z最前面使用前綴”lib”,中間是庫的名字和后綴”.so”,最后三個數字是版本號。x是主版本號(Major Version Number),y是次版本號(Minor Version Number),z是發布版本號(Release Version Number)。

主版本號(不兼容):重大升級,不同主版本的庫之間的庫是不兼容的。所以如果要保證向后兼容就不能刪除舊的動態庫的版本。

次版本號(向下兼容): 增量升級,增加一些新的接口但保留原有接口。高次版本號的庫向后兼容低次版本號的庫。

發布版本號(相互兼容):庫的一些諸如錯誤修改、性能改進等,不添加新接口,也不更改接口。主版本號和次版本號相同的前提下,不同發布版本之間完全兼容。

SO-NAME       
         嚴格遵守上述規定,確實能避免動態庫因為版本沖突的問題,但是讀者可能有疑問:在程序加載或運行的時候,動態鏈接器是如何知道程序依賴哪些庫,如何選擇庫的不同版本?
Solaris和Linux等采用SO-NAME( Shortfor shared object name )的命名機制來記錄共享庫的依賴關系。每個共享庫都有一個對應的“SO-NAME”(共享庫文件名去掉次版本號和發布版本號)。比如一個共享庫名為libtest.so.3.8.2,那么它的SO-NAME就是libtest.so.3。

在Linux系統中,系統會為每個共享庫所在的目錄創建一個跟SO-NAME相同的並且指向它的軟連接(Symbol Link)。這個軟連接會指向目錄中主版本號相同、次版本號和發布版本號最新的共享庫。也就是說,比如目錄中有兩個共享庫版本分別為:/lib/libtest.so.3.8.2和/lib/libtest.so.3.7.5,么軟連接/lib/libtest.so.3指向/lib/libtest.so.3.8.2。

建立以SO-NAME為名字的軟連接的目的是,使得所有依賴某個共享庫的模塊,在編譯、鏈接和運行時,都使用共享庫的SO-NAME,而不需要使用詳細版本號。在編譯生產ELF文件時候,如果文件A依賴於文件B,那么A的鏈接文件中的”.dynamic”段中會有DT_NEED類型的字段,字段的值就是B的SO-NAME。這樣當動態鏈接器進行共享庫依賴文件查找時,就會依據系統中各種共享庫目錄中的SO-NAME軟連接自動定向到最新兼容版本的共享庫。

★  readelf -d sharelibrary 可以查看so-name
★  Linux提供了一個工具——ldconfig,當系統中安裝或更新一個共享庫時,需要運行這個工具,它會遍歷默認所有共享庫目錄,比如/lib,/usr/lib等,然后更新所有的軟鏈接,使她們指向最新共享庫。

Link Name 
       當我們在編譯器里使用共享庫的時候,如用GCC的“-l”參數鏈接共享庫libtXXX.so.3.8.1,只需要在編譯器命令行指定 -l XXX 即可,省略了前綴和版本信息。編譯器會根據當前環境,在系統中的相關路徑(往往由-L參數指定)查找最新版本的XXX庫。這個XXX就是共享庫的“鏈接名”。不同類型的庫可能有相同的鏈接名,比如C語言運行庫有靜態版本(libc.a)也動態版本(libc.so.x.y.z)的區別,如果在鏈接時使用參數”-lc”,那么連接器就會根據輸出文件的情況(動態/靜態)來選擇合適版本的庫。eg. ld使用“-static”參數時嗎,”-lc”會查找libc.a;如果使用“-Bdynamic”(默認),會查找最新版本的libc.so.x.y.z。

更詳細可以參見

http://www.linuxidc.com/Linux/2012-04/59071.htm

 

代碼:       

1. File libhello.c

/* hello.c - demonstrate library use. */
#include <stdio.h>
void hello(void)
{ printf("Hello, library world./n");}

2. File libhello.h

/* libhello.h - demonstrate library use. */
void hello(void);

3. File main.c

/* main.c -- demonstrate direct use of the "hello" routine */
#include "hello.h"
int main(void)
{
hello();
return 0;
}

1.生成共享庫,關聯real name 和soname 。

     gcc -g -Wall -fPIC -c hello.c -o hello.o

     gcc -shared -W,soname,-libhello.so.0 -o libhello.so.0.0.0 hello.o

     將會生成共享庫libhello.so.0.0.0.

     可以用系統提供的工具查看共享庫的頭:

      readelf -d libhello.so.0.0.0 | grep libhello

ox00000000000e(SONAME)    library soname: [libhello.so.0]

2.應用程序,引用共享庫。

      先手動生成link 名字,以被后面的程序鏈接時用

      ln -s libhello.so.0.0.0 libhello.so.0

      gcc -g -Wall -c main.c -o main.o -I.

      gcc  -o main main.o -lhello -L.

(這里我會出問題。因為:執行gcc  -o main main.o -lhello -L.命令的時候,默認會找libhello.so這個文件,但是顯然沒有,直接出錯。我又執行:

ln -s libhello.so.0.0.0 libhello.so 之后,才可以,到現在,沒明白為什么?)

      查看編譯出來的程序:

      readelf -d main | grep libhello

ox000000000001(NEEDED)    shared library: [libhello.so.0]

      運行該程序,需要指定共享庫的路徑。 有兩種辦法,第一種使用環境變量“LD_LIBRARY_PATH”. 兩外一種辦法就是將共享庫拷貝到系統目錄(path 環境變量指定的其中一個目錄)。

      暫停! 我們還沒有解決一個問題是,程序只知道soname,怎么從soname 找到共享庫,即real name 文件呢? 這需要我們定義一個link文件,連接到共享庫本身。

      ln -s libhello.so.0.0.0 libhello.so.0

      當然這個路徑需要放到LD_LIBRARY_PATH環境變量中。

     這樣就可以運行該程序。

[Note]Linux 系統提供一個命令 ldconifg 專門為生成共享庫的soname 文件,以便程序在加載時后通過soname 找到共享庫。 同時該命令也為加速加載共享庫,把系統的共享庫放到一個緩存文件中,這樣可以提高查找速度。可以用下面命令看一下系統已有的被緩存起來的共享庫。

     ld -p

運行程序:

./main這樣,直接運行是不行了。動態庫必須在運行的時候,也指定路徑。所以,請將.so文件,復制到/lib或者/usr/lib下,然后執行 ldconfig /lib命令就OK了。

ldconfig是一個動態鏈接庫管理命令
為了讓動態鏈接庫為系統所共享,還需運行動態鏈接庫的管理命令--ldconfig
ldconfig  命令的用途,主要是在默認搜尋目錄(/lib和/usr/lib)以及動態庫配置文件/etc/ld.so.conf內所列的目錄下,搜索出可共享的動態 鏈接庫(格式如前介紹,lib*.so*),進而創建出動態裝入程序(ld.so)所需的連接和緩存文件.緩存文件默認為  /etc/ld.so.cache,此文件保存已排好序的動態鏈接庫名字列表.
ldconfig通常在系統啟動時運行,而當用戶安裝了一個新的動態鏈接庫時,就需要手工運行這個命令.)

3.共享庫,小版本升級,即接口不變.

   當升級小版本時,共享庫的soname 是不變的,所以需要重新把soname 的那個連接文件指定新版本就可以。 調用ldconfig命令,系統會幫你做修改那個soname link文件,並把它指向新的版本呢。這時候你的應用程序就自動升級了。

4.共享庫,主版本升級,即接口發生變化。

  當升級主版本時,共享庫的soname 就會加1.比如libhello.so.0.0.0 變為 libhello.so.1.0.0. 這時候再運行ldconfig 文件,就會發現生成兩個連接 文件。

    ln -s libhello.so.0---->libhello.so.0.0.0

    ln -s libhello.so.1----->libhello.so.1.0.0

盡管共享庫升級,但是你的程序依舊用的是舊的共享庫,並且兩個之間不會相互影響。

    問題是如果更新的共享庫只是增加一些接口,並沒有修改已有的接口,也就是向前兼容。但是這時候它的主版本號卻增加1. 如果你的應用程序想調用新的共享庫,該怎么辦? 簡單,只要手工把soname 文件修改,使其指向新的版本就可以。(這時候ldconfig 文件不會幫你做這樣的事,因為這時候soname 和real name 的版本號主板本號不一致,只能手動修改)。

  比如: ln -s libhello.so.0 ---> libhello.so.1.0.0

  但是有時候,主版本號增加,接口發生變化,可能向前不兼容。這時候再這樣子修改,就會報錯,“xx”方法找不到之類的錯誤。

總結一下,Linux 系統是通過共享庫的三個不同名字,來管理共享庫的多個版本。 real name 就是共享庫的實際文件名字,soname 就是共享庫加載時的用的文件名。在生成共享庫的時候,編譯器將soname 綁定到共享庫的文件頭里,二者關聯起來。 在應用程序引用共享庫時,其通過link name 來完成,link時將按照系統指定的目錄去搜索link名字找到共享庫,並將共享庫的soname寫在應用程序的頭文件里。當應用程序加載共享庫時,就會通過soname在系統指定的目錄(path or LD_LIBRARY)去尋找共享庫。

當共享庫升級時,分為兩種。一種是主板本不變,升級小版本和build 號。在這種情況下,系統會通過更新soname( ldconfig 來維護),來使用新的版本號。這中情況下,舊版本就沒有用,可以刪掉。

另外一種是主版本升級,其意味着庫的接口發生變化,當然,這時候不能覆蓋已有的soname。系統通過增加一個soname(ldconfig -p 里面增加一項),使得新舊版本同時存在。原有的應用程序在加載時,還是根據自己頭文件的舊soname 去尋找老的庫文件。

5.如果編譯的時候沒有指定,共享庫的soname,會怎么樣?

  這是一個trick 的地方。第一系統將會在生成庫的時候,就沒有soname放到庫的頭里面。從而應用程序連接時候,就把linkname 放到應用程序依賴庫里面。或者換句話說就是,soname這時候不帶版本號。 有時候有人直接利用這點來升級應用程序,比如,新版本的庫,直接拷貝到系統目錄下,就會覆蓋掉已經存在的舊的庫文件,直接升級。 這個給程序員很大程度的便利性,如果一步小心,就會調到類似windows的Dll hell 陷阱里面。建議不要這樣做。

【Note】

  1. 指定共享庫加載的路徑。LD_LIBRARY_PATH 優先於 path 環境變量。

  2. ldd 可以查看程序,或者共享庫依賴的庫的路徑

  3. nm 查看共享庫暴露的接口

  4. ldconfig 可以自動生成soname 的連接文件。並提供catch 加速查找。

  5.readelf 可以查看動態庫的信息,比如依賴的庫,本身的soname。

  6. objdump 與readelf 類似。

  7 ld The GUN linker

  8. ld.so  dynamic linker or loader

  9. as the portable GNU assembley

【Reference】

http://tldp.org/HOWTO/Program-Library-HOWTO/shared-libraries.html

 

http://m.oschina.net/blog/367397


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM