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. Mac下配置/使用GitHub

    一.配置1. 查看是否有id_rsa.pub文件:cd ~/.ssh 2. 如果没有id_rsa.pub文件,执行如下命令来生成id_rsa.pub文件: ssh-keygen -t rsa -C & ...

  2. TOR的十个最好的替代工具

    TOR的十个最好的替代工具: 一.Comodo Dragon(基于Chromium) TOR基于Firefox,因为我们换个口味,首先推荐一个基于开源项目Chromium的Comodo Dragon, ...

  3. [LeetCode] Network Delay Time 网络延迟时间——最短路算法 Bellman-Ford(DP) 和 dijkstra(本质上就是BFS的迭代变种)

    There are N network nodes, labelled 1 to N. Given times, a list of travel times as directed edges ti ...

  4. Scanner类完成用户键盘录入

    l  Scanner类 Scanner类是引用数据类型的一种,我们可以使用该类来完成用户键盘录入,获取到录入的数据. Scanner使用步骤: 导包:import java.util.Scanner; ...

  5. vs2015如何使用附加进程调试发布在IIS上项目

    1.如何使用附加进程调试IIS上的网站项目 1)在IIS部署一个网站项目 2)保证浏览器可访问(比如访问登陆页面) 3)在项目中LoginController断点,并在工具栏的调试找到附加到进程 4) ...

  6. HDU 1005 Number Sequence(数论)

    HDU 1005 Number Sequence(数论) Problem Description: A number sequence is defined as follows:f(1) = 1, ...

  7. react router @4 和 vue路由 详解(七)react路由守卫

    完整版:https://www.cnblogs.com/yangyangxxb/p/10066650.html 12.react路由守卫? a.在之前的版本中,React Router 也提供了类似的 ...

  8. C++解析六-继承

    面向对象程序设计中最重要的一个概念是继承.继承允许我们依据另一个类来定义一个类,这使得创建和维护一个应用程序变得更容易.这样做,也达到了重用代码功能和提高执行时间的效果.当创建一个类时,您不需要重新编 ...

  9. 《Python》网络编程之验证客户端连接的合法性、socketserver模块

    一.socket的更多方法介绍 # 服务端套接字函数 s.bind() # 绑定(主机,端口号)到套接字 s.listen() # 开始TCP监听 s.accept() # 被动接受TCP客户的连接, ...

  10. Android : 基于alsa库的音乐播放

    继上篇:Android : alsa-lib 移植 ,这篇随笔实现一个demo基于移植好的alsa库在Android平台上播放wav文件: 一.利用ffmeg将一个mp3文件转换成wav文件: (1) ...