概述
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_DYLIB
LC_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 可执行文件- Universal (fat) binariesExecutables所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复