XBAsyncStackTrace 0.1

XBAsyncStackTrace 0.1

xiaobochen 维护。



  • xiaobochen

XBAsyncStackTrace

关于iOS进行异步栈跟踪的记录

使用方法

将以下代码添加以开始记录异步栈跟踪。(目前只支持 dispatch 和 performSelector:onThread:withObject:waitUntilDone:modes:)

[[XBAsyncStackTraceManager sharedInstance] setMaxBackTraceLimit:32];//set the max back trace frame limit, default is 32.
[[XBAsyncStackTraceManager sharedInstance] beginHook];

在您的崩溃处理器中获取异步栈跟踪。

XBThreadAsyncTraceRecord *record = [[XBAsyncStackTraceManager sharedInstance] asyncTraceForPthread:pthread_for_crash_thread];
void **backTrace = record.asyncStackTrace.backTrace;
size_t size = record.asyncStackTrace.size;

为什么需要异步栈跟踪

- (void)callCrashFunc {
    id object = (__bridge id)(void*)1;
    [object class];
//    NSLog(@"remove this line will cause tail call optimization");
}
- (void)testDispatchAsyncCrash {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self callCrashFunc];
    });
}

注意:使用 -O1(debug 默认 -O0) 编译。猜猜这个代码崩溃时的栈跟踪会是什么?

* thread #3, queue = 'com.apple.root.default-qos', stop reason = signal SIGSEGV
  * frame #0: 0x000000010186fd85 libobjc.A.dylib`objc_msgSend + 5
    frame #1: 0x0000000104166595 libdispatch.dylib`_dispatch_call_block_and_release + 12
    frame #2: 0x0000000104167602 libdispatch.dylib`_dispatch_client_callout + 8
    frame #3: 0x000000010416a064 libdispatch.dylib`_dispatch_queue_override_invoke + 1028
    frame #4: 0x000000010417800a libdispatch.dylib`_dispatch_root_queue_drain + 351
    frame #5: 0x00000001041789af libdispatch.dylib`_dispatch_worker_thread2 + 130
    frame #6: 0x0000000104553169 libsystem_pthread.dylib`_pthread_wqthread + 1387
    frame #7: 0x0000000104552be9 libsystem_pthread.dylib`start_wqthread + 13

令人惊讶!实际上,由于 callCrashFunc 块最后一行调用另一个函数,如果启用了编译器优化,将进行尾部调用优化。所以 [object class] 的汇编代码将是跳转操作码而不是调用操作码(对于 ARM,它将是 b 而不是 bl,对于 x86,它将是 j 而不是 call)。这意味着实际的崩溃地址不会被推入栈中,这意味着栈跟踪将不会包含有关实际崩溃地址的任何信息。但是,XBAsyncStackTrace 会记录异步栈跟踪,就像在队列调试:启用回溯记录(Product -> Scheme -> Edit Scheme)时 Xcode 所做的那样(调试导航器会显示当前运行的函数被入队到哪个 dispatch 的堆栈帧)。以下显示 XBAsyncStackTrace 为示例崩溃记录的异步栈跟踪。

0   XBAsyncStackTraceExample            0x000000010c89d75c blockRecordAsyncTrace + 76
1   XBAsyncStackTraceExample            0x000000010c89d302 wrap_dispatch_async + 98
2   XBAsyncStackTraceExample            0x000000010c89c02c -[ViewController testDispatchAsyncCrash] + 92
3   XBAsyncStackTraceExample            0x000000010c89be3d -[ViewController viewDidLoad] + 269
4   UIKitCore                           0x0000000110ae44e1 -[UIViewController loadViewIfRequired] + 1186
5   UIKitCore                           0x0000000110ae4940 -[UIViewController view] + 27

##工作原理 我们钩接 dispatch 异步/after/屏障函数,包括阻塞版本和函数版本。在替换函数的开始,我们记录调用栈跟踪,并使用另一个块参数调用原始的 dispatch 函数,该参数将当前线程的异步堆栈跟踪设置为记录的堆栈跟踪,并在块结束时清除当前线程的异步堆栈跟踪。如果是“_f”版本,我们为新参数分配一个新的参数记录传入 dispatch 函数的函数和上下文以及当前调用堆栈跟踪,并将另一个函数传递给 dispatch,这样它就可以接收新参数,将当前线程的异步堆栈跟踪设置为记录的堆栈跟踪,调用原始函数,并在块结束时清除当前线程的异步堆栈跟踪,与块版本相同。在你的崩溃处理程序中,获取线程的异步堆栈跟踪。如果有,这必须是崩溃函数的异步堆栈跟踪。对于 performSelector:onThread:withObject:waitUntilDone:modes:,它做了同样的事情。

安装

pod 'XBAsyncStackTrace' 注意:XBAsyncStackTrace依赖于fishhook进行函数钩接。如果你通过Pod安装,Pod也会安装fishhook。如果你需要链接从源构建的XBAsyncStackTrace库,也需要链接fishhook。

示例

在 XBAsyncStackTraceExample 中,你应该先运行 pod install,然后打开工作空间。你应该在项目中设置断点,并在 lldb 中打印 "pro hand -p true -s false SIGSEGV",这样 lldb 就不会在 SIGSEGV 时停止,以便在调试过程中捕获崩溃。