arm64e由于引入了PAC機制,導致符號地址發生了巨大變化。也給堆棧回溯帶來了問題。
背景
從去年新iphone發布后,我們陸陸續續發現crash上報組件上報的crash總很奇怪,堆棧里要么只有棧幀0,要么就只有幾個棧幀,而丟失了絕大部分的棧,起初我們沒有在意,只是懷疑crash上報組件又引入了什么奇怪的bug,但因為量比較低,并不影響我們解決主要的crash問題(畢竟XS/XR設備還不是主流)。但是后來又遇到了一次QAPM上報的內存監控堆棧,發現也很奇怪堆棧還是丟失了很多,只有棧幀0或者main函數等信息,其它都只是符號地址,如下:
Thread 1 :
1 libsystem_kernel.dylib 0x1ab941000 0x1ab959c60
2 libsystem_kernel.dylib 0x1ab941000 0x1ab9590e8
3 unkonwn 0x57fe81ab7f7154
4 unkonwn 0x7dc001ab7f75f0
5 unkonwn 0x668381aba151f4
6 unkonwn 0x797701ac975b68
7 unkonwn 0x656881ac748400
8 unkonwn 0x56b181ac756554
9 unkonwn 0x26eb01ac97b9a8
10 unkonwn 0x73d081ac86a820
11 unkonwn 0x1c4d01ab7e1884
12 unkonwn 0xfc01ab7ee404
13 unkonwn 0x49f081ac7db250
14 unkonwn 0xce101ac84915c
15 unkonwn 0x7edd01abd40acc
16 unkonwn 0x2cad81abd40a8c
17 unkonwn 0x53ff01abd3ff30
18 unkonwn 0x661501abd3fbbc
19 unkonwn 0x5aeb81abcb6768
20 unkonwn 0x404001abd3f664
21 unkonwn 0x2ffd81ac73a7c4
22 unkonwn 0x476581d9099cd8
23 unkonwn 0x509081d89383c8
24 unkonwn 0x659f81d8938e5c
25 unkonwn 0x26c181d8937eb8
26 unkonwn 0x1ab581d893cea8
27 unkonwn 0x56c481d8c83904
28 unkonwn 0x563b81ae794c58
29 unkonwn 0x4dd581ab7e1884
30 unkonwn 0x4e4901ab7e4e5c
31 unkonwn 0x697381ae7d118c
32 unkonwn 0x1ca181ae7d0e08
33 unkonwn 0x204381ae7d1404
34 unkonwn 0x42a001abd62444
35 unkonwn 0x7ca881abd623c0
36 unkonwn 0x7e8c81abd61c7c
37 unkonwn 0x626881abd5c950
38 unkonwn 0x199c01abd5c254
39 unkonwn 0x209801adf9bd8c
40 unkonwn 0x458381d90a44c0
41 unkonwn 0x62f78102b5047c
42 libdyld.dylib 0x1ab818000 0x1ab818fd8
地址明顯有問題,而導致翻譯失敗。
PAC
偶然間看到內網有人解決了最近某個版本的一個top 2的crash,提到了一個堆棧回溯失敗的問題和原因,豁然開朗。
果然是PAC問題,雖然新iphone剛發布時就知道有PAC的特性,但我們一直沒重視。而且本身我們的crash問題堆棧的量又特別特別少,導致我們也沒認真對待。
正好結合最近和以前遇到的問題,發現問題系統設備都是arm64e設備和12系統,才意識到我們的堆棧回溯失敗的根因也都是PAC問題,索性就再研究下了PAC。
什么是PAC
PAC即Pointer Authentication,它的目的即檢測和保護地址不被意外或惡意修改,使得應用執行更加安全。
比如如果當前pc指向了PAC后的值0x00691e8104c560d4,而我們應用強制將其改為其他地址,則會導致內存訪問錯誤或PAC校驗失敗而crash。
PAC特性是由硬件提供的,保護了函數調用期間,棧空間和地址的安全。
它利用VA的高位來實現,當執行該VA時,會有專門的指令解碼VA并讀取指令。任何導致該VA在讀與寫之間不一致的行為,都會觸發PAC校驗失敗。PAC校驗失敗會觸發內存崩潰而crash;
PAC的結構如下:
在iphone上,由于VA值的上限只用到36bits,所以會利用VA的高28位來作為PAC存儲使用。
備注:xnu規定的iOS的VA最大值為#define MACH_VM_MAX_ADDRESS 0x0000000FC0000000,即只需要36bits即可。
處理PAC
根據Apple的文檔Preparing Your App to Work with Pointer Authentication ,處理PAC的地址問題我們直接使用ptrauth_strip宏(#include <ptrauth.h>)即可,但該死的蘋果限制了該宏僅在arm64e下才生效,所以此路不通。
但很慶幸,PAC的本質是在arm64e上它把VA無用的高28bits拿來用作PAC了,所以我們只要取低36bits即為實際的VA了。
備注:準確點是arm64e中PAC只用到了第62bit到36bit共27bits,最高位第63bit在arm64中是tag pointer標記位.
所以我們只要直接取低36bits就能愉快的玩耍了。
va = va & 0x0fffffffff;//得到PAC strip后的原始VA值.
結語
果然任何小問題都不要因為量不多而不重視它,很可能每個小問題的背后都隱藏了一個未知的新問題。
iOS正向開發果然越來越難了。
參考
Preparing Your App to Work with Pointer Authentication