Как можно получить трассировку стека в C?

Я знаю, что нет стандартной функции C, чтобы сделать это. Мне было интересно, какие методы для этого в Windows и *nix? (Windows XP - моя самая важная ОС, чтобы сделать это прямо сейчас.)

11 ответов

Решение

Мы использовали это для наших проектов:

https://www.codeproject.com/kb/threads/stackwalker.aspx

Код немного грязный ИМХО, но он работает хорошо. Только для Windows

glibc предоставляет функцию backtrace().

http://www.gnu.org/software/libc/manual/html_node/Backtraces.html

Есть backtrace() и backtrace_symbols():

Со страницы руководства:

     #include <execinfo.h>
     #include <stdio.h>
     ...
     void* callstack[128];
     int i, frames = backtrace(callstack, 128);
     char** strs = backtrace_symbols(callstack, frames);
     for (i = 0; i < frames; ++i) {
         printf("%s\n", strs[i]);
     }
     free(strs);
     ...

Один из способов использовать это более удобным способом /OOP - сохранить результат backtrace_symbols () в конструкторе класса исключений. Таким образом, всякий раз, когда вы генерируете исключение такого типа, у вас есть трассировка стека. Затем просто предоставьте функцию для распечатки. Например:


class MyException : public std::exception {

    char ** strs;
    MyException( const std::string & message ) {
         int i, frames = backtrace(callstack, 128);
         strs = backtrace_symbols(callstack, frames);
    }

    void printStackTrace() {
        for (i = 0; i 

...


try {
   throw MyException("Oops!");
} catch ( MyException e ) {
    e.printStackTrace();
}

Та да!

Примечание: включение флагов оптимизации может сделать полученную трассировку стека неточной. В идеале эту функцию можно использовать при включенных флагах отладки и отключенных флагах оптимизации.

Для Windows проверьте API StackWalk64() (также на 32-битной Windows). Для UNIX вы должны использовать родной способ ОС или сделать возврат к glibc backtrace(), если таковой имеется.

Однако обратите внимание, что использование Stacktrace в нативном коде редко является хорошей идеей - не потому, что это невозможно, а потому, что вы обычно пытаетесь достичь неправильной цели.

В большинстве случаев люди пытаются получить стековую трассировку, скажем, в исключительных обстоятельствах, например, при обнаружении исключения, сбое утверждения или - наихудшее и самое неправильное из всех - когда вы получаете фатальное "исключение" или сигнал, похожий на нарушение сегментации.

Учитывая последнюю проблему, большинство API потребует от вас явного выделения памяти или может сделать это внутренне. Выполнение этого в хрупком состоянии, в котором ваша программа может находиться в данный момент, может на самом деле ухудшить ситуацию. Например, отчет о сбое (или coredump) не будет отражать фактическую причину проблемы, но ваша неудачная попытка ее обработать).

Я предполагаю, что вы пытаетесь достичь этой фатальной обработки ошибок, поскольку большинство людей, похоже, пытаются это сделать, когда дело доходит до получения трассировки стека. Если это так, я бы положился на отладчик (во время разработки) и запуск процесса coredump (или мини-дамп на окнах). Вместе с правильным управлением символами у вас не должно возникнуть проблем с выяснением вызывающей инструкции после смерти.

Вы должны использовать библиотеку раскрутки.

unw_cursor_t cursor; unw_context_t uc;
unw_word_t ip, sp;
unw_getcontext(&uc);
unw_init_local(&cursor, &uc);
unsigned long a[100];
int ctr = 0;

while (unw_step(&cursor) > 0) {
  unw_get_reg(&cursor, UNW_REG_IP, &ip);
  unw_get_reg(&cursor, UNW_REG_SP, &sp);
  if (ctr >= 10) break;
  a[ctr++] = ip;
}

Ваш подход также будет работать нормально, если вы не позвоните из общей библиотеки.

Вы можете использовать addr2line команда в Linux, чтобы получить исходную функцию / номер строки соответствующего ПК.

Для Windows CaptureStackBackTrace() также вариант, который требует меньше кода подготовки на стороне пользователя, чем StackWalk64() делает. (Также для похожего сценария, который у меня был, CaptureStackBackTrace() в конечном итоге работает лучше (более надежно), чем StackWalk64().)

Не существует независимого от платформы способа сделать это.

Ближайшее, что вы можете сделать, это запустить код без оптимизации. Таким образом, вы можете подключиться к процессу (используя отладчик Visual C++ или GDB) и получить трассировку пригодного для использования стека.

У Solaris есть команда pstack, которая также была скопирована в Linux.

Могу ли я указать вам на мою статью. Это всего лишь несколько строк кода.

Post Mortem Debugging

Хотя в настоящее время у меня есть проблемы с реализацией этого x64.

В течение последних нескольких лет я использовал libbacktrace Иэна Ланса Тейлора. Это намного чище, чем функции в библиотеке GNU C, которые требуют экспорта всех символов. Он предоставляет больше полезности для генерации следов, чем libunwind. И наконец, что не менее важно, он не побежден ASLR, как и подходы, требующие внешних инструментов, таких как addr2line,

Libbacktrace изначально была частью дистрибутива GCC, но теперь она доступна автору как отдельная библиотека под лицензией BSD:

https://github.com/ianlancetaylor/libbacktrace

На момент написания статьи я не использовал бы ничего другого, если бы мне не нужно было генерировать обратные трассировки на платформе, которая не поддерживается libbacktrace.

Вы можете сделать это, пройдя стопку назад. В действительности, тем не менее, часто проще добавить идентификатор в стек вызовов в начале каждой функции и вставить его в конец, а затем просто пройтись по нему, распечатывая содержимое. Это немного PITA, но он работает хорошо и в итоге сэкономит ваше время.

Другие вопросы по тегам