iOS多線程讀寫(xiě)崩潰分析

最近再次遇到多線程讀寫(xiě)導(dǎo)致的crash 問(wèn)題,寫(xiě)了一個(gè)測(cè)試demo厉碟,記錄分析過(guò)程喊巍。

 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];
        });
    }

上面是暴力重現(xiàn)多線程讀寫(xiě)的崩潰,在debug環(huán)境下墨榄,開(kāi)啟zombie ,窗口會(huì)輸出:

 message sent to deallocated instance 0x170200c50

上面用了10000次碰撞才觸發(fā)崩潰玄糟,日常debug 環(huán)境下很難出現(xiàn)。但是到了線上環(huán)境袄秩,用戶量一大阵翎,問(wèn)題就出現(xiàn)了逢并。然后,我們只能通過(guò)崩潰日志查找崩潰郭卫。

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

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 訪問(wèn)了非法的地址(地址還沒(méi)有從系統(tǒng)映射到當(dāng)前進(jìn)程的內(nèi)存空間), 一般是野指針導(dǎo)致, 而野指針一般由于多線程操作對(duì)象導(dǎo)致.
SIGABRT 一般是Exception或者其他的代碼主動(dòng)退出的問(wèn)題.
SIGTRAP 代碼里面觸發(fā)了調(diào)試指令, 該指令可能由編譯器提供的trap方法觸發(fā), 如'__builtin_trap()'
SIGBUS 一般由于地址對(duì)齊問(wèn)題導(dǎo)致, 單純的OC代碼挺難觸發(fā)的, 主要是系統(tǒng)庫(kù)方法或者其他c實(shí)現(xiàn)的方法導(dǎo)致
SIGILL 表示執(zhí)行了非法的cpu指令, 但是一般是由于死循環(huán)導(dǎo)致

通過(guò)崩潰日志砍聊,定位到崩潰的點(diǎn)在:

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 ,每個(gè)frame 都有一個(gè)編號(hào),它是當(dāng)前frame 在整個(gè)調(diào)用棧的索引贰军〔r颍看到frame 3 是demo代碼調(diào)用的地方,當(dāng)前pc 地址** 0x0000000184f48894** 對(duì)應(yīng)frame 0 調(diào)用地址词疼,而其他的frame 都是歷史記錄俯树,不會(huì)保存當(dāng)前frame所有寄存器的值,只存了lr 寄存器的內(nèi)容(FYI: lr 是方法調(diào)用完之后贰盗,要返回的地址)许饿。

從frame 2 就可以知道,對(duì)象被over release 了舵盈。實(shí)際情況一般是:丟失重要的堆棧信息陋率。下面純粹是在只有frame 3 的堆棧下,怎么定位問(wèn)題秽晚。

frame 3 ,只有一個(gè) setObject:也就是: self.object = [TestObject new]; 咋一看瓦糟,不怎么可能崩潰。下面來(lái)分析一下:

可以看到堆棧地址是: 0x00000001000e03f4赴蝇,程序加載到內(nèi)存的地址在0x1000d8000 - 0x1000e3fff 之間菩浙。

通過(guò)計(jì)算 0x00000001000e03f4 - 0x1000d8000 = 0x83F4。

0x83F4 為相對(duì)偏移扯再,這時(shí)候使用hopper 看看在0x83F4 究竟是什么芍耘。

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

frame 3 的lr 寄存器保存了調(diào)用方法的下一個(gè)指令地址,那么可以確定崩潰發(fā)生在: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
// 保存方法調(diào)用的現(xiàn)場(chǎng)

00000001000083c0         adrp       x8, #0x10000d000
00000001000083c4         add        x8, x8, #0x538                              ; _OBJC_IVAR_$_ViewController._object
// 動(dòng)態(tài)定位獲取ViewController._object的描述地址, 放入x8

00000001000083c8         stur       x0, [x29, #-0x8]
00000001000083cc         str        x1, [sp, #0x10]
00000001000083d0         str        x2, [sp, #0x8]
// 把參數(shù)self/selector/傳進(jìn)來(lái)的TestObject對(duì)象, 存到棧里

00000001000083d4         ldr        x0, [sp, #0x8]
00000001000083d8         ldur       x1, [x29, #-0x8]
00000001000083dc         ldrsw      x8, x8
00000001000083e0         add        x8, x1, x8
// 從x8里把_object的在ViewController對(duì)象的偏移量取出來(lái), 并與x1相加, 也就是`self指針+偏移量`, 結(jié)果存在x8 里面

00000001000083e4         str        x0, sp
// 把傳進(jìn)來(lái)的對(duì)象存入棧
00000001000083e8         mov        x0, x8
// 把`self指針+偏移量`指針?lè)湃離0
00000001000083ec         ldr        x1, sp
// 把傳進(jìn)來(lái)的對(duì)象從棧里取出來(lái)放到x1
00000001000083f0         bl         imp___stubs__objc_storeStrong
// 把x1里傳進(jìn)來(lái)的對(duì)象賦值給x0, 然后強(qiáng)引用一次
00000001000083f4         ldp        x29, x30, [sp, #0x20]
00000001000083f8         add        sp, sp, #0x30
// 恢復(fù)最前面保存的現(xiàn)場(chǎng)
00000001000083fc         ret
// 返回

上面其實(shí)就是一段setter 的代碼,崩潰發(fā)生在imp___stubs__objc_storeStrong秃殉,通過(guò)查看蘋果開(kāi)源代碼: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 并不是原子性操作坝初,當(dāng)線程A可能執(zhí)行到*location = obj 時(shí),另外一個(gè)線程B執(zhí)行 prev = *location; 钾军。那么當(dāng)線程A繼續(xù)執(zhí)行到objc_release(prev); 線程B 繼續(xù)執(zhí)行 鳄袍,跑到objc_release(prev), 此刻吏恭,prev已經(jīng)被釋放過(guò)了拗小。Crash ~~~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市樱哼,隨后出現(xiàn)的幾起案子哀九,更是在濱河造成了極大的恐慌剿配,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件阅束,死亡現(xiàn)場(chǎng)離奇詭異呼胚,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)息裸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門蝇更,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人呼盆,你說(shuō)我怎么就攤上這事年扩。” “怎么了访圃?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵常遂,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我挽荠,道長(zhǎng),這世上最難降的妖魔是什么平绩? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任圈匆,我火速辦了婚禮,結(jié)果婚禮上捏雌,老公的妹妹穿的比我還像新娘跃赚。我一直安慰自己,他們只是感情好性湿,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開(kāi)白布纬傲。 她就那樣靜靜地躺著,像睡著了一般肤频。 火紅的嫁衣襯著肌膚如雪叹括。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,287評(píng)論 1 301
  • 那天宵荒,我揣著相機(jī)與錄音汁雷,去河邊找鬼。 笑死报咳,一個(gè)胖子當(dāng)著我的面吹牛侠讯,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播暑刃,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼厢漩,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了岩臣?” 一聲冷哼從身側(cè)響起溜嗜,我...
    開(kāi)封第一講書(shū)人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤宵膨,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后粱胜,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體柄驻,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年焙压,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了鸿脓。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡涯曲,死狀恐怖野哭,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情幻件,我是刑警寧澤拨黔,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站绰沥,受9級(jí)特大地震影響篱蝇,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜徽曲,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一零截、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧秃臣,春花似錦涧衙、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至稚虎,卻和暖如春撤嫩,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蠢终。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工非洲, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蜕径。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓两踏,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親兜喻。 傳聞我的和親對(duì)象是個(gè)殘疾皇子梦染,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容