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] К сожалению, ни одно из средств, описанных в этой главе, не может отследить ошибки в памяти, связанные с глобальными переменными; для этого нужна помощь компилятора. В первом издании этой книги рассматривался инструмент под названием 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
наиболее полезна при работе в отладчике, поскольку при возникновении ошибки он позволяет выполнить обратную трассировку вплоть до функций управления памятью. В результате вы максимально приблизитесь к месту возникновения ошибки.
Читать дальше