libAccessibility通知Crash排查記錄分析
Crash 信息
Last Exception : 0 libobjc.A.dylib 0x00000001bee86f40 _objc_msgSend + 32 1 CoreFoundation 0x00000001a6132834 ___CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ + 28 2 CoreFoundation 0x00000001a61cefd4 ____CFXRegistrationPost_block_invoke + 52 3 CoreFoundation 0x00000001a61a21d0 __CFXRegistrationPost + 456 4 CoreFoundation 0x00000001a61488ac __CFXNotificationPost + 728 5 Foundation 0x00000001a7917754 -[NSNotificationCenter postNotificationName:object:userInfo:] + 96 6 CoreFoundation 0x00000001a6132834 ___CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ + 28 7 CoreFoundation 0x00000001a61cefd4 ____CFXRegistrationPost_block_invoke + 52 8 CoreFoundation 0x00000001a61a21d0 __CFXRegistrationPost + 456 9 CoreFoundation 0x00000001a61488ac __CFXNotificationPost + 728 10 CoreFoundation 0x00000001a616fe88 _CFNotificationCenterPostNotificationWithOptions + 136 11 libAccessibility.dylib 0x00000001a9e275f4 __updateCachePreferenceAndRepostNotification + 144 12 libAccessibility.dylib 0x00000001a9e21ea8 ____axsHandlePrefChanged_block_invoke + 132 13 libdispatch.dylib 0x00000001a5e06e6c __dispatch_call_block_and_release + 32 14 libdispatch.dylib 0x00000001a5e08a30 __dispatch_client_callout + 20 15 libdispatch.dylib 0x00000001a5e16f48 __dispatch_main_queue_drain + 928 16 libdispatch.dylib 0x00000001a5e16b98 __dispatch_main_queue_callback_4CF + 44 17 CoreFoundation 0x00000001a6159800 ___CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 16 18 CoreFoundation 0x00000001a6113704 ___CFRunLoopRun + 2532 19 CoreFoundation 0x00000001a6126bc8 _CFRunLoopRunSpecific + 600 20 GraphicsServices 0x00000001c225a374 _GSEventRunModal + 164 21 UIKitCore 0x00000001a8a96648 -[UIApplication _run] + 1100 22 UIKitCore 0x00000001a8817d90 _UIApplicationMain + 364 23 CustomApp 0x0000000100fe1c64 main (main.mm:0) 24 ??? 0x0000000109285ce4 0x000000010926c000 + 0 ------ Exception Type: SIGSEGV SEGV_ACCERR Exception Codes: fault addr: 0x0080000000000008 Crashed Thread: 0 0x1a9e1e000 - 0x1a9e43fff arm64 <309485b32e2c3803ae988866d303d7fd> libAccessibility.dylib
libAccessibility 在發(fā)送通知時產(chǎn)生了 Crash。
復(fù)現(xiàn)場景
在某些路徑可以復(fù)現(xiàn) Crash:

這里取出對象 isa 中的 class 對象 PAC 驗簽后使用,在 _objc_msgSend + 32 尋址時 Crash,是典型的對象內(nèi)存管理異常問題。
但按對通知中心的認知,會對 observer 弱引用,post 時不應(yīng)該出現(xiàn)釋放后引用指針未置 nil 的情況。先順便回溯分析一下調(diào)用棧。
簡單引用分析
CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER 會跳轉(zhuǎn)到入?yún)?block 的 invoke 函數(shù):
Foundation`__66-[NSNotificationCenter _addObserver:selector:name:object:options:]_block_invoke:
0x1a8531d0c <+0>: mov x2, x1
0x1a8531d10 <+4>: ldp x8, x1, [x0, #0x20]
0x1a8531d14 <+8>: mvn x0, x8
0x1a8531d18 <+12>: b 0x1a69cadb8
這個函數(shù)看起來就很熟悉了,在我們常規(guī)的添加通知接口產(chǎn)生。 block 的 invoke 函數(shù)第一個參數(shù)是 block 本身,<+4> 處是取出該 block 的兩個引用對象,他們分別是:
<UILabel: 0x12abb49c0…> _accessibilityButtonShapesChangedNotification:
后續(xù)就是調(diào)用 objc_msgSend 發(fā)送消息了。 接著看一下 block 對該 observer 的引用類型,最簡單的方法就是查看該 block 的 dispose 函數(shù)指令,里面會對引用的 strong 對象 release、weak 對象 storeWeak,把 block 內(nèi)存消息打印出來:
(lldb) re read x0
x0 = 0x0000000281ae8f90
(lldb) x 0x0000000281ae8f90
0x281ae8f90: 30 91 f9 2e 02 00 00 00 06 00 00 c1 00 00 00 00 0...............
0x281ae8fa0: 34 42 4a a8 01 00 00 00 e0 02 95 ff 01 00 00 00 4BJ.............
flags 記錄了 block 變量的各種信息,處于 block 第二個 8 字節(jié)0xc1000006,其 24 位為 1 說明在堆上,25 位為 0 說明沒有 dispose/copy 函數(shù),所以這個 block 對 observer 相當于 assign 引用。
有些奇怪,向上分析成本太高,直接開啟 Zombie 試圖去找到這個 Crash 的對象及其產(chǎn)生時機。
尋找 Crash 對象
開啟 Zombie 后果然輕松復(fù)現(xiàn):
-[UILabel _accessibilityButtonShapesChangedNotification:]: message sent to deallocated instance 0x12b7fc7d0 (lldb) x 0x12b7fc7d0 0x12b7fc7d0: d3 0b cb 83 02 00 00 00 00 00 00 00 00 00 00 00 ................ 0x12b7fc7e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ (lldb) po (Class)0x0283cb0bd3 _NSZombie_UILabel (lldb) p/t 0x0283cb0bd3 (long) $10 = 0b0000000000000000000000000000001010000011110010110000101111010011
Zombie 機制大概是在對象 dealloc 時把對象的 isa 類部分指向一個新的類(比如這里的 _NSZombie_UILabel),后續(xù)再向該對象發(fā)送消息就會被攔截下來報錯,所以對象地址不會變。
那就 Hook UILabel 的-initWithFrame: / dealloc方法,打印其地址、堆棧、父視圖鏈條等消息,觸發(fā) zombie 時根據(jù)地址找到對應(yīng)的信息:
dealloc 時 UILabel:0x13bb30d90, 調(diào)用棧:( 0 CustomApp 0x0000000102879b0c -[UIView(QBCrashFix) qbCrashFix_dealloc] + 136 1 CustomApp 0x0000000103d8a3e0 -[UILabel(Foo) dealloc] + 96 2 libobjc.A.dylib 0x00000001aafdd5d8 B3A78098-C0FB-3DCD-B1AC-0712762510DB + 5592 3 libobjc.A.dylib 0x00000001aafe0f40 objc_autoreleasePoolPop + 256 4 CoreFoundation 0x00000001b1ca6a74 _CFAutoreleasePoolPop + 32 5 CoreFoundation 0x00000001b1cb93f8 42C5C917-0447-3995-B50F-DE4D132C2435 + 594936
發(fā)現(xiàn)了罪魁禍首:
@implementation UILabel (Foo)
…
- (void)dealloc {
objc_setAssociatedObject(…);
#if !__has_feature(objc_arc)
[super dealloc];
#endif
}
@end
這是在分類里面的定義,相當于把-[UILabel dealloc]方法調(diào)用跳過了,看下其實現(xiàn):
UIKitCore`-[UILabel dealloc]:
…
0x1b3ed0624 <+52>: bl 0x1b54ad680 ; objc_msgSend$defaultCenter
0x1b3ed0628 <+56>: bl 0x1b75b7840
0x1b3ed062c <+60>: mov x20, x0
0x1b3ed0630 <+64>: ldr x2, [x19, x21]
0x1b3ed0634 <+68>: bl 0x1b544b400 ; objc_msgSend$_removeObserver:
…
發(fā)現(xiàn)內(nèi)部有移除 Notification 的邏輯,由于跳過了這些指令導(dǎo)致通知中心該 observer 未被移除引發(fā) Crash。
這個文件是直接 copy 的開源代碼,導(dǎo)致了全局-[UILabel dealloc]走不到,再次證明了無腦 copy 代碼的危害性。
另外,比較好奇的點是該場景通知中心對 UILabel 的引用似乎不是弱引用。
通知中心是否一定弱引用 observer
直接 Debug 發(fā)現(xiàn)在 -[UILabel initWithFrame:] 中會直接調(diào)用到底層接口注冊 Notification:

在 CFXNotificationRegistrarAdd 時參數(shù)情況是:
(lldb) po $x0 <CFXNotificationRegistrar 0x11aa05cf0 [0x2091ba3a0]> (lldb) po $x1 UIAccessibilityButtonShapesEnabledStatusDidChangeNotification (lldb) po $x3 <UILabel: 0x146f56920…>
確實和 Crash 時的通知一致。
斷點到 -[UILabel initWithFrame:] 結(jié)束時去看一下該對象的 isa 情況:
(lldb) x 0x146f56920 0x146f56920: bb 90 e7 07 02 00 00 01 00 00 00 00 00 00 00 00 ................ 0x146f56930: 00 00 00 00 00 00 00 00 30 0a 08 3f 01 00 00 00 ........0..?.... (lldb) p/t 0x0100000207e790bb (long) $4 = 0b0000000100000000000000000000001000000111111001111001000010111011
發(fā)現(xiàn) isa 第三個位也就是 weakly_referenced 的值為0,說明底層注冊 Notification 接口并不會像 -[NSNotificationCenter addObserver:selector:name:object:] 一樣對 observer 弱引用,所以需要在 dealloc 手動移除通知。
以上就是libAccessibility通知Crash排查記錄分析的詳細內(nèi)容,更多關(guān)于libAccessibility通知Crash排查的資料請關(guān)注腳本之家其它相關(guān)文章!

