头条稳定性治理:ARC 环境中对 Objective-C 对象赋值的 Crash 隐患

动手点关注

7e80284de905b63610cad49d5682b2f7.gif

干货不迷路

ARC 环境下在多线程中执行赋值代码可能会产生野指针,导致 EXC_BAD_ACCESS 崩溃。

这种崩溃发生的概率很低,在开发和灰度阶段即使执行到相应代码也很难崩溃,因此容易遗漏到正式环境。在上亿级用户的 App 往往会成为 Top 问题,对指标造成影响,并且很难排查。

今日头条在治理 Crash 的过程中彻底解决了数十个此类崩溃,发现其具有一定共性。本文详细分析崩溃发生的过程,以及总结了容易出现问题的场景,希望在大家遇到此类问题时能提供一些思路。

1. 原理

Objective-C 对象的赋值过程包含创建新值、保留旧值、加载新值、释放旧值四步。相比 MRC,ARC 环境中编译器会自动插入保留与释放旧值的步骤:

NSObject *_instance;
void foo(void) {
    _instance = [[NSObject alloc] init];
}

44a51141d5d84885f05dfe2785c64664.png

973b7bcbe69eae31c72f41eab071016e.png

这点在 AutomaticReferenceCounting [1] 文档中有提到,通过汇编代码也可以分析:

1491ccdf53eb2626abb25a627d30fdee.png

3b44923e27f00f3051d868e2898f9e4a.png

objc_release 会减小对象的引用计数,减小到 0 时对象就会被销毁,假如这时有其它线程正在使用这个对象,那么使用对象的线程就很可能发生崩溃。

2. 崩溃场景

为了演示仅一行赋值代码就能造成崩溃,以及清晰地分析崩溃的原因,我设计了一个 Demo,在 B 线程中释放 A 线程创建的对象使 C 线程崩溃:

c0911e70b853ee6aeac8e2edecd67f3d.png

复现过程:

a8169e80e2159d36ab1978c8f713e17e.png

  1. A、B、C 三个线程同时进入 foo 函数

  2. A 线程先创建初始值 _instance

    A 线程执行到 _instance = x0, 创建了新值并赋给 _instance;此时 _instance 引用计数为 1;

  3. B、C 线程读取到 A 线程创建的初始值 _instance

    B、C 线程分别执行到 x1 = _instance 时,从 _instance 中读到线程 A 创建的对象,保存到各自的上下文中;_instance 引用计数仍为 1;

  4. B 线程释放 _instance

    B 线程执行 objc_release(x1) 后会释放 _instance;_instance 引用计数变为 0,被销毁;

  5. C 线程访问 _instance

    C 线程执行到 objc_release(x1) 时访问 _instance;由于 _instance 已经被销毁,访问时会发生崩溃。

16d1976753c73c7db83f49ada4bfea44.gif

使用 lldb 的 thread continue 指令 [2] 来控制整个流程,它可以仅让一个线程执行,其它线程保持挂起。

  1. 3 个线程同时进入 foo 函数

    操作步骤:在 foo 函数里面打上断点,可以多次测试让 3 个线程同时进入断点。

    如图,线程 2 3 4 同时进入了 foo 函数:

    814dc843019720497d84f4194503525b.png

  2. 线程 2 执行到 _instance = x0,创建初始值并赋给 _instance

    操作步骤:在 Thread 2 中给汇编代码第 10 行打断点,执行 thread continue,使 Thread 2 执行完 _instance = x0。

    可以看到 Thread 2 创建的实例为 0x000000002813e400:

    68b0fb67c07e67ab32067af1abe836b9.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值