1. 程式人生 > >再談Linux下的動態庫

再談Linux下的動態庫

為了解決上一篇的部落格《Linux下靜態庫、動態庫的建立和使用》最後留下的問題,今天總結一下Linux下動態庫

版本號的控制。

一、動態庫版本號的組成
    對於上一篇部落格中提到的庫檔案libcurl.4.3.0,其中4代表主版本號,3代表次版本號,0代表發行版本號,
    因此動態庫的命名形式為:libname.x.y.z
    x -- 主版本號(不相容):重大升級,不同主版本的庫之間的庫是不相容的。所以如果要保證向後相容就不能刪除舊             的動態庫的版本。

    y -- 次版本號(向下相容): 增量升級,增加一些新的介面但保留原有介面。高次版本號的庫向後相容低次版本號的               庫。
    z -- 釋出版本號(相互相容):庫的一些諸如錯誤修改、效能改進等,不新增新介面,也不更改介面。主版本號和次            版本號相同的前提下,不同釋出版本之間完全相容。
    
二、動態庫的3個名字
1、real name 
        動態庫的real name即動態庫本身的檔名,例如libcurl.so.4.3.0
2、soname (short for shared object name)
         動態庫的soname即是libname.so.x,顯然是保留了主版本號,例如libcurl.so.4。
3、linker name
        動態庫的linker name即連結名,libname.so,去掉了後面的版本號,例如libcurl.so。


三、建立帶有soname的動態庫
        在我的上一篇部落格中建立的動態庫是沒有版本號的,也沒有soname的。
        假如我要開發並維護一個動態庫,庫的版本控制是必要的,就要按照上述版本號命名的方法。
        gcc -c -fpic mylib.c -o mylib.o
        gcc -shared -Wl,-soname,libmylib.so.1 -o libmylib.so.1.0.0 mylib.o
        生成了庫檔案libmylib.so.1.0.0,然後執行命令:readelf -d libmylib.so.1.0.0
       結果如下:
  Dynamic section at offset 0x668 contains 21 entries:
  Tag        Type                         Name/Value
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
 0x000000000000000e (SONAME)             Library soname: [libmylib.so.1]
 0x000000000000000c (INIT)               0x468
 0x000000000000000d (FINI)               0x5c8
 0x000000006ffffef5 (GNU_HASH)           0x1b8
 0x0000000000000005 (STRTAB)             0x318
 0x0000000000000006 (SYMTAB)             0x1f8
 0x000000000000000a (STRSZ)              133 (bytes)
 0x000000000000000b (SYMENT)             24 (bytes)
 0x0000000000000003 (PLTGOT)             0x200810
 0x0000000000000002 (PLTRELSZ)           48 (bytes)
 0x0000000000000014 (PLTREL)             RELA
 0x0000000000000017 (JMPREL)             0x438
 0x0000000000000007 (RELA)               0x3d8
 0x0000000000000008 (RELASZ)             96 (bytes)
 0x0000000000000009 (RELAENT)            24 (bytes)
 0x000000006ffffffe (VERNEED)            0x3b8
 0x000000006fffffff (VERNEEDNUM)         1
 0x000000006ffffff0 (VERSYM)             0x39e
 0x000000006ffffff9 (RELACOUNT)          1
 0x0000000000000000 (NULL)               0x0

      由上面的結果可以看到,有一個SONAME欄位,Library soname:libmylib.so.1,由此得出結論,在編譯生成動態庫時,由於我指定了庫檔案 的soname為libmylib.so.1,編譯器會這個soname寫入庫檔案的頭部。還有一個NEEDED欄位表示該動態庫所依賴的其他動態庫,在本例中是依賴標C庫。
 
四、使用帶有SONAME的動態庫
          測試環境:CentOS6.5 x86_64   GCC 4.4.7
          執行pwd:/home/user/lib 
          執行ls: libmylib.so.1.0.0  main.c  mylib.c  mylib.h  mylib.o
          編譯:gcc main.c -o main -L. -lmylib
          編譯報錯:
         /usr/bin/ld: cannot find -lmylib
         collect2: ld returned 1 exit status
         這樣編譯無法通過,上篇部落格已經說明原因。
        這樣編譯:gcc main.c -o main libmylib.so.1.0.0  -Wl,-rpath=/home/user/lib
        可以通過,這樣是直接把libmylib.so.1.0.0當成目標檔案,完成連結過程,生成可執行程式main
        然後執行命令:readelf -d main
Dynamic section at offset 0x7a8 contains 22 entries:
  Tag        Type                         Name/Value
 0x0000000000000001 (NEEDED)             Shared library: [libmylib.so.1]
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
 0x000000000000000f (RPATH)              Library rpath: [/home/user/lib]
 0x000000000000000c (INIT)               0x400498
 0x000000000000000d (FINI)               0x4006b8
 0x000000006ffffef5 (GNU_HASH)           0x400260
 0x0000000000000005 (STRTAB)             0x400388
 0x0000000000000006 (SYMTAB)             0x400298
 0x000000000000000a (STRSZ)              146 (bytes)
 0x000000000000000b (SYMENT)             24 (bytes)
 0x0000000000000015 (DEBUG)              0x0
 0x0000000000000003 (PLTGOT)             0x600960
 0x0000000000000002 (PLTRELSZ)           48 (bytes)
 0x0000000000000014 (PLTREL)             RELA
 0x0000000000000017 (JMPREL)             0x400468
 0x0000000000000007 (RELA)               0x400450
 0x0000000000000008 (RELASZ)             24 (bytes)
 0x0000000000000009 (RELAENT)            24 (bytes)
 0x000000006ffffffe (VERNEED)            0x400430
 0x000000006fffffff (VERNEEDNUM)         1
 0x000000006ffffff0 (VERSYM)             0x40041a
 0x0000000000000000 (NULL)               0x0

        由以上執行結果可知,main依賴2個動態庫,分別是libmylib.so.1,libc.so.6
 顯然libmylib.so.1是libmylib.so.1.0.0的SONAME,由此得出結論:
 編譯器在連結動態庫時,會讀取庫的SONAME,並且寫在可執行檔案的頭部,那麼程式在執行時,
 會根據自己的頭部記錄的SONAME,去搜索對應的動態庫。其中RPATH欄位是動態庫搜尋路徑,程式
 執行時,會首先去這個路徑尋找所依賴的動態庫。

       但是此時,生成的main並不能執行,報錯如下:
       ./main: error while loading shared libraries: libmylib.so.1: cannot open shared object 
       file: No such file or directory
      為何不能執行呢?執行:ldd main  結果如下
 [[email protected] lib]# ldd main
  linux-vdso.so.1 =>  (0x00007fff7c3ff000)
libmylib.so.1 => not found
libc.so.6 => /lib64/libc.so.6 (0x00000034b0800000)

/lib64/ld-linux-x86-64.so.2 (0x00000034b0000000)

       原來libmylib.so.1這個檔案 not found,這個檔案確實沒有,只有libmylib.so.1.0.0,由此得出結論:
程式執行會很據自己頭部資訊去尋找這些以SONAME命名的庫檔案。

         上一篇部落格提到,使用動態庫有4種方法,上面我們使用的是最後一種方法,那麼再試試其他方法吧!!!

         編譯:gcc main.c -o main libmylib.so.1.0.0 ,然後再把/home/user/lib路徑加入/etc/ld.so.conf檔案中,

         執行:ldconfig

  檢視當前目錄有何變化,執行:ll
  total 32
  lrwxrwxrwx. 1 user user   17 Aug 26 15:32 libmylib.so.1 -> libmylib.so.1.0.0
  -rwxrwxr-x. 1 user user 5900 Aug 26 14:37 libmylib.so.1.0.0
  -rwxrwxr-x. 1 user user 6731 Aug 26 15:13 main
  -rw-r--r--. 1 user user  345 Aug 26 14:52 main.c
  -rw-rw-r--. 1 user user  340 Aug 26 14:22 mylib.c
  -rw-rw-r--. 1 user user  304 Aug 26 14:21 mylib.h
  -rw-rw-r--. 1 user user 1544 Aug 26 14:37 mylib.o

       由上面結果可以看到,當前目錄生成了一個名為libmylib.so.1軟連結,而且指向libmylib.so.1.0.0
此時在執行:ldd main
[[email protected] lib]# ldd main
linux-vdso.so.1 =>  (0x00007fff8ebff000)
libmylib.so.1 => /home/user/lib/libmylib.so.1 (0x00007fac4c78a000)
libc.so.6 => /lib64/libc.so.6 (0x00000034b0800000)
/lib64/ld-linux-x86-64.so.2 (0x00000034b0000000)

        可以看出,main頭部資訊裡記錄是:libmylib.so.1,因此執行時就去找這個檔案,於是找到了而這個文                         件/home/user/lib/libmylib.so.1,而它是個軟連結指向libmylib.so.1.0.0,於是就可以載入這個庫檔案了,

        程式可以正確運行了。

五、ldconfig有何用處?
        由上面可以看出,ldconfig作用就是生成了一個以SONAME命名並且指向庫檔案的軟連結。
        如果現在我的庫檔案更新了,增加一些介面,依然相容原來的版本,那麼庫檔名應該為:                                         libmylib.so.1.1.0,SONAME仍然是libmylib.so.1
        gcc -c -fpic mylib.c -o mylib.o
        gcc -shared -Wl,-soname,libmylib.so.1 -o libmylib.so.1.1.0 mylib.o
[[email protected] lib]# ll
  total 40
  lrwxrwxrwx. 1 root root   17 Aug 26 15:47 libmylib.so.1 -> libmylib.so.1.0.0
  -rwxrwxr-x. 1 user user 5900 Aug 26 14:37 libmylib.so.1.0.0
  -rwxr-xr-x. 1 root root 5900 Aug 26 15:59 libmylib.so.1.1.0
  -rwxrwxr-x. 1 user user 6731 Aug 26 15:13 main
  -rw-r--r--. 1 user user  345 Aug 26 14:52 main.c
  -rw-rw-r--. 1 user user  340 Aug 26 14:22 mylib.c
  -rw-rw-r--. 1 user user  304 Aug 26 14:21 mylib.h
  -rw-rw-r--. 1 user user 1544 Aug 26 14:37 mylib.o
      顯然多了一個庫檔案libmylib.so.1.1.0,然後再執行:ldconfig
[email protected] lib]# ll
  total 40
  lrwxrwxrwx. 1 root root   17 Aug 26 16:28 libmylib.so.1 -> libmylib.so.1.1.0
  -rwxrwxr-x. 1 user user 5900 Aug 26 14:37 libmylib.so.1.0.0
  -rwxr-xr-x. 1 root root 5900 Aug 26 15:59 libmylib.so.1.1.0
  -rwxrwxr-x. 1 user user 6731 Aug 26 15:13 main
  -rw-r--r--. 1 user user  345 Aug 26 14:52 main.c
  -rw-rw-r--. 1 user user  340 Aug 26 14:22 mylib.c
  -rw-rw-r--. 1 user user  304 Aug 26 14:21 mylib.h
  -rw-rw-r--. 1 user user 1544 Aug 26 14:37 mylib.o
          結果:軟連結libmylib.so.1不再指向libmylib.so.1.0.0,而是指向libmylib.so.1.1.0,這是為什麼呢?
顯然軟連結會指向一個最新的版本,程式就會載入到這個最新的庫,由於相同SONAME的庫高版本是向下相容           的, 因此可以最大程度的相容程式。
        如果我的動態庫又更新了,這次變化很大,刪除了一些以前的介面,那麼這次主版本號要變化了,庫檔名應該        為 libmylib.so.2.0.0, SONAME也變化為libmylib.so.2, 而且不相容以前的版本libmylib.so.1.x.y.
  gcc -c -fpic mylib.c -o mylib.o
  gcc -shared -Wl,-soname,libmylib.so.2 -o libmylib.so.2.0.0 mylib.o
  執行:ldconfig
  [[email protected] lib]# ll
  total 48
  lrwxrwxrwx. 1 root root   17 Aug 26 16:28 libmylib.so.1 -> libmylib.so.1.1.0
  -rwxrwxr-x. 1 user user 5900 Aug 26 14:37 libmylib.so.1.0.0
  -rwxr-xr-x. 1 root root 5900 Aug 26 15:59 libmylib.so.1.1.0
  lrwxrwxrwx. 1 root root   17 Aug 26 16:41 libmylib.so.2 -> libmylib.so.2.0.0
  -rwxr-xr-x. 1 root root 5900 Aug 26 16:41 libmylib.so.2.0.0
 -rwxrwxr-x. 1 user user 6731 Aug 26 15:13 main
  -rw-r--r--. 1 user user  345 Aug 26 14:52 main.c
  -rw-rw-r--. 1 user user  340 Aug 26 14:22 mylib.c
  -rw-rw-r--. 1 user user  304 Aug 26 14:21 mylib.h
  -rw-rw-r--. 1 user user 1544 Aug 26 14:37 mylib.o
   於是乎,又生成一個庫檔案libmylib.so.2.0.0 和一個軟連結 libmylib.so.2,顯然多個不同SONAME的庫檔案可以同時工作了,互不影響。
  結論: 1、ldconfig會遍歷/etc/ld.so.conf 中的目錄(也包括/lib,/usr/lib)下的庫檔案,根據讀取庫檔案的                           SONAME,會在庫檔案 所在目錄,生成一個以SONAME命名的軟連結並且指向這個庫檔案。
                      2、如果有多個SONAME相同的庫檔案,那麼這個軟連結只會指向一個最新版本的庫檔案,即次版本號                    最大的庫,若次版本號也相同,會指向釋出版本號最大的庫檔案。
                     3、ldconfig還會生成快取記錄在/etc/ld.so.cache中,程式尋找庫,會首先找這個cache檔案。

     關於linker name:

      我們在連結庫的時候,比如 gcc main.c -o main  -L. -lmylib,只需要使用庫的名字即可,

      如何才能做到這樣子呢?

      那就必須手動建立軟連結: ln -s libmylib.so.1 libmylib.so,生成軟連結libmylib.so指向libmylib.so.1,libmylib.so.1       也是一個軟連結。
      編譯引數 -L. -lmylib 表示在當前目錄尋找libmylib.so檔案,
      libmylib.so -> libmylib.so.1 -> libmylib.so.1.1.0 最終會連結到具體的庫檔案。
      當然 libmylib.so 也可以指向 libmylib.so.1,這個需要根據實際情況,選擇庫檔案的版本。

  六、解決上篇部落格的留下的問題
                 通過以上的分析,已經清楚了動態庫的載入細節,可以解決哪個問題了。
      問題: gcc main.c -o main /home/user/lib/libcurl.so.4.3.0  -Wl,-rpath=/home/user/lib 
     這種可以編譯通過,但是程式無法執行。
     解決方法:        方法一、在進入/home/user/lib目錄下,建立SONAME軟連結,ln -s libcurl.so.4.3.0 libcurl.so.4

                                   方法二、把libcurl.so.4.3.0檔案改名為libcurl.so.4,如果使用的庫不更新的話,可以使用這種方法。



上面講了那麼多,下面做一個要點總結:

     1、要解決不同版本庫依賴問題,要遵守上述的庫版本號命名約定。
     2、如何建立帶有SONAME的動態庫。
     3、編譯器在編譯連結動態庫時,會先讀取動態庫的SONAME,並把SONAME記錄在可執行檔案的頭部(還有一              種特殊情況,假入動態庫沒有SONAME,例如libmylib.so,那麼編譯器會把動態庫的檔名新增到可執行那個文            件的頭部,其實就是real name, 其實種情況,可以理解為real name,Soname,linker name 三者相同)
     4、程式執行時,會讀取自己頭部記錄的所依賴的庫檔名字,會根據這個名字去尋找同名的檔案(去哪裡尋找,            前面已經總結),找到該檔案後加載它,顯然大多時候,是找到一個軟連結,該軟連線指向具體的庫檔案,這            樣便可以載入成功。
     5、關於ldconfig的作用,這裡不再陳述。

由於筆者的水平有限,出錯在所難免,懇請讀者拍磚指正,謝謝閱讀。