<div class="article-content">
<h1 class="heading">成因</h1>
<p>野指针就是指向一个已删除的对象或者受限内存区域的指针。 我们写C++的时候强调指针初始化为NULL,强调用完后也为其赋值为NULL,谁分配的谁回收,来避免野指针的问题。 比较常见的就是这个指针指向的内存,在别处被回收了,但是这个指针不知道,依然还指向这块内存。 MRC 时代因为引用计数手动控制,所以内存很容易在别处被回收。ARC解决了大部分这种问题。、 在iOS9之前,系统库的<code>delegate</code>和<code>target-action</code>有一部分是<code>assign(unsafe_unretain)</code>的形式,这时候如果内存在别处被回收了,也是会出现野指针的。 所以iOS9之后这些地方就改成了weak内存修饰符,内存被回收的时候通过weak表,把这些指针设为nil。也大幅度减少了野指针的出现。</p>
<p>如果现在在工程中依然频繁出现野指针,几乎可以肯定是错误地使用了内存。</p>
<h1 class="heading">表现:Crash</h1>
<p>对于<code>Mach</code>、<code>Unix</code>、<code>NSException</code>三种不同层级的crash,NSException比较好说,可以直接定位到OC代码。问题主要来自<code>EXC_BAD_ACCESS(SIGSEGV)</code>这种异常,难以在我们的应用代码中定位。</p>
<p></p>
<figure>
<figcaption></figcaption>
</figure>
<p></p>
<ul><li>SIGILL 执行了非法指令,一般是可执行文件出现了错误</li><li>SIGTRAP 断点指令或者其他trap指令产生</li><li>SIGABRT 调用abort产生</li><li>SIGBUS 非法地址。比如错误的内存类型访问、内存地址对齐等</li><li>SIGSEGV 非法地址。访问未分配内存、写入没有写权限的内存等</li><li>SIGFPE 致命的算术运算。比如数值溢出、NaN数值等</li></ul>
<p>实际我们遇到<code>Mach Exception</code>绝大部分都是野指针的问题。SIGSEGV/SIGABRT/SIGTRAP 比较多见。 野指针问题表现千奇百怪,而且因为崩溃的地方并不是造成野指针的地方,而且难以重现,所以问题往往难以定位。 </p>
<figure>
<figcaption></figcaption>
</figure> 腾讯Bugly的这张图可以看到,野指针几乎可以造成各种类型的
<code>Mach Exception</code>。
<p></p>
<h1 class="heading">定位工具</h1>
<h2 class="heading">Zoombie Object</h2>
<p>这是目前帮助最大的调试模式。实现原理就是 hook 住了对象的dealloc方法,通过调用自己的<code>__dealloc_zombie</code>方法来把对象进行僵尸化。</p>
<pre class="blockcode"><code class="hljs c copyable"><span class="hljs-function">id <span class="hljs-title">object_dispose</span><span class="hljs-params">(id obj)</span>
</span>{
<span class="hljs-keyword">if</span> (!obj) <span class="hljs-keyword">return</span> nil;
objc_destructInstance(obj);
<span class="hljs-built_in">free</span>(obj);
<span class="hljs-keyword">return</span> nil;
}
<span class="copy-code-btn">复制代码</span></code></pre>
<p>正常的对象释放方法如上,但是僵尸对象调用了<code>objc_destructInstance</code>后就直接return了,不再<code>free(obj);</code>。同时生成一个<code>"_NSZombie_" + clsName</code>类名,调用<code>objc_setClass(self, zombieCls);</code>修改对象的 isa 指针,令其指向特殊的僵尸类。 如果这个对象再次收到消息,<code>objc_msgsend</code>的时候,调用abort()崩溃并打印出调用的方法。</p>
<p>野指针指向的内存没有被覆盖的时候,或者被覆盖成可以访问的内存的时候,不一定会出现崩溃。这个时候向对象发送消息,不一定会崩溃(可能刚好有这个方法),或者向已经释放的对象发送消息。 但是如果野指针指向的是僵尸对象,那就一定会崩溃了,会崩溃在僵尸对象第一次被其它消息访问的时候。</p>
<h4 class="heading">Zombie Object without Xcode</h4>
<p>僵尸对象必须在连接Xcode中debug的时候使用,如果我们想跟我们的崩溃收集工具集成在一起,就需要自己实现类似Zombie Object的东西。 逻辑是通过hook住NSObject的根类的dealloc方法,然后在新的dealloc方法中将本来即将释放的对象的isa指针改为指向我们创建的一个新的僵尸类。</p>
<p><a href="https://link.juejin.im?target=https%3A%2F%2Fsummer20140803.github.io%2F2017%2F12%2F25%2FiOS%25E4%25BD%25BF%25E7%2594%25A8%25E4%25BB%25A3%25E7%25A0%2581%25E6%258E%2592%25E6%259F%25A5%25E9%2587%258E%25E6%258C%2587%25E9%2592%2588%25E9%2594%2599%25E8%25AF%25AF%2F">iOS使用代码排查野指针错误 </a>和 <a href="https://link.juejin.im?target=https%3A%2F%2Fblog.csdn.net%2Fwithout2002%2Farticle%2Fdetails%2F43342675">开发自己的NSZombie</a>这两篇文章里介绍了在代码里实现类似Zoombie Object的方法,然而实际上是无法使用的,这两种实现跟 Zombie Object 实现上不小的区别,实际应用中有大量误判的情况。</p>
<p>误判的原因主要是dealloc的实现和僵尸类的实现跟Zombie Object不一样。 参考Apple的源码 |
|