背景描述:
在cocpoads管理下自研庫和第三方的開源庫可以使用frameWork的方式集成到主工程中打毛,在我們編譯過程中柿赊,只要不改動pod下集成的庫代碼,第一次編譯后幻枉,第三方庫會編譯成frameWork碰声,通過Xcode 的 buildSetting 的 framework search Path 記錄所有生成的framework地址,在項目的編譯后期去link這些framework 熬甫。在沒有修改pod子工程的庫文件前提下胰挑,之后的每一次build都直接link緩存中編譯過的framework文件。但是當文件編譯成framework椿肩,成為了可執(zhí)行的二進制文件瞻颂,我們卻任然可以借助Xcode做源碼調(diào)試。那么Xcode工具是如何通過一個二進制文件執(zhí)行時找到它對應的源碼文件的郑象?
鑒于上個問題贡这,我們可以先看看Xcode的編譯過程
Xcode作為一個GUI工具,實際上是通過調(diào)用一系列的命令行工具厂榛,將命令行工具處理的結(jié)果匯總輸出盖矫。Xcode 使用clang編譯器進行編譯 會使用一系列 xcrun clang 的命令來編譯文件,xcrun是用來定位clang工具位置的
接著看編譯過程中都經(jīng)歷了些什么击奶?
大致過程如下:
1.文件預處理
符號化 (Tokenization)
宏定義的展開
include 的展開
2.語法語義分析
將符號化后的內(nèi)容轉(zhuǎn)化為一棵解析樹 (parse tree)
解析樹做語義分析
輸出一棵抽象語法樹(Abstract Syntax Tree* (AST))
3.代碼生成和優(yōu)化
將 AST 轉(zhuǎn)換為更低級的中間碼 (LLVM IR)
對生成的中間碼做優(yōu)化
生成特定目標代碼
輸出匯編代碼
4.匯編器
將匯編代碼轉(zhuǎn)換為目標對象文件辈双。
5.link文件
將多個目標對象文件合并為一個可執(zhí)行文件 (或者一個動態(tài)庫)
整個過程比較復雜,特別是語義分析柜砾,和代碼優(yōu)化過程湃望,可以先簡單的看一下與處理過程。clang 有很多命令,可以通過
`$xcrun clang —help `
查看提示信息
其中關(guān)于預編譯的:
-c Only run preprocess, compile, and assemble steps
-E Only run the preprocessor
我嘗試使用上面的命令預編譯了main.c文件
main.c文件:
#include <stdio.h>
int main(int argc, char *argv[])
{
printf("Hello World!\n");
return 0;
}
在main.c當前目錄下執(zhí)行
xcrun clang -E main.c | open -f
Open -f 是將預編譯結(jié)果在文件中打開证芭,預編譯部分結(jié)果截圖如下:
1.png
預編譯過程因為要展開所有的宏定義瞳浦,inclue , import。 輸出的文件中行前面的 “#”號 (hash) 接著的數(shù)字 代表的插入的源文件中的行號檩帐, 后面的文件路徑代表的是需要引入的文件的路徑 后面的數(shù)字代表的是引入的內(nèi)容在新生成的文件所在的行數(shù)术幔,hash下面跟的內(nèi)容是指定文件 指定行數(shù) 所引入的內(nèi)容,例如第二張圖中
將 /Application/Xcode.app/Contents/Developer/platforms/MacOSX10.12.sdk/usr/include/machine/_types.h 文件中的第33行湃密,34行诅挑,55行的代碼段
插入新生成的文件中。
Xcode也支持查看文件的預編譯結(jié)果 泛源,選中文件點擊product—>perform Action —>preprocess,之后便能看到預編譯的結(jié)果拔妥,可以對比所引入的文件所在行數(shù)和引入的內(nèi)容是否對應。
Xcode基于預編譯的結(jié)果去構(gòu)建語法樹达箍,生成中間字節(jié)碼没龙,下面是hello world的LLVM中間字節(jié)碼
可以簡單了解一下上面llvm中間碼的意思,@符號是LLVM中間字節(jié)碼(IR)的變量前綴缎玫,@代表全局變量標志硬纤,%代表局部變量標志。@.str是全局變量名赃磨,%cast210 是局部變量的名稱 筝家。i8, i32,i64代表的存儲字節(jié)類型邻辉,char是i8溪王,一個字節(jié),int是i32值骇,4個字節(jié)莹菱。@main,@put吱瘩,@puts是方法名道伟,在IR中function和全局變量統(tǒng)稱全局值(global values)都是@前綴標志。
根據(jù)中間字節(jié)碼(LLVM的字節(jié)碼)做代碼優(yōu)化使碾,優(yōu)化后輸出匯編代碼皱卓。
可以使用下面命令來查看生成的匯編代碼:
xcrun clang -S -o - main.c | open -f
結(jié)果如下圖:
按照上面說的步驟,就進入了調(diào)用匯編器編譯匯編代碼了部逮,匯編器將匯編代碼轉(zhuǎn)換成機器碼,稱為目標對象文件嫂易,在MacOSX兄朋, ios下為 Mach-O文件格式下圖是蘋果官方文檔對mach-o文件格式的介紹:
Mach-O 的組成結(jié)構(gòu)如下圖所示包括了Header、Load commands、Data(包含Segement的具體數(shù)據(jù))
我們可以在工程的編譯路徑下找到編譯后生成的可執(zhí)行文件APP颅和,我本地路徑為~/Library/Developer/Xcode/DerivedData/App-ackwqnrbjrdvslercxzskjictqru/Build/Intermediates/App.build/Release-iphonesimulator/App.build/Objects-normal/i386
在此路徑下使用size工具查看文件結(jié)構(gòu)
xcrun size -x -l -m App
輸出
剛好與蘋果給出的結(jié)構(gòu)圖相匹配,可以暫時先簡單的了解下傅事,__TEXT segment 包含了被執(zhí)行的代碼,它被以只讀和可執(zhí)行的方式映射峡扩。__DATA segment 中包含了可讀寫數(shù)據(jù)蹭越, 以可讀寫和不可執(zhí)行的方式映射,它包含了將會被更改的數(shù)據(jù)教届。__LINKEDIT segment 包含了動態(tài)鏈接器的原始數(shù)據(jù)响鹃,如符號,字符串和重定位的表的入口.
最后進入連接器連接的階段
鏈接器解決了目標文件和庫之間的鏈接案训,鏈接器需要將所需的lib函數(shù)(可以理解為系統(tǒng)提供的函數(shù))买置,庫函文件的內(nèi)存地址編碼進最后的可執(zhí)行文件中,接著鏈接器會輸出可以運行的執(zhí)行文件:a.out强霎,得到a.out 命令為:
xcrun clang man.c
用size 工具查看a.out的文件結(jié)構(gòu)
可以看到 main.c文件生成的可執(zhí)行文件a.out 的虛擬地址空間從0x10000000 開始的忿项,之前的地址是不可訪問
此時在借助mac下可執(zhí)行文件的閱讀器。machOView工具城舞,看生成的a.out的文件結(jié)構(gòu)和內(nèi)容轩触,發(fā)現(xiàn)了一張叫symbols的表
這里面就很清楚的記錄了,編譯前的字符串家夺,在編譯成二進制可執(zhí)行文件后的虛擬地址脱柱,和app打包生成的dsym基本類似,這就可以猜想秦踪,蘋果是如何通過crash的二進制棧信息解析出可讀性的程序代碼調(diào)用棧的褐捻。借助symbols表給我的希望,我用machOView打開了我編譯生成的frameWork
原諒我把圖片糊得像翔椅邓。柠逞。。景馁。因為是公司的項目的frameWork
首先第一張里表示了在不同平臺下的可執(zhí)行數(shù)據(jù)板壮,當前frameWork可在ARM,X86合住,Arm64绰精,X86_64平臺下執(zhí)行
第二張圖是所有frameWork內(nèi)的類名對應的編譯生成的虛擬地址
第三張是挑了第二張圖中的一個類的symbols表,記錄的是當前文件編譯后的字符對照表透葛。
那么有了以上三張表的對比后我們很容易的了解到Xcode工具如何通過運行期間的二進制地址對應到具體文件的具體代碼調(diào)用了笨使。