iOS小筑iOSiOS基本功

iOS多线程读写崩溃分析

2017-05-06  本文已影响887人  vedon_fu

最近再次遇到多线程读写导致的crash 问题,写了一个测试demo,记录分析过程。

 for (int i = 0; i < 10000; i++)
    {
        
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            self.object = [TestObject new];            
        });
       dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            self.object = [TestObject new];
        });
    }

上面是暴力重现多线程读写的崩溃,在debug环境下,开启zombie ,窗口会输出:

 message sent to deallocated instance 0x170200c50

上面用了10000次碰撞才触发崩溃,日常debug 环境下很难出现。但是到了线上环境,用户量一大,问题就出现了。然后,我们只能通过崩溃日志查找崩溃。

下面截取有用的崩溃日志部分:

Incident Identifier: A22F5FFF-F98D-4F3B-95C3-45790E61F049
CrashReporter Key:   33c3939d695bcfab6c9a16efca18399fae8a83c3
Hardware Model:      iPhone6,2
Process:             Crash_mulThread [716]
Path:                /private/var/containers/Bundle/Application/7CCB0B27-4B51-4D77-B571-A49153C8E8B7/Crash_mulThread.app/Crash_mulThread
Identifier:          vedon.Crash-mulThread
Version:             1 (1.0)
Code Type:           ARM-64 (Native)
Role:                Foreground
Parent Process:      launchd [1]
Coalition:           vedon.Crash-mulThread [1266]


Date/Time:           2017-05-05 23:58:50.9184 +0800
Launch Time:         2017-05-05 23:58:50.6346 +0800
OS Version:          iPhone OS 10.2.1 (14D27)
Report Version:      104

Exception Type:  EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x00000003a42abec8
Termination Signal: Segmentation fault: 11
Termination Reason: Namespace SIGNAL, Code 0xb
Terminating Process: exc handler [0]
Triggered by Thread:  4

Filtered syslog:
None found

Thread 4 name:  Dispatch queue: com.apple.root.default-qos
Thread 4 Crashed:
0   libobjc.A.dylib                 0x0000000184f48894 objc_class::demangledName(bool) + 28
1   libobjc.A.dylib                 0x0000000184f5bc04 objc_object::overrelease_error() + 24
2   libobjc.A.dylib                 0x0000000184f5bc04 objc_object::overrelease_error() + 24
3   Crash_mulThread                 0x00000001000e03f4 -[ViewController setObject:] (ViewController.m:13)
4   Crash_mulThread                 0x00000001000e0148 __29-[ViewController viewDidLoad]_block_invoke (ViewController.m:29)
5   libdispatch.dylib               0x00000001853921fc _dispatch_call_block_and_release + 24
6   libdispatch.dylib               0x00000001853921bc _dispatch_client_callout + 16
7   libdispatch.dylib               0x00000001853a0a4c _dispatch_queue_override_invoke + 732
8   libdispatch.dylib               0x00000001853a234c _dispatch_root_queue_drain + 572
9   libdispatch.dylib               0x00000001853a20ac _dispatch_worker_thread3 + 124
10  libsystem_pthread.dylib         0x000000018559b2a0 _pthread_wqthread + 1288
11  libsystem_pthread.dylib         0x000000018559ad8c start_wqthread + 4


Thread 4 crashed with ARM Thread State (64-bit):
    x0: 0x00000003a42abea8   x1: 0x0000000000000000   x2: 0x000000017401a1f0   x3: 0x000000017401a200
    x4: 0x00000001700f0e00   x5: 0x0000000000000000   x6: 0x0000000000000000   x7: 0x0000000000000000
    x8: 0xbaddf653a42abead   x9: 0x000009a1000e5665  x10: 0xffffe9a1000e5665  x11: 0x000000330000007f
   x12: 0x000000010101e110  x13: 0x000005a1000e554d  x14: 0x00000001a597a340  x15: 0x0000000000397c01
   x16: 0x0000000184f580f4  x17: 0x00000001000e03b4  x18: 0x0000000000000000  x19: 0x0000000170019be0
   x20: 0x00000003a42abea8  x21: 0x0000000000000000  x22: 0x0000000000000000  x23: 0x00000001aa54d200
   x24: 0x000000016e1bf0e0  x25: 0x00000001abc326c0  x26: 0x0000000000000014  x27: 0x0000000000000004
   x28: 0xffffffffffffffff   fp: 0x000000016e1bed10   lr: 0x0000000184f5bc04
    sp: 0x000000016e1becd0   pc: 0x0000000184f48894 cpsr: 0x80000000

Binary Images:
0x1000d8000 - 0x1000e3fff Crash_mulThread arm64  <e2e3d2adf95930e19b6da09621898c31> /var/containers/Bundle/Application/7CCB0B27-4B51-4D77-B571-A49153C8E8B7/Crash_mulThread.app/Crash_mulThread
0x1001dc000 - 0x10020bfff dyld arm64  <f54ed85a94253887886a8028e20ed8ba> /usr/lib/dyld
0x184ebc000 - 0x184ebdfff libSystem.B.dylib arm64  <1b4d75209f4a37969a9575de48d48668> /usr/lib/libSystem.B.dylib
0x184ebe000 - 0x184f13fff libc++.1.dylib arm64  <b2db8b1d09283b7bafe1b2933adc5dfd> /usr/lib/libc++.1.dylib
0x184f14000 - 0x184f34fff libc++abi.dylib arm64  <e3419bbaface31b5970c6c8d430be26d> /usr/lib/libc++abi.dylib
0x184f38000 - 0x185311fff libobjc.A.dylib arm64  <538f809dcd7c35ceb59d99802248f045> /usr/lib/libobjc.A.dylib

FYI

SIGSEGV 访问了非法的地址(地址还没有从系统映射到当前进程的内存空间), 一般是野指针导致, 而野指针一般由于多线程操作对象导致.
SIGABRT 一般是Exception或者其他的代码主动退出的问题.
SIGTRAP 代码里面触发了调试指令, 该指令可能由编译器提供的trap方法触发, 如'__builtin_trap()'
SIGBUS 一般由于地址对齐问题导致, 单纯的OC代码挺难触发的, 主要是系统库方法或者其他c实现的方法导致
SIGILL 表示执行了非法的cpu指令, 但是一般是由于死循环导致

通过崩溃日志,定位到崩溃的点在:

0   libobjc.A.dylib                   0x0000000184f48894 objc_class::demangledName(bool) + 28
1   libobjc.A.dylib                   0x0000000184f5bc04 objc_object::overrelease_error() + 24
2   libobjc.A.dylib                   0x0000000184f5bc04 objc_object::overrelease_error() + 24
3   Crash_mulThread                   0x00000001000e03f4 -[ViewController setObject:] (ViewController.m:13)

每条崩溃堆栈的记录称为frame ,每个frame 都有一个编号,它是当前frame 在整个调用栈的索引。看到frame 3 是demo代码调用的地方,当前pc 地址** 0x0000000184f48894** 对应frame 0 调用地址,而其他的frame 都是历史记录,不会保存当前frame所有寄存器的值,只存了lr 寄存器的内容(FYI: lr 是方法调用完之后,要返回的地址)。

从frame 2 就可以知道,对象被over release 了。实际情况一般是:丢失重要的堆栈信息。下面纯粹是在只有frame 3 的堆栈下,怎么定位问题。

frame 3 ,只有一个 setObject:也就是: self.object = [TestObject new]; 咋一看,不怎么可能崩溃。下面来分析一下:

可以看到堆栈地址是: 0x00000001000e03f4,程序加载到内存的地址在0x1000d8000 - 0x1000e3fff 之间。

通过计算 0x00000001000e03f4 - 0x1000d8000 = 0x83F4。

0x83F4 为相对偏移,这时候使用hopper 看看在0x83F4 究竟是什么。

Screen Shot 2017-05-06 at 12.40.14 AM.png

frame 3 的lr 寄存器保存了调用方法的下一个指令地址,那么可以确定崩溃发生在:imp___stubs__objc_storeStrong,下面分析一下这段汇编做了什么。

00000001000083b4         sub        sp, sp, #0x30                               ; Objective C Implementation defined at 0x10000c478 (instance method), DATA XREF=0x10000c478
00000001000083b8         stp        x29, x30, [sp, #0x20]
00000001000083bc         add        x29, sp, #0x20
// 保存方法调用的现场

00000001000083c0         adrp       x8, #0x10000d000
00000001000083c4         add        x8, x8, #0x538                              ; _OBJC_IVAR_$_ViewController._object
// 动态定位获取ViewController._object的描述地址, 放入x8

00000001000083c8         stur       x0, [x29, #-0x8]
00000001000083cc         str        x1, [sp, #0x10]
00000001000083d0         str        x2, [sp, #0x8]
// 把参数self/selector/传进来的TestObject对象, 存到栈里

00000001000083d4         ldr        x0, [sp, #0x8]
00000001000083d8         ldur       x1, [x29, #-0x8]
00000001000083dc         ldrsw      x8, x8
00000001000083e0         add        x8, x1, x8
// 从x8里把_object的在ViewController对象的偏移量取出来, 并与x1相加, 也就是`self指针+偏移量`, 结果存在x8 里面

00000001000083e4         str        x0, sp
// 把传进来的对象存入栈
00000001000083e8         mov        x0, x8
// 把`self指针+偏移量`指针放入x0
00000001000083ec         ldr        x1, sp
// 把传进来的对象从栈里取出来放到x1
00000001000083f0         bl         imp___stubs__objc_storeStrong
// 把x1里传进来的对象赋值给x0, 然后强引用一次
00000001000083f4         ldp        x29, x30, [sp, #0x20]
00000001000083f8         add        sp, sp, #0x30
// 恢复最前面保存的现场
00000001000083fc         ret
// 返回

上面其实就是一段setter 的代码,崩溃发生在imp___stubs__objc_storeStrong,通过查看苹果开源代码:objc_storeStrong

void
objc_storeStrong(id *location, id obj)
{
    id prev = *location;
    if (obj == prev) {
        return;
    }
    objc_retain(obj);
    *location = obj;
    objc_release(prev);
}

objc_storeStrong 并不是原子性操作,当线程A可能执行到*location = obj 时,另外一个线程B执行 prev = *location; 。那么当线程A继续执行到objc_release(prev); 线程B 继续执行 ,跑到objc_release(prev), 此刻,prev已经被释放过了。Crash ~~~

上一篇下一篇

猜你喜欢

热点阅读