Книга: Linux программирование в примерах
15.5.2.2. Electric Fence
15.5.2.2. Electric Fence
В разделе 3.1 «Адресное пространство Linux/Unix» мы описали, как динамическая память выделяется из кучи, которая может расти и сокращаться (с помощью вызовов brk()
или sbrk()
, описанных в разделе 3.2.3 «Системные вызовы: brk()
и sbrk()
»).
Ну, картина, которую мы там представили, является упрощением действительности. Более развитые системные вызовы (не рассматриваемые в данной книге) позволяют добавлять в адресное пространство процесса дополнительные, необязательно смежные сегменты памяти. Многие отладчики malloc()
работают с использованием этих системных вызовов для добавления новых областей адресного пространства при каждом выделении. Преимуществом этой схемы является то, что операционная система и аппаратное обеспечение защиты памяти компьютера взаимодействуют для обеспечения недействительности доступа к памяти за пределами этих изолированных сегментов, генерируя сигнал SIGSEGV
. Эта схема изображена на рис. 15.1.
Рис. 15.1. Адресное пространство Linux/Unix, включая специальные области
Первым пакетом отладки, реализовавшим эту схему, был Electric Fence. Electric Fence является вставляемым заместителем для malloc()
и др. Он работает на многих системах Unix и GNU/Linux; он доступен с FTP архива его авторов.[178] Он поставляется также со многими дистрибутивами GNU/Linux, хотя, возможно, вам придется выбрать ею явным образом при установке системы.
После компоновки программы с Electric Fence любой доступ за пределами выделенной памяти генерирует SIGSEGV
. Electric Fence также перехватывает попытки использования уже освобожденной памяти. Вот простая программа, которая иллюстрирует обе проблемы.
1 /* ch15-badmem1.с --- плохо обращается с памятью */
2
3 #include <stdio.h>
4 #include <stdlib.h>
5
6 int main(int argc, char **argv)
7 {
8 char *p;
9 int i;
10
11 p = malloc(30);
12
13 strcpy(p, "not 30 bytes");
14 printf("p = <%s>n", p);
15
16 if (argc ==2) {
17 if (strcmp(argv[1], "-b") == 0)
18 p[42] = 'a'; /* коснуться за пределами границы */
19 else if (strcmp(argv[1], "-f") == 0) {
20 free(p); /* освободить память, затем использовать ее */
21 p[0] = 'b';
22 }
23 }
24
25 /* освобождение (p); */
26
27 return 0;
28 }
Эта программа осуществляет простую проверку опций командной строки, чтобы решить, как вести себя плохо: -b
вызывает доступ к памяти за ее выделенными страницами, а -f
пытается использовать освобожденную память. (Строки 18 и 21 являются соответственно опасными.) Обратите внимание, что без опций указатель никогда не освобождается (строка 25), Electric Fence не перехватывает этот случай.
Одним из способов использования Electric Fence, способом, который гарантированно работает на различных системах Unix и GNU/Linux, является статическая компоновка с ним вашей программы. Затем программа должна быть запущена из отладчика. (Документация Electric Fence явно указывает, что Electric Fence не следует компоновать с двоичным файлом готового изделия.) Следующий сеанс демонстрирует эту процедуру и показывает, что происходит для обеих опций командной строки:
$ cc -g ch15-badmem1.c -lefence -о ch15-badmem1 /* Откомпилировать; компоновка статическая */
$ gdb ch15-badmem1 /* Запустить из отладчика */
GNU gdb 5.3
...
(gdb) run -b /* Попробовать опцию -b */
Starting program: /home/arnold/progex/code/ch15/ch15-badmem1 -b
[New Thread 8192 (LWP 28021)]
Electric Fence 2.2.0 Copyright (C) 1987-1999 Bruce Perens <[email protected]>
p = <not 30 bytes>
Program received signal SIGSBGV, Segmentation fault.
SIGSBGV: GDB prints where
[Switching to Thread 8192 (LWP 28021)]
0x080485b6 in main (argc=2, argv=0xbffff8a4) at ch15-badmem1.c:18
18 p[42] = 'a'; /* коснуться за пределами границы */
(gdb) run -f /* Теперь попробовать опцию -f */
The program being debugged has been started already.
Start it from the beginning? (y or n) y /* Да */
Starting program: /home/arnold/progex/code/ch15/ch15-badmem1 -f
[New Thread 8192 (LWP 28024)]
Electric Fence 2.2.0 Copyright (C) 1987-1999 Bruce Perens <[email protected]>
p = <not 30 bytes>
Program received signal SIGSEGV, Segmentation fault. /* Снова SIGSEGV */
[Switching to Thread 8192 (LWP 28024)]
0x080485e8 in main (argc=2, argv=0xbffff8a4) at ch15-badmem1.c:21
21 p[0] = 'b';
На системах, которые поддерживают разделяемые библиотеки и переменную окружения LD_PRELOAD
(в том числе и на GNU/Linux), вам не нужно явным образом компоновать библиотеку efence
. Вместо этого сценарий оболочки ef
организует запуск программы с соответствующей настройкой.
Хотя мы не описали механизмы подробно, GNU/Linux (и другие системы Unix) поддерживают разделяемые (shared) библиотеки, особые версии библиотечных процедур, которые хранятся в одном файле на диске, вместо того, чтобы копироваться в каждый отдельный двоичный исполняемый файл программы. Разделяемые библиотеки экономят дисковое пространство и могут сохранить системную память, поскольку все программы, использующие разделяемые библиотеки, используют одну и ту же копию библиотеки в памяти. Платой за это является замедление загрузки программы, поскольку программу и разделяемую библиотеку нужно подключить друг к другу прежде, чем программа сможет начать выполнение. (Обычно это прозрачно для вас, пользователя.)
Переменная окружения LD_PRELOAD
заставляет системный загрузчик программ (который загружает исполняемые файлы в память) связаться со специальной библиотекой до стандартных библиотек. Сценарий ef
использует эту особенность для связывания набора функций malloc()
в Electric Fence.[179] Таким образом, повторная компоновка даже не нужна. Этот пример демонстрирует ef
:
$ cc -g ch15-badmem1.c -о ch15-badmem1 /* Компилировать как обычно */
$ ef ch15-badmem1 -b /* Запустить с использованием ef, создает дамп ядра */
Electric Fence 2.2.0 Copyright (С) 1987-1999 Bruce Perens <[email protected]>
p = <not 30 bytes>
/usr/bin/ef: line 20: 28005 Segmentation fault (core dumped)
( export LD_PRELOAD=libefence.so.0.0; exec $* )
$ ef ch15-badmem1 -f /* Запустить с использованием ef, снова создает дамп ядра */
Electric Fence 2.2.0 Copyright (С) 1987-1999 Bruce Perens <[email protected]>
p = <not 30 bytes>
/usr/bin/ef: line 20: 28007 Segmentation fault (core dumped)
( export LD_PRELOAD=libefence.so.0.0; exec $* )
$ ls -l core* /* Linux создает для нас разные файлы core */
-rw------- 1 arnold devel 217088 Aug 28 15:40 core.28005
-rw------- 1 arnold devel 212992 Aug 28 15:40 core.28007
GNU/Linux создает файлы core
, которые включают в свое имя ID процесса. В данном случае такое поведение полезно, поскольку мы можем отдельно отлаживать каждый файл core
:
$ gdb ch15-badmem1 core.28005 /* От опции -b */
GNU gdb 5.3
...
Core was generated by 'ch15-badmem1 -b'.
Program terminated with signal 11, Segmentation fault.
...
#0 0x08048466 in main (argc=2, argv=0xbffff8c4) at ch15-badmem1.c:18
18 p[42] = 'a'; /* touch outside the bounds */
(gdb) quit
$ gdb ch15-badmem1 core.28007 /* От опции -f */
GNU gdb 5.3
...
Core was generated by 'ch15-badmem1 -f'.
Program terminated with signal 11, Segmentation fault.
...
#0 0x08048498 in main (argc=2, argv=0xbffff8c4) at ch15-badmem1.с:21
21 p[0] = 'b';
Справочная страница efence(3) описывает несколько переменных окружения, которые должны быть установлены, чтобы настроить поведение Electric Fence. Следующие три наиболее примечательны.
EF_PROTECT_BELOW
Установка этой переменной в 1 заставляет Electric Fence проверять «недоборы» (underruns) вместо «переборов» (overruns) при выходе за пределы отведенной памяти. «Перебор», т.е. доступ к памяти в области за выделенной, был продемонстрирован ранее. «Недобор» является доступом к памяти, расположенной перед выделенной областью памяти.
EF_PROTECT_FREE
Установка этой переменной в 1 предотвращает повторное использование Electric Fence памяти, которая была корректно освобождена. Это полезно, когда вы думаете, что программа может получать доступ к освобожденной памяти; если освобожденная память впоследствии была выделена заново, доступ к ней через предыдущий висячий указатель остался бы в противном случае незамеченным.
EF_ALLOW_MALLOC_0
При наличии ненулевого значения Electric Fence допускает вызовы 'malloc(0)
'. Такие вызовы в стандартном С технически действительны, но могут представлять программную ошибку. Соответственно Electric Fence по умолчанию их запрещает.
Вдобавок к переменным окружения Electric Fence предоставляет глобальные переменные с такими же названиями. Вы можете изменить их значения из отладчика, так что можно динамически изменять поведение программы, которая уже начала выполнение. Подробности см. в efence(3).