Как можно перебирать кадры стека вручную в C?
При обработке сигналов в приложениях я могу правильно видеть обратную трассировку в отладчике. Но системный вызов backtrace не отображает кадры стека правильно. Есть ли разница в том, как gdb хранит кадры стека и как системный вызов backtrace сбрасывает их?
3 ответа
Вы не можете переносить итерацию по кадрам стека в C99 или C11.
Во-первых, потому что в стандарте C нет гарантии на какой-либо стек вызовов. (можно представить, что какой-то компилятор C выполняет весь анализ программы и избегает стека, если он бесполезен, например, если рекурсия не может произойти; такого компилятора C я не знаю). Посмотрите, например, этот вопрос C FAQ для странных реализаций Си.
Затем, потому что компилятор может иногда делать некоторые оптимизации, например, встроенные вызовы (даже для функций, не отмеченных inline
в частности, при запросе оптимизации времени соединения с -flto
перешел к gcc
) или иногда испускают хвостовые вызовы (и GCC может делать оба). Вы можете отключить оптимизацию, но тогда вы потеряете много производительности. Оптимизирующий компилятор помещает некоторые переменные только в регистры и повторно использует некоторые стековые слоты для нескольких переменных.
Наконец, на некоторых архитектурах (например, 32-битных x86), некоторый код (в частности, некоторый библиотечный код, например, внутри libc) может быть скомпилирован с -fomit-frame-pointer
и тогда нет способа получить информацию о кадре без нее.
Вы могли бы использовать libbacktrace
Ян Тейлор внутри GCC; Вы также можете использовать функцию backtrace(3) из GNU glibc; Вы могли бы даже использовать встроенные адреса возврата, доступные при компиляции с GCC. Но все эти инструменты могут не работать с оптимизированным кодом.
В практическом плане, если вам действительно нужна некоторая обратная трассировка, либо реализуйте это самостоятельно (я сделал это в моей устаревшей системе MELT, чей код C++ генерируется, упаковав локальные файлы в какой-то локальный struct
), или избегайте чрезмерной оптимизации, например, путем компиляции с gcc -g -O1
только.
Заметить, что backtrace
это не системный вызов (перечисленный в syscalls (2)), а glibc
-специфическая библиотечная функция.
Читайте также очень внимательно сигнал (7) и sigreturn (2). Существует очень немного (безопасных для асинхронного сигнала) функций, которые можно надежно вызывать (прямо или косвенно) из обработчиков сигналов, и backtrace
(или же printf
) не среди них. На практике портативный обработчик сигнала должен часто просто устанавливать некоторые volatile sigatomic_t
флаг, который вы должны проверить в другом месте - или вызовите siglongjmp (3).
Проблема может заключаться в том, что к тому времени, когда будет получена обратная трассировка, ваш стек может быть поврежден.
Просто возьмите адрес локальной переменной в функции. Рассчитать размер стека. добавьте его из адреса локальной переменной и выведите его между адресом переменной дополнения и переменной местоположения;.
Вы напечатали свой стек:)
Отладчик использует дополнительный набор дополнительных данных, помещаемых компилятором в двоичный файл gcc, когда вы используете -g
вариант. Эти данные не используются обратным вызовом, а используется только основная информация о компоновщике. Это означает, например, что любые статические данные не могут быть просмотрены с помощью backtrace, но с помощью gdb, это также приводит к различным оптимизациям, нарушающим backtrace, которые gdb обходит через явное знание.
Помните, что gdb специфичен для определенного языка и компилятора, тогда как обратная трассировка гораздо более переносима.
Смотрите справочную страницу backtrace http://linux.die.net/man/3/backtrace:
Пропуск указателей кадра (как подразумевается любым из ненулевых уровней оптимизации gcc(1)) может привести к нарушению этих предположений.
Если бы вызов backtrace хотел использовать эту информацию, он бы заставил вас всегда компилировать с символами отладки и имел бы гораздо большие накладные расходы и многие другие проблемы.