I am trying to catch signals such as SIGSEGV in my Android NDK app for debugging purpose. For that, I have set up a sigaction that is called.
I am now trying to get the
In order to to get stacktrace of code which caused SIGSEGV instead of stacktrace of the signal handler, you have to get ARM registers from ucontext_t
and use them for unwinding.
But it is hard to do with _Unwind_Backtrace()
. Thus, if you use libc++ (LLVM STL), better try precompiled libunwind
for 32-bit ARM, bundled with modern Android NDKs (at sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/libunwind.a
). Here is a sample code.
// This method can only be used on 32-bit ARM with libc++ (LLVM STL).
// Android NDK r16b contains "libunwind.a" for armeabi-v7a ABI.
// This library is even silently linked in by the ndk-build,
// so we don't have to add it manually in "Android.mk".
// We can use this library, but we need matching headers,
// namely "libunwind.h" and "__libunwind_config.h".
// For NDK r16b, the headers can be fetched here:
// https://android.googlesource.com/platform/external/libunwind_llvm/+/ndk-r16/include/
#if _LIBCPP_VERSION && __has_include("libunwind.h")
#include "libunwind.h"
#endif
struct BacktraceState {
const ucontext_t* signal_ucontext;
size_t address_count = 0;
static const size_t address_count_max = 30;
uintptr_t addresses[address_count_max] = {};
BacktraceState(const ucontext_t* ucontext) : signal_ucontext(ucontext) {}
bool AddAddress(uintptr_t ip) {
// No more space in the storage. Fail.
if (address_count >= address_count_max)
return false;
// Reset the Thumb bit, if it is set.
const uintptr_t thumb_bit = 1;
ip &= ~thumb_bit;
// Ignore null addresses.
if (ip == 0)
return true;
// Finally add the address to the storage.
addresses[address_count++] = ip;
return true;
}
};
void CaptureBacktraceUsingLibUnwind(BacktraceState* state) {
assert(state);
// Initialize unw_context and unw_cursor.
unw_context_t unw_context = {};
unw_getcontext(&unw_context);
unw_cursor_t unw_cursor = {};
unw_init_local(&unw_cursor, &unw_context);
// Get more contexts.
const ucontext_t* signal_ucontext = state->signal_ucontext;
assert(signal_ucontext);
const sigcontext* signal_mcontext = &(signal_ucontext->uc_mcontext);
assert(signal_mcontext);
// Set registers.
unw_set_reg(&unw_cursor, UNW_ARM_R0, signal_mcontext->arm_r0);
unw_set_reg(&unw_cursor, UNW_ARM_R1, signal_mcontext->arm_r1);
unw_set_reg(&unw_cursor, UNW_ARM_R2, signal_mcontext->arm_r2);
unw_set_reg(&unw_cursor, UNW_ARM_R3, signal_mcontext->arm_r3);
unw_set_reg(&unw_cursor, UNW_ARM_R4, signal_mcontext->arm_r4);
unw_set_reg(&unw_cursor, UNW_ARM_R5, signal_mcontext->arm_r5);
unw_set_reg(&unw_cursor, UNW_ARM_R6, signal_mcontext->arm_r6);
unw_set_reg(&unw_cursor, UNW_ARM_R7, signal_mcontext->arm_r7);
unw_set_reg(&unw_cursor, UNW_ARM_R8, signal_mcontext->arm_r8);
unw_set_reg(&unw_cursor, UNW_ARM_R9, signal_mcontext->arm_r9);
unw_set_reg(&unw_cursor, UNW_ARM_R10, signal_mcontext->arm_r10);
unw_set_reg(&unw_cursor, UNW_ARM_R11, signal_mcontext->arm_fp);
unw_set_reg(&unw_cursor, UNW_ARM_R12, signal_mcontext->arm_ip);
unw_set_reg(&unw_cursor, UNW_ARM_R13, signal_mcontext->arm_sp);
unw_set_reg(&unw_cursor, UNW_ARM_R14, signal_mcontext->arm_lr);
unw_set_reg(&unw_cursor, UNW_ARM_R15, signal_mcontext->arm_pc);
unw_set_reg(&unw_cursor, UNW_REG_IP, signal_mcontext->arm_pc);
unw_set_reg(&unw_cursor, UNW_REG_SP, signal_mcontext->arm_sp);
// unw_step() does not return the first IP.
state->AddAddress(signal_mcontext->arm_pc);
// Unwind frames one by one, going up the frame stack.
while (unw_step(&unw_cursor) > 0) {
unw_word_t ip = 0;
unw_get_reg(&unw_cursor, UNW_REG_IP, &ip);
bool ok = state->AddAddress(ip);
if (!ok)
break;
}
}
void SigActionHandler(int sig, siginfo_t* info, void* ucontext) {
const ucontext_t* signal_ucontext = (const ucontext_t*)ucontext;
assert(signal_ucontext);
BacktraceState backtrace_state(signal_ucontext);
CaptureBacktraceUsingLibUnwind(&backtrace_state);
// Do something with the backtrace - print, save to file, etc.
}
I am also advising to take a look at this my answer which contains more code and more info:
https://stackoverflow.com/a/50027799/1016580
If you use libstdc++ (GNU STL), use Vasily Galkin's solution:
https://stackoverflow.com/a/30515756/1016580
, which is the same as Dar Hoo's solution from another post:
https://stackoverflow.com/a/48593413/1016580
You can get the stack base address with pthread_getattr_np
and pthread_attr_getstack
, but what you really need is the PC and SP at the time of the crash. On Linux, you can pull these out of the ucontext
.
If you set the SA_SIGINFO
flag when you configure the signal handler, your handler function gets three arguments instead of one. The third void*
argument is a ucontext
pointer. The accepted answer to this question explains a bit more.
Once you've got all that you can unwind the stack. If you don't mind stepping outside the bounds of what the NDK provides, Android's libcorkscrew has functions that can unwind stacks and output the results. This is used by the debuggerd daemon to dump native crashes to the log file.
It may be useful to know that native crashes logged by debuggerd generate stack dumps in /data/tombstones/
. The file permissions render it inaccessible to normal apps, but on a modified device you could just pull these out and send them.
In my practice standard _Unwind_Backtrace failed to switch to pre-signal stack.
I've managed to get some before-signal stacks by calling internal libgcc __gnu_Unwind_Backtrace - it has an extra agrument being "current registry values" - so it operates on given stack, not on current stack.
//definitions copied from arm-specific libgcc 4.8 sources.
struct core_regs
{
_uw r[16];
};
typedef struct
{
_uw demand_save_flags;
struct core_regs core;
} phase2_vrs;
extern "C"
_Unwind_Reason_Code
__gnu_Unwind_Backtrace(_Unwind_Trace_Fn trace, void * trace_argument,
phase2_vrs * entry_vrs);
// Getting backtrace with those definitions
//istead of _Unwind_Backtrace(tracer, &state);
if (const ucontext_t* signal_context = last_sigaction_parameter)
{
phase2_vrs pre_signal_state = {};
pre_signal_state.core = *reinterpret_cast<const core_regs*>(&(signal_context->uc_mcontext.arm_r0));
__gnu_Unwind_Backtrace(tracer, &state, &pre_signal_state);
}