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

ELF文件加载与动态链接(二)的更多相关文章

  1. ELF文件加载与动态链接(一)

    关于ELF文件的详细介绍,推荐阅读: ELF文件格式分析 —— 滕启明.ELF文件由ELF头部.程序头部表.节区头部表以及节区4部分组成. 通过objdump工具和readelf工具,可以观察ELF文 ...

  2. cmake中设置ELF文件加载动态库的位置

    1. 三个文件 1. world.c #include<stdio.h> void world(void) { printf("world.\n"); } 2. hel ...

  3. C#开发奇技淫巧二:根据dll文件加载C++或者Delphi插件

    原文:C#开发奇技淫巧二:根据dll文件加载C++或者Delphi插件 这两天忙着把框架改为支持加载C++和Delphi的插件,来不及更新blog了.      原来的写的框架只支持c#插件,这个好做 ...

  4. 使用js加载器动态加载外部js、css文件

    let MiniSite = new Object(); /** * 判断浏览器 */ MiniSite.Browser = { ie: /msie/.test(window.navigator.us ...

  5. Android 的 so 文件加载机制

    本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布 最近碰到一些 so 文件问题,顺便将相关知识点梳理一下. 提问 本文的结论是跟着 System.loadlibrary() 一层层源 ...

  6. 插件化框架解读之so 文件加载机制(四)

    阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680 提问 本文的结论是跟着 System.loadlibrary() ...

  7. 动态库DLL加载方式-静态加载和动态加载

    静态加载: 如果你有a.dll和a.lib,两个文件都有的话可以用静态加载的方式: message函数的声明你应该知道吧,把它的声明和下面的语句写到一个头文件中 #pragma comment(lib ...

  8. 动态链接库dll的 静态加载 与 动态加载

    dll 两种链接方式  : 动态链接和静态链接(链接亦称加载) 动态链接是指在生成可执行文件时不将所有程序用到的函数链接到一个文件,因为有许多函数在操作系统带的dll文件中,当程序运行时直接从操作系统 ...

  9. JVM系列【3】Class文件加载过程

    JVM系列笔记目录 虚拟机的基础概念 class文件结构 class文件加载过程 jvm内存模型 JVM常用指令 GC与调优 Class文件加载过程 JVM加载Class文件主要分3个过程:Loadi ...

随机推荐

  1. 如何把一个杯子卖到上万元,不学你就OUT了

    我们可以看看一个产品卖到多少钱需要占有什么样的资源: 第1种卖法:卖产品本身的使用价值,只能卖3元/个 如果你将他仅仅当一只普通的杯子,放在普通的商店,用普通的销售方法,也许它最多只能卖3元钱,还可能 ...

  2. iperf测试工具

    一.iperf工具安装: 1.获取iperf源码安装包(iperf-3.0.5.tar.gz) 2.将iperf安装包上传到服务器/tmp/目录并解压 [root@localhost /]#cd /t ...

  3. redis事件监听及在订单系统中的使用

    https://blog.csdn.net/qq_37334135/article/details/77717248 通常在网上买好物品,或者说手机扫码后,点击付款,这时就会向后台发送请求,生成订单信 ...

  4. python(3)之字符串

    字符串常用操作如下: name="huang yuqing"print(name.count("h"))#计算包含字符的个数print(name.capital ...

  5. oracle中查询当前系统时间用到的dual是什么?

    oracle我们查询当前系统时间的时候,经常用到dual,这个是什么东西呢? -- 查询系统时间 结果:2018/04/17 15:00:48 -- select sysdate from dual; ...

  6. 方便好用的 Idea mybatis 插件 MyBatisCodeHelper

    优点: 1.mapper文件(即表对应的dao)与xml文件自由切换,方便代码评审: 2.自动代码生成功能提高开发效率,mysql数据库创建好表结构,写完 pojo(注意字段类型要统一用对象类型!), ...

  7. mysql不会使用索引,导致全表扫描情况

    不会使用索引,导致全表扫描情况 1.不要使用in操作符,这样数据库会进行全表扫描,推荐方案:在业务密集的SQL当中尽量不采用IN操作符 2.not in 使用not in也不会走索引推荐方案:用not ...

  8. opencv测试代码

    摄像头摄影 #include <iostream>#include <opencv2/opencv.hpp>using namespace cv;using namespace ...

  9. 给rm命令设置别名防止误操作

    1通过alias命令查看别名格式并按照格式修改 alias rm='rm -i' 修改为 alias rm='echo command not found' 2通过编辑vim /etc/profile ...

  10. session和cokkie的区别与作用

    session在计算机中,尤其是在网络应用中,称为“会话机制”,Session对象存储特定用户会话所需的属性及配置信息,这样,当用户在应用程序的web页之间跳转时,存储在session对象中的变量将不 ...