1. 程式人生 > >把共享庫(SO)載入到指定的記憶體地址

把共享庫(SO)載入到指定的記憶體地址

把共享庫(SO)載入到指定的記憶體地址

 

轉載時請註明出處:http://blog.csdn.net/absurd

https://blog.csdn.net/absurd/article/details/860140

一位朋友最近遇到一個棘手的問題,希望把共享庫(SO)載入到指定的記憶體地址,目的可能是想通過prelink來加快應用程式的起動速度。他問我有沒有什麼方法。我知道Windows下是可以的,比如在VC6裡設定/base的值就行了,所以相信在linux下也是可行的。

 

VC有編譯選項可以設定,猜想gcc也應該有吧。gcc本身只是一個外殼,連結工作是由於ld完成的,當然是應該去閱讀ld

命令列選項文件。很快發現ld有個—image-base選項,可以設定動態庫的載入地址。

 

通過Xlinker把這個引數傳遞給ld,但是ld不能識別這個選項:

gcc -g -shared test.c -Xlinker --image-base -Xlinker 0x00c00000 -o libtest.so

/usr/bin/ld: unrecognized option '--image-base'

/usr/bin/ld: use the --help option for usage information

collect2: ld returned 1 exit statu

s

再仔細看手冊,原來這個選項只適用於PE

檔案,PE檔案是Windows下專用的,在linux下自然用不了,看來得另想辦法。

 

我知道ld script可以控制ld的行為,於是研究ld script的寫法,按照手冊裡的說明,寫了一個簡單的ld script:

     SECTIONS

     {

       . = 0x00c00000;

       .text : { *(.text) }

       .data : { *(.data) }

       .bss : { *(.bss) }

     }

 

按下列方式編譯:

gcc -shared -g -Xlinker --script -Xlinker ld.s test.c -o libtest.so

gcc -g main.c -L./ -ltest -o test.exe

 

ldd檢視,載入地址正確。

[[email protected] lds]# ldd test.exe

        linux-gate.so.1 =>  (0x00aff000)

        libtest.so => ./libtest.so (0x00c00000)

        libc.so.6 => /lib/libc.so.6 (0x007fa000)

        /lib/ld-linux.so.2 (0x007dd000)

 

但執行時會crash:

[[email protected] lds]# ./test.exe

Segmentation fault

 

除錯執行可以發現:

(gdb) r

Starting program: /work/test/lds/test.exe

Reading symbols from shared object read from target memory...done.

Loaded system supplied DSO at 0x452000

 

Program received signal SIGSEGV, Segmentation fault.

0x007e7a10 in _dl_relocate_object () from /lib/ld-linux.so.2

(gdb) bt

#0  0x007e7a10 in _dl_relocate_object () from /lib/ld-linux.so.2

#1  0x007e0833 in dl_main () from /lib/ld-linux.so.2

#2  0x007f056b in _dl_sysdep_start () from /lib/ld-linux.so.2

#3  0x007df48f in _dl_start () from /lib/ld-linux.so.2

#4  0x007dd847 in _start () from /lib/ld-linux.so.2

 

猜想可能是ld.s寫得不全,導致一些資訊不正確。於是用ld –verbose匯出一個預設的ld script。不出所料,預設的ld script非常冗長,下面是開頭一段:

/* Script for -z combreloc: combine and sort reloc sections */

OUTPUT_FORMAT("elf32-i386", "elf32-i386",

          "elf32-i386")

OUTPUT_ARCH(i386)

ENTRY(_start)

SEARCH_DIR("/usr/i386-redhat-linux/lib"); SEARCH_DIR("/usr/local/lib"); SEARCH_DIR("/lib"); SEARCH_DIR("/usr/lib");

SECTIONS

{

  /* Read-only sections, merged into text segment: */

  PROVIDE (__executable_start = 0x08048000); . = 0x08048000 + SIZEOF_HEADERS;

  .interp         : { *(.interp) }

  .hash           : { *(.hash) }

  .dynsym         : { *(.dynsym) }

  .dynstr         : { *(.dynstr) }

  .gnu.version    : { *(.gnu.version) }

  .gnu.version_d  : { *(.gnu.version_d) }

  .gnu.version_r  : { *(.gnu.version_r) }

 

 

按照ld script的語法,我把它修改為(紅色部分為新增行)

/* Script for -z combreloc: combine and sort reloc sections */

OUTPUT_FORMAT("elf32-i386", "elf32-i386",

          "elf32-i386")

OUTPUT_ARCH(i386)

ENTRY(_start)

SEARCH_DIR("/usr/i386-redhat-linux/lib"); SEARCH_DIR("/usr/local/lib"); SEARCH_DIR("/lib"); SEARCH_DIR("/usr/lib");

SECTIONS

{

  /* Read-only sections, merged into text segment: */

  PROVIDE (__executable_start = 0x08048000); . = 0x08048000 + SIZEOF_HEADERS;

  . = 0x00c00000;

  .interp         : { *(.interp) }

  .hash           : { *(.hash) }

  .dynsym         : { *(.dynsym) }

  .dynstr         : { *(.dynstr) }

  .gnu.version    : { *(.gnu.version) }

  .gnu.version_d  : { *(.gnu.version_d) }

  .gnu.version_r  : { *(.gnu.version_r) }

 

 

用這個ld script再次測試,一切正常。又驗證多個共享庫的情況,也正常,下面是測試資料:

 

test.c

int test(int n)

{

    return n;

}

test1.c

int test1(int n)

{

    return n;

}

main.c

extern int test(int n);

extern int test1(int n);

#include <stdio.h>

 

int main(int argccharargv[])

{

    printf("Hello: %d %d/n"test(100), test1(200));

 

    getchar();

    return 0;

}

Makefile

all:

    gcc -shared -g -Xlinker --script -Xlinker ld.s test.c -o libtest.so

    gcc -shared -g -Xlinker --script -Xlinker ld1.s test1.c -o libtest1.so

    gcc -g main.c -L./ -ltest -ltest1 -o test.exe

 

clean:

    rm -f *.so *.exe

 

libtest.so的載入地址為:0x00c00000

libtest1.so的載入地址為:0x00d00000

 

ldd顯示結果:

        linux-gate.so.1 =>  (0x00aa3000)

        libtest.so => ./libtest.so (0x00c00000)

        libtest1.so => ./libtest1.so (0x00d00000)

        libc.so.6 => /lib/libc.so.6 (0x007fa000)

        /lib/ld-linux.so.2 (0x007dd000)

maps的內容為:

007dd000-007f6000 r-xp 00000000 03:01 521466     /lib/ld-2.4.so

007f6000-007f7000 r-xp 00018000 03:01 521466     /lib/ld-2.4.so

007f7000-007f8000 rwxp 00019000 03:01 521466     /lib/ld-2.4.so

007fa000-00926000 r-xp 00000000 03:01 523579     /lib/libc-2.4.so

00926000-00929000 r-xp 0012b000 03:01 523579     /lib/libc-2.4.so

00929000-0092a000 rwxp 0012e000 03:01 523579     /lib/libc-2.4.so

0092a000-0092d000 rwxp 0092a000 00:00 0

00c00000-00c01000 r-xp 00001000 03:03 16370      /work/test/ldsex/libtest.so

00c01000-00c02000 rwxp 00001000 03:03 16370      /work/test/ldsex/libtest.so

00cf1000-00cf2000 r-xp 00cf1000 00:00 0          [vdso]

00d00000-00d01000 r-xp 00001000 03:03 16373      /work/test/ldsex/libtest1.so

00d01000-00d02000 rwxp 00001000 03:03 16373      /work/test/ldsex/libtest1.so

08048000-08049000 r-xp 00000000 03:03 16374      /work/test/ldsex/test.exe

08049000-0804a000 rw-p 00000000 03:03 16374      /work/test/ldsex/test.exe

b7fdf000-b7fe0000 rw-p b7fdf000 00:00 0

b7fed000-b7ff0000 rw-p b7fed000 00:00 0

bf8db000-bf8f0000 rw-p bf8db000 00:00 0          [stack]

 

從以上測試結果可以看出,這種方法是可行的。