8

First of all: this question has already been asked a couple times, and some answers are useful, but none provide a working solution. I started by trying the code from this answer. Surprisingly, it does its thing, but there's a huge problem: the only way of calling this code I can think of is SIGSEGV handler, and it has its own stack - hence I can't get the actual stack of my crashed app just like that.

Then, I tried incorporating this answer. It's slightly better - it yields the first item of the stack (the method in which crash occurred). But that's in - no actual backtrace. So as soon as the crash occurs inside a 3rd-party library (or standard library), this information is meaningless.

How can I further improve the code and finally get the stack trace for my poor crashed app?

P. S. Tested on Android 4.0.3 and Android 5.0, so far the behavior is the same. I want to support at least 5.0 and the recent previous versions like 4.3-4.4.

Community
  • 1
  • 1
Violet Giraffe
  • 29,070
  • 38
  • 172
  • 299
  • Just to be clear: you're trying to write an in-app crash handler that captures the stack trace after a failure? The signal handler does not have its own stack unless `sigaltstack` was used, though it's possible the stack unwinder doesn't know how to step across the signal stack frame. What is your ultimate goal? ACRA for the NDK? – fadden Apr 09 '15 at 15:40
  • @fadden: correct, I want to get a post-mortem stack trace. It would appear as the signal handler has its own stack, it originates in `art::handleFault` or something like that (on 5.0). What's ACRA? – Violet Giraffe Apr 09 '15 at 15:52
  • I heard a rumor that ART was providing its own signal handler that chained to the previous handler; if that's the case, you will see different behavior on 5.0 vs. 4.x with Dalvik. This is on top of the system-installed signal handler that feeds the debuggerd system handler (which outputs the stack trace to the log file after a native crash). I didn't think the Android pthread lib was using `sigaltstack`, but I haven't checked in a while. You'd need to dig SP out of the signal frame and use that as your unwind point, rather than the current SP. – fadden Apr 09 '15 at 16:10
  • @fadden: Thanks. I believe I can get the SP, but I don't know where to feed it - the code I've found so far only accepts PC explicitly and makes API calls with it. Taking PC from `ucontext` has helped, but not much. – Violet Giraffe Apr 09 '15 at 16:30
  • You may be able to use some of the stuff in https://android.googlesource.com/platform/system/core/+/lollipop-release/libbacktrace/ (note `Unwind` in https://android.googlesource.com/platform/system/core/+/lollipop-release/include/backtrace/Backtrace.h takes the ucontext). I don't know offhand if any of that stuff is published in the NDK. – fadden Apr 09 '15 at 17:00
  • @fadden: apparently, signal handler does have its own stack: http://stackoverflow.com/a/13170014/634821 This is the same on Android 4 and 5, only the specifics differ. – Violet Giraffe Apr 10 '15 at 08:24

1 Answers1

1

Did you try the coffeecatch library?

It is a JNI signal catcher that allows to turn SIGSEGV(+) signals into the java exceptions with mixed jni/java backtrace. It works up to API-19, but I had no chance to test it on API>19 yet. It provides program addresses which could be passed to addr2line in order to get the final references to a sources.

Code template:

#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));
    ....
}

Example of the trace:

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)

Native (jni) part of the stack trace was:

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)

And finally get a humanoid-readable backtrace:

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
Fvwm
  • 51
  • 3
  • 1
    I've tried it and couldn't do what I want. But I also didn't find a way to get the stack (be it addresses or symbols). Is it in the Coffecatch API? Do you have any example code? Also, are the addresses compatible with `dladdr`? – Violet Giraffe Apr 20 '15 at 14:21
  • See my changed answer above. + be sure that you use the latest version from git, old versions does not work with modern ndk. – Fvwm Apr 22 '15 at 13:06
  • Thanks. I've seen that it can throw a Java exception with some sort of a stack trace, but I need those addresses in the `COFFEE_CATCH` block, how hard would it be to modify the coffecatch source to get access to that list of addresses? Also, having to wrap each method in `try / catch` is a great hindrance. I have hundreds (over 100, that's for sure) native methods, and each must be modified. A global signal handler is infinitely more convenient... as long as it works. – Violet Giraffe Apr 22 '15 at 13:30
  • If you want to dump backtrace from inside signal handler it would big refactoring job (and cofee sources can help you only as reference design example), my estimation is 2-4 days. The next way is to copy signal context in static buffer (cofeecatch copy it to special "catch" place) and then dump it inside the default Thread.setDefaultUncaughtExceptionHandler(..) handler. But as long as you will have to set the signal handler for all threads it may be simple just to wrap with COFFEE_CATCH block thread's "main" functions. – Fvwm Apr 23 '15 at 15:57
  • Okay, let's say I'm sticking to the normal coffeecatch structure - try-catch in each native method. I just want the callstack in the `catch` block and no interaction with Java. How hard is that? – Violet Giraffe Apr 23 '15 at 17:26
  • It depends on how strong you are in C,C++ programming. As for me it could take an hour to find the point where java exception is generated, then next hour to replace env->Throw(..) with log print. And the rest of the day for testing/improving/optimizations. But much more simple (and faster) would be to wrap thread's body functions with coffeecatch and when just pick a backtrace up with logcat. – Fvwm Apr 27 '15 at 11:19