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. Git:标签管理

    1.创建标签 1.1切换需要打标签的分支 git checkout branch-name 1.2创建标签 git tag tag-name 1.3查看标签 git tag 1.4注意:打标签时默认在 ...

  2. js获取当前iframe的ID

    self.frameElement.getAttribute('id');其他属性也可以通过这种方式获取. 也可以通过 window.frameElement.id 获取当前iframe的ID

  3. Linux Shell获取系统资源使用百分比(CentOS)

    CPU使用率: top -b -n | | 内存使用率: free -m | grep '^-' | awk '{print $3/($3+$4)*100"%"}' IO使用率(F ...

  4. python字典和列表的高级应用

    1.将序列分解为单独的变量 1.1问题 包含n个元素的元组或列表.字符串.文件.迭代器.生成器,将它分解为n个变量 1.2方案 直接通过赋值操作 要求:变量个数要等于元素个数 当执行分解操作时,有时需 ...

  5. JQuery进度条

    需要实现效果如下图. 页面代码<div class='progress_bar' data-color='#f66' data-per='"+list[i].percent+" ...

  6. react router @4 和 vue路由 详解(一)vue路由基础和使用

    完整版:https://www.cnblogs.com/yangyangxxb/p/10066650.html 1.vue路由基础和使用 a.大概目录 我这里建了一个router文件夹,文件夹下有in ...

  7. learning ddr mode register MR2

  8. React中禁止chrome填充密码表单

    当 input 的 type="password" 时,chrome浏览器会以 type="password" 为标识记住输入的用户名和密码, 如果chrome ...

  9. 将远程mysql服务器数据导出 csv 并发送到我的本机

    1.在SQL上执行查询并导出操作 '; 2.发送到本机 SQL>system sz idIsNull2.csv;

  10. 九. Python基础(9)--命名空间, 作用域

    九. Python基础(9)--命名空间, 作用域 1 ● !a 与 not a 注意, C/C++可以用if !a表示if a == 0, 但是Python中只能用if not a来表示同样的意义. ...