Подтвердить что ты не робот

Как развернуть стек, чтобы получить обратную трассировку для указанного указателя стека (SP)?

Я пишу это для 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. Думал, что смогу понять что-то, если я увижу его источник, но это намного сложнее, чем я ожидал.

4b9b3361

Ответ 1

Во-первых, вам нужно прочитать раздел "Безопасные асинхронные сигналы":

http://man7.org/linux/man-pages/man7/signal.7.html

Это весь набор функций, которые можно безопасно вызывать в обработчике сигналов. О худшем, что вы можете сделать, - это вызвать все, что вызывает malloc()/free() под капотом - или сделать это самостоятельно.

Во-вторых, сначала запустите обработчик сигнала.

В-третьих, это, вероятно, возможно:

Как получить обратную ссылку С++ на Android

Android NDK: получение обратной трассы