_ivanC

Open Source Projects
Code that Might be Useful to You

Talks I've Given
On Technologies and Ideas

Follow Me On

GitHub
If coding is your thing

Twitter
If you tweet

Facebook
If you like something new

Weibo
If you like sharing

简书
If you like writing

利用简单的逆向来解决GCD崩溃


我们平时在开发过程中,偶尔会遇到一些崩溃日志,看到堆栈就想放弃的,例如下面这个,很明显的野指针崩溃,但是不知道崩溃在哪里

Thread 0 Crashed:
0   libobjc.A.dylib                     0x0000000185f5ef70 objc_msgSend + 16
1   libdispatch.dylib                   0x000000018639e1fc _dispatch_call_block_and_release + 24
2   libdispatch.dylib                   0x000000018639e1bc _dispatch_client_callout + 16
3   libdispatch.dylib                   0x00000001863a2d68 _dispatch_main_queue_callback_4CF + 1000
4   CoreFoundation                      0x00000001874c2810 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 12
5   CoreFoundation                      0x00000001874c03fc __CFRunLoopRun + 1660
6   CoreFoundation                      0x00000001873ee2b8 CFRunLoopRunSpecific + 444
7   GraphicsServices                    0x0000000188ea2198 GSEventRunModal + 180
8   UIKit                               0x000000018d4357fc -[UIApplication _run] + 684
9   UIKit                               0x000000018d430534 UIApplicationMain + 208
10  DemoApp                              0x00000001000591bc main (main.mm:25)
11  ???                                 0x00000001863d15b8 0x0 + 0

当然,从其他的堆栈看来,也没有什么明显有用的信息 这种时候,按照一般的公司开发流程和节奏的话,如果这种崩溃不多,基本上不会花很多时间来研究 但是,一旦这种崩溃很多的话,就原地爆炸了

解决之路

回到问题上来,遇到这种问题我们一般可以简单逆向看一下,说不定有意外的惊喜,这里需要用到hopper来实现逆向

首先,我们看一下崩溃日志底部的两个重要信息

寄存器信息
Thread 0 crashed with ARM-64 Thread State:
    pc: 0x0000000185f5ef70     fp: 0x000000016fdaac10     sp: 0x000000016fdaabf0     x0: 0x0000000135d168b0 
    x1: 0x0000000197b5f199     x2: 0x00000001706868b0     x3: 0x000000017465d4f0     x4: 0x0000000000000000 
    x5: 0x0000000000000000     x6: 0x00000001007f01fc     x7: 0x0000000000000000     x8: 0x000000010137a000 
    x9: 0x000000018def68ee    x10: 0x0000000134b40c00    x11: 0x0000008c000000ff    x12: 0x0000000134b41ae0 
   x13: 0x20000000135d5d93    x14: 0x0000000008000000    x15: 0x0000000000000000    x16: 0x00000000135d5d90 
   x17: 0x00000001873f3da8    x18: 0x0000000000000000    x19: 0x000000017068eab0    x20: 0x0000000170a4d590 
   x21: 0x00000001706868b0    x22: 0x0000000000000000    x23: 0x0000000000000014    x24: 0x00000001acdf8d20 
   x25: 0x0000000000000000    x26: 0xffffffffffffffff    x27: 0x0000000170e68f80    x28: 0x0000000002ffffff 
    lr: 0x00000001007f025c   cpsr: 0x0000000020000000 
模块地址信息
binary Images:
       0x100054000 -        0x100fa3fff +DemoApp arm64  <593c3b8025743182ab2b8948cf9f33f7> /var/containers/Bundle/Application/29AF15E3-7F1D-4FB8-AC08-C72C9EB8521F/DemoApp.app/DemoApp
       0x185ec8000 -        0x185ec9fff  libSystem.B.dylib arm64  <1b4d75209f4a37969a9575de48d48668> /usr/lib/libSystem.B.dylib
       0x185eca000 -        0x185f1ffff  libc++.1.dylib arm64  <b2db8b1d09283b7bafe1b2933adc5dfd> /usr/lib/libc++.1.dylib
       0x185f20000 -        0x185f40fff  libc++abi.dylib arm64  <e3419bbaface31b5970c6c8d430be26d> /usr/lib/libc++abi.dylib

在这个例子里,我们只关心LR寄存器的值 lr: 0x00000001007f025c 我们再看看DemoApp的地址范围 0x100054000 - 0x100fa3fff +DemoApp 发现这个地址刚好DemoApp的范围内!

我们迎来了第一个惊喜 因为lr存放着当前调用的返回地址,意味着崩溃点的被调用的地方,是在我们自己的代码里面,而不是系统api内部,这样我们还是可以把它揪出来的。

我们可以简单计算出绝对偏移获得函数的地址,即 lr - 模块基址 这里是0x79C25C = 0x00000001007f025c - 0x100054000

这里开始我们要用到Hopper

很快的第二个惊喜 在这个案例上,我们很幸运的,一下子就圈定了崩溃点的范围,如下

        ; ================ B E G I N N I N G   O F   P R O C E D U R E ================


                     -[ImageDisplayCell setRefDataItem:]:
000000010079c050         stp        x22, x21, [sp, #-0x30]!                     ; Objective C Implementation defined at 0x1011cabd0 (instance method), DATA XREF=0x1011cabd0
000000010079c054         stp        x20, x19, [sp, #0x10]
000000010079c058         stp        x29, x30, [sp, #0x20]
000000010079c05c         add        x29, sp, #0x20
000000010079c060         sub        sp, sp, #0x60
000000010079c064         mov        x19, x2
000000010079c068         mov        x20, x0
000000010079c06c         adrp       x8, #0x101350000
000000010079c070         ldrsw      x21, [x8, #0x640]                           ; 0x101350640
000000010079c074         ldr        x0, x20, x21
000000010079c078         cmp        x0, x19
000000010079c07c         b.eq       loc_10079c1b0

000000010079c080         adrp       x8, #0x101306000                            ; @selector(updateProgressiveImageWithData:expectedNumberOfBytes:)
000000010079c084         ldr        x1, [x8, #0x1c8]                            ; "release",@selector(release)
000000010079c088         bl         imp___stubs__objc_msgSend
000000010079c08c         adrp       x8, #0x101306000                            ; @selector(updateProgressiveImageWithData:expectedNumberOfBytes:)
000000010079c090         ldr        x1, [x8, #0x338]                            ; "retain",@selector(retain)
000000010079c094         mov        x0, x19
000000010079c098         bl         imp___stubs__objc_msgSend
000000010079c09c         str        x0, x20, x21
000000010079c0a0         adrp       x8, #0x101326000                            ; @selector(setFirstResponseItem:)
000000010079c0a4         ldr        x1, [x8, #0x520]                            ; "picTitle",@selector(picTitle)
000000010079c0a8         mov        x0, x19
000000010079c0ac         bl         imp___stubs__objc_msgSend
000000010079c0b0         mov        x21, x0
000000010079c0b4         adrp       x8, #0x10130a000                            ; @selector(s_component)
000000010079c0b8         ldr        x1, [x8, #0xd98]                            ; "titleLabel",@selector(titleLabel)
000000010079c0bc         mov        x0, x20
000000010079c0c0         bl         imp___stubs__objc_msgSend
000000010079c0c4         adrp       x8, #0x101302000                            ; @selector(tableView:canPerformAction:forRowAtIndexPath:withSender:)
000000010079c0c8         ldr        x1, [x8, #0xf60]                            ; "setText:",@selector(setText:)
000000010079c0cc         mov        x2, x21
000000010079c0d0         bl         imp___stubs__objc_msgSend
000000010079c0f0         add        x8, sp, #0x30
000000010079c0f4         stp        xzr, x8, [sp, #0x30]
000000010079c0f8         movz       w8, #0x5200
000000010079c0fc         str        w8, [sp, #0x40]
000000010079c100         orr        w8, wzr, #0x30
000000010079c104         str        w8, [sp, #0x44]
000000010079c108         adr        x8, #0x10079c1e0
000000010079c10c         nop
000000010079c110         fmov       d0, x8
000000010079c114         adr        x8, #0x10079c1f0
000000010079c118         nop
000000010079c11c         ins        v0, x8
000000010079c120         stur       q0, [sp, #0x48]
000000010079c124         str        x20, [sp, #0x58]
000000010079c128         adrp       x8, #0x101326000                            ; @selector(setFirstResponseItem:)
000000010079c12c         ldr        x1, [x8, #0x470]                            ; "picUrl",@selector(picUrl)
000000010079c130         mov        x0, x19
000000010079c134         bl         imp___stubs__objc_msgSend
000000010079c138         adrp       x8, #0x101309000                            ; @selector(customAttributes)
000000010079c13c         ldr        x1, [x8, #0x178]                            ; "urlDecodedString",@selector(urlDecodedString)
000000010079c140         bl         imp___stubs__objc_msgSend
000000010079c144         mov        x19, x0
000000010079c148         adrp       x8, #0x10133e000
000000010079c14c         ldr        x0, [x8, #0x518]                            ; objc_cls_ref_ImageLoadManager,__objc_class_ImageLoadManager_class
000000010079c150         adrp       x8, #0x101305000                            ; @selector(dataWithBytesNoCopy:length:freeWhenDone:)
000000010079c154         ldr        x1, [x8, #0x9f8]                            ; "sharedManager",@selector(sharedManager)
000000010079c158         bl         imp___stubs__objc_msgSend
000000010079c15c         adrp       x8, #0x100f50000
000000010079c160         ldr        x8, [x8, #0x6b8]                            ; __NSConcreteStackBlock_100f506b8,__NSConcreteStackBlock
000000010079c164         str        x8, sp
000000010079c168         movz       w8, #0xc200
000000010079c16c         stp        w8, wzr, [sp, #0x8]
000000010079c170         adr        x8, #0x10079c1fc
000000010079c174         nop
000000010079c178         str        x8, [sp, #0x10]
000000010079c17c         adrp       x8, #0x100f82000
000000010079c180         add        x8, x8, #0xe0                               ; 0x100f820e0
000000010079c184         stp        x8, x19, [sp, #0x18]
000000010079c188         add        x8, sp, #0x30
000000010079c18c         str        x8, [sp, #0x28]
000000010079c190         adrp       x8, #0x10130d000                            ; @selector(hideHistoryView)
000000010079c194         ldr        x1, [x8, #0x5a8]                            ; "loadImageForURLString:completionBlock:",@selector(loadImageForURLString:completionBlock:)
000000010079c198         mov        x3, sp
000000010079c19c         mov        x2, x19
000000010079c1a0         bl         imp___stubs__objc_msgSend
000000010079c1a4         add        x0, sp, #0x30
000000010079c1a8         orr        w1, wzr, #0x8
000000010079c1ac         bl         imp___stubs___Block_object_dispose

                     loc_10079c1b0:
000000010079c1b0         sub        sp, x29, #0x20                              ; CODE XREF=-[ImageDisplayCell setRefDataItem:]+44
000000010079c1b4         ldp        x29, x30, [sp, #0x20]
000000010079c1b8         ldp        x20, x19, [sp, #0x10]
000000010079c1bc         ldp        x22, x21, [sp]!, #0x30
000000010079c1c0         ret
                        ; endp
000000010079c1c4         b          -[ImageDisplayCell setRefDataItem:]+376
000000010079c1c8         mov        x19, x0                                     ; CODE XREF=-[ImageDisplayCell setRefDataItem:]+372
000000010079c1cc         add        x0, sp, #0x30
000000010079c1d0         orr        w1, wzr, #0x8
000000010079c1d4         bl         imp___stubs___Block_object_dispose
000000010079c1d8         mov        x0, x19
000000010079c1dc         bl         imp___stubs___Unwind_Resume
000000010079c1e0         dd         0x9100a000                                  ; DATA XREF=-[ImageDisplayCell setRefDataItem:]+184
000000010079c1e4         ldr        x1, [x1, #0x28]
000000010079c1e8         movz       w2, #0x83
000000010079c1ec         b          imp___stubs___Block_object_assign
000000010079c1f0         dd         0xf9401400                                  ; DATA XREF=-[ImageDisplayCell setRefDataItem:]+196
000000010079c1f4         movz       w1, #0x83
000000010079c1f8         b          imp___stubs___Block_object_dispose
000000010079c1fc         dd         0xa9bd57f6                                  ; DATA XREF=-[ImageDisplayCell setRefDataItem:]+288
000000010079c200         stp        x20, x19, [sp, #0x10]
000000010079c204         stp        x29, x30, [sp, #0x20]
000000010079c208         add        x29, sp, #0x20
000000010079c20c         mov        x19, x1
000000010079c210         mov        x20, x0
000000010079c214         cbz        x19, -[ImageDisplayCell setRefDataItem:]+552
000000010079c218         ldr        x21, [x20, #0x20]
000000010079c21c         adrp       x8, #0x101305000
000000010079c220         ldr        x1, [x8, #0xd8]
000000010079c224         mov        x0, x2
000000010079c228         bl         imp___stubs__objc_msgSend
000000010079c22c         mov        x2, x0
000000010079c230         adrp       x8, #0x101300000
000000010079c234         ldr        x1, [x8, #0x940]
000000010079c238         mov        x0, x21
000000010079c23c         bl         imp___stubs__objc_msgSend
000000010079c240         cbz        w0, -[ImageDisplayCell setRefDataItem:]+552
000000010079c244         ldr        x8, [x20, #0x28]
000000010079c248         ldr        x8, [x8, #0x8]
000000010079c24c         ldr        x0, [x8, #0x28]
000000010079c250         adrp       x8, #0x101326000
000000010079c254         ldr        x1, [x8, #0x818]
000000010079c258         bl         imp___stubs__objc_msgSend
000000010079c25c         adrp       x8, #0x101301000
000000010079c260         ldr        x1, [x8, #0x260]
000000010079c264         mov        x2, x19
000000010079c268         ldp        x29, x30, [sp, #0x20]
000000010079c26c         ldp        x20, x19, [sp, #0x10]
000000010079c270         ldp        x22, x21, [sp]!, #0x30
000000010079c274         b          imp___stubs__objc_msgSend
000000010079c278         ldp        x29, x30, [sp, #0x20]                       ; CODE XREF=-[ImageDisplayCell setRefDataItem:]+452, -[ImageDisplayCell setRefDataItem:]+496
000000010079c27c         ldp        x20, x19, [sp, #0x10]
000000010079c280         ldp        x22, x21, [sp]!, #0x30
000000010079c284         ret

到这里基本上对比源码,我们就已经可以找到原因了,因为很明确的崩溃函数是在-[ImageDisplayCell setRefDataItem:]

- (void)setRefDataItem:(PicBRefDataItem *)refDataItem
{
    if (_refDataItem != refDataItem)
    {
        [_refDataItem release];
        _refDataItem = [refDataItem retain];
        
        NSString *title = [refDataItem picTitle];
        self.titleLabel.text = title;
        
        __weak typeof(self) weakSelf = self;
        
        NSString *requestURL = [refDataItem.picUrl urlDecodedString];
        [[ImageLoadManager sharedManager] loadImageForURLString:requestURL
                                                     completionBlock:^(UIImage *image, NSURL *imageURL, NSData *data, NSError *error, BOOL isCache) {
                                                         
                                                         if (image && [requestURL isEqualToString:[imageURL absoluteString]])
                                                         {
                                                             weakSelf.thumbnailImageView.image = image;
                                                         }
                                                     }];
    }
}

但是我们还是坚持一下,进一步看看是否可以精确定位到问题出现在哪一步

即使汇编不大好,也可以很容易地和源码对应起来,而我们算出来的地址,就在loc_10079c1b0这个块里面,对应的就是源码的block,我们看一下崩溃点前后的一小段汇编

000000010079c240         cbz        w0, -[ImageDisplayCell setRefDataItem:]+552
000000010079c244         ldr        x8, [x20, #0x28]
000000010079c248         ldr        x8, [x8, #0x8]
000000010079c24c         ldr        x0, [x8, #0x28]
000000010079c250         adrp       x8, #0x101326000
000000010079c254         ldr        x1, [x8, #0x818]
000000010079c258         bl         imp___stubs__objc_msgSend
000000010079c25c         adrp       x8, #0x101301000
000000010079c260         ldr        x1, [x8, #0x260]
000000010079c264         mov        x2, x19

在崩溃点000000010079c25c 其实已经挂掉了,前面的一个objc_msgSend,就是传说中的野指针崩溃,我们就要定位这个崩溃的方法,看上面一段

000000010079c250         adrp       x8, #0x101326000
000000010079c254         ldr        x1, [x8, #0x818]

adrp作为常量加载跳转,在这个场景下,x1存的是selector,总结起来,就是说,objc_msgSend 调用的方法入口地址是 0x101326000 + 0x818

我们不妨Go Address:0x101326818 看一下

0000000101326818         dq         0x100cf2a9b                                 ; 
@selector(thumbnailImageView), "thumbnailImageView", DATA XREF=-[ImageDisplayCell initialized]+276, -[ImageDisplayCell layoutSubviews]+156, -[ImageDisplayCell prepareForReuse]+80

到这里,基本可以精确定位到案发现场了,对应到源码,从上下文来看,就是weakSelf野指针了

        __weak typeof(self) weakSelf = self;                                        
       weakSelf.thumbnailImageView.image = image;

发现原因很简单,就是在MRC上__block相当于assign,在一个异步block里面,有可能已经野指针了,修复方法很多,最简单的修复就是用weak

这是一个很容易犯的错误,但是当代码量到了一定的规模,收集上来的崩溃日志没有明显提示方法入口的时候,这种方法还是实用的,习惯之后,也很容易上手。

PS: 简单科普一下LR这个寄存器

R14称为子程序链接寄存器LR(Link Register),当执行子程序调用指令(BL)时,R14可得到R15(程序计数器PC)的备 份.在每一种运行模式下,都可用R14保存子程序的返回地址,当用BL或BLX指令调用子程序时,将PC的当前值复制给 R14,执行完子程序后,又将R14的值复制回PC,即可完成子程序的调用返回。以上的描述可用指令完成。

参考文档

崩溃分析汇编基础 by Vedon_fu