Программа valgrind полезна в тех случаях, когда Ваша программа использует динамическую память (практически любая нетривиальная программа использует динамическую память). С помощью valgrind Вы можете установить, в каких строчках кода выполняются некорректные действия, например, чтение неинициализированного значения (которое, вообще говоря, может произвольно изменяться от запуска к запуску).
Рассмотрим следующую (очевидно неправильную) программу. Если Вы не видите в ней ошибок, то valgrind нам поможет их выявить. Если же Вы и так видите ошибки, то это хорошо, но не будьте слишком самоуверены: в реальных задачах Вы будете допускать ошибки, которые будут менее заметны.
#include #include int main(void) < int n = 123; double *data; data = (double *)malloc(n * sizeof(double)); if(data[n-1] >0) printf(«Last element is positive!n»); return 0; >
Откомпилируем и запустим эту программу.
$ gcc test_for_valgrind.c $ ./a.out $
Как видим, она успешно откомпилировалась (без ошибок и предупреждений) и выполнилась. Поскольку мы ничего не увидели на экране, то последний элемент массива (с индексом n-1 ) оказался меньше или равен нулю.
How to use valgrind
Ошибки условного перехода
Выполним теперь эту программу «под valgrind». Поскольку мы хотим, чтобы valgrind печатал информацию об ошибках со ссылками на строки исходного кода (информация о том, что в программе есть ошибки не очень полезна сама по себе), то сначала перекомпилируем программу с включением отладочной информации. (Вы можете самостоятельно проверить, что выводит valgrind, если программа откомплирована без ключа -g).
$ gcc -g test_for_valgrind.c $ valgrind -q ./a.out ==12306== Conditional jump or move depends on uninitialised value(s) ==12306== at 0x4005C1: main (test_for_valgrind.c:9) ==12306== $
В нашем первом примере запустим valgrind с ключом -q, который минимизирует объем выводимой информации. Мы получили информацию об одной ошибке. На числа ==12306== можно не обращать никакого внимания — это номер процесса, который был присвоен системой при запуске нашей программы.
Из текста сообщения об ошибке видна, что в строке 9 файла test_for_valgrind.c (в функции main ) был выполнен условный переход (conditional jump), который зависит от неинициализированного значения или значений. Условный переход — это оператор if , оператор цикл while и т.п. В 9 строке нашего файла написано if(data[n-1] > 0) . Источник ошибки ясен. Мы выделили память с помощью вызова malloc , но не записали туда никаких значений. Результат сравнения data[n-1] > 0 в этом случае может быть каким угодно.
Исправим эту ошибку, добавив цикл инициализации элементов массива числами 0, 1, 2, .
#include #include int main(void) < int n = 123, k; double *data; data = (double *)malloc(n * sizeof(double)); for(k = 0; k 0) printf(«Last element is positive!n»); return 0; >
Компилируем и запускаем исправленную программу и видим, что сообщение о положительности последнего элемента массива выводится: ю
Учимся находить утечки памяти. Valgrind — инструмент инженера.
$ gcc -g test_for_valgrind.c $ ./a.out Last element is positive! $
Казалось бы, теперь все нормально. Но, если мы запустим новую версию под valgrind, то мы опять получим сообщение об ошибке. Исправив одну ошибку мы допустили другую (очень распространенная ситуация).
Ошибки записи в память
Выполним измененную программу под valgrind и разберем выведенные сообщения.
$ gcc -g test_for_valgrind.c $ valgrind -q ./a.out ==12378== Invalid write of size 8 ==12378== at 0x4005C3: main (test_for_valgrind.c:10) ==12378== Address 0x51fc418 is 0 bytes after a block of size 984 alloc’d ==12378== at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==12378== by 0x40059C: main (test_for_valgrind.c:8) ==12378== Last element is positive! $
На этот раз ошибка связана с недопустимой операцией записи (Invalid write). Заметим, что несмотря на выполнения «недопусимой записи» наша программа все-таки выполнилась и напечата ожидаемый результат (Last element is positive!). Тем не менее в ней есть ошибка, которая может проявиться только при определенных условиях.
Первые две строчки сообщения об ошибке говорят, что ошибка записи произошла в файле test_for_valgrind.c в строке 10. Было записано 8 байт (это размер значения типа double ). Третья строчка (Address 0x51fc418 is 0 bytes after a block of size 984 alloc’d) означает, что запись этих 8 байт проводилась по адресу, который идет сразу после выделенного блока памяти размером 984 байта. В этом числе угадывается размер нашего массива, так как 984=123*8. Последние две строчки говорят о том, где этот блок памяти был выделен (в функции malloc , которая была вызвана в функции main из 8-ой строки файла test_for_valgrind.c).
Причина этой ошибки в том, что управляющая переменная цикл инициализации значений массива for(k = 0; k пробегает значения от 0 до n включительно, а последний допустимый индекс в массиве — это n-1 .
«Утечка» памяти
После исправления ошибки в цикле инициализации массива data наша программа выполняется без ошибок.
$ gcc -g test_for_valgrind.c $ valgrind -q ./a.out Last element is positive!
. но она все еще не идеальна. Если мы уберем ключ -q (который сокращает до минимума объем выводимой valgrind информации), то мы получим следующее сообщение.
$ valgrind ./a.out ==12561== Memcheck, a memory error detector ==12561== Copyright (C) 2002-2013, and GNU GPL’d, by Julian Seward et al. ==12561== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info ==12561== Command: ./a.out ==12561== Last element is positive! ==12561== ==12561== HEAP SUMMARY: ==12561== in use at exit: 984 bytes in 1 blocks ==12561== total heap usage: 1 allocs, 0 frees, 984 bytes allocated ==12561== ==12561== LEAK SUMMARY: ==12561== definitely lost: 984 bytes in 1 blocks ==12561== indirectly lost: 0 bytes in 0 blocks ==12561== possibly lost: 0 bytes in 0 blocks ==12561== still reachable: 0 bytes in 0 blocks ==12561== suppressed: 0 bytes in 0 blocks ==12561== Rerun with —leak-check=full to see details of leaked memory ==12561== ==12561== For counts of detected and suppressed errors, rerun with: -v ==12561== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) $
Строчка «in use at exit: 984 bytes in 1 blocks» в середине выдачи, и следующая за ней строчка «total heap usage: 1 allocs, 0 frees, 984 bytes allocated», показывают, что мы один раз вызвали функцию выделения памяти (в нашем случае malloc ) и ни разу не вызвали функцию освобождения памяти free . Такая ситуация называется «утечкой памяти». Если запрошенная память больше не нужна в программе, то ее нужно освободить.
Как написано в последних строчках выдачи, valgrind предлагает запустить себя с указанием дополнительного ключа —leak-check=full. Если мы выполним эту просьбу (с добавлением ключа -q), то результат будет следующим.
$ valgrind —leak-check=full -q ./a.out Last element is positive! ==12608== 984 bytes in 1 blocks are definitely lost in loss record 1 of 1 ==12608== at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==12608== by 0x40059C: main (test_for_valgrind.c:8) ==12608==
В отличие от предыдущего запуска, когда мы только узнал, что осталась неосвобожденная память, здесь явно указано, что «пропало» (не было освобождо) 984 байта, которые были выделены одним блоком в строке 7 файла test_for_valgrind.c. Это очень полезная информация, так как обычно программа выделяет значительное число блоков памяти. Если мы не знаем, какая память осталась не освобожденной при окончании программы, то довольно сложно самостоятельно найти место, где она выделялась.
Идеальная программа
После исправления всех ошибок наша «идеальная» программа (конечно, она абсолютно бессмысленна по сути, так как мы сразу можем сказать, что последний жлемент массива всегда положительный) выглядит так:
#include #include int main(void) < int n = 123, k; double *data; data = (double *)malloc(n * sizeof(double)); for(k = 0; k < n; k++) data[k] = 1.0 * k; if(data[n-1] >0) printf(«Last element is positive!n»); free(data); return 0; >
Запуск этой версии под valgrind не выводит никаких ошибок.
$ gcc -g test_for_valgrind.c $ valgrind —leak-check=full -q ./a.out Last element is positive! $
Резюме
- откомпилировать свою программу с включением отладочной информации gcc -g имя_Вашего_файла.c ;
- запустить valgrind -q —leak-check=full имя_выполняемого_файла ;
- исправить все ошибки, сообщения о которых выдает предыдущая команда;
- запустить valgrind -v —leak-check=full имя_выполняемого_файла и убедиться, что в последней строчке (ERROR SUMMARY) стоят нули.
Источник: serg.tk
Использование Valgrind для поиска утечек и недопустимого использования памяти
Valgrind является многоцелевым инструментом профилирования кода и отладки памяти для Linux на x86, и, начиная с версии 3 и AMD64. Это позволяет запускать программу в собственной среде Valgrind, что контролирует использование памяти, например, вызовы malloc и free (или new и delete в C++). Если вы используете неинициализированную память, записываете за пределами концов массива, или не освобождаете указатель, Valgrind может это обнаружить. Поскольку это наиболее распространенные проблемы, эта статья будет сосредоточена главным образом на использовании Valgrind для обнаружения простых проблем с памятью, хотя Valgrind — это инструмент, который может сделать гораздо больше.
Для пользователей Windows: если вы не имеете доступа к Linux, или если вы хотите разрабатывать специальное программное обеспечение Windows, вас может заинтересовать IBM Purify, которая имеет аналогичные Valgrind функции для поиска утечек и неправильного доступа к памяти. Доступна пробная версия.
Получение Valgrind
Если вы работаете в Linux и у вас пока нет копии, то вы можете получить Valgrind на странице загрузки Valgrind.
Установка проста: распакуйте архив используя команды bzip2 и tar (X.Y.Z — номер версии в примерах ниже, разделённые символами точки):
bzip2 -d valgrind-X.Y.Z.tar.bz2
tar -xf valgrind-X.Y.Z.tar
После выполнения этих команд, будет создан каталог с именем valgrind-X.Y.Z , зайдите в этот каталог (команда cd и через пробел путь) и выполните следующие команды:
./configure
make
make install
Теперь, когда вы установили Valgrind, давайте посмотрим как его использовать.
Поиск утечек памяти с помощью Valgrind
Утечки памяти одни из самых трудных для обнаружения ошибок, потому что они не вызывают никаких внешних проблем, до тех пор, пока у вас не закончится память и вам не удастся вызвать malloc . В самом деле, при работе с языками C или C++, которые не имеют сборки мусора, почти половину времени вы можете потратить на правильное освобождение памяти. И даже одна ошибка может дорого обойтись, если ваша программа работает достаточно долго и следует этой ветви кода.
Когда вы запустите код, вы должны будете указать инструмент, который хотите использовать, просто запустите Valgrind для получения текущего списка. В этой статье мы сосредоточимся в основном на инструменте Memcheck, так как Valgrind с инструментом Memcheck позволит нам проверить правильность использования памяти. Без дополнительных аргументов, Valgrind выведет обзор вызовов free и malloc:
valgrind —tool=memcheck myProg
myProg — это имя программы (объектный файл, который получается после процесса построения проекта — компиляции). Если в проекте нет утечки памяти, вывод будет похож на этот
==15916== HEAP SUMMARY:
==15916== in use at exit: 16 bytes in 1 blocks
==15916== total heap usage: 5 allocs, 4 frees, 80 bytes allocated
==15916==
==15916== LEAK SUMMARY:
==15916== definitely lost: 16 bytes in 1 blocks
==15916== indirectly lost: 0 bytes in 0 blocks
==15916== possibly lost: 0 bytes in 0 blocks
==15916== still reachable: 0 bytes in 0 blocks
==15916== suppressed: 0 bytes in 0 blocks
==15916== Rerun with —leak-check=full to see details of leaked memory
==15916==
==15916== For counts of detected and suppressed errors, rerun with: -v
==15916== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
(Обратите внимание, что 15916 — идентификатор процесса в системе, он будет отличаться от запуска к запуску.)
Если у вас утечка памяти, то количество malloc и количество free будут отличаться (вы не можете использовать один free для освобождения памяти, принадлежащей более чем одному malloc ). Мы вернемся к ошибкам позже, а сейчас заметьте, что некоторые ошибки могут быть упущены — это потому, что эти ошибки будут в стандартных библиотечных функциях, а не в вашем коде.
Если количество malloc отличается от количества free , вы захотите перезапустить программу снова с опцией leak-check . Это покажет вам все вызовы malloc/new и т.д., которые не имеют соответствующего free .
В целях демонстрации я буду использовать очень простую программу, которую я скомпилирую в исполняемый файл под названием «example1»
int main() < char *ix = new char [10]; // или, char *ix = malloc(10) для языка СИ return 0; >
Это покажет некоторую информацию о программе, завершающуюся списком вызовов new , которые не имеют последующих вызовов delete :
==18350== HEAP SUMMARY:
==18350== in use at exit: 10 bytes in 1 blocks
==18350== total heap usage: 1 allocs, 0 frees, 10 bytes allocated
Это не дает нам столько информации, как хотелось бы, но — мы знаем, что утечка памяти произошла из-за вызова new в main , но у нас нет номера строки. Проблема в том, что мы компилировали без параметра -g в g++ , который добавляет символы отладки. Поэтому, если мы перекомпилируем код с отладочной информацией, мы получим более полезную информацию:
==15635== HEAP SUMMARY:
==15635== in use at exit: 10 bytes in 1 blocks
==15635== total heap usage: 1 allocs, 0 frees, 10 bytes allocated
==15635==
==15635== 10 bytes in 1 blocks are definitely lost in loss record 1 of 1
==15635== at 0x4C2BAD7: operator new[](unsigned long) (vg_replace_malloc.c:363)
==15635== by 0x400575: main (man.cpp:3)
Теперь мы знаем точную строку, где был вызов new , в третьей строке. Хотя отслеживание места, где необходимо освободить память, еще под вопросом, по крайней мере, вы знаете, с чего начать поиск. И так как для каждого вызова malloc или new , вы должны иметь план по работе с памятью, знание, где произошла утечка памяти поможет вам выяснить, где начать поиск.
Иногда —leak-check=yes не показывает все утечки памяти. Чтобы найти абсолютно все непарные вызовы free или new , необходимо использовать —show-reachable=yes . Вывод программы будет почти точно такой же, но он будет показывать больше неосвобождённой памяти.
Поиск недопустимого использования указателя с Valgrind
Valgrind может также показывать неверное использование памяти с помощью инструмента Memcheck. Например, если выделить массив импользуя malloc или new , а затем попытаться получить доступ к элементу за пределами массива:
char *ptr = new char [10]; ptr[10] = ‘c’;
Valgrind обнаружит его. Например, проверим следующую программу, с помощью Valgrind
int main()
Сначала компилируем в g++ этот исходник, команда g++ -g programname . После этого в терминале вводим команду запуска Valgrind:
valgrind —tool=memcheck —leak-check=yes ‘/home/den/a.out’
В ответ Valgrind нам выдаст следующее сообщение:
==15911== Invalid write of size 1
==15911== at 0x400582: main (man.cpp:4)
==15911== Address 0x5a0504a is 0 bytes after a block of size 10 alloc’d
==15911== at 0x4C2BAD7: operator new[](unsigned long) (vg_replace_malloc.c:363)
==15911== by 0x400575: main (man.cpp:3)
Это говорит нам о том, что мы используем указатель, выделенный для 10 байт, за пределами этого диапазона, — следовательно, мы получаем Invalid write . Если бы мы пытались читать из этой памяти, мы бы получили предупреждение Invalid read of size num , где num — это объем памяти, который мы пытаемся прочитать. (Для char это будет один, а для int это будет либо 2, либо 4, в зависимости от вашей системы.) Как обычно, Valgrind выводит трассировку стека вызовов функций, так что мы точно знаем, где произошла ошибка .
Обнаружение использования неинициализированных переменных
Другой тип операции, которую обнаруживает Valgrind, это использование неинициализированного значения в условном операторе. Хотя у вас должна войти в привычку инициализация всех переменных, которые вы создаете, Valgrind поможет найти их в тех случаях, когда вы ее не делаете. Например, выполнив следующий код:
#include int main() < int num; if(num == 0) std::cout
через Valgrind, получим следующий ответ:
==16284== Conditional jump or move depends on uninitialised value(s)
==16284== at 0x4006E0: main (man.cpp:6)
Valgrind достаточно умен, чтобы знать, что, если переменной не присваивается значение, то эта переменная все еще находится в «неинициализированном» состоянии, а значит никаких операций с ней быть не должно, до тех пор пока она не инициализируется. Например, выполнив следующий код:
#include int func(int val) < if(val < 7) < std::cout > int main()
в Valgrind, результом будет следующее предупреждение:
==16371== Conditional jump or move depends on uninitialised value(s)
==16371== at 0x4006E3: func(int) (man.cpp:5)
==16371== by 0x400707: main (man.cpp:14)
Вы думаете, что проблема была в func , и что остальная часть вызовов стека, вероятно, не так уж важна. Но так как main предоставляет неинициализированное значение в foo (мы не присваиваем значение num ), то получается, что здесь мы должны начать искать и отслеживать путь присвоения переменных, пока не найдем переменную, которая не была инициализирована.
Это поможет только если вы на самом деле тестируете ту ветвь кода, и, в частности, тот условный оператор. Убедитесь в том, что вы охватили все пути выполнения во время тестирования!
Что еще находит Valgrind
Valgrind обнаружит другие случаи неправильного использования памяти: если вы вызываете delete дважды с одним и тем же значением указателя, Valgrind обнаружит это, и вы получите сообщение об ошибке:
==16441== Invalid free() / delete / delete[] / realloc()
Valgrind также обнаруживает неправильно выбранные методы освобождения памяти. Например, в C++ есть три основных варианта для освобождения динамической памяти: free , delete и delete[] . Функция free должна быть согласована с вызовом malloc , а не с вызовом, например, delete — на некоторых системах вы могли бы не делать этого, но это не очень переносимо. Кроме того, ключевое слово delete должно быть только в паре с ключевым словом new (для выделения отдельных объектов), а ключевое слово delete[] должно быть только в паре с ключевым словом new[] (для распределения массивов). (Хотя некоторые компиляторы позволят обойтись без использования неправильной версии delete , нет никакой гарантии, что все из них позволят это.)
Что Valgrind не найдет?
Valgrind не выполняет проверку границ в статических массивах (выделенных в стеке). Так что если вы объявите массив внутри функции:
int main()
то Valgrind не предупредит вас! Одно из возможных решений для тестирования — просто изменить свои статические массивы на динамически выделяемые, где вы получите проверку на границы, хотя это может внести дополнительную путаницу связанную с вызовами delete .
Еще несколько предостережений
Какой недостаток использования Valgrind? Он использует больше памяти — до двух раз больше, чем ваша обычная программа. Если вы тестируете программу, которая использует очень много памяти, у вас могут возникнуть проблемы. Также увеличивается время запуска программы, когда вы используете Valgrind. Но это не проблема, так как это только во время тестирования.
Но если ваша программа медленная, это может быть существенным.
Наконец, Valgrind не обнаружит все ваши ошибки — если вы не проверите переполнение буфера с помощью длинных строк ввода, Valgrind не скажет, что ваш код может записывать в память, которой он не должен касаться. Valgrind, как и любой другой инструмент, необходимо использовать разумно, как способ устранения проблем.
Вывод
Valgrind является инструментом для архитектур x86 и AMD64, и в настоящее время работает под управлением Linux. Valgrind позволяет программисту запустить исполняемый файл внутри своей собственной среды, в которой он проверяет непарные вызовы malloc и другие виды недопустимого использования памяти (например, инициализируемой памяти) или неверных операций с памятью (например, двойное освобождение блока памяти или вызов неправильной функции освобождения памяти). Valgrind не проверяет использование статистически выделенных массивов.
Источник: cppstudio.com
Записки программиста
Поиск ошибок работы с памятью в C/C++ при помощи Valgrind
Если вы пишете код на языке C или C++, поиск и устранение ошибок работы с памятью, таких, как утечки, выход за границы массива или обращение к неинициализированной памяти, могут доставить немало хлопот. Существует по крайней мере два инструмента для решения этих проблем — Valgrind (не путать с Vagrant!) и Clang’овский MemorySanitizer.
Последний работает исключительно под Linux и показал себя несколько сырым и не слишком гибким инструментом, поэтому поговорим о Valgrind. Он довольно гибок и работает везде. Кроме того, в отличие от MemorySanitizer, Valgrind может находить неинициализированные данные с точностью до одного бита. Из недостатков Valgrind стоит отметить сравнительно низкую скорость работы.
Простой пример
Перейдем сразу к делу и проверим работу Valgrind на такой программе:
#include
#include
#include
void run_test ( int i )
{
int delta = 123 ;
char * mem = malloc ( 1024 ) ;
strcpy ( mem, «i = » ) ;
printf ( «%s %d n » , mem, i + delta ) ;
/* free(mem); */
}
void main ( )
{
int i ;
for ( i = 0 ; i < 10 ; i ++ )
run_test ( i ) ;
}
Компилируем с отладочными символами и запускаем ее под Valgrind:
gcc -O0 -g vgcheck.c -o vgcheck
valgrind . / vgcheck
==1948== HEAP SUMMARY:
==1948== in use at exit: 10,240 bytes in 10 blocks
==1948== total heap usage: 11 allocs, 1 frees, 11,264 bytes allo.
==1948==
==1948== LEAK SUMMARY:
==1948== definitely lost: 10,240 bytes in 10 blocks
==1948== indirectly lost: 0 bytes in 0 blocks
==1948== possibly lost: 0 bytes in 0 blocks
==1948== still reachable: 0 bytes in 0 blocks
==1948== suppressed: 0 bytes in 0 blocks
==1948== Rerun with —leak-check=full to see details of leaked memory
Видим, что память утекла. Запускаем с —leak-check=full :
==2047== 10,240 bytes in 10 blocks are definitely lost in loss recor.
==2047== at 0x4C2AF1F: malloc (in /usr/lib/valgrind/vgpreload_mem.
==2047== by 0x400561: run_test (vgcheck.c:8)
==2047== by 0x4005AF: main (vgcheck.c:18)
Теперь раскомментируем вызов free и уберем инициализацию переменной delta . Посмотрим, увидит ли Valgrind обращение к неинициализированной памяти:
==2102== Conditional jump or move depends on uninitialised value(s)
==2102== at 0x4E8003C: vfprintf (in /usr/lib/libc-2.25.so)
==2102== by 0x4E87EA5: printf (in /usr/lib/libc-2.25.so)
==2102== by 0x4005CA: run_test (vgcheck.c:10)
==2102== by 0x4005F4: main (vgcheck.c:18)
Видит. Запустим с —track-origins=yes чтобы найти, откуда именно пришла неинициализированная переменаая:
==2205== Conditional jump or move depends on uninitialised value(s)
==2205== at 0x4E800EE: vfprintf (in /usr/lib/libc-2.25.so)
==2205== by 0x4E87EA5: printf (in /usr/lib/libc-2.25.so)
==2205== by 0x4005CA: run_test (vgcheck.c:10)
==2205== by 0x4005F4: main (vgcheck.c:18)
==2205== Uninitialised value was created by a stack allocation
==2205== at 0x400586: run_test (vgcheck.c:6)
Как видите, Valgrind нашел место объявления неинициализированной переменой с точностью до имени файла и номера строчки.
Теперь исправим все ошибки:
==2239== HEAP SUMMARY:
==2239== in use at exit: 0 bytes in 0 blocks
==2239== total heap usage: 11 allocs, 11 frees, 11,264 bytes allo.
==2239==
==2239== All heap blocks were freed — no leaks are possible
Ну разве не красота?
Пример посложнее — запускаем PostgreSQL под Valgrind
Рассмотрим, как происходит запуск под Valgrind больших программ, например, PostgreSQL. Работа с памятью в этой РСУБД устроена особым образом. Например, в ней используются иерархические пулы памяти (memory contexts). Для понимания всего этого хозяйства Valgrind’у нужны подсказки. Чтобы такие подсказки появились, нужно раскомментировать строчку:
#define USE_VALGRIND
… в файле src/include/pg_config_manual.h, после чего полностью пересобрать PostgreSQL. Затем запуск под Valgrind осуществляется как-то так:
valgrind —leak-check =no —track-origins = yes —gen-suppressions =all
—read-var-info = yes
—log-file = $HOME / work / postgrespro / postgresql-valgrind /% p.log
—suppressions =src / tools / valgrind.supp —time-stamp = yes
—trace-children = yes postgres -D
$HOME / work / postgrespro / postgresql-install / data-master
2 >
Memcheck:Leak
.
obj:/usr/*lib*/libpango*
>
В зависимости от используемых флагов, make installcheck под Valgrind’ом на моем ноутбуке выполняется от получаса до часа. Для сравнения, без Valgrind’а соответствующий прогон тестов занимает порядка 3.5 минут. Отсюда можно сделать вывод, что программа под Valgrind выполняется в 10-20 раз медленнее.
Использование Valgrind совместно с GDB
Посмотрев на приведенные выше отчеты Valgrind’а об ошибках, можно заметить, что в определенном смысле они недостаточно информативны. В частности, в них нет имен переменных и информации о том, какие конкретно данные в них лежали на момент возникновения ошибки. Решается эта проблема запуском Valgrind’а с флагами:
valgrind —vgdb = yes —vgdb-error = 1 дальше_как_обычно
Эти флаги говорят Valgrind остановить процесс и запустить gdb-сервер после возникновения первой ошибки. Можно указать и —vgdb-error=0 , чтобы подключиться к процессу отладчиком сразу после его запуска. Однако это может быть плохой идеей, если вы также указали —trace-children=yes и при этом программа создает множество дочерних процессов.
При возникновении ошибки Valgrind напишет:
==00:00:00:06.603 16153== TO DEBUG THIS PROCESS USING GDB: start GDB.
==00:00:00:06.603 16153== /path/to/gdb postgres
==00:00:00:06.603 16153== and then give GDB the following command
==00:00:00:06.603 16153== target remote | vgdb —pid=16153
После этого, чтобы подключиться к процессу при помощи GDB, говорим:
# где postgres — имя исполняемого файла
gdb postgres
… и уже в отладчике:
target remote | vgdb —pid=16153
Из интересных дополнительных команд доступны следующие. Посмотреть список утечек:
monitor leak_check
Узнать, кто ссылается на память:
monitor who_points_at (address) (len)
Проверка инициализированности памяти (0 — бит инициализирован, 1 — не инициализирован, _ — not addressable):
monitor get_vbits (address) (len)
monitor help
Дальше отлаживаем, как обычно. Например, говорим continue . Как только произойдет следующая ошибка, программа снова остановится по брейкпоинту. Можно смотреть значения переменных, перемещаться между фреймами стека, ставить собственные брейкпоинты, и так далее.
К сожалению, в рамках одного поста невозможно рассмотреть абсолютно все возможности Valgrind. Например, в него входят инструменты Callgrind и Massif, предназначенные для поиска узких мест в коде и профилирования памяти соответственно. Эти инструменты я не рассматриваю, так как для решения названных задач предпочитаю использовать perf и Heaptrack. Также существует инструмент Helgrind, предназначенный для поиска гонок. Его изучение я вынужден оставить вам в качестве упражнения.
Как видите, пользоваться Valgrind крайне просто. Он, конечно, не идеален. Как уже отмечалось, Valgrind существенно замедляет выполнение программы. Кроме того, в нем случаются ложноположительные срабатывания. Однако последняя проблема решается составлением специфичного для вашего проекта файла подавления конкретных отчетов об ошибках.
Так или иначе, если вы пишете на C/C++ и не прогоняете код под Valgrind хотя бы в Jenkins или TeamCity незадолго до релиза, вы явно делаете что-то не так!
А как вы ищете утечки и обращения к неинициализированной памяти?
Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.
Источник: eax.me