方片四 發表於 2022-11-30 10:55:31

libAccessibility通知Crash排查记录分析

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li>Crash 信息</li><li>复现场景</li><li>简单引用分析</li><li>寻找 Crash 对象</li><li>通知中心是否一定弱引用 observer</li></ul></div><p class="maodian"></p><h2>Crash 信息</h2>
<div class="cros igoods"><div class="goodsin" data-img="https://img14.360buyimg.com/pop/g15/M07/03/07/rBEhWlLeHvAIAAAAAAF63V5AyGcAAIFhAP-UgwAAXr1206.jpg" data-name="汇编语言(第2版)/高等院校精品课程系列教材" data-owner="京东自营" data-price="28.4" data-tgid="38" data-url="https://union-click.jd.com/jdc?e=&amp;p=JF8BAMkJK1olXwUCVFxaDE4XBV8IG1kcVAMFXG4ZVxNJXF9RXh5UHw0cSgYYXBcIWDoXSQVJQwYCVldUDUwfHDZNRwYlPV1CMzkZEk50UTsLeANdIHFfSj4lTkcbM2gNHF4dXwMBZF5eDkwXAmoIK2sVXDZQOobrvpOysnPcsdTA1ZEyVW5dD0wfBWkOGlsXVQ8EZF5VDHtUVypcWBhdbTYyV25tOEsnAF9WdVpGWwUCUFcPZhZEAWwNThlUMwAGXF9VAEwXM20JGlkXbTY"></div></div>
<div class="jb51code"><pre class="brush:bash;">Last Exception :
0libobjc.A.dylib                0x00000001bee86f40 _objc_msgSend +32
1CoreFoundation               0x00000001a6132834 ___CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ +28
2CoreFoundation               0x00000001a61cefd4 ____CFXRegistrationPost_block_invoke +52
3CoreFoundation               0x00000001a61a21d0 __CFXRegistrationPost +456
4CoreFoundation               0x00000001a61488ac __CFXNotificationPost +728
5Foundation                     0x00000001a7917754 - +96
6CoreFoundation               0x00000001a6132834 ___CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ +28
7CoreFoundation               0x00000001a61cefd4 ____CFXRegistrationPost_block_invoke +52
8CoreFoundation               0x00000001a61a21d0 __CFXRegistrationPost +456
9CoreFoundation               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 - +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 - 0x1a9e43fffarm64 &lt;309485b32e2c3803ae988866d303d7fd&gt; libAccessibility.dylib
</pre></div>
<p>libAccessibility 在发送通知时产生了 Crash。</p>
<p class="maodian"></p><h2>复现场景</h2>
<p>在某些路径可以复现 Crash:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202211/20221130091010020.jpg" /></p>
<p>这里取出对象 isa 中的 class 对象 PAC 验签后使用,在 _objc_msgSend + 32 寻址时 Crash,是典型的对象内存管理异常问题。</p>
<p>但按对通知中心的认知,会对 observer 弱引用,post 时不应该出现释放后引用指针未置 nil 的情况。先顺便回溯分析一下调用栈。</p>
<p class="maodian"></p><h2>简单引用分析</h2>
<p><strong>CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER</strong> 会跳转到入参 block 的 invoke 函数:</p>
<div class="jb51code"><pre class="brush:bash;">Foundation`__66-_block_invoke:
    0x1a8531d0c &lt;+0&gt;:mov    x2, x1
    0x1a8531d10 &lt;+4&gt;:ldp    x8, x1,
    0x1a8531d14 &lt;+8&gt;:mvn    x0, x8
    0x1a8531d18 &lt;+12&gt;: b      0x1a69cadb8
</pre></div>
<p>这个函数看起来就很熟悉了,在我们常规的添加通知接口产生。 block 的 invoke 函数第一个参数是 block 本身,&lt;+4&gt; 处是取出该 block 的两个引用对象,他们分别是:</p>
<div class="jb51code"><pre class="brush:bash;">&lt;UILabel: 0x12abb49c0…&gt;
_accessibilityButtonShapesChangedNotification:
</pre></div>
<p>后续就是调用 objc_msgSend 发送消息了。 接着看一下 block 对该 observer 的引用类型,最简单的方法就是查看该 block 的 dispose 函数指令,里面会对引用的 strong 对象 release、weak 对象 storeWeak,把 block 内存消息打印出来:</p>
<div class="jb51code"><pre class="brush:bash;">(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 000...............
0x281ae8fa0: 34 42 4a a8 01 00 00 00 e0 02 95 ff 01 00 00 004BJ.............
</pre></div>
<p>flags 记录了 block 变量的各种信息,处于 block 第二个 8 字节<code>0xc1000006</code>,其 24 位为 1 说明在堆上,25 位为 0 说明没有 dispose/copy 函数,所以这个 block 对 observer 相当于 assign 引用。</p>
<p>有些奇怪,向上分析成本太高,直接开启 Zombie 试图去找到这个 Crash 的对象及其产生时机。</p>
<p class="maodian"></p><h2>寻找 Crash 对象</h2>
<p>开启 Zombie 后果然轻松复现:</p>
<div class="jb51code"><pre class="brush:bash;">-: 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
</pre></div>
<p>Zombie 机制大概是在对象 dealloc 时把对象的 isa 类部分指向一个新的类(比如这里的 _NSZombie_UILabel),后续再向该对象发送消息就会被拦截下来报错,所以对象地址不会变。</p>
<p>那就 Hook UILabel 的<code>-initWithFrame: / dealloc</code>方法,打印其地址、堆栈、父视图链条等消息,触发 zombie 时根据地址找到对应的信息:</p>
<div class="jb51code"><pre class="brush:bash;">dealloc 时 UILabel:0x13bb30d90, 调用栈:(
        0   CustomApp                           0x0000000102879b0c - + 136
        1   CustomApp                           0x0000000103d8a3e0 - + 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
</pre></div>
<p>发现了罪魁祸首:</p>
<div class="jb51code"><pre class="brush:bash;">@implementation UILabel (Foo)

- (void)dealloc {
    objc_setAssociatedObject(…);
#if !__has_feature(objc_arc)
    ;
#endif
}
@end
</pre></div>
<p>这是在分类里面的定义,相当于把<code>-</code>方法调用跳过了,看下其实现:</p>
<div class="jb51code"><pre class="brush:bash;">UIKitCore`-:

    0x1b3ed0624 &lt;+52&gt;:bl   0x1b54ad680               ; objc_msgSend$defaultCenter
    0x1b3ed0628 &lt;+56&gt;:bl   0x1b75b7840
    0x1b3ed062c &lt;+60&gt;:mov    x20, x0
    0x1b3ed0630 &lt;+64&gt;:ldr    x2,
    0x1b3ed0634 &lt;+68&gt;:bl   0x1b544b400               ; objc_msgSend$_removeObserver:

</pre></div>
<p>发现内部有移除 Notification 的逻辑,由于跳过了这些指令导致通知中心该 observer 未被移除引发 Crash。</p>
<p>这个文件是直接 copy 的开源代码,导致了全局<code>-</code>走不到,再次证明了无脑 copy 代码的危害性。</p>
<p>另外,比较好奇的点是该场景通知中心对 UILabel 的引用似乎不是弱引用。</p>
<p class="maodian"></p><h2>通知中心是否一定弱引用 observer</h2>
<p>直接 Debug 发现在 - 中会直接调用到底层接口注册 Notification:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202211/20221130091010021.jpg" /></p>
<p>在 CFXNotificationRegistrarAdd 时参数情况是:</p>
<div class="jb51code"><pre class="brush:bash;">(lldb) po $x0
&lt;CFXNotificationRegistrar 0x11aa05cf0 &gt;
(lldb) po $x1
UIAccessibilityButtonShapesEnabledStatusDidChangeNotification
(lldb) po $x3
&lt;UILabel: 0x146f56920…&gt;
</pre></div>
<p>确实和 Crash 时的通知一致。</p>
<p>断点到 - 结束时去看一下该对象的 isa 情况:</p>
<div class="jb51code"><pre class="brush:bash;">(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
</pre></div>
<p>发现 isa 第三个位也就是 weakly_referenced 的值为0,说明底层注册 Notification 接口并不会像 - 一样对 observer 弱引用,所以需要在 dealloc 手动移除通知。</p>
<p>以上就是libAccessibility通知Crash排查记录分析的详细内容,更多关于libAccessibility通知Crash排查的资料请关注琼殿技术社区其它相关文章!</p>
                           
                            <div class="art_xg">
                              <b>您可能感兴趣的文章:</b><ul><li>Android Java crash 处理流程详解</li><li>Swift踩坑实战之一个字符引发的Crash</li><li>CrashRpt使用案例详解</li><li>Android app会crash的原因及解决方法</li><li>iOS中程序异常Crash友好化处理详解</li><li>iOS监控笔记之启动crash</li><li>os_object_release&nbsp;Crash&nbsp;排查记录分析</li></ul>
                            </div>

                        </div>
                        <!--endmain-->
頁: [1]
查看完整版本: libAccessibility通知Crash排查记录分析