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 在发送告诉时发生了 Crash。

复现场景

在某些路径可以复现 Crash:

记 libAccessibility 通知 Crash 排查

这儿取出目标 isa 中的 class 目标 PAC 验签后运用,在 _objc_msgSend + 32 寻址时 Crash,是典型的目标内存办理异常问题。

但按对告诉中心的认知,会对 observer 弱引证,post 时不应该出现开释后引证指针未置 nil 的状况。先顺便回溯剖析一下调用栈。

简单引证剖析

CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER 会跳转到入参 block 的 invoke 函数:

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

这个函数看起来就很熟悉了,在我们常规的增加告诉接口发生。 block 的 invoke 函数第一个参数是 block 自身,<+4> 处是取出该 block 的两个引证目标,他们分别是:

<UILabel: 0x12abb49c0…>
_accessibilityButtonShapesChangedNotification:

后续便是调用 objc_msgSend 发送音讯了。 接着看一下 block 对该 observer 的引证类型,最简单的办法便是检查该 block 的 dispose 函数指令,里边会对引证的 strong 目标 release、weak 目标 storeWeak,把 block 内存音讯打印出来:

(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 字节0xc1000006,其 24 位为 1 阐明在堆上,25 位为 0 阐明没有 dispose/copy 函数,所以这个 block 对 observer 相当于 assign 引证。

有些古怪,向上剖析本钱太高,直接敞开 Zombie 企图去找到这个 Crash 的目标及其发生时机。

寻觅 Crash 目标

敞开 Zombie 后公然轻松复现:

-[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),后续再向该目标发送音讯就会被拦截下来报错,所以目标地址不会变。

那就 Hook UILabel 的-initWithFrame: / dealloc办法,打印其地址、仓库、父视图链条等音讯,触发 zombie 时依据地址找到对应的信息:

dealloc 时 UILabel:0x13bb30d90, 调用栈:(
	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

发现了罪魁祸首:

@implementation UILabel (Foo)
…
- (void)dealloc {
    objc_setAssociatedObject(…);
#if !__has_feature(objc_arc)
    [super dealloc];
#endif
}
@end

这是在分类里边的定义,相当于把-[UILabel dealloc]办法调用跳过了,看下其完成:

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:

发现内部有移除 Notification 的逻辑,因为跳过了这些指令导致告诉中心该 observer 未被移除引发 Crash。

这个文件是直接 copy 的开源代码,导致了全局-[UILabel dealloc]走不到,再次证明了无脑 copy 代码的危害性。

别的,比较好奇的点是该场景告诉中心对 UILabel 的引证似乎不是弱引证。

告诉中心是否一定弱引证 observer

直接 Debug 发现在 -[UILabel initWithFrame:] 中会直接调用到底层接口注册 Notification:

记 libAccessibility 通知 Crash 排查

在 CFXNotificationRegistrarAdd 时参数状况是:

(lldb) po $x0
<CFXNotificationRegistrar 0x11aa05cf0 [0x2091ba3a0]>
(lldb) po $x1
UIAccessibilityButtonShapesEnabledStatusDidChangeNotification
(lldb) po $x3
<UILabel: 0x146f56920…>

的确和 Crash 时的告诉一致。

断点到 -[UILabel initWithFrame:] 结束时去看一下该目标的 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

发现 isa 第三个位也便是 weakly_referenced 的值为0,阐明底层注册 Notification 接口并不会像 -[NSNotificationCenter addObserver:selector:name:object:] 一样对 observer 弱引证,所以需要在 dealloc 手动移除告诉。