如何在Android上的JNI下捕获SIGSEGV(分段错误)并获得堆栈跟踪?


92

我正在将一个项目移到新的Android Native Development Kit(即JNI)上,我想抓住SIGSEGV(如果它发生的话(可能还有SIGILL,SIGABRT,SIGFPE)),以便呈现一个不错的崩溃报告对话框,而不是(或之前)当前发生的情况:该进程立即异常死亡,并且操作系统可能会尝试重新启动它。(编辑: JVM / Dalvik VM捕获信号并记录堆栈跟踪和其他有用的信息;我只想向用户提供将这些信息通过电子邮件发送给我的选项。)

情况是:我没有编写大量的C代码,完成了该应用程序中的大部分工作(所有游戏逻辑),尽管在许多其他平台上都经过了充分的测试,但我在Android中完全有可能端口,将其作为垃圾进行处理并在本机代码中导致崩溃,因此我希望当前显示在Android日志中的崩溃转储(本机和Java)(我猜在非Android情况下将是stderr)。我可以随意修改C和Java代码,尽管回调(传入和传出JNI)的数量大约为40,显然,小差异的加分。

我听说过J2SE,libjsig.so中的信号链库,并且如果我可以在Android上安全地安装类似的信号处理程序,那将解决我的问题中的棘手部分,但是我没有看到这样的Android / Dalvik库。


如果可以通过包装脚本启动Java VM,则可以检查应用程序是否异常退出,并进行错误报告。这样一来,您就可以轻松捕获各种异常出口,无论它们是SIGSEGV,SIGKILL还是其他。但是,我认为这在股票Android应用程序中是不可能的,因此请将其作为注释发布(从答案转换为)。
sleske

另请参阅:无法使用Valgrind运行Java Android程序,了解如何使用包装脚本(在adb shell中)启动Android应用。
sleske

1
答案需要更新。接受的答案中提供的源代码将由于调用非异步信号安全函数而导致未定义的行为。请在这里看到:stackoverflow.com/questions/34547199/...
user1506104

Answers:


82

编辑:从Jelly Bean开始,您无法获取堆栈跟踪,因为READ_LOGS走了。:-(

实际上,我的信号处理程序没有做任何多余的工作,并且已经发布了使用它的代码,您可以在github上看到(编辑:链接到历史版本;从那时起我删除了崩溃处理程序)。这是如何做:

  1. 使用sigaction()捕捉到信号并存储旧的处理程序。(android.c:570
  2. 时间流逝,发生段错误。
  3. 在信号处理程序中,最后一次调用JNI,然后调用旧的处理程序。(android.c:528
  4. 在该JNI调用中,记录所有有用的调试信息,然后调用startActivity()标记为需要在其自己的进程中进行的活动。(SGTPuzzles.java:962AndroidManifest.xml中:28
  5. 当您从Java回来并调用该旧处理程序时,Android框架将连接到debuggerd为您记录一个不错的本机跟踪,然后该过程将终止。(debugger.cdebuggerd.c
  6. 同时,您的崩溃处理活动正在启动。确实,您应该将PID传递给它,以便它可以等待第5步完成;我不这样做 在这里,您向用户道歉,并询问是否可以发送日志。如果是这样,请收集的输出logcat -d -v threadtime并启动ACTION_SEND收件人,主题和正文已填写的用户。用户必须按发送。(CrashHandler.javaSGTPuzzles.java : 462strings.xml:41
  7. 当心logcat失败或花费超过几秒钟的时间。我遇到了一种设备,T-Mobile Pulse / Huawei U8220,logcat立即进入T(跟踪)状态并挂起。(CrashHandler.java:70strings.xml中:51

在非Android情况下,其中一些可能会有所不同。您需要收集自己的本机跟踪,请参见另一个问题,具体取决于您拥有的是哪种libc。您需要处理该跟踪的转储,启动单独的崩溃处理程序进程,并以适合您平台的某些方式发送电子邮件,但是我认为一般方法仍然可以使用。


2
理想情况下,您应该检查崩溃是否发生在您的磁带库中。如果它发生在其他地方(例如,在VM内部),则从信号处理程序进行的JNI调用可能会使情况更加混乱。这毕竟不是世界末日,因为无论如何您都处于崩溃的中期,但这可能会使诊断VM崩溃更加困难(或导致奇怪的VM崩溃,最终导致Android错误报告并使所有人困惑)。
2010年

@Chris非常高兴分享您的研究项目!
olafure 2011年

谢谢,这对于查找我的JNI发疯的地方很有用。另外,DCS校友们您好!
尼克,

3
从服务以新流程启动活动还需要以下代码:newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Graeme 2012年

1
这个解决方案在Jelly Bean下仍然有效吗?步骤6不会记录任何debuggerd输出吗?
乔什(Josh)2013年

14

我是有点晚,但我有完全相同的需求,我已经开发了一个小型图书馆,以解决这一问题,通过捕捉常见的崩溃(SEGVSIBGUS里面等)JNI代码,并定期更换他们java.lang.Error 的例外。值得一提的是,如果客户端在Android> =上运行4.1.1,则堆栈跟踪将嵌入崩溃的已解析回溯跟踪(包含完整本机堆栈跟踪的伪跟踪)。您不会从恶意崩溃中恢复(例如,如果破坏了分配器),但是至少它应该允许您从大多数崩溃中恢复。(请报告成功和失败,该代码是全新的)

有关更多信息,访问https://github.com/xroche/coffeecatch (代码为BSD 2-Clauses许可


6

FWIW,Google Breakpad在Android上运行良好。我做了移植工作,我们将其作为Firefox Mobile的一部分进行了运输。它需要一些设置,因为它不会在客户端提供堆栈跟踪,但会向您发送原始堆栈内存,并在服务器端进行堆栈遍历(因此您不必在应用程序中附带调试符号) )。


1
考虑到绝对缺少的文档,几乎不可能配置Breakpad
shader

其实并不难,项目Wiki上有很多文档。事实上,Android的出现,现在是一个NDK构建Makefile文件,它应该是超级好用:code.google.com/p/google-breakpad/source/browse/trunk/...
泰德Mielczarek

您还需要编译用于预处理Android调试符号文件的模块,并且只能在Linux上进行编译。在Mac上编译时-它仅生成Mac / iOS dSym预处理器。
着色器2012年

5

以我的有限经验(非Android),在将控制权返回给Java代码之前,JNI代码中的SIGSEGV通常会使JVM崩溃。我隐约记得曾经听说过一些非Sun JVM,它使您可以捕获SIGSEGV,但是AFAICR不能指望能够这样做。

您可以尝试用C捕获它们(请参阅sigaction(2)),尽管在SIGSEGV(或SIGFPE或SIGILL)处理程序之后您可以做的很少,因为正式定义了进程的持续行为。


好吧,在“忽略不是由kill(2)或raise(3)生成的SIGFPE,SIGILL或SIGSEGV信号”之后,行为是不确定的,但不一定在捕获此类信号期间发生。当前的计划是尝试一个C信号处理程序,该处理程序可以回调Java,并以某种方式终止线程而不终止进程。这可能或不可能。:-)
克里斯·博伊尔


1
...除非我不能使用backtrace(),因为Android不使用glibc,它使用了Bionic。:-(一些涉及_Unwind_Backtraceunwind.h将被替代需要。
克里斯·博伊尔
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.