Я пишу это для Android (только для ARM), но я считаю, что принцип аналогичен и для родового Linux.
Я пытаюсь захватить трассировку стека из обработчика сигнала, так что я могу зарегистрировать его, когда мое приложение выйдет из строя. Это то, что я придумал с помощью <unwind.h>
.
Инициализация:
struct sigaction signalhandlerDescriptor;
memset(&signalhandlerDescriptor, 0, sizeof(signalhandlerDescriptor));
signalhandlerDescriptor.sa_flags = SA_SIGINFO;
signalhandlerDescriptor._u._sa_sigaction = signalHandler;
sigaction(SIGSEGV, &signalhandlerDescriptor, 0);
Сам код:
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);
}
Проблема: если я получаю регистрацию ПК, вызывая _Unwind_GetIP(context)
в unwindCallback
, я получаю полную трассировку для стека обработчика сигнала. Это отдельный стек, и это явно не то, что я хочу. Поэтому я попытался предоставить ПК, взятый из обработчика сигнала ucontext
, и получил странный результат: я получаю одну запись в стеке, это правильная запись - функция, которая вызвала сигнал в первую очередь. Но он регистрировался дважды (даже адрес один и тот же, так что это не ошибка символьного имени). Очевидно, что это не очень хорошо - мне нужен весь стек. И мне интересно, является ли этот результат просто случайным (то есть он не должен работать вообще.
Теперь, я прочитал, мне нужно также указать указатель стека, который я, по-видимому, могу получить от ucontext
, как и ПК. Но я не знаю, что с этим делать. Нужно ли вручную раскручивать, а не использовать _Unwind_Backtrace
? Если да, можете ли вы дать мне пример кода? Я искал лучшую часть дня и все еще не мог найти ничего, что мог бы скопировать и вставить в мой проект.
Для чего стоит источник libunwind, который содержит определение _Unwind_Backtrace
. Думал, что смогу понять что-то, если я увижу его источник, но это намного сложнее, чем я ожидал.