Несмотря на то что С бесспорно является стандартным языком программирования в системах Linux, он имеет ряд особенностей, не дающих программистам возможности писать код, не содержащий тонких ошибок, которые впоследствии очень сложно отладить. Утечки памяти (когда память, выделенная с помощью та 11 ос (), никогда не освобождается посредством free ()) и переволнение буфера (например, запись за пределы массива) — наиболее распространенные и трудные для обнаружения программные ошибки. Недогрузка буфера (вроде записи перед началом массива) — менее распространенное, но обычно еще более тяжелое для отслеживания явление. В этой главе представлены несколько средств отладки, которые могут значительно упростить обнаружение и изоляцию упомянутых проблем.
71 Код, содержащий ошибки / broken. с / include
int broken(void){ char dyn; char local[5];
/ Для начала немного перезаписать буфер / dyn malloc (5); strcpy(dyn, "12345"); printf("1: %s\n", dyn); free(dyn);
/ Теперь перезаписать буфер изрядно / dyn malloc (5); strcpy(dyn, "12345678"); printf ("2: %s\n", dyn);
24 / Пройти перед началом выделенного с помощью malloc локального буфера / 25 (dyn1) \0 1;
26 printf("3: %s\n", dyn);
27 / обратите внимание, что указатель не освобожден! /
28
29 / Теперь двинуться после переменной local / 30 strcpy(local, "12345");
31 printf("4: %s\n", local);
32 local [1] '\0';
33 printf ("5: %s\n", local);
34
35 / Наконец, атаковать пространство данных global / 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 (), локальные переменные размещенные в стеке программы и глобальные переменные, хранящиеся в отдельной области памяти, которая была статически распределена при запуске программы1. Для каждого класса памяти эта тестовая программа выполняет запись за пределами зарезервированной области памяти (по одному байту) и также сохраняет байт непосредственно перед зарезервированной областью. К тому же в коде имеется утечка памяти, что позволит продемонстрировать, как с помощью различных средств отследить эти утечки.
Несмотря на то что в представленном коде кроется много проблем, в действительности, он работает нормально. Не означает ли это, что проблемы подобного рода не важны? Ни в коем случае! Переполнение буфера часто приводит к неправильному поведению программы задолго до фактического его переполнения, а утечки памяти в программах, работающих длительное время, приводят к пустой растрате ресурсов компьютера. Более того, переполнение буфера является классическим источником уязвимостей безопасности, как описано в главе 22.