8.5.3 Устранение повреждений памяти

Предыдущая  Содержание  Следующая V*D*V

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

 

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

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

Разыменование указателей памяти после того, как эта память была освобождена.

 

Такие ошибки очень трудно поймать. Поиск ошибку вручную может оказаться сложнейшей задача, так как это будет означать сканирование всего кода. Изучим программный инструмент для поиска повреждений памяти, Valgrind. Valgrind может быть загружен с веб-сайта http://valgrind.kde.org.

Ниже приводятся некоторые важные особенности Valgrind.

 

Valgrind работает только под Linux на платформе x86. Это может выглядеть как основной сдерживающий фактор от его использования, учитывая, что многие встраиваемые платформы базируются не на архитектуре x86. Однако, многие проекты для встраиваемых систем перед переносом их на целевую платформу первоначально запускают свои приложения на платформе на базе x86; так происходит потому, что целевая платформа может быть недоступна, когда приложения находятся в разработке. В таких случаях разработчики могут тестировать свои приложения на наличие повреждений памяти до их перевода в целевую платформу полностью на платформе x86.

Valgrind очень сильно привязан к ОС и её библиотекам; на момент написания, дистрибутив Valgrind версии 2.2.0 предполагается запускать на ядре версии 2.4 или 2.6 и glibc версии 2.2.x или 2.3.x.

В случае с Valgrind приложение может работать без какой-либо пересборки. Например, если бы вы захотели запустить Valgrind с исполняемым файлом ps, вы бы запустили его так, как показано в Распечатке 8.8.

Valgrind работает имитируя для выполнения вашей программы CPU x86. Побочным эффектом этого является то, что программа работает медленнее, так как для учёта использования системных ресурсов Valgrind отлавливает соответствующие вызовы (системные вызовы, вызовы работы с памятью). Кроме того, при использовании с Valgrind приложение имеет тенденцию потреблять больше памяти. Valgrind это отличный инструмент, который может сделать гораздо больше, чем просто бороться с повреждением памяти, например, профилирование кэша и обнаружение состояния гонок в многопоточных программах. Тем не менее, настоящий раздел изучает использование Valgrind для борьбы с повреждением памяти. Ниже приводится список проверок памяти, которые можно выполнить с помощью Valgrind:
– Использование не проинициализированной памяти
– Утечки памяти
– Переполнения памяти
– Повреждение стек
– Использование указателей памяти после того, как соответствующая память была освобождена
– Несоответствующие указатели в malloc/free

 

Архитектура Valgrind может быть разложена на два уровня: ядро и внешняя облочка. Ядро представляет собой эмулятор x86, который переводит весь исполняемый код в свой собственный код операций. Затем переведённый код оценивается и выполняется на реальном CPU. Оценка зависит от выбранного вида внешней оболочки. Архитектура Valgrind является модульной, позволяя легко подключить к ядру новую внешнюю оболочку.

Мы сосредоточим наше внимание на оболочку проверки памяти, memcheck. Это используемая по умолчанию оболочка Valgrind (любая другая оболочка должна вызываться специально с помощью аргумента командной строки --skin). Memcheck работает, связывая каждый байт оперативной памяти с двумя значениями: битом V (допустимое значение) и битом A (достоверный адрес). Бит V определяет, определено ли в программе значение этому байту. Например, проинициализированный байт памяти имеет установленный бит V; соответственно, не проинициализированные переменные можно отслеживать с помощью бита A. Бит A отслеживает, может ли ячейка памяти быть доступной. Аналогично, когда выполняется вызов выделения памяти с помощью malloc(), все байты выделенной памяти имеют установленный бит V. Другая оболочка, addrcheck, предоставляет все возможности memcheck, кроме проверки на неопределённое значение. Она делает это используя бит V.  Оболочка addrcheck может использоваться в качестве легковесного контролера памяти; она быстрее и легче, чем memcheck.

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

 

#include <stdlib.h>

main()

{

  int *p;

  int c = *p;

  if(c == 0)

    ...

  return;

}

 

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

 

==4409== Use of uninitialized value of size 4

==4409==   at 0x804833B: main (in /tmp/x)

==4409==   by 0x40258A46: __libc_start_main

           (in /lib/libc-2.3.2.so)

==4409== by 0x8048298: ??? (start.S:81)

==4409==

==4409== ERROR SUMMARY: 1 errors from 1 contexts

         (suppressed: 0 from 0)

==4409== malloc/free: in use at exit: 0 bytes in 0 blocks.

==4409== malloc/free: 0 allocs, 0 frees, 0 bytes allocated.

 

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

 

#include <stdlib.h>

main()

{

  int *i = (int *)malloc(sizeof(int));

  *i = 10;

  free(i);

  printf("%d\n",*i);

}

 

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

 

==4437== 1 errors in context 1 of 1:

==4437== Invalid read of size 4

==4437==    at 0x80483CD: main (x.c:6)

==4437==    by 0x40258A46: __libc_start_main

                          (in /lib/libc-2.3.2.so)

==4437==   by 0x8048300: ??? (start.S:81)

==4437==   Address 0x411C7024 is 0 bytes inside a block of

           size 4 free'd

 

Предыдущая  Содержание  Следующая