动态库连接器–动态库链接信息(Mach-O文件格式和程序从加载到执行过程)
section cmd | 说明 | 举例 |
---|---|---|
__text | 主程序代码 | |
__stubs | 用于动态库链接的桩 | |
__stub_helper | 用于动态库链接的桩 | |
__cstring | 常亮字符串符号表描述信息,通过该区信息,可以获得常亮字符串符号表地址 | |
__unwind_info | 这里字段不是太理解啥意思,希望大家指点下 |
动态库连接器–动态库链接信息
总结了mach-o文件的两个最重要的部分,那么动态库根据加载命令如何动态链接到内存中的呢?下面总结这个动态过程。
- 系统通过加载命令,获得动态加载器的地址/usr/lib/dyly(其解决的问题是,把代码段__TEXT中和动态库相关内容进行关联,比如代码中如何调用到哪个动态库的相关代码段的偏移地址)
(dyly是用户态进程,这个是开源的,不属于kern内核的部分)感兴趣可以看看这里
总结下:在加载命令中,和动态库和链接相关命令有如下几个:
- 段命令
- LC_DYLD_INFO_ONLY
- LC_LOAD_DYLIB
- LC_LOAD_DYLINKER
- LC_SYMTAB
- LC_DYSYMTAB
- 区命令
- __stubs
- __stubs_helper
在进行动态链接器工作前,要先解析相关工作环境参数
- LC_LOAD_DYLINKER 获得动态加载器地址
- LC_LOAD_DYLIB 二进制文件依赖动态库信息
可以使用otool工具,读取该部分信息
代码5.0
yingfang:mach-o文件结构-src fangying$ otool -L a.out
a.out:
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1225.1.1)
- LC_DYLD_INFO_ONLY 动态库信息,根据该命令是真正动态库绑定,地址重定向重要的信息。
结合之前otool -l的信息中的command 4命令,下面struct就是该命令对应的数据结构:
代码5.1
struct dyld_info_command {
uint32_t cmd; /* LC_DYLD_INFO or LC_DYLD_INFO_ONLY */
uint32_t cmdsize; /* sizeof(struct dyld_info_command) */
uint32_t rebase_off;
uint32_t rebase_size;
uint32_t bind_off;
uint32_t bind_size;
uint32_t weak_bind_off;
uint32_t weak_bind_size;
uint32_t lazy_bind_off;
uint32_t lazy_bind_size;
uint32_t export_off;
uint32_t export_size;
};
根据该加载命令的字段偏移,系统可以得到压缩动态数据信息区(dymanic load info)。根据上述数据,dymanic load info数据区,主要包含了5种数据:
(下面的一些内容,我的理解可能不是太正确,希望和大家一起讨论)dyld_info_command具体定义地址
dymanic load info | 说明 | 举例 |
---|---|---|
重定向数据 rebase | demo中该段数据位 11 22 10 51 | 11: 高位0x10表示设置立即数类型,低位0x01表示立即数类型为指针 22: 表示REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB + 2 重定向到数据段2。结合上面的信息,就是重定向到数据段2,该段数据信息为一个指针 结合数据段2的数据,获得一个重定向符号指针[0x100001010 -> _printf] |
绑定数据 bind | 在demo中进行动态绑定依赖的dyld的函数 | U dyld_stub_binder |
弱绑定数据 weak bind | 用于弱绑定动态库,就像weak_framework一样 | |
懒绑定数据 lazy bind | 对于需要从动态库加载的函数符号 | demo中有两个: U _printf U _scanf |
export数据 | 用于对外开放的函数 | demo中只有两个 0000000100000000 T __mh_execute_header 0000000100000f50 T _main |
对于相关的绑定函数查找,可以使用nm命令
代码5.2
yingfang:mach-o文件结构-src fangying$ nm a.out
0000000100000000 T __mh_execute_header
0000000100000f50 T _main
U _printf
U dyld_stub_binder
>
标注:dymanic load info数据是以命令码(命令码就是一个字节码)的形式,传递具体内容。高四位表示真正命令名,低四位表示一个立即数。00表示该类型命令结束
可以使用dylyinfo获得这部分信息读取
代码5.3
yingfang:mach-o文件结构-src fangying$ xcrun dyldinfo -opcodes a.out
rebase opcodes:
0x0000 REBASE_OPCODE_SET_TYPE_IMM(1)
0x0001 REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB(2, 0x00000010)
0x0003 REBASE_OPCODE_DO_REBASE_IMM_TIMES(1)
0x0004 REBASE_OPCODE_DONE()
binding opcodes:
0x0000 BIND_OPCODE_SET_DYLIB_ORDINAL_IMM(1)
0x0001 BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM(0x00, dyld_stub_binder)
0x0013 BIND_OPCODE_SET_TYPE_IMM(1)
0x0014 BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB(0x02, 0x00000000)
0x0016 BIND_OPCODE_DO_BIND()
0x0017 BIND_OPCODE_DONE
no compressed weak binding info
lazy binding opcodes:
0x0000 BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB(0x02, 0x00000010)
0x0002 BIND_OPCODE_SET_DYLIB_ORDINAL_IMM(1)
0x0003 BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM(0x00, _printf)
0x000C BIND_OPCODE_DO_BIND()
0x000D BIND_OPCODE_DONE
0x000E BIND_OPCODE_DONE
0x000F BIND_OPCODE_DONE
动态库链接器运行结果
上面总结,动态链接器相关信息(加载命令信息,加载命令偏移地址相关信息)。现在总结下动态链接器运行的结果是什么?如何使用相关动态链接信息,完成相关过程的?
从字面上,之前我理解,链接器就是把文本段原来动态库函数相关地址,用真实的动态库地址替换,生成一个真实的完整的可执行文本。比如demo中printf是其它动态库的,但是其真实地址对于可执行文件是不知道,只有这个执行文件运行时,通过动态链接器完善其原来文本段地址。
在总结之前两个问题前,先总结下__stubs(桩)的区概念:该区存放的是二进制文件中未定义符号的占位符,编译器生成代码时会创建对符号桩区的调用,链接器在运行时解决对桩的这些调用。链接器解决方案是在被调用的地址处,放置一条JMP指令。JMP指令将控制权转交给真实的函数体。
回顾下nm命令结果:U表示未定义的符号
yingfang:mach-o文件结构-src fangying$ nm a.out
0000000100000000 T __mh_execute_header
0000000100000f50 T _main
U _printf
U dyld_stub_binder
看下demo中main函数汇编代码:
- demo中真实的print函数调用,编译器,编译为callq 0x100000f84 ## symbol stub for: _printf
- 汇编代码中,callq 0x100000f84 . 注意0x100000f84,这个地址是 __TEXT段的__stubs区的地址。换句话说,就是JMP到__stubs(桩区)– 段1 section位__stubs的起始地址
- 0x100000f84地址,是一段汇编指令 0x100000f84: jmpq *0x86(%rip) # 0x100001010
- 0x100001010地址,是指向(代码7.2)数据段__la_symbol_ptr区,由于这个数据都符号指针,查看下该地址指向的数据区域(代码7.3)
- 0x100001010地址指针,指向地址为4294971292(数据段都在高位,所以相比__TEXT地址,这个地址是正常的)
- 4294971292地址,执行汇编代码请看(代码7.4) 0x100000fa1: jmpq 0x100000f8c。请注意 0x100000f8c就是__TEXT段section __stub_helper区的起始地址。该地址是执行汇编代码具体请看(代码7.5)0x100000f95: jmpq *0x65(%rip) # 0x100001000
- 0x100001000地址,恰好是__DATA段, __nl_symbol_ptr区的其实地址,该地址指向的数据值位为 0x100001000: 0x0000000000000000 0x0000000000000000 (代码7.6)
>
总结:
+ __stubs区和__stub_helper区是帮助动态链接器找到指定数据段__nl_symbol_ptr区,二进制文件用0x0000000000000000进行占位,在运行时,系统根据dynamic loader info信息,把占位符换为调用dylib的dyld_stub_binder函数的汇编指令。
+ 当第一次调用完动态库中的符号后,动态链接器会根据dynamic loader info信息,把数据段__la_symbol_ptr指向正在的符号地址,而不是指向_nl_symbol_ptr区
http://blog.****.net/bjtufang/article/details/50628310