【问题标题】:Getting stack trace on Android NDK在 Android NDK 上获取堆栈跟踪
【发布时间】:2015-06-15 00:31:11
【问题描述】:

首先:这个问题已经被问过几次了,有些答案很有用,但没有一个提供有效的解决方案。 我首先尝试了this answer 的代码。令人惊讶的是,它确实做到了,但有一个大问题:我能想到的调用此代码的唯一方法是 SIGSEGV 处理程序,它有自己的堆栈 - 因此我无法获得崩溃应用程序的实际堆栈就这样。

然后,我尝试合并this answer。稍微好一点 - 它产生堆栈的第一项(发生崩溃的方法)。但这就是 - 没有实际的回溯。因此,一旦崩溃发生在第三方库(或标准库)中,这些信息就毫无意义。

如何进一步改进代码并最终为我糟糕的崩溃应用获取堆栈跟踪?

P。 S. 在 Android 4.0.3 和 Android 5.0 上测试,到目前为止行为是相同的。我希望至少支持 5.0 和最近的早期版本,例如 4.3-4.4。

【问题讨论】:

  • 要明确一点:您正在尝试编写一个应用内崩溃处理程序,该处理程序在失败后捕获堆栈跟踪?除非使用sigaltstack,否则信号处理程序没有自己的堆栈,尽管堆栈展开器可能不知道如何跨过信号堆栈帧。你的最终目标是什么? NDK 的 ACRA?
  • @fadden:正确,我想获得事后堆栈跟踪。它会显示为信号处理程序有自己的堆栈,它起源于art::handleFault 或类似的东西(在 5.0 上)。什么是 ACRA?
  • 我听说 ART 提供了自己的信号处理程序,它链接到前一个处理程序;如果是这种情况,您将在 5.0 和 4.x 上看到使用 Dalvik 的不同行为。这是系统安装的信号处理程序的顶部,该处理程序提供调试器系统处理程序(在本机崩溃后将堆栈跟踪输出到日志文件)。我不认为 Android pthread 库正在使用sigaltstack,但我有一段时间没有检查过。您需要从信号帧中挖掘 SP 并将其用作您的放松点,而不是当前的 SP。
  • @fadden:谢谢。我相信我可以获得 SP,但我不知道在哪里提供它 - 我到目前为止发现的代码只明确接受 PC 并使用它进行 API 调用。从ucontext 获取 PC 有所帮助,但作用不大。
  • 你也许可以使用android.googlesource.com/platform/system/core/+/… 中的一些东西(注意android.googlesource.com/platform/system/core/+/… 中的Unwind 采用ucontext)。我不知道这些东西是否会在 NDK 中发布。

标签: android c++ linux android-ndk stack-trace


【解决方案1】:

您尝试过coffeecatch 库吗?

它是一个 JNI 信号捕捉器,允许将 SIGSEGV(+) 信号转换为带有混合 jni/java 回溯的 java 异常。它适用于 API-19,但我还没有机会在 API>19 上对其进行测试。它提供了可以传递给 addr2line 的程序地址,以便获得对源的最终引用。

代码模板:

#include "coffeejni.h"
#include "coffeecatch.h"

void   MyClass::foo(JNIEnv *env, int arg1, int arg2) {
    ....
    int  rc;
    COFFEE_TRY_JNI(env, rc = crashInside(arg1, arg2));
    ....
}

跟踪示例:

F/myapp   (24535): "DESIGN ERROR": thread=t1
F/myapp   (24535): java.lang.Error: signal 11 (Address not mapped to object) at address 0xdeadbaad [at libc.so:0x18282]
F/myapp   (24535):  at com.example.NativeSupport.nsc(Native Method)
F/myapp   (24535):  at com.example.NativeSupport.nsc_quiet(NativeSupport.java:328)
F/myapp   (24535):  at com.example.NativeSupport.loop(NativeSupport.java:287)
F/myapp   (24535):  at com.example.NativeSupport.access$2(NativeSupport.java:274)
F/myapp   (24535):  at com.example.NativeSupport$2.run(NativeSupport.java:124)
F/myapp   (24535):  at java.lang.Thread.run(Thread.java:856)
F/myapp   (24535): Caused by: java.lang.Error: signal 11 (Address not mapped to object) at address 0xdeadbaad [at libc.so:0x18282]
F/myapp   (24535):  at system.lib.libc_so.0x18282(Native Method)
F/myapp   (24535):  at system.lib.libc_so.0xdc04(abort:0x4:0)
F/myapp   (24535):  at data.data.example.lib.libexample_so.0xf147(Native Method)
F/myapp   (24535):  at data.data.example.lib.libexample_so.0x12d1b(Native Method)
F/myapp   (24535):  at data.data.example.lib.libexample_so.0x1347b(Native Method)
F/myapp   (24535):  at data.data.example.lib.libexample_so.0x13969(Native Method)
F/myapp   (24535):  at data.data.example.lib.libexample_so.0x13ab3(Native Method)
F/myapp   (24535):  at data.data.example.lib.libexample_so.0x17a9b(Native Method)
F/myapp   (24535):  at system.lib.libdvm_so.0x1f4b0(dvmPlatformInvoke:0x70:0)
F/myapp   (24535):  at system.lib.libdvm_so.0x4dfa5(dvmCallJNIMethod(unsigned int const*, JValue*, Method const*, Thread*):0x164:0)
F/myapp   (24535):  at system.lib.libdvm_so.0x28920(Native Method)
F/myapp   (24535):  at system.lib.libdvm_so.0x2d0b0(dvmInterpret(Thread*, Method const*, JValue*):0xb4:0)
F/myapp   (24535):  at system.lib.libdvm_so.0x5f599(dvmCallMethodV(Thread*, Method const*, Object*, bool, JValue*, std::__va_list):0x110:0)
F/myapp   (24535):  at system.lib.libdvm_so.0x5f5c3(dvmCallMethod(Thread*, Method const*, Object*, JValue*, ...):0x14:0)
F/myapp   (24535):  at system.lib.libdvm_so.0x549eb(Native Method)
F/myapp   (24535):  at system.lib.libc_so.0x12dd0(__thread_entry:0x30:0)
F/myapp   (24535):  at system.lib.libc_so.0x12534(pthread_create:0xac:0)

堆栈跟踪的本机 (jni) 部分是:

F/myapp   (24535):  at data.data.example.lib.libexample_so.0xf147(Native Method)
F/myapp   (24535):  at data.data.example.lib.libexample_so.0x12d1b(Native Method)
F/myapp   (24535):  at data.data.example.lib.libexample_so.0x1347b(Native Method)
F/myapp   (24535):  at data.data.example.lib.libexample_so.0x13969(Native Method)
F/myapp   (24535):  at data.data.example.lib.libexample_so.0x13ab3(Native Method)
F/myapp   (24535):  at data.data.example.lib.libexample_so.0x17a9b(Native Method)

最后得到一个人形可读的回溯:

cd android-ndk/toolchains/arm-linux-androideabi-4.8/prebuilt/linux-x86_64/bin
./arm-linux-androideabi-addr2line -e /home/joe/myproj/obj/local/armeabi-v7a/libexample.so 0xf147 0x12d1b 0x1347b 0x13969 0x13ab3 0x17a9b

【讨论】:

  • 我已经尝试过了,但无法做到我想要的。但我也没有找到获取堆栈的方法(无论是地址还是符号)。它在 Coffecatch API 中吗?你有任何示例代码吗?另外,地址是否与dladdr兼容?
  • 请参阅上面我更改的答案。 + 确保您使用 git 的最新版本,旧版本不适用于现代 ndk。
  • 谢谢。我已经看到它可以通过某种堆栈跟踪引发 Java 异常,但我需要 COFFEE_CATCH 块中的这些地址,修改 coffecatch 源以访问该地址列表有多难?此外,必须将每个方法包装在 try / catch 中是一个很大的障碍。我有数百个(肯定超过 100 个)本机方法,每个都必须修改。全局信号处理程序无限方便......只要它有效。
  • 如果您想从信号处理程序内部转储回溯,这将是一项大型重构工作(并且咖啡资源只能作为参考设计示例帮助您),我的估计是 2-4 天。下一种方法是在静态缓冲区中复制信号上下文(cofeecatch 将其复制到特殊的“捕获”位置),然后将其转储到默认的 Thread.setDefaultUncaughtExceptionHandler(..) 处理程序中。但是,只要您必须为所有线程设置信号处理程序,只需使用 COFFEE_CATCH 块线程的“主”函数进行包装就可能很简单。
  • 好吧,假设我坚持正常的coffeecatch 结构——在每个本地方法中都使用try-catch。我只想要catch 块中的调用堆栈并且不与Java 交互。这有多难?
猜你喜欢
  • 2013-10-02
  • 1970-01-01
  • 1970-01-01
  • 2010-10-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多