GNU/Linux 库机制笔记

来自Jack's Lab
跳转到: 导航, 搜索

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 x86 下动态链接库机制

以下内容原记载于个人百度空间 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 里亦没有代码,不需要执行。




3 PowerPC 下动态链接库机制

以下内容原记载于个人百度空间 http://hi.baidu.com/comcat/item/60c60b49f77216aede2a9f81 时间:2008-07-19 17:24


考虑以下简单的程序:

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

root@comcat:/root> gdb ./test 
(gdb) disass main
Dump of assembler code for function main:
0x1000044c <main+0>:    stwu    r1,-16(r1)
0x10000450 <main+4>:    mflr    r0
0x10000454 <main+8>:    stw     r0,20(r1)
0x10000458 <main+12>:    stw     r31,12(r1)
0x1000045c <main+16>:    mr      r31,r1
0x10000460 <main+20>:    lis     r9,4096
0x10000464 <main+24>:    addi    r3,r9,2056
0x10000468 <main+28>:    li      r4,0
0x1000046c <main+32>:    crclr   4*cr1+eq
0x10000470 <main+36>:    bl      0x100109c0 <printf@plt>
0x10000474 <main+40>:    lis     r9,4096
0x10000478 <main+44>:    addi    r3,r9,2056
0x1000047c <main+48>:    li      r4,8
0x10000480 <main+52>:    crclr   4*cr1+eq
0x10000484 <main+56>:    bl      0x100109c0 <printf@plt>
0x10000488 <main+60>:    li      r0,0
0x1000048c <main+64>:    mr      r3,r0
0x10000490 <main+68>:    lwz     r11,0(r1)
0x10000494 <main+72>:    lwz     r0,4(r11)
0x10000498 <main+76>:    mtlr    r0
0x1000049c <main+80>:    lwz     r31,-4(r11)
0x100004a0 <main+84>:    mr      r1,r11
0x100004a4 <main+88>:    blr
End of assembler dump.
(gdb) b *0x10000468
Breakpoint 1 at 0x10000468: file enum.c, line 13.
(gdb) r
Starting program: /root/test 

Breakpoint 1, 0x10000468 in main () at test.c:13
13    test.c: No such file or directory.
    in test.c
(gdb) x /6i 0x100109c0
0x100109c0 <printf@plt>:    li      r11,8
0x100109c4 <printf@plt+4>:    b       0x10010988 <_GLOBAL_OFFSET_TABLE_+44>
0x100109c8 <printf@plt+8>:    .long 0x0
0x100109cc <printf@plt+12>:    .long 0x0
0x100109d0 <printf@plt+16>:    .long 0x0
0x100109d4 <completed.6562>:    .long 0x0
(gdb) x /10i 0x10010988
0x10010988 <_GLOBAL_OFFSET_TABLE_+44>:    rlwinm r12,r11,1,0,30
0x1001098c <_GLOBAL_OFFSET_TABLE_+48>:    add     r11,r12,r11
0x10010990 <_GLOBAL_OFFSET_TABLE_+52>:    li      r12,14468
0x10010994 <_GLOBAL_OFFSET_TABLE_+56>:    addis   r12,r12,4093
0x10010998 <_GLOBAL_OFFSET_TABLE_+60>:    mtctr   r12
0x1001099c <_GLOBAL_OFFSET_TABLE_+64>:    li      r12,0
0x100109a0 <_GLOBAL_OFFSET_TABLE_+68>:    addis   r12,r12,12288
0x100109a4 <_GLOBAL_OFFSET_TABLE_+72>:    bctr
0x100109a8 <_GLOBAL_OFFSET_TABLE_+76>:    .long 0x0
0x100109ac <_GLOBAL_OFFSET_TABLE_+80>:    .long 0x0
(gdb) si
0x1000046c    13    in enum.c
(gdb) si
0x10000470    13    in enum.c
(gdb) 
0x100109c0 in printf@plt ()
(gdb) 
0x100109c4 in printf@plt ()
(gdb) 
0x10010988 in ?? ()
(gdb) 
0x1001098c in ?? ()
(gdb) 
0x10010990 in ?? ()
(gdb) 
0x10010994 in ?? ()
(gdb) 
0x10010998 in ?? ()
(gdb) 
0x1001099c in ?? ()
(gdb) 
0x100109a0 in ?? ()
(gdb) 
0x100109a4 in ?? ()
(gdb) 
0x0ffd3884 in ?? () from /lib/ld.so.1               ----> 动态链接器解析 printf 符号,获得其函数实体地址,然后重写 0x100109c0 处的指令
(gdb) b *0x1000047c
Breakpoint 2 at 0x1000047c: file test.c, line 14.
(gdb) c
Continuing.
f[0] = 0

Breakpoint 2, 0x1000047c in main () at test.c:14
14    in test.c
(gdb) x /6i 0x100109c0
0x100109c0 <printf@plt>:    b       0xfeba210 <printf>    ----> 第一次调用 printf 后,此处即被动态链接器 ld.so.1 重填
0x100109c4 <printf@plt+4>:    b       0x10010988 <_GLOBAL_OFFSET_TABLE_+44>
0x100109c8 <printf@plt+8>:    .long 0x0
0x100109cc <printf@plt+12>:    .long 0x0
0x100109d0 <printf@plt+16>:    .long 0x0
0x100109d4 <completed.6562>:    .long 0x0
(gdb) x /10i 0x10010988
0x10010988 <_GLOBAL_OFFSET_TABLE_+44>:    rlwinm r12,r11,1,0,30
0x1001098c <_GLOBAL_OFFSET_TABLE_+48>:    add     r11,r12,r11
0x10010990 <_GLOBAL_OFFSET_TABLE_+52>:    li      r12,14468
0x10010994 <_GLOBAL_OFFSET_TABLE_+56>:    addis   r12,r12,4093
0x10010998 <_GLOBAL_OFFSET_TABLE_+60>:    mtctr   r12
0x1001099c <_GLOBAL_OFFSET_TABLE_+64>:    li      r12,0
0x100109a0 <_GLOBAL_OFFSET_TABLE_+68>:    addis   r12,r12,12288
0x100109a4 <_GLOBAL_OFFSET_TABLE_+72>:    bctr
0x100109a8 <_GLOBAL_OFFSET_TABLE_+76>:    .long 0x0
0x100109ac <_GLOBAL_OFFSET_TABLE_+80>:    .long 0x0

PowerPC 下,动态链接的机制与 x86 不同之处在于:

PowerPC 上的 GOT 里是一些指令,第一次调用后,PLT 会被重填为新的指令。

而 x86 上 GOT 只是存放函数地址,起初是 PLT 里的下一条指令,第一次调用后会被修改为 printf 的地址,PLT 的内容不会被修改。
























个人工具
名字空间

变换
操作
导航
工具箱