一次cache_getImp野指針排查

1.前言

最近線上突然多了一些crash假勿,類型是SEGV_ACCER,一看就認(rèn)為是對象野指針了,基本都是多線程讀寫導(dǎo)致的;

但是仔細(xì)再一看crash堆棧,不是平常的objc_xxxx,而是 cache_getImp 這就有點(diǎn)怪了绑蔫,和平常的objc_msgSend或objc_retain等掛的不太一樣运沦;

我們實(shí)際的業(yè)務(wù)代碼掛在如下

[delegate performSelector:didReceiveDataSelector withObject:self withObject:data];

實(shí)際的crash堆棧是

Thread 0 Crashed: 
0  libobjc.A.dylib                0x00000001837bcd04 _cache_getImp +  4
1  libobjc.A.dylib                0x00000001837b1900 _lookUpImpOrNil +  12
2  libobjc.A.dylib                0x00000001837a7578 class_respondsToSelector + 32
3  CoreFoundation                 0x00000001845f41d8 ____forwarding___ +  372
4  CoreFoundation                 0x00000001844da41c _CF_forwarding_prep_0 + 80
5  mttlite                        0x0000000102cd94f4 -[QBASIHTTPRequest passOnReceivedData:] (QBASIHTTPRequest.m:2111)
6  Foundation                     0x000000018503a0ec ___NSThreadPerformPerform +  340
7  CoreFoundation                 0x0000000184597404 ___CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ +  24
 +  24
8  CoreFoundation                 0x0000000184596c2c ___CFRunLoopDoSources0 +  276

2.分析

這個(gè)問題又是不好復(fù)現(xiàn)的問題,那只能沿著Crash堆棧反向去推了配深;

順手就找來objc源碼對比著看携添;
掛的代碼在_cache_getImp 這里實(shí)際上是一段匯編代碼(蘋果為了優(yōu)化效率該函數(shù)時(shí)直接匯編實(shí)現(xiàn)的),
oc對應(yīng)源碼如下

IMP lookUpImpOrNil(Class cls, SEL sel, id inst, 
                   bool initialize, bool cache, bool resolver)
{
    IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
    if (imp == _objc_msgForward_impcache) return nil;
    else return imp;
}

IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    Class curClass;
    IMP imp = nil;
    Method meth;
    bool triedResolver = NO;

    runtimeLock.assertUnlocked();

    // Optimistic cache lookup
    if (cache) {
        imp = cache_getImp(cls, sel);//這里是匯編實(shí)現(xiàn)篓叶,crash發(fā)生在這里
        if (imp) return imp;
    }
    //...省略之后代碼
}

我們看到cache_getImp傳了2個(gè)參數(shù)cls和sel烈掠;嘗試一步步step into最終定位到了crash堆棧的匯編代碼如下:
這里x0就是上面C代碼的cls參數(shù)羞秤,x1就是上面C代碼的sel;

libobjc.A.dylib`cache_getImp:
->  0x182a70d00 <+0>:   and    x16, x0, #0xffffffff8
    0x182a70d04 <+4>:   ldp    x10, x11, [x16, #0x10]
    0x182a70d08 <+8>:   and    w12, w1, w11
    0x182a70d0c <+12>:  add    x12, x10, x12, lsl #4
    0x182a70d10 <+16>:  ldp    x9, x17, [x12]
    0x182a70d14 <+20>:  cmp    x9, x1
    0x182a70d18 <+24>:  b.ne   0x182a70d24               ; <+36>
    0x182a70d1c <+28>:  mov    x0, x17
    0x182a70d20 <+32>:  ret    
    0x182a70d24 <+36>:  cbz    x9, 0x182a70d68           ; <+104>
    0x182a70d28 <+40>:  cmp    x12, x10
    0x182a70d2c <+44>:  b.eq   0x182a70d38               ; <+56>
    0x182a70d30 <+48>:  ldp    x9, x17, [x12, #-0x10]!
    0x182a70d34 <+52>:  b      0x182a70d14               ; <+20>
    0x182a70d38 <+56>:  add    x12, x12, w11, uxtw #4
    0x182a70d3c <+60>:  ldp    x9, x17, [x12]
    0x182a70d40 <+64>:  cmp    x9, x1
    0x182a70d44 <+68>:  b.ne   0x182a70d50               ; <+80>
    0x182a70d48 <+72>:  mov    x0, x17
    0x182a70d4c <+76>:  ret    
    0x182a70d50 <+80>:  cbz    x9, 0x182a70d68           ; <+104>
    0x182a70d54 <+84>:  cmp    x12, x10
    0x182a70d58 <+88>:  b.eq   0x182a70d64               ; <+100>
    0x182a70d5c <+92>:  ldp    x9, x17, [x12, #-0x10]!
    0x182a70d60 <+96>:  b      0x182a70d40               ; <+64>
    0x182a70d64 <+100>: b      0x182a70d68               ; <+104>
    0x182a70d68 <+104>: mov    x0, #0x0
    0x182a70d6c <+108>: ret    
    0x182a70d70 <+112>: nop    
    0x182a70d74 <+116>: nop    
    0x182a70d78 <+120>: nop    
    0x182a70d7c <+124>: nop    

寄存器信息如下

(lldb) re read -a
General Purpose Registers:
        x0 = 0x0000000106c13998  (void *)0x000001a106c139c1
        x1 = 0x000000018e24619b  "forwardingTargetForSelector:"
        x2 = 0x0000000000000000
        x3 = 0x0000000000000000
        x4 = 0x0000000000000001
        x5 = 0x0000000000000001
        x6 = 0x0000000000000000
        x7 = 0x0000000000000000
        x8 = 0x0000000000000000
        x9 = 0x0000000000000002
       x10 = 0x000000012fed70f0
       x11 = 0x000000130000001f
       x12 = 0x000000012fed7150
       x13 = 0x000001a106c1399d
       x14 = 0x0000000000000000
       x15 = 0x0005210000052100
       x16 = 0x0000000106c13998  (void *)0x000001a106c139c1
       x17 = 0x000000018378e3c0  CoreFoundation`_CF_forwarding_prep_0
       x18 = 0x0000000000000000
       x19 = 0x0000000000000000
       x20 = 0x000000018e24619b  "forwardingTargetForSelector:"
       x21 = 0x0000000106c13998  (void *)0x000001a106c139c1
       x22 = 0x0000000000000001
       x23 = 0x0000000105f3cae0  "QBWeakProxy"
       x24 = 0x0000000000000000
       x25 = 0x000000018e24619b  "forwardingTargetForSelector:"
       x26 = 0x0000000000000000
       x27 = 0x00000001b440f000  Foundation`NSUnarchiver.map
       x28 = 0x000000010a878090
        fp = 0x000000016dd1a580
        lr = 0x0000000182a65974  libobjc.A.dylib`lookUpImpOrForward + 64
        sp = 0x000000016dd1a530
        pc = 0x0000000182a70d00  libobjc.A.dylib`cache_getImp

掛在了<+4>偏移左敌,即第2條指令這里瘾蛋,這里是在干什么;
第一步指令and x16, x0, #0xffffffff8這里應(yīng)該是為了內(nèi)存對齊矫限,
接著ldp x10, x11, [x16, #0x10] 這里是關(guān)鍵哺哼;這里是從x16對應(yīng)指針+16偏移處進(jìn)行間接尋址,取出對應(yīng)內(nèi)存地址開始的16字節(jié)內(nèi)容叼风,依次賦值給x10,x11取董;實(shí)際的作用是從Class的cache_t中取出緩存的方法列表;

關(guān)于為什么是+16无宿,參照如下結(jié)構(gòu)就能明白了

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
    //```省略
}

因?yàn)閤0指向了一個(gè)Class對象茵汰, 所以其內(nèi)存地址偏移16字節(jié)的地方自然就是objc_class的第3個(gè)成員變量即cache_t cache;
好的孽鸡,crash正好就發(fā)生在這里蹂午,讀取這個(gè)內(nèi)存的時(shí)候出錯(cuò)了,而且這個(gè)地址是一個(gè)非法地址梭灿,因?yàn)榈刂房臻g不在主進(jìn)程的地址空間范圍內(nèi)画侣,只能懷疑是傳入的cls有問題

cls有問題,那就說明delegate的isa有問題堡妒,但是一般而言isa是不可能有問題的,否則crash的堆棧就不是在這里了配乱,那么只有一種可能delegate有問題,導(dǎo)致delegate+8地址偏移出錯(cuò)皮迟,取出來的isa地址越界搬泥,所以導(dǎo)致了這個(gè)crash;

3.結(jié)論

由上分析伏尼,初步認(rèn)為這個(gè)crash本質(zhì)還是因?yàn)閭魅氲膶ο笠爸羔樍朔揲荩瑢?dǎo)致獲取其isa時(shí)出現(xiàn)了問題,接著再操作讀取這個(gè)問題isa時(shí)自然就會(huì)野指針了爆阶;所以解決辦法還是對傳入對象做多線程讀寫互斥加鎖燥透;

至于為什么這里不是平常的objc_msgSend問題呢?
這里有個(gè)特別的地方在于之前我們?yōu)榱私鉀QASIHTTPRequest的assign delegate的問題辨图,引用了NSProxy去管理weak delegate班套;所以最終delegate實(shí)際上是一個(gè)把消息轉(zhuǎn)發(fā)給weak delegate的NSProxy實(shí)例,而不是一個(gè)NSObject實(shí)例

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末故河,一起剝皮案震驚了整個(gè)濱河市吱韭,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌鱼的,老刑警劉巖理盆,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件痘煤,死亡現(xiàn)場離奇詭異,居然都是意外死亡猿规,警方通過查閱死者的電腦和手機(jī)衷快,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來坎拐,“玉大人烦磁,你說我怎么就攤上這事『哂拢” “怎么了都伪?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長积担。 經(jīng)常有香客問我陨晶,道長,這世上最難降的妖魔是什么帝璧? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任先誉,我火速辦了婚禮,結(jié)果婚禮上的烁,老公的妹妹穿的比我還像新娘褐耳。我一直安慰自己,他們只是感情好渴庆,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布铃芦。 她就那樣靜靜地躺著,像睡著了一般襟雷。 火紅的嫁衣襯著肌膚如雪刃滓。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天耸弄,我揣著相機(jī)與錄音咧虎,去河邊找鬼。 笑死计呈,一個(gè)胖子當(dāng)著我的面吹牛砰诵,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播捌显,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼茁彭,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了苇瓣?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤偿乖,失蹤者是張志新(化名)和其女友劉穎击罪,沒想到半個(gè)月后哲嘲,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡媳禁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年眠副,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片竣稽。...
    茶點(diǎn)故事閱讀 40,040評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡囱怕,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出毫别,到底是詐尸還是另有隱情娃弓,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布岛宦,位于F島的核電站台丛,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏砾肺。R本人自食惡果不足惜挽霉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望变汪。 院中可真熱鬧侠坎,春花似錦、人聲如沸裙盾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽闷煤。三九已至童芹,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鲤拿,已是汗流浹背假褪。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留近顷,地道東北人生音。 一個(gè)月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像窒升,于是被迫代替她去往敵國和親缀遍。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評論 2 355

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

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉饱须,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,719評論 0 9
  • 一.objc_msgSend函數(shù)簡介 以前去面試域醇,有人問了這個(gè)一個(gè)問題 發(fā)生了什么?一聽這個(gè)問題,一臉懵逼譬挚。這不就...
    充滿活力的早晨閱讀 476評論 0 2
  • 我們常常會(huì)聽說 Objective-C 是一門動(dòng)態(tài)語言锅铅,那么這個(gè)「動(dòng)態(tài)」表現(xiàn)在哪呢?我想最主要的表現(xiàn)就是 Obje...
    Ethan_Struggle閱讀 2,195評論 0 7
  • 眾里尋他千百度减宣,驀然回首盐须,那人卻在燈火闌珊處。--《青玉案·元夕》 要學(xué)會(huì)看crash崩潰和報(bào)告 一個(gè)應(yīng)用程序并不...
    歐陽大哥2013閱讀 21,873評論 38 231
  • (注:圖源網(wǎng)絡(luò),侵權(quán)立刪) 原創(chuàng):畫千番 前不久接到一個(gè)朋友的喜訊闷尿,不由得感慨她終于要嫁出去了塑径。 朋友三十有一,...
    畫千番閱讀 347評論 0 5