1. 桩函数的出现

首先在 main 函数中打断点:

image.png

开启汇编调试,运行代码,结果如下:

image.png

上图可知调用 NSLog 实际都是 callq 0x10659f788。在该地址上 的调用地址上打断点:

(lldb) breakpoint set -a 0x10659f788
Breakpoint 8: where = XKLibTest`symbol stub for: NSLog, address = 0x000000010659f788

执行代码,进入到对应断点,只有一句代码:

image.png

这里是一个关键点,慢慢看;

我们先来看 0x10659f788 这个值怎么来的

首先使用 image list找出偏移:

image.png

然后看下图:

image.png

到这里,我们知道了 NSLog 在被调用时,是直接跳转到了 __stubs 对应位置,然后执行该位置存储的机器码,这里猜测 FF25 就是 jmpq 的机器码指令,而 9A18、9C18 等等就是 jmpq 后面跟的参数,也就是和符号顺序相关的参数;

至此,可以得出结论:

  • 在静态编译时期,外部符号的实际调用代码会被替换成桩函数的调用(call stub);

2. 桩函数的具体含义

到这里,我们还是有疑问,0x000000010659f804 这个是怎么来的, NSLog 最终又是怎么被调用的呢?

我们继续看 jmpq *addr(%rip)

这个汇编代码的意思是:

  1. address = Current address + Value before (%rip) + Current Instruction Size;
  2. 取出 1 中计算得到的 address 中的值;

或者可以直接理解成:下一段汇编代码的地址 + rip 前的地址,然后取出这个指针的值进行跳转;

这里我们来算一下,从 Mach-O 文件中可以看到:

  1. 下一个指令的地址为:278E
  2. 指令集大小为 6 ;

如图:

image.png

所以可以这么算:

(lldb) p/x 0x10659f78e + 0x189a
(long) $3 = 0x00000001065a1028

然后我们来看看 0x00000001065a1028 中存储的是什么:

image.png

即:0x000000010659f804,和汇编代码的注释一致;

至此,我们又可以得出一个结论:

  • __stubs 表中存储的是机器码,其形式为:跳转指令 + 参数。

这个肯定是和不同的架构相关的,所以针对不同架构要区别对待。因为在 mac 中查看 Mach-O 、系统动态库时比较方便,所以当前使用的是模拟器,即 x86 架构;

3. 桩函数和懒加载符号表

这里其实还有一个关键点,0x000000010659f804 存储在哪里?这就需要看看 0x00000001065a1028 在 Mach-O 文件中的表现了,减去偏移之后如下:

image.png

这里其实我们也可以得出一个结论:

  • 桩函数总是去懒加载符号表中取出符号对应的指针,以此来完成符号函数的调用,只不过懒加载符号在静态编译时,其指针指向和 binder 相关的机器码位置有关;

4. stub_helper 函数

至此,我们知道了懒加载符号表中初始化时,存储的是 0x000000010659f804 这个地址。其实到这里,我们基本可以猜出来 0x000000010659f804 这个地址应该就是和懒加载符号绑定相关函数的地址了。

所以,我们需要看看这个地址在 Mach-O 中的表现,减去偏移看下结果:

(lldb) p/x 0x000000010659f804 - 0x10659d000
(long) $5 = 0x0000000000002804

对比下 Mach-O 文件来看下:

image.png

也就是说,懒加载符号表初始化时存储的这个地址指向 __stub_helper

从上图可以看出,0x000000010659f804 这个地址就是执行了 push + jmp 的指令,jmp 的位置都是一样的,push 的值不一样,而 push 的值肯定就是和懒加载符号顺序相关;

大概可以参数来 0x1000027f4 是 binder 函数的真正逻辑代码,而 push 就是传递符号在符号表中位置的参数;

为了验证自己的猜测,先打个断点看看实际的汇编代码吧,应该和 Mach-O 中的大差不差:

image.png

0x10659f7f4 就是 mach-o 文件的地址加上了偏移之后的结果,实际上都是指向 000027f4 这个地址,我们来看看这个地址存储的东西是否和 Mach-O 一致:

image.png

如上图,取 7 个字节转换大小端之后的结果为:0x051d8d4c + 0x180000 -> 0x4C8D1D05180000,和 Mach-o 中存储的值一致。

总结:

  • 懒加载符号表中的指针初始化时,指向 stub_helper 函数。stub_helper 的作用和 stub 函数类似,都是一个代理或者说统筹代码的接口。其逻辑就是调用 binder 函数并传递符号位置参数,参数连同汇编指令(push)一起固定在了 __stup_helper 中;

5. binder 函数

根据上文,binder 函数实际的机器码其实在 mach-O 中就已经能看到了(lea r11......),但是作为辅助验证,我们来打个断点看看实际的调用:

image.png

从注释中可以看到,这里就出现了一个关键的函数:dyld_stub_binder 。先不慌,既然这里又出现了jmpq *xxx(%rip) 这种汇编,我们来算一下 0x00007fff2025cbb4 这个地址怎么来的,也算是作为复习了:

0x10659f7fd 下一个指令的地址图上直接可以看到,即为 0x10659f803,所以:

(lldb) p/x 0x10659f803 + 0x181d
(long) $8 = 0x00000001065a1020
(lldb) x/4xw 0x00000001065a1020
0x1065a1020: 0x2025cbb4 0x00007fff 0x0659f804 0x00000001

验证成功;

接着,因为 binder 函数时从 0x00000001065a1020 这个地址取出来的,我们来看看 0x00000001065a1020 在 Mach-O 中的表现:

image.png

和运行时的汇编代码完美匹配。

至此,可以得到一个结论:

  • 静态编译时期或者说懒加载符号还未被加载之前,懒加载符号表中指针指向 stub_help 函数,这个函数传递符号的位置参数,最终调用 dyld_stub_binder 函数执行懒加载符号的绑定流程;

6. binder 函数汇编

到这里,接下来就应该看看 dyld_stub_binder 函数了,打个断点:

image.png

这里的汇编代码有点多,以后再具体分析吧;

7. 第二次调用

继续看,第二次调用 NSLog 时是这样的:

image.png

看注释可以知道这里指针存储的位置就是实际的 NSLog 的函数地址了。

也就是说,0x00000001065a1028 这个地址原本存储的是 stub_help 函数相关的位置,并传递了符号的位置信息,最终调用了 binder 函数。而现在被替换成了真实的函数地址,也就是在 binder 函数调用之后,真实的函数地址被替换到了懒加载符号表中,即:

image.png

至此,懒加载符号的绑定流程就结果了,流程图如下:

image.png

ps:其实真实逻辑是没有上图中的”已绑定“的判断的,都是直接调用,上图这么画是为了加深理解;

8. 总结

总结:

  1. 静态编译时期,外部符号函数的调用代码都会被替换成对应桩函数的调用代码;
  2. 每个符号都有对应的桩函数,存储于 __stubs 中。符号的不同本质上是桩函数参数的不同,这个参数和符号在懒加载符号表中的位置有关,而且这个参数(位置)编译时期就确定了,以机器指令的形式(FF25xxxx0000)固定在 __stubs 中;
  3. 静态编译时期,懒加载符号表的指针指向 __stub_help 函数,在 binder 完毕之后被替换成实际函数的地址。而__stub_help 函数的逻辑和 桩函数一样,异常简单,通过 push 传递符号位置参数,然后直接调用 binder 函数;
  4. 桩函数不关心 binder 具体逻辑,每次调用外部函数都是通过桩函数进行调用。而它只是按部就班的每次都去懒加载符号表中取出该符号的指针指向的地址,然后进行跳转。所以第一次调用懒加载符号跳转到了 binder 函数,而第二次调用则跳转到了真实的函数地址;
  5. 非懒加载符号在动态链接时就在非懒加载符号表中写入了函数的具体地址;

一句话总结:

桩函数每次调用都会去懒加载符号表中取符号对应的函数地址进行调用。静态编译时期,懒加载符号的指针指向 dyld_stub_binder,所以第一次调用时都会执行外部函数 dyld_stub_binder 来对懒加载符号进行绑定,而 dyld_stub_binder 是非懒加载符号,在动态链接时就已经绑定。dyld_stub_binder 调用完毕之后替换懒加载符号中的指针,指向了实际的函数地址,所以第二次调用仍然是利用桩函数去懒加载符号表中取出指针,但此时的指针已经指向实际的函数地址,可以直接调用;


http://www.taodudu.cc/news/show-1912767.html

相关文章:

  • 汇编:call和jmp
  • code review的一些思考
  • iOS:fishhook原理分析
  • mach-O文件结构分析
  • 设备唯一标志的解决方案
  • iOS:主流启动优化方案浅析
  • iOS:segment对齐原则
  • HTTP缓存机制及其在iOS中的应用
  • iOS:SideTable
  • iOS:isa指针
  • iOS底层:PAGEZERO的作用
  • iOS图形学(三):屏幕成像原理
  • iOS图形学(四):iOS中的绘图框架
  • Java基础(一):简介和基础数据类型
  • Java基础(二):面向对象
  • Java:常量池
  • Java基础(三):常用对象
  • Java基础(四):异常处理
  • Java基础(五):多线程
  • Android:权限处理
  • AsyncTask的基本使用
  • 在Nginx中配置SSL证书
  • Base64编码流程
  • Nginx配置基础认证
  • Cookie、Session、Token、RefreshToken
  • JSCore浅析及其在iOS上的使用
  • 编程语言的动态性(Dart和OC对比)
  • iOS:Universal Link
  • AFN中的鉴权
  • openGL ES 教程(二):渲染管线

iOS:懒加载符号绑定流程相关推荐

  1. mybatis -- 懒加载原理

    目录 测试代码 调试代码 为什么BlogResp2是代理对象呢? 什么时候创建的代理对象呢? 让我们看一下源码 懒加载的赋值流程 懒加载失效的原因 blogResp2的代理对象是如何构建lazyLoa ...

  2. Linux C++ libdl.so dlfcn.h使用方法(dlopen()、dlsym()、dlclose()、dlerror())(用于动态链接库操作)(懒加载、立即加载)共享库符号、动态库

    文章目录 dlfcn.h解析 - dlopen():打开一个动态链接库文件并返回一个句柄,该句柄用于后续的操作,如查找符号.关闭库等. - dlsym():在打开的动态链接库中查找指定的符号,并返回符 ...

  3. iOS开发UI中懒加载的使用方法

    1.懒加载基本 懒加载--也称为延迟加载,即在需要的时候才加载(效率低,占用内存小).所谓懒加载,写的是其getter方法.说的通俗一点,就是在开发中,当程序中需要利用的资源时.在程序启动的时候不加载 ...

  4. iOS开发UI篇—懒加载

    iOS开发UI篇-懒加载 1.懒加载基本 懒加载--也称为延迟加载,即在需要的时候才加载(效率低,占用内存小).所谓懒加载,写的是其get方法. 注意:如果是懒加载的话则一定要注意先判断是否已经有了, ...

  5. (0020)iOS 开发之-设计模式-懒加载解惑

    提醒:这是苹果公司提倡的做法.其实苹果公司做的IOS系统中很多地方都用到了懒加载的方式,比如控制器的View的创建. 懒加载:也称为延迟加载,即在需要的时候才加载(效率低,占用内存小).所谓懒加载,其 ...

  6. ios加载本地html懒加载图片方案,IOS开发中加载大量网络图片优化方法

    IOS开发中加载大量网络图片如何优化 1.概述 在IOS下通过URL读一张网络图片并不像其他编程语言那样可以直接把图片路径放到图片路径的位置就ok,而是需要我们通过一段类似流的方式去加载网络图片,接着 ...

  7. 【iOS】—— 懒加载

    懒加载 首先,要明白懒加载的概念,懒加载的实质就是延迟加载,iOS设备在加载时有限度,如果我们的数据很大,一次性将其全部加载出来可能对内存的损耗比较大,懒加载的作用就是将其在需要的时候再加载出来. 懒 ...

  8. ios wkweb设置图片_ios·WKWebView\UIWebView加载HTMLString,实现图片懒加载

    背景: 项目中开发商品类型数据,数据可变性较大,所以商品详情数据存在文案和图片富文本显示,后台返回了html格式的数据供前端展示. 如果用webView直接显示的话,需要等html内容完全展示才能获取 ...

  9. 懒加载和预加载的区别_类的动态创建(ro,rw)amp; 懒加载类和非懒加载类底层加载的区别 amp; 类和分类的搭配分析...

    黑客技术点击右侧关注,了解黑客的世界! Java开发进阶点击右侧关注,掌握进阶之路! Python开发点击右侧关注,探讨技术话题!作者丨OSMin链接:https://juejin.im/post/5 ...

  10. 分页组件change_javascript原生瀑布流+图片懒加载组件

    我不是天使:javascript原生手动分页组件​zhuanlan.zhihu.com 我不是天使:javascript原生自定义轮播图组件​zhuanlan.zhihu.com 一大早就去办签证,啦 ...

最新文章

  1. 基于OpenCV的多位数检测器
  2. 打破情感分类准确率80分天花板!更充分的知识图谱结合范式
  3. 用C语言实现Ping程序功能
  4. linux mint 18.3 内核,Linux Mint Linux用户可以升级到18.2 18.3”
  5. Markdown预览功能不可用解决方案
  6. 科技圈CEO用微鲸尬明星脸,除了罗永浩还有哪些大牛光荣上榜?
  7. 利用Gabor变换法分析纹理图像 matlab代码实现
  8. IOS开发之异步加载网络图片并缓存本地实现瀑布流(一)
  9. oracle导入源数据库文件,将dmp文件数据导入oracle数据库
  10. python使用 docx 库操作 docx 格式文件
  11. python运行时关闭硬件信息-Python实现的读取电脑硬件信息功能示例
  12. 啤酒和饮料,切面条(python)
  13. C语言REPEAT程序,汇编语言定使用WHILE、REPEAT、FOR 和 FORC伪指令定义重复语句块
  14. python3 setup.py install_安装Twisted执行python3 setup.py install报错
  15. python实现利用留数定理分解分式多项式
  16. CMT2380F32模块开发6-flash例程
  17. NOI银河英雄传说(并查集)
  18. JQ8900语音模块组合播放
  19. 怎么设置360主页的html,怎么使用360安全卫士设置浏览器主页
  20. 石墨加工机器keyword

热门文章

  1. ajax 跨域 提交cookie,Ajax跨域请求COOKIE无法带上的完美解决办法
  2. 再谈mysql之执行计划explain
  3. Java实现单链表翻转
  4. Spotlight监控Oracle--Spotlight On Oracle安装和使用
  5. java集合框架之LinkedList
  6. Mesos框架对比:Marathon 和 Aurora
  7. python-pip : Depends: python-setuptools (= 0.6c1) 问题
  8. linux上jdk安装
  9. Java建造者模式(Builder模式)
  10. Ubuntu 第2章 基本命令和文件系统