Valgrind mac os как пользоваться

Обновлено: 07.07.2024

Я загрузил Valgrind и извлек папку. Я совершенно заблудился и понятия не имею, как это построить.

Я не знаю НИКАКИХ команд терминала / консоли и, как правило, новичок в программировании, поэтому я понятия не имею, как его «собрать» или «скомпилировать». У меня просто есть папка Valgrind с кучей случайных файлов в ней.

Может кто-нибудь сказать мне, как действовать? Я уже проверил веб-сайт / документацию, но на самом деле он не дал мне инструкций по установке, только инструкции по использованию.

PS: Я знаю, что уже отправил этот вопрос, но предыдущий вопрос был закрыт из-за того, что он был слишком расплывчатым. Я перепостил этот с дополнительной информацией.

PSS: Все, что я в основном спрашиваю, это что мне делать, чтобы установить Valgrind сразу после того, как я загрузил его с веб-сайта и распаковал файлы?

Используйте brew : brew install valgrind

Вот что сработало на моем Mac (10.6). Дважды проверьте, что у вас последняя версия, а затем перейдите в несжатый каталог.

Предлагаю вам поступить так, как написали еще, и прочитать ридми.

Начните сборку; /usr/local - это место в файловой системе, в которое будет установлена ​​программа. Есть много аргументов, таких как prefix , которые доступны для настройки установки для вашей конкретной системы, если она не работает по умолчанию. Обычно просто использование ./configure работает отлично.

Или вы, вероятно, могли бы получить его от fink, macports или homebrew.

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

После того, как вы установили macports, вам нужно ввести:

Поскольку вы говорите, что у вас нет опыта работы с командными строками, давайте просто кратко рассмотрим различные части этой команды:

  • sudo означает, что остальная часть команды будет выполняться от имени пользователя root и запросит ваш пароль. Это необходимо для того, чтобы установщик имел правильные разрешения.
  • port - это инструмент командной строки для управления macports.
  • install - это команда для port . Попробуйте port help увидеть список команд.
  • valgrind сообщает macports, что он должен устанавливать
  • +universal - вариант. Это указывает macports настроить valgrind для поддержки 32- и 64-разрядной поддержки.

Основной процесс довольно прост:

Убедитесь, что вы находитесь в правильном каталоге.

Когда это закончится, запустите:

На этом этапе вам нужно будет su войти в корневой каталог (это относительно сложно сделать, см. примечание в конце). Как root, запустите:

Когда это будет завершено, у вас будет рабочая установка valgrind. Проверьте это, запустив

Чтобы su войти в систему root, вам необходимо создать учетную запись root. Если вы не делали этого раньше, см. Инструкции от Apple здесь.

Чтобы выполнить фактическое su , вам сначала нужно будет запустить учетную запись с правами администратора. Если вы еще этого не сделали, вы можете переключать пользователей следующим образом:

Где admin - имя пользователя с правами администратора (вам нужно будет ввести пароль для этой учетной записи). Оттуда вы можете запустить su , чтобы войти в root:

Вам нужно будет ввести пароль root, который вы установили ранее.

(1) Чтобы правильно установить его, сначала введите следующую команду в Терминале (которая открывает формулы Valgrind)

И измените URL-адрес в разделе заголовка

(2) Выполните обновление для Homebrew:

(3) Наконец, используйте следующую команду для установки Valgrind из HEAD:

Источники

Вы можете установить его через brew для Mac:

Возможно, вам придется вручную связать запись с /usr/local/bin , поскольку brew отказался сделать это в моем случае:

Не так давно мы пытались продемонстрировать пользу от использования статического анализатора PVS-Studio одной из компаний. Ничего дельного из этого не вышло. Но в процессе переписки я подготовил развёрнутый ответ, касающийся методологий статического и динамического анализа. Сейчас я решил оформить этот ответ в виде небольшой статьи. Думаю, текст может показаться интересным читателям, да и просто можно будет использовать эту статью при общении с новыми потенциальными клиентами.

Итак, в процессе переписке был задан вопрос приблизительно следующего содержания:

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

Мной был подготовлен следующий ответ, который я привожу, сделав только небольшие поправки:

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

Пытаться найти что-то в давно и стабильно работающем коде достаточно неблагодарное занятие. Смысл статического анализа — это предотвратить множество ошибок на самых ранних этапах. Да, большинство этих ошибок можно найти другими методами. Их заметит или сам программист, или выявят большие тесты или тестировщики. В худшем случае, об ошибках сообщат пользователи. Но в любом случае, это зря потраченное время. Многие из опечаток, ошибок Copy-Paste и прочих ляпов можно устранить ещё на самых ранних этапах с помощью статического анализа. Найти многие ошибки сразу после написания кода — вот главная его ценность. Нахождение ошибки на любом следующем этапе обходится во много раз дороже.

Хорошо, допустим мы убедили вас, что статический анализ может найти какие-то ошибки. Справедлив вопрос, но нужен ли он, раз есть такие инструменты как valgrind. Ведь они действительно дают меньше ложных срабатываний.

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

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

Анализатор PVS-Studio выдаёт предупреждение: V514 Dividing sizeof a pointer 'sizeof (m_letters)' by another value. There is a probability of logical error presence. slow.h 238

Скорее всего, когда-то член класса 'm_letters' являлся массивом с жестко заданным размером. Конечно, это просто предположение, но вполне вероятное. Например, вначале было написано как-то так: size_t m_letters[MAX_COUNT];. В те времена определение размера массива было корректным:

Затем массив стал динамическим, и переменная 'm_letters' превратилась в простой указатель. Теперь выражение «sizeof(m_letters)/sizeof(*m_letters)» всегда равно единице. В 32-битной системе размер указателя и типа size_t равны 4. В 64-битной системе размер этих типов будет 8. Однако, независимо от того, делим мы 4 на 4 или 8 на 8, мы всегда получаем 1.

Таким образом, функция Fill() обнуляет только один байт массива. Ошибка может вовсе не проявить себя, если память случайно окажется уже обнулённой или, если неинициализированные элементы не будут использоваться. В этом её коварство. Но может произойти чтение неинициализированного элемента массива.

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

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

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

Кстати, немного отойдём от темы. Мы можем не только предложить вам наш анализатор, но и услуги по аудиту кода. Результатом такого аудита может стать документ, включающий набор рекомендаций по улучшениям. Его вполне можно включить в стандарт кодирования. Мы уже имеем опыт выполнения таких работ. Например, чтобы не допускать ошибок, связанных с вычислением размера массива, можно рекомендовать использовать специальную технологию (подсмотренную в Chromium):

Макрос 'arraysize' нельзя применить к обыкновенному указателю. Возникнет ошибка компиляции. Тем самым мы защищаемся от случайных ошибок. Если вдруг массив превратится в указатель, нельзя будет пропустить место, где вычисляется его размер.

Вернёмся к статическому и динамическому анализу. Например, посмотрим на эту функцию:

С точки зрения динамического анализа здесь нет ничего подозрительного. В свою очередь, статический анализатор PVS-Studio предлагает обратить внимание на переменную 'leading': V560 A part of conditional expression is always false: !leading. recyr_int.hh 220

Думаю, здесь нет никакой ошибки. Переменная 'leading' оказалась после рефакторинга лишней. А вдруг нет? Вдруг код не дописан? Это то место, на которое стоит обратить внимание. И, если переменная лишняя, то удалить её, чтобы она не сбивала с толку не только анализатор, но и людей, которые будут поддерживать этот код.

Такие ошибки не могут быть найдены динамическим анализом. Ему тут нечего искать. Это просто некорректные логические выражения.

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

Рассмотрим функцию sslDeriveKeys, работающую с приватными данными:

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

Нас интересует локальный массив 'buf'. Так как он хранит приватные данные, то в конце функции сделана попытка обнулить этот массив с помощью функции memset(). В этом и заключается ошибка.

Смотрите, после вызова memset() локальный массив 'buf' более не используется. Это значит, что компилятор вправе удалить вызов фунции memset(), так как её вызов не оказывает никакого эффекта с точки зрения языка Си/Си++. Более того, он не только в праве, но и действительно удалит эту функцию в release версии.

В результате, приватные данные останутся в памяти и теоретически могут попасть туда, куда им попадать не надо. Таким образом, проект становится чуть менее безопасным из-за ошибки в сторонней библиотеке.

PVS-Studio выдаст следующее предупреждение: V597 The compiler could delete the 'memset' function call, which is used to flush 'buf' buffer. The RtlSecureZeroMemory() function should be used to erase the private data. sslv3.c 123

Надеюсь нам удалось показать отличия между статическим и динамическим анализатором кода. Эти два подхода хорошо дополняют друг друга. То, что статический анализ выдаёт много ложных срабатываний не является проблемой. С ними можно работать и устранять, настраивать анализатор. В случае интереса, мы можем проделать такую работу, чтобы сократить количество предупреждений до комфортного уровня.

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

Я загрузил Valgrind и извлек папку. Я совершенно запуталась, и понятия не имею, как его построить.

Я не знаю никаких команд терминала / консоли и, как правило, новичок в программировании, поэтому я понятия не имею, как "построить" или "скомпилировать" его. У меня просто есть папка под названием Valgrind с кучей случайных файлов в нем.

может кто-нибудь сказать мне, как действовать? Я уже проверил веб-сайт / документация, но на самом деле это не дало мне инструкции по установке, просто инструкции по использованию.

PS: Я знаю, что я уже разместил этот вопрос, но предыдущий вопрос был закрыт, за слишком расплывчатый. Я перепостил это с дополнительной информацией.

PSS: все, что я в основном спрашиваю, что мне делать, чтобы установить Valgrind сразу после загрузки с веб-сайта и извлечь файлы?

использовать brew : brew install valgrind

вот что работало на моем mac (10.6). Дважды проверьте, что у вас есть последняя версия (прямо сейчас, 3.7.0), затем перейдите в несжатый каталог

Я предлагаю вам сделать, как другой опубликованный и прочитать readme.

начать строить; /usr/local - это место в файловой системе, в которое будет установлена программа. Есть много аргументов как prefix которые доступны для настройки установки в вашей конкретной системе, если она не работает по умолчанию. Обычно просто используя ./configure работает отлично, хотя.

или вы, вероятно, могли бы получить его от fink или macports или homebrew.

вы можете установить его через brew для Mac:

возможно, вам придется вручную связать запись с /usr/local/bin так как Брю отказался это делать в моем случае:

основной процесс довольно прост:

убедитесь, что вы находитесь в правильном каталоге.

когда это закончится, запустите:

в этот момент вам нужно будет su в корень (это довольно сложно сделать, см. Примечание В конце). Как root, запустите:

когда это будет закончено, у вас будет рабочая установка valgrind. Тест бег!--12-->

до su в root вам нужно будет настроить учетную запись root. Если вы не делали этого в прошлом, см. инструкции от apple здесь.

для выполнения su , сначала вам нужно будет запустить учетную запись с правами администратора. Если это еще не сделано, можно переключить пользователей следующим образом:

здесь admin имя пользователя с правами администратора привилегии (нужно будет ввести пароль для этой учетной записи). Оттуда вы можете запустить su чтобы войти в root:

вам нужно будет ввести пароль root, который вы установили ранее.

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

после установки macports необходимо ввести:

как вы говорите, у вас нет никакого опыта командной строки, давайте просто кратко рассмотрим различные части этой команды:

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

Данная статья содержит краткое описание принципов работы с valgrind и использования различных его модулей. Данное описание соответствует valgrind версии 3.3. Дополнительную информацию о работе с valgrind вы можете найти на его сайте, который содержит руководства разного уровня сложности, начиная от достаточно легкого Quick Start, и заканчивая подробными руководством пользователя и техническим описанием системы.

Архитектура Valgrind

Valgrind имеет модульную архитектуру, и состоит из ядра, которое выполняет эмуляцию процессора, а конкретные модули выполняют сбор и анализ информации, полученной во время выполнения кода на эмуляторе. Valgrind работает под управлением ОС Linux на процессорах x86, amd64, ppc32 и ppc64 (стоит отметить, что ведуться работы по переносу Valgrind и на другие ОС), при этом существуют некоторые ограничения, которые потенциально могут повлиять на работу исследуемых программ. 1

В поставку valgrind входят следующие модули-анализаторы:

memcheck основной модуль, обеспечивающий обнаружение утечек памяти, и прочих ошибок, связанных с неправильной работой с областями памяти — чтением или записью за пределами выделенных регионов и т.п. cachegrind анализирует выполнение кода, собирая данные о (не)попаданиях в кэш, и точках перехода (когда процессор неправильно предсказывает ветвление). Эта статистика собирается для всей программы, отдельных функций и строк кода callgrind анализирует вызовы функций, используя примерно ту же методику, что и модуль cachegrind. Позволяет построить дерево вызовов функций, и соответственно, проанализировать узкие места в работе программы. massif позволяет проанализировать выделение памяти различными частями программы helgrind анализирует выполняемый код на наличие различных ошибок синхронизации, при использовании многопоточного кода, использующего POSIX Threads.

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

Начало работы с valgrind

В настоящее время valgrind входит в состав практически всех дистрибутивов Linux, и только в редких случаях требуется его установка вручную, поэтому я пропущу описание этого процесса 2 .

Работа с valgrind достаточно проста — его поведение полностью управляется опциями командной строки, а также не требует специальной подготовки программы, которую вы хотите проанализировать (Хотя все-таки рекомендуется пересобрать программу с отладочной информацией и отключенной оптимизацией используя флаги компиляции -g и -O0 ). Если программа запускается командой " программа аргументы ", то для ее запуска под управлением valgrind, необходимо в начало этой командной строки добавить слово valgrind , и указать опции, необходимые для его работы. Например, так:

что приведет к запуску нужной программы c заданными аргументами, и для нее будет проведен поиск утечек памяти.

По умолчанию, valgrind запускает модуль memcheck, однако пользователь может указать какой модуль должен выполняться с помощью опции --tool , передав в качестве аргумента имя нужного модуля, например, вот так:

Стоит отметить, что часто используемые опции можно задать один раз, используя глобальный файл конфигурации (

/.valgrindrc ), так что вам не придется их набирать при каждом запуске valgrind.

Общие опции запуска программы

Некоторые опции командной строки являются общими для всех модулей. К наиболее часто используемым опциям можно отнести 3 :

Опции управления обработкой ошибок

Пользователь valgrind имеет достаточно большой набор опций, предназначенных для управления процессом обработки ошибок — начиная от опций управления форматом вывода, и заканчивая опциями, задающими размер стека.

Пользователь также может управлять тем, сколько и каких ошибок будет выведено в отчет. Для этого имеется опция --error-limit ( yes или no , по умолчанию yes ), которая позволяет ограничить отчет выводом 1000 различных ошибок. Если пользователь не ограничивает вывод ошибок, то это также сказывается на производительности.

Кроме того, пользователь может управлять тем, какие ошибки будут выдаваться в отчет, а какие нет. Это делается с помощью задания специальных директив (suppressions), которые записываются в файлы, имена которых можно передать с помощью опции --suppressions . В поставке valgrind есть файл (обычно это /usr/lib/valgrind/default.supp ), в котором перечислены известные ошибки glibc, но кроме того, пользователь может изготовить собственный файл, для чего можно использовать опцию --gen-suppressions , которая будет запрашивать пользователя, нужно ли сгенерировать директиву для данной ошибки, или нет.

Пользователь также имеет возможность запуска отладчика при нахождении ошибок. Для этого существует опция --db-attach ( yes или no , по умолчанию no ), при использовании которой у пользователя будет запрашиваться разрешение на запуск отладчика. Опции для запуска отладчика могут быть указаны с помощью опции --db-command , но значений по умолчанию вполне достаточно для большинства случаев.

Поиск утечек памяти

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

--leak-check включает (значение yes , summary или full ) или отключает (значение no ) функцию обнаружения утечек памяти. Стоит отметить, что при использовании значения summary , memcheck выдает лишь краткую информацию об утечках памяти, тогда как при других значениях, кроме сводной информации, будет выдаваться еще и информация о месте, в котором происходит эта утечка памяти. --leak-resolution (возможные значения low , med или high ) указывает способ сравнения стека вызовов функций. При значениях low и med , в сравнении используются два или четыре последних вызова, соответственно, а при high , сравнивается полный стек вызова. Эта опция влияет лишь на способ представления результатов поиска ошибок. --undef-value-errors ( yes или no ) определяет, будут ли показывать ошибки об использовании не инициализированных значений.

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

Интерпретация полученных результатов

Существует несколько видов ошибок, обнаруживаемых модулем memcheck. Ошибки чтения и записи за пределами выделенной памяти (и еще несколько видов ошибок) выдаются сразу, в процессе работы программы. А ошибки, ведущие к утечкам памяти, выдаются valgrind'ом после завершения работы анализируемой программы. Формат выдачи этих ошибок немного отличается, поэтому они будут описаны по отдельности.

Каждая строка в выводе valgrind имеет префикс вида

где число обозначает идентификатор запущенного процесса.

Ошибки работы с памятью

В ходе своей работы, модуль memcheck определяет несколько видов ошибок работы с памятью:

  • чтение или запись по неправильным адресам памяти — за границами выделенных блоков памяти и т.п.
  • использование не инициализированных значений, в том числе и для переменных выделяемых на стеке
  • ошибки освобождения памяти, например, когда блок памяти уже был освобожден в другом месте
  • использование "неправильной" функции освобождения памяти, например использование delete для памяти, выделенной с помощью new []
  • передача некорректных параметров системным вызовам, например указание неправильных указателей для операций чтения из буфера, указанного пользователем
  • пересечение границ блоков памяти при использовании операций копирования/перемещения данных между двумя блоками памяти

Для этих ошибок данные выдаются по мере их обнаружения, и обычно они выглядят следующим образом:

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

Нахождение утечек памяти

При окончании работы программы valgrind выдает сводную таблицу, описывающую количество найденных ошибок, а также выделение памяти в программе, например:

И в самом конце отчета, выдается сводная таблица по каждому из типов ошибок работы с памятью:

Definitely lost означает, что valgrind нашел область памяти, на которую нет указателей, т.е. программист не освободил память, при выходе указателя за область видимости. Possibly lost показывает, что найден указатель, указывающий на часть области памяти, но valgrind не уверен в том, что указатель на начало области памяти до сих пор существует (это может происходить в тех случаях, когда программист вручную управляет указателями). Still reachable обычно означает, что valgrind нашел указатель на начало не освобожденного блока памяти, что во многих случаях связано с выделением глобальных переменных и т.п. вещей. Обычно эта информация показывается только при указании опции --show-reachable со значением yes .

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

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

Полезные советы при работе с memcheck

Отсутствие информации об исходном коде При анализе программ, которые используют подгружаемые модули может возникнуть ситуация, когда valgrind не может выдать информацию о коде, приведшем к выделению "потерянных" блоков памяти. Это происходит из-за того, что в момент, когда valgrind собирает информацию о выделении и использовании памяти, модуль может быть уже выгружен, и valgrind не может найти отладочную информацию. Это приводит к тому, что при выводе отчета такие места будут отмечены знаками . , что означает отсутствие отладочной информации. Чтобы избежать этого, необходимо не использовать вызовы dlclose для выгрузки модуля до завершения программы (но это, соответственно, потребует изменение исходного кода программы).

Профилирование программ

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

cachegrind

Модуль cachegrind проводит сбор статистики по попаданию в кэш первого и второго уровней процессора при выполнении операций чтения и записи данных и инструкций программ, а также статистику по работе модуля предсказания ветвлений в программах. По умолчанию, сбор статистики о предсказании ветвления инструкций (branch prediction) не проводится, и если вы хотите получить эти данные, то вы должны указать опцию --branch-sim со значением yes . Кроме этого, пользователь имеет возможность указания дополнительных опций, например, задающих размеры кэшей и т.п.

Результаты собранные данным модулем по умолчанию выводятся в файл с именем cachegrind.out.<pid> ( pid — идентификатор процесса). Если вы хотите использовать другое имя файла, то можете воспользоваться опцией --cachegrind-out-file .

После завершения программы, valgrind выдаст таблицу с суммарными данными, собранными во время выполнения программы, например:

в которой перечислены данные по выборке инструкций и данных процессором. А в файл cachegrind.out (достаточно большой даже для очень простых программ), попадут детальные данные, которые можно использовать для поиска "узких" мест в программах. Удобным средством анализа является программа kcachegrind, но и в поставке valgrind есть программа cg_annotate , которая позволяет проводить анализ производительности программ 4 .

Для получения данных, в качестве параметров программы cg_annotate указывают имя файла с результатами, собранными cachegrind, а также (опционально) список файлов с исходными текстами, которые будут аннотированы по результатам работы cg_annotate . Чтобы не указывать все файлы с исходными текстами вручную, cg_annotate принимает опцию --auto со значением yes , и автоматически ищет нужные файлы (с помощью опции -I можно указать каталоги, в которых должен производиться поиск файлов).

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

callgrind

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

Данные собранные модулем выводятся в файл callgrind.out.<pid> , который затем может быть проанализирован с помощью программ kcachegrind или callgrind_annotate (входящей в поставку valgrind).

callgrind_annotate выводит на экран данные о выполнении различных функций, и может представлять их в различном виде, в зависимости от указанных опций. Также как и для cg_annotate , можно указать опцию --auto , чтобы избежать указания файлов с исходными текстами вручную.

По умолчанию, callgrind выводит информацию один раз, в конце выполнения программы. Но пользователи, которым это нужно, могут использовать программу callgrind_control из поставки valgrind для получения промежуточных данных по запросу, или периодически.

Анализ выделения памяти в программе

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

После завершения программы под управлением massif, valgrind выдает краткую сводку использования памяти, а подробные данные выводятся в файл massif.out.<pid> . Для анализа этих данных может использоваться программа ms_print , входящая в поставку valgrind. Эта программа может выдавать данные в виде графиков, демонстрирующих выделение памяти в программе в процессе работы, например вот так:

Пользователь может использовать дополнительные опции massif для управления частотой снятия снапшотов, их количеством, списком функций, для которых будет производиться анализ (можно, например, отслеживать только new или malloc ) и т.п.

Поиск ошибок синхронизации

За поиск этого класса ошибок отвечает модуль helgrind. Он позволяет найти ошибки синхронизации в программах на языках C, C++ & Fortran, использующих POSIX Thread API. Helgrind помогает обнаружить следующие классы ошибок:

  • потенциальные блокировки (deadlocks), возникающие из-за неправильного порядка выставления блокировок
  • повреждение данных (data races) из-за неправильных, или отсутствующих блокировок на доступ к памяти
  • неправильное использование функций POSIX API. Этот класс ошибок включает в себя разные ошибки, например рекурсивное блокирование не рекурсивного мутекса, освобождение памяти, хранящей блокировку (мутекс) и т.д.

Helgrind позволяет найти эти ошибки за счет отслеживания состояния каждого из байтов памяти, используемой программой, а также за счет мониторинга различных событий, связанных с выполнением нитей (threads) — их создания и завершения, получение и освобождения блокировок и т.п.

Формат вывода информации немного похож на формат вывода ошибок модулем memcheck:

В данном примере helgrind указывает на возможное повреждение данных при одновременном выводе данных на экран несколькими нитями исполнения. Кроме стека вызова функций, приводящего к ошибке, также выдается состояние памяти до и после возникновения ошибки (old и new state), а также причина возникновения ошибки (в нашем случае — отсутствие блокировок для данного участка памяти).

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

Дополнительные программы для работы с valgrind

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

  • Программа alleyoop позволяет работать с valgrind, используя графический интерфейс пользователя. Она позволяет выбрать нужную утилиту, и запустить valgrind с нужными опциями, а затем проанализировать вывод valgrind, и обеспечить навигацию по коду, который вызывает ошибки. В настоящее время поддерживается работа с модулями memcheck, helgrind и cachegrind.
  • Для визуализации данных, полученных от модулей callgrind и cachegrind, существует программа — kcachegrind, которая отображает полученные данные, и позволяет выполнять навигацию по исходному коду программы. Программа позволяет отображать собранные данные различными способами — в виде таблиц, карт и графов вызова функций. Пример отображения этих данных вы можете видеть на рисунке.


1. Хорошее описание архитектуры Valgrind и принципов его работы можно найти в статье Valgrind: A Framework for Heavyweight Dynamic Binary Instrumentation

2. Интересующиеся, могут найти описание процесса сборки и установки в руководстве пользователя valgrind.

3. Для задания опций используется стандартная форма. Некоторые опции могут иметь одно-буквенные сокращения, и если они имеют аргументы, то аргументы задаются через пробел после указания соответствующей опции. Для опций, с полным названием, таких как <code>--log-file , аргументы указываются сразу после опции, используя знак = в качестве разделителя имени и значения.

4. В руководстве пользователя valgrind есть описание формата данных, выводимых модулем cachegrind, так что пользователь может писать и свои анализаторы.

Работа с 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

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

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

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

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

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

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

  • В столбце слева мы видим число ==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 .

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Читайте также: