Что-то, что я написал когда-то в образовательных целях ...
Рассмотрим следующую c-программу:
int q[200];
main(void) {
int i;
for(i=0;i<2000;i++) {
q[i]=i;
}
}
после его компиляции и выполнения создается дамп ядра:
$ gcc -ggdb3 segfault.c
$ ulimit -c unlimited
$ ./a.out
Segmentation fault (core dumped)
теперь использую GDB для выполнения посмертного анализа:
$ gdb -q ./a.out core
Program terminated with signal 11, Segmentation fault.
[New process 7221]
#0 0x080483b4 in main () at s.c:8
8 q[i]=i;
(gdb) p i
$1 = 1008
(gdb)
да, программа не выполняла segfault, когда кто-то писал за пределами выделенных 200 элементов, вместо этого она аварийно завершала работу, когда i = 1008, почему?
Введите страницы.
В UNIX / Linux можно определить размер страницы несколькими способами, один из них - использовать системную функцию sysconf () следующим образом:
#include <stdio.h>
#include <unistd.h> // sysconf(3)
int main(void) {
printf("The page size for this system is %ld bytes.\n",
sysconf(_SC_PAGESIZE));
return 0;
}
что дает результат:
Размер страницы для этой системы составляет 4096 байт.
или можно использовать утилиту командной строки getconf следующим образом:
$ getconf PAGESIZE
4096
вскрытие
Оказывается, segfault возникает не при i = 200, а при i = 1008, давайте разберемся, почему. Запустите GDB, чтобы провести посмертный анализ:
$gdb -q ./a.out core
Core was generated by `./a.out'.
Program terminated with signal 11, Segmentation fault.
[New process 4605]
#0 0x080483b4 in main () at seg.c:6
6 q[i]=i;
(gdb) p i
$1 = 1008
(gdb) p &q
$2 = (int (*)[200]) 0x804a040
(gdb) p &q[199]
$3 = (int *) 0x804a35c
q заканчивался по адресу 0x804a35c, точнее, последний байт q [199] находился в этом месте. Размер страницы, как мы видели ранее, составляет 4096 байт, а 32-битный размер слова машины дает, что виртуальный адрес разбивается на 20-битный номер страницы и 12-битное смещение.
q [] оканчивается номером виртуальной страницы:
0x804a = 32842 смещение:
0x35c = 860, так что все еще было:
4096 - 864 = 3232 байта осталось на той странице памяти, на которой был выделен q []. Это пространство может содержать:
3232/4 = 808 целых чисел, и код обрабатывал его так, как если бы он содержал элементы q в позициях от 200 до 1008.
Все мы знаем, что этих элементов не существует, и компилятор не жаловался, как и hw, поскольку у нас есть права на запись на эту страницу. Только когда я = 1008, q [] ссылался на адрес на другой странице, для которой у нас не было разрешения на запись, виртуальная память hw обнаружила это и запустила segfault.
Целое число хранится в 4 байтах, что означает, что эта страница содержит 808 (3236/4) дополнительных поддельных элементов, что означает, что доступ к этим элементам от q [200], q [201] до элемента 199 по-прежнему совершенно законен. + 808 = 1007 (q [1007]) без активации ошибки сегмента. При доступе к q [1008] вы попадаете на новую страницу с другими разрешениями.
person
Fredrik Pihl
schedule
23.06.2011
sizeof(int)==4
, вы выделили ничтожные 12 байтов из стека. Ваш код пишет за пределами конца массива. Это не переполнение стека. Это неопределенное поведение. - person David Hammen   schedule 23.06.2011arr[3]
означает выделить 3int
пространства, доступного для моего использования, это не означает создать 3int
пространства из эфира, хотя это было бы законной реализацией, если бы это было физически возможно. Вы набрасываете любую память / адрес, который оказывается рядом сarr
(ну, фактически, по соседству), который, как говорит Дэвид, является UB. Да, это часть вашего стека (стандарты C и C ++ не говорят о стеке, но на практике именно туда попадают автоматические переменные). - person Steve Jessop   schedule 23.06.2011arr[3] = 99;
уже хватило бы. - person Peter - Reinstate Monica   schedule 26.11.2018