从 swift 获取人类可读的堆栈跟踪

Boa*_*rdy 7 ios swift

我第一次在 Apple iOS 项目上工作,我需要检索人类可读的堆栈跟踪,包括类名称和行号等,我可以将其存储为字符串以供记录和/或将来存储在某处。

我有各种各样的堆栈跟踪,但它似乎是符号,所以它不太容易看清实际发生了什么。

以下是我目前拥有的代码:

Thread.callStackSymbols.forEach({print($0)})
Run Code Online (Sandbox Code Playgroud)

堆栈跟踪的示例如下

0   TestCrash                           0x0000000104056e1c $s9TestCrash14ViewControllerC03btnaB0yyypF + 1480
1   TestCrash                           0x0000000104057320 $s9TestCrash14ViewControllerC03btnaB0yyypFTo + 76
2   UIKitCore                           0x00000001843d2fc4 -[UIApplication sendAction:to:from:forEvent:] + 96
3   UIKitCore                           0x0000000183d70c80 -[UIControl sendAction:to:forEvent:] + 220
4   UIKitCore                           0x0000000183d70fc4 -[UIControl _sendActionsForEvents:withEvent:] + 352
5   UIKitCore                           0x0000000183d6f924 -[UIControl touchesEnded:withEvent:] + 532
6   UIKitCore                           0x000000018440d034 -[UIWindow _sendTouchesForEvent:] + 1112
7   UIKitCore                           0x000000018440e920 -[UIWindow sendEvent:] + 3824
8   UIKitCore                           0x00000001843ea2ac -[UIApplication sendEvent:] + 608
9   UIKitCore                           0x000000018446f8bc __processEventQueue + 13600
10  UIKitCore                           0x0000000184467434 __eventFetcherSourceCallback + 108
11  CoreFoundation                      0x00000001803654f4 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 24
12  CoreFoundation                      0x00000001803653f4 __CFRunLoopDoSource0 + 204
13  CoreFoundation                      0x000000018036474c __CFRunLoopDoSources0 + 256
14  CoreFoundation                      0x000000018035ed94 __CFRunLoopRun + 760
15  CoreFoundation                      0x000000018035e58c CFRunLoopRunSpecific + 572
16  GraphicsServices                    0x000000018b9c2740 GSEventRunModal + 160
17  UIKitCore                           0x00000001843ccbf0 -[UIApplication _run] + 964
18  UIKitCore                           0x00000001843d19d0 UIApplicationMain + 112
19  libswiftUIKit.dylib                 0x00000001b220f328 $s5UIKit17UIApplicationMainys5Int32VAD_SpySpys4Int8VGGSgSSSgAJtF + 100
20  TestCrash                           0x0000000104058308 $sSo21UIApplicationDelegateP5UIKitE4mainyyFZ + 120
21  TestCrash                           0x0000000104058280 $s9TestCrash11AppDelegateC5$mainyyFZ + 48
22  TestCrash                           0x000000010405834c main + 32
23  libdyld.dylib                       0x0000000180223cbc start + 4
Run Code Online (Sandbox Code Playgroud)

这样做的原因是我希望能够将堆栈上传到服务器进行日志记录(是的,我了解崩溃报告服务,我正在创建自己的版本)。

在Xcode构建设置我已经设置Deployment PostprocessingStrip Debug Symbols During Copy并且Strip Linked Product但它似乎并没有太大的差别。

我还查看了 Linux 的 dwarfdump 和 atos 项目(这是将发送日志的服务器类型),但这些似乎都不起作用,因为它要么只是再次给我十六进制值,要么 dwarfdump 只是抛出了一些错误,但两者都在我发现的这些项目中,我发现似乎不受支持并且现在已经很老了,所以我猜在编译代码的情况下发生了一些变化,这意味着这不再有效,因此为什么要避免构建过程剥离调试信息,所以它以编程方式可用。

Pra*_*tti 7

所以,我觉得首先是重新实现像火力地堡Crashlytics崩溃报告解决方案是不平凡的,并不可取给出的服务质量是Crashlytics提供免费的。崩溃报告很困难,因为您需要避免框架本身崩溃,因为与内存地址、函数指针、操作系统信号和多线程的低级接口没有类型安全和垃圾收集(在 C++ 中)。众所周知,测试给出各种可能的崩溃、应用程序状态、不同的设备(旧设备需要轻量级)以及具有死锁/线程/内存泄漏的边缘情况也是非常困难的。如果你仍然想继续,我将在下面概述主要考虑因素。

运行时符号化的问题是实现 API 以读取调试符号、上传符号和拦截异常处理程序。callStackSymbols不是意在机器可读的,所以你不得不重新实现使用功能,如调用堆栈解析器callStackReturnAddresses

这很重要,因为不仅崩溃必须被符号化,而且需要在应用程序因崩溃而终止之前上传到服务器/缓存(通常通过在检查应用程序是否仍然活动时忙等待来完成) . 这意味着代码需要高效运行以生成带有文件和行号元数据的堆栈帧(C++ 最适合)。

没有高级 C++ 库的情况下执行异步上传,并且在不使用本机 Foundation API 的情况下进行持久本地缓存是具有挑战性的(事件还需要在专用线程上排队,使用锁来确保同步)。除了语言障碍之外,名称还需要用线程信息进行符号化和解调(每个堆栈帧还需要包括当时其他每个线程的状态)。默认情况下,崩溃堆栈跟踪确实包含方法名称(已损坏),但需要在应用程序运行时上传文件名和行号等附加信息。您看到十六进制数字是内存地址,因此这些需要在运行时转换为适当的对象元数据并发送。在 C 中拦截异常的方法是使用Signal Handlers - 拦截 iOS 内核信号:

{SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGSEGV, SIGSYS, SIGTRAP};
Run Code Online (Sandbox Code Playgroud)

如果信号不是其中之一,您仍然可以发送请求,但使用“未知”信号处理程序来拦截崩溃

话虽如此,使用调试符号将崩溃报告器拼接在一起可能是可行的。你能做到这一点只有一次,当你存档应用程序(你并不需要一个物理设备)。您可以在将应用程序发送到 App Store 时执行一次此操作。Fastlane 可以通过输出目录简化此操作(另一种方法是在 xcodebuild 中使用环境变量 DWARF 目标文件夹)。然后您可以解析服务器上的调试符号。

找到.xcarchive并执行此操作以将主要目标和任何链接框架的 dsym 压缩在一起:

ditto -c -k --keepParent -rsrc <AppName>.xcarchive/dSYMs/*.dSYM <AppName>-${PRODUCT_VERSION}-dSYM.zip
Run Code Online (Sandbox Code Playgroud)

你提到你不知道在哪里可以找到调试符号。如果您想知道档案所在的位置,可以转到 Xcode Organizer,Archives,然后右键单击 Show in Finder。然后右键单击以显示包内容以查看 dSYM。

即使您对相当基本的崩溃报告器感到满意,您也需要加密服务器上传,因为在发送原始崩溃日志时代码库可能会被逆向工程!您还需要手动检查Build UUID,以确保对崩溃的应用程序版本进行符号化(否则符号化将失败)。

另一种方法是使用 App Store 提供的崩溃日志(可在 Xcode 中下载)。这样做的优势主要是易于设置、与 Xcode 的集成以及报告的质量。如果您坚持使用 Apple,您还可以获得 Jetsam 内存日志警告。然后,服务器本身可以使用 dSYM 和脚本轻松地自动构建崩溃报告:

//Put dsyms and crash log in one folder and navigate to that folder
export DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer"
cp -i /Applications/Xcode.app/Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/Resources/symbolicatecrash ./
./symbolicatecrash unsymbolicated.crash > symbolicated.crash
Run Code Online (Sandbox Code Playgroud)

我希望这为您指明了正确的方向;不幸的是,无法为您提供功能齐全的崩溃报告解决方案的完整代码(它太宽泛、太复杂,更不用说超出了 SO 问题的范围)。:-)