找回密码
 注册
搜索
热搜: 超星 读书 找书
查看: 4712|回复: 0

[【推荐】] Modifying a Dynamic Library Without Changing the Source Code

[复制链接]
发表于 2009-8-6 14:53:51 | 显示全部楼层 |阅读模式
在不更動原始程式碼的前提下,修改動態程式庫
透過 LD_PRELOAD 環境變數來置放你自己的程式碼,是很簡單的動作

有時候你或許會想知道,在未修改程式庫 (library) 的前提下,動態程式庫會產生什麼結果 (你最近有試著建構 glibc 嗎?) 或者,你可能會想去覆寫 (override) 程式庫裡頭某些函式 (functions),以便會有不同的行為,比方說針對某個 CPU 作處理、避免某個特定的 USB 訊息被傳送,或者類似的操作。 這些動作都可以透過 LD_PRELOAD 環境變數來實現,你可以很簡單的置放你要的程式碼。

舉例來說,你可以建立一個共享程式庫,比方說,shim.so (譯註:\"shim\" 一詞在英文裡頭的意思就是夾在縫隙裡頭的墊片),你想要讓這個動態程式庫在其他共享程式庫之前被載入。 我們想執行的程式如果成為 \"test\" 的話,可以使用以下命令:


    LD_PRELOAD=/home/greg/test/shim.so ./test

以上命令告知 glibc,預先從 /home/greg/test 目錄下去載入我們的 shim.so 動態程式庫,然後才去載入執行 ./test 所需的其他動態程式庫。

建立這個 shim 程式庫是相當簡單的,感謝 kernel 開發者 Anton Blanchard 的一封舊信,裡頭提及這樣的範例。

  那我們就設計以紀錄 libusb 程式庫被其他程式呼叫的狀況,作為範例吧。libusb 是個 user space 的程式庫,可用以存取 USB 裝置的資訊,而不需要透過 kernel driver 來對 USB 裝置操作。

  我們透過 libusb 提供的 usb_open() 函式作出發,以下的程式磝xx段就是用以紀錄存取本函式的狀況:


  usb_dev_handle *usb_open(struct usb_device *dev)
  {
   static usb_dev_handle *(*libusb_open)
         (struct usb_device *dev) = NULL;
   void *handle;
   usb_dev_handle *usb_handle;
   char *error;
   if (!libusb_open) {
    handle = dlopen(\"/usr/lib/libusb.so\",
            RTLD_LAZY);
    if (!handle) {
     fputs(dlerror(), stderr);
     exit(1);
    }
    libusb_open = dlsym(handle, \"usb_open\");
    if ((error = dlerror()) != NULL) {
     fprintf(stderr, \"%s\\n\", error);
     exit(1);
    }
   }
   printf(\"calling usb_open(%s)\\n\", dev->filename);
   usb_handle = libusb_open(dev);
   printf(\"usb_open() returned %p\\n\", usb_handle);
   return usb_handle;
  }

可以用以下方式來編譯:


gcc -Wall -O2 -fpic -shared -ldl -o shim.so shim.c

以上指令從原始程式碼 shim.c,建立一個名為 shim.so 的共享程式庫。如果測試程式是透過前述 LD_PRELOAD 方式執行的話,那麼,測試程式中所有呼叫 usb_open() 函式的呼叫將會使用我們自己的函式庫實作 (譯註:也就是說採用 shim.c 裡面的實作,而非原本 libusb.so 裡頭的)。 在我的函式實作中,會透過以下 dlopen 呼叫的方式,試著載入真實的 libusb 程式庫:


  handle = dlopen(\"/usr/lib/libusb.so\",
          RTLD_LAZY);

注意到 RTLD_LAZY 的參數,這是因為我不想要讓載入器立即去解析所有的 symbol。 相反的,我想要讓 shim.so 裡頭的程式碼需要時,才去作解析的動作。

如果 dlopen() 函式成功的話,程式會去索取指向真實 usb_open 函式的 pointer (譯註:也就是存放於 libusb.so 的 usb_open() 進入點),這是透過 dlsym 呼叫取得。


  libusb_open = dlsym(handle, \"usb_open\");

如果以上呼叫成功的話,shim.so 的函式現在就擁有 libusb 裡頭函式真正的 pointer (進入點)。 這樣一來,可以在任何時間呼叫,為了我們的預期,我們會在作完記錄動作後呼叫,如下所示:


printf(\"calling usb_open(%p)\\n\", dev);
usb_handle = libusb_open(dev);

這也允許程式碼在(真實)函式呼叫後、但尚未返回原本的程式之前,作一些動作。

舉例來說,我們會希望 shim.so 提供以下的輸出方式:


calling usb_open(002)
usb_open() returned 0x8061100
calling usb_open(002)
usb_open() returned 0x8061100
calling usb_open(002)
usb_open() returned 0x8061100
calling usb_open(002)
usb_open() returned 0x8061100
calling usb_open(002)
usb_open() returned 0x8061120
calling usb_open(002)
usb_open() returned 0x8061120

為了記錄更複雜的函式,比方說 usb_control_message(),我們也套用之前加諸於 usb_open 的動作:


int usb_control_msg(usb_dev_handle *dev,
          int requesttype,
          int request,
          int value,
          int index,
          char *bytes,
          int size,
          int timeout)
{
static int(*libusb_control_msg)
      (usb_dev_handle *dev,
       int requesttype, int request,
       int value, int index, char *bytes,
       int size, int timeout) = NULL;
void *handle;
int ret, i;
char *error;
if (!libusb_control_msg) {
  handle = dlopen(\"/usr/lib/libusb.so\", RTLD_LAZY);
  if (!handle) {
   fputs(dlerror(), stderr);
   exit(1);
  }
  libusb_control_msg = dlsym(handle, \"usb_control_msg\");
  if ((error = dlerror()) != NULL) {
   fprintf(stderr, \"%s\\n\", error);
   exit(1);
  }
}
printf(\"calling usb_control_msg(%p, %04x, \"
     \"%04x, %04x, %04x, %p, %d, %d)\\n\"
     \"\\tbytes = \", dev, requesttype,
     request, value, index, bytes, size,
     timeout);
for (i = 0; i < size; ++i)
  printf (\"%02x \", (unsigned char)bytes);
printf(\"\\n\");
ret = libusb_control_msg(dev, requesttype,
              request, value,
              index, bytes, size,
              timeout);
printf(\"usb_control_msg(%p) returned %d\\n\"
     \"\\tbytes = \", dev, ret);
for (i = 0; i < size; ++i)
  printf (\"%02x \", (unsigned char)bytes);
printf(\"\\n\");
return ret;
}

透過 LD_PREDLOAD=shim.so 來執行測試程式,將會得到以下輸出:


usb_open() returned 0x8061100
calling usb_control_msg(0x8061100, 00c0, 0013, 6c7e, c41b, 0x80610a8, 8, 1000)
    bytes = c9 ea e7 73 2a 36 a6 7b
usb_control_msg(0x8061100) returned 8
    bytes = 81 93 1a c4 85 27 a0 73
calling usb_open(002)
usb_open() returned 0x8061120
calling usb_control_msg(0x8061120, 00c0, 0017, 9381, c413, 0x8061100, 8, 1000)
    bytes = 39 83 1d cc 85 27 a0 73
usb_control_msg(0x8061120) returned 8
    bytes = 35 15 51 2e 26 52 93 43
calling usb_open(002)
usb_open() returned 0x8061120
calling usb_control_msg(0x8061120, 00c0, 0017, 9389, c413, 0x8061108, 8, 1000)
    bytes = 80 92 1b c6 e3 a3 fa 9d
usb_control_msg(0x8061120) returned 8
    bytes = 80 92 1b c6 e3 a3 fa 9d

所以,我們知道,使用 LD_PRELOAD 環境變數可以讓我們很容易在動態程式庫之前,置放我們自己的實作。

Greg Kroah-Hartman currently is the Linux kernel maintainer for a variety of different driver subsystems. He works for IBM, doing Linux kernel-related things, and can be reached at greg@kroah.com [1].
Links
[1] http://www.linuxjournal.com/
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

Archiver|手机版|小黑屋|网上读书园地

GMT+8, 2024-12-22 20:50 , Processed in 0.192572 second(s), 5 queries , Redis On.

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表