➠更多技术干货请戳:听云博客linux
动态连接web
要解决空间浪费和更新困难这两个问题最简单的方法就是把程序的模块相互分割开来,造成独立的文件,而再也不将它们静态的连接在一块儿。简单地讲,就是不对那些组成程序的目标文件进行连接,等到程序要运行时才进行连接。也就是说,把连接过程推迟到了运行时再进行,这就是 _动态连接(Dynamic Linking)_的思想。函数
延迟绑定(PLT)性能
动态连接比静态连接性能低,主要缘由是动态连接下对于全局和静态的数据访问都要进行复杂的GOT定位,间接寻址;对于模块间的调用也要先定位GOT,间接跳转, 程序的运行速度必定会减慢。 另外由于动态连接是在运行时完成连接的工做:在程序开始执行时,动态连接器都要进行一次连接工做,动态连接器会 search 并 load 所须要的 共享对象,而后进行符号查询 地址重定位,这一系列动做会减慢程序的启动速度。 优化
PLT 就是为了优化动态连接性能而存在,基本思想就是 当函数第一次被调用到时才进行绑定(符号查找,重定位),若是没有用到则不进行 bind。 这样在程序执行时,模块间的函数调用都没有进行 bind , 而是须要调用时才由 动态连接器来负责 bind 。这样能够加速程序的启动速度。spa
Mach-O Lazy Bind调试
Mach-O 文件经过dyld 加载的时候并无肯定每个函数的具体地址在哪里,而是在真正调用该函数的时候经过 过程连接表(procedure linkage table),简称 PLT,来进行一次lazybind。对象
结合Mach-O 文件的分析与代码的调试简单的分析一下。blog
源代码以下:图片
分别在两个printf函数处下 断点。
第一个调用printf函数
在0x100000f52 \<+34\>行处经过callq 0x100000f76 来调用printf。
执行callq指令 以后代码跳转到这里:
这里的jmpq 要跳转到 0x0000000100000F8C ,这个地址是从 —Data , —la-symbol-ptr 中的Lazy Symbol Pointers 中获取到的。
经过lldb 的命令 查看 0x100001010处的地址 获取了一样的值。
经过—stub—helper进行lazybind
在Mach-O 中每个Symbol Stub 可能有如下两种行为其中之一:跳转到函数的指令,执行函数体,经过动态动态库连接器查找函数的Symbol,而后执行函数体
查看 —stubs的Section 数据,发现只有一个函数就是 printf
这里的Data 其实就是上面看到的 jmpq 的代码。执行以后代码跳转到了这样的代码片断。
这里就是经过 —stub-helper来调用 dyld-stubbinder函数来计算printf 函数的真是地址。 经过下面的 信息能够看出来,jmpq 0x100000f7c ,就是在压如入参数0x0 (函数的link 的时候给的编号) 以后跳转到Section的起始处,调用 binder(一段汇编代码, 做用就是计算具体的函数地址,并调用printf 函数)
第二次调用printf 函数
执行指令以后发现和第一次调用printf 已经不同了。
再一次查看 0x100001010 处内存值。已经很第一次不一样了,也就是说, —Data, —la-symbol-ptr 中指向printf地址的值已经发生了变化,指向了 printf的指令。
这就证实了,延迟绑定只会在第一次调用的时候发生。整个流程与 linux中的PLT 和GOT 在实现逻辑上基本相同,只是实现代码不一样而已。
参考《Mac OS X and iOS Internals》、《连接,装载与库》