研究 Dynamic Loader, #1: dlopen

jollen 發表於 February 5, 2007 11:54 PM

學到 ELF 的格式,並了解 .text/.data/.bss section 後,接下來絕對不能錯過「ELF loader」領域最經典的題目 - dynamic loader。不把 dynamic loader 從頭到腳好好研究一遍的話,實在是可惜了!

要學習 dynamic loader 的議題,並深入核心實作,最好可以由 ld.so 程式設計的主題切入。ld.so 是 Linux 的 dynamic loader,如果 man ld.so 的話,可以得到以下解釋:

DESCRIPTION
       ld.so loads the shared libraries needed by a program, prepares the pro-
       gram to run, and then runs it.  Unless  explicitly  specified  via  the
       -static  option to ld during compilation, all Linux programs are incom-
       plete and require further linking at run time.

與 dynamic loader 有關的系統管理面議題如下,這些是基本功課,應先行了解:

  • /etc/ld.so.conf(ld.so 的設定檔)
  • ldconfig
  • LD_LIBRARY_PATH

ld.so 其實就是我們所熟悉的 /lib/ld-linux.so.2 執行檔。至於 ld.so 的程式設計,則是由以下 3 個主要的函數切入:

#include <dlfcn.h>
void *dlopen(const char *filename, int flag); 
void *dlsym(void *handle, const char *symbol); 
int dlclose(void *handle); 

這個部份可以 'man dlopen',便能得到非常詳盡的說明,同時還有一個範例程式。以下的範例是由 dlopen 的 man page 節錄出來的,我在裡頭加上了註解供您參考:

/* Filename: dl_call.c */
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>

int main(void) 
{
    void *handle;	/* shared library 的 'handle' 指標 */
    double (*cosine)(double);   /* 指向 shared library 裡的函數 */
    char *error;	/* 記錄 dynamic loader 的錯誤訊息 */

    /* 開啟 shared library 'libm' */
    handle = dlopen ("libm.so", RTLD_LAZY);
    if (!handle) {
        fprintf (stderr, "%s\n", dlerror());
        exit(1);
    }

    dlerror();    /* Clear any existing error */

    /* 在 handle 指向的 shared library 裡找到 "cos" 函數,
     * 並傳回他的 memory address 
     */
    cosine = dlsym(handle, "cos");
    if ((error = dlerror()) != NULL)  {
        fprintf (stderr, "%s\n", error);
        exit(1);
    }

    /* indirect function call (函數指標呼叫),
     * 呼叫所指定的函數
     */
    printf ("%f\n", (*cosine)(2.0));
    dlclose(handle);
    return 0;
}

dlopen()RTLD_LAZY 參數說明如下(節錄自 man page):

RTLD_LAZY
    Perform lazy binding. Only resolve symbols as the code that references 
them is executed. If the symbol is never referenced, then it is never 
resolved. (Lazy binding is only performed for function references; references 
to variables are always immediately bound when the library is loaded.)

由於使用到 dlopen() 函數,編譯時請與 libdl 做連結:

$ gcc -o dl_call dl_call.c -ldl

以上的做法等於:

 printf ("%f\n", cos(2.0));

直接呼叫 libm 的函數時,則是與 libm 做連結:

$ gcc -o dl_call dl_call.c -lm

單純由程式設計的角度來看,利用 dlopen()+dlsym() 來「呼叫函數」與「直接呼叫函數」有什麼應用上的差異呢?事實上,libdl 的使用是很普遍的,許多軟體將自己的功能模組(modules)做成 shared library,並以 dlopen() 載入後使用,由於 shared library 是「可抽換」的,因此可透過 libdl 來做出「Plug-ins」這樣的功能。

Jollen's Blog 使用 Github issues 與讀者交流討論。請點擊上方的文章專屬 issue,或 open a new issue

您可透過電子郵件 jollen@jollen.org,或是 Linkedin 與我連絡。更歡迎使用微信,請搜尋 WeChat ID:jollentw