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

83

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

Кевин
источник
Я подробно протестировал несколько методов по адресу: stackoverflow.com/questions/3899870/print-call-stack-in-c-or-c/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Ответы:

81

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

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

Sanxiyn
источник
6
glibc FTW ... снова. (Это еще одна причина, по которой я считаю glibc абсолютным золотым стандартом, когда речь идет о программировании на C (и о компиляторе, который к нему прилагается).)
Trevor Boyd Smith
4
Но подождите, это еще не все! Функция backtrace () предоставляет только массив указателей void *, представляющих функции стека вызовов. "Это не очень полезно. Arg." Не бойся! glibc предоставляет функцию, которая преобразует все адреса void * (адреса функций стека вызовов) в удобочитаемые строковые символы. char ** backtrace_symbols (void *const *buffer, int size)
Тревор Бойд Смит,
1
Предостережение: я думаю, работает только для функций C. @Trevor: Это просто поиск syms по адресу в таблице ELF.
Конрад Мейер
2
Есть также тот, void backtrace_symbols_fd(void *const *buffer, int size, int fd)который, например, может отправлять вывод напрямую в stdout / err.
wkz
2
backtrace_symbols()отстой. Он требует экспорта всех символов и не поддерживает символы DWARF (отладочные). libbacktrace - намного лучший вариант во многих (большинстве) случаях.
Эрван Легран
29

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

На странице руководства:

Один из способов использовать это более удобным способом / ООП - сохранить результат 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 < frames; ++i) {
            printf("%s\n", strs[i]);
        }
        free(strs);
    }
};

...

Да да!

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

Том
источник
@shuckc только для преобразования адреса в строку символов, что можно сделать извне, используя другие инструменты, если это необходимо.
Вудро Барлоу,
22

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

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

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

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

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

Christian.K
источник
2
Вы правы в том, что попытки выделения памяти в обработчике сигналов или исключений - уязвимость. Один из возможных выходов - выделить фиксированное количество «аварийного» пространства при запуске программы или использовать статический буфер.
j_random_hacker
Другой выход - создание сервиса coredump, который работает независимо
Kobor42
5

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

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

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

user262802
источник
"исходная функция / номер строки"? Что, если компоновка оптимизирована для уменьшения размера кода? Я скажу, однако, что это похоже на полезный проект. Жалко, что нет возможности получить регистры. Я обязательно займусь этим. Вы знаете, что он абсолютно не зависит от процессора? Просто работает со всем, что имеет компилятор C?
Mawg говорит восстановить Монику
1
Хорошо, этот комментарий того стоил, хотя бы из-за упоминания полезной команды addr2line!
Ogre Psalm33
addr2line не работает для перемещаемого кода в системах с ASLR (т.е. большинство из того, что люди использовали в течение последнего десятилетия).
Эрван Легран
4

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

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

Нильс Пипенбринк
источник
Это не помогает мне, когда на встроенном компьютере в полевых условиях происходит сбой. :(
Кевин
@Kevin: Даже на встроенных машинах обычно есть способ получить заглушку удаленного отладчика или хотя бы дамп ядра. Хотя, может быть, не сразу, когда он будет развернут в полевых условиях ...
ephemient
если вы запускаете с помощью gcc-glibc на выбранной вами платформе windows / linux / mac ... тогда backtrace () и backtrace_symbols () будут работать на всех трех платформах. Учитывая это утверждение, я бы использовал слова «нет [портативного] способа сделать это».
Тревор Бойд Смит,
4

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

Квазидарт
источник
2

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

шум
источник
1
Полезно, но не совсем C (это внешняя утилита).
ephemient
1
также из описания (раздел: ограничения) «: pstack в настоящее время работает только в Linux, только на машине x86 с 32-битными двоичными файлами ELF (64-битные не поддерживаются)»
Сиро Коста
0

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

Серафина Бросиус
источник
1
Не могли бы вы более подробно объяснить «движение по стеку назад»?
Spidey
@Spidey Во встроенных системах иногда это все, что у вас есть - я думаю, это было отклонено, потому что платформа - WinXP. Но без libc, поддерживающего обход стека, в основном вам придется «обходить» стек. Вы начинаете с текущего базового указателя (на x86 это содержимое регистра RBP). Это приведет вас к тому, чтобы указать в стеке 1. сохраненную предыдущую RBP (что позволяет продолжать обход стека) и 2. адрес возврата вызова / перехода (сохраненный регистр RIP вызывающей функции), который сообщает вам, что это была за функция. Затем, если у вас есть доступ к таблице символов, вы можете найти адрес функции.
Тед Миддлтон
0

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

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

https://github.com/ianlancetaylor/libbacktrace

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

Эрван Легран
источник