我想捕获iOS应用程序中发生的所有异常并将它们记录到文件中,并最终将它们发送到应用程序使用的后端服务器.
我一直在阅读有关这个主题的内容,并发现设备发送的信号的使用情况并对其进行处理,但我不确定它是否会破坏App Store评论指南,或者它可能会引入其他问题.
我添加了以下内容AppDelegate:
NSSetUncaughtExceptionHandler { (exception) in
log.error(exception)
}
signal(SIGABRT) { s in
log.error(Thread.callStackSymbols.prettified())
exit(s)
}
signal(SIGILL) { s in
log.error(Thread.callStackSymbols.prettified())
exit(s)
}
signal(SIGSEGV) { s in
log.error(Thread.callStackSymbols.prettified())
exit(s)
}
Run Code Online (Sandbox Code Playgroud)
exit()kill(getpid(), SIGKILL)而不是exit()?资源
Mat*_*tie 16
以前的Crashlytics iOS SDK维护者.
您上面编写的代码确实存在许多技术问题.
首先,实际上很少有函数被定义为在信号处理程序内调用是安全的.man sigaction列出他们.您编写的代码不是信号安全的,并且会不时发生死锁.这一切都将取决于崩溃的线程当时正在做什么.
第二个是你试图在处理程序之后退出程序.您必须记住,信号/异常处理程序是进程范围的资源,您可能不是唯一使用它们的人.您必须保存预先存在的处理程序,然后在处理后恢复它们.否则,您可能会对应用可能正在使用的其他系统产生负面影响.正如您目前所写的那样,即使是Apple自己的崩溃记者也不会被调用.但是,也许你想要这种行为.
第三,您没有捕获所有线程堆栈.这是崩溃报告的关键信息,但增加了很多复杂性.
第四,信号实际上不是最低级错误系统.不要与运行时异常混淆(即NSException)mach异常是用于在iOS上实现信号的基础机制.它们是一个更强大的系统,但也复杂得多.信号有一堆陷阱和限制,马赫例外可以解决.
这些只是让我头疼的问题.崩溃报告是棘手的业务.但是,我不希望你认为它是神奇的,当然不是.您可以构建一个有效的系统.
我想指出的一件事是,崩溃记者没有给你关于失败的反馈.所以,你可能会构建一些可以在25%的时间内工作的东西,而且因为你只看到有效的报告,你会认为"嘿,这很有效!".Crashlytics必须多年来努力确定失败的原因并尝试减轻它们.如果这一切都是有趣的,你可以检查出一个讲我做对Crashlytics系统.
更新:
那么,如果您发送此代码会发生什么?好吧,有时你会收到有用的报道.有时,您的崩溃处理代码本身会崩溃,这将导致无限循环.有时您的代码会死锁,并有效地挂起您的应用.
Apple已经制作了exit公共API(无论好坏),因此您完全可以使用它.
我建议继续走这条路只是为了学习目的.如果你有一个你关心的真实应用程序,我认为集成现有的开源报告系统并将其指向你控制的后端服务器将更负责任.没有第三方,也没有必要担心弊大于利.
可以创建自定义崩溃报告器,但绝对不建议这样做,因为后台发生的很多事情很容易被遗忘,并且可能会引入许多未定义的行为。即使使用第三方框架也可能很麻烦,但这通常是更好的方法。
\n\n感谢大家提供有关此主题的信息。
\n\n我在原始问题中提到的方法将对苹果自己的崩溃报告器产生影响,并且由于信号处理不当而引入未定义的行为。UNIX 信号并未涵盖异步信号安全函数的所有错误和 API 处理工作。苹果的崩溃报告器使用的 Mach 异常处理是更好的选择,但它更复杂。
\n\nexit()破坏Apple App Store审核吗?不会。的使用exit()更多的是关系到app的正常运行。如果应用程序无论如何都会崩溃,则调用exit()不是问题。
引用爱斯基摩人的话:
\n\n\n\n\n您不得调用 exit。这样做有两个问题:
\n\nexit 不是异步信号安全的。事实上,exit 可以通过 atexit 注册的处理程序运行任意代码。如果您想退出该进程,请调用 _exit。
\n\n无论如何,退出进程都是一个坏主意,因为它会阻止Apple崩溃报告器运行或导致它记录错误的状态(信号处理程序的状态而不是崩溃线程的状态)。
\n\n更好的解决方案是取消注册信号处理程序(将其设置为\n SIG_DFL),然后返回
\n
由于我将这个问题交叉发布到苹果的官方支持论坛,并从著名的爱斯基摩人那里得到了非常长的描述性答案,我想与任何决定走与我相同的道路并开始研究这种方法的人分享。
\n\n引自爱斯基摩人
\n\n\n\n\n在我们开始之前,我\xe2\x80\x99d 希望您看一下我的新文章“实施\n 您自己的崩溃报告者”帖子。我\xe2\x80\x99一直想写这个\n一段时间,你的问题给了我一个很好的借口来分配\n时间。
\n\n你写了:
\n\n我需要捕获 iOS 应用程序中发生的所有异常并将它们记录到文件中,并最终将它们发送到应用程序使用的后端服务器。
\n\n我强烈建议不要这样做。我的《实现你自己的\n 崩溃报告》帖子解释了为什么这如此困难。它还提供了一些关于如何避免问题的建议,但最终\xe2\x80\x99 无法\n 实现可靠、二进制\n 兼容且足够的第三方崩溃报告程序调试复杂的问题
\n\n抛开这个问题,让\xe2\x80\x99s 看看你的具体问题:
\n\n这是个好方法吗?
\n\n不。问题是你的极简崩溃报告器会扰乱 Apple 崩溃报告器的\n 行为。上述帖子详细讨论了这个问题。
\n\n它会因为使用 exit() 而违反 App Store 审查准则吗?
\n\n不。iOS\xe2\x80\x99s 禁止调用 exit 是为了应用程序的正常运行。如果您的应用程序无论如何都会崩溃,则调用 exit\n 不是问题。
\n\n但是,调用 exit 会加剧我在上一点中提到的问题。
\n\n使用kill(getpid(), SIGKILL) 代替更好吗?
\n\n这不会大幅改善事情。
\n\ncallStackSymbols 没有符号化,有没有办法符号化\n callStackSymbols?
\n\n不可以。设备上的符号化非常棘手,应该避免。我再次在上面引用的帖子中详细讨论了这一点。
\n\n分享和享受
\n
由于链接可能会损坏,我也会引用帖子。
\n\n\n\n实现你自己的崩溃报告器
\n\n我经常收到有关第三方崩溃报告的问题。这些\n通常出现在以下两种情况之一:
\n\n\n
\n\n- 人们正在尝试实现自己的崩溃报告程序。
\n- 人们已经实现了自己的崩溃报告器,并尝试根据它生成的报告来调试问题。
\n这是一个复杂的问题,这篇文章是我试图解决其中一些复杂问题的尝试。
\n\n如果您对我在这里提出的任何问题有后续问题,\n请在 中开始一个新线程。
\n\n重要以下所有内容都是我自己的直接经验。这些都不应被视为官方 DTS 政策。如果您有问题需要官方解答(也许您正试图说服您的老板,实现您自己的崩溃报告器是一个非常糟糕的主意:-),您应该打开 DTS技术支持事件,我们可以在那里讨论事情。
\n\n分享并享受 \xe2\x80\x94 Quinn \xe2\x80\x9cThe Eskimo!\xe2\x80\x9d Apple 开发者关系、\n 开发者技术支持、核心操作系统/硬件
\n\nlet myEmail = "eskimo"\n + "1" + "@apple.com"
\n\n范围
\n\n首先,我只能谈谈这个问题的技术方面。还有其他方面超出了我的职权范围:
\n\n\n
\n\n- 我不为应用程序审核工作,只有他们才能给出关于商店允许或不允许哪些内容的明确答案。
\n- 做你自己的崩溃报告者会对隐私产生重大影响。
\n重要信息如果您实施自己的崩溃报告程序,请与律师讨论隐私影响。
\n\n这篇文章假设您正在实现自己的崩溃报告器。\n 很多人使用来自另一个第三方的崩溃报告器。从我的角度来看,这些是同一件事。如果您使用自定义崩溃报告器,则无论实际代码来自何处,您都应对其行为(无论好坏)负责。
\n\n注意:如果您使用其他第三方的崩溃报告器,请运行保留 Apple 崩溃报告中概述的测试,以验证\n\xe2\x80\x99 是否正常工作。
\n\n一般建议
\n\n我强烈建议不要实现自己的崩溃报告程序。 实现一个基本的崩溃报告器非常容易,它足以调试简单的问题。创建一个好的崩溃报告器是不可能的,它可靠、二进制兼容并且足以调试复杂的问题。
\n\n\xe2\x80\x9c不可能?\xe2\x80\x9d,我听到你问,\xe2\x80\x9c\xe2\x80\x99s 对于 Quinn 来说是一个非常强烈的词。他\xe2\x80\x99s 通常更加谨慎。\xe2\x80\x9d 是的,\xe2\x80\x99s 是的,我\n 通常更加谨慎,但在这种情况下我\xe2\x80\x99m对这个结论非常有信心。
\n\n实现自己的崩溃报告器有两个基本问题:
\n\n\n
\n\n- \n
在 iOS(以及其他基于 iOS 的平台、watchOS 和 tvOS)上,您的崩溃报告程序必须在崩溃的进程内运行。这意味着它永远不可能 100% 可靠。如果进程崩溃,那么根据定义,它处于未定义状态。试图在这种状态下做真正的工作只是自找问题1。
- \n
为了获得良好的结果,您的崩溃报告器必须与系统实现细节密切相关。这些可能会随着版本的不同而发生变化,这会使崩溃报告者所做的假设无效。对于 Apple 崩溃报告器来说,这不是一个问题,因为它是随系统一起提供的。然而,您的产品中内置的\xe2\x80\x99 崩溃报告程序总是很脆弱。
\n\n我\xe2\x80\x99m 在这里讲述来之不易的经验。在 PowerPC 到 Intel 的过渡期间,我在 DTS 工作,看到很多使用自定义崩溃报告程序的人在这个过程中遇到了困难。
尽管如此,这篇文章仍然存在,因为很多人忽略了我的一般建议,因此后续部分包含有关特定技术问题的建议。
\n\n警告不要将以下任何内容解释为鼓励实现您自己的崩溃报告程序。我强烈建议不要这样做。\n 但是,如果您忽略我的建议,那么您至少应该尝试\n 将风险降至最低,这就是本文档其余部分的内容。
\n\n1在 macOS 上,\xe2\x80\x99 的崩溃报告器可能会耗尽进程,就像 Apple 崩溃报告器一样。然而,这也带来了它自己的问题:当运行完进程时,您无法访问崩溃进程的各种关键状态,而不必严格绑定到不被视为 API 的实现细节。
\n\n保留 Apple 崩溃报告
\n\n您必须确保您的崩溃报告器不会\xe2\x80\x99 干扰 Apple\n 崩溃报告器。部分崩溃不是由代码引起的,而是由框架代码中的问题引起的,编写得不好的崩溃报告器会扰乱 Apple 崩溃报告器,并使诊断这些问题变得更加困难。
\n\n此外,在处理真正难以调试的问题时,您确实需要 Apple 崩溃报告中显示的 xe2x80x99s 更晦涩的信息。如果你破坏这些信息,最终会使困难问题变得更加困难。
\n\n为了避免这些问题,我建议您测试崩溃报告器\xe2\x80\x99s\n 对 Apple 崩溃报告器的影响。基本思想是:
\n\n\n
\n\n- 创建一个生成一组特定崩溃的程序。
\n- 经历每一次崩溃。
\n- 验证您的崩溃报告器是否产生合理的结果。
\n- 验证 Apple 崩溃报告程序是否也能生成合理的结果。
\n对于第 1 步,您的测试套件应包括:
\n\n\n
\n\n- 您的代码引发的未处理的语言异常
\n- 操作系统抛出的未处理的语言异常(访问
\nNSArray越界是获取此异常的简单方法)- 内存访问异常
\n- 非法指令异常
\n- 断点异常
\n确保在主线程和辅助线程上测试所有这些情况。
\n\n关于步骤 4,检查生成的 Apple 崩溃报告\n 是否包含以下正确值:
\n\n\n
\n\n- 异常信息
\n- 崩溃的线程
\n- 该线程\xe2\x80\x99s状态
\n- 任何特定于应用程序的信息,尤其是最后的异常回溯
\n信号
\n\n许多第三方崩溃报告程序使用 UNIX 信号来捕获崩溃。\n 这很遗憾,因为使用 Mach 异常处理(Apple 崩溃报告器使用的机制)通常是更好的选择。\n 然而,有两个原因支持 UNIX 信号而不是 Mach\n 异常处理:
\n\n\n
\n\n- 在基于 iOS 的平台上,您的崩溃报告器必须在进程内运行,并且进行进程内 Mach 异常处理是不可行的。
\n- 人们对 UNIX 信号更加熟悉。Mach 异常处理和一般的 Mach 消息传递非常晦涩难懂。
\n如果您为崩溃报告器使用 UNIX 信号,请注意此 API 有一些明显的缺陷。首先也是最重要的,您的信号处理程序只能使用异步信号安全函数1。
\n\nsigaction您可以在手册页\n 2中找到这些函数的列表。警告此列表不包括
\n\nmalloc. 这意味着崩溃报告器 xe2x80x99s 信号处理程序无法使用 Objective-C 或 Swift,因为 xe2x80x99s 无法限制这些语言运行时如何分配内存。这意味着您\xe2\x80\x99 被困在C 或C++ 中,但即使在那里,您也必须小心遵守此约束。\n\n\n特工:\xe2\x80\x99 比你想象的还要糟糕。
\n许多崩溃报告使用类似的函数
\n\nbacktrace(请参阅其手册页)\n 从其信号处理程序中获取回溯。有\xe2\x80\x99s两个问题\n:\n
\n\n- \n
backtrace不是异步信号安全函数。- \n
backtrace使用 na\xc3\xafve 算法,该算法不能很好地处理交叉信号处理程序堆栈帧 [3]。后一个示例特别令人担忧,因为它隐藏了触发信号的堆栈帧的身份。
\n\n如果您要从信号中回溯,则必须使用崩溃的线程 xe2x80x99 状态(可通过 handlers
\n\nuap参数访问)来启动回溯。相应地,如果您的崩溃报告器想要记录\n 崩溃线程的状态,\xe2\x80\x99 就是获取它的地方。
\n\n最后,\xe2\x80\x99s 的问题是如何从信号处理程序中退出。\n 你不能调用 exit。这样做有两个问题:
\n\n\n
\n\n- \n
exit异步信号不安全。事实上,exit可以通过注册的处理程序运行任意代码atexit。如果您想退出进程,请调用_exit.- 无论如何,退出进程都是一个坏主意,因为它会阻止 Apple 崩溃报告器运行或导致其记录\n 不正确的状态(信号处理程序的状态,而不是\n 崩溃线程的状态)。
\n更好的解决方案是取消注册信号处理程序(将其设置为 \n
\n\nSIG_DFL)然后返回。这将导致崩溃的进程继续执行,再次崩溃,并通过 Apple 崩溃报告器生成崩溃报告。1虽然崩溃报告器捕获的常见信号在技术上不是异步信号(除了
\n\nSIGABRT),但您仍然必须将它们视为异步信号,因为它们可以随时在任何线程上发生。2将此列表扩展到在系统调用上作为精简填充程序实现的其他例程是合理的。
\n\nvm_read例如,我对从信号处理程序调用(见下文)没有任何疑虑。[3] 当内核在线程上运行信号处理程序时,交叉信号处理程序堆栈帧由内核推送到堆栈。由于没有 API 来了解这些帧的结构,因此无法单独回溯这些帧之一。我\xe2\x80\x99m很乐意\n详细介绍,但\xe2\x80\x99s确实与此讨论无关。如果\n你\xe2\x80\x99感兴趣,请开始一个新线程,我们可以在那里聊天。
\n\n读取记忆
\n\n信号处理程序必须非常小心它所接触的内存,因为该内存的内容可能已被触发信号的崩溃损坏。我的一般规则是信号处理程序可以安全地访问:
\n\n\n
\n\n- 它的代码
\n- 它的堆栈
\n- 它的论据
\n- 不可变的全局状态
\n在最后一点中,我\xe2\x80\x99m使用immutable来表示启动后不可变。我认为在进程启动时、安装信号处理程序之前设置一些全局状态是合理的,然后在信号处理程序中依赖它。
\n\n安装信号处理程序后更改任何全局状态都是危险的,如果需要这样做,则必须小心确保信号处理程序看到一致的状态,即使可能在中途发生崩溃改变。
\n\n请注意,您不能使用互斥体来保护此全局状态,因为互斥体不是异步信号安全的(即使它们是您的,如果互斥体由线程持有,则死锁崩溃了)。您应该能够为此使用原子操作,但是众所周知,原子操作很难正确使用(如果每次我向开发人员指出他们\xe2时我都有一美元\n) \x80\x99 不正确地使用原子\n 操作,我\xe2\x80\x99d 的报酬非常低(-:但是\xe2\x80\x99 仍然有很多开发人员!)。
\n\n如果您的信号处理程序读取其他内存,则必须注意避免在读取时发生崩溃。\xe2\x80\x99s 没有用于此\n 1的 BSD 级别 API ,因此我建议您使用
\n\nvm_read.1执行此操作的传统 UNIX 方法是安装\n 信号处理程序来捕获由读取触发的任何内存异常,\n 但现在我们\xe2\x80\x99 正在讨论信号处理程序中的信号处理,并且\n\xe2 \x80\x99s 只是愚蠢的。
\n\n写入文件
\n\n如果您想从信号处理程序编写崩溃报告,则必须使用低级 UNIX API(
\n\nopen、write、close),因为只有这些低级 API 被记录为异步信号安全。您还必须提前设置路径,因为用于确定文件写入位置的标准 API(NSFileManager例如)不是异步信号安全的。离线符号
\n\n不要尝试从信号处理程序中进行符号化。相反,\n 向崩溃报告写入足够的信息以支持脱机\n 符号化。具体来说:
\n\n\n
\n\n- 要符号化的地址
\n- 对于进程中的每个 Mach-O 映像:\n \n
\n\n
- 图像路径
\n- 图像UUID
\n- 图片加载地址
\n\n\n
<mach-o/dyld.h>您可以使用\n 1中的 API 获取大部分 Mach-O 图像信息 。但请注意,这些 API 不是异步信号安全的。您\xe2\x80\x99需要提前获取此信息并缓存它以供信号处理程序记录。当您处理加载和卸载代码时,Mach-O 映像列表可能会发生变化,这一事实使情况变得复杂。这需要您与信号处理程序共享可变状态,这正是我在读取内存中建议反对的做法。
\n\n注意
\n\n_dyld_register_func_for_add_image您可以分别使用\n 和来了解图像加载和卸载_dyld_register_func_for_remove_image。1我相信您\xe2\x80\x99需要解析Mach-O加载命令来获取\n图像UUID。
\n
| 归档时间: |
|
| 查看次数: |
666 次 |
| 最近记录: |