複製鏈接
請複製以下鏈接發送給好友

dlopen

鎖定
dlopen()是一個計算機函數,功能是以指定模式打開指定的動態鏈接庫文件,並返回一個句柄給dlsym()的調用進程。使用dlclose()來卸載打開的庫。
外文名
dlopen
功    能
打開一個動態鏈接庫
包含頭文件
#include
解析方式
RTLD_LAZY

dlopen介紹

dlopen這個函數加載由以null結尾的字符串文件名命名的動態共享對象(共享庫)文件,併為加載的對象返回不透明的“句柄”。此句柄與 dlopen API 中的其他函數一起使用,例如dlsym(),dladdr(),dlinfo()和dlclose() [1] 
如果 filename 為 NULL,則返回的句柄用於主程序。如果 filename 包含斜槓(“/”),則它被解釋為(相對或絕對)路徑名。否則,動態鏈接器將按如下方式搜索對象(有關詳細信息,請參閲ld.so(8)):
  • (僅限ELF)如果調用程序的可執行文件包含 DT_RPATH 標記,並且不包含 DT_RUNPATH 標記,則會搜索 DT_RPATH 標記中列出的目錄。
  • 如果在程序啓動時,環境變量 LD_LIBRARY_PATH 被定義為包含以冒號分隔的目錄列表,則會搜索這些目錄。 (作為安全措施,set-user-ID 和 set-group-ID程序將忽略此變量。)
  • (僅限ELF)如果調用程序的可執行文件包含 DT_RUNPATH 標記,則搜索該標記中列出的目錄。
  • 檢查緩存文件/etc/ld.so.cache(由ldconfig(8)維護)以查看它是否包含filename的條目。
  • 搜索目錄 /lib和 /usr/lib(按此順序)。
如果 filename 指定的對象依賴於其他共享對象,則動態鏈接器也會使用相同的規則自動加載這些對象。 (如果這些對象依次具有依賴性,則此過程可以遞歸地發生)
flags 參數必須包括以下兩個值中的一個:
  • RTLD_LAZY執行延遲綁定。僅在執行引用它們的代碼時解析符號。如果從未引用該符號,則永遠不會解析它(只對函數引用執行延遲綁定;在加載共享對象時,對變量的引用總是立即綁定)。自 glibc 2.1.1,此標誌被LD_BIND_NOW環境變量的效果覆蓋。
  • RTLD_NOW如果指定了此值,或者環境變量LD_BIND_NOW設置為非空字符串,則在dlopen()返回之前,將解析共享對象中的所有未定義符號。如果無法執行此操作,則會返回錯誤。
flags 也可以通過以下零或多個值進行或運算設置:
  • RTLD_GLOBAL此共享對象定義的符號將可用於後續加載的共享對象的符號解析。
  • RTLD_LOCAL這與RTLD_GLOBAL相反,如果未指定任何標誌,則為默認值。此共享對象中定義的符號不可用於解析後續加載的共享對象中的引用。
  • RTLD_NODELETE (since glibc 2.2)在dlclose()期間不要卸載共享對象。因此,如果稍後使用dlopen()重新加載對象,則不會重新初始化對象的靜態變量。
  • RTLD_NOLOAD (since glibc 2.2)不要加載共享對象。這可用於測試對象是否已經駐留(如果不是,則dlopen()返回 NULL,如果是駐留則返回對象的句柄)。此標誌還可用於提升已加載的共享對象上的標誌。例如,以前使用RTLD_LOCAL加載的共享對象可以使用RTLD_NOLOAD | RTLD_GLOBAL重新打開。
  • RTLD_DEEPBIND (since glibc 2.3.4)將符號的查找範圍放在此共享對象的全局範圍之前。這意味着自包含對象將優先使用自己的符號,而不是全局符號,這些符號包含在已加載的對象中。

dlopen基本定義

dlopen功能

打開一個動態鏈接庫,並返回動態鏈接庫的句柄

dlopen包含頭文件

#include <dlfcn.h>

dlopen函數定義

void * dlopen( const char * pathname, int mode);

dlopen函數描述

mode是打開方式,其值有多個,不同操作系統上實現的功能有所不同,在linux下,按功能可分為三類:
1、解析方式
RTLD_LAZY:在dlopen返回前,對於動態庫中的未定義的符號不執行解析(只對函數引用有效,對於變量引用總是立即解析)。
RTLD_NOW: 需要在dlopen返回前,解析出所有未定義符號,如果解析不出來,在dlopen會返回NULL,錯誤為symbol: xxxx.......
2、作用範圍,可與解析方式通過“|”組合使用。
RTLD_GLOBAL:動態庫中定義的符號可被其後打開的其它庫解析。
RTLD_LOCAL: 與RTLD_GLOBAL作用相反,動態庫中定義的符號不能被其後打開的其它庫重定位。如果沒有指明是RTLD_GLOBAL還是RTLD_LOCAL,則缺省為RTLD_LOCAL。
3、作用方式
RTLD_NODELETE: 在dlclose()期間不卸載庫,並且在以後使用dlopen()重新加載庫時不初始化庫中的靜態變量。這個flag不是POSIX-2001標準。
RTLD_NOLOAD: 不加載庫。可用於測試庫是否已加載(dlopen()返回NULL説明未加載,否則説明已加載),也可用於改變已加載庫的flag,如:先前加載庫的flag為RTLD_LOCAL,用dlopen(RTLD_NOLOAD|RTLD_GLOBAL)後flag將變成RTLD_GLOBAL。這個flag不是POSIX-2001標準。
RTLD_DEEPBIND:在搜索全局符號前先搜索庫內的符號,避免同名符號的衝突。這個flag不是POSIX-2001標準。
返回值:
打開錯誤返回NULL
成功,返回庫引用
編譯時候要加入 -ldl (指定dl庫) -rdynamic(通知鏈接器將所有符號添加到動態符號表中(目的是能夠通過使用 dlopen 來實現向後跟蹤)
例如
gcc test.c -o test -ldl-rdynamic

dlopen流程

打開庫:void* dlopen(const char* libfile,int flag);
取函數:void* dlsym(void* handler, const char* symbol);
運行函數:func
關閉庫:int dlclose(void* handler);

dlopen使用 dlopen

dlopen()是一個強大的庫函數。該函數將打開一個新庫,並把它裝入內存。該函數主要用來加載庫中的符號,這些符號在編譯的時候是不知道的。比如 Apache Web 服務器利用這個函數在運行過程中加載模塊,這為它提供了額外的能力。一個配置文件控制了加載模塊的過程。這種機制使得在系統中添加或者刪除一個模塊時,都不需要重新編譯了。
可以在自己的程序中使用 dlopen()。dlopen() 在 dlfcn.h 中定義,並在 dl 庫中實現。它需要兩個參數:一個文件名和一個標誌。文件名可以是我們學習過的庫中的 soname。標誌指明是否立刻計算庫的依賴性。如果設置為 RTLD_NOW 的話,則立刻計算;如果設置的是 RTLD_LAZY,則在需要的時候才計算。另外,可以指定 RTLD_GLOBAL,它使得那些在以後才加載的庫可以獲得其中的符號。
當庫被裝入後,可以把 dlopen() 返回的句柄作為給 dlsym() 的第一個參數,以獲得符號在庫中的地址。使用這個地址,就可以獲得庫中特定函數的指針,並且調用裝載庫中的相應函數。

dlopen實例

runlib.c
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#define DLL_PATH "./libsd.so"
typedef int (*func)(int, int);
int main(){
    void *dlhandler;
    char *error;
    func func = NULL;
    dlhandler = dlopen(DLL_PATH,RTLD_LAZY);
    if(dlhandler == NULL){
        fprintf(stderr,"%s\n",dlerror());
        exit(-1);
    }
    dlerror();
    func = dlsym(dlhandler,"sumab");
    printf("%d\n",func(1,2));
    dlclose(dlhandler);
    return 0;
}
編譯:
gcc -rdynamic -o runlib runlib.c -ldl
運行:
[teanee@localhost runlib]$ ./runlib
3
成功調用sumab函數。
參考資料
  • 1.    鄒能人. Linux下動態注入機制的研究與實現[D].