СТАТЬЯ
15.02.02

Часть 1

Средства отладки C/C++: использование Rational Purify при работе c GDB (Часть 2)

© Горан Бегик (Goran Begic), Rational Software
Переведено БНТП по заказу Interface Ltd.

Отладка приложения, подготовленного с помощью RationalPurify

Работа с подготовленным приложением в GDB ни чем не от работы с исходной, неподготовленной версией тестируемой программы. Исполнение приложения начинается с помощью команды "r".

Если вы не выбирали каких-либо специфических условий выполнения, приложение будет работать без прерываний и Purify сообщит о трех ошибках, только что описанных выше (см. Рисунок 2).

Рис. 2. Подготовленная программа, работающая в среде RationalPurify

Во время исполнения приложения, в отладчике, или самостоятельно, Purify собирает информацию и отображает ее, в режиме времени, близком к реальному. На Рисунке 3 показан первый отчет о чтении из неинициализированной области памяти (UMR).

Рис. 3. Отчет Purify об ошибке UMR.

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

Если вы продолжите выполнение, следующий отчет будет сходным: будут показаны местоположение ошибки и место распределения памяти, не оставляющие сомнений об ошибке работы с памятью во время исполнения. Это действительно важно, так как иначе приложение будет работать «нормально» большую часть времени, мы можем не обнаружить ошибку записи в память за рамками границ (ABW - Out-of-BoundsWrite), как не сможем и предсказать, когда это реально приведет к затиранию корректных данных и вызовет сбой.

Рисунок 4 показывает отчет об ошибке ABW. В цикле for() приложение копирует элементы массива из десяти символов в память, распределенную для массива из пяти символов. Этот тип ошибок очень опасен и не должен оставаться в приложении, особенно в версии, отправляемой заказчику.

Рис. 4. Отчет об ошибке ABW в Purify

Детальная проверка и тестирование приложения с помощью RationalPurify и отладчика GDB

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

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

(gdb) break 10

Эта инструкция предписывает приложению остановить выполнение на десятой строке исходного кода.

Для начала исполнения приложение должно быть запущено в отладчике GDB. Для этого служит команда 'r' (run).

(gdb) r

Подготовленное приложение стартует, запустит Purify, и остановится в контрольной точке.

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

(gdb) print string1

Вы можете продолжить исполнение подготовленного приложения с помощью команды 'c' (от continue - продолжить).

(gdb) c

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

(gdb) break main

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

(gdb) break <line number, or function name> <additional condition>

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

(gdb) delete

За подробными инструкциями по работе с контрольными точками обратитесь к руководству по отладчику GDB, указанному в Библиографии.

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

sleep(30);

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

Я добавил эту строку в наш небольшой пример "Hello, RationalEdge", перекомпилировал и повторно подготовил приложение. Так как нам нужен PID (Processidentifier – идентификатор процесса) для подготовленного приложения, мы запустим программу с помощью следующей команды:

./a.out &

В результате мы получим PID. В данном случае:

[1] 15050

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

gdb ./a.out 15050

Преимущество отладки при совместном использовании GDB и RationalPurify

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

Использование функций APIRationalPurify
APIPurify включает функции, которые могут вам помочь при отладке и диагностике ошибок работы с памятью.

Вызов функций APIPurify из отладчика GDB. Некоторые функции API предназначены для вызова из отладчика. Например:

purify_stop_here()

В отладчике GDB вы можете вызвать эту функцию с помощью следующей команды GDB:

(gdb) break purify_stop_here

После этого API установит контрольную точку на вызов любого отчета Purify об ошибке. В нашем примере первая остановка программы произойдет в момент выдачи сообщения Purify об ошибке UMR.

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

(gdb) bt
#0 0x535d4 in purify_stop_here ()
#1 0x413a4 in strlen ()

#2 0x5710c in main () at helloEdge.c:10

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

purify_describe(addr)

Эта функция сообщит, как Purify рассматривает данный фрагмент памяти: как «глобальные данные» (globaldata), как «содержимое стека» (onthestack) или как «X байт, начиная с распределенного блока по адресу Y». В отладчике GDB вызов этой функции может быть объединен с командой print. Например:

(gdb) print purify_describe(addr)

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

purify -printhomedir

В этом каталоге находится и библиотека заглушек APIPurify (purify_stubs.a).

#include<purify.h>

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

Вот несколько примеров функций API, которые могут быть вызваны из вашего приложения:

purify_is_running -

Возвращает TRUE (истина) если программа приготовлена для работы с Purify.

purify_printf (_with_call_chain) -

Записывает сообщение в журнал (с информацией о стеке вызовов).

purify_new_leaks / purify_new_inuse -

Сообщает объем утерянной/занятой памяти со времени последнего вызова.

 
Использование сторожевых точек (Watchpoint) в RationalPurify
Установив специальные сторожевые точки (watchpoint) в Purify вы можете отслеживать конкретные типы операций доступа к памяти. Использование сторожевых точек может оказаться очень полезным в ситуации, когда память мистическим образом изменяется между моментом инициализации и моментом использования. Когда сторожевые точки установлены, Purify автоматически сообщает о точной причине и результатах каждой операции доступа к памяти.

Существуют четыре причины, по которым сторожевые точки Purify предпочтительнее сторожевых точек отладчика. Сторожевые точки Purify:

Наш пример "Hello, Rational Edge" так мал и тривиален, что нам в действительности не нужны сторожевые точки для понимания того, что случается в ходе исполнения программы. Но давайте посмотрим на следующий фрагмент кода:

Object * myObject = NULL ; // global pointer
int main()
{
   myObject = create();
   result = compute(myObject);
   report(result);
   destroy(myObject);
   return 0;
}

Для разработчика, отвечающего за этот фрагмент, следующий сценарий может быть реальной проблемой. Предположим, мы запускаем нашу программу в Rational Purify и Purify сообщает об «утечке» памяти для объекта – несмотря на то, что мы уничтожили объект myObject в функции destroy().

Нам необходимо использовать отладчик для анализа конкретных событий, приведших к утечке памяти. С помощью отладчика мы можем определить, например, что объект myObject уже был уничтожен (указатель - NULL) в момент вызова destroy()! Так что же случилось с глобальным указателем на "myObject" в этой программе?

#include "purify.h" // header file for Purify API
Object * myObject = NULL ; // global pointer
int main()
{
   if (purify_is_running() ) {
      purify_watch_n(&myObject, 4, "w") ;
   }
   myObject = create_object();
   result = compute(myObject);
   report(result);
   destroy(myObject);
   return 0;
}

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

Если у нас ошибка в функции compute(), например, за счет преждевременного обнуления указателя на объект, без предварительного уничтожения объекта, то Purify будет корректно сообщать об утечке памяти – даже если мы создали функцию уничтожения объекта. Эта ошибка будет обнаружена практически мгновенно при неожиданной остановке на сторожевой точке в функции compute(). Строка кода, вызвавшая ошибку, сама по себе может не вызывать никаких подозрений. Разработчик может обнулить ссылку на объект до его уничтожения в строке MyObject=NULL;.

Сторожевая точка Purfy приведет нас прямо к источнику проблемы, как показано на Рисунке 5.

Рис. 5. Отчет Rational Purify о неожиданном срабатывании второй сторожевой точки Purify.

Подавление сбора и отображения информации об ошибках в Purify
Rational Purify позволяет вам подавить сбор и отображение определенных сообщений в окне просмотра. Это может быть полезно если:

Есть несколько способов выполнить это подавление:

Положим, у вас есть программа, иногда использующая счетчик (counter):

if (use_the_counter)
   object.counter = counter_initialize();
object.counter++;
if (use_the_counter)
   return object.counter;
else
   return 0;
 
use_the_counter – логическая переменная, имеющая только два состояния: "true" или "false."

В этом кусочке кода мы инициализируем счетчик только при условии "true"; если условие ложно (false) тогда мы читаем из него. Это создаст ошибку UMR, в случае если use_the_counter равно "false." Но в действительности это не ошибка и это является хорошим кандидатом для подавления.

Некоторые ошибки подавлены по умолчанию. Например, Purify отличает общие операции чтения неинициализированной памяти от чтения неинициализированной памяти только для создания копии. Такие сообщения о копировании неинициализированной памяти (UMC - Uninitialized Memory Copy) подавляются по умолчанию. Если позже вы будете использовать эту копию, Purify сгенерирует сообщение UMR.

Вот пример сообщения UMR:

/* Suppose arg_ptr points to uninitialized memory */
void SomeFun(int *arg_ptr) {
   int local = *arg_ptr; /* UMC (suppressed) */
   printf("value is %d\n", local); /* UMR here */
}

Если вы хотите видеть эти сообщения в Purify, тогда вам нужно включить режим "View/Suppressed Messages."

 

Комбинация RationalPurify с отладчиком экономит время и деньги

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

Примечание: Другие популярные отладчики, такие как dbx или отладчик, поставляемый со средами разработки SunForte и SunWorkshop, тоже хорошо работают вместе с RationalPurify. Для HewlettPackard (HP) наиболее популярный отладчик называется WDB и является открытой реализацией HP отладчика GDB. Дополнительную информацию об отладчиках вы можете найти на сайтах, перечисленных ниже в разделе Библиография. Полнофункциональная версия RationalPurifyPlus может быть получена для целей ознакомления на Web-странице PurifyPlus.
Библиография
Руководство GNUGD
Документация по PurityPlus
Отладчик HPwdb
Отладка с помощью dbx

Дополнительная информация

Дополнительную информацию Вы можете получить в компании Interface Ltd.

Обсудить на форуме Rational Software
Отправить ссылку на страницу по e-mail


Interface Ltd.
Тel/Fax: +7(095) 105-0049 (многоканальный)
Отправить E-Mail
http://www.interface.ru
Ваши замечания и предложения отправляйте автору
По техническим вопросам обращайтесь к вебмастеру
Документ опубликован: 15.02.02