Mac/iOS手动输出 调用堆栈,并符号化

如果想在代码里面调用某个方法的时候输出调用堆栈的话,我们一般这样做。
直接调用这个方法 [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。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值