如果想在代码里面调用某个方法的时候输出调用堆栈的话,我们一般这样做。
直接调用这个方法 [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。