在Android NDK上获取堆栈跟踪

8
首先,这个问题已经被提出了几次,有些答案很有用,但是没有提供可行的解决方案。我开始尝试使用这个答案中的代码。令人惊讶的是,它确实可以运行,但是有一个巨大的问题:我能想到唯一调用这个代码的方法是SIGSEGV处理程序,并且它有自己的堆栈 - 因此我不能像那样获取我的崩溃应用的实际堆栈。
然后,我尝试整合这个答案。它稍微好一点 - 它会生成堆栈的第一项(发生崩溃的方法)。但是这只是初级的 - 没有实际的回溯信息。因此,一旦崩溃发生在第三方库(或标准库)内部时,这些信息就没有意义了。
我该如何进一步改进代码并最终获得我的可怜应用程序的堆栈跟踪呢?
P.S. 已在Android 4.0.3和Android 5.0上测试过,到目前为止,行为是相同的。我想支持至少5.0及其之前的版本,如4.3-4.4。

只是想弄清楚:您正在尝试编写一个应用内崩溃处理程序,在失败后捕获堆栈跟踪吗?除非使用了sigaltstack,否则信号处理程序没有自己的堆栈,尽管可能堆栈展开器不知道如何跨越信号堆栈帧。您的最终目标是什么?针对 NDK 的 ACRA? - fadden
@fadden:没错,我想要获取事后堆栈跟踪。看起来信号处理程序有自己的堆栈,它源于art::handleFault或类似的东西(在5.0上)。ACRA是什么? - Violet Giraffe
我听说ART提供了自己的信号处理程序,可以链接到先前的处理程序;如果是这样,您将在Dalvik的5.0与4.x上看到不同的行为。这是在系统安装的信号处理程序之上的,该程序向debuggerd系统处理程序提供数据(在本地崩溃后将堆栈跟踪输出到日志文件中)。我认为Android pthread库没有使用sigaltstack,但我有一段时间没有检查过了。您需要从信号帧中挖出SP并将其用作取消编译点,而不是当前SP。 - fadden
@fadden:谢谢。我相信我可以得到SP,但我不知道在哪里输入它 - 到目前为止我找到的代码只明确接受PC并使用它进行API调用。从ucontext中获取PC有所帮助,但效果不大。 - Violet Giraffe
你可以尝试使用 https://android.googlesource.com/platform/system/core/+/lollipop-release/libbacktrace/ 中的一些内容(请注意,https://android.googlesource.com/platform/system/core/+/lollipop-release/include/backtrace/Backtrace.h 中的 Unwind 需要 ucontext)。我不确定这些内容是否在 NDK 中发布。 - fadden
@fadden:显然,信号处理程序确实有自己的堆栈:https://dev59.com/42025IYBdhLWcg3wChOc#13170014 这在Android 4和5上都是一样的,只是具体细节有所不同。 - Violet Giraffe
1个回答

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

1
我已经尝试过了,但是还不能实现我想要的功能。而且我也没有找到获取堆栈(无论是地址还是符号)的方法。这个在Coffeecatch API里吗?你有例子代码吗?另外,这些地址和dladdr兼容吗? - Violet Giraffe
请查看我上面修改过的答案。同时确保您使用的是最新版本的git,旧版本无法与现代ndk兼容。 - Fvwm
谢谢。我看到它可以抛出一个带有某种堆栈跟踪的Java异常,但我需要在COFFEE_CATCH块中的那些地址,修改coffecatch源代码以获取访问该地址列表有多难? 此外,必须在每个方法中包装try/catch是一个很大的阻碍。我有数百个(超过100个,这是确定的)本地方法,每个方法都必须进行修改。全局信号处理程序更加方便...只要它能正常工作。 - Violet Giraffe
如果你想从信号处理程序内部转储回溯信息,这将是一个重构的工作(而咖啡源仅可作为参考设计示例),我的估计是2-4天。下一种方法是将信号上下文复制到静态缓冲区中(cofeecatch将其复制到特殊的“catch”位置),然后在默认的Thread.setDefaultUncaughtExceptionHandler(..)处理程序中转储它。但只要你必须为所有线程设置信号处理程序,可能只需使用COFFEE_CATCH块包装线程的“main”函数就可以了。 - Fvwm
好的,假设我坚持使用正常的coffeecatch结构 - 在每个本地方法中使用try-catch。我只想在catch块中获取调用堆栈,而不与Java进行交互。这有多难? - Violet Giraffe
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Fvwm

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接