Executables
A process is created as a result of loading a specially crafted file into memory. This file has to be in a format that is understood by the operating system, which in turn can parse the file, set up the required dependencies (such as libraries), initialize the runtime environment, and begin execution.
翻译过来,可执行文件是指 可以被进程加载到内存 可被操作系统解析、设置依赖库、初始化运行时环境以及可执行的文件。
iOS主要用到两种格式的可执行文件:
- Universal (fat) binaries:Multiple-architecture binaries used exclusively in OS X.
- Mach-O:OS X native binary format.
Universal (fat) binaries
由于设备架构的不断变化,由以前的32位到64位,arm也在随之升级,一种二进制文件无法同时兼容32位和64设备,因此产生了通用(胖)二进制文件,可以兼容所有设备。之所以能够兼容是因为含有多个不同架构的独立二进制文件,执行时, 只会选择一种架构的二进制文件。不过也会带来一个弊端:体积较大,这也是为何又称之为胖二进制的原因。
下面我们通过MachOView看一个通用二进制文件,其中包含了ARM_V7(4s)和ARM64_ALL两种架构的可执行文件,如下图
操作系统根据当前设备的cputype 和 cpusubtype 去自动的选择一个最适合的去加载。虽然胖二进制文件在磁盘上可能会占据很大一部分空间,但是真正加载到内存的并不大。如下图:
Mach-O Binaries
Mach-O,是Mach object文件格式的缩写,是一种可执行文件、目标代码、共享程序库、动态加载代码和核心DUMP。类似于windows上的PE格式 (Portable Executable ), linux上的elf格式 (Executable and Linking Format)。
Mach-O格式的文件
mach-o/fat.h loader.h
#define MH_OBJECT
0x1
/* 目标文件*/
#define MH_EXECUTE
0x2
/* 可执行文件 */
#define MH_FVMLIB
0x3
/* fixed VM shared library file */
#define MH_CORE
0x4
/*核心转储文件 */
#define MH_PRELOAD
0x5
/* preloaded executable file */
#define MH_DYLIB
0x6
/* dynamically bound shared library */
#define MH_DYLINKER 0x7
/* dynamic link editor */
#define MH_BUNDLE
0x8
/* dynamically bound bundle file */
#define MH_DYLIB_STUB
0x9
/* shared library stub for static */
/*
linking only, no section contents */
#define MH_DSYM
0xa
/* companion file with only debug */
/*
sections */
#define MH_KEXT_BUNDLE
0xb
/* x86_64 kexts */
常见的
Mach-O格式的文件MH_OBJECT目标文件.o.a/ .framework静态库- 静态库即多个
.o文件存放在一起实现特定的功能
- 静态库即多个
MH_EXECUTE可执行文件.app/MyApp.out
MH_DYLIB动态库.framework/xxx/dylib
MH_DYLINKER动态链接器usr/lib/dyld
MH_DSYM存储二进制文件符号信息的文件.dYSM/Contents/Resources/DWARF/MyApp
Mach-O文件的基本结构
Mach-O包含三个主要区域
Header: 文件类型, 目标架构
Load command: 描述文件在虚拟内存中的逻辑与布局
Data:Load command中定义的原始数据
Mach-O结构详解
Mach Header(arm64)
Magic Number: 魔数, 表示支持设备的CPU位数
oxFEEDFACE: 表示32位二进制oxFEEDFACF: 表示64位二进制
cputype和cpusubtype:CPU类型和子类型filetype:Mach-O文件类型ncmds和sizeofcmds: 用于加载器的加载命令的条数和大小flags: 动态链接器dyld的标志- reserved: 保留字,64位专用,暂时没什么用
Load commands
Mach-O header 已经包含了非常详细的指令信息,可以直接告诉内核加载器如何设置、加载二进制文件 。Load commands 紧跟在 mach_header之后,其中一些指令可以直接被内核加载器理解,其他的则由动态链接器处理
命令 十六进制 作用 LC_SEGMENT
LC_SEGMENT_640x01/0x19 将这些段加载到对应的进程空间上去(区分32位和64位) LC_LOAD_DYLINKER 0x0E 加载dyld, 值得注意的是,每个Mach-O文件只能有一个段 LC_UUID 0x1B 将UUID这个值保存到执行进程的上下文中,同样每个Mach-O文件只能有一个段 LC_THREAD 0x04 开启一个Mach线程, 但是不分配栈(这个不常见) LC_UNIXTHREAD 0x05 开启一个UNIX线程,其实最主要的用途是告诉加载器当前主函数的位置.
这条命令在10.8之后被LC_MAIN取代。LC_MAIN 0x80000028 在10.8之后代替LC_UNIXTHREAD, 告诉加载器当前主函数的位置 LC_CODE_SIGNATURE 0x1D 这个是数字签名段 LC_ENCRYPTION_INFO 0x21 加密二进制文件, 貌似在IOS下使用的比较频繁。
常见段
__PAGEZERO: 空指针陷阱段
_TEXT: 程序代码段
__DATA: 程序数据段
__RODATA:read only程序只读数据段
__LINKEDIT: 链接器使用段Load commands 中最主要的命令就是LC_SEGMENT (or LC_SEGMENT64) commands ,它会告诉内核如何为进程设置内存空间。这些segments 可以直接从Mach-O文件中被加载到内存。
section段常见字段
Segment Name: 该Segment的名称, 用于load_segment
VM Address: 该段的虚拟物理地址
VM Size: 该段所需要分配的虚拟内存大小(字节)
File Offset: 该段在文件中的偏移量
File Size: 该段在文件中占据的字节数
Maximum VM Protection: 段的页面所需要的最高内存保护
ox1: x 执行
ox2: w 写
0x4: r 读
Initial VM Protection: 段页面初始化的内存保护
Number of Sections: 段中section区的数量
Flags: 其他标志位
tips: 小结:根据LC_SEGMENT命令 设置进程虚拟内存
对于每一个段, 将其内容从Mach-O文件加载到内存中
即从Mach-O文件中的偏移量为File Offset处加载File Size字节内容到虚拟内存地址VM Address处VM Size字节空间内
段中区section详解
- 常见区
section
__text: 主程序代码__stubs, __stub_helper: 用于动态链接的桩__cstring: 程序中c语言字符串__const: 常量__RODATA,__objc_methname:OC方法名称__RODATA,__objc_methntype:OC方法类型__RODATA,__objc_classname:OC类名__DATA,__objc_classlist:OC类列表__DATA,__objc_protollist:OC原型列表__DATA,__objc_imageinfo:OC镜像信息__DATA,__objc_const:OC常量__DATA,__objc_selfrefs:OC类自引用(self)__DATA,__objc_superrefs:OC类超类引用(super)__DATA,__objc_protolrefs:OC原型引用__DATA, __bss: 没有初始化和初始化为0 的全局变量
Load Commmands加载命令中其他信息
LC_MAIN- 设置程序主线程
入口地址和栈大小
- 设置程序主线程
LC_CODE_SIGNATURE包含
Mach-O文件的代码签名没有签名或签名不正确, 该进程会被kill, 程序崩溃
Mach-O中动态库的加载
动态库来源
系统提供的动态库
第三方动态库
如图:
DingTalk使用大量的系统动态库即
Mach-O镜像中有很多对外部库以及符号的引用这些引用将在程序启动时, 由
动态链接器 /usr/lib/dyld来执行符号绑定
加载动态链接器
LC_LOAD_DYLINKER: 内核执行该命令时, 启动dyld
获取符号表
LC_SYMTAB: 符号地址表LC_DYSYMTAB: 动态符号地址表
加载动态库
LC_LOAD_WEAK_DYLIBLC_LOAD_DYLIB
- 动态库加载流程小结
- 首先启动
dyld动态链接器; 内核根据LC_LOAD_DYLINKER启动/usr/lib/dyld- 如果
Mach-O文件中使用了外部定义的符号或函数, 则会在文本段__TEXT有__stubs, __stub_helper区; 区内放着本地未被定义的符号; 编译器在编译源码时会创建对这些未定义符号桩区的调用dyld运行时, 会在符号桩区调用地址上; 添加JMP 到 真实函数地址的指令- 至于
dyld怎么找到指定的动态库中指定的函数地址? 此时dyld将加载Load Command中的LC_LOAD_DYLIB命令LC_LOAD_DYLIB(动态库),dyld将加载每一个指定的库且搜寻匹配的符号- 当符号匹配时, 将在符号表(由
dyld加载LC_SYMTAB,LC_DYSYMTAB获取)查找对应的函数/符号地址
.main()之前的过程有哪些?
1、main之前的加载过程
1)dyld 开始将程序二进制文件初始化
2)交由ImageLoader 读取 image,其中包含了我们的类,方法等各种符号(Class、Protocol 、Selector、 IMP)
3)由于runtime 向dyld 绑定了回调,当image加载到内存后,dyld会通知runtime进行处理
4)runtime 接手后调用map_images做解析和处理
5)接下来load_images 中调用call_load_methods方法,遍历所有加载进来的Class,按继承层次依次调用Class的+load和其他Category的+load方法
6)至此 所有的信息都被加载到内存中
7)最后dyld调用真正的main函数
注意:dyld会缓存上一次把信息加载内存的缓存,所以第二次比第一次启动快一点
https://www.safaribooksonline.com/library/view/mac-os-x/9781118236055/9781118236055c04.xhtml
https://developer.apple.com/library/content/documentation/Performance/Conceptual/ManagingMemory/Articles/AboutMemory.html#//apple_ref/doc/uid/20001880-BCICIHAB
https://blog.csdn.net/dongaxis/article/details/41114071
http://blog.sina.com.cn/s/blog_185268e880102xpd7.html
http://www.ruanyifeng.com/blog/2014/11/compiler.html
https://www.jianshu.com/p/90f5ec723175
https://blog.csdn.net/yanglei3kyou/article/details/48825917
最后
以上就是痴情冷风最近收集整理的关于iOS 可执行文件- Universal (fat) binariesExecutables的全部内容,更多相关iOS内容请搜索靠谱客的其他文章。
发表评论 取消回复