首頁>技術>

開源專案專題系列

(三)

1.開源專案名稱:WBBlades

2.github地址:

https://github.com/wuba/WBBlades

3.簡介:WBBlades是58同城推出的一款Mach-O檔案解析的APP分析工具集,包含以Pod為維度的APP大小分析、無用類檢測以及無符合表情景下的崩潰解析。目前WBBlades已經成為58同城客戶端在並行研發和多app研發場景下效能檢測與品質保證的不可或缺工具。

WBBlades中的工具,基於相同的Mach-O檔案解析技術,主要具備以下特點:

Ø App大小分析工具

無需編譯連結,可直接對靜態庫檔案進行分析,相對基於linkmap檔案的分析更高效和靈活;實現了模擬連結器的工作流程,對靜態庫二進位制進行資料提取和合並,從而直接輸出靜態庫連結後的大小,模擬後的大小與真實編譯連結的大小無顯著性差異。

Ø 無用類檢測工具

相比已公開的常見方案,支援對類的繼承、動態呼叫、自身類呼叫、屬性及成員變數等情景下的無用類檢測,無用類識別精度更高。

Ø 無符號表崩潰解析工具

在無符號表的情況下,通過對二進位制檔案的解析實現OC程式碼的日誌符號化;易擴充套件、易推廣,具備可在終端上獨立執行能力,可通過指令碼呼叫、傳參。背景介紹

WBBlades工具集基於不同時間點的需求背景,不斷探索,產出了相應的工具。

1. 大小分析工具

首先誕生的APP大小分析工具。58同城作為一個平臺型的APP,經常需要接入或更新SDK,在接入SDK之前,我們需要明確SDK或更新的大小成本,並與相關方評估成本和收益。在此之前,如果要評估一個SDK的接入大小,我們往往需要編譯SDK提供的demo,通過demo的大小來估算SDK的大小,或者通過linkmap來分析SDK的大小。其弊端在於對demo進行編譯連結,長此以往是個耗費體力和時間的工作。

我們期望能夠根據靜態庫檔案直接分析出靜態庫連結後的大小,無需編譯連結,因此我們仿照模擬連結器的工作流程,對靜態庫二進位制進行資料提取和合並,從而直接輸出靜態庫連結後的大小;此外,我們將工具推廣到58同城APP的大小分析,通過對cocoapods所產生的靜態庫產物進行大小分析,從而實現了58同城APP的大小分佈統計,實現了一套方案多處複用。

2. 無用類檢測

通過對58同城APP大小分佈統計,我們發現58同城的APP程式碼大小和資源大小比例為:13:5,打破了我們以往認為資源瘦身是最有效的手段的思維慣式。58同城的幾個主要業務線程式碼和資源大小在arm64+3x屏手機上資料對比如下圖所示:

圖 1業務線程式碼與資源大小對比

因此,從程式碼的角度實現瘦身是十分必要的。我們首先調研了業界的常用技術手段,分別是:

指令碼分析原始碼基於linkmap+Clang的檢測技術針對framework目標檔案優化的技術方案基於otool+Mach-O的檢測技術

前三種技術方案的優缺點由於篇幅限制,在此不做贅述。第四種方案也是業界使用最廣泛的方案,通過otool命令提取可執行檔案的classlist section 和 classref section。形成classlist和classref的地址差集。但是此方案存在以下問題:

(1)動態呼叫的類並不會被加入到classref中。

(2)作為基類、作為屬性、成員變數不會被加入到classref。

(3)呼叫了load方法,將自身註冊到其他資料中(如RN的module註冊)不會被放入到classref中。

(4)類自身內部呼叫,外界並沒有呼叫,這個類也被放入classref中。

為了解決上述問題,我們通過解析Mach-O檔案,從一定程度上解決前3個問題,並通過對反彙編引擎capstone的應用,解決問題(4)。

3. 日誌解析工具

58同城在業務開發階段提供給測試同學的測試包都是通過Jenkins服務打包。隨著業務的發展,58同城APP的大小越來越龐大,這就導致測試同學從Jenkins伺服器上下載APP的時間較長。為了能夠儘可能的減小下載大小,58同城將APP的符號表在打包期間從應用程式中剝離出來形成dSYM檔案,儲存在打包伺服器中。因此測試同學下載的Jenkins包是不包含符號表資訊的。由於剝離出來的dSYM檔案較大,為了節省伺服器空間,dSYM在保留2天后會自動清除。假設有這樣一個場景,即測試同學下載了一個測試包,在測試到第三天時發生了不可穩定復現的崩潰,在這種即沒有dSYM檔案也沒有bugly的symbol檔案的情況下,如何才能恢復堆疊符號?為此,我們通過解析Mach-O檔案,查詢崩潰偏移地址位於哪個函式的指令區間範圍內,從而實現崩潰日誌的符號化。

技術原理

WBBlades工具集的技術原理是一致的,都是基於Mach-O檔案的解析。整個專案的核心是基於類是如何在Mach-O檔案中儲存的。在了解類在Mach-O檔案中儲存結構後,我們就能通過Mach-O檔案獲取當前專案中有哪些類,每個類的類名及方法名,以及方法的指令儲存範圍。如果能清楚準確的知道以上資訊,那麼專案程式碼基本上可以說是半透明的了,我們能做的事情也就非常多了。

Mach-O檔案可以分為兩大段:文字段和資料段。在文字段中主要儲存的是字串及指令,類名、方法名最終都是以字串的形式儲存在文字段中。方法編譯後也以指令的形式儲存在文字段中,以arm64架構為例,方法編譯後,會形成N條4位元組的指令儲存到__text section中。而在DATA段中,本質上儲存的都是地址,這些地址通過直接或多次定址後會索引到TEXT段。例如(__DATA,__objc_classlist)節,該節資料儲存的是所有類的地址(這裡所說的地址是指距Mach-O檔案起始地址的偏移地址)。arm64的二進位制檔案中,此地址指向一個class64的結構體。class64結構體起始8位元組用於記錄類的isa指標,指向類的元類。末尾8位元組用於記錄data,指向類的class64Info結構體。class64Info儲存類名的字串地址及方法列表地址。class64結構體和class64Info結構體如下:

struct class64 { unsigned long long isa;//元類在檔案中的地址 unsigned long long superClass; unsigned long long cache; unsigned long long vtable; unsigned long long data;//class64Info在檔案中的地址};struct class64Info { unsigned int flags; unsigned int instanceStart; unsigned int instanceSize; unsigned int reserved; unsigned long long instanceVarLayout; unsigned long long name;//類名在檔案中的地址 unsigned long long baseMethods;//方法列表在檔案中的地址 unsigned long long baseProtocols; unsigned long long instanceVariables; unsigned long long weakInstanceVariables; unsigned long long baseProperties;};

方法列表中儲存的是類的例項方法,如果想要查詢類方法,那麼需要先跳轉到元類的class64Info結構體,再查詢方法列表。根據上面的定址後,我們能夠找到每個類的所有類方法和例項方法。那麼方法在方法列表中是如何儲存的呢?首先來看一張簡圖:

圖 2 方法列表

從上圖可以看出,在方法列表中,前8位元組為method64_list_t結構體,用於說明方法的數量,此後檔案中連續儲存method64_t結構體,通過method64_t我們可以找到這個方法的名稱和函式起始地址。在這裡,我們先簡單總結下前面的內容:首先根據Mach-O檔案的(__DATA,__objc_classlist)節獲取所有類的地址,根據地址從Mach-O檔案中讀取class64結構體,根據class64結構體data成員記錄的地址獲取class64Info結構體。最後再根據class64Info結構體的baseMethods成員獲取到例項方法列表(類方法需要讀取元類資料,讀取方法相同)。獲取到方法列表的起始地址後,即可根據method64_list_t + N*method64_t的方式從檔案中讀取每個方法的函式地址。獲取到的函式地址即為函式距離Mach-O檔案的偏移地址,如函式地址為0xAAAA,則說明在Mach-O檔案,從0xAAAA位元組開始是某函式的地址,此後連續N位元組為該函式的函式指令。

圖 3 如何在Mach-O檔案中獲取函式地址

假設函式起始地址為0xAAAA,函式結束地址為0xBBBB,那麼當崩潰堆疊顯示偏移地址為0xAABB時,則說明崩潰指令位於該函式之類範圍內,也就是說此函式發生崩潰。那麼問題來了,根據上面所述的方法,可以通過Mach-O檔案定位到函式的起始地址,可以通過崩潰日誌確定崩潰發生時的指令偏移地址,那麼如何才能確定函式的結束地址呢?在iOS系統中,一條arm64的指令為4位元組,當函式結束時會執行一條ret指令,當我們從函式起始地址開始掃描,掃描到ret指令時即可認為函式結束。ret指令為無操作碼指令,其指令固定為0xC0035FD6。我們從0xAAAA開始讀取指令,每次按序獲取4位元組並讀取這4位元組的內容,如果內容為0xC0035FD6則認為函式結束。同理,在遍歷類的過程中我們也可以獲取成員變數、繼承關係等資訊。

在這裡留個懸念,上文中主要是以OC語言作為示例,並且本質上是利用了OC語言的動態特性來實現檔案解析的。換言之,如果是C/C++語言,那麼上面的過程是無法生效的,那麼作為蘋果極力推崇的Swift,上面的定址過程是否能生效呢?感興趣的同學可以按上文中的過程嘗試下。

技術方案對比

1. 大小分析

主流的大小分析方案是通過linkmap檔案分析,本方案與linkmap檔案分析相比,使用較為簡單,無需編譯連結即可分析統計。

圖 4 linkmap與WBBlades對比

在準確性方面,linkmap為鏈器件記錄的資料,理論上來說更為準確。但是我們隨機選取了19個SDK,通過linkmap(以https://github.com/huanxsd/LinkMap為例)和WBBlades分別進行分析,兩個方案的資料比較接近,偏差在10%以內。

2. 無用類檢測

基於otool+Mach-O的檢測技術是目前使用較多的方案,58同城APP早期也是選用此方案,其優點為簡單快速,但是58同城APP大小較為龐大,內部程式碼複雜多樣,因此解析出的資料誤差較大,準確率較低。

由於WBBlades的無用類檢測在檢測debug包時,為了提高精確度遍歷了符號表和指令。在58同城APP中存在2800萬條指令以及百萬級別的符號表,非常耗時。為了解決耗時問題,我們提供了圈選功能,開發者檢視某幾個SDK或某幾個Pod的無用類。

3. 日誌解析工具

WBBlades的日誌解析功能最大的特點是不依賴任何符號表,58同城APP具備dSYM檔案符號化、bugly的symbol檔案符號化、無符號表符號化多維度多場景的日誌解析能力。除了不依賴符號表之外,WBBlades的日誌解析功能可以整合到APP中,在APP內實現崩潰攔截+崩潰解析的一條龍服務。

圖 7 WBBlades日誌解析Mac工具

如何使用

WBBlades 提供了2種使用方式,開發可以通過Mac應用直接使用相應的功能:

圖 8 Mac 視覺化工具主介面

也可以通過終端呼叫,輸入相應的引數即可使用相應的功能。如果需要實現自動化流程,那麼可以通過指令碼呼叫並賦參。

後期規劃

我們將持續優化和改進工具的準確性和易用性,目前大小分析工具對C++/Swift編譯的檔案存在一定的誤差,這是我們後期要重點關注的問題。現階段工具集只支援arm64架構的Mach-O檔案,如果有必要我們將增加對Fat檔案及armv7架構的支援。另外,APP內日誌解析相關程式碼將在後期開源。

如何貢獻&問題反饋

我們誠摯地希望開發者提出寶貴的意見和建議,您可以在 https://github.com/wuba/WBBlades 了解專案原始碼、使用方法、啟動方式等。歡迎提交 PR 或者 Issue,向我們反饋建議和問題。

作者簡介

鄧竹立,58同城使用者價值增長部-iOS技術部資深研發工程師,WBBlades主要開發者之一,主要負責WBBlades的底層開發與調優。

樸惠姝,58同城使用者價值增長部-iOS技術部高階研發工程師,主要負責客戶端效能優化及工具研發,跨平臺庫的研發。

林雅明,58同城使用者價值增長部-iOS技術部開發工程師,主要負責WBBlades的Mac版應用開發。

參考文獻

1. Capstone: https://github.com/aquynh/capstone

2. LinkMap: https://github.com/huanxsd/LinkMap

3. MachOView: https://github.com/gdbinit/MachOView

https://juejin.im/post/5d5d1a92e51d45620923886a

5. 趣探 Mach-O:檔案格式分析:

https://www.jianshu.com/p/54d842db3f69

6. iOS Crash 捕獲及堆疊符號化思路剖析:

https://www.jianshu.com/p/29051908c74b

想了解更多開源專案資訊?

與專案成員零距離交流?

一切應有盡有

最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • 「正點原子Linux連載」第四十六章Linux蜂鳴器實驗