動態鏈接庫的搜索路徑

man ld.so(8)說,如果庫依賴不包括“/”,那麼它將按照下面的規則按順序搜索:

  • (僅對ELF格式)如果可執行文件包含DT_RPATH標籤,並且不包含DT_RUNPATH標籤,將從DT_RPATH列出的路徑搜索。(DT_RPATH已經被廢棄,請用DT_RUNPATH)
  • 如果LD_LIBRARY_PATH在程序運行時被定義,那麼將從它包含的路徑開始。安全起見,對於set-user-ID或者set-group-ID的程序,這個變量將被忽略。
  • (僅對ELF格式)如果可執行文件包含DT_RUNPATH標籤,將從這個標籤列出的路徑開始搜索。
  • 從 /etc/ld.so.cache(運行ldconfig產生)中查找文件
  • 從/lib以及/urs/lib,按順序搜索。如果鏈接時指定-z nodefaultlib,這個步驟將被忽略。

看起來夠簡潔的,當做休閒,寫個程序驗證一下。但在這之前,先介紹一個Glibc擴展的函數(POSIX中沒有)

#define _GNU_SOURCE
#include <dlfcn.h>

int dladdr(void* addr, Dl_info *info);

這個函數解析傳入的函數指針(第一個參數),將信息填充到Dl_info的結構體

typedef struct {
    const char *dli_fname;  /* Pathname of shared object that contains address */
    void       *dli_fbase;  /* Address at which shared object  is loaded */
    const char *dli_sname;  /* Name of nearest symbol with addresslower than addr */
    void       *dli_saddr;  /* Exact address of symbol named in dli_sname */
} Dl_info;

下面是程序以及需要加載的動態庫的代碼:

  • ld_main.c
int main()
{
    lib_fun();
    return 0;
}
  • ld_lib.c
#define _GNU_SOURCE
#include <stdio.h>
#include <dlfcn.h>
int lib_fun()
{
    Dl_info dl_info;
    dladdr((void*)lib_fun, &dl_info);
    fprintf(stdout, ".so@ %s.\n", dl_info.dli_fname);
    return 0;
}

編譯這兩個文件:

1、動態庫:gcc --shared -fPIC ld_lib.c -o libld_lib.so -ldl
2、主程序:gcc ld_main.c -o ld_main -Wl,-rpath,./  -ldl -lld_lib -L./

-Wl,-rpath編譯選項將在程序中生成DT_RPATH節點,使用readelf會看到Library rpath被設為當前目錄:

接下來將生成的libld_lib.so拷貝到前面介紹到的搜索路徑:
對於LD_LIBRARY_PATH,隨便設置:export LD_LIBRARY_PATH=../
對於ld.so.conf提到的路徑,在/etc/ld.so.conf.d/下面隨便找一個,或者自己建立一個,這裡用系統自帶的libc.conf
中提到的路徑:/usr/local/lib

然後運行(每次都刪除程序優先加載的so文件):

(ld.so.conf路徑更新文件後需要運行ldconfig更新cache,否則會找不到文件,如上圖)。

關於-z nodefaultlib鏈接選項:

看來它真起作用了 關於DT_RUNPATH,需要用到--enable-new-dtags鏈接選項:

Linux下程序默認不會從當前路徑搜索.so文件,這對於自行開發的分為很多模塊,要安裝在同一目錄的“程序”來說不是個優點。還好可以用DT_RUNPATH指定.so的加載路徑)