Почему текстовые секции программы Linux начинаются с 0x0804800, а вершины стека начинаются с 0xbffffff?
В соответствии с Руководством по сборке для хакеров (часть 2) Организация виртуальной памяти, программа для Linux .text
разделы начинаются с 0x0804800
и вершины стека начинаются в 0xbffffff
, Какое значение имеют эти цифры? Почему бы не начать .text
в 0x0000000
(или же 0x0000020
или же 0x0000040
пройти следующие 32 или 64 бита NULL
)? Почему бы не начать вершину стека в 0xfffffff
?
2 ответа
Давайте начнем с того, что скажем так: в большинстве случаев различные разделы не нужно размещать в определенном месте, важнее макет. В настоящее время вершина стека фактически рандомизирована, см. Здесь.
0x08048000 - это адрес по умолчанию, с которого ld запускает первый PT_LOAD
сегмент на Linux/x86. В Linux/amd64 значением по умолчанию является 0x400000, и вы можете изменить значение по умолчанию с помощью специального сценария компоновщика. Вы также можете изменить, где раздел.text начинается с -Wl,-Ttext,0xNNNNNNNN
флаг для GCC. Чтобы понять, почему.text не отображается по адресу 0, имейте в виду, что указатель NULL обычно отображается для ((void *) 0) для удобства. В таком случае полезно, чтобы нулевая страница отображалась как недоступная, чтобы перехватить использование указателей NULL. Память до начала.text на самом деле используется многими вещами; принимать cat /proc/self/maps
В качестве примера:
$ cat /proc/self/maps
001c0000-00317000 r-xp 00000000 08:01 245836 /lib/libc-2.12.1.so
00317000-00318000 ---p 00157000 08:01 245836 /lib/libc-2.12.1.so
00318000-0031a000 r--p 00157000 08:01 245836 /lib/libc-2.12.1.so
0031a000-0031b000 rw-p 00159000 08:01 245836 /lib/libc-2.12.1.so
0031b000-0031e000 rw-p 00000000 00:00 0
00376000-00377000 r-xp 00000000 00:00 0 [vdso]
00852000-0086e000 r-xp 00000000 08:01 245783 /lib/ld-2.12.1.so
0086e000-0086f000 r--p 0001b000 08:01 245783 /lib/ld-2.12.1.so
0086f000-00870000 rw-p 0001c000 08:01 245783 /lib/ld-2.12.1.so
08048000-08051000 r-xp 00000000 08:01 2244617 /bin/cat
08051000-08052000 r--p 00008000 08:01 2244617 /bin/cat
08052000-08053000 rw-p 00009000 08:01 2244617 /bin/cat
09ab5000-09ad6000 rw-p 00000000 00:00 0 [heap]
b7502000-b7702000 r--p 00000000 08:01 4456455 /usr/lib/locale/locale-archive
b7702000-b7703000 rw-p 00000000 00:00 0
b771b000-b771c000 r--p 002a1000 08:01 4456455 /usr/lib/locale/locale-archive
b771c000-b771e000 rw-p 00000000 00:00 0
bfbd9000-bfbfa000 rw-p 00000000 00:00 0 [stack]
Здесь мы видим библиотеку C, динамический загрузчик ld.so и ядро VDSO (библиотека динамического кода с отображением ядра, которая предоставляет некоторые интерфейсы ядру). Обратите внимание, что начало кучи также рандомизировано.
Там не так много значения.
Стек обычно увеличивается вниз (до младших адресов), поэтому разумно (но не обязательно) размещать его по старшим адресам и иметь некоторое пространство для его расширения в сторону младших адресов.
Что касается не использования адреса 0 для разделов программы, здесь есть некоторая логика. Во-первых, много программного обеспечения использует 0 для NULL
недопустимый указатель на C и C++, который не должен быть разыменован. Многие программы имеют ошибки в том, что они фактически пытаются читать или записывать память по адресу 0 без надлежащей проверки указателя. Если вы сделаете область памяти вокруг адреса 0 недоступной для программы, вы можете обнаружить некоторые из этих ошибок (программа аварийно завершит работу или остановится в отладчике). Кроме того, так как NULL
является недопустимым указателем, по этому адресу не должно быть данных или кода (если он есть, вы не можете отличить указатель от него от NULL
).
На платформе x86 память вокруг адреса 0 обычно становится недоступной посредством преобразования виртуальных адресов в физические. Таблицы страниц настраиваются таким образом, что запись для виртуального адреса 0 не резервируется страницей физической памяти, а размер страницы обычно составляет 4 КБ, а не просто несколько байтов. Вот почему, если вы удалите адрес 0, вы также удалите адреса с 1 по 4095. Также разумно извлечь более 4 КБ адресного пространства по адресу 0. Причина этого - указатели на структуры в C и C++. Вы можете иметь NULL
указатель на структуру, и когда вы разыменовываете ее, попытка доступа к памяти происходит по адресу, содержащемуся в указателе (0) плюс расстояние между элементом структуры, к которому вы пытаетесь обратиться, и началом структуры (0 для первого элемента больше 0 для остальных).
При выборе конкретных диапазонов адресов для программ могут быть и другие соображения, но я не могу говорить за все из них. ОС может захотеть сохранить некоторые связанные с программой вещи (структуры данных) внутри самой программы, так почему бы не использовать фиксированное местоположение для этого рядом с одним из концов доступной части адресного пространства?