iOS App啓動優化(四):編譯期插樁 && 獲取方法符號

iOS App啓動優化(一):檢測啓動時間html

iOS App啓動優化(二):物理內存和虛擬內存bash

iOS App啓動優化(三):二進制重排markdown

iOS App啓動優化(四):編譯期插樁 && 獲取方法符號函數

iOS App啓動優化(五):收集符號 && 生成 Order File工具

iOS App啓動優化(六):實用黨直接看這裏oop

相關概念

編譯器插樁就是在代碼編譯期間修改已有的代碼或生成新代碼。post

編譯期時,在每個函數內部二進制源數據添加 hook 代碼來實現全局 hook 效果。優化

編譯期插樁會涉及關於 LLVM 的內容,可是對代碼具體實現影響不大,想了解相關概念能夠看看ui

LLVM及編譯過程spa

編譯LLVM

怎麼找到插樁方法

說白了咱們要跟蹤到 每一個方法的執行,從而獲取到啓動時 方法執行的順序,而後再按照這個順序去編寫order file

跟蹤的具體實現會用到 clangSanitizerCoverage,這是什麼東西??

遇到事情不要慌,先打開文檔看一看 ~ clang文檔

文檔很重要,萬一裏面有 demo 呢?

代碼覆蓋率檢測工具(SanitizerCoverage)

LLVM 具備內置的簡單代碼覆蓋率檢測工具( SanitizerCoverage

  • 它能夠在函數,塊、邊緣級別插入用戶定義函數並提供回調
  • 它能夠實現了簡單的可視化覆蓋率報告

經過看守者跟蹤 (Tracing PCs with guards)

文檔是個好東西~裏面就有 example

沒明白怎麼用?問題不大~

具體實現

添加設置

Target -> Build Setting -> Custom Complier Flags -> Other C Flags 添加 -fsanitize-coverage=trace-pc-guard

添加方法

我是在 viewController 裏面進行的,把這兩個方法複製進去

void __sanitizer_cov_trace_pc_guard_init(uint32_t *start,
                                         uint32_t *stop) {
  static uint64_t N;  // Counter for the guards.
  if (start == stop || *start) return;  // Initialize only once.
  printf("INIT: %p %p\n", start, stop);
  for (uint32_t *x = start; x < stop; x++)
    *x = ++N;  // Guards should start from 1.
}

void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
  if (!*guard) return;
  void *PC = __builtin_return_address(0);
  char PcDescr[1024];
  printf("guard: %p %x PC %s\n", guard, *guard, PcDescr);
}
複製代碼

執行代碼看一看

__sanitizer_cov_trace_pc_guard_init

這是什麼...沒看懂,仍是打個斷點看一看吧

start 裏面存的是一堆序號,stop 裏面會不會也是序號呢?

看看 stop 驗證一下,發現裏面的並非序號,這就尷尬了...

思考了一會發現想知道最後一個序號,應該把 stop 地址往前移動了再查看!

向前挪4個字節看看

真找到了,startstop 中間是 01 ~ 0e,十進制的 1~14

這會不會是函數的序號呢?給他安排個函數試試看

0e 變成了 0f ,真的增長了一個。

Block 、 C函數

看到這裏可能以爲~ hook 一下都能拿到這有啥了不得。來點新花樣

  • 添加一個 block
  • 添加一個 c函數

0xf + 2 = 0x11,數量增長了2~驗證成功

可是還不夠,我萬一是混編咋辦呢?得添加個 swfit 函數試試。

Swift 混編處理

Target -> Build Setting -> Custom Complier Flags -> Other Swift Flags 添加

  • -sanitize-coverage=func
  • -sanitize=undefined

運行代碼,輸出地址

數量一會兒增長到了0x1a,我猜想生成文件同時生成了很多自帶方法,那麼咱們屏蔽掉剛剛新建的blockc函數oc函數看看數量是否變成1a - 0x3便可。

1a - 0x3 = 0x17 驗證成功

__sanitizer_cov_trace_pc_guard

上述 guard_init 方法裏面能夠獲取到全部方法的數量,那麼確定也有辦法獲取方法具體的相關信息。重點就是接下來要分析的__sanitizer_cov_trace_pc_guard

添加點擊方法,調用一下剛剛添加的方法

運行代碼,點擊屏幕兩次

每次點擊最終輸出 test, 以此爲界能夠看到每次點擊會進入 guard方法 8次,在[ViewController touchesBegan:withEvent:] 斷點查看一下。

能夠看到在調用方法的時候插入了 __sanitizer_cov_trace_pc_guard 方法。

這裏實際上是方法內部代碼執行的第一句,在此以前的代碼行是在棧平衡和準備寄存器數據。

獲取方法地址

在執行了 __sanitizer_cov_trace_pc_guard 後斷點,讀取一下 lr 寄存器的內容

這裏能夠看到lr裏面存儲的是 [ViewController touchesBegan:withEvent:]

這就太神奇了,難道從新執行了一次嗎?不是的,若是從新執行就會陷入死循環,很明顯代碼並無。

函數嵌套時,跳轉函數 bl 會保存下一條指令的地址在 X30,也就是lr寄存器。

funcA 調用了 funcB,在彙編裏面會被翻譯成 bl + 0x????, 該指令會首先將下一條彙編指令的地址保存在 x30 寄存器, 而後在跳轉到 bl 後面傳遞的指定地址去執行。

bl 跳轉到某個地址的原理就是修改 pc 寄存器的值來指向到要跳轉的地址。

並且實際上 funcB 中也會對 x29x30 寄存器的值作保護,防止子函數跳轉其餘函數覆蓋 x30 的值。

funcB 執行返回指令 ret 時 , 會讀取 x30 的地址跳回去, 就返回到上一層函數的下一步。

因此在這裏實際上是執行了 __sanitizer_cov_trace_pc_guard 後返回到了原來 [ViewController touchesBegan:withEvent:] 的首行。

也就是說,咱們能夠在 __sanitizer_cov_trace_pc_guard 函數裏面拿到原方法的地址!來看看是怎麼作到的。

重點在這個 __builtin_return_address 函數,它的做用其實就是去讀取 x30 中所存儲的要返回時下一條指令的地址。那麼這個 PC 就是咱們要的方法地址!

獲取方法符號

導入頭文件

#import <dlfcn.h>
複製代碼

dlfcn.h 中有一個 dladdr() 方法,能夠經過函數內部地址找到函數符號。

該方法須要用到結構體Dl_info,裏面還有一些其餘信息~

獲取到一個個 Dl_info,打印出來看看。

sname 咱們要的方法符號了,並且順序正是調用函數的順序!

到這裏經過編譯器插樁獲取方法符號已經成功了,那立刻到項目寫 order file 就大功告成了?

no~too young too simple~ T.T

接下來請看 iOS App啓動優化(五):收集符號 && 生成 Order File

相關文章
相關標籤/搜索