Valgrind linux как установить

Valgrind linux как установить

Работа с Valgrind

Установка, общая информация

Средство отладки Valgrind входит в состав большинства дистрибутивов Linux. Установка в Ubuntu и Debian:

Valgrind является фреймворком, на основе которого созданы несколько инструментов (tools). Некоторые из них предназначены для поиска ошибок связанных с многопоточносью (Helgrind), другие для оптимизации программ (Cachegrind).

Нас будет в первую очередь интересовать memcheck — инструмент для поиска ошибок, возникающих при работе с памятью. При запуске Valgrind без явного указания инструмента будет запущен именно memcheck.

Подготовка программы для отладки

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

Компиляция с выводом отладочной информации при помощи GCC

Ключи компилятора GCC, отвечающие за отладочную информацию, начинаются с -g. . Например просто ключ -g добавляет в файл «базовую» отладочную информацию. Для вывода подробной отладочной информации, в т.ч. расширений предназначенных для отладчика GDB используется ключ -ggdb3 :

Отладочная информация, оптимизации и поведение программы

Важно понимать, что при наличии в программе ошибок (неопределённого поведения) оптимизации компилятора могут изменять наблюдаемое поведение программы. Например, программа может создавать видимость абсолютно корректной работы при оптимизации с уровнем -O1 , но «падать» с уровнем -O3 . Эта неприятная особенность языков C и С++ является «расплатой» за саму возможность применять некоторые важные оптимизации (т.е. за возможность достичь высокой производительности). Я постараюсь написать об этом отдельный пост.

Наиболее удобно отлаживать программу вообще без оптимизации (т.е. собранной без ключей -O. либо с ключом -O0 ), но, как уже было сказано, ошибка при этом может исчезнуть.

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

Запуск программы под Valgrind-ом

Для запуска программы под Valgrind-ом вы просто указываете список параметров Valgrind-а, затем название вашей программы, и затем параметры, которые вы хотите передать вашей программе. Пример:

Данная команда запустит valgrind с параметром —leak-check=yes (поиск утечек памяти), который с свою очередь запустит программу my-program с параметрами -o out.txt , в поток stdin будет перенаправлен файл in.txt .

Если запустить Valgrind как valgrind ./my-program —leak-check=yes , то параметр —leak-check=yes будет передан программе my-program, а не valgrind

Если вы хотите запустить несколько тестов из shell-скрипта, то следует иметь в виду, что Valgrind по-умолчанию не отслеживает ошибки в дочерних процессах: valgrind ./test-my-program.sh будет проверять командную оболочку bash , а не вашу программу. Но это легко исправить:

Читайте также:  Windows 10 fingerprint настройка

valgrind —trace-children=yes ./test-my-program.sh

Valgrind значительно замедляет работу программы (на порядок и более), это несколько сужает область его применения

Для поиска утечек памяти следует использовать ключ —leak-check=yes , ошибки доступа к памяти диагностируются по умолчанию (дополнительных параметров не требуется).

После запуска Valgrind будет выводить ошибки в поток stderr . При желании вы можете перенаправить этот поток в файл средствами командной оболочки ( 2> log.txt ) либо при помощи параметра —log-file .

Виды ошибок и интерпретация результата

Общий формат вывода, бэктрейс

Сообщение об ошибке может выглядеть следующим образом:

Рассмотрим по отдельности элементы, из которых оно состоит.

  • В столбце слева мы видим число ==72250== . Это идентификатор процесса (pid). Если вам вдруг потребуется отлаживать сразу несколько взаимодействующих между собой процессов (хотя в нашем курсе такая необходимость вряд ли возникнет), он поможет вам понять, в каком именно из процессов произошла ошибка.
  • Далее, в первой строке указан вид ошибки (возможные виды ошибок будут рассмотрены далее). В данном случае Valgrind указывает нам, что произошла ошибка доступа к памяти: попытка записать 8 байт в область, к которой корректно написанная программа не должна обращаться.
  • Все последующие строки представляют собой бэктрейс.

Бэктрейс (backtrace, называемый также stack trace, иногда call string, последовательность вызовов) — это способ точно указать в какой именно точке программы произошло интересующее нас событие (в нашем случае, ошибка). Проблемное место — файл bitmap.h , строка 333 (строки нумеруются с 1, этому соглашению следуют все инструменты для работы с кодом) в функции bitmap_initialize_stat . Указан также адрес в памяти, 0x10200988 (адрес мог бы понадобиться, если по какой-то причине не удалось установить соответствие с исходным кодом). Информации о том, что ошибка произошла в функции bitmap_initialize_stat могло бы быть вполне достаточно, чтобы устранить ошибку, но если бы на этом месте оказалась функция memcpy , то вряд ли бы такая информация была полезной: в большой программе могут быть сотни и тысячи вызовов многих частых функций. Поэтому Valgrind показывает нам, откуда была вызвана функция bitmap_initialize_stat , а именно, из функции bitmap_obstack_alloc_stat (в файле bitmap.c , строка 286), а та, в свою очередь была вызвана из функции df_analyze и так далее, до функции main которая вызвала метод main класса toplev в файле toplev.c .

Рассмотрим некоторые типичные ошибки, встречающиеся в программах и то, какие диагностические сообщения будет выводить Valgrind.

Чтение неинициализированной памяти

Попробуем скомпилировать и запустить следующую программу:

Как видим, в ней выделяется массив data, в первые 3 элемента которого по замыслу разработчика записываются символы a , b и c соответственно. В строке 9 (номера строк указаны в комментарии справа) допущена опечатка, из-за которой символ c записывается во второй элемент, а не в третий. Если запустить программу под Valgrind-ом, мы увидим следующее:

Первое сообщение означает, что в функции _itoa_word было использовано неинициализированное значение длиной в 8 байт. Почему именно 8, а не 1? Это определяется тем, что именно Valgrind считает использованием. Дело в том, что оптимизирующий компилятор может генерировать код, в котом будут присутствовать операции копирования неинициализированных значений, которых в исходной программе не было. Чтобы избежать ложноположительных срабатываний Valgrind не считает копирование использованием. При передаче data[2] в виде параметра в функцию printf значение приводится к типу int , затем где-то в самой функции printf преобразуется в 8-байтное значение, и только здесь Valgrind обнаруживает его использование.

Читайте также:  При перезагрузке перестал загружаться windows

Второе сообщение явно указывает строку 11 в нашей программе: «условный переход или пересылка зависит от неинициализированного значения». В программе присутствует сравнение, результат которого зависит от data[2] (а там, как мы знаем, «мусор»).

Выход за границу массива

Теперь рассмотрим другой пример:

Здесь заново реализована библиотечная функция strdup , которая выделяет память и копирует в неё строку. В ней намеренно допущена ошибка: выделяется на 1 байт меньше, чем необходимо. Valgrind выдаёт следующие сообщения:

Первое сообщение говорит там о том, что в строке 10 произведена некорректная операция записи 1 байта: байт по адресу 0x51de04d находится непосредственно за (0 bytes after) блоком памяти размером 13 байт, выделенном в строке 8.

Во втором сообщении сказано, что внутри функции vfprintf произошло чтение из того же адреса памяти.

Использование памяти после освобождения

Изменим функцию main в предыдущем примере следующим образом:

Сообщение об ошибке тоже изменилось:

Из него видно, что в функции vfprintf, в которую мы попали, вызвав printf в строке 19, произошло чтение из ранее освобождённой области памяти: адрес 0x51de040 находится непосредственно в начале (0 bytes inside) блока длиной 13 байт, освобождённого в строке 18.

Ещё одна ошибка:

В строке 20 мы пытаемся освободить память, которая уже была освобождена ранее. Кстати, эту ошибку часто способен обнаружить аллокатор памяти в библиотеке glibc. Если мы запустим программу без Valgrind, то увидим следующее:

Повторный вызов free приводит к аварийному завершению программы (Valgrind же подменяет функцию free на собственную реализацию и этого не происходит). Как видим, сообщение менее информативно: неясно где именно произошла ошибка. Об этом можно было бы узнать, запустив программу под отладчиком GDB, но в отличие от Valgrind, он не помог бы отследить, когда память была освобождена в первый раз.

Теперь изменим функцию main в нашем примере следующим образом:

Если запустить программу под Valgrind-ом с ключом —leak-check=yes , то Valgrind сообщит нам о том, какие именно блоки памяти не были освобождены:

Несмотря на то что Valgrind крайне мощный и полезный инструмент, он всё же не способен справиться с некоторыми типами ошибок при работе с памятью. А именно, Valgrind не обнаруживает ошибки доступа к переменным со статическим и автоматическим временем жизни (иначе говоря, памяти выделенной в сегменте данных либо на стеке). Обнаруживаются только ошибки, возникающие при работе с динамической памятью (кучей). Например, рассмотрим следующую программу:

Valgrind обнаруживает в ней одну ошибку:

Хотя на самом деле в ней присутствует выход за границы массивов src и dest .

Лирическое отступление. Обнаружение выхода за границы массива на этапе компиляции

Ошибки, имеющися в приведённом примере может обнаружить оптимизирующий компилятор. Скомпилируем программу с оптимизацией:

GCC выдаёт следующие предупреждения:

Clang (вплоть до последней на сегодняшний день версии 3.7) в этой ситуации уступает GCC. Он не способен обнаружить проблем в этой программе. Это связано с принципиально различающимися подходами GCC и Clang к диагностике: Clang (это т.н. фронтэнд компилятора) анализирует программу, переводит её в промежуточное представление и одновременно пытается диагностировать ошибки. Далее промежуточное представление оптимизируется бэкэндом (LLVM), на этом этапе диагностика ошибок не производится. В GCC же диагностика некоторых ошибок производится бэкэндом, за счёт этого те механизмы (анализ количества итераций цикла), которые используются для оптимизации программы удаётся применить также для диагностики ошибок.

Читайте также:  Dxwebsetup exe для windows 10 64 bit

Источник

Valgrind memcheck

Valgrind Memcheck is a tool for detecting memory-usage problems such as leaks, invalid memory access, incorrect freeing, and referencing undefined values.

Valgrind integration in CLion works on Linux, macOS, and Windows via WSL (see Valgrind on WSL).

Configure Valgrind

Install Valgrind on your system.

For WSL, install Valgrind on your WSL instance ( sudo apt-get install valgrind ) and provide CLion with the path to the executable (see next steps).

In CLion, go to Settings / Preferences | Build, Execution, Deployment | Dynamic Analysis Tools | Valgrind .

CLion will attempt to detect the Valgrind binary by searching in standard locations defined in the system PATH variable.

In case of a non-standard Valgrind location, set the path manually in the Valgrind executable field.

Specify analysis options or use the default ones.

Optionally, configure the list of the suppression files. For example, you may want to add a suppression file to turn off the Valgrind checks for some particular libraries.

Configure Valgrind on WSL

To use Valgrind on Windows via WSL, provide the ‘subsystem’ path to Valgrind, which is /usr/bin/valgrind by default, instead of the actual Windows location of the Valgrind binary. However, this path will not be valid until you select the CMake profile connected to the WSL toolchain. For this, do one of the following:

Set the WSL toolchain as default. This way, it will automatically connect to the default CMake profile:

Create a separate CMake profile, connect it to the WSL toolchain, and select this profile in the configurations switcher:

Run targets

To run a target (application or test) with Valgrind Memcheck, do one of the following:

Click the gutter icon and select Run ‘your_target_name’ with Valgrind Memcheck option from the menu:

Select the desired Run/Debug configuration and call Run | Run ‘your_target_name’ with Valgrind Memcheck from the main menu or click the dedicated toolbar icon:

If Valgrind was not configured previously, CLion will open the configuration dialog.

Valgrind might not work correctly when run on a project with Google sanitizers. CLion will warn you about that:

Explore results

When the launch is finished, open the Valgrind tab in the Run tool window.

The left-hand pane shows the list of all problems found by Valgrind Memcheck. Each entry includes the instruction pointer, problematic function name, and (if possible) the source file where the problem is located.

In the right-hand pane, you can find full stack frame description and the corresponding code preview (when possible).

Press F4 to jump back to the source code.

Export and import results

To export the results, click the Export icon in the left pane of the Valgrind tab and specify the destination XML file: Note that the Export button is unavailable during execution.

To import the results, select Run | Import Valgrind XML Results from the main menu and choose the XML file to import from.

Источник

Оцените статью