動態庫加載路徑之RPATH與RUNPATH(小記)

  • 考慮翻譯Qt官方blog中的RPATH and RUNPATH這篇文章,在繼續之前,我需要先驗證自己的理解是正確的,至少能自圓其說,能說服自己。

用例子說話

 

二進制

對應源碼

有一個程序

a.out

main.c

需要加載插件A

libA.so

liba.c

A需要另一個動態庫

libB.so

libB1.c 或 libB2.c

本文的關注點就是:到底是哪一個libB.so被加載

目錄結構:

/home/debao/ttt/a.out
/home/debao/ttt/libA.so
/home/debao/ttt/libB.so
/usr/lib/libB.so

具體源碼

  • main.c ==> ./a.out

#include <stdio.h>
#include <dlfcn.h>
typedef int (*funcA)(int, int);
int main()
{
    void * plugin = dlopen("./libA.so", RTLD_LAZY);
    funcA f = (funcA)dlsym(plugin, "funcA");
    printf("main: %d\n", f(3,4));
    return 0;
}
  • liba.c ==> ./libA.so

#include <stdio.h>
int funcB(int, int);
int funcA(int a, int b)
{
    printf("hello from funcA\n");
    return funcB(a, b);
}
  • libb1.c ==> ./libB.so

#include <stdio.h>
int funcB(int a, int b)
{
    printf("Hello from funcB 1\n");
    return a*b;
}  
  • libb2.c ==> /usr/lib/libB.so

#include <stdio.h>
int funcB(int a, int b)
{
    printf("Hello from funcB 2\n");
    return a*b;
}  

編譯庫文件

  • 編譯動態庫libB.so

$ gcc -shared -fPIC libb2.c -o libB2.so
$ sudo mv libB2.so /usr/lib/libB.so
$ gcc -shared -fPIC libb.c -o libB.so
  • 編譯動態庫libA.so

$ gcc -shared -fPIC liba.c -o libA.so -L. -lB

順便看看該elf文件的頭部信息:

$ readelf libA.so -d

Dynamic section at offset 0xf20 contains 21 entries:
  Tag        Type      Name/Value
 0x00000001 (NEEDED)   Shared library: [libB.so]
 0x00000001 (NEEDED)   Shared library: [libc.so.6]
...

恩,只有庫的文件名信息,而沒有路徑信息。

編譯程序

  • 第一次編譯運行(什麼路徑都不加)

$ gcc main.c -ldl
$ ./a.out 
hello from funcA
Hello from funcB 2
main: 12

程序:dlopen從當前目錄找到libA.so,然後卻在/usr/lib/中找到libB.so(沒有使用當前目錄的libB.so,這是我們需要的麼?)

  • 第二次編譯運行(使用DT_RPATH)

$ gcc main.c -ldl  -Wl,--rpath=.
$ ./a.out 
hello from funcA
Hello from funcB 1
main: 12

恩,使用當前目錄的libB.so,很理想的東西

  • 可是,由於DT_RPATH無法被環境變量LD_LIBRARY_PATH覆蓋,不是不建議被使用,而是建議使用DT_RUNPATH麼?

  • 第三次編譯運行(使用DT_RUNPATH)

$ gcc main.c -ldl -Wl,--rpath=.,--enable-new-dtags 
$ ./a.out 
hello from funcA
Hello from funcB 2
main: 12

問題重新出現,使用的系統路徑中的libB.so 而不是當前目錄下的。

程序頭部信息

通過下列命令可以查看:

$ readelf -d a.out

爲了完整起見,列出前面3次編譯的程序的信息:

  • 沒有rpath和runpath

Dynamic section at offset 0xf20 contains 21 entries:
  Tag        Type                         Name/Value
 0x00000001 (NEEDED)                     Shared library: [libdl.so.2]
 0x00000001 (NEEDED)                     Shared library: [libc.so.6]
 0x0000000c (INIT)                       0x8048360
...
  • 包含rpath

Dynamic section at offset 0xf18 contains 22 entries:
  Tag        Type                         Name/Value
 0x00000001 (NEEDED)                     Shared library: [libdl.so.2]
 0x00000001 (NEEDED)                     Shared library: [libc.so.6]
 0x0000000f (RPATH)                      Library rpath: [.]
 0x0000000c (INIT)                       0x8048360
....
  • 包含rpath和runpath

Dynamic section at offset 0xf10 contains 23 entries:
  Tag        Type                         Name/Value
 0x00000001 (NEEDED)                     Shared library: [libdl.so.2]
 0x00000001 (NEEDED)                     Shared library: [libc.so.6]
 0x0000000f (RPATH)                      Library rpath: [.]
 0x0000001d (RUNPATH)                    Library runpath: [.]

原因

RPATH and RUNPATH給出這個問題的答案:

Unless loading object has RUNPATH:
    RPATH of the loading object,
        then the RPATH of its loader (unless it has a RUNPATH), ...,
        until the end of the chain, which is either the executable
        or an object loaded by dlopen
    Unless executable has RUNPATH:
        RPATH of the executable
LD_LIBRARY_PATH
RUNPATH of the loading object
ld.so.cache
default dirs

用它解釋第一個程序:

  • libA.so 沒有RUNPATH,故而
    • 使用其RPATH (沒有)
    • 遞歸查找其loader直到鏈條的頂端(可執行程序或被dlopen打開的對象)的RPATH或者遇RUNPATH退出 (沒有命中)
    • 可執行程序沒有RUNPATH,故而
      • 使用其RPATH (沒有)
  • 環境變量LD_LIBRARY_PATH,(沒有)
  • libA.so 的RUNPATH (沒有)
  • ld.so.cache (沒有命中)
  • 默認路徑/usr/lib (命中)

用它解釋第二個程序:

  • libA.so 沒有RUNPATH,故而
    • 使用其RPATH (沒有)
    • 遞歸查找其loader直到鏈條的頂端(可執行程序或被dlopen打開的對象)的RPATH或者遇RUNPATH退出 (沒有命中)
    • 可執行程序沒有RUNPATH,故而
      • 使用其RPATH (命中)

用它解釋第三個程序:

  • libA.so 沒有RUNPATH,故而
    • 使用其RPATH (沒有)
    • 遞歸查找其loader直到鏈條的頂端(可執行程序或被dlopen打開的對象)的RPATH或者遇RUNPATH退出 (沒有命中)
    • 可執行程序有RUNPATH,(繼續前行)
  • 環境變量LD_LIBRARY_PATH,(沒有)
  • libA.so 的RUNPATH (沒有)
  • ld.so.cache (沒有命中)
  • 默認路徑/usr/lib (命中)

有意思的就是這個程序了,可執行程序的RUNPATH是一個重要的判斷條件,卻並不被做爲這兒搜索路徑!!

結束

本文是在kubuntu 11.10下編寫測試的。爲了儘可能簡單,例子也都是認爲製造的。而且我們看到,在使用RPATH的時候是正常的,RUNPATH一般來說,被推薦使用,但這兒它卻不能正常工作。

所以,當使用RUNPATH時,我們需要明白:某些情況下可能需要設置環境變量 LD_LIBRARY_PATH


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章