程序的加载与链接(六) 动态库 lazy bind

简述

看了很久的关于动态库链接加载的知识,但对其中的一些细节一直似懂非懂的,所以决定实践一下加深一下印象。本文主要介绍动态库符号的lazy bind 过程。

Demo

先实现一个简单的动态库

// .h
int add(int a,int b);
int sub(int a,int b);

// .c 
#import "*.h"

int add(int a,int b) {

    return a + b;
}

int sub(int a,int b) {
 
    return a - b;
}

实现一个简单的app

// ViewController.m
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    int result = add(3, 4);
//    void * addPoint = add;
//    void * subPoint = sub;
    
    result = sub(3, 4);
    
    NSLog(@"********");
    
    
}


传送门

符号表

屏幕快照 2019-11-04 上午11.17.37.png
  • 符号名: _add
  • 类型: 未定义
  • 来源: adder lib

间接符号表

所有需要间接访问的符号都存放在这里,包括lazy和no_lazy符号,类似于重定位表的格式


屏幕快照 2019-11-04 上午11.18.30.png
  • 符号名
  • 段名
  • 地址

lazy bind 符号表

存放着lazy bind 符号地址信息, 例如: _add 符号地址0x100003038, 它的value 是多少呢?0x100001a80

屏幕快照 2019-11-04 下午3.57.35.png

0x100001a80 指向的是一段汇编代码:

0000000100001a70         lea        r11, qword [ds:0x100003008]                 ; 0x100003008 (imp___nl_symbol_ptr_dyld_stub_binder + 0x8), XREF=0x100000170, 0x100001a85, 0x100001a8f, 0x100001a99, 0x100001aa3, 0x100001aad, 0x100001ab7, 0x100001ac1, 0x100001acb, 0x100001ad5, 0x100001adf
0000000100001a77         push       r11
0000000100001a79         jmp        qword [ds:imp___nl_symbol_ptr_dyld_stub_binder]
0000000100001a7f         nop        
0000000100001a80         push       0x3f                                        ; XREF=imp___la_symbol_ptr__add
0000000100001a85         jmp        0x100001a70

整理一下就是:

0000000100001a80         push       0x3f    
0000000100001a70         lea        r11, qword [ds:0x100003008]                 ; 0x100003008 (imp___nl_symbol_ptr_dyld_stub_binder + 0x8), XREF=0x100000170, 0x100001a85, 0x100001a8f, 0x100001a99, 0x100001aa3, 0x100001aad, 0x100001ab7, 0x100001ac1, 0x100001acb, 0x100001ad5, 0x100001adf
0000000100001a77         push       r11
0000000100001a79         jmp        qword [ds:imp___nl_symbol_ptr_dyld_stub_binder]

稍后再解释它的作用

验证lazy bind

我现在要校验一下add函数调用之后, _add 符号的间接地址指针是否被修改了

  • 程序启动前main函数地址: 0x100001740
  • 运行时地址main函数地址: 0x1023c6740
  • 因此得到全局偏移量 offset: 0x1023c6740 - 0x100001740
  • 计算得到_add 符号间接寻址地址: offset + 0x100003038 = 0x1023c8038
  • 查看 0x1023c8038 内存: 0x00000001026b8f40


    屏幕快照 2019-11-04 下午2.39.16.png
  • log add 函数地址: 0x00000001026b8f40
屏幕快照 2019-11-04 下午2.40.41.png

这样就成功校验了,lazy bind 理论是正确的

: 观察完整的lazy bind 其实还有一步,就是在没有调用add符号之前,查看间接符号的值, 这个值等于 0x100001a80 + offset, 即指向上面提到的那段汇编代码

lazy bind 过程

  1. 调用 stub_add 函数


    屏幕快照 2019-11-04 下午3.14.31.png
  1. stub_add 会跳转到 间接符号表所指向的地址(间接寻址的关键指令)


    屏幕快照 2019-11-04 下午3.15.30.png
屏幕快照 2019-11-04 上午11.19.03.png

此时 :imp___la_symbol_ptr__add 指向的就是上述的那段汇编代码的地址, 静态地址: 0x100001a80 ,动态地址 : 0x100001a80 + offset

下面分别使用MachOView 和 Hopper disassembler 分析一下source code

屏幕快照 2019-11-04 下午3.17.51.png
屏幕快照 2019-11-04 下午3.19.41.png

imp___nl_symbol_ptr_dyld_stub_binder 所处的section:


屏幕快照 2019-11-04 下午4.32.06.png

该段代码最终会调用 imp___nl_symbol_ptr_dyld_stub_binder 函数,由动态链接库dyld.lib的stub_binde函数完成符号绑定操作。 imp___nl_symbol_ptr_dyld_stub_binder 需要两个参数

  • lazy binder point address: 0x100003008
  • symbol flag: 主要靠这个识别是哪个函数需要bind,至于为什么是0x3f 具体是怎么编码的还没研究过

imp___nl_symbol_ptr_dyld_stub_binder 属于no_lazy_symbol point,这个在程序启动后就已经初始化完毕,它指向dyld.lib 的 stub_binder 函数地址, stub_binder完成查找_add函数的地址,并且写入间接符号表中,最终写入地址( 0x100001a80 + offset), 之后就不再需要绑定了

  • 第一次调用add函数: imp___la_symbol_ptr__add 指向 stub_bind 代码块,触发bind操作
  • 第二次调用add函数: imp___la_symbol_ptr__add 指向add函数真实地址,直接调用add函数

总结

程序加载过程

  1. LC_SEGMENT_64 : 加载代码段、数据段
  2. LC_LOAD_DYLINKER: 加载动态库链接器
  3. LC_LOAD_DYLIB: 加载动态库
  4. 完成 no lazy bind
  5. 程序启动后, 在第一调用动态库函数时完成 lazy bind 操作
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容