美团 iOS 工程 zsource 命令背后的那些事儿
zsource 命令是什么?
美團 App 在 2015 年就已經(jīng)基于 CocoaPods 完成了組件化的工作。在組件化的改造過程中,為了能夠加速整體工程的構(gòu)建速度,我們對需要集成進美團 App 的組件進行了二進制化,同時提供一個叫做 cocoapods-binary 的 CocoaPods 插件來支持本地工程使用二進制。因此,美團 App 的開發(fā)者在集成開發(fā)時,除了自己正在開發(fā)的組件,其他的組件都以二進制的形式存在。
使用二進制,雖然會給工程帶來構(gòu)建速度的提升,但是會帶來一個新的問題:在調(diào)試工程時,那些使用二進制的組件,無法像源碼調(diào)試那樣看到足夠豐富的調(diào)試信息。例如,如果程序在二進制組件的代碼中崩潰,我們只能看到該組件的堆棧信息和一些不明所以的匯編代碼:
和業(yè)界大多的組件化方案類似,美團 App 的組件化方案也提供了將一個組件從二進制切換到源碼的機制。美團工程的開發(fā)者能夠使用一系列配置和命令來切換組件的源碼和二進制狀態(tài),但每次切換都需要重新執(zhí)行 pod install。這種方式在組件化的初期是沒有什么問題的。但隨著美團 App 的組件數(shù)量不斷增長,即便是只切換一個組件的狀態(tài),單次 pod install 的時間也增長到了分鐘級。而且這種方式每切換一次就必須重新編譯運行一次 App,在追查一些偶現(xiàn)崩潰問題時,開發(fā)體驗非常不友好,也不利于崩潰問題的快速定位分析。
為了解決以上提到的這些問題,我們利用 CocoaPods 的插件機制,為 CocoaPods 的 pod 命令增加了 zsource 子命令,開發(fā)者可以在使用二進制構(gòu)建工程的同時,非常快速地將一個組件調(diào)出源碼進行調(diào)試,具體的使用效果可以看一下如下的屏幕錄制:
zsource 命令的開發(fā)始末
在推出 zsource 功能后,很多同學(xué)都對 zsource 背后的技術(shù)原理十分感興趣。其實 zsource 整個功能的開發(fā)流程也十分的有趣,就像小說一樣,分為幾個不同的時期:
- 原理猜想
- 查閱資料
- 簡單粗暴的嘗試
- 柳暗花明
- 工程化
原理猜想
如果讓我們猜想 Xcode 斷點調(diào)試功能的實現(xiàn)原理,可能大部分人都會猜這樣一種可能:Xcode 在編譯 Debug 版本的二進制過程中,在二進制中某個字段存儲了該二進制所對應(yīng)的源碼的文件地址。當我們在 Xcode 中打斷點進行調(diào)試的時候,Xcode 會根據(jù)二進制中這個字段中存儲的源碼文件地址,打開對應(yīng)的源碼文件,并在 UI 上展示該源碼文件。
道理好像沒有什么問題,但是事實是這樣嗎?在某次團建回國的航班上,我們組成威和志宇兩位同學(xué)在提出這種猜想后,拿出電腦,做了一個這樣的小實驗:
實驗中,他們分別創(chuàng)建了兩個 Xcode 工程 A 和 B,工程 A 會產(chǎn)出一個二進制 libA.a。工程 B 中會接將 A 的產(chǎn)出 libA.a 拖到工程中,然后設(shè)置 A 中代碼的符號斷點,然后編譯運行。結(jié)果發(fā)現(xiàn),當斷點斷在 A 中的代碼時,Xcode 會直接跳轉(zhuǎn)到 A 的源文件中,并且可以繼續(xù)增加斷點以及正常的單步調(diào)試。
通過這個實驗,成威和志宇同學(xué)確定了猜想的正確性。那么接下來需要做的,就是確定二進制中,這個源文件地址信息具體藏在哪一個字段中。
查閱資料
我們都知道蘋果的 Mach-O 二進制文件使用的是 DWARF 這種格式來存放調(diào)試相關(guān)的數(shù)據(jù)的。但因為我們很難從這個問題中提煉幾個精確的關(guān)鍵詞在搜索引擎中檢索,所以很難通過簡單的幾次檢索就獲取到我們想要的答案:二進制這個字段的名稱,在初期甚至無法確定這個字段應(yīng)該是從 Mach-O 的資料中檢索還是從 DWARF 的資料中檢索。
在沒有太好的搜索結(jié)果的情況下,我們一度曾經(jīng)想嘗試去從頭去啃一啃找到的一些二進制相關(guān)的文檔:
- osx-abi-macho-file-format-reference
- Introduction to the DWARF Debugging Format
- DWARF 1.1.0 Reference
簡單粗暴的嘗試
然而,由于對二進制格式不是那么熟悉,也不太了解二進制相關(guān)的詞匯和概念,所以閱讀文檔的速度就非常緩慢。
不過,技術(shù)的有趣之處就在于,有時候你可以基于我們的猜想,任意去嘗試,跳過艱辛的文檔閱讀過程。在文檔閱讀遇到挫折后,我們猜想,二進制中很有可能也是用字符來存儲這些源碼信息的,那么如果我們就把二進制當做字符來看,是不是能搜到一些東西呢?
于是我們試著做了一個比較簡單的二進制文件,二進制文件中僅僅包含一個 ZSCViewController,然后用 xxd 這個命令嘗試讀取二進制中的內(nèi)容,考慮到 xxd 的輸出會折行,我們選取了 ZSCViewController 字符串的子串進行過濾:
xxd ./libZSource.a | grep -C 5 'ZSCViewControlle'果真得到了一些結(jié)果:
通過這個實驗,我們確定了二進制中源碼文件的路徑確實是用普通的字符來存儲的;緊接著,我們用 MachOViewer 來查看二進制文件,以獲取到更友好的二進制信息。利用 MachOViewer,我們可以發(fā)現(xiàn)這些信息都存在了二進制的 “__debug_str” Section 中。
雖然還是不確定這個地址所對應(yīng)的字段叫什么,但研究到這里,我們還是有所進展的,最起碼我們可以假定這個路徑一定是緊跟在 “Apple LLVM version 10.0.0 ” 字符后面的,然后利用一些讀取 Mach-O 的 Ruby 庫,比如 ruby-macho,基于這個假定來讀取這個路徑,為這個特性的工具化提供一絲可能性。
柳暗花明
簡單的嘗試沒有得到想要的答案,但透過 Section 的名字,可以確定源碼文件的路徑信息和 DWARF 有關(guān)。
長時間和 CI 打交道的經(jīng)驗告訴我們,對于每一種二進制格式,蘋果公司都會提供一個可以專門用于解析的命令行工具,所以我們就嘗試找了找有沒有解析二進制中 DWARF 格式的命令行工具。
功夫不負有心人,我們找到了 dwarfdump,那么用它來看看之前的那個二進制文件:
dwarfdump ./libZSource.a | grep 'ZSCViewContro'果然有了更好的輸出:
這里我們注意到了 AT_name 這個字段名。拿著這個字段名,去前面給出的 DWARF 1.1.0 Reference 文檔中查閱,我們可以得知:
An AT_name attribute whose value is a null-terminated string containing the full or relative path name of the primary source file from which the compilation unit was derived.
進一步查詢,我們可以找到另一個和他類似的字段 —— AT_comp_dir:
An AT_comp_dir attribute whose value is a null-terminated string containing the current working directory of the compilation command that produced this compilation unit in whatever form makes sense Forelax the host system.
看起來,這兩個字段就是我們所苦苦追尋的答案了。
工程化
通過實驗,以及找到的這兩個字段的描述,我們基本可以確定,即便工程是使用二進制構(gòu)建,只要二進制 AT_name 字段中的路徑存在對應(yīng)的源碼文件,App 一樣可以使用源碼進行斷點調(diào)試。這種調(diào)試方式除了修改源碼再次構(gòu)建不能生效以外,其他的調(diào)試場景都和直接使用源碼構(gòu)建無異。考慮到我們?nèi)粘5恼{(diào)試場景絕大多數(shù)都只需要查看其他組件的源碼,并不需要修改,把這個功能工程化還是非常有意義的。
那接下來的事情就比較簡單了:
幸運的是,查看完美團 App 的幾百個組件后,我們發(fā)現(xiàn)只有少數(shù)近一年內(nèi)沒有制作過二進制的組件路徑比較不同,其他都相同,因此可以先忽略這一小部分組件。如果這部分組件需要支持該功能,只要再制作一次二進制即可。
確定方案以后,寫代碼就很簡單了,最終我們利用 CocoaPods,提供了 zsource 的三個命令:
總結(jié)
zsource 功能整體的開發(fā)過程基本上都是基于一個個的猜想和實驗來完成的,整體的開發(fā)上線過程實際上只花了兩個晚上。但如果在沒有基礎(chǔ)知識的情況下,選擇把上文中提到的參考資料都看懂后再動手,可能會花費更多的時間。這一個有趣的驗證過程也充分說明,有時候我們可以不拘泥于冗長的文檔以及資料,通過類似逆向工程的方式,非常快速地拿到我們需要的答案。此時我們再回過頭去看文檔,可能會獲得比直接看文檔更好的效果。
最后,非常感謝成威老師和志宇同學(xué)對技術(shù)的崇高追求,即便在飛機上,也愿意拿出電腦驗證自己的猜想,為 zsource 后續(xù)的工程化落地提供了更多的可能。
作者簡介
- 宇杰,美團 iOS 工程師,2016 年加入美團,先后參與美團 App 持續(xù)集成平臺建設(shè)、美團 App ReactNative 平臺化等工作。目前在參與美團 App 工程效率提升和 Flutter 應(yīng)用的相關(guān)工作。
總結(jié)
以上是生活随笔為你收集整理的美团 iOS 工程 zsource 命令背后的那些事儿的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 顶会论文:基于神经网络StarNet的行
- 下一篇: 美团的DBProxy实践