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。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章