GNU/Linux 库机制笔记

来自Jack's Lab
(版本间的差异)
跳转到: 导航, 搜索
第1行: 第1行:
 +
== Notes ==
 +
 
早期发表与 openrays 的个人blog: 2006-09-08 10:44
 
早期发表与 openrays 的个人blog: 2006-09-08 10:44
  
第138行: 第140行:
  
  
 +
<br><br>
 +
 +
== 动态链接库机制 ==
 +
 +
以下内容原记载于个人百度空间 http://hi.baidu.com/comcat/item/4166ef00ab975ad11ef04683 时间:2008-07-19 20:47
 +
 +
 +
考虑以下简单的程序:
 +
 +
<pre>
 +
int main()
 +
{
 +
    printf("f[0] = %d\n", 0x0);   
 +
    printf("f[0] = %d\n", 0x8);   
 +
    return 0;
 +
}
 +
 +
[root@comcat]$ gdb ./test
 +
 +
(gdb) disass main
 +
Dump of assembler code for function main:
 +
0x08048374 <main+0>:    lea    0x4(%esp),%ecx
 +
0x08048378 <main+4>:    and    $0xfffffff0,%esp
 +
0x0804837b <main+7>:    pushl -0x4(%ecx)
 +
0x0804837e <main+10>:  push  %ebp
 +
0x0804837f <main+11>:  mov    %esp,%ebp
 +
0x08048381 <main+13>:  push  %ecx
 +
0x08048382 <main+14>:  sub    $0x14,%esp
 +
0x08048385 <main+17>:  movl  $0x0,0x4(%esp)
 +
0x0804838d <main+25>:  movl  $0x8048480,(%esp)
 +
0x08048394 <main+32>:  call  0x80482d8 <printf@plt>
 +
0x08048399 <main+37>:  movl  $0x8,0x4(%esp)
 +
0x080483a1 <main+45>:  movl  $0x8048480,(%esp)
 +
0x080483a8 <main+52>:  call  0x80482d8 <printf@plt>
 +
0x080483ad <main+57>:  mov    $0x0,%eax
 +
0x080483b2 <main+62>:  add    $0x14,%esp
 +
0x080483b5 <main+65>:  pop    %ecx
 +
0x080483b6 <main+66>:  pop    %ebp
 +
0x080483b7 <main+67>:  lea    -0x4(%ecx),%esp
 +
0x080483ba <main+70>:  ret   
 +
End of assembler dump.
 +
(gdb) x /8i 0x80482d8
 +
0x80482d8 <printf@plt>: jmp    *0x804958c      ----> 0x804958c 处的值为 0x80482de,则其实际上跳到了下一条指令处。
 +
0x80482de <printf@plt+6>:      push  $0x10
 +
0x80482e3 <printf@plt+11>:      jmp    0x80482a8 <_init+48>
 +
0x80482e8:      Cannot access memory at address 0x80482e8
 +
(gdb) x /4x 0x804958c
 +
0x804958c <_GLOBAL_OFFSET_TABLE_+20>:  0x080482de      0x00000000      0x00000000      0x0804949c
 +
(gdb) b *0x0804838d
 +
Breakpoint 1 at 0x804838d: file test.c, line 3.
 +
(gdb) r
 +
Starting program: /home/comcat/develop/test/dl/test
 +
 +
Breakpoint 1, 0x0804838d in main () at test.c:3
 +
3              printf("f[0] = %d\n", 0);
 +
(gdb) si
 +
0x08048394      3              printf("f[0] = %d\n", 0);
 +
(gdb)
 +
0x080482d8 in printf@plt ()
 +
(gdb)
 +
0x080482de in printf@plt ()
 +
(gdb)
 +
0x080482e3 in printf@plt ()
 +
 +
(gdb) x /4i 0x80482a8
 +
0x80482a8 <_init+48>:  pushl 0x804957c
 +
0x80482ae <_init+54>:  jmp    *0x8049580
 +
0x80482b4 <_init+60>:  add    %al,(%eax)
 +
0x80482b6 <_init+62>:  add    %al,(%eax)
 +
(gdb) x /4x 0x8049580
 +
0x8049580 <_GLOBAL_OFFSET_TABLE_+8>:    0xb7fc02d0      0x080482be      0xb7e6e370      0x080482de       
 +
                        ----> 0xb7fc02d0 处为动态链接器的解析函数 _dl_runtime_resolve()
 +
 +
(gdb) si
 +
0x080482a8 in ?? ()
 +
(gdb) si
 +
0x080482ae in ?? ()
 +
(gdb) si
 +
0xb7fc02d0 in _dl_runtime_resolve () from /lib/ld-linux.so.2
 +
 +
(gdb) b *0x080483a1
 +
Breakpoint 2 at 0x80483a1: file test.c, line 4.
 +
(gdb) c
 +
Continuing.
 +
f[0] = 0
 +
 +
Breakpoint 2, 0x080483a1 in main () at test.c:4      ----> 第一次 printf 调用已经完,动态链接器已经修改 GOT 的相应项为 printf 的地址
 +
4              printf("f[0] = %d\n", 8);
 +
 +
(gdb) x /8i 0x80482d8            ----> PLT 没有变化,但原 GOT 入口 0x804958c 处的值已经被改写为 printf 的地址
 +
0x80482d8 <printf@plt>: jmp    *0x804958c
 +
0x80482de <printf@plt+6>:      push  $0x10
 +
0x80482e3 <printf@plt+11>:      jmp    0x80482a8 <_init+48>
 +
0x80482e8:      add    %al,(%eax)
 +
0x80482ea:      add    %al,(%eax)
 +
0x80482ec:      add    %al,(%eax)
 +
0x80482ee:      add    %al,(%eax)
 +
0x80482f0 <printf@plt+24>:      xor    %ebp,%ebp
 +
(gdb) x /4x 0x804958c
 +
0x804958c <_GLOBAL_OFFSET_TABLE_+20>:  0xb7e9f170      0x00000000      0x00000000      0x0804949c
 +
(gdb) x /4i 0xb7e9f170
 +
0xb7e9f170 <printf>:    push  %ebp
 +
0xb7e9f171 <printf+1>: mov    %esp,%ebp
 +
0xb7e9f173 <printf+3>: push  %ebx
 +
0xb7e9f174 <printf+4>: call  0xb7e6e28f <__i686.get_pc_thunk.bx>
 +
</pre>
 +
 +
x86 下动态链接机制,动态链接器不会更改 PLT,只会在用到相应链接函数时,才会去解析符号,然后将对应的地址更新到 GOT 的相应项,GOT 里亦没有代码,不需要执行。
 +
 +
 +
<br><br>
 +
<br><br>
 +
<br><br>
 +
<br><br>
 +
<br><br>
 +
<br><br>
 +
<br><br>
 +
<br><br>
 +
<br><br>
 
<br><br>
 
<br><br>
 
<br><br>
 
<br><br>
 
<br><br>
 
<br><br>

2013年9月11日 (三) 14:53的版本

1 Notes

早期发表与 openrays 的个人blog: 2006-09-08 10:44


1. 创建静态库:

   gcc -c hello.c -o hello.o
   ar rcs libhello.a hello.o


2. 使用静态库:

   gcc -o test test.c -static -L. -lhello


3. 共享库版本: version.minor.release


4. 构建动态共享库:


gcc/g++下加 -fPIC -shared 参数即可

其中 -fPIC 作用于编译阶段,告诉编译器产生与位置无关代码(Position-Independent Code),则产生的代码中,没有绝对地址,全部使用相对地址,故而代码可以被加载器加载到内存的任意位置,都可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的。可以export LD_DEBUG=files,查看每次加载共享库的实际地址。

其中 -shared 作用于链接阶段,实际传递给链接器ld,让其添加作为共享库所需要的额外描述信息,去除共享库所不需的信息。

可以分解为如下步骤:

    I. gcc -c err.c -fPIC -o err.o
    II. gcc -shared -o liberr.so.0.0 err.o

        II <==> ld -Bshareable -o liberr.so.0.0 err.o

    III. ln -s liberr.so.0.0 liberr.so


5. 动态共享库的使用:


a. 由共享库加载器自动加载

    gcc -o test test.c -lerr -L. -Wl,-rpath=./

    -Wl,option
      Pass option as an option to the linker. If option contains commas,
      it is split into multiple options at the commas.

    -rpath: 指定运行时搜索动态库的路径,可以用环境变量LD_LIBRARY_PATH指定。


b. 程序自己控制加载、符号解析(使用libc6之dl库)

    gcc cos.c -o cos -ldl

    /* cos.c */
    #include <stdio.h>
    #include <dlfcn.h>

    int main()
    {
      void *handle;
      double (*cosine)(double);
      char *error;
      double rev;

      handle = dlopen("libm.so", RTLD_LAZY); // 加载动态库
      if(!handle)
      {
          fprintf(stderr, "%s\n", dlerror());
          exit(1);
      }

      dlerror();

      cosine = dlsym(handle, "cos"); // 解析符号cos
      if((error = dlerror()) != NULL)
      {
          fprintf(stderr, "%s\n", error);
          exit(1);
      }

      rev = cosine(3.1415926); // 使用cos函数
      printf("The cos result is: %f\n", rev);

      dlclose(handle);

      return 0;
    }


6. GNU/Linux下动态库之加载器为/lib/ld-linux.so, 可执行的。

 /lib/ld-linux.so ./cos <===> ./cos


7. 有用的环境变量

  
    LD_LIBRARY_PATH

      指定运行时动态库的搜索路径

    LD_DEBUG

      调试用,其值可为:

      libs     display library search paths
      reloc     display relocation processing
      files     display progress for input file
      symbols   display symbol table processing
      bindings   display information about symbol binding
      versions   display version dependencies
      all       all previous options combined
      statistics display relocation statistics
      unused     determined unused DSOs
      help     display this help message and exit


8. 搜索含有cos函数的共享库名

   nm -AD /lib/* /usr/lib/* 2>/dev/null | grep "cos$"
   nm -- 从对象文件中列出符号。


9. 读取ELF文件格式信息

   readelf -Ds ./libfoo.so #读出共享库的符号




2 动态链接库机制

以下内容原记载于个人百度空间 http://hi.baidu.com/comcat/item/4166ef00ab975ad11ef04683 时间:2008-07-19 20:47


考虑以下简单的程序:

int main()
{
    printf("f[0] = %d\n", 0x0);    
    printf("f[0] = %d\n", 0x8);    
    return 0;
}

[root@comcat]$ gdb ./test 

(gdb) disass main
Dump of assembler code for function main:
0x08048374 <main+0>:    lea    0x4(%esp),%ecx
0x08048378 <main+4>:    and    $0xfffffff0,%esp
0x0804837b <main+7>:    pushl -0x4(%ecx)
0x0804837e <main+10>:   push   %ebp
0x0804837f <main+11>:   mov    %esp,%ebp
0x08048381 <main+13>:   push   %ecx
0x08048382 <main+14>:   sub    $0x14,%esp
0x08048385 <main+17>:   movl   $0x0,0x4(%esp)
0x0804838d <main+25>:   movl   $0x8048480,(%esp)
0x08048394 <main+32>:   call   0x80482d8 <printf@plt>
0x08048399 <main+37>:   movl   $0x8,0x4(%esp)
0x080483a1 <main+45>:   movl   $0x8048480,(%esp)
0x080483a8 <main+52>:   call   0x80482d8 <printf@plt>
0x080483ad <main+57>:   mov    $0x0,%eax
0x080483b2 <main+62>:   add    $0x14,%esp
0x080483b5 <main+65>:   pop    %ecx
0x080483b6 <main+66>:   pop    %ebp
0x080483b7 <main+67>:   lea    -0x4(%ecx),%esp
0x080483ba <main+70>:   ret    
End of assembler dump.
(gdb) x /8i 0x80482d8
0x80482d8 <printf@plt>: jmp    *0x804958c      ----> 0x804958c 处的值为 0x80482de,则其实际上跳到了下一条指令处。
0x80482de <printf@plt+6>:       push   $0x10
0x80482e3 <printf@plt+11>:      jmp    0x80482a8 <_init+48>
0x80482e8:      Cannot access memory at address 0x80482e8
(gdb) x /4x 0x804958c
0x804958c <_GLOBAL_OFFSET_TABLE_+20>:   0x080482de      0x00000000      0x00000000      0x0804949c
(gdb) b *0x0804838d
Breakpoint 1 at 0x804838d: file test.c, line 3.
(gdb) r
Starting program: /home/comcat/develop/test/dl/test 

Breakpoint 1, 0x0804838d in main () at test.c:3
3               printf("f[0] = %d\n", 0);
(gdb) si
0x08048394      3               printf("f[0] = %d\n", 0);
(gdb) 
0x080482d8 in printf@plt ()
(gdb) 
0x080482de in printf@plt ()
(gdb) 
0x080482e3 in printf@plt ()

(gdb) x /4i 0x80482a8
0x80482a8 <_init+48>:   pushl 0x804957c
0x80482ae <_init+54>:   jmp    *0x8049580
0x80482b4 <_init+60>:   add    %al,(%eax)
0x80482b6 <_init+62>:   add    %al,(%eax)
(gdb) x /4x 0x8049580
0x8049580 <_GLOBAL_OFFSET_TABLE_+8>:    0xb7fc02d0      0x080482be      0xb7e6e370      0x080482de        
                        ----> 0xb7fc02d0 处为动态链接器的解析函数 _dl_runtime_resolve()

(gdb) si
0x080482a8 in ?? ()
(gdb) si
0x080482ae in ?? ()
(gdb) si
0xb7fc02d0 in _dl_runtime_resolve () from /lib/ld-linux.so.2

(gdb) b *0x080483a1
Breakpoint 2 at 0x80483a1: file test.c, line 4.
(gdb) c
Continuing.
f[0] = 0

Breakpoint 2, 0x080483a1 in main () at test.c:4       ----> 第一次 printf 调用已经完,动态链接器已经修改 GOT 的相应项为 printf 的地址
4               printf("f[0] = %d\n", 8);

(gdb) x /8i 0x80482d8             ----> PLT 没有变化,但原 GOT 入口 0x804958c 处的值已经被改写为 printf 的地址
0x80482d8 <printf@plt>: jmp    *0x804958c
0x80482de <printf@plt+6>:       push   $0x10
0x80482e3 <printf@plt+11>:      jmp    0x80482a8 <_init+48>
0x80482e8:      add    %al,(%eax)
0x80482ea:      add    %al,(%eax)
0x80482ec:      add    %al,(%eax)
0x80482ee:      add    %al,(%eax)
0x80482f0 <printf@plt+24>:      xor    %ebp,%ebp
(gdb) x /4x 0x804958c
0x804958c <_GLOBAL_OFFSET_TABLE_+20>:   0xb7e9f170      0x00000000      0x00000000      0x0804949c
(gdb) x /4i 0xb7e9f170
0xb7e9f170 <printf>:    push   %ebp
0xb7e9f171 <printf+1>: mov    %esp,%ebp
0xb7e9f173 <printf+3>: push   %ebx
0xb7e9f174 <printf+4>: call   0xb7e6e28f <__i686.get_pc_thunk.bx>

x86 下动态链接机制,动态链接器不会更改 PLT,只会在用到相应链接函数时,才会去解析符号,然后将对应的地址更新到 GOT 的相应项,GOT 里亦没有代码,不需要执行。


























个人工具
名字空间

变换
操作
导航
工具箱