• 动态链接学习的一些简单总结


    一、源码编写编译

    经过几天的学习,对动态链接进行一些简单的摸索,并将学习总结记录如下。

    首先编写两个测试源码文件:

    [ main.c ]

     1 #include <stdio.h>
     2 
     3 extern int subVal;
     4 void SubFunc(void);
     5 int mainVal = 3;
     6 
     7 void MainFunc(void)
     8 {
     9 
    10 }
    11 
    12 int main()
    13 {
    14     puts("In Main.");
    15 
    16     mainVal = 6;
    17     subVal = 6;
    18 
    19     MainFunc();
    20     SubFunc();
    21 
    22     return 0;
    23 }

    [ subFunc.c ]

     1 #include <stdio.h>
     2 
     3 static int staticSubVal = 3;
     4 int subVal = 3;
     5 
     6 static void StaticTestFunc(void)
     7 {
     8 
     9 }
    10 
    11 void TestFunc(void)
    12 {
    13 
    14 }
    15 
    16 void SubFunc()
    17 {
    18     puts("In SubFunc.");
    19 
    20     staticSubVal = 8;
    21     subVal = 8;
    22 
    23     StaticTestFunc();
    24     TestFunc();
    25 }

    编译源代码:

    gcc -shared -fPIC -o libsubFunc.so subFunc.c
    gcc -o main main.c -lsubFunc -L.

     二、查看对应的ELF文件及反汇编代码

    ① main ELF:

          ...
    [11] .init PROGBITS 0000000000000630 00000630
       0000000000000017 0000000000000000 AX 0 0 4
    [12] .plt PROGBITS 0000000000000650 00000650
       0000000000000030 0000000000000010 AX 0 0 16
    [13] .plt.got PROGBITS 0000000000000680 00000680
       0000000000000008 0000000000000008 AX 0 0 8
    [14] .text PROGBITS 0000000000000690 00000690
       00000000000001c2 0000000000000000 AX 0 0 16
    [22] .got PROGBITS 0000000000200fb0 00000fb0
       0000000000000050 0000000000000008 WA 0 0 8
    [23] .data PROGBITS 0000000000201000 00001000
       0000000000000014 0000000000000000 WA 0 0 8
    [24] .bss NOBITS 0000000000201014 00001014
       000000000000000c 0000000000000000 WA 0 0 4
    [25] .comment PROGBITS 0000000000000000 00001014
       000000000000002b 0000000000000001 MS 0 0 1
          ...    

     ② main DISASM:

    00000000000007a1 <main>:
      7a1: 55 push %rbp
      7a2: 48 89 e5 mov %rsp,%rbp
      7a5: 48 8d 3d b8 00 00 00 lea 0xb8(%rip),%rdi # 864 <_IO_stdin_used+0x4>
      7ac: e8 af fe ff ff callq 660 <puts@plt>
      7b1: c7 05 55 08 20 00 06 movl $0x6,0x200855(%rip) # 201010 <mainVal>
      7b8: 00 00 00 
      7bb: c7 05 4f 08 20 00 06 movl $0x6,0x20084f(%rip) # 201014 <subVal>
      7c2: 00 00 00 
      7c5: e8 d0 ff ff ff callq 79a <MainFunc>
      7ca: e8 a1 fe ff ff callq 670 <SubFunc@plt>
      7cf: b8 00 00 00 00 mov $0x0,%eax
      7d4: 5d pop %rbp
      7d5: c3 retq 
    ③ libsubFunc.so ELF:
          ...        
    [ 9] .init PROGBITS 0000000000000560 00000560
       0000000000000017 0000000000000000 AX 0 0 4
    [10] .plt PROGBITS 0000000000000580 00000580
       0000000000000030 0000000000000010 AX 0 0 16
    [11] .plt.got PROGBITS 00000000000005b0 000005b0
       0000000000000008 0000000000000008 AX 0 0 8
    [12] .text PROGBITS 00000000000005c0 000005c0
       000000000000011c 0000000000000000 AX 0 0 16
    [20] .got PROGBITS 0000000000200fd8 00000fd8
       0000000000000028 0000000000000008 WA 0 0 8
    [21] .got.plt PROGBITS 0000000000201000 00001000
       0000000000000028 0000000000000008 WA 0 0 8
    [22] .data PROGBITS 0000000000201028 00001028
       0000000000000010 0000000000000000 WA 0 0 8
    [23] .bss NOBITS 0000000000201038 00001038
       0000000000000008 0000000000000000 WA 0 0 1
          ...
    ④ libsubFunc.so DISASM:
    00000000000006a8 <SubFunc>:
      6a8: 55 push %rbp
      6a9: 48 89 e5 mov %rsp,%rbp
      6ac: 48 8d 3d 32 00 00 00 lea 0x32(%rip),%rdi # 6e5 <_fini+0x9>
      6b3: e8 d8 fe ff ff callq 590 <puts@plt>
      6b8: c7 05 6e 09 20 00 08 movl $0x8,0x20096e(%rip) # 201030 <staticSubVal>
      6bf: 00 00 00 
      6c2: 48 8b 05 17 09 20 00 mov 0x200917(%rip),%rax # 200fe0 <subVal@@Base-0x54>
      6c9: c7 00 08 00 00 00 movl $0x8,(%rax)
      6cf: e8 c6 ff ff ff callq 69a <StaticTestFunc>
      6d4: e8 c7 fe ff ff callq 5a0 <TestFunc@plt>
      6d9: 90 nop
      6da: 5d pop %rbp
      6db: c3 retq

    学习总结一:

    (1)与动态链接不同,静态链接在编译生成可执行程序后,程序中所有的符号地址都已经确定了下来,在运行时系统可以直接指向程序的Entry Point,默认情况则为_start,并将运行权限完全交给可执行程序。

    (2)而动态链接时,由于动态库是共享的且其地址由系统分配至合适的区域,其地址是不能确定的。在运行可执行程序时,系统会将执行权限先交至链接器(例如:libc.so.6),所以动态链接的符号时在运行时进行链接的。为了增快速度,链接器会采用Lazy的绑定方式,即函数的调用地址会在在第一次调用时才进行绑定。

      ①第一次调用时,实际的调用地址还未绑定,会执行解析绑定程序:

           

       ②绑定程序结束后,函数的调用地址将填入GOT相应位置,下次调用时可直接跳转:

               

    学习总结二:
    (1)动态链接时,如果调用 static 的变量及函数,会采用相对寻址的方式,数据段会创建一份副本。
    (2)调用非static的变量及函数时,都会通过PLT(procedure linkage table)间接调用。链接器链接时,为了增快速度,函数的调用地址会在在第一次调用时才进行绑定,解析绑定函数为_dl_runtime_resolve_xsave,参数需要两个,分别是重定位索引下标和所在模块ID,具体过程如下:

    重定位表在ELF文件中可以找到:

      

    三、运行调试验证

    具体调用过程可如下调试所示:

    ───────────────────────────────────────────────[ DISASM ]────────────────────────────────────────────────
        0x7ffff7bd36b3 <SubFunc+11> call puts@plt <0x7ffff7bd3590>
        0x7ffff7bd36b8 <SubFunc+16> mov dword ptr [rip + 0x20096e], 8 <0x7ffff7dd4030>
        0x7ffff7bd36c2 <SubFunc+26> mov rax, qword ptr [rip + 0x200917]
        0x7ffff7bd36c9 <SubFunc+33> mov dword ptr [rax], 8
        0x7ffff7bd36cf <SubFunc+39> call StaticTestFunc <0x7ffff7bd369a>0x7ffff7bd36d4 <SubFunc+44> call TestFunc@plt <0x7ffff7bd35a0>
    
    进入:
    ───────────────────────────────────────────────[ DISASM ]────────────────────────────────────────────────
    ►   0x7ffff7bd35a0 <TestFunc@plt> jmp qword ptr [rip + 0x200a7a] <0x7ffff7dd4020>
        0x7ffff7bd35a6 <TestFunc@plt+6> push 1
        0x7ffff7bd35ab <TestFunc@plt+11> jmp 0x7ffff7bd35800x7ffff7bd3580 push qword ptr [rip + 0x200a82] <0x7ffff7dd4008>
        0x7ffff7bd3586 jmp qword ptr [rip + 0x200a84] <0x7ffff7dec680>0x7ffff7dec680 <_dl_runtime_resolve_xsave> push rbx
        0x7ffff7dec681 <_dl_runtime_resolve_xsave+1> mov rbx, rsp
    
    执行到 0x7ffff7bd3586 时,栈如下所示:
    ────────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────
        00:0000│ rsp 0x7fffffffe3a8 —▸ 0x7ffff7ff5000 —▸ 0x7ffff7bd3000 ◂— jg 0x7ffff7bd3047
        01:00080x7fffffffe3b0 ◂— 0x1
        02:00100x7fffffffe3b8 —▸ 0x7ffff7bd36d9 (SubFunc+49) ◂— nop 
        03:0018│ rbp 0x7fffffffe3c0 —▸ 0x7fffffffe3d0 —▸ 0x5555555547e0 (__libc_csu_init) ◂— push r15
        04:00200x7fffffffe3c8 —▸ 0x5555555547cf (main+46) ◂— mov eax, 0

    由实际调用过程清楚可见,在调用 jmp qword ptr [rip + 0x200a84] <0x7ffff7dec680> 解析绑定函数前,的确传入两个参数,重写位下标 <TestFunc@plt+6> push 1 以及模块的ID push qword ptr [rip + 0x200a82] <0x7ffff7dd4008> 

    动态库使用时应编译成PIC形式,进程在使用时可使用相对寻址的方式,而无需修改代码中的地址从而产生副本代码浪费了内存,并且共享库所占物理内存可直接进行映射。关于数据部分,可执行程序程序模块在链接时就要将访问到时的数据地址确定下来,如果在可执行程序模块中使用了动态库中的全局变量,那么将在.bss段中创建一个副本,运行时则使用动态库中的初始值进行初始化,而动态库中GOT中该变量的指针也会指向此副本。

    DSO的优点:1)动态链接库的好处是可以节省内存占用,资源共享;

           2)团队之间成果物升级方便,无需重新编译;

    刚接触该部分内容,理解不够透彻,如总结有问题,日后会维护修改。

    本文图片摘自:zhuanlan.zhihu.com/p/25892385 感谢博主

    更为详细的博文推荐:blog.csdn.net/taocr/article/details/52433614

  • 相关阅读:
    Oracle数据库学习1--简介,基本了解
    数据导出excel表格和Word文档
    Ado.Net 数据库增删改查(联合版)
    Ado.Net 数据库增删改查
    Chapter 10. 设计模式--单例模式
    Chapter 10. 设计模式--工厂模式
    Chapter 9. 线程
    Chapter 8. 进程
    Chapter 7. 对话框控件
    Chapter 6. ListBox控件(双击播放图片)
  • 原文地址:https://www.cnblogs.com/GyForever1004/p/11749169.html
Copyright © 2020-2023  润新知