Зачем включать стектрейс в стандарт C++?
Группа разработчиков хочет включить стекстрейс в стандарт C++. О том, на каком основании и зачем это нужно рассказываем в нашей статье.
5К открытий5К показов
Алексей Горгуров, преподаватель МТУСИ, старший разработчик НПЛ «Медоптика» и Software Developer в Synchro Software
Российская рабочая группа по стандартизации C++ собирает предложения разработчиков, чтобы донести их до Международного комитета по стандартизации. Все нововведения призваны сделать язык более логичным и упростить его использование. Сейчас на рассмотрении у комитета находится предложение о включении в стандарт стектрейса. О том, зачем это нужно, рассказываем ниже.
Иногда при работе над большим проектом возникает необходимость узнать, как мы попали в ту или иную функцию. Например, если интересующая нас функция вызывается только при обнаружении ошибки, то нам захочется увидеть последовательность вызовов, приводящих к проблеме. Для этой цели можно было бы использовать отладчик — остановить исполнение в интересующем месте и распечатать стек. Однако во многих сценариях этот способ неприменим: в случаях, если нужно получить стек вызовов с устройства, где нет отладчика, или с приложения, не терпящего вмешательства извне. В подобных случаях необходимо получать стек прямо в процессе исполнения программы. Это можно сделать разными способами — в зависимости от потребностей, компилятора и от платформы.
При работе на Linux, и если в стеке не нужно отображать исходные файлы и номера строк — можно воспользоваться вызовами backtrace
и backtrace_symbols
из библиотеки glibc. Результирующий стек может выглядеть примерно так:
В этом стеке выражение в круглых скобках означает адрес исполняющейся в данный момент процессорной инструкции (myfunc+0x1a
означает, что к адресу функции myfunc
прибавляется смещение 0x1a
). Страшная строчка _ZN3clsIdE3barEv
— это тоже имя функции. Так как backtrace()
предназначен для работы в первую очередь с кодом, написанном на C, то, если вы пишете на C++, имена ваших функций будут выводиться в некрасивом (мангленном) виде. Воспользовавшись специальными стронними инструментами, можно получить из строчки _ZN3clsIdE3barEv
читаемое имя функции void cls<double>::bar()
.
Если мы захотим получить похожую информацию на платформе Windows при работе с компилятором MSVC, то можно использовать вызовы CaptureStackBackTrace
и SymFromAddr
из библиотеки kernel32.dll, или воспользоваться специальной библиотекой DbgHelp от Microsoft. И тут мы столкнёмся как со старыми проблемами нечитаемых мангленных имён, так и с новой проблемой — многие из вышеперечисленных функций неверно работают в многопоточной среде.
Если же нам ко всем прочему захочется получать ещё и имена исходных файлов в стектрейсе — придётся очень сильно постараться и научиться читать отладочную информацию, генерируемую компилятором вместе с кодом. Существует несколько форматов такой отладочной информации. GCC и Clang используют формат DWARF, а MSVC, — компилятор С++, использующийся в Visual Studio, — использует формат PDB. Чтение такой отладочной информации – непростой процесс, поэтому если требуется отображать информацию по номерам строк — проще воспользоваться уже готовым решением. И тут нас будут ждать очередные проблемы.
Так, библиотека libbacktrace умеет работать с отладочной информацией в формате DWARF, а, кроме того, доступна не только на Linux, но и на MacOS, а при компиляции приложения с помощью GCC или Clang сможет показать стек даже на Windows. Однако она не умеет работать с отладочной информацией, сгенерированной компилятором MSVC.
Обилие возможных форматов, компиляторов и платформ, необходимость подключать различные сторонние библиотеки, порождает сложности при желании включить эту функциональность в свой проект. Решить проблему помогло бы включение классов для получения стека вызовов в стандарт C++. Такое предложение поступило на сайт Национальной рабочей группы по стандартизации C++. Согласно предложению, для распечатки стека достаточно будет написать:
На выходе получим что-то вроде:
Теперь не нужно ограничиваться определенной платформой или зависеть от компилятора — если предложение будет принято, любой компилятор, поддерживающий стандарт, сможет скомпилировать этот код. Не нужно будет волноваться о формате отладочной информации — компилятор позаботится и об этом. Исчезнет зависимость от сторонних библиотек.
Например, чтобы вывести стек при фатальной ошибке, можно будет написать свой обработчик std::terminate
:
и зарегистрировать его:
Кроме этого примера применений может быть масса — например, для вывода стека при обработке исключений и assert’ов или в профилировщиках для сбора информации о местах, где выделяется память.
Реализацию предлагаемой библиотеки в стандарт можно найти в репозитории Boost.Stacktrace, само предложение можно почитать на сайте Рабочей Группы 21.
В настоящее время готовится небольшое расширение для уже имеющегося предложения, включающее в себя возможность компактно сохранять и переносить стектрейсы с одной машины на другую.
5К открытий5К показов