如果想在代碼裏面調用某個方法的時候輸出調用堆棧的話,我們一般這樣做。
直接調用這個方法 [NSThread callStackSymbols];
- DEBUG 或 這 RELEASE 直接運行
就可以輸出下面的調用堆棧
0 DSYMTest 0x0000000100000f66 -[ViewController getNowBacktrace] + 41
1 DSYMTest 0x0000000100000f12 -[ViewController method3] + 19
2 libdispatch.dylib 0x0000000100342f1b _dispatch_client_callout + 8
3 libdispatch.dylib 0x00000001003462be _dispatch_continuation_pop + 563
4 libdispatch.dylib 0x000000010035aa87 _dispatch_source_invoke + 2124
5 libdispatch.dylib 0x0000000100351800 _dispatch_main_queue_callback_4CF + 1425
6 CoreFoundation 0x00007fff2c541227 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9
7 CoreFoundation 0x00007fff2c540951 __CFRunLoopRun + 2289
8 CoreFoundation 0x00007fff2c53fe0e CFRunLoopRunSpecific + 455
9 HIToolbox 0x00007fff2b82c9db RunCurrentEventLoopInMode + 292
10 HIToolbox 0x00007fff2b82c715 ReceiveNextEventCommon + 603
11 HIToolbox 0x00007fff2b82c4a6 _BlockUntilNextEventMatchingListInModeWithFilter + 64
12 AppKit 0x00007fff29bc6ffb _DPSNextEvent + 965
13 AppKit 0x00007fff29bc5d93 -[NSApplication(NSEvent) _nextEventMatchingEventMask:untilDate:inMode:dequeue:] + 1361
14 AppKit 0x00007fff29bbfeb0 -[NSApplication run] + 699
15 AppKit 0x00007fff29baf3f0 NSApplicationMain + 777
16 libdyld.dylib 0x00007fff589b93d5 start + 1
17 ???
其中app 中的 getNowBacktrace 和 method3 被自動符號化了。
- 發佈的安裝APP
但是如果在 已經打包好的發佈的app 裏面的話,直接調用上面的方法 會得到未符號化的 堆棧 像這樣
0 DSYMTest 0x0000000100219cc3 DSYMTest + 3267
1 DSYMTest 0x0000000100219c6f DSYMTest + 3183
2 libdispatch.dylib 0x00007fff5896c63d _dispatch_client_callout + 8
3 libdispatch.dylib 0x00007fff5896ede6 _dispatch_continuation_pop + 414
4 libdispatch.dylib 0x00007fff5897df42 _dispatch_source_invoke + 2056
5 libdispatch.dylib 0x00007fff5897754b _dispatch_main_queue_callback_4CF + 813
6 CoreFoundation 0x00007fff2c541227 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9
7 CoreFoundation 0x00007fff2c540951 __CFRunLoopRun + 2289
8 CoreFoundation 0x00007fff2c53fe0e CFRunLoopRunSpecific + 455
9 HIToolbox 0x00007fff2b82c9db RunCurrentEventLoopInMode + 292
10 HIToolbox 0x00007fff2b82c715 ReceiveNextEventCommon + 603
11 HIToolbox 0x00007fff2b82c4a6 _BlockUntilNextEventMatchingListInModeWithFilter + 64
12 AppKit 0x00007fff29bc6ffb _DPSNextEvent + 965
13 AppKit 0x00007fff29bc5d93 -[NSApplication(NSEvent) _nextEventMatchingEventMask:untilDate:inMode:dequeue:] + 1361
14 AppKit 0x00007fff29bbfeb0 -[NSApplication run] + 699
15 AppKit 0x00007fff29baf3f0 NSApplicationMain + 777
16 libdyld.dylib 0x00007fff589b93d5 start + 1
可以看見 getNowBacktrace 和 method3 本來應該是這兩個方法的位置 被 DSYMTest 直接替換了,在第四列 也沒有 load address 存在, 比如這樣的
0 DSYMTest 0x0000000100219cc3 0x100219000 + 3267
我們從 符號解析的這篇文章 知道 發佈的 app 由於 ALSR 的存在,即使有 DSYM 文件,想要解析 堆棧的符號地址,也需要當前image 庫的load address。
那麼問題來了,我們怎麼才能在代碼中拿到 當前堆棧運行時的 load address 呢。
完整代碼如下
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self method3];
});
}
- (void)method3 {
NSLog(@"%@", [self getNowBacktrace]);
}
- (NSString *)getNowBacktrace {
//獲取程序的調用堆棧
NSArray *back = [NSThread callStackSymbols];
//獲取運行app 的load address
NSString *loadImageAddress = [self getPrismImageLoadAddress];
NSString *backtrace = [back componentsJoinedByString:@"\n"];
return [[NSString alloc] initWithFormat:@"\nprism Load image address:%@\ncall backtrace:\n%@", loadImageAddress, backtrace];
}
- (NSString *)getPrismImageLoadAddress {
const struct mach_header *executableHeader = NULL;
//遍歷所有綁定運行的image
for (uint32_t i = 0; i < _dyld_image_count(); i++){
const struct mach_header *header = _dyld_get_image_header(i);
//找到可執行文件類型的image
if (header->filetype == MH_EXECUTE){
executableHeader = header;
break;
}
}
//將指針地址轉換成string類型
NSString *address = [NSString stringWithFormat:@"%zi", (NSInteger)executableHeader];
return address;
}
可以看到 我們用 _dyld_get_image_header()
遍歷取 所有加載的image,然後找到 filetype == MH_EXECUTE
可執行文件的這個image 的頭指針。
運行上面的代碼,輸出的log 就變成了
prism Load image address:4297166848
call backtrace:
0 DSYMTest 0x0000000100219cc3 DSYMTest + 3267
1 DSYMTest 0x0000000100219c6f DSYMTest + 3183
2 libdispatch.dylib 0x00007fff5896c63d _dispatch_client_callout + 8
3 libdispatch.dylib 0x00007fff5896ede6 _dispatch_continuation_pop + 414
4 libdispatch.dylib 0x00007fff5897df42 _dispatch_source_invoke + 2056
5 libdispatch.dylib 0x00007fff5897754b _dispatch_main_queue_callback_4CF + 813
6 CoreFoundation 0x00007fff2c541227 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9
7 CoreFoundation 0x00007fff2c540951 __CFRunLoopRun + 2289
8 CoreFoundation 0x00007fff2c53fe0e CFRunLoopRunSpecific + 455
9 HIToolbox 0x00007fff2b82c9db RunCurrentEventLoopInMode + 292
10 HIToolbox 0x00007fff2b82c715 ReceiveNextEventCommon + 603
11 HIToolbox 0x00007fff2b82c4a6 _BlockUntilNextEventMatchingListInModeWithFilter + 64
12 AppKit 0x00007fff29bc6ffb _DPSNextEvent + 965
13 AppKit 0x00007fff29bc5d93 -[NSApplication(NSEvent) _nextEventMatchingEventMask:untilDate:inMode:dequeue:] + 1361
14 AppKit 0x00007fff29bbfeb0 -[NSApplication run] + 699
15 AppKit 0x00007fff29baf3f0 NSApplicationMain + 777
16 libdyld.dylib 0x00007fff589b93d5 start + 1
其中 4297166848 就是我們需要的load address,使用計算器 轉換成16進製爲 0x100219000
再運用 符號解析的這篇文章 我們這篇文章的 解析 方式進行解析就可以了。
比如運用 atos 解析第一行堆棧的符號
xcrun atos -o DSYMTest.app.dSYM/Contents/Resources/DWARF/DSYMTest -arch x86_64 -l 0x100219000 0x0000000100219cc3
輸出
-[ViewController getNowBacktrace] (in DSYMTest) (ViewController.m:82)
總結:
- 在debug 和 release 直接編譯運行的時候,直接輸出調用堆棧即可, xcode 會幫我們自動符號化
- 在發佈的app 中,在輸出調用堆棧的同時還必須輸出當前庫的load address。