Хотя Electric Fence непосредственно не указывает на место, где произошла ошибка, проблема становится намного очевидней. Можно легко и точно определить проблемный участок, запустив программу под управлением отладчика, например
gdb
. Для того чтоб
gdb
смог точно указать на проблему, соберите программу с отладочной информацией, указав для
gcc
флажок
– g
, затем запустите
gdb
и зададите имя исполняемого файла, который нужно отладить. Когда программы уничтожается,
gdb
точно показывает, в какой строке произошел сбой.
Вот как выглядит описанная процедура.
$ gcc -ggdb -Wall -о broken broken.с -lefence
$ gdb broken
...
(gdb) run
Starting program: /usr/src/lad/code/broken
Electric Fence 2.2.0 Copyright (C) 1987 - 1999 Bruce Perens.
1: 12345
Program received signal SIGSEGV, Segmentation fault.
Программа
получила сигнал SIGSEGV, ошибка сегментации.
0х007948с6 in strcpy from /lib/tls/libc.so.6
(gdb) where
#0 0x007948c6 in strcpy from /lib/tls/libc.so.6
#1 0x08048566 in broken at broken.c:21
#2 0x08048638 in main at broken.c:47
(gdb)
Благодаря Electric Fence и
gdb
, становится понятно, что в строке 21 файла
broken.с
имеется ошибка, связанная со вторым вызовом
strcpy
.
7.5.2. Выравнивание памяти
Хотя инструмент Electric Fence очень помог в обнаружении второй проблемы в коде, а именно — вызова
strcpy
, переполнившего буфер, первое переполнение буфера найдено не было.
Проблему в этом случае нужно решать с помощью выравнивания памяти. Большинство современных компьютеров требуют, чтобы многобайтные объекты начинались с определенных смещений в оперативной памяти. Например, процессоры Alpha требуют, чтобы 8-байтовый тип — длинное целое (
long
) — начинался с адреса, кратного 8. Это значит, что длинное целое может располагаться по адресу 0x1000 или 0x1008, но не 0x1005 [11] .
11
Большинство традиционных систем Unix передают ошибку шины (
SIGBUS
) процессу, который пытается использовать невыровненные данные. Ядро Linux обрабатывает доступ к невыровненным данным так, чтобы процесс мог нормально продолжать работу, хотя за это приходится платить существенным снижением производительности.
На основе этих соглашений реализации
malloc
обычно возвращают память, первый байт которой выровнен в соответствии с размером слова процессора (4 байта для 32-разрядных и 8 байтов на 64-разрядных процессоров). По умолчанию Electric Fence пытается эмулировать такое поведение, предлагая функцию
malloc
, возвращающую только адреса, кратные
sizeof(int)
.
В большинстве программ подобное выравнивание не критично, то есть распределение памяти происходит инкрементным образом на основе размера машинного слова либо в виде простых символьных строк, для которых требования по выравниванию не предусмотрены (поскольку каждый элемент занимает всего 1 байт).
В случае с нашей тестовой программой первый вызов
malloc
распределил пять байт.
Для того чтобы Electric Fence удовлетворял своим ограничениям по выравниванию, он трактует этот вызов как запрос восьми байт, с дополнительными тремя доступными байтами. В этом случае небольшие переполнения буфера, распространяющиеся на эту область, не перехватываются.
В связи с тем, что выравнивание
malloc
обычно можно игнорировать, а выравнивание может способствовать незаметному переполнению буфера, Electric Fence предоставляет возможность управление выравниванием через переменную окружения
ЕF_ALIGNMENT
. Если эта переменная установлена, все результаты
malloc
выравниваются в соответствии с ее значением. Например, если переменная установлена в значение 5, все результаты
malloc
будут рассматриваться как кратные 5 (тем не менее, это значение не особенно полезно). Для отключения выравнивания памяти перед запуском программы установите
ЕF_ALIGNMENT
в
1
. В среде Linux некорректно выровненный доступ в любом случае исправляются в ядре, несмотря на то, что в результате скорость выполнения программы может существенно снизиться. Программа будет функционировать корректно, если только в ней не присутствуют небольшие переполнения буфера.
Ниже приведен пример поведения тестовой программы, скомпонованной с Electric Fence, после установки
ЕF_ALIGNMENT
в
1
.
$ export EF_ALIGNMENT=1
$ gdb broken
...
(gdb) run
Starting program: /usr/src/lad/code/broken
Electric Fence 2.2.0 Copyright (C) 1987 - 1999 Bruce Perens.
Program received signal SIGSEGV, Segmentation fault.
0x002a78c6 in strcpy from /lib/tls/libc.so.6
(gdb) where
#0 0x002a78c6 in strcpy from /lib/tls/libc.so.6
#1 0x08048522 in broken at broken.c:15
#2 0x08048638 in main at broken.с:47
На этот раз Electric Fence нашел переполнение буфера, которое произошло первым.
7.5.3. Другие средства
Electric Fence не только помогает обнаружить переполнение буфера, но и может найти недогрузку буфера (выполняя доступ к памяти, расположенной перед началом выделяемого
malloc
буфера) и получает доступ к памяти, освобождаемой с помощью
free
. Если переменная окружения
EF_PROTECT_BELOW
установлена в
1
, Electric Fence перехватывает недогрузку буфера вместо его переполнения. Это происходит путем размещения недоступной области памяти непосредственно перед фактической областью памяти, возвращаемой функцией
malloc
. При этом Electric Fence не сможет обнаружить переполнение буфера из-за страничной организации памяти, реализованной в большинстве процессоров. Выравнивание памяти может затруднить обнаружение переполнения буфера, однако оно не влияет на недогрузку буфера. Функция
malloc
из Electric Fence всегда возвращает адрес памяти в начале страницы, которая всегда выровнена по границе слова.
Если
EF_PROTECT_FREE
установлена в
1
,
free
делает переданную ей область памяти недоступной, но не возвращает ее в пул свободной памяти. Если программа пытается получить доступ к этой памяти на любом этапе в будущем, ядро обнаружит несанкционированный доступ. Настройка
EF_PROTECT_FREE
помогает удостовериться, что код ни на одном этапе выполнения не использует память, освобожденную с помощью
free
.
7.5.4. Ограничения
Несмотря на то что Electric Fence выполняет неплохую работу по обнаружению переполнения буферов, выделенных
malloc
, он не помогает отслеживать проблемы ни с глобальными, ни с локальными данными. Electric Fence также не обнаруживает утечки памяти, потому решать эту проблему придется другими средствами.
7.5.5. Потребление ресурсов
Хотя Electric Fence является мощным, легким в употреблении и быстрым инструментом (поскольку все проверки доступа осуществляются аппаратными средствами), за все это приходится платить свою цену. Большинство процессоров позволяют системе управлять доступом к памяти только в единицах, равных странице, за один раз. На процессорах Intel 80x86, например, каждая страница занимает 4096 байт. Вследствие того, что Electric Fence требует от
malloc
установки двух разных областей памяти для каждого вызова (одна — позволяющая доступ, а другая — запрещающая), каждый вызов
malloc
потребляет страницу памяти, или 4 Кбайт [12] ! Если в тестируемом коде распределяется множество небольших участков памяти, его компоновка с Electric Fence может легко увеличить потребление памяти программы на два или три порядка. При этом использование
EF_PROTECT_FREE
еще более усугубляет положение, поскольку память никогда не освобождается.
12
Во всяком случае это справедливо для систем Linux/Intel и Linux/SPARC. Размер страницы зависит от базовой аппаратной архитектуры и в некоторых системах может составлять 16 Кбайт и больше.