Расширенные возможности IBM Rational Purify: настройка отчетов и инструментов Purify

Сатиш Чандра Гупта & Ананд Гаурав

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

IBM Rational Purify - это улучшенное средство обнаружения ошибок памяти, которое помогает быстро и точно локализовать проблемы, связанные с нарушением целостности информации. После оснащения приложения средствами Purify и его запуска Purify тщательно исследует каждое обращение к памяти и уведомляет о любой ошибке целостности данных еще до ее возникновения. Вы можете узнать о различных типах ошибок памяти и том, как использовать Purify для их обнаружения, из предыдущей статьи: Navigating "C" in a "leaky" boat? Try Purify ("Плывете по языку С в дырявой лодке? Попробуйте Purify.").

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

  • Управление кэшем: управление и совместное использование двоичных файлов.
  • Фрагменты Purify: оснащение средствами Purify лишь части приложения для сокращения времени выполнения и диапазона обнаруживаемых ошибок.
  • Управление динамически распределяемой памятью: ограничение объема используемой памяти при работе приложения, оснащенного средствами Purify, и настройка отчета об использовании памяти.

Опции Purify

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

  • опции периода сборки;
  • опции периода исполнения.

Опции периода сборки необходимо использовать во время оснащения средствами Purify. Например, если вы не хотите производить в своей программе проверку переполнения буфера, то при оснащении средствами Purify можно использовать опцию -static-checking.

$ purify -static-checking=no cc your_prog.c

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

$ purify -chain-length=10 cc your_prog.c

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

  • Переменная окружения PUREOPTIONS применима к Purify, а также к другим продуктам PurifyPlus, а именно к Quantify и PureCoverage.
  • Переменная окружения PURIFYOPTIONS работает только для Purify.

Ранее указанные опции можно установить в оболочках sh, ksh или bash следующим образом:

$ export PURIFYOPTIONS="-static-checking=no -chain-length=10"

В оболочках csh или tcsh можно использовать следующую опцию:

% setenv PURIFYOPTIONS "-static-checking=no -chain-length=10"

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

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

Purify предлагает несколько опций периода сборки, чтобы получить доступ к справке и информации о версии:

$ purify -version

$ purify -usage

$ purify -help

$ purify -onlinehelp

Другой полезной опцией сборки является -print-home-dir . Она печатает имя каталога, где установлен пакет Purify. Ее можно использовать, например, чтобы запустить сценарий purify_what_options . Он позволяет узнать, какие опции были использованы во время оснащения:

$ `purify -print-home-dir`/purify_what_options <your_prog.pure>

Другим интересным вариантом использования этой опции является компилирование программы, которая включает файл purify.h для доступа к Purify API. Файл заголовков появляется в домашнем каталоге Purify, и эта команда будет содержать этот каталог в пути поиска подключаемых файлов, который указывается для компилятора:

$ cc -c -I`purify -print-home-dir` your_prog.c

Директивы Purify

Кроме опций командной строки, Purify позволяет указать различные директивы для тонкой настройки оснащения и отчетов об ошибках. Директивы помогают настроить использование Purify для конкретного проекта. Например, рассмотрим ситуацию, когда возникает ошибка памяти (скажем, неинициализированное чтение памяти, или UMR) в одной из функций (foo, например) в библиотеке стороннего разработчика (например, libfoo), которая используется в проекте. Эту ошибку вы исправить не можете. Вероятно, все, что вам остается, - это отправить отчет об ошибке производителю библиотеки и ждать исправления. Если вы не хотите видеть эту ошибку в отчете Purify, поскольку исправить ее невозможно, можно указать директиву suppress:

suppress umr foo

Это указывает Purify не показывать ошибки UMR в функции foo. Чтобы увидеть ошибки, скрытые директивами suppress, нужно в графической оболочке Purify из меню View выбрать пункт Suppressed. Purify позволяет точно указывать, что вы хотите видеть. Вот несколько примеров.

  • Чтобы подавить только UMR в определенной цепочке вызовов, можно указать всю эту цепочку:

suppress umr printf; foo; bar; main

  • Чтобы подавить ошибки UMR, для которых цепочка вызовов должна соответствовать определенным критериям, где троеточие (...) означает любое количество вхождений функций без каких-либо ограничений на имя функции:

suppress umr ...; foo; ...; main

  • Чтобы подавить все ошибки UMR в библиотеке (стороннего разработчика), где звездочка (*) означает любое количество вхождений любого символа:

suppress umr "libfoo*"

  • Чтобы подавить все ошибки UMR, происходящие где бы то ни было (что, очевидно, очень рискованно):

suppress umr *

Purify обрабатывает директивы, указанные в файлах с названием .purify в следующих каталогах и в следующем порядке:

1.     <purify-installation-home>/.purify

2.     <users-home-dir>/.purify

3.     <current-working-directory>/.purify

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

Либо можно выбрать в графической оболочке Purify одну из тех ошибок, которые нужно подавлять, щелкнуть на ней правой кнопкой мыши. В появившемся контекстном меню выберите пункт Suppress. Появится диалоговое окно, показанное на рис. 1. В нем можно указать тип ошибки, цепочку вызова и т.д. Можно сделать подавление постоянным, указав местоположение соответствующего файла .purify и нажав кнопку Make permanent.

Рисунок 1. Диалоговое окно Purify Suppression.

 

Другая директива, kill, работает в точности аналогично директиве suppress. Отличие состоит в том, что в первом случае сообщения недоступны в графическом интерфейсе, даже если использовать меню View > Suppressed Messages. Если ошибка происходит многократно (тысячи или десятки тысяч раз), то программа будет работать быстрее, если вместо подавления примените директиву kill.

Управление кэшем

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

Есть несколько опций сборки, имеющих отношение к кэшу. Например, если вы не хотите, чтобы оснащенные библиотеки были разбросаны по разным каталогам, где располагались их исходные версии, а, наоборот, вы хотите хранить их в каталоге кэширования, то можно использовать опцию -always-use-cache-dir . Также можно указать выбранный каталог для кэширования вместо того, чтобы использовать каталог, заданный по умолчанию. Для этого используется следующая опция: -cache-dir=<dir-name> . С помощью этих опций можно удалить все оснащенные библиотеки из кэша, просто удалив указанный каталог для кэширования.

При оснащении программы Purify оснащает библиотеку только в том случае, если она была изменена с момента последнего оснащения, либо если невозможно найти оснащенную версию библиотеки. Это помогает сэкономить время. Если вы не хотите использовать уже оснащенные версии библиотек, то можно использовать опцию сборки -force-rebuild , и Purify заново обработает все нужные библиотеки. Это полезно, если вы решили использовать расширенные опции оснащения (например, -static-checking-guardzone ), и хотите повторно принудительно провести обработку библиотек.

Как правило, нельзя оснастить программу на одном компьютере, а затем запускать ее на другом. Причина очень проста. Программа зависит обычно от системных библиотек (например, libc). Эти библиотеки могут на разных компьютерах отличаться, в частности, из-за версии пакетов исправлений в различных операционных системах. Чтобы гарантировать, что каждая программа получает доступ к оснащенным библиотекам, соответствующим исходным библиотекам в той же самой системе, механизм кэширования в Purify организует оснащенные библиотеки на основе имени хоста. Кроме того, ваша программа может также зависеть от некоторых библиотек, написанных вами или сторонними производителями, которые одинаковы на всех компьютерах или даже совместно используются из одного сетевого ресурса. В подобной ситуации вы можете не захотеть, чтобы в кэше находилось несколько оснащенных версий этих библиотек для каждого имени хоста, поскольку эти копии будут идентичны, и их хранение - это просто трата дискового пространства. Этого можно избежать, используя механизм repure в Purify на платформах IBM® AIX®, Linux® и Solaris® UNIX®. При этом нужно во время оснащения позаботиться о нескольких вещах.

1.     Во-первых, выберите путь, который существует на всех машинах. Причем реальное место, на которое указывает этот путь, должно быть для каждой машины локальным. Например, каталог /tmp существует на всех компьютерах с системой UNIX®, но он всегда локальный и совместно двумя машинами никогда не используется. Совет. Не используйте пути (например, свой домашний каталог или другие подобные места), которые видны другим компьютерам через сетевую информационную службу (Network Information Service, NIS) или сетевую файловую систему (Network File System, NFS).

2.     Опция -local-cache-dir укажет Purify хранить оснащенные библиотеки, специфичные для системы, в месте, на которое указывает, например, следующий путь:

$ purify -always-use-cache-dir -local-cache-dir=/tmp -cache-dir=./cache \

      cc -g test.c -o test.pure

3.     Вы можете запустить оснащенную программу на том же компьютере:

$ ./test.pure

4.     Если вы хотите запустить оснащенную программу на другом компьютере, просто "переочистите" ее на другой машине с помощью команды repure:

$ repure ./test.pure

5.     После использования команды repure можно запустить программу также и на другой машине:

$ ./test.pure

6.     Повторите этап с командой repure (шаг 4) для каждого компьютера, на котором вы хотите запускать оснащенную программу.

Старые оснащенные файлы можно вычистить с помощью сценария pure_remove_old_files , расположенного в домашнем каталоге установленного пакета Purify:

`purify -print-home-dir`/pure_remove_old_files <path> <days>

Эта команда лишь удаляет оснащенные копии библиотек: файлы, чьи имена содержат _pure_ , и которые заканчиваются специфичными для совместно используемых библиотек расширениями, например, .so или .sl . Например, можно вычистить все оснащенные файлы, которые старше 14 дней и хранятся в любом месте файловой системы. Это делается следующей командой:

$ pure_remove_old_files / 14

Фрагменты Purify

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

Примечание:
Выборочное оснащение работает только на платформах HP-UX и AIX. На платформе Solaris SPARC оно поддерживается с ограничениями, а на платформах Solaris x86 и Linux не поддерживается совсем.

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

Вот как можно исключить библиотеку с помощью опции selective:

$ purify -selective -exclude-libs=libfoo1.so:libfoo2.so cc -g app.c \

      -o a.out.pure -lfoo1 -lfoo2 -lbar

Можно использовать опцию -exclude-libs и предоставить список библиотек (разделенный двоеточиями), которые нужно исключить. В качестве альтернативы можно использовать директиву exclude в файле директив .purify :

exclude libfoo*

Как уже объяснялось, в директивах звездочка (*) интерпретируется как любое количество вхождений любого символа

Опция -selective заставляет Purify использовать более надежные алгоритмы для обнаружения и исключения ложных ошибок. Они могут возникать из-за исключения некоторых библиотек. Например:

  • Предположим, что main() вызывает foo(), а foo() вызывает bar(), и вы исключаете foo() из оснащения. Предположим также, что main() распределяет память, foo() инициализирует эту память, а bar() ее использует. Если код foo() не был оснащен вставками, то Purify не узнает, что память инициализировалась в foo(). И если память используется в bar(), это может привести к появлению в отчете сообщения о неинициализированном чтении памяти (uninitialized memory read, UMR). Опция -selective указывает Purify тщательнее проверять ошибки, чтобы избежать подобных ложных сообщений.
  • Исключение библиотеки может также привести к тому, что Purify пропустит некоторые ошибки, даже в оснащенном коде. Предположим, что main() вызывает foo() и передает этой функции инициализированный буфер. При каждом использовании буфера в функции foo() Purify будет сообщать об ошибке UMR. Предположим, что foo() находится в совместно используемой библиотеке libfoo.so, и вы ее исключили. Поскольку функция foo() не оснащена средствами проверки, то в этом случае в ней не будет никакого кода проверки доступа к памяти, который вставляет Purify. Поэтому, хотя ошибка и находится в оснащенном коде, никаких сообщений об ошибке не появится. Ошибка же может произойти из-за того, что оснащенная функция  main() ошибочно передала неинициализированный буфер функции foo().

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

Purify может определить ошибки переполнения буфера даже в коде, не оснащенном средствами проверки. Но это произойдет не тогда, когда ошибка, собственно, происходит, а позже, при освобождении буфера. Purify выдает сообщения об ошибках при их возникновении. Но поскольку у Purify нет возможности вставить команды проверки в неоснащенный код, в отчет не попадают никакие ошибки, связанные с этим кодом. Если использовать опцию -late-detect-logic , то Purify будет выполнять дополнительные проверки при освобождении блока динамически распределяемой памяти. При обнаружении переполнения буфера Purify выдаст сообщение об ошибке, связанной с попыткой записи вне выделенного блока памяти (Array Bound Write Late, ABWL).

Ошибки управления динамически распределяемой памятью

Purify предлагает несколько опций для контроля использования памяти и времени выполнения оснащенной программы. Когда программа освобождает блок памяти, Purify не освобождает эту память немедленно. Вместо этого освобожденный блок ставится в очередь типа "первый вошел - первый вышел" (First In-First Out, FIFO). Это позволяет Purify обнаруживать "повисшие" (т.е. указывающие на несуществующие объекты) указатели. Они возникают, когда программа пытается получить доступ к памяти уже после ее освобождения. Это так называемые ошибки чтения-записи освобожденной памяти (Free Memory Read/Write, FMR/FMW). Если бы мы позволили освободить блок сразу обратно в "кучу", то память бы можно было сразу после этого повторно использовать. Тогда у Purify не было бы возможности различить неправильный доступ к старому блоку и правильный доступ к новому блоку.

По умолчанию длина очереди составляет 100. Когда очередь заполняется, Purify освобождает первый блок в очереди и добавляет в нее вновь освобожденный блок. Длину очереди можно поменять с помощью опции -free-queue-length=<value> . Более длинная очередь увеличивает время, в течение которого будут надежно обнаружены "повисшие" указатели на освобожденную память. Однако увеличение длины очереди также заставляет программу использовать больше памяти (реальной или виртуальной), поскольку действия со свободной памятью откладываются. Таким образом, увеличение длины очереди повышает вероятность обнаружения "повисших" указателей, но это достигается ценой большего расхода памяти.

Свободная очередь используется только для маленьких блоков. Под маленькими понимаются блоки, не превышающие определенного размера. Большие блоки освобождаются сразу обратно в "кучу", чтобы избежать риска перерасхода памяти. Можно с помощью опции -free-queue-threshold=<value> указать граничное значение для помещения блока памяти в очередь. По умолчанию это значение составляет 10000 байт. Любой блок памяти, превышающий это значение, освобождается немедленно.

В конце программы Purify сообщает обо всех утечках памяти. Если вы также хотите видеть все используемые блоки памяти, это можно указать с помощью опции -inuse-at-exit=yes . Аналогично, чтобы найти в конце программы используемые дескрипторы файлов, можно использовать опцию -fds-inuse-at-exit=yes .

В системе AIX, если вам интересны лишь утечки памяти, можно использовать опцию -memory-leaks-only. Тогда Purify произведет лишь очень незначительное оснащение, предназначенное только для обнаружения утечек памяти и других ошибок управления динамической памятью, например, ошибок Freeing Memory Mismatch (FMM). В этом случае программа будет работать гораздо быстрее, поскольку Purify не будет делать своей обычной проверки при каждом доступе к памяти.

Резюме

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


Страница сайта http://www.interface.ru
Оригинал находится по адресу http://www.interface.ru/home.asp?artId=9754