Чтение онлайн

ЖАНРЫ

Разработка приложений в среде Linux. Второе издание

Троан Эрик В.

Шрифт:

36: strcpy(global, "12345");

37: printf ("6: %s\n", global);

38:

39: /* И записать поверх пространства перед буфером global */

40: global[-1] = '\0';

41: printf("7: %s\n", global);

42:

43: return 0;

44: }

45:

46: int main (void) {

47: return broken;

48: }

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

пула памяти (кучи) с помощью
malloc
, локальные переменные размещенные в стеке программы и глобальные переменные, хранящиеся в отдельной области памяти, которая была статически распределена при запуске программы [9] . Для каждого класса памяти эта тестовая программа выполняет запись за пределами зарезервированной области памяти (по одному байту) и также сохраняет байт непосредственно перед зарезервированной областью. К тому же в коде имеется утечка памяти, что позволит продемонстрировать, как с помощью различных средств отследить эти утечки.

9

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

gcc
, однако он больше не поддерживается. К официальному компилятору
gcc
добавлена новая технология под названием mudflap, которая описана в текущем руководстве по
gcc
.

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

Ниже показан пример выполнения программы.

$ gcc -Wall -о broken broken.с

$ ./broken

1: 12345

2: 12345678

3: 12345678

4: 12345

5: 12345

6: 12345

7: 12345

7.2. Средства проверки памяти, входящие в состав

glibc

Библиотека GNU С (

glibc
) предлагает три простых средства проверки памяти. Первые два —
mcheck
и
MALLOC_CHECK_
— вызывают проверку на непротиворечивость структуры данных кучи, а третье средство —
mtrace
— выдает трассировку распределения и освобождения памяти для дальнейшей обработки.

7.2.1. Поиск повреждений кучи

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

Если вы установили переменную окружения

MALLOC_CHECK_
, выбирается другой, несколько более медленный набор функций управления памятью. Этот набор более устойчив к ошибкам и может обнаруживать ситуации, когда
free
вызывается более одного раза для одного и того же указателя, а также когда происходят однобайтные переполнения буфера. Если
MALLOC_CHECK_
установлена в
0
, функции управления памятью просто более устойчивы
к ошибкам, но не выдают никаких предупреждений. Если
MALLOC_CHECK_
установлена в
1
, функции управления памятью выводят предупреждения о стандартных ошибках при замеченной проблеме. Если
MALLOC_CHECK_
установлена в
2
, функции управления памятью вызывают
abort
, когда замечают проблемы.

Установка

MALLOC_CHECK_
в
0
может оказаться полезной, если вам мешает найти ошибку в памяти другая ошибка, которую в этот момент исправить нет возможности; эта установка позволяет работать с другими средствами отслеживания ошибок памяти.

Установка

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

Установка

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

$ MALLOC_CHECK_=1 ./broken

malloc: using debugging hooks

malloc: используются отладочные функции

1: 12345

free: invalid pointer 0x80ac008!

free: недопустимый указатель 0x80ac008!

2: 12345678

3: 12345678

4: 12345

5: 12345

6: 12345

7: 12345

$ MALLOC_CHECK_=2 gdb ./broken

...

(gdb) run

Starting program: /usr/src/lad/code/broken

Запуск программы: /usr/src/lad/code/broken

1: 12345

Program received signal SIGABRT, Aborted.

Программа получила сигнал SIGABRT, прервана.

0x00 с 64 с 32 in _dl_sysinfo_int80 from/lib/ld-linux.so.2

(gdb) where

#0 0x00c64c32 in _dl_sysinfo_int80 from /lib/ld-linux.so.2

#1 0x00322969 in raise from /lib/tls/libc.so.6

#2 0x00324322 in abort from /lib/tls/libc.so.6

#3 0x0036d9af in free_check from /lib/tls/libc.so.6

#4 0x0036afa5 in free from /lib/tls/libc.so.6

#5 0x0804842b in broken at broken.c:17

#6 0x08048520 in main at broken.с:47

Другой способ заставить

glibc
проверить кучу на непротиворечивость — воспользоваться функцией
mcheck
:

typedef void(*mcheck Callback)(enummcheck_status status);

void mcheck(mcheck Callback cb) ;

В случае вызова функции

mcheck
, функция
malloc
размещает известные последовательности байтов перед и после возвращенной области памяти, чтобы можно было обнаружить переполнение или недогрузку буфера,
free
ищет эти сигнатуры и, если они были повреждены, вызывает функцию, указанную аргументом
cb
. Если
cb
равен
NULL
, выполняется выход. Запуск программы, связанной с
mcheck
, в gdb может показать, какие именно области памяти были повреждены, если только они явно освобождаются с помощью
free
. Однако метод
mcheck
не может точно определить место ошибки; лишь программист может вычислить это, основываясь на понимании логики работы программы.

Поделиться с друзьями: