How can I catch SIGSEGV (segmentation fault) and get a stack trace under JNI on Android?
我正在将一个项目转移到新的Android本机开发工具包(即JNI)中,我想捕获sigsegv,如果它发生(也可能是sigill、sigabrt、sigfpe),以便呈现一个很好的崩溃报告对话框,而不是(或之前)当前发生的事情:进程立即不确定地死亡,操作系统可能试图重新开始吧。(编辑:jvm/dalvik-vm捕获信号并记录堆栈跟踪和其他有用信息;我只想为用户提供将该信息通过电子邮件发送给我的选项。)
情况是:一个很大的C代码,我没有编写,在这个应用程序(大部分游戏逻辑)做大部分的工作,尽管它在许多其他平台上被很好的测试,但是在我的Android端口中,我完全有可能为它提供垃圾并导致本地代码崩溃,所以我希望CurrE的崩溃转储(本地和Java)tly出现在android日志中(我想在非android情况下是stderr)。我可以随意地修改C和Java代码,尽管回调(JNI的输入和输出)都是40左右,很明显是小差别的加分。
我听说过j2se,libjsig.so中的信号链库,如果我能在android上安全地安装这样的信号处理器,这就解决了我的问题,但是我没有看到android/dalvik这样的库。
- 如果您可以通过包装脚本启动Java虚拟机,您可以检查应用程序是否异常退出,并执行错误报告。这将使您能够清楚地捕捉到各种异常出口,无论它们是sigsegv、sigkill还是其他什么。不过,我不认为这是可能的股票Android应用程序,所以张贴这个评论(从答案转换)。
- 还可以看到:不能用Valgrind运行Java Android程序,如何用包装脚本启动Android应用程序(在ADB shell中)。
- 答案需要更新。接受答案中提供的源代码将导致由于调用非异步信号安全函数而导致未定义的行为。请参见:stackoverflow.com/questions/34547199/…
编辑:从JellyBean开始,你就不能得到堆栈跟踪,因为READ_LOGS消失了。:
实际上,我有一个信号处理程序在工作时没有做任何太奇怪的事情,并且使用它发布了代码,您可以在GitHub上看到(编辑:链接到历史版本;从那时起我就删除了崩溃处理程序)。以下是如何:
使用sigaction()捕捉信号并存储旧的处理程序。(安卓C:570)
时间过了,就会发生一个分段故障。
在信号处理程序中,最后一次调用JNI,然后调用旧的处理程序。(安卓C:528)
在该JNI调用中,记录任何有用的调试信息,并对标记为需要处于其自身进程中的活动调用startActivity()。(SjtSimuls.java:962,ANDROIDMANDS.XML:28)
当您从Java返回并调用旧的处理程序时,Android框架将连接到EDCOX1 OR 3,以便为您记录一个好的本地跟踪,然后该进程将死亡。(debugger.c、debuggerd.c)
同时,您的碰撞处理活动正在启动。实际上,您应该将PID传递给它,以便它可以等待第5步完成;我不这样做。在这里,您向用户道歉并询问是否可以发送日志。如果是这样的话,收集logcat -d -v threadtime的输出并发射一个ACTION_SEND,填写接收者、主题和主体。用户必须按"发送"。(CRASHANTHLLR.java,SGTSimple):Java:462,StR.xml:41
当心logcat失败或需要几秒钟以上。我遇到过一个设备,T-Mobile Pulse/Huawei U8220,logcat立即进入T状态并挂起。(CRASHANTHANDL.java:70,SCOR.XML: 51)
在非Android环境中,有些情况会有所不同。你需要收集你自己的本地跟踪,看看另一个问题,这取决于你有什么类型的libc。您需要处理转储跟踪、启动单独的崩溃处理程序进程,并以适合您的平台的某些适当方式发送电子邮件,但我认为一般的方法仍然有效。
- 理想情况下,你应该检查一下图书馆是否发生了车祸。如果它发生在其他地方(比如说,在虚拟机内部),来自信号处理程序的JNI调用可能会严重混淆事情。这并不是世界末日,因为无论如何,你都处于崩溃的中期,但它可能会使诊断虚拟机崩溃更加困难(或者导致一个奇怪的虚拟机崩溃,最终出现在一个Android错误报告中,并迷惑了所有人)。
- 你很高兴@chris分享你的研究项目!
- 谢谢,这对找到我的JNI疯了的地方很有用。还有,来自华盛顿校友的问候!
- @法登是真的,但我不知道怎么弄清楚。因为在Debuggerd记录任何东西之前,我需要自己做一些堆栈展开(在Android上看起来非常重要),或者经常设置一个in/out标志或者分离/附加处理程序,在这种情况下,我肯定会错过某个位置。
- 从服务启动新流程中的活动还需要以下代码:newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);。
- 是的,活动新任务很重要,如果你不使用它,新活动就不会开始。
- 这个解决方案在果冻豆下仍然有效吗?步骤6是否会记录任何debuggerd输出?
- 我创建了一个小库,它使用这些步骤为JNI代码启用ACRA崩溃报告。你的帖子很有帮助,非常感谢!
我有点晚了,但我有同样的需求,我开发了一个小库来解决这个问题,通过捕获JNI代码中的常见崩溃(SEGV、SIBGUS等),并用常规java.lang.Error异常替换它们。另外,如果客户机在android上运行>=4.1.1,那么堆栈跟踪将嵌入崩溃的解析后跟踪(包含完整本机堆栈跟踪的伪跟踪)。您将无法从恶性崩溃中恢复(例如,如果您损坏了分配器),但至少它应该允许您从大多数崩溃中恢复。(请报告成功和失败,代码是全新的)
更多信息请访问https://github.com/xroche/coffeeecatch(代码为BSD 2条款许可证)
fwiw,谷歌BreakPad在Android上运行良好。我做了移植工作,我们将把它作为Firefox移动的一部分进行运送。它需要一些设置,因为它不提供客户端的堆栈跟踪,而是向您发送原始堆栈内存,并执行堆栈遍历服务器端操作(因此您不必在应用程序中附带调试符号)。
- 考虑到绝对丢失的文档,几乎不可能配置breakpad。
- 这真的不难,而且项目wiki上有很多文档。事实上,对于Android来说,现在有了一个ndk构建makefile,它应该非常容易使用:code.google.com/p/google breakpad/source/browse/trunk/…
- 您还需要编译为Android预处理调试符号文件的模块,并且只能在Linux上编译该模块。在Mac上编译时,它只构建Mac/IOS DSYM预处理器。
在我有限的经验(非Android)中,JNI代码中的SIGSEGV通常会在控制返回到Java代码之前崩溃JVM。我隐约记得听说过一些非Sun的JVM可以让你抓到Sigsegv,但在很小的范围内,你不能指望它能做到。
您可以尝试在C语言中捕获它们(参见sigaction(2)),尽管在sigsegv(或sigfpe或sigill)处理程序之后可以做很少的事情,因为进程的持续行为是官方定义的。
- 好吧,在"忽略不是由kill(2)或raise(3)产生的sigfpe、sigill或sigsegv信号"之后,行为是未定义的,但在捕获这样的信号时不一定如此。当前的计划是尝试调用回Java的C信号处理程序,不知何故,在不终止进程的情况下终止线程。这可能或不可能。-)
- C回溯指令:stackoverflow.com/questions/76822/…
- …但我不能使用backtrace(),因为Android不使用glibc,它使用仿生。:—(需要从unwind.h得到的与_Unwind_Backtrace有关的东西。