当我们设置了 Enable Bitcode=YES
,进行Archive时,bitcode会被嵌入到链接后的Mach-O中,用于提交到App Store。从编译日志中可以看出,Archive时多了一个编译参数 -fembed-bitcode
。
非Archive编译时,Enable Bitcode 将会增加编译参数 -fembed-bitcode-marker
, 该参数用于在Mach-O中做标记,但是不会真正产生Bitcode。因为本地编译调试时并不需要bitcode,去掉这个不必要的步骤加快编译速度。
对于静态库等打开了Bitcode编译,通过MachOview查看会发现有一个__LLVM, __bitcode
段;而全工程编译出来对应的是 __LLVM, __bundle
段;可以使用 segedit
命令将指定的Section导出:
segedit XXX.o -extract __LLVM __bitcode result.bc
如上图,需要注意的是, __LLVM, __bundle
导出的并不是直接的bitcode格式,通过MachOView查看可得知是Xar文档格式,xar格式包含了一个xml
格式的文件头(TOC),里面用于存放各种文件的基本属性以及一些附加附加信息,可以通过xar命令查看并解压
xar -d toc.xml -f bundle # 导出文件头
查看xml的结构,包括以下内容:
- ld 的基本参数,我们链接时使用的是clang,实际上clang内部调用了ld,这里记录的是ld的参数
- version: bitcode bundle 的版本号
- architecture: 目标架构
- platform: 目标平台
- sdkversion: sdk版本
- dylibs: 链接的动态库
- link-options: 其他链接参数
- 文件目录
- checksum类型
- 创建时间
- 每个文件的信息
- 文件名,这里并非原始文件名,而是按照链接时输入的顺序被重命名为数字序号
- 基本属性,包括checksum、偏移、大小等
- 文件类型,一般是Bitcode,还有两种特殊类型,Object以及Bundle,这里卖个关子,大家有兴趣可已自行研究(想想如果一个源代码文件是.s格式,要如何支持bitcode)
- 编译器类型(clang/swift)及编译参数,这部分就是object文件中
__LLVM,__cmdline
的内容
- 下一个文件的信息(如有)
- 重复
如要提取bitcode,需要进一步解压:
xar -xf result.bc
通过otool检查二进制文件是否开启了bitcode:
otool -arch armv7 -l xxxx.a | grep __LLVM | wc -l
通过判断是否包含 __LLVM
来判断是否支持bitcode,但是这种方式区分不了bitcode和bitcode-marker,确定是否包含bitcode,还需要检查otool输出中__LLVM
Segment 的长度,如果长度只有1个字节,则并不能代表真正开启了bitcode:
$ otool -l test_bitcode.o | grep -A 2 __LLVM | grep size
size 0x0000000000000b10
size 0x0000000000000042
$ otool -l test_bitcode_marker.o | grep -A 2 __LLVM | grep size
size 0x0000000000000001
size 0x0000000000000001
另外,Archive版本引入了 Symbol Hiding
和 Debug info Striping
机制,在链接时,bitcode中所有非导出符号均被隐藏,取而代之的是 __hidden#0_
或者 __ir_hidden#1_
这样的形式,debug信息也只保留了line-table,所有跟文件路径、标识符、导出符号等相关的信息全部都从bitcode中移除,相当于做了一层混淆,这种情况从IR代码中打印出的方法名全为__hidden#XXX,目前在尝试从中拿到内存地址再到符号表中去找映射关系。
符号表其实是DWARF的集合形式,它是内存地址与函数名,文件名,行号的映射表。DWARF
全名是 Debugging with Attribute Record Formats ,是一种调试信息的存放格式。
DWARF 第一版发布于 1992 年,主要是为 UNIX 下的调试器提供必要的调试信息,例如内存地址对应的文件名以及代码行号等信息,通常用于源码级别调试使用。另外通过 DWARF,还能还原运行时的地址成为可读的源码符号(及行号)。
DWARF 调试信息简单的来说就是在机器码和对应的源代码之间建立一座桥梁,大大提高了调试程序的能力。
iOS 中引入 DWARF
这种调试信息格式,其实也是顺应历史的潮流,因为 DWARF
已经在类 UNIX 系统中逐步替换 stabs
(symbol table strings),成为一种主流的调试信息格式。使用 GCC 或者 LLVM 系列编译器都可以很方便的生成 DWARF 调试信息。
知道了Bitcode在Mach-O的位置以及获取方法,再来看看Mach-O的其他内容,Mach-O
其实是 Mach Object
文件格式的缩写。
属于 Mach-O
格式的常见文件
- 目标文件 .o
- 库文件
- .a
- .dylib
- Framework
- 可执行文件
- dyld ( 动态链接器 )
- .dsym ( 符号表 )
使用 file
命令可以查看文件类型
一张网上到处都有的结构图:
1. Header
描述了 Mach-O 的 CPU 架构、文件类型以及加载命令等信息。
可进入 Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/mach-o/loader.h
查看头文件
struct mach_header_64 {
uint32_t magic; /* 魔数 0xfeedface、0xcafebabe、0xfeedfacf 分别对应64、fat、64*/
//如0xfeedface表示32位二进制格式,0xfeedfacf表示64位;
cpu_type_t cputype; /* cpu 类型 比如 ARM */
cpu_subtype_t cpusubtype; /* cpu 具体类型 比如arm64 , armv7 */
uint32_t filetype; /* 文件类型 例如可执行文件 .. 具体见下面枚举 */
uint32_t ncmds; /* load commands 加载命令条数 */
uint32_t sizeofcmds; /* load commands 加载命令大小*/
uint32_t flags; /* 标志位标识二进制文件支持的功能 , 主要是和系统加载、链接有关 具体见下面枚举*/
uint32_t reserved; /* reserved , 保留字段 */
};
filetype:
#define MH_OBJECT 0x1 /* Target 文件:编译器对源码编译后得到的中间结果 */
#define MH_EXECUTE 0x2 /* 可执行二进制文件 */
#define MH_FVMLIB 0x3 /* VM 共享库文件 */
#define MH_CORE 0x4 /* Core 文件,一般在 App Crash 产生 */
#define MH_PRELOAD 0x5 /* preloaded executable file */
#define MH_DYLIB 0x6 /* 动态库 */
#define MH_DYLINKER 0x7 /* 动态连接器 /usr/lib/dyld */
#define MH_BUNDLE 0x8 /* 非独立的二进制文件,往往通过 gcc-bundle 生成 */
#define MH_DYLIB_STUB 0x9 /* 静态链接文件 */
#define MH_DSYM 0xa /* 符号文件以及调试信息,在解析堆栈符号中常用 */
#define MH_KEXT_BUNDLE 0xb /* x86_64 内核扩展 */
flags:
#define MH_NOUNDEFS 0x1 /* Target 文件中没有带未定义的符号,常为静态二进制文件 */
#define MH_INCRLINK 0x2 /* the object file is the output of an
incremental link against a base file
and can't be link edited again */
#define MH_DYLDLINK 0x4 /* the object file is input for the
dynamic linker and can't be staticly
link edited again */
#define MH_BINDATLOAD 0x8 /* the object file's undefined
references are bound by the dynamic
linker when loaded. */
#define MH_PREBOUND 0x10 /* the file has its dynamic undefined
references prebound. */
#define MH_SPLIT_SEGS 0x20 /* Target 文件中的只读 Segment 和可读写 Segment 分开 */
#define MH_TWOLEVEL 0x80 /* 该 Image 使用二级命名空间(two name space binding)绑定方案 */
#define MH_FORCE_FLAT 0x100 /* 使用扁平命名空间(flat name space binding)绑定(与 MH_TWOLEVEL 互斥) */
#define MH_WEAK_DEFINES 0x8000 /* 二进制文件使用了弱符号 */
#define MH_BINDS_TO_WEAK 0x10000 /* 二进制文件链接了弱符号 */
#define MH_ALLOW_STACK_EXECUTION 0x20000/* 允许 Stack 可执行 */
#define MH_PIE 0x200000 /* 对可执行的文件类型启用地址空间 layout 随机化 */
#define MH_NO_HEAP_EXECUTION 0x1000000 /* 将 Heap 标记为不可执行,可防止 heap spray 攻击 */
2. Load Commands
描述了文件中数据的具体组织结构,不同的数据类型使用不同的加载命令表示。
Load Commands
详细保存着加载指令的内容 , 告诉链接器如何去加载这个 Mach-O
文件.
通过查看内存地址我们发现 , 在内存中 , Load Commands
是紧跟在 Mach_header
之后的 .
名称 | 含义 |
---|---|
LC_SEGMENT_64 | 将文件中(32位或64位)的段映射到进程地址空间中 |
LC_DYLD_INFO_ONLY | 动态链接相关信息 |
LC_SYMTAB | 符号地址 |
LC_DYSYMTAB | 动态符号表地址 |
LC_LOAD_DYLINKER | 加载方式,dyld |
LC_UUID | 文件的UUID |
LC_VERSION_MIN_MACOSX | 支持的最低操作系统版本 |
LC_SOURCE_VERSION | 源代码版本 |
LC_MAIN | 设置程序主线程的入口地址和栈大小 |
LC_LOAD_DYLIB | 依赖库的路径,包含三方库 |
LC_FUNCTION_STARTS | 函数起始地址表 |
LC_CODE_SIGNATURE | 代码签名 |
3. Data
每一个段(Segment)的数据都保存在其中,段的概念和 ELF 文件中段的概念类似,都拥有一个或多个 Section ,用来存放数据和代码。
Segment:
#define SEG_PAGEZERO "__PAGEZERO" /* 当时 MH_EXECUTE 文件时,捕获到空指针 */
#define SEG_TEXT "__TEXT" /* 代码/只读数据段 */
#define SEG_DATA "__DATA" /* 数据段 */
#define SECT_DATA "__data" /* the real initialized data section */
/* no padding, no bss overlap */
#define SECT_BSS "__bss" /* the real uninitialized data section*/
/* no padding */
#define SECT_COMMON "__common" /* the section common symbols are */
/* allocated in by the link editor */
#define SEG_OBJC "__OBJC" /* Objective-C runtime 段 */
#define SECT_OBJC_SYMBOLS "__symbol_table" /* symbol table */
#define SECT_OBJC_MODULES "__module_info" /* module information */
#define SECT_OBJC_STRINGS "__selector_strs" /* string table */
#define SECT_OBJC_REFS "__selector_refs" /* string table */
#define SEG_ICON "__ICON" /* the icon segment */
#define SECT_ICON_HEADER "__header" /* the icon headers */
#define SECT_ICON_TIFF "__tiff" /* the icons in tiff format */
#define SEG_LINKEDIT "__LINKEDIT" /* 包含需要被动态链接器使用的符号和其他表,包括符号表、字符串表等 */
Segment数据结构:
struct segment_command_64 {
uint32_t cmd; /* LC_SEGMENT_64 */
uint32_t cmdsize; /* section_64 结构体所需要的空间 */
char segname[16]; /* segment 名字,上述宏中的定义 */
uint64_t vmaddr; /* 所描述段的虚拟内存地址 */
uint64_t vmsize; /* 为当前段分配的虚拟内存大小 */
uint64_t fileoff; /* 当前段在文件中的偏移量 */
uint64_t filesize; /* 当前段在文件中占用的字节 */
vm_prot_t maxprot; /* 段所在页所需要的最高内存保护,用八进制表示 */
vm_prot_t initprot; /* 段所在页原始内存保护 */
uint32_t nsects; /* 段中 Section 数量 */
uint32_t flags; /* 标识符 */
};
Section数据结构:
struct section_64 {
char sectname[16]; /* Section 名字 */
char segname[16]; /* Section 所在的 Segment 名称 */
uint64_t addr; /* Section 所在的内存地址 */
uint64_t size; /* Section 的大小 */
uint32_t offset; /* Section 所在的文件偏移 */
uint32_t align; /* Section 的内存对齐边界 (2 的次幂) */
uint32_t reloff; /* 重定位信息的文件偏移 */
uint32_t nreloc; /* 重定位条目的数目 */
uint32_t flags; /* 标志属性 */
uint32_t reserved1; /* 保留字段1 (for offset or index) */
uint32_t reserved2; /* 保留字段2 (for count or sizeof) */
uint32_t reserved3; /* 保留字段3 */
};
部分的 Segment (主要指的 __TEXT
和 __DATA
)可以进一步分解为 Section。之所以按照 Segment -> Section 的结构组织方式,是因为在同一个 Segment 下的 Section,可以控制相同的权限,也可以不完全按照 Page 的大小进行内存对其,节省内存的空间。而 Segment 对外整体暴露,在程序载入阶段映射成一个完整的虚拟内存,更好的做到内存对齐。
下面列举一些常见的 Section。
Section | 用途 |
---|---|
__TEXT.__text |
主程序代码 |
__TEXT.__cstring |
C 语言字符串 |
__TEXT.__const |
const 关键字修饰的常量 |
__TEXT.__stubs |
用于 Stub 的占位代码,很多地方称之为桩代码。 |
__TEXT.__stubs_helper |
当 Stub 无法找到真正的符号地址后的最终指向 |
__TEXT.__objc_methname |
Objective-C 方法名称 |
__TEXT.__objc_methtype |
Objective-C 方法类型 |
__TEXT.__objc_classname |
Objective-C 类名称 |
__DATA.__data |
初始化过的可变数据 |
__DATA.__la_symbol_ptr |
lazy binding 的指针表,表中的指针一开始都指向 __stub_helper |
__DATA.nl_symbol_ptr |
非 lazy binding 的指针表,每个表项中的指针都指向一个在装载过程中,被动态链机器搜索完成的符号 |
__DATA.__const |
没有初始化过的常量 |
__DATA.__cfstring |
程序中使用的 Core Foundation 字符串(CFStringRefs ) |
__DATA.__bss |
BSS,存放为初始化的全局变量,即常说的静态内存分配 |
__DATA.__common |
没有初始化过的符号声明 |
__DATA.__objc_classlist |
Objective-C 类列表 |
__DATA.__objc_protolist |
Objective-C 原型 |
__DATA.__objc_imginfo |
Objective-C 镜像信息 |
__DATA.__objc_selfrefs |
Objective-C self 引用 |
__DATA.__objc_protorefs |
Objective-C 原型引用 |
__DATA.__objc_superrefs |
Objective-C 超类引用 |
ASLR
Bitcode的工作流程及安全性评估