logo search
Методические указания для АЗ-ИБ

Сегментация памяти программы

Прежде чем изучать ошибки переполнение буфера, рассмотрим как устроена память программы.

Память программы делится на пять сегментов:

Каждый сегмент представляет специальный участок памяти, отведённый для определённой цели. Сегмент текста иногда также называют сегментом кода. В нём располагаются ассемблированные машинные команды. Выполнение команд, находящихся в этом сегменте, происходит нелинейным образом благодаря управляющим структурам и функциям высокого уровня, которые компилируются в команды ветвления, перехода и вызова функций (branch, jump и call) на языке ассемблера. В процессоре есть собственная специальная память (регистры). Некоторые особые регистры следят за ходом выполнения программы. Один из наиболее примечательных регистров расширенный указатель команд (EIP Extended Instruction Pointer). EIP служит указателем, содержащим адрес выполняемой в данный момент инструкции. Когда выполняется программа, в EIP записывается адрес первой команды в сегменте текста. Затем процессор осуществляет следующий цикл выполнения:

  1. Прочесть команду, на которую указывает EIP.

  2. Прибавить к содержимому EIP длину команды в байтах.

  3. Выполнить команду, прочитанную на шаге 1.

  4. Перейти к шагу 1.

Иногда прочтённой командой оказывается команда перехода или вызова, которая изменяет значение EIP, помещая в него другой адрес памяти. Процессор не обращает внимания на такие изменения, будучи готовым к нелинейному характеру выполнения. Поэтому если на шаге 3 изменить EIP, то процессор вернётся к шагу 1 и прочтёт ту команду, которая находится по адресу, записанному в EIP.

В сегменте текста запрещена запись, поскольку он используется только для хранения кода, а не переменных. Это не позволяет модифицировать код программы, и попытки записать что-либо в этот сегмент памяти приводят к извещению пользователя об аварии и прекращению выполнения программы. Другим преимуществом того, что в этом сегменте разрешено только чтение, является возможность совместного использования его несколькими экземплярами программы, которые могут выполняться одновременно, не мешая друг другу. Следует также заметить, что этот сегмент памяти имеет фиксированный размер, потому что в нём не происходит никаких изменений. Сегменты data и bss используются для хранения глобальных и статических переменных программы. В сегмент data записываются инициализированные глобальные переменные, строки и другие константы, используемые в программе. В сегмент bss записываются не инициализированные переменные. Хотя эти сегменты доступны для записи, их размер также фиксирован. Сегмент кучи отводится для остальных переменных программы. Важно заметить, что размер кучи не фиксирован: она может уменьшаться или увеличиваться по мере необходимости. Всей памятью кучи управляют алгоритмы выделения и освобождения памяти, которые резервируют в куче участок памяти для использования, а затем отменяют резервирование и разрешают повторно использовать эту память для последующего резервирования. Куча растёт или сокращается в зависимости от того, сколько памяти зарезервировано для неё. Рост кучи происходит вниз в направлении больших адресов памяти.

Сегмент стека тоже имеет переменный размер и используется как временная память для хранения контекста во время вызова функций. Когда программа вызывает функцию, последняя получает собственный комплект передаваемых ей переменных, а код функции находится в другом участке памяти в сегменте текста (или кода). Поскольку при вызове функции надо изменить контекст и EIP, в стек записываются все передаваемые переменные и адрес возврата, который должен быть записан в EIP после выполнения функции. В общих понятиях вычислительной техники стеком называют часто используемую абстрактную структуру данных. Он обрабатывается в порядке "первым пришёл, последним ушёл" (FILO), означающем, что элемент, первым помещённый в стек, будет извлечён оттуда последним. Помещение элемента в стек называют проталкиванием (pushing), а извлечение из стека выталкиванием (popping).

В соответствии с названием сегмент стека в памяти фактически является структурой данных типа стека. Адрес вершины (конца) стека хранится в регистре ESP (Extended Stack Pointer расширенный указатель стека) и постоянно меняется по мере проталкивания элементов в стек или выталкивания из него. Это очень динамичный режим, и понятно поэтому, что размер стека также не фиксирован. В противоположность куче, стек при увеличении размера растёт в сторону младших адресов памяти. При вызове функции в стек проталкивается группа данных в виде структуры, называемой кадром стека. Для ссылки на переменные в текущем кадре стека служит регистр EBP (Extended Base Pointer расширенный указатель базы). В каждом кадре стека содержатся параметры функции, её локальные переменные и два указателя, необходимых, чтобы вернуться в исходное состояние: сохранённый указатель кадра стека (SFP) и адрес возврата. Указатель кадра стека нужен для восстановления предшествующего значения EBP, а адрес возврата для восстановления в EIP адреса команды, следующей после вызова функции.