一次_os_object_retain的crash

_os_object_retain一看挂在dispatch里,想当然都会认为难道系统有bug了,但是问题不都是靠瞎猜的,还是有了分析才好下结论。

1.前言

最近新版本刚上线就发现少量特别的新增crash,堆栈如下

0 libdispatch.dylib 0x000000018fc296e0 __os_object_retain$VARIANT$mp + 72
1 mttlite 0x00000001033384ec +[CategorySearchDatas selectedSearchInfo] (CategorySearchDatas.m:0)
//...省略中间无意义堆栈
8 libsystem_pthread.dylib 0x000000018fe6a310 __pthread_body + 128
9 libsystem_pthread.dylib 0x000000018fe6a270 _pthread_start + 48
10 libsystem_pthread.dylib 0x000000018fe6dd08 start_wqthread + 4
0 libdispatch.dylib 0x0000000181dbf900 _os_object_retain + 72
1 mttlite 0x00000001023e84ec +[CategorySearchDatas selectedSearchInfo] (CategorySearchDatas.m:0)
2 mttlite 0x00000001001399d0 -[MttSearchEngineSelectorView selectedEngineView] (MttSearchEngineSelectorView.m:317)
//...省略中间堆栈
40 CoreFoundation 0x0000000182de2dc4 CFRunLoopRunSpecific + 452
41 UIKit 0x00000001890a2fc8 -[UIApplication _run] + 652
42 UIKit 0x000000018909dc9c UIApplicationMain + 196
43 mttlite 0x00000001000a08d0 main (main.mm:35)
44 libdyld.dylib 0x0000000181df159c _dyld_process_info_notify_release + 32

一开始只看到第一个堆栈,总是无法解释为何;直到看到第二个堆栈时;就大概坚定了猜测,这肯定有多线程问题,一看crash附件的代码和调用,还真找到了一处疑似问题代码,如下:

+(dispatch_queue_t) getSearchQueue
{
    static dispatch_queue_t searchQueue = nil;
    if (!searchQueue) {
        searchQueue = dispatch_queue_create("engineInfo.MttSearch.SerialQueue", DISPATCH_QUEUE_SERIAL);
    }
    return searchQueue;
    
}

但是光有猜测还是不够的,还是需要验证;
这里我们依旧无法严格复现,那就按照惯例,从汇编代码倒推;

2.分析

原始crash堆栈信息如下:

Thread 13 Crashed: 
0  libdispatch.dylib              0x000000018515ea48 __os_object_retain$VARIANT$mp +  72
8  mttlite                        0x0000000102f184ec +[CategorySearchDatas selectedSearchInfo] (CategorySearchDatas.m:0)

定位到汇编代码如下:

libdispatch.dylib`_os_object_retain:
->  0x10a72e0a8 <+0>:  mov    x8, x0
    0x10a72e0ac <+4>:  ldr    w9, [x8, #0xc]!
    0x10a72e0b0 <+8>:  orr    w10, wzr, #0x7fffffff
    0x10a72e0b4 <+12>: cmp    w9, w10
    0x10a72e0b8 <+16>: b.eq   0x10a72e0d0               ; <+40>
    0x10a72e0bc <+20>: ldxr   w9, [x8]
    0x10a72e0c0 <+24>: add    w10, w9, #0x1             ; =0x1 
    0x10a72e0c4 <+28>: stxr   w11, w10, [x8]
    0x10a72e0c8 <+32>: cbnz   w11, 0x10a72e0bc          ; <+20>
    0x10a72e0cc <+36>: tbnz   w9, #0x1f, 0x10a72e0d4    ; <+44>
    0x10a72e0d0 <+40>: ret    
    0x10a72e0d4 <+44>: stp    x20, x21, [sp, #-0x10]!
    0x10a72e0d8 <+48>: adrp   x20, 49
    0x10a72e0dc <+52>: add    x20, x20, #0x4b2          ; =0x4b2 
    0x10a72e0e0 <+56>: adrp   x21, 55
    0x10a72e0e4 <+60>: add    x21, x21, #0xc40          ; =0xc40 
    0x10a72e0e8 <+64>: str    x20, [x21, #0x8]
    0x10a72e0ec <+68>: ldp    x20, x21, [sp], #0x10
    0x10a72e0f0 <+72>: brk    #0x1

挂在<+72>,而+72的brk指令就是触发一个中断,其实也就是命中了系统的SIGTRAP策略;
逐行分析汇编代码;关键点在<+36>处,tbnz指令,只要w9的第31位!=0则就一定会走进brk的SIGTRAP中;

接下来开始分析这段代码到底在干什么,关键点是<+20>到<+36>的代码

    0x10a72e0ac <+4>:  ldr    w9, [x8, #0xc]!      ;w9=x0->0xC,x8=x8+0xC
    0x10a72e0b0 <+8>:  orr    w10, wzr, #0x7fffffff    ;w10=0xefffffff;即int_max
    0x10a72e0b4 <+12>: cmp    w9, w10              ;比较w9和int_max
    0x10a72e0b8 <+16>: b.eq   0x10a72e0d0               ; w9==int_max则直接return不管
    0x10a72e0bc <+20>: ldxr   w9, [x8]                        ;w9=x0->0xC,即取x0对应指针结构的成员变量
    0x10a72e0c0 <+24>: add    w10, w9, #0x1             ; =0x1 ,w10=w9+1=x0->0xC+1
    0x10a72e0c4 <+28>: stxr   w11, w10, [x8]              ;将w10的值写入x8指向的内存即x0->0xC=w10;w11记录写入结果,成功为0
    0x10a72e0c8 <+32>: cbnz   w11, 0x10a72e0bc          ; 如果写入失败,跳到异常处理
    0x10a72e0cc <+36>: tbnz   w9, #0x1f, 0x10a72e0d4    ; 判定w9的第31位(是符号位)是否为0,为0则为正数>0而继续,否则跳到异常处理;;

大概翻译为如下伪代码

w9=x0->ref_count;
x8=&x0->ref_count; //取地址操作
w10=INT_MAX;
if(w9==w10)
  return ;
w9=*x8;//*取指针内容
w10=w9+1;
*x8=w10;//写入指针内容,写入失败则跳到异常处理
if(w9&0x40000000==0)
 return ;
//接着进入异常处理逻辑;

这里也就是实现对os_object的retain操作;如果retain失败则说明有问题;
这里很明显是retain失败导致进入了异常处理流程;
那为什么retain失败呢?只可能是对象已经被释放了呗;

为了证实这个猜测,找来dispatch源码一看,的确如此

//https://opensource.apple.com/source/libdispatch/libdispatch-228.18/src/object.c
_os_object_t
_os_object_retain(_os_object_t obj)
{
    int xref_cnt = obj->os_obj_xref_cnt;
    if (slowpath(xref_cnt == _OS_OBJECT_GLOBAL_REFCNT)) {
        return obj; // global object
    }
    xref_cnt = dispatch_atomic_inc2o(obj, os_obj_xref_cnt);
    if (slowpath(xref_cnt <= 0)) {
        _OS_OBJECT_CLIENT_CRASH("Resurrection of an object");
    }
    return obj;
}

//_OS_OBJECT_GLOBAL_REFCNT=INT_MAX;

ps: 为何if (slowpath(xref_cnt <= 0))这行C代码在汇编的解释如此简单,直接变成了位运算判定了?这里没有问题,一时把31位误想成了第30位了。。。这里直接判断符号位就知道是正数即可;

3.结论

由汇编分析再加dispatch源码实锤佐证,得到如下结论:

如果获得的xref_cnt(大概是引用计数)<=0,就进入SIGTRAP逻辑,因为说明对象已经被释放了,再去retain当然有问题了;

这就更加说明传进来的os_object有问题,是个已经被释放的对象了;
至此问题更加明了清晰了,就是由于传入的对象提前被释放,导致执行retain操作而挂了;

解决办法当然简单明了,dispatch_once初始化该静态变量即可;

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

推荐阅读更多精彩内容