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

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

Thread 0 Crashed: 0libobjc.A.dylib0x0000000185f5ef70 objc_msgSend + 16 1libdispatch.dylib0x000000018639e1fc _dispatch_call_block_and_release + 24 2libdispatch.dylib0x000000018639e1bc _dispatch_client_callout + 16 3libdispatch.dylib0x00000001863a2d68 _dispatch_main_queue_callback_4CF + 1000 4CoreFoundation0x00000001874c2810 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 12 5CoreFoundation0x00000001874c03fc __CFRunLoopRun + 1660 6CoreFoundation0x00000001873ee2b8 CFRunLoopRunSpecific + 444 7GraphicsServices0x0000000188ea2198 GSEventRunModal + 180 8UIKit0x000000018d4357fc -[UIApplication _run] + 684 9UIKit0x000000018d430534 UIApplicationMain + 208 10DemoApp0x00000001000591bc main (main.mm:25) 11???0x00000001863d15b8 0x0 + 0

当然,从其他的堆栈看来,也没有什么明显有用的信息
这种时候,按照一般的公司开发流程和节奏的话,如果这种崩溃不多,基本上不会花很多时间来研究
但是,一旦这种崩溃很多的话,就原地爆炸了
解决之路 回到问题上来,遇到这种问题我们一般可以简单逆向看一下,说不定有意外的惊喜,这里需要用到hopper来实现逆向
首先,我们看一下崩溃日志底部的两个重要信息
寄存器信息
Thread 0 crashed with ARM-64 Thread State: pc: 0x0000000185f5ef70fp: 0x000000016fdaac10sp: 0x000000016fdaabf0x0: 0x0000000135d168b0 x1: 0x0000000197b5f199x2: 0x00000001706868b0x3: 0x000000017465d4f0x4: 0x0000000000000000 x5: 0x0000000000000000x6: 0x00000001007f01fcx7: 0x0000000000000000x8: 0x000000010137a000 x9: 0x000000018def68eex10: 0x0000000134b40c00x11: 0x0000008c000000ffx12: 0x0000000134b41ae0 x13: 0x20000000135d5d93x14: 0x0000000008000000x15: 0x0000000000000000x16: 0x00000000135d5d90 x17: 0x00000001873f3da8x18: 0x0000000000000000x19: 0x000000017068eab0x20: 0x0000000170a4d590 x21: 0x00000001706868b0x22: 0x0000000000000000x23: 0x0000000000000014x24: 0x00000001acdf8d20 x25: 0x0000000000000000x26: 0xffffffffffffffffx27: 0x0000000170e68f80x28: 0x0000000002ffffff lr: 0x00000001007f025ccpsr: 0x0000000020000000

模块地址信息
binary Images: 0x100054000 -0x100fa3fff +DemoApp arm64<593c3b8025743182ab2b8948cf9f33f7> /var/containers/Bundle/Application/29AF15E3-7F1D-4FB8-AC08-C72C9EB8521F/DemoApp.app/DemoApp 0x185ec8000 -0x185ec9ffflibSystem.B.dylib arm64<1b4d75209f4a37969a9575de48d48668> /usr/lib/libSystem.B.dylib 0x185eca000 -0x185f1fffflibc++.1.dylib arm64 /usr/lib/libc++.1.dylib 0x185f20000 -0x185f40ffflibc++abi.dylib arm64 /usr/lib/libc++abi.dylib

在这个例子里,我们只关心LR寄存器的值
lr: 0x00000001007f025c
我们再看看DemoApp的地址范围
0x100054000 - 0x100fa3fff +DemoApp
发现这个地址刚好DemoApp的范围内!
我们迎来了第一个惊喜
因为lr存放着当前调用的返回地址,意味着崩溃点的被调用的地方,是在我们自己的代码里面,而不是系统api内部,这样我们还是可以把它揪出来的。
我们可以简单计算出绝对偏移获得函数的地址,即 lr - 模块基址
这里是0x79C25C = 0x00000001007f025c - 0x100054000
这里开始我们要用到Hopper
  • 打开Hopper,加载这个DemoApp的可执行文件
  • Go Address : ** 0x79C25C**
很快的第二个惊喜
在这个案例上,我们很幸运的,一下子就圈定了崩溃点的范围,如下
; ================ B E G I N N I N GO FP R O C E D U R E ================-[ImageDisplayCell setRefDataItem:]: 000000010079c050stpx22, x21, [sp, #-0x30]!; Objective C Implementation defined at 0x1011cabd0 (instance method), DATA XREF=0x1011cabd0 000000010079c054stpx20, x19, [sp, #0x10] 000000010079c058stpx29, x30, [sp, #0x20] 000000010079c05caddx29, sp, #0x20 000000010079c060subsp, sp, #0x60 000000010079c064movx19, x2 000000010079c068movx20, x0 000000010079c06cadrpx8, #0x101350000 000000010079c070ldrswx21, [x8, #0x640]; 0x101350640 000000010079c074ldrx0, x20, x21 000000010079c078cmpx0, x19 000000010079c07cb.eqloc_10079c1b0000000010079c080adrpx8, #0x101306000; @selector(updateProgressiveImageWithData:expectedNumberOfBytes:) 000000010079c084ldrx1, [x8, #0x1c8]; "release",@selector(release) 000000010079c088blimp___stubs__objc_msgSend 000000010079c08cadrpx8, #0x101306000; @selector(updateProgressiveImageWithData:expectedNumberOfBytes:) 000000010079c090ldrx1, [x8, #0x338]; "retain",@selector(retain) 000000010079c094movx0, x19 000000010079c098blimp___stubs__objc_msgSend 000000010079c09cstrx0, x20, x21 000000010079c0a0adrpx8, #0x101326000; @selector(setFirstResponseItem:) 000000010079c0a4ldrx1, [x8, #0x520]; "picTitle",@selector(picTitle) 000000010079c0a8movx0, x19 000000010079c0acblimp___stubs__objc_msgSend 000000010079c0b0movx21, x0 000000010079c0b4adrpx8, #0x10130a000; @selector(s_component) 000000010079c0b8ldrx1, [x8, #0xd98]; "titleLabel",@selector(titleLabel) 000000010079c0bcmovx0, x20 000000010079c0c0blimp___stubs__objc_msgSend 000000010079c0c4adrpx8, #0x101302000; @selector(tableView:canPerformAction:forRowAtIndexPath:withSender:) 000000010079c0c8ldrx1, [x8, #0xf60]; "setText:",@selector(setText:) 000000010079c0ccmovx2, x21 000000010079c0d0blimp___stubs__objc_msgSend 000000010079c0f0addx8, sp, #0x30 000000010079c0f4stpxzr, x8, [sp, #0x30] 000000010079c0f8movzw8, #0x5200 000000010079c0fcstrw8, [sp, #0x40] 000000010079c100orrw8, wzr, #0x30 000000010079c104strw8, [sp, #0x44] 000000010079c108adrx8, #0x10079c1e0 000000010079c10cnop 000000010079c110fmovd0, x8 000000010079c114adrx8, #0x10079c1f0 000000010079c118nop 000000010079c11cinsv0, x8 000000010079c120sturq0, [sp, #0x48] 000000010079c124strx20, [sp, #0x58] 000000010079c128adrpx8, #0x101326000; @selector(setFirstResponseItem:) 000000010079c12cldrx1, [x8, #0x470]; "picUrl",@selector(picUrl) 000000010079c130movx0, x19 000000010079c134blimp___stubs__objc_msgSend 000000010079c138adrpx8, #0x101309000; @selector(customAttributes) 000000010079c13cldrx1, [x8, #0x178]; "urlDecodedString",@selector(urlDecodedString) 000000010079c140blimp___stubs__objc_msgSend 000000010079c144movx19, x0 000000010079c148adrpx8, #0x10133e000 000000010079c14cldrx0, [x8, #0x518]; objc_cls_ref_ImageLoadManager,__objc_class_ImageLoadManager_class 000000010079c150adrpx8, #0x101305000; @selector(dataWithBytesNoCopy:length:freeWhenDone:) 000000010079c154ldrx1, [x8, #0x9f8]; "sharedManager",@selector(sharedManager) 000000010079c158blimp___stubs__objc_msgSend 000000010079c15cadrpx8, #0x100f50000 000000010079c160ldrx8, [x8, #0x6b8]; __NSConcreteStackBlock_100f506b8,__NSConcreteStackBlock 000000010079c164strx8, sp 000000010079c168movzw8, #0xc200 000000010079c16cstpw8, wzr, [sp, #0x8] 000000010079c170adrx8, #0x10079c1fc 000000010079c174nop 000000010079c178strx8, [sp, #0x10] 000000010079c17cadrpx8, #0x100f82000 000000010079c180addx8, x8, #0xe0; 0x100f820e0 000000010079c184stpx8, x19, [sp, #0x18] 000000010079c188addx8, sp, #0x30 000000010079c18cstrx8, [sp, #0x28] 000000010079c190adrpx8, #0x10130d000; @selector(hideHistoryView) 000000010079c194ldrx1, [x8, #0x5a8]; "loadImageForURLString:completionBlock:",@selector(loadImageForURLString:completionBlock:) 000000010079c198movx3, sp 000000010079c19cmovx2, x19 000000010079c1a0blimp___stubs__objc_msgSend 000000010079c1a4addx0, sp, #0x30 000000010079c1a8orrw1, wzr, #0x8 000000010079c1acblimp___stubs___Block_object_disposeloc_10079c1b0: 000000010079c1b0subsp, x29, #0x20; CODE XREF=-[ImageDisplayCell setRefDataItem:]+44 000000010079c1b4ldpx29, x30, [sp, #0x20] 000000010079c1b8ldpx20, x19, [sp, #0x10] 000000010079c1bcldpx22, x21, [sp]!, #0x30 000000010079c1c0ret ; endp 000000010079c1c4b-[ImageDisplayCell setRefDataItem:]+376 000000010079c1c8movx19, x0; CODE XREF=-[ImageDisplayCell setRefDataItem:]+372 000000010079c1ccaddx0, sp, #0x30 000000010079c1d0orrw1, wzr, #0x8 000000010079c1d4blimp___stubs___Block_object_dispose 000000010079c1d8movx0, x19 000000010079c1dcblimp___stubs___Unwind_Resume 000000010079c1e0dd0x9100a000; DATA XREF=-[ImageDisplayCell setRefDataItem:]+184 000000010079c1e4ldrx1, [x1, #0x28] 000000010079c1e8movzw2, #0x83 000000010079c1ecbimp___stubs___Block_object_assign 000000010079c1f0dd0xf9401400; DATA XREF=-[ImageDisplayCell setRefDataItem:]+196 000000010079c1f4movzw1, #0x83 000000010079c1f8bimp___stubs___Block_object_dispose 000000010079c1fcdd0xa9bd57f6; DATA XREF=-[ImageDisplayCell setRefDataItem:]+288 000000010079c200stpx20, x19, [sp, #0x10] 000000010079c204stpx29, x30, [sp, #0x20] 000000010079c208addx29, sp, #0x20 000000010079c20cmovx19, x1 000000010079c210movx20, x0 000000010079c214cbzx19, -[ImageDisplayCell setRefDataItem:]+552 000000010079c218ldrx21, [x20, #0x20] 000000010079c21cadrpx8, #0x101305000 000000010079c220ldrx1, [x8, #0xd8] 000000010079c224movx0, x2 000000010079c228blimp___stubs__objc_msgSend 000000010079c22cmovx2, x0 000000010079c230adrpx8, #0x101300000 000000010079c234ldrx1, [x8, #0x940] 000000010079c238movx0, x21 000000010079c23cblimp___stubs__objc_msgSend 000000010079c240cbzw0, -[ImageDisplayCell setRefDataItem:]+552 000000010079c244ldrx8, [x20, #0x28] 000000010079c248ldrx8, [x8, #0x8] 000000010079c24cldrx0, [x8, #0x28] 000000010079c250adrpx8, #0x101326000 000000010079c254ldrx1, [x8, #0x818] 000000010079c258blimp___stubs__objc_msgSend 000000010079c25cadrpx8, #0x101301000 000000010079c260ldrx1, [x8, #0x260] 000000010079c264movx2, x19 000000010079c268ldpx29, x30, [sp, #0x20] 000000010079c26cldpx20, x19, [sp, #0x10] 000000010079c270ldpx22, x21, [sp]!, #0x30 000000010079c274bimp___stubs__objc_msgSend 000000010079c278ldpx29, x30, [sp, #0x20]; CODE XREF=-[ImageDisplayCell setRefDataItem:]+452, -[ImageDisplayCell setRefDataItem:]+496 000000010079c27cldpx20, x19, [sp, #0x10] 000000010079c280ldpx22, x21, [sp]!, #0x30 000000010079c284ret

到这里基本上对比源码,我们就已经可以找到原因了,因为很明确的崩溃函数是在-[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,我们看一下崩溃点前后的一小段汇编
000000010079c240cbzw0, -[ImageDisplayCell setRefDataItem:]+552 000000010079c244ldrx8, [x20, #0x28] 000000010079c248ldrx8, [x8, #0x8] 000000010079c24cldrx0, [x8, #0x28] 000000010079c250adrpx8, #0x101326000 000000010079c254ldrx1, [x8, #0x818] 000000010079c258blimp___stubs__objc_msgSend 000000010079c25cadrpx8, #0x101301000 000000010079c260ldrx1, [x8, #0x260] 000000010079c264movx2, x19

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

adrp作为常量加载跳转,在这个场景下,x1存的是selector,总结起来,就是说,objc_msgSend 调用的方法入口地址是
0x101326000 + 0x818
我们不妨Go Address:0x101326818 看一下
0000000101326818dq0x100cf2a9b; @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
【利用简单的逆向来解决GCD崩溃】转载链接
有兴趣一起研究 群号:112365317

    推荐阅读