當(dāng)我們設(shè)置了 Enable Bitcode=YES
立砸,進(jìn)行Archive時为朋,bitcode會被嵌入到鏈接后的Mach-O中,用于提交到App Store。從編譯日志中可以看出谈跛,Archive時多了一個編譯參數(shù) -fembed-bitcode
。
非Archive編譯時塑陵,Enable Bitcode 將會增加編譯參數(shù) -fembed-bitcode-marker
感憾, 該參數(shù)用于在Mach-O中做標(biāo)記,但是不會真正產(chǎn)生Bitcode令花。因為本地編譯調(diào)試時并不需要bitcode吹菱,去掉這個不必要的步驟加快編譯速度。
對于靜態(tài)庫等打開了Bitcode編譯彭则,通過MachOview查看會發(fā)現(xiàn)有一個__LLVM, __bitcode
段鳍刷;而全工程編譯出來對應(yīng)的是 __LLVM, __bundle
段;可以使用 segedit
命令將指定的Section導(dǎo)出:
segedit XXX.o -extract __LLVM __bitcode result.bc
如上圖俯抖,需要注意的是输瓜, __LLVM, __bundle
導(dǎo)出的并不是直接的bitcode格式,通過MachOView查看可得知是Xar文檔格式芬萍,xar格式包含了一個xml
格式的文件頭(TOC)尤揣,里面用于存放各種文件的基本屬性以及一些附加附加信息,可以通過xar命令查看并解壓
xar -d toc.xml -f bundle # 導(dǎo)出文件頭
查看xml的結(jié)構(gòu)柬祠,包括以下內(nèi)容:
- ld 的基本參數(shù)北戏,我們鏈接時使用的是clang,實際上clang內(nèi)部調(diào)用了ld漫蛔,這里記錄的是ld的參數(shù)
- version: bitcode bundle 的版本號
- architecture: 目標(biāo)架構(gòu)
- platform: 目標(biāo)平臺
- sdkversion: sdk版本
- dylibs: 鏈接的動態(tài)庫
- link-options: 其他鏈接參數(shù)
- 文件目錄
- checksum類型
- 創(chuàng)建時間
- 每個文件的信息
- 文件名嗜愈,這里并非原始文件名,而是按照鏈接時輸入的順序被重命名為數(shù)字序號
- 基本屬性莽龟,包括checksum蠕嫁、偏移、大小等
- 文件類型毯盈,一般是Bitcode剃毒,還有兩種特殊類型,Object以及Bundle搂赋,這里賣個關(guān)子赘阀,大家有興趣可已自行研究(想想如果一個源代碼文件是.s格式,要如何支持bitcode)
- 編譯器類型(clang/swift)及編譯參數(shù)脑奠,這部分就是object文件中
__LLVM,__cmdline
的內(nèi)容
- 下一個文件的信息(如有)
- 重復(fù)
如要提取bitcode基公,需要進(jìn)一步解壓:
xar -xf result.bc
通過otool檢查二進(jìn)制文件是否開啟了bitcode:
otool -arch armv7 -l xxxx.a | grep __LLVM | wc -l
通過判斷是否包含 __LLVM
來判斷是否支持bitcode,但是這種方式區(qū)分不了bitcode和bitcode-marker捺信,確定是否包含bitcode酌媒,還需要檢查otool輸出中__LLVM
Segment 的長度欠痴,如果長度只有1個字節(jié),則并不能代表真正開啟了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中所有非導(dǎo)出符號均被隱藏雨席,取而代之的是 __hidden#0_
或者 __ir_hidden#1_
這樣的形式菩咨,debug信息也只保留了line-table,所有跟文件路徑陡厘、標(biāo)識符抽米、導(dǎo)出符號等相關(guān)的信息全部都從bitcode中移除,相當(dāng)于做了一層混淆糙置,這種情況從IR代碼中打印出的方法名全為__hidden#XXX云茸,目前在嘗試從中拿到內(nèi)存地址再到符號表中去找映射關(guān)系。
符號表其實是DWARF的集合形式谤饭,它是內(nèi)存地址與函數(shù)名标捺,文件名,行號的映射表揉抵。DWARF
全名是 Debugging with Attribute Record Formats 亡容,是一種調(diào)試信息的存放格式。
DWARF 第一版發(fā)布于 1992 年冤今,主要是為 UNIX 下的調(diào)試器提供必要的調(diào)試信息闺兢,例如內(nèi)存地址對應(yīng)的文件名以及代碼行號等信息,通常用于源碼級別調(diào)試使用戏罢。另外通過 DWARF屋谭,還能還原運行時的地址成為可讀的源碼符號(及行號)。
DWARF 調(diào)試信息簡單的來說就是在機器碼和對應(yīng)的源代碼之間建立一座橋梁帖汞,大大提高了調(diào)試程序的能力戴而。
iOS 中引入 DWARF
這種調(diào)試信息格式,其實也是順應(yīng)歷史的潮流翩蘸,因為 DWARF
已經(jīng)在類 UNIX 系統(tǒng)中逐步替換 stabs
(symbol table strings),成為一種主流的調(diào)試信息格式淮逊。使用 GCC 或者 LLVM 系列編譯器都可以很方便的生成 DWARF 調(diào)試信息催首。
知道了Bitcode在Mach-O的位置以及獲取方法,再來看看Mach-O的其他內(nèi)容泄鹏,Mach-O
其實是 Mach Object
文件格式的縮寫郎任。
屬于 Mach-O
格式的常見文件
- 目標(biāo)文件 .o
- 庫文件
- .a
- .dylib
- Framework
- 可執(zhí)行文件
- dyld ( 動態(tài)鏈接器 )
- .dsym ( 符號表 )
使用 file
命令可以查看文件類型
一張網(wǎng)上到處都有的結(jié)構(gòu)圖:
1. Header
描述了 Mach-O 的 CPU 架構(gòu)、文件類型以及加載命令等信息备籽。
可進(jìn)入 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; /* 魔數(shù) 0xfeedface舶治、0xcafebabe分井、0xfeedfacf 分別對應(yīng)64、fat霉猛、64*/
//如0xfeedface表示32位二進(jìn)制格式尺锚,0xfeedfacf表示64位;
cpu_type_t cputype; /* cpu 類型 比如 ARM */
cpu_subtype_t cpusubtype; /* cpu 具體類型 比如arm64 , armv7 */
uint32_t filetype; /* 文件類型 例如可執(zhí)行文件 .. 具體見下面枚舉 */
uint32_t ncmds; /* load commands 加載命令條數(shù) */
uint32_t sizeofcmds; /* load commands 加載命令大小*/
uint32_t flags; /* 標(biāo)志位標(biāo)識二進(jìn)制文件支持的功能 , 主要是和系統(tǒng)加載惜浅、鏈接有關(guān) 具體見下面枚舉*/
uint32_t reserved; /* reserved , 保留字段 */
};
filetype:
#define MH_OBJECT 0x1 /* Target 文件:編譯器對源碼編譯后得到的中間結(jié)果 */
#define MH_EXECUTE 0x2 /* 可執(zhí)行二進(jìn)制文件 */
#define MH_FVMLIB 0x3 /* VM 共享庫文件 */
#define MH_CORE 0x4 /* Core 文件瘫辩,一般在 App Crash 產(chǎn)生 */
#define MH_PRELOAD 0x5 /* preloaded executable file */
#define MH_DYLIB 0x6 /* 動態(tài)庫 */
#define MH_DYLINKER 0x7 /* 動態(tài)連接器 /usr/lib/dyld */
#define MH_BUNDLE 0x8 /* 非獨立的二進(jìn)制文件,往往通過 gcc-bundle 生成 */
#define MH_DYLIB_STUB 0x9 /* 靜態(tài)鏈接文件 */
#define MH_DSYM 0xa /* 符號文件以及調(diào)試信息坛悉,在解析堆棧符號中常用 */
#define MH_KEXT_BUNDLE 0xb /* x86_64 內(nèi)核擴(kuò)展 */
flags:
#define MH_NOUNDEFS 0x1 /* Target 文件中沒有帶未定義的符號伐厌,常為靜態(tài)二進(jìn)制文件 */
#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 /* 二進(jìn)制文件使用了弱符號 */
#define MH_BINDS_TO_WEAK 0x10000 /* 二進(jìn)制文件鏈接了弱符號 */
#define MH_ALLOW_STACK_EXECUTION 0x20000/* 允許 Stack 可執(zhí)行 */
#define MH_PIE 0x200000 /* 對可執(zhí)行的文件類型啟用地址空間 layout 隨機化 */
#define MH_NO_HEAP_EXECUTION 0x1000000 /* 將 Heap 標(biāo)記為不可執(zhí)行,可防止 heap spray 攻擊 */
2. Load Commands
描述了文件中數(shù)據(jù)的具體組織結(jié)構(gòu)裸影,不同的數(shù)據(jù)類型使用不同的加載命令表示挣轨。
Load Commands
詳細(xì)保存著加載指令的內(nèi)容 , 告訴鏈接器如何去加載這個 Mach-O
文件.
通過查看內(nèi)存地址我們發(fā)現(xiàn) , 在內(nèi)存中 , Load Commands
是緊跟在 Mach_header
之后的 .
名稱 | 含義 |
---|---|
LC_SEGMENT_64 | 將文件中(32位或64位)的段映射到進(jìn)程地址空間中 |
LC_DYLD_INFO_ONLY | 動態(tài)鏈接相關(guān)信息 |
LC_SYMTAB | 符號地址 |
LC_DYSYMTAB | 動態(tài)符號表地址 |
LC_LOAD_DYLINKER | 加載方式,dyld |
LC_UUID | 文件的UUID |
LC_VERSION_MIN_MACOSX | 支持的最低操作系統(tǒng)版本 |
LC_SOURCE_VERSION | 源代碼版本 |
LC_MAIN | 設(shè)置程序主線程的入口地址和棧大小 |
LC_LOAD_DYLIB | 依賴庫的路徑轩猩,包含三方庫 |
LC_FUNCTION_STARTS | 函數(shù)起始地址表 |
LC_CODE_SIGNATURE | 代碼簽名 |
3. Data
每一個段(Segment)的數(shù)據(jù)都保存在其中刃唐,段的概念和 ELF 文件中段的概念類似,都擁有一個或多個 Section 界轩,用來存放數(shù)據(jù)和代碼画饥。
Segment:
#define SEG_PAGEZERO "__PAGEZERO" /* 當(dāng)時 MH_EXECUTE 文件時,捕獲到空指針 */
#define SEG_TEXT "__TEXT" /* 代碼/只讀數(shù)據(jù)段 */
#define SEG_DATA "__DATA" /* 數(shù)據(jù)段 */
#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" /* 包含需要被動態(tài)鏈接器使用的符號和其他表浊猾,包括符號表抖甘、字符串表等 */
Segment數(shù)據(jù)結(jié)構(gòu):
struct segment_command_64 {
uint32_t cmd; /* LC_SEGMENT_64 */
uint32_t cmdsize; /* section_64 結(jié)構(gòu)體所需要的空間 */
char segname[16]; /* segment 名字,上述宏中的定義 */
uint64_t vmaddr; /* 所描述段的虛擬內(nèi)存地址 */
uint64_t vmsize; /* 為當(dāng)前段分配的虛擬內(nèi)存大小 */
uint64_t fileoff; /* 當(dāng)前段在文件中的偏移量 */
uint64_t filesize; /* 當(dāng)前段在文件中占用的字節(jié) */
vm_prot_t maxprot; /* 段所在頁所需要的最高內(nèi)存保護(hù)葫慎,用八進(jìn)制表示 */
vm_prot_t initprot; /* 段所在頁原始內(nèi)存保護(hù) */
uint32_t nsects; /* 段中 Section 數(shù)量 */
uint32_t flags; /* 標(biāo)識符 */
};
Section數(shù)據(jù)結(jié)構(gòu):
struct section_64 {
char sectname[16]; /* Section 名字 */
char segname[16]; /* Section 所在的 Segment 名稱 */
uint64_t addr; /* Section 所在的內(nèi)存地址 */
uint64_t size; /* Section 的大小 */
uint32_t offset; /* Section 所在的文件偏移 */
uint32_t align; /* Section 的內(nèi)存對齊邊界 (2 的次冪) */
uint32_t reloff; /* 重定位信息的文件偏移 */
uint32_t nreloc; /* 重定位條目的數(shù)目 */
uint32_t flags; /* 標(biāo)志屬性 */
uint32_t reserved1; /* 保留字段1 (for offset or index) */
uint32_t reserved2; /* 保留字段2 (for count or sizeof) */
uint32_t reserved3; /* 保留字段3 */
};
部分的 Segment (主要指的 __TEXT
和 __DATA
)可以進(jìn)一步分解為 Section衔彻。之所以按照 Segment -> Section 的結(jié)構(gòu)組織方式,是因為在同一個 Segment 下的 Section偷办,可以控制相同的權(quán)限艰额,也可以不完全按照 Page 的大小進(jìn)行內(nèi)存對其,節(jié)省內(nèi)存的空間椒涯。而 Segment 對外整體暴露柄沮,在程序載入階段映射成一個完整的虛擬內(nèi)存,更好的做到內(nèi)存對齊废岂。
下面列舉一些常見的 Section祖搓。
Section | 用途 |
---|---|
__TEXT.__text |
主程序代碼 |
__TEXT.__cstring |
C 語言字符串 |
__TEXT.__const |
const 關(guān)鍵字修飾的常量 |
__TEXT.__stubs |
用于 Stub 的占位代碼,很多地方稱之為樁代碼湖苞。 |
__TEXT.__stubs_helper |
當(dāng) Stub 無法找到真正的符號地址后的最終指向 |
__TEXT.__objc_methname |
Objective-C 方法名稱 |
__TEXT.__objc_methtype |
Objective-C 方法類型 |
__TEXT.__objc_classname |
Objective-C 類名稱 |
__DATA.__data |
初始化過的可變數(shù)據(jù) |
__DATA.__la_symbol_ptr |
lazy binding 的指針表拯欧,表中的指針一開始都指向 __stub_helper
|
__DATA.nl_symbol_ptr |
非 lazy binding 的指針表,每個表項中的指針都指向一個在裝載過程中财骨,被動態(tài)鏈機器搜索完成的符號 |
__DATA.__const |
沒有初始化過的常量 |
__DATA.__cfstring |
程序中使用的 Core Foundation 字符串(CFStringRefs ) |
__DATA.__bss |
BSS镐作,存放為初始化的全局變量藏姐,即常說的靜態(tài)內(nèi)存分配 |
__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 超類引用 |