Пятница, 23.08.2019, 22:04
Приветствую Вас, Гость |
Меню сайта
Наш опрос
Нужен ли форум на этом сайте?
Всего ответов: 1193
Статистика

Онлайн всего: 1
Гостей: 1
Пользователей: 0

Стек (stack) в Ассемблере

Итак, что же такое стек и для чего он нужен?

Стек - это специально отведенная область памяти для хранения промежуточных данных.

Давайте немного договоримся об определениях.

Пусть будет так, что сегмент "растет" сверху вниз:

 0000
0001
0002
...
FFFE
FFFF

Т.е. мы как бы погружаемся под землю. Таким образом (сверху вниз) выполняется программа (если, конечно, не встречаются инструкции (команды) типа jmp, call и т.п.).

Стек же наоборот пополняется снизу вверх. Вершина стека - 0FFFFh, а низ (дно) - 0000h.

Когда мы вызываем какую-нибудь подпрограмму командой call, процессор кладет в стек адрес следующей за командой call инструкции. Следить за стеком позволяет пара регистров SS:SP. Многие уже, наверное, заметили, что при запуске какой-нибудь com-программы регистр SP равен 0FFFEh, а сегментный регистр SS, как уже упоминалось в предыдущих главах, равен нашему единственному сегменту (CSEG) (как и CS, DS, ES).

Теперь вам необходимо вооружиться отладчиком. Давайте рассмотрим вышесказанное на примере:

Напечатайте такую программу в редакторе (обязательно!):

 CSEG segment
assume cs:CSEG, ds:CSEG, es:CSEG, ss:CSEG
org 100h
begin:
call Our_proc
int 20h
Our_proc proc
ret
Our_proc endp
CSEG ends
end Begin

Ничего сложного... Запускаем отладчик.

Итак, смотрим на пару регистров SS:SP. SS=CS=DS=ES (это понятно). SP=0FFFEh (т.е. указывает на вершину стека).

Теперь заходим в процедуру. Для CV нажимаем F8, для AFD F1.

Опа! SP изменился. Он уменьшился на 2. Компьютер поместил в стек адрес возврата из процедуры (на инструкцию int 20h) Проще говоря, call перешел на метку Our_proc, поместив в стек адрес возврата из этой подпрограммы.

Нажимаем еще раз F8/F1. Что получилось? SP опять изменился! Но теперь он увеличился на 2 (стал равным 0FFFEh). Т.е. команда ret взяла из стека адрес возврата и перешла по нему. Как раз на int 20h.

В данном случае говорят, что мы выравнили стек. Он был изначально равен 0FFFEh и остался равен перед выходом 0FFFEh.

Вот один из способов использования стека. Но на этом мы не остановимся и рассмотрим несколько операторов ассемблера, которые позволяют работать со стеком.

Оператор

Перевод

Применение

Процессор

PUSH приемник

push - втолкнуть

Поместить в стек число

8086

 

Оператор

Перевод

Применение

Процессор

POP приемник

pop - вытолкнуть

Достать из стека число

8086

Допустим, нам нужно временно сохранит какое-нибудь число. Например, перед вызовом процедуры, прерывания или циклом

Вот как это запишется:

  ...
(1) mov ax,345h
(2) push ax
(3) mov ah,10h
(4) int 16h

(5) pop ax
...

Здесь мы загружаем в AX число 345h, сохраняем его, ждем нажатия клавиши (при этом сама клавиша будет в AX, т.е. AX изменится) и восстанавливаем AX. В итоге AX будет содержать число 345h, что, как говорится, и требовалось доказать.

Однако, стоит заметить такой момент. Допустим, мы помещаем в стек следующие регистры: AX, BX, CX:

  push ax
push bx
push cx

Обратите внимание, что восстанавливать со стека нужно в обратном порядке:

 pop cx
pop bx
pop ax

Если вы поменяете местами регистры при восстановлении, то ничего страшного не произойдет, только содержать они будут другие числа. Например:

 mov ax,1234h
mov bx,5678h
push ax
push bx

pop ax
pop bx

В итоге AX будет равен 5678h, а BX - 1234h.

Но в процедурах ассемблера необходимо очень тщательно следить за стеком. Вот пример:

 ...
call Our_proc
int 20h
...
Our_proc proc
mov ax,15
push ax
mov ah,9
mov dx,offset Str
int 21h
ret
Our_proc endp

...

Обратите внимание, что мы "забыли" восстановить из стека AX в нашей процедуре (Our_proc). Что произойдет? Компьютер, дойдя до оператора ret, вытащит из стека не адрес возврата, а число 15 и перейдет на этот адрес. Что находится по адресу 15 - не известно. Машина, скорее всего, "зависнет". Надеюсь, что это понятно.

Следить за стеком (как уже говорилось) позволяет пара регистров SS (сегмент):SP (смещение).

Программист может менять как сегмент, так и смещение, но при этом следует иметь ввиду, что перед сменой регистров SS и SP необходимо запретить все прерывания, а после изменения разрешить (запрет прерываний вешает компьютер! Восстановление же - приводит к нормальной работе. После команды cli всегда должна идти sti, иначе компьютер виснет!). Это позволяют сделать следующие операторы:

Оператор

Перевод

Применение

Процессор

CLI

CLear Interrupt - запретить

Запретить прерывания

8086

 

Оператор

Перевод

Применение

Процессор

STI

reSTore Interrupt - восстановить

Разрешить прерывания

8086

Для чего это нужно? Давайте рассмотрим это в двух словах, т.к. механизму работы прерываний необходимо посвятить целый выпуск. Если что-то не понятно - просто опустите, не обращайте внимания

Когда вы создали программу и запустили ее на выполнение, то работает не только она одна. Простейший пример - таймер, который вызывается примерно 18 раз в секунду для обновления. Компьютер всегда что-то делает! Даже тогда, когда ждет от вас нажатия клавиши. Что происходит, когда вызывается прерывание от таймера? Примерно тоже, что и при вызове процедуры. Компьютер запоминает в стеке адрес текущей команды, а также все регистры и переходит на адрес прерывания, по которому находится процедура обработки этого прерывания (например, таймера, которая обновит показания часов/минут/секунд). Затем, как процедура отработала, компьютер восстановит из стека адрес возврата и все регистры, и наша программа пойдет работать дальше

Вот пример, в котором мы изменим регистры SS:SP, не запрещая прерывания:

 (1) mov ax,100h
(2) mov ss,ax

(3) mov sp,200h

Допустим, прерывание таймера сработало после строки (2). Что у нас получилось? SS равен 100h, а SP еще не успел измениться. Получается, что сегмент стека верный, а смещение - осталось прежним (допустим, SP=0FFFEh). В итоге, SS=100h, а SP=0FFFEh. Компьюетр и сохранит по этому адресу данные. Какой при этом код программы затрется - не известно. Мы ведь хотели сделать SS=100h, а SP=200h! Хорошо, если две строки успели выполниться перед вызовом прерывания. Хорошо также, если по адресу 100h:0FFFEh ничего нет (память свободна). А если есть? Тогда компьютер, скорее всего, "зависнет".

Отсюда два правила:

1) Прежде чем менять регистры SS:SP, необходимо запретить все прерывания командой cli, а затем разрешить командой sti.

2) SS:SP нужно устанавливать на свободную область памяти. При этом следует убедиться, что код не утратил работоспособности.

Мы знаем, что после загрузки com-программы в память, SS равен сегменту, куда загрузилась программа, а SP - 0FFFEh. Код программы начинается с адреса 100h (org 100h). Вершина стека - конец нашего сегмента. Если наша программа занимает, скажем 2000h байт, то можем установить SP в 2200h. В этом случае мы отводим 100h (именно сто) байт для стека (т.к. программа загружается по адресу 100h, то 2000h нужно прибавить 100h). Стек, как вы помните, растет снизу вверх. Если мы переполним стек (например, поместим более 100h данных), то затрется часть нашей программы снизу. Имейте это ввиду!

Вот пример изменеия регистров стека:

 cli
mov ax,0B900h
mov ss,ax
mov sp,100h

sti
Форма входа
Поиск
Мы в сети
Реклама
Для того чтобы не видеть рекламу в правом верхнем углу сайта пройдите простую процедуру регистрации
ФОРУМ
У нас наконецто появился форум! Добро пожаловать! Будьте первыми, задайте направление форуму! =)
--- Не стесняемся - заходим на форум! ---