iOS App啓動優化(三):二進制重排markdown
iOS App啓動優化(四):編譯期插樁 && 獲取方法符號函數
iOS App啓動優化(五):收集符號 && 生成 Order File工具
編譯器插樁就是在代碼編譯期間修改已有的代碼或生成新代碼。post
編譯期時,在每個函數內部二進制源數據添加 hook
代碼來實現全局 hook
效果。優化
編譯期插樁會涉及關於 LLVM
的內容,可是對代碼具體實現影響不大,想了解相關概念能夠看看ui
LLVM及編譯過程spa
說白了咱們要跟蹤到 每一個方法的執行,從而獲取到啓動時 方法執行的順序,而後再按照這個順序去編寫order file
。
跟蹤的具體實現會用到 clang
的 SanitizerCoverage
,這是什麼東西??
遇到事情不要慌,先打開文檔看一看 ~ clang文檔
文檔很重要,萬一裏面有 demo
呢?
LLVM
具備內置的簡單代碼覆蓋率檢測工具(
SanitizerCoverage
)
文檔是個好東西~裏面就有 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); } 複製代碼
執行代碼看一看
這是什麼...沒看懂,仍是打個斷點看一看吧
start
裏面存的是一堆序號,stop
裏面會不會也是序號呢?
看看 stop
驗證一下,發現裏面的並非序號,這就尷尬了...
思考了一會發現想知道最後一個序號,應該把 stop
地址往前移動了再查看!
向前挪4個字節看看
真找到了,start
和 stop
中間是 01 ~ 0e
,十進制的 1~14
。
這會不會是函數的序號呢?給他安排個函數試試看
0e
變成了
0f
,真的增長了一個。
看到這裏可能以爲~ hook
一下都能拿到這有啥了不得。來點新花樣
block
c函數
0xf + 2 = 0x11
,數量增長了2~驗證成功
可是還不夠,我萬一是混編咋辦呢?得添加個 swfit
函數試試。
Target -> Build Setting -> Custom Complier Flags -> Other Swift Flags
添加
-sanitize-coverage=func
-sanitize=undefined
運行代碼,輸出地址
數量一會兒增長到了0x1a
,我猜想生成文件同時生成了很多自帶方法,那麼咱們屏蔽掉剛剛新建的block
、c函數
、oc函數
看看數量是否變成1a - 0x3
便可。
1a - 0x3 = 0x17
驗證成功
上述 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
中也會對 x29
、x30
寄存器的值作保護,防止子函數跳轉其餘函數覆蓋 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