2. Команды работы со стеком
Стек – это область памяти, специально выделяемая для временного хранения данных программы. Важность стека определяется тем, что для него в структуре программы предусмотрен отдельный сегмент. На тот случай, если программист забыл описать сегмент стека в своей программе, компоновщик tlink выдаст предупреждающее сообщение.
Для работы со стеком предназначены три регистра:
SS – регистр сегмента стека;
SP/ESP – регистр указателя стека;
ВР/ЕВР – регистр указателя базы кадра стека.
Размер стека зависит от режима работы процессора и ограничивается значением 64 Кбайт (или 4 Гбайт в защищенном режиме). В каждый момент времени доступен только один стек, адрес сегмента которого содержится в регистре SS. Этот стек называется текущим. Для того чтобы обратиться к другому стеку («переключить стек»), необходимо загрузить в регистр SS другой адрес. Регистр SS автоматически используется процессором для выполнения всех команд, работающих со стеком.
Особенности работы со стеком:
запись и чтение данных в стеке осуществляются в соответствии с принципом LIFO (Last In First Out — «последним пришел, первым ушел»);
по мере записи данных в стек последний растет в сторону младших адресов. Эта особенность заложена в алгоритм команд работы со стеком;
при использовании регистров ESP/SP и ЕВР/ВР для адресации памяти ассемблер автоматически считает, что содержащиеся в нем значения представляют собой смещения относительно сегментного регистра SS.
В общем случае стек организован так, как показано на рис. 3.
Регистры SS, ESP/SP и ЕВР/ВР используются комплексно, и каждый из них имеет свое функциональное назначение. Регистр ESP/SP всегда указывает на вершину стека, то есть содержит смещение, по которому в стек был занесен последний элемент. Команды работы со стеком неявно изменяют этот регистр так, чтобы он указывал всегда на последний записанный в стек элемент. Если стек пуст, то значение ESP равно адресу последнего байта сегмента, выделенного под стек. При занесении элемента в стек процессор уменьшает значение регистра ESP, а затем записывает элемент по адресу новой вершины. При извлечении данных из стека процессор копирует элемент, расположенный по адресу вершины, а затем увеличивает значение регистра указателя стека ESP. Таким образом, получается, что стек растет вниз, в сторону уменьшения адресов.
Рис.3 Схема организации стека
Для получения доступа к элементам не на вершине, а внутри стека применяют регистр ЕВР. Регистр ЕВР – регистр указателя базы кадра стека. Например, типичным приемом при входе в подпрограмму является передача нужных параметров путем записи их в стек. Если подпрограмма тоже активно работает со стеком, то доступ к этим параметрам становится проблематичным. Выход в том, чтобы после записи нужных данных в стек сохранить адрес вершины стека в указателе базы кадра стека — регистре ЕВР. Значение в ЕВР в дальнейшем можно использовать для доступа к переданным параметрам.
Начало стека расположено в старших адресах памяти. На рис. 3 этот адрес обозначен парой SS:ffff. Смещение ffff приведено здесь условно. Реально это значение определяется величиной, которая задается при описании сегмента стека в программе. Адресная пара SS:ffff — это максимальное для реального режима значение адреса начала стека, так как размер сегмента в нем ограничен величиной 64 Кбайт (0ffffh).
Для организации работы со стеком существуют специальные команды и чтения:
команда PUSH выполняет запись значения <источник> в вершину стека: push <источник>
Алгоритм работы этой команды включает в себя два действия (рис. 4):
1. значение SP уменьшается на 2 – (SP) = (SP) – 2;
2. значение источника записывается по адресу, указываемому парой SS:SP;
команда POP выполняет запись значения из вершины стека по месту, указанному операндом <приемник> (значение при этом «снимается» с вершины стека): pop <приемник>
Рис. 4 Принцип работы команды PUSH
Алгоритм работы команды POP обратен алгоритму команды PUSH (рис.5).
Рис. 5. Принцип работы команды POP
Запись содержимого вершины стека по месту, указанному операндом <приемник>;
Увеличение значения SP – (SP) = (SP) + 2;
команда PUSHA предназначена для групповой записи в стек. По этой команде в стек последовательно записывается содержимое регистров АХ, СХ, DX, BX, SP, BP, SI, DI. Причем записывается оригинальное содержимое SP, то есть то, которое было до выдачи команды PUSHA (рис. 6);
команда PUSHAW почти идентична команде PUSHA. Отличия заключаются в следующем:
при значении атрибута сегмента use16 — алгоритм работы PUSHAW аналогичен алгоритму PUSHA;
при значении атрибута сегмента use32 — алгоритм работы команды PUSHAW не меняется (то есть она нечувствительна к разрядности сегмента и всегда работает с регистрами размером в слово — АХ, СХ, DX, BX, SP, BP, SI, DI), а команда PUSHA чувствительна к разрядности сегмента и при указании 32-разрядного сегмента работает с соответствующими 32-разрядными регистрами (то есть ЕАХ, ЕСХ, EDX, EBX, ESP, EBP, ESI, EDI).
Рис. 6. Принцип работы команды PUSHA
Представленная далее группа команд позволяет сохранить в стеке регистр флагов и записать слово или двойное слово. При этом перечисленные команды – единственные в системе команд процессора, которые позволяют получить доступ (и которые нуждаются в этом доступе) ко всему содержимому регистра флагов:
Команда PUSHF сохраняет регистр флагов в стеке. Работа этой команды зависит от атрибута размера сегмента:
use16 — в стек записывается регистр FLAGS размером два байта;
use32 — в стек записывается регистр ЕFLAGS размером четыре байта.
Команда PUSHFW сохраняет в стеке регистр флагов размером в слово. С атрибутом use16 всегда работает так же, как команда PUSHF.
Команда PUSHFD сохраняет в стеке регистр флагов FLAGS или EFLAGS в зависимости от атрибута размера сегмента (то есть то же, что и PUSHF).
Следующие три команды также выполняют действия, обратные действиям рассмотренных выше команд:
POPF
POPFW
POPFD
Использование стека практически неизбежно при: вызов подпрограмм; временное сохранение значений регистров; определение локальных переменных в процедуре.