我們平時在開發過程中,偶爾會遇到一些崩潰日志,看到堆棧就想放棄的,例如下面這個,很明顯的野指針崩潰,但是不知道崩潰在哪里
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
- 打開Hopper,加載這個DemoApp的可執行文件
- Go Address : ** 0x79C25C**
很快的第二個驚喜
在這個案例上,我們很幸運的,一下子就圈定了崩潰點的范圍,如下
; ================ 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,即可完成子程序的調用返回。以上的描述可用指令完成。