离久的小站

Mach-O 文件格式

2019/04/21 Share

Mach-O是一种文件的格式; 是iOS/Mac OS上存储程序以及库的标准格式

类型

1
2
3
4
5
6
7
8
9
10
11
12
13
#define MH_OBJECT   0x1     /* 目标文件 例如:.o 、.a 、.framework(静态库) */
#define MH_EXECUTE 0x2 /* 可执行文件 例如:.app 、.out */
#define MH_FVMLIB 0x3 /* VM共享库文件 */
#define MH_CORE 0x4 /*核心转储文件 */
#define MH_PRELOAD 0x5 /* 预加载的可执行文件 */
#define MH_DYLIB 0x6 /* 动态库 .dylib */
#define MH_DYLINKER 0x7 /* 动态链接器 usr/lib/dyld */
#define MH_BUNDLE 0x8 /* 动态绑定的bundle文件 */
#define MH_DYLIB_STUB 0x9 /* shared library stub for static */
/* linking only, no section contents */
#define MH_DSYM 0xa /* 存储二进制文件符号信息的文件 */
/* sections */
#define MH_KEXT_BUNDLE 0xb /* x86_64 kexts */

基本结构

Mach-O 包含三个主要区域

  1. Header: 文件类型, 目标架构

  2. Load command: 描述文件在虚拟内存中的逻辑与布局

  3. Raw segment date : Load command中定义的原始数据

可以从我的仓库下载 Mach-O View 来查看文件的结构

使用 Mach-O View 来打开可执行文件

由于上图的可执行文件只适配了ARM64,一般通用可以执行文件如图下

含有多个不同架构的独立二进制文件; 故体积较大

执行时, 只会选择一种架构的二进制文件

Mach Header

  1. Magic Number 表示支持设备的CPU位数

    • oxFEEDFACE : 表示32位二进制
    • oxFEEDFACF : 表示64位二进制
  2. cputype和 cpusubtype: CPU类型和子类型

  3. filetype : Mach-O文件类型
  4. ncmds 和 sizeofcmds: 用于加载器的 加载命令的条数和大小
  5. flags : 动态链接器dyld的标志

SEGMENT(LC_SEGMENT / LC_SEGMENT_64)

  1. __PAGEZERO: 空指针陷阱段
  2. _TEXT: 程序代码段
  3. __DATA: 程序数据段
  4. __RODATA: read only程序只读数据段
  5. __LINKEDIT: 链接器使用段

    Section

    1. Segment Name: 该Segment的名称, 用于load_segment
    2. VM Address: 该段的虚拟物理地址
    3. VM Size: 该段所需要分配的虚拟内存大小(字节)
    4. File Offset: 该段在文件中的偏移量
    5. File Size: 该段在文件中占据的字节数
    6. Maximum VM Protection: 段的页面所需要的最高内存保护
      • ox1: x 执行
      • ox2: w 写
      • 0x4: r 读
    7. Initial VM Protection: 段页面初始化的内存保护
    8. Number of Sections: 段中section区的数量
    9. Flags: 其他标志位
1
2
3
4
5
根据LC_SEGMENT命令 设置进程虚拟内存

对于每一个段, 将其内容从Mach-O文件加载到内存中

即从Mach-O文件中的偏移量为 File Offset处加载File Size字节内容到虚拟内存地址VM Address处VM Size字节空间内

常见区section

  1. __text: 主程序代码
  2. __stubs, __stub_helper: 用于动态链接的桩
  3. __cstring: 程序中c语言字符串
  4. __const: 常量
  5. __RODATA, __objc_methname: OC方法名称
  6. __RODATA, __objc_methntype: OC方法类型
  7. __RODATA, __objc_classname: OC类名
  8. __DATA, __objc_classlist: OC类列表
  9. __DATA, __objc_protollist: OC原型列表
  10. __DATA, __objc_imageinfo: OC镜像信息
  11. __DATA, __objc_const: OC常量
  12. __DATA, __objc_selfrefs: OC类自引用(self)
  13. __DATA, __objc_superrefs: OC类超类引用(super)
  14. __DATA, __objc_protolrefs: OC原型引用
  15. __DATA, __bss: 没有初始化和初始化为0 的全局变量

关于 LoadCommand

LC_MAIN

设置程序主线程入口地址 和 栈大小

LC_CODE_SIGNATURE

  1. 包含Mach-O文件的代码签名
  2. 没有签名 或 签名不正确, 该进程会被kill, 程序崩溃

关于动态库

即Mach-O镜像中有很多对外部库以及符号的引用

这些引用将在程序启动时, 由动态链接器 /usr/lib/dyld来执行符号绑定

  • 加载动态链接器

    LC_LOAD_DYLINKER: 内核执行该命令时, 启动dyld

  • 符号表

    LC_SYMTAB: 符号地址表

    LC_DYSYMTAB: 动态符号地址表

  • 加载动态库

    LC_LOAD_WEAK_DYLIB

    LC_LOAD_DYLIB

1
2
3
4
5
6
7
8
9
10
11
12
13
动态库加载流程小结:

1. 首先启动dyld动态链接器; 内核根据LC_LOAD_DYLINKER启动/usr/lib/dyld

2. 如果Mach-O文件中使用了外部定义的符号或函数, 则会在文本段\_\_TEXT有\_\_stubs,\_\_stub_helper区; 区内放着本地未被定义的符号; 编译器在编译源码时会创建对这些未定义符号桩区的调用

3. dyld运行时, 会在符号桩区调用地址上; 添加JMP 到 真实函数地址的指令

4. 此时dyld将加载Load Command中的LC_LOAD_DYLIB命令

5. LC_LOAD_DYLIB(动态库), dyld将加载每一个指定的库且搜寻匹配的符号

6. 当符号匹配时, 将在符号表(由dyld加载LC_SYMTAB, LC_DYSYMTAB获取)查找对应的函数/符号地址

关于 符号表 、动态符号表 和 字符串表

  • Symbol Table: 符号表
  • Dynamic Symbol Table: 动态符号表
  • String Table: 字符串表
CATALOG
  1. 1. 类型
  2. 2. 基本结构
    1. 2.1. Mach Header
    2. 2.2. SEGMENT(LC_SEGMENT / LC_SEGMENT_64)
      1. 2.2.1. Section
    3. 2.3. 常见区section
  3. 3. 关于 LoadCommand
    1. 3.1. LC_MAIN
    2. 3.2. LC_CODE_SIGNATURE
    3. 3.3. 关于动态库
    4. 3.4. 关于 符号表 、动态符号表 和 字符串表