iOS多线程读写崩溃分析

最近再次遇到多线程读写导致的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 ~~~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容