Утечки памяти в C++ проектах. Инструменты обнаружения.

Memory Leak Detection in C++
June 1st, 2003 by Cal Erickson in Software

Предыдущая статья на эту тему (Memory Leak Detection in Embedded Systems, LJ, September 2002, http://www.linuxjournal.com/article/6059) была посвящена утечкам памяти в проектах, выполненных на С. Теперь уделим некоторое время программам на C++. Утилиты, обсуждаемые в данной статье, умеют находить ошибки в программах, но не утечки памяти ядра. Все эти инструменты использовались при подготовке релизов MontaVista Linux Professional Edition 2.1 и 3.0 и один из них, dmalloc, поставляется в составе MontaVista Linux.

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

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

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

Большинство программ, призванных бороться с утечками памяти, доступны в исходных кодах. Их запуск на архитектурах, не тождественных х86, обычно связан с некоторым процессом портирования. Это портирование может быть как всего лишь перекомпиляцией кросс-компилятором, так и необходимостью изменить некоторые куски ассемблерного кода под целевую архитектуру. Некоторые утилиты поставляются с рекомендациями по их использованию на других архитектурах и советами по сборке в кросс-сборочных средах.

dmalloc

Автор dmalloc, утилиты, которая была описана в отдельной статье в сентябре 2002 года, указывает, что его знания в области С++ ограниченны, поэтому таковы же и его знания в области утечек памяти в программах на С++.
Чтобы использовать утилиту в мультипоточных приложениях на С++, ее код должен быть слинкован с приложением статически.

ccmalloc

Утилита ccmalloc - это профилировщик памяти с простой моделью использования, которая поддерживает динамическое связывание, но не dlopen. Она умеет определять утечки памяти, повторное освобождение одних и тех же данных, запись и перезапись в уже освобожденные области. Она отображает статистику выделения и освобождения памяти. Она применима в случаях, когда использовалась оптимизация и стриппинг и поддерживает С++.

Также предоставляется информация о номерах строк и файлов для всей цепочки вызовов, а не только для последнего вызова malloc/free. Для использования ccmalloc не требуется перекомпиляция, достаточно использовать -lccmalloc -ldl или ccmalloc.o -ldl. Утилита предоставляет эффективное отображение цепочек вызовов, настройку вида представления цепочки, выборочный вывод цепочек, компрессированный журнал и пользовательский конфигурационный файл .ccmalloc. Основная документация содержится в файле ccmalloc.cfg. Тестовые файлы, которые поставляются с программой, содержат дополнительную документацию. Для работы с утилитой и сжатыми журналами потребуются программы nm, gdb и gzip.

NJAMD

NJAMD, как утверждает автор этой программы, "не просто отладчик выделения памяти". Как и большинство отладчиков выделения памяти, утилита имеет собственную реализацию malloc(). Эти подставленные функции могут производить ряд проверок выделения, в том числе и специфических, например, событий переполнения динамических буферов и вторичного использования объектов из памяти после ее освобождения. Библиотека NJAMD может быть подвергнута LD_PRELOAD или может быть слинкована с программой. При первом выделении памяти она захватывает для собственных нужд большой буфер размером 20 Мб и из него выделяет память приложению по мере поступления запросов.

NJAMD может быть использована самостоятельно или в качестве внешнего интерфейса для gdb. Она также содержит инструмент для анализа кучи после фатального сбоя. Другой инструмент позволяет отлаживать приложение, пропуская стадию перекомпиляции, просто предварительно загружая библиотеку. NJAMD также способна отслеживать утечки в библиотечных функциях, которые используются как обертки для malloc и free, функций выделения памяти для объектов GUI и функций С++ new и delete.
Часто утечки памяти не обнаруживаются немедленно, но таятся, выскакивая в самый эффектный момент. Отслеживание таких ошибок может занять долгое время. NJAMD имеет множество настроек, позволяющих менять уровень детектирования проблем, затрачивая больше или меньше ресурсов в зависимости от потребности.
Как и почти всегда для подобных программ, NJAMD предназначена для использования исключительно на стадии разработки, так как сильно замедляет систему.

YAMD

YAMD (yet another memory debugger) - еще один пакет для отслеживания событий на границах выделенных блоков памяти. Программа делает свою работу, используя страничный механизм управления памятью процессора. Устанавливаются факты чтения-записи с параметрами вне допустимого диапазона. Отловленные события заносятся в журнал с именем файла и номером строки вместе с трассировочной информацией. Трассировка бывает полезна, так как выделением памяти занимается весьма ограниченное количество процедур.

Библиотека также эмулирует malloc и free. Это помогает отловить множество непрямых вызовов malloc, таких, например, которые производятся из strdup, а также действия new и delete. Однако, если операции new и delete перегружены, этот механизм не работает.

Так же как и другие программы этого типа, YAMD нуждается в большом количестве виртуальной памяти или большом свопе для своего шаманства. Как правило, на встроенных системах такой роскоши не предполагается. Здесь, также как и в других случаях, работает подход выполнения отладки на рабочей станции. Когда эта отладка закончена, можно переносить приложение на целевую платформу в надежде что большинство, если не все, причины утечек памяти ликвидированы.

YAMD предоставляет скрипт, run-yamd, который помогает запускать программу более простым способом. Он имеет ряд опций для описания покрытия определенных условий. Файл журнала может быть создан в процессе анализа дампа ядра. Также для отладки приложения, находящегося под контролем YAMD, может быть использован стандартный отладчик. Хотя, в данном случае возможны проблемы, если YAMD используется в режиме preloaded, в отличие от ситуации, когда YAMD статически слинкован при сборке.

Valgrind

Valgrind - сравнительно молодой представитель этого класса программ из мира open-source. Используется только для x86-GNU Linux систем. У этой программы больше возможностей, чем у предыдущих отладчиков памяти, однако эти возможности ограничены одной архитектурой. Когда приложение запущено под контролем Valgrind, проверяются все вызовы malloc, free, new и delete и все операции чтения и записи в память. Valgrind умеет отлавливать неинициализированную память, утечки, некоторые неверные типы использования POSIX threads и несимметричное использование malloc/free или new/delete.

Valgrind также может использоваться с gdb для отлова ошибок, позволяя программисту обработать ситуации около контрольных точек. В некоторых случаях после оперативного изготовления патча отладка может быть продолжена. Valgrind умеет работать с большими приложениями, такими, как KDE 3, Mozilla, OpenOffice.

Другой полезной особенностью Valgrind является его способность представить детали профилирования кеша. Он может детально симулировать кеши первого и второго уровней, подсчитывая, сколько обращений сгенерировала та или иная строка кода.

Имеется приличная документация и HOWTO с большим количеством примеров. Веб-сайт проекта содержит много информации и прост для навигации.

Дисплей ошибок Valgrind сопровождает описание ошибки идентификатором процесса отлаживаемой программы. Адреса отображаются вместе с номерами строк и именами файлов исходного кода. Также предоставляется полная трассировочная информация.

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

Valgrind выполняет свои проверки путем запуска приложения в виртуальной процессорной среде. Он заставляет динамический линковщик и загрузчик вначале загружать симулятор, а затем приложение и его библиотеки в среду симулятора. В процессе выполнения программы все данные собираются и либо отображаются на дисплей, либо заносятся в журнал.

mpatrol

Библиотека mpatrol может быть слинкована с вашим приложением для трассировки и ослеживания выделения памяти. Была написана как кроссплатформенная и запускается на множестве целевых платформ. Среди доступных процессорных архитектур MIPS, PowerPC и StrongARM, который был поддежан некоторыми пользователями из MontaVista.

Библиотека весьма конфигурабельна. Кроме использования кучи она может быть настроена на выделение памяти из статического массива фиксированного размера. Может быть собрана статически, динамически, или для многопоточного использования. Также может быть представлена одним большим объектным файлом, что позволяет напрямую связать ее с приложением.

Код библиотеки содержит подмены для 44 различных случаев выделения памяти и работы функций. Перехватчики, предоставляющие эти процедуры, могут быть вызваны из gdb. Это позволяет отлаживать приложение, находящееся под контролем mpatrol.

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

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

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

Если по каким-либо соображениям программист захочет симулировать стресс-тест в условиях недостатка памяти, mpatrol можно настроить на ограничение памяти. Это позволит протестировать условия, которые не наблюдаются в лабораторной среде. Кроме того, тестирующая программа может эмулировать ошибки выделения памяти для проверки процедур восстановления. Снимки кучи могут быть использованы для получения нижней и верхней границ количества используемой памяти.

Insure++

Insure++ не является свободным продуктом. Программа производится Parasoft и не распространяется под лицензией GPL, однако это хороший инструмент для поиска утечек памяти и покрытия кода проверками. По функциональности похожа на mpatrol.

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

Под Линукс программу установит несложно, однако она закрепляется за машиной, на которую установлена. Поставляется с исчерпывающим количеством документации и некоторыми дополнениями. Утилита для покрытия кода идет отдельно, но доступна для отметки в инсталляторе.

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

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

Специальный плагин, который называется Inuse, отображает во время работы тестируемого приложения использование памяти. Вывод утилиты представлен точной диаграммой, содержащей полную информацию о том, какие участки запрашиваются, в каких местах могут быть утечки, как они будут возрастать со временем. Я имел опыт общения с клиентом, обнаружившим, что почти все его C++ - классы страдают некоторыми утечками, которые на рабочей станции казались незначительными. На встроенных системах, которые могут работать месяцами, а то и годами, незначительные утечки через некоторое время превратятся в проблему. С помощью Insure++ такие проблемы легко находятся, локализуются и исправляются. Другие доступные инструменты не отловили незначительные утечки в приложениях этого клиента.

Анализ покрытия кода осуществляется другой утилитой, TCA. Во время работы приложения под Insure++, данные могут передаваться TCA, который графически показывает, какой код сейчас выполняется. С помощью графического интерфейса TCA можно анализировать покрытие кода визуально.

Назад