I'm writing this for Android (ARM only), but I believe the principle is the same for generic Linux as well.
I'm trying to capture the stack trace from within the signal handler, so that I can log it when my app crashes. This is what I've come up with using <unwind.h>
.
Initialization:
struct sigaction signalhandlerDescriptor;
memset(&signalhandlerDescriptor, 0, sizeof(signalhandlerDescriptor));
signalhandlerDescriptor.sa_flags = SA_SIGINFO;
signalhandlerDescriptor._u._sa_sigaction = signalHandler;
sigaction(SIGSEGV, &signalhandlerDescriptor, 0);
The code itself:
struct BacktraceState
{
void** current;
void** end;
void* pc;
};
inline _Unwind_Reason_Code unwindCallback(struct _Unwind_Context* context, void* arg)
{
BacktraceState* state = static_cast<BacktraceState*>(arg);
state->pc = (void*)_Unwind_GetIP(context);
if (state->pc)
{
if (state->current == state->end)
return _URC_END_OF_STACK;
else
*state->current++ = reinterpret_cast<void*>(state->pc);
}
return _URC_NO_REASON;
}
inline size_t captureBacktrace(void** addrs, size_t max, unsigned long pc)
{
BacktraceState state = {addrs, addrs + max, (void*)pc};
_Unwind_Backtrace(unwindCallback, &state);
personality_routine();
return state.current - addrs;
}
inline void dumpBacktrace(std::ostream& os, void** addrs, size_t count)
{
for (size_t idx = 0; idx < count; ++idx) {
const void* addr = addrs[idx];
const char* symbol = "";
Dl_info info;
if (dladdr(addr, &info) && info.dli_sname) {
symbol = info.dli_sname;
}
int status = -3;
char * demangledName = abi::__cxa_demangle(symbol, 0, 0, &status);
os << "#" << idx << ": " << addr << " " << (status == 0 ? demangledName : symbol) << "\n";
free(demangledName);
}
}
void signalHandler(int sig, siginfo_t *siginfo, void *uctx)
{
ucontext * context = (ucontext*)uctx;
unsigned long PC = context->uc_mcontext.arm_pc;
unsigned long SP = context->uc_mcontext.arm_sp;
Logger() << __PRETTY_FUNCTION__ << "Fatal signal:" << sig;
const size_t maxNumAddresses = 50;
void* addresses[maxNumAddresses];
std::ostringstream oss;
const size_t actualNumAddresses = captureBacktrace(addresses, maxNumAddresses, PC);
dumpBacktrace(oss, addresses, actualNumAddresses);
Logger() << oss.str();
exit(EXIT_FAILURE);
}
Problem: if I get the PC register by calling _Unwind_GetIP(context)
in unwindCallback
, I get the complete trace for the signal handler stack. Which is a separate stack, and that's obviously not what I want. So I tried supplying the PC taken from the ucontext
in signal handler, and got a weird result: I get one stack entry, it is the correct entry - the function which caused the signal in the first place. But it's logged twice (even the address is the same, so it's not a symbolic name look up bug). Obviously, that's not good enough - I need the whole stack. And I wonder if this result is merely accidental (i. e. it shouldn't work in general.
Now, I read I need to also supply the stack pointer, which I apparently can get from ucontext
, same as PC. But I don't know what to do with it. Do I have to unwind manually instead of using _Unwind_Backtrace
? If so, can you give me sample code? I've been searching for the better part of a day, and still couldn't find anything I could copy and paste into my project.
For what it's worth, here's the libunwind source which contains _Unwind_Backtrace
definition. Thought I could figure something out if I see its source, but it's way more complicated than I expected.
corkscrew/backtrace.h
, which is not available in the NDK, and I've heard that library has been removed from Android 5. – Pomace-fno-omit-frame-pointer
is passed in theCFLAGS
to the compiler to allow tracing the call-graph. Frame pointers will be silently disabled by the optimisation flags (eg.-O2
). – BeckettdumpBacktrace
is called, even with -O3. – Pomaceexecinfo.h
, are thebacktrace()
family of functions available within a native executable on Android? – Beckett<execinfo.h>
is not present in the NDK so I can't even include it. – Pomaceucontext
actually alter the PC register state to alter the execution flow? And third, are you sure that returning will get me back onto the original stack? – Pomacemcontext->arm_pc += (mcontext->arm_cpsr & 0x20) == 0x20 ? 2 : 4;
mean? – Pomacereturn
? Is it even possible to get back onto the main stack from a signal handler stack? – Pomacesigaction
to register my signal handler - same way as you did in the sample linked earlier today. – Pomace-rdynamic
and-funwind-tables
. Let me work on an example. – Pomace