Есть ли способ сбрасывать стек вызовов в запущенном процессе на C или C ++ каждый раз, когда вызывается определенная функция? Я имею в виду примерно следующее:
void foo()
{
print_stack_trace();
// foo's body
return
}
Где print_stack_trace
работает аналогично caller
Perl.
Или что-то вроде этого:
int main (void)
{
// will print out debug info every time foo() is called
register_stack_trace_function(foo);
// etc...
}
где register_stack_trace_function
ставит какую-то внутреннюю точку останова, которая будет вызывать печать трассировки стека при каждом foo
вызове.
Есть ли что-нибудь подобное в какой-нибудь стандартной библиотеке C?
Я работаю над Linux, использую GCC.
Задний план
У меня есть тестовый запуск, который ведет себя по-разному в зависимости от некоторых переключателей командной строки, которые не должны влиять на это поведение. В моем коде есть генератор псевдослучайных чисел, который, как я полагаю, вызывается по-разному в зависимости от этих переключателей. Я хочу иметь возможность запускать тест с каждым набором переключателей и смотреть, вызывается ли генератор случайных чисел по-разному для каждого из них.
s/easier/either/
как, черт возьми, это случилось?s/either/easier
. Что мне нужно сделать с gdb, так это написать сценарий, который прерывает эту функцию и распечатывает трассировку стека, а затем продолжает. Теперь, когда я думаю об этом, возможно, мне пора узнать о сценариях gdb.Ответы:
Для решения только для Linux вы можете использовать backtrace (3), который просто возвращает массив
void *
(фактически каждый из них указывает на адрес возврата из соответствующего кадра стека). Чтобы преобразовать их во что-то полезное, есть backtrace_symbols (3) .Обратите внимание на раздел примечаний в backtrace (3) :
источник
glibc
сожалению, в Linuxbacktrace_symbols
функции не предоставляют имя функции, имя исходного файла и номер строки.-rdynamic
, также убедитесь, что ваша система сборки не добавляет-fvisibility=hidden
параметр! (так как это полностью отбросит эффект-rdynamic
)Ускорение трассировки стека
Документировано по адресу: https://www.boost.org/doc/libs/1_66_0/doc/html/stacktrace/getting_started.html#stacktrace.getting_started.how_to_print_current_call_stack
Это самый удобный вариант, который я видел до сих пор, потому что он:
действительно может распечатать номера строк.
Он просто делает вызовы ,
addr2line
однако , что это некрасиво и может быть медленным , если ваши принимают слишком много следов.разоблачает по умолчанию
Boost - это только заголовок, поэтому, скорее всего, нет необходимости изменять вашу систему сборки
boost_stacktrace.cpp
К сожалению, это кажется более поздним дополнением, и пакет
libboost-stacktrace-dev
отсутствует в Ubuntu 16.04, только 18.04:Мы должны добавить
-ldl
в конце, иначе компиляция не удастся.Вывод:
Вывод и поясняется далее в разделе «glibc backtrace» ниже, который аналогичен.
Обратите внимание на то, как
my_func_1(int)
иmy_func_1(float)
, искаженные из-за перегрузки функций , были хорошо разобраны для нас.Обратите внимание, что первые
int
вызовы отключены на одну строку (28 вместо 27, а второй - на две строки (27 вместо 29). В комментариях было высказано предположение, что это связано с тем, что рассматривается следующий адрес инструкции, который превращает 27 в 28, а 29 выпрыгивает из цикла и становится 27.Затем мы видим, что с
-O3
, вывод полностью искажен:Обратные трассировки обычно безвозвратно искажаются оптимизацией. Оптимизация хвостового вызова является ярким примером этого: что такое оптимизация хвостового вызова?
Бенчмарк запущен
-O3
:Вывод:
Итак, как и ожидалось, мы видим, что этот метод чрезвычайно медленен для внешних вызовов
addr2line
и будет возможен только в том случае, если выполняется ограниченное количество вызовов.Кажется, что каждая печать обратной трассировки занимает сотни миллисекунд, поэтому имейте в виду, что если обратная трассировка происходит очень часто, производительность программы значительно снизится.
Проверено на Ubuntu 19.10, GCC 9.2.1, boost 1.67.0.
Glibc
backtrace
Документировано по адресу: https://www.gnu.org/software/libc/manual/html_node/Backtraces.html
main.c
Обобщение:
-rdynamic
является ключевым обязательным параметром.Бегать:
Выходы:
Итак, мы сразу видим, что произошла оптимизация встраивания, и некоторые функции были потеряны из трассировки.
Если мы попытаемся получить адреса:
мы получаем:
который полностью выключен.
Если мы сделаем то же самое с
-O0
вместо этого,./main.out
даст правильную полную трассировку:а потом:
дает:
так что линии расходятся только на одну, TODO, почему? Но это все еще можно использовать.
Вывод: обратные трассировки могут идеально отображаться только с
-O0
. При оптимизации исходная трассировка коренным образом изменяется в скомпилированном коде.Я не смог найти простой способ автоматически разобрать символы C ++ с этим, однако вот несколько уловок:
Проверено на Ubuntu 16.04, GCC 6.4.0, libc 2.23.
Glibc
backtrace_symbols_fd
Этот помощник немного удобнее, чем
backtrace_symbols
и производит в основном идентичный вывод:Проверено на Ubuntu 16.04, GCC 6.4.0, libc 2.23.
glibc
backtrace
с C ++ разборчивым хаком 1:-export-dynamic
+dladdr
Адаптировано из: https://gist.github.com/fmela/591333/c64f4eb86037bb237862a8283df70cdfc25f01d3
Это "взлом", потому что он требует изменения ELF с помощью
-export-dynamic
.glibc_ldl.cpp
Скомпилируйте и запустите:
вывод:
Проверено на Ubuntu 18.04.
glibc
backtrace
с деманглингом C ++, хак 2: анализ вывода обратной трассировкиПоказано по адресу: https://panthema.net/2008/0901-stacktrace-demangled/
Это взлом, потому что он требует синтаксического анализа.
TODO - скомпилировать и показать здесь.
libunwind
TODO есть ли у этого преимущества перед трассировкой glibc? Очень похожий вывод, также требует изменения команды сборки, но не является частью glibc, поэтому требуется установка дополнительного пакета.
Код адаптирован из: https://eli.thegreenplace.net/2015/programmatic-access-to-the-call-stack-in-c/
main.c
Скомпилируйте и запустите:
Либо
#define _XOPEN_SOURCE 700
должно быть сверху, либо мы должны использовать-std=gnu99
:Бегать:
Вывод:
и:
дает:
С
-O0
:и:
дает:
Проверено на Ubuntu 16.04, GCC 6.4.0, libunwind 1.1.
libunwind с распознаванием имен C ++
Код адаптирован из: https://eli.thegreenplace.net/2015/programmatic-access-to-the-call-stack-in-c/
unwind.cpp
Скомпилируйте и запустите:
Вывод:
а затем мы можем найти строки
my_func_2
иmy_func_1(int)
с:который дает:
TODO: почему строки расходятся по одной?
Проверено на Ubuntu 18.04, GCC 7.4.0, libunwind 1.2.1.
GDB автоматизация
Мы также можем сделать это с GDB без перекомпиляции, используя: Как выполнить определенное действие при достижении определенной точки останова в GDB?
Хотя, если вы собираетесь много печатать трассировку, это, вероятно, будет менее быстрым, чем другие варианты, но, возможно, мы сможем достичь собственных скоростей
compile code
, но мне лень тестировать это сейчас: как вызвать сборку в gdb?main.cpp
main.gdb
Скомпилируйте и запустите:
Вывод:
TODO Я хотел сделать это
-ex
из командной строки, чтобы не создавать,main.gdb
но я не мог заставитьcommands
работать там.Протестировано в Ubuntu 19.04, GDB 8.2.
Ядро Linux
Как распечатать текущую трассировку стека потока внутри ядра Linux?
libdwfl
Первоначально об этом упоминалось по адресу: https://stackoverflow.com/a/60713161/895245, и это может быть лучший метод, но мне нужно провести еще немного тестов, но, пожалуйста, проголосуйте за этот ответ.
TODO: Я попытался свести к минимуму код в этом ответе, который работал, до одной функции, но это segfault, дайте мне знать, если кто-нибудь сможет понять, почему.
dwfl.cpp
Скомпилируйте и запустите:
Вывод:
Тестовый прогон:
Вывод:
Итак, мы видим, что этот метод в 10 раз быстрее, чем stacktrace Boost, и поэтому может быть применим для большего количества вариантов использования.
Протестировано в Ubuntu 19.10 amd64, libdw-dev 0.176-1.1.
Смотрите также
источник
Стандартного способа сделать это не существует. Для окон функциональность предоставляется в библиотеке DbgHelp.
источник
Вы можете использовать макрос-функцию вместо оператора return в конкретной функции.
Например, вместо использования return,
Вы можете использовать функцию макроса.
Всякий раз, когда в функции происходит ошибка, вы увидите стек вызовов в стиле Java, как показано ниже.
Полный исходный код доступен здесь.
c-callstack на https://github.com/Nanolat
источник
Еще один ответ на старую ветку.
Когда мне нужно это сделать, я обычно просто использую
system()
иpstack
Так что примерно так:
Это выводит
Это должно работать в Linux, FreeBSD и Solaris. Я не думаю, что в macOS есть pstack или простой эквивалент, но у этого потока, похоже, есть альтернатива .
Если вы используете
C
, вам нужно будет использоватьC
строковые функции.Я использовал 7 для максимального количества цифр в PID, основываясь на этом сообщении .
источник
Специфично для Linux, TL; DR:
backtrace
inglibc
создает точные трассировки стека, только когда-lunwind
он связан (недокументированная функция, специфичная для платформы).#include <elfutils/libdwfl.h>
(эта библиотека задокументированы только в заголовочном файле).backtrace_symbols
иbacktrace_symbolsd_fd
наименее информативны.В современном Linux вы можете получить адреса трассировки стека с помощью функции
backtrace
. Недокументированный способbacktrace
создания более точных адресов на популярных платформах - это установить ссылку на-lunwind
(libunwind-dev
в Ubuntu 18.04) (см. Пример вывода ниже).backtrace
использует функцию,_Unwind_Backtrace
и по умолчанию последняя исходит из,libgcc_s.so.1
и эта реализация наиболее переносима. Когда-lunwind
она связана, она предоставляет более точную версию,_Unwind_Backtrace
но эта библиотека менее переносима (см. Поддерживаемые архитектуры вlibunwind/src
).К сожалению, товарищ
backtrace_symbolsd
иbacktrace_symbols_fd
функции не могли преобразовать адреса трассировки стека в имена функций с именем исходного файла и номером строки, вероятно, уже десять лет (см. Пример вывода ниже).Однако есть другой метод преобразования адресов в символы, и он дает наиболее полезные трассировки с именем функции , исходным файлом и номером строки . Метод заключается в
#include <elfutils/libdwfl.h>
подключении-ldw
(libdw-dev
в Ubuntu 18.04).Рабочий пример C ++ (
test.cc
):Скомпилировано на Ubuntu 18.04.4 LTS с gcc-8.3:
Выходы:
Когда нет
-lunwind
привязки, он дает менее точную трассировку стека:Для сравнения,
backtrace_symbols_fd
вывод для той же трассировки стека наименее информативен:В производственной версии (а также в версии на языке C) вы можете сделать этот код более надежным, заменив
boost::core::demangle
,std::string
иstd::cout
их базовые вызовы.Вы также можете переопределить,
__cxa_throw
чтобы захватить трассировку стека при возникновении исключения и распечатать ее при обнаружении исключения. К тому времени, когда он входит вcatch
блок, стек уже размотан, поэтому вызывать его уже поздноbacktrace
, и именно поэтому необходимо захватить стек,throw
который реализуется функцией__cxa_throw
. Обратите внимание, что в многопоточной программе__cxa_throw
может одновременно вызываться несколько потоков, так что если она захватывает трассировку стека в глобальный массив, который должен бытьthread_local
.источник
-lunwind
проблема была обнаружена приlibunwind
написании этого сообщения, я ранее использовал напрямую для получения трассировки стека и собирался опубликовать его, ноbacktrace
делает это за меня, когда-lunwind
он связан.gcc
не предоставляет API, верно?Вы можете реализовать функционал самостоятельно:
Используйте глобальный (строковый) стек и в начале каждой функции помещайте имя функции и другие значения (например, параметры) в этот стек; при выходе из функции вытолкните его снова.
Напишите функцию, которая будет распечатывать содержимое стека при ее вызове, и используйте ее в функции, в которой вы хотите видеть стек вызовов.
Это может показаться трудоемким, но весьма полезным.
источник
call_registror MY_SUPERSECRETNAME(__FUNCTION__);
который подталкивает аргумент в своем конструкторе и всплывает в своем деструкторе. FUNCTION всегда представляет имя текущей функции.Конечно, следующий вопрос: хватит ли этого?
Основным недостатком трассировки стека является то, что почему у вас есть конкретная вызываемая функция, у вас нет ничего другого, например значения ее аргументов, что очень полезно для отладки.
Если у вас есть доступ к gcc и gdb, я бы посоветовал использовать
assert
для проверки определенного условия и создания дампа памяти, если оно не выполняется. Конечно, это означает, что процесс остановится, но у вас будет полноценный отчет вместо простой трассировки стека.Если вы хотите менее навязчивый способ, вы всегда можете использовать ведение журнала. Существуют очень эффективные лесозаготовительные предприятия, например, Pantheios . Что еще раз может дать вам гораздо более точное представление о том, что происходит.
источник
Вы можете использовать для этого Poppy . Обычно он используется для сбора трассировки стека во время сбоя, но он также может выводить его для работающей программы.
А теперь самое интересное: он может выводить фактические значения параметров для каждой функции в стеке и даже локальные переменные, счетчики циклов и т. Д.
источник
Я знаю, что эта ветка устарела, но думаю, что она может быть полезна другим людям. Если вы используете gcc, вы можете использовать его возможности инструмента (параметр -finstrument-functions) для регистрации любого вызова функции (вход и выход). Взгляните на это для получения дополнительной информации: http://hacktalks.blogspot.fr/2013/08/gcc-instrument-functions.html
Таким образом, вы можете, например, помещать и вставлять все вызовы в стек, а когда вы хотите его распечатать, вы просто смотрите, что у вас есть в стеке.
Протестировал, работает отлично и очень удобно
ОБНОВЛЕНИЕ: вы также можете найти информацию о параметре компиляции -finstrument-functions в документе GCC, касающемся параметров инструментария: https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html
источник
Вы можете использовать библиотеки Boost для печати текущего стека вызовов.
Человек здесь: https://www.boost.org/doc/libs/1_65_1/doc/html/stacktrace.html
источник
cannot locate SymEnumSymbolsExW at C:\Windows\SYSTEM32\dbgeng.dll
на Win10 ошибка .Вы можете использовать профилировщик GNU. Он также показывает график звонков! команда есть,
gprof
и вам нужно скомпилировать свой код с какой-либо опцией.источник
Нет, хотя решения, зависящие от платформы, могут существовать.
источник