Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Уроки ассемблеру. Быстро и просто. Урок 9


Сегодня мы поговорим о стеке (stack). Что же это такое? Если просто — это выделенная область памяти для хранения произвольных данных. Мы помещаем туда на какое-то время значения, содержащиеся в регистрах, например, если необходимо туда загрузить какое-то другое значение, а потом можем его вернуть назад, причём не обязательно в этот регистр. Если мы откроем программу AFDPro, в самом верху (чуть правее середины) мы увидим надпись "Stack", а под ним — его первые значения (верхушку). По мере того, как мы кладём туда какие-нибудь данные, стек растёт — последнее записанное значение оказывается сверху, а остальные смещаются вниз.

Это то, что мы видим в отладчике. На самом деле надо запомнить, что стек растёт снизу вверх, т.е. его начало находится по адресу 0FFFFh (и хранится в регистре SP, а точнее— SS:SP, когда наша программа будет состоять не из одного сегмента), а самый конец — в 00000h.

Помещает данные в стек команда push, а возвращает — pop. Например:

...
mov ax,9 ;Заносим в ax число 9
push ax ;Заносим 9 в стек
pop ax ;Вынимаем 9 из стека в ax

Как видим, оперировать мы можем только последним значением в стеке. Однако всё усложняется тем, что стек используем не только мы, но и сам ассемблер. Например, при использовании оператора call туда заносится адрес выхода из подпрограммы. Возьмём программу из урока 5:

.286
CSEG segment
assume cs:CSEG, ds:CSEG, es:CSEG, ss:CSEG
org 100h
begin:
;Всё написанное выше пока опускаем.

jmp exit ;"Прыгаем" на метку exit, не выполняя операторы ниже.

NameProg proc ;Начало нашей подпрограммы.
mov ah,9 ;Загружаем в регистр ah число 9 (указываем функцию).
mov dx,offset helloworld ;Указываем, что за фразу мы будем выводить.
int 21h ;Выводим фразу.
ret
NameProg endp ;Конец подпрограммы.

exit: ;Метка на шаге 2.

call NameProg ;Вызываем подпрограмму вывода, которая выводит фразу.

int 20h ;Выходим в DOS.

helloworld db 'Hello, world!$' ;Определяем переменную helloworld, доступную побайтно, с фразой
;"Hello, world!". В одинарных кавычках, после знака "!" ставим
;знак "$".

;Завершение программы.
CSEG ends
end begin


Запустим отладчик afdpro test.com и посмотрим, как меняется стек. При выполнении оператора call отладчик "прыгает" на адрес mov ah,9, при этом в стек заносится адрес выхода из подпрограммы (используется при выполнении ret). Попробуем воспользоваться этим (жирным шрифтом выделены добавленные строки):

.286
CSEG segment
assume cs:CSEG, ds:CSEG, es:CSEG, ss:CSEG
org 100h
begin:
;Всё написанное выше пока опускаем.

jmp exit ;"Прыгаем" на метку exit, не выполняя операторы ниже.

NameProg proc ;Начало нашей подпрограммы.

pop ax ;Вынимаем из стека значение для ret.
mov ax,offset metka ;Подмениваем адрес возврата на адрес строки mov ah,9
push ax ;Заносим в стек.
metka: ;Метка-адрес для занесения в ax (куда прыгать). Или не
;использовать метку, а вместо строки mov ax,offset metka
;можно было написать mov ax,0107h

mov ah,9 ;Загружаем в регистр ah число 9 (указываем функцию).
mov dx,offset helloworld ;Указываем, что за фразу мы будем выводить.
int 21h ;Выводим фразу.
ret
NameProg endp ;Конец подпрограммы.

exit: ;Метка на шаге 2.

call NameProg ;Вызываем подпрограмму вывода, которая выводит фразу.

int 20h ;Выходим в DOS.

helloworld db 'Hello, world!$' ;Определяем переменную helloworld, доступную побайтно, с фразой
;"Hello, world!". В одинарных кавычках, после знака "!" ставим
;знак "$".

;Завершение программы.
CSEG ends
end begin

Ассемблируем программу: ml test.asm /AT. Запускаем. Вместо одинарного вывода фразы "Hello, world!" фраза выводится два раза! Почему? Понаблюдаем в отладчике: afdpro test.com. Мы видим, как подменивается адрес выхода из подпрограммы, и вместо перехода с ret на int 20 программа снова "прыгает" на mov ah,9 - фраза выводится второй раз. Последнее значение стека команда ret обнуляет, поэтому при следующем заходе в ret программа никуда не "прыгает" и корректно оканчивает свою работу.

Данный пример достаточно простой. Давайте его усложнив, поменяв адрес стека. Возьмём программу из того же урока (выводящую строчку "Hello, world!" 5 раз):

.286
CSEG segment
assume cs:CSEG, ds:CSEG, es:CSEG, ss:CSEG
org 100h
begin:
;Всё написанное выше пока опускаем.
mov cx,5 ;Устанавливаем значение счётчика в 5.

metka:
mov ah,9 ;Загружаем в регистр ah число 9 (указываем функцию).
mov dx,offset helloworld ;Указываем, что за фразу мы будем выводить.
int 21h ;Выводим фразу.
loop metka ;Переходим на метку metka и уменьшаем cx на 1.

int 20h ;Выходим в DOS.

helloworld db 'Hello, world!$' ;Определяем переменную helloworld, доступную побайтно, с фразой
;"Hello, world!". В одинарных кавычках, после знака "!" ставим
;знак "$".

;Завершение программы.
CSEG ends
end begin

И преобразуем к такому виду:

.286
CSEG segment
assume cs:CSEG, ds:CSEG, es:CSEG, ss:CSEG
org 100h
begin:
;Всё написанное выше пока опускаем.

mov sp,offset metka ;Изменяем начало стека на адрес нашей метки metka
mov ax,0009h ;Заносим значение 00009h в ax
push ax ;Помещаем цифру "9" в стек - но стек находится по адресу
;metka, т.е. функции mov ah,9 (сама метка места не занимает),
;таким образом, значение в стеке mov ah,9 смещается,
;а перед ним записывается число 9 - вместо цифры "5"
;в инструкции mov cx,5


mov cx,5 ;Устанавливаем значение счётчика в 5.
;Так было до тех пор, пока мы "на лету" не поменяли
;код программы при помощи стека.

metka:
mov ah,9 ;Загружаем в регистр ah число 9 (указываем функцию).
mov dx,offset helloworld ;Указываем, что за фразу мы будем выводить.
int 21h ;Выводим фразу.
loop metka ;Переходим на метку metka и уменьшаем cx на 1.

int 20h ;Выходим в DOS.

helloworld db 'Hello, world!$' ;Определяем переменную helloworld, доступную побайтно, с фразой
;"Hello, world!". В одинарных кавычках, после знака "!" ставим
;знак "$".

;Завершение программы.
CSEG ends
end begin


Вроде ничего не изменилось, весь код, кроме трёх строк (выделенных жирным шрифтом), сохранён. Но первая программа выведет строчку "Hello, world!" 5 раз, а вторая - 9. Потому что "на лету" мы подменили в самом коде программы цифру "5" на "9". Вот она, сила ассемблера! Такого не могут себе позволить языки высокого уровня.

В избранное