zoukankan      html  css  js  c++  java
  • ELF文件加载与动态链接(二)

    GOT应该保存的是puts函数的绝对虚地址,这里为什么保存的却是puts@plt的第二条指令呢? 原来“解释器”将动态库载入内存后,并没有直接将函数地址更新到GOT表中,而是在函数第一次被调用时,才会进行函数地址的重定位,这样做的好处是可以加快程序加载速度,尤其对大型程序来说。有关这方面的更详细的信息,可以搜索“动态链接库的延迟绑定技术”。

    继续看第二条指令,pushq $0x0代表什么? 查看Hello world程序的重定位节:

    ezreal@ez:~/workdir$ readelf -a hello
    ...
    Relocation section '.rela.plt' at offset 0x398 contains 3 entries:
      Offset          Info           Type           Sym. Value    Sym. Name + Addend
    000000601018  000100000007 R_X86_64_JUMP_SLO 0000000000000000 puts + 0
    000000601020  000200000007 R_X86_64_JUMP_SLO 0000000000000000 __libc_start_main + 0
    000000601028  000300000007 R_X86_64_JUMP_SLO 0000000000000000 __gmon_start__ + 0

    其中的第一项就是puts的重定位信息,$0x0即代表puts相对于.rela.plt节的相对偏移。类似的可以看到$0x1代表__libc_start_main的相对偏移,$0x2代表__gmon_start__的相对偏移。

    而OFFSET这个域表示的是puts函数在GOT表项中的位置,0000000000601018,从puts@plt的第一条指令可以看出这一点。

    向堆栈中压入这个偏移量的目的一是找到puts函数的符号名,二是找到puts函数地址在GOT表项中的位置,以便后面定位到puts的相对虚地址时写入到这个GOT表项。

    puts@plt的第三条指令就跳到了PLT0的位置。这条指令只是将0x400400这个数值压入堆栈,它实际上是GOT表项的第二个元素即GOT[1],上面写了GOT[1]是共享库链表的地址。

    接着PLT0的第二条指令即跳到了GOT[2]中所保存的地址,即_dl_runtime_resolve函数的入口。_dl_runtime_resolve的定义如下:

    _dl_runtime_resolve:
        pushl %eax      # Preserve registers otherwise clobbered.
        pushl %ecx
        pushl %edx
        movl 16(%esp), %edx # Copy args pushed by PLT in register.  Note
        movl 12(%esp), %eax # that `fixup' takes its parameters in regs.
        call _dl_fixup      # Call resolver.
        popl %edx       # Get register content back.
        popl %ecx
        xchgl %eax, (%esp)  # Get %eax contents end store function address.
        ret $8          # Jump to function address.

    _dl_runtime_resolve的汇编代码请参考: http://althing.cs.dartmouth.edu/secref/resources/plt-got.txt

    从调用puts函数到现在,总共有两次压栈操作,一次是压入puts函数的重定向信息的偏移量(pushq $0x0),一次是GOT[1](pushq 0x200c02(%rip))。上面汇编指令的两次movl操作就是将这两个数据分别取到edx和eax,然后调用_dl_fixup找到puts函数的实际加载地址,将它写到GOT中,并把这个地址压入eax作为_dl_runtime_resolve函数的返回值。xchagl指令恢复eax寄存器,并将puts函数地址压到栈顶,这样当执行ret指令后,控制就转移到puts函数内部。ret指令同时也完成了清栈动作,使栈顶指向puts函数的返回地址(main函数中callq的下一条指令),这样当puts函数返回时程序就走到正确的位置执行。

    当第二次调用puts函数时,由于其地址已经存在GOT对应表项中,直接根据地址跳转就可以。下面画了一张图理解上述过程:

    总结

    用户通过shell执行程序,shell通过exceve进入系统调用。【用户态】
    sys_execve经过一系列过程,并最终通过ELF文件的处理函数load_elf_binary将用户程序和ELF“解释器”加载进内存,并将控制权交给“解释器”。【内核态】
    ELF“解释器”进行相关共享库的加载,并最终把控制权交给用户程序。由“解释器”处理用户程序运行过程中符号的动态解析。【用户态】

    参考资料

      1. ELF文件格式分析 —— 滕启明

      2. ELF文件的加载和动态链接过程

      3. PLT/GOT探索,http://althing.cs.dartmouth.edu/secref/resources/plt-got.txt

      4. Stackoverflow,_dl_runtime_resolve — When do the shared objects get loaded in to memory?

      5. rip相对寻址,http://bbs.pediy.com/showthread.php?t=193425

      6. RIP相对寻址技术,一句话总结,http://www.vxjump.net/files/virus_analysis/X64_vir_infect.txt

  • 相关阅读:
    [转]常用数字处理算法的Verilog实现
    [转]Linux必学的60个命令
    [转]4位超前进位加法器代码及原理
    [转]FPGA & Verilog开发经验若干
    [转]Verilog中parameter和define的区别
    [转]VIM标记 mark 详解
    关于获取服务的需求列表
    Office 2007 探索之路 Outlook
    利用反射建立单一调用的WebService
    寻宝记
  • 原文地址:https://www.cnblogs.com/gm-201705/p/9901553.html
Copyright © 2011-2022 走看看