果脯 發表於 2022-11-30 11:02:26

os_object_release Crash 排查记录分析

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li>Crash 信息</li><li>确认目标对象类型</li><li>定位 Crash 场景</li></ul></div><p class="maodian"></p><h2>Crash 信息</h2>
<div class="cros igoods"><div class="goodsin" data-img="https://img14.360buyimg.com/pop/jfs/t26347/205/518997859/496403/747e657b/5bb01b96N9ce780d0.jpg" data-name="80x86汇编语言程序设计(第2版)/高等院校规划教材·计算机科学与技术系列" data-owner="京东自营" data-price="38.7" data-tgid="38" data-url="https://union-click.jd.com/jdc?e=&amp;p=JF8BAMkJK1olXwUCVFxaDE4XBV8IGF4QWAQAV24ZVxNJXF9RXh5UHw0cSgYYXBcIWDoXSQVJQwYBUVtYCkkUHDZNRwYlNXRJVEQpdTVyGRELSTAPIAYCVAY6aEcbM2gNHF4dXwMBZF5eDkwXAmoIK2sVXDZQOobrvpOysnPcsdTA1ZEyVW5dD0wfBWkOH1gUXQcLZF5VDHtUVypcWBhdbTYyV25tOEsnAF9WdVpGWwQDBwoIZhZEAWxfEghLMwEAUVZVDkkTM20JGlkXbTY"></div></div>
<p>线上存在一个持续很久的 Crash,由于没有明确业务栈且量级不算大,让它成为了老赖之一,Crash 栈是这样的:</p>
<div class="jb51code"><pre class="brush:bash;">Thread 55
0libdispatch.dylib            0x0000000188a8cf8c __os_object_release_internal_n +80
1libdispatch.dylib            0x0000000188a96eec __dispatch_lane_invoke +1152
2libdispatch.dylib            0x0000000188aa14bc __dispatch_workloop_worker_thread +764
3libsystem_pthread.dylib      0x00000001d4bde7a4 __pthread_wqthread +276
——-
Exception Type: SIGTRAP
Exception Codes: fault addr: 0x0000000188a8cf8c
Crashed Thread: 55
Thread 55 crashed with ARM Thread State (64-bit):
    x0:0x0000000281a86580    x1:0x0000000000000002
0x188a8a000 - 0x188acefffarm64 &lt;ff408738d75b3061ad994a929c0162d2&gt; libdispatch.dylib
</pre></div>
<p>由于不能明确是哪个业务代码引起的,所以先确认 Crash 的对象是哪个类型。</p>
<p class="maodian"></p><h2>确认目标对象类型</h2>
<p>Crash 日志看不出来目标对象类型,只知道是一个 SIGTRAP,应该是 GCD 调用<code>__builtin_trap()</code>触发软中断结束进程 ,尝试从源码入手,顶层函数逻辑是这样的:</p>
<div class="jb51code"><pre class="brush:bash;">DISPATCH_NOINLINE
void _os_object_release_internal_n(_os_object_t obj, uint16_t n) {
        return _os_object_release_internal_n_inline(obj, n);
}
DISPATCH_ALWAYS_INLINE
static inline void _os_object_release_internal_n_inline(_os_object_t obj, int n)
{
        int ref_cnt = _os_object_refcnt_sub(obj, n);
        if (likely(ref_cnt &gt;= 0)) {
                return;
        }
        if (unlikely(ref_cnt &lt; -1)) {
                _OS_OBJECT_CLIENT_CRASH("Over-release of an object");
        }
        // _os_object_refcnt_dispose_barrier() is in _os_object_dispose()
        return _os_object_dispose(obj);
}
</pre></div>
<p><code>_OS_OBJECT_CLIENT_CRASH()</code>就是调用的<code>__builtin_trap()</code>,那确认就是一个<code>os_object_t</code>对象的 Over-Release 问题了。<code>os_object_t</code>定义是这样的:</p>
<div class="jb51code"><pre class="brush:bash;">typedef struct _os_object_s {
        _OS_OBJECT_HEADER(
        const _os_object_vtable_s *os_obj_isa,
        os_obj_ref_cnt,
        os_obj_xref_cnt);
} _os_object_s;
typedef struct _os_object_s *_os_object_t;
</pre></div>
<p>这就是 GCD 类的结构体定义,和 NSObject 类似的内存布局,但<code>os_object_t</code>衍生类众多还需明确是哪一个。</p>
<p>继续看上一个函数<code>_dispatch_lane_invoke</code>,发现它的代码量很大,且由于 GCD 大量的 inline 函数,很难确定是哪里调用了<code>_os_object_release_internal_n</code>。这个时候就要换一种方式,直接反汇编就能快速确认。</p>
<p>使用和 Crash 栈相同系统设备切 release 环境运行,但有点奇怪的是反汇编代码和<code>_dispatch_lane_invoke</code>偏移对不上。那就用 hopper 直接打开 uuid 对应的 libdispatch.dylib 可执行文件吧,找到偏移处:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202211/20221130091010022.png" /></p>
<p>接下来就要确认<code>bl _os_object_release_internal_n</code>时<code>x0</code>寄存器值怎么来的,这个函数一千多行指令分析工作量太大,但这里可以明确的是这个函数只有这一处调用 <code>_os_object_release_internal_n</code>。</p>
<p>那又回到 GCD 源码,估计就是尾部的一个调用了(代码有修改,去除无用代码和 inline 调用):</p>
<div class="jb51code"><pre class="brush:bash;">_dispatch_lane_invoke(…) {
        dispatch_queue_t dq = dqu._dq;
        …
        return _os_object_release_internal_n(dou._os_obj, 2);
}
</pre></div>
<p>翻了一下各个 Crash 日志<code>x1</code>寄存器都是 2 可以对得上。同时运行时反汇编指令虽然对不上,但对比找到同样逻辑的汇编代码段,<code>br</code>到这个偏移也能确认<code>x0</code>就是<code>dispatch_queue_t</code>。</p>
<p class="maodian"></p><h2>定位 Crash 场景</h2>
<p>既然产生 Over-Release 的对象是 <code>dispatch_queue_t</code>,那推测就是业务代码使用时存在内存管理问题,最蠢的方式就是找到所有的<code>dispatch_queue_create()</code>调用排查各个场景是否有问题。</p>
<p>不过在这之前可以多看一下 Crash 日志,调用栈有<code>dispatch_workloop_worker_thread</code>可以推测当前时机是业务<code>block</code>加入了 GCD 队列,现在已经开始调度了。举个例子,如果在<code>dispatch_async(queue, block)</code>时 queue 就已经释放了,那 Crash 栈就会有<code>dispatch_async</code>,说明在调用<code>dispatch_async(queue, block)</code>时 queue 是正常的,在调度过程要结束时 queue 才被其它线程释放,立即走到<code>_dispatch_lane_invoke</code>的尾调用时才触发了 Over-Release。</p>
<p>那其它线程引起 queue 释放的时机和当前 Crash 时机应该很近,也就是说其它线程此时的堆栈大概率有释放这个<code>dispatch_queue_t</code>的调用,排查后发现基本上在另外一个线程都有这么一段调用栈:</p>
<div class="jb51code"><pre class="brush:bash;">9libdispatch.dylib            0x0000000188a8dfc0 - +56
10 AnyProject                     0x0000000107c9b724 - +164
11 AnyProject                        0x0000000107cbc10c - +76
</pre></div>
<p>那大概率问题就出在<code>AnySDKClass</code>,运行时找到其<code>dealloc</code>方法:</p>
<div class="jb51code"><pre class="brush:bash;">…
    0x107ddab88 &lt;+124&gt;: bl   0x109994540               ; symbol stub for: dispatch_sync
    0x107ddab8c &lt;+128&gt;: add    x0, x19, #0x10            ; =0x10
    0x107ddab90 &lt;+132&gt;: mov    x1, #0x0
    0x107ddab94 &lt;+136&gt;: bl   0x10999589c               ; symbol stub for: objc_storeWeak
    0x107ddab98 &lt;+140&gt;: ldr    x0,
    0x107ddab9c &lt;+144&gt;: str    xzr,
    0x107ddaba0 &lt;+148&gt;: bl   0x1099957e8               ; symbol stub for: objc_release
    0x107ddaba4 &lt;+152&gt;: ldr    x0,
    0x107ddaba8 &lt;+156&gt;: str    xzr,
    0x107ddabac &lt;+160&gt;: bl   0x1099957e8               ; symbol stub for: objc_release

</pre></div>
<p>断点到对应偏移<code>0x107ddabac</code>处,找到这个 queue 的类型:</p>
<div class="jb51code"><pre class="brush:bash;">br set -a 0x107ddabac
po $x0
&lt;OS_dispatch_queue_serial: anyName = { xref = 1, ref = 1, sref = 1, target = com.apple.root.default-qos.overcommit, width = 0x1, state = 0x001ffe2000000000, in-flight = 0}&gt;
</pre></div>
<p>那剩下的工作就是找到对应 SDK 源码,分析出这个 serial queue 的内存管理问题了。</p>
<p>以上就是os_object_release Crash 排查记录分析的详细内容,更多关于os_object_release Crash排查的资料请关注琼殿技术社区其它相关文章!</p>
                           
                            <div class="art_xg">
                              <b>您可能感兴趣的文章:</b><ul><li>libAccessibility通知Crash排查记录分析</li><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></ul>
                            </div>

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