Главная страница

Ассемблер для блондинок (и блондинов). И блондинов


Скачать 0.75 Mb.
НазваниеИ блондинов
АнкорАссемблер для блондинок (и блондинов).pdf
Дата14.12.2021
Размер0.75 Mb.
Формат файлаpdf
Имя файлаАссемблер для блондинок (и блондинов).pdf
ТипПрактическая работа
#303672
страница5 из 6
1   2   3   4   5   6
include console.inc
.code
Start:
mov ebp,esp; запомним указатель стека
inint первое число примем за максимальное
xor ecx,ecx; здесь будем считать количество чисел в стеке
VVOD: ; ввод чисел
outchar '?'
inint esi
cmp esi,0
je PRINT; конец последовательности, можно печатать
cmp esi,eax
jge IZM; число больше или равно max - изменяется максимум
push esi; максимум не изменился, число надо записать в стек
inc ecx; в стеке стало на 1 число больше
jmp VVOD
IZM:
mov eax,esi; изменилось значение максимума
mov esp,ebp; очищаем стек, так как числа мы записали зря, они стоят до максимума
xor ecx,ecx; обнулим счетчик, так как в стеке чисел теперь нет
jmp VVOD
PRINT: ; печатаем числа, в ECX их количество
pop esi; достаем из стека
outint esi,10
loop PRINT
MsgBox "Конец задачи","Повторить программу ?", \
MB_YESNO+MB_ICONQUESTION
cmp eax,IDYES
je Start
exit
end Start Заметим, что в наших программах мы работали с двойными словами (dd), именно таков тип операндов у стековых команд. Если надо работать с числами меньшего размера, писать в стеки брать из стека все равно следует использовать двойные слова, расширяя исходные числа до 4 байт. Использование стека
1. Вышеприведенная программа, конечно, носит демонстрационный характер – не так часто приходится печатать последовательность после максимума, но, тем не менее, одно из предназначений стека – временное хранение последовательности данных, бывают задачи, где он для этого используется.
2. Присваивание значения переменной (копирование из памяти в память. Действие X:=Y, где оба числа – из памяти, в ассемблере приходится делать с использованием регистра. Можно это сделать с помощью стека
push Y
pop X

3. Поменять местами значения переменных. Команд столько же, но иногда не хочется портить регистры. Сохранить, а потом восстановить регистр флагов – если надо использовать ранее полученные значения флагов
5. Сохранить, а потом восстановить значения регистров – по понятным причинам.
6. При работе с вложенными циклами количество повторений циклов удобно сохранять в стеке, так как эти числа надо извлекать в порядке обратном тому, в котором клали.
7. Иногда в стеке удобно сохранять сравнительно большие массивы информации, необходимой для работы программы (вместо того, чтобы отводить место под такой массив стандартным образом в памяти. Посмотрим такую работу со стеком на примере, где массив нужен для хранения результатов подсчета результата. Похожий пример был рассмотрен выше при работе с массивами. Задача. Вводится текст сточкой в конце. Напечатать количество вхождений каждой маленькой латинской буквы. Для подсчета создадим в стеке массив – вначале запишем в стек 26 (столько латинских букв) нулей. Введя очередную букву, определим ее номер в алфавите и увеличим на 1 соответствующую компоненту в стеке. Доступ к компонентам стека при этом будем осуществлять с помощью модификатора ESP – будем использовать как базу, то есть вершина стека является началом массива, поэтому адресу, в [ESP] хранится количество вхождений буквы 'a'. Количество вхождений буквы 'b' хранится в следующей компоненте стека, по адресу [ESP+4] (изменение адреса на 4 потому, что в стеке хранятся двойные слова. Для нахождения нужного адреса в стеке будем использовать модификатор EAX, в котором будем хранить номер буквы в алфавите.
include console.inc
.code
Start:
outstrln "Вводите текст сточкой в конце"
mov ecx,26
Obnul: ; обнуление 26 позиций в стеке
push 0
loop Obnul vvod: ввод строки символов и обработка
inchar al
cmp al,'.'; строка заканчивается точкой
je kon
cmp al,'a'
jb vvod; не буква
cmp al,'z'
ja vvod; не буква
; сюда попали только если была введена маленькая буква
sub al,'a'; получили номер буквы
inc dword ptr[esp+4*eax]
; в стеке хранятся двойные слова, поэтому умножаем на 4
jmp vvod kon:
outstrln "печать"
mov al,'a' print:

outchar al; очередная буква
pop ebx; забираем из стека соответствующее ей число
outword ebx,10,' - '
inc al; переходим к следующей букве
cmp al,'z'
jbe print
newline
exit
end Start Аккуратность при работе со стеком Стек используется для разных целей, иногда его использование "явно" невидно. Например, макрокоманды ввода-вывода активно пользуются стеком, на стеке основана работа процедур. Поэтому при небрежной работе со стеком можно непросто получить неправильные числа в ответе, а "подвесить" программу, так как она "не туда уйдет. Главное правило – правило подводника количество погружений равно количеству всплытий – сколько положили, равно столько и достали. Теперь, когда мы знаем, каково предназначение регистра ESP, четко повторим и поясним правило, высказанное ранее. Кстати, название этого регистра происходит от словосочетания Stack Pointer – указатель (вершины) стека. Правила языка не запрещают изменять его значение, использовать его в арифметических командах. Однако следует понимать, что все изменения значения этого регистра приводят к изменению положения вершины стека (если что-то положить в стека потом изменить значениe ESP, то команда pop уже вытащит что-то другое. Если пользуемся содержимым стека как массивом, также важно правильно этот массив в стек положить и вовремя его оттуда вытащить. Немного об очистке стека. При чтении из стека информации командой pop число, которое было на вершине стека, из стека, конечно жене стирается, оно там остается, изменяется вершина стека, число оказывается в "незанятой" памяти. Оно может быть стерто (изменено, когда на это место что-то в стек положат. Не надо надеяться, что это число останется в неизменном состоянии до тех пор, пока не будет выполнена явно прописанная в программе команда push. Cтеком пользуются макрокоманды, он используется при работе процедур, да и сам компьютер может писать туда свои данные. Поэтому, например, любой ввод-вывод испортит то, что раньше "числилось" в стеке.
14. Процедуры Несколько примеров разных процедур с разными видами передачи параметров. Работа с массивом. Передача параметров через регистры
include console.inc
comment *
Процедуры работы с массивами
*
.data
N equ 10
X dd N dup (?)
Y dd (N/2) dup (99); N/2=N div 2
.code
VVMAS proc
; ввод массива, передача параметров через регистры
; ebx - адрес начала, ecx - количество
outstr 'Ввод массива. Количество элементов'
outwordln ecx
@VV:
inint dword ptr[ebx]
add ebx,4

loop @VV; метка "необычная, чтобы в программе не было такой же
ret
VVMAS endp
; ==================================
PRMAS proc
; вывод массива, передача параметров через регистры
; ebx - адрес начала, ecx - количество
newline
PP:
outint dword ptr[ebx],10
add ebx,4
loop PP
newline
ret
PRMAS endp
; ==================================
Start:
ClrScr
ConsoleTitle " Массивы. Процедуры. Передача параметров через стек"
; перед вызовом процедуры надо в регистры занести нужные значения
mov ebx,offset X; адрес X
mov ecx,N
call VVMAS
lea ebx,X
mov ecx,N
call PRMAS
mov ebx,offset Y; адрес Y
mov ecx,N/2
call PRMAS Передача параметров через стек. Написать процедуру нахождения максимального из двух чисел. Параметры в стеке, результат – в регистре. Применить эту процедуру дважды, чтобы найти максимальное из трех чисел.
include console.inc
comment * eax:=MAX(A,B) - параметры в стеке
*
.data
G dd 23
M dd 56
P dd 67
.code
Max proc; параметры в стеке, ответ в eax
; пролог
push ebp
mov ebp,esp
push ebx; сохраняем используемые регистры
mov ebx,[ebp+8]; первый параметр
mov eax,[ebp+12]; второй параметр
cmp ebx,eax
jbe @F; это метка без имени – переход на ближайшую метку @@ вперед
mov eax,ebx
@@:

; эпилог
pop ebx
pop ebp; убрали из стека все, что туда клали с помощью PUSH
ret 2*4; убрали из стека 2 параметра
Max endp
Start:
push 123
; положим в стек это число, чтобы проверить, всели из него убрали в процедуре
push G
push M
call Max; два числа в стеке, вызываем процедуру
outwordln eax; это максимум первых двух чисел
push eax
push P; максимум и третье число кладем в стек
call Max
outwordln eax; теперь это максимум всех трех чисел
pop eax ; проверяем – то ли в стеке, что мы положили вначале должно быть 123
exit
end Start Вводи вывод массива – передача параметров через стек
include console.inc
comment *
Процедуры работы с массивами
*
.data
N equ 5
X dd N dup (?)
Y dd N/2 dup (99)
.code
VVMAS proc
; procedure VVMAS(var X:Mas; N:Longword);
; ввод массива, передача параметров через стек,
; внизу - количество, выше - адрес начала
push ebp
mov ebp,esp
push ecx
push ebx
mov ebx,[ebp+8]
mov ecx,[ebp+12]
outstr 'Ввод массива. Количество элементов'
outwordln ecx
@@:
inint dword ptr[ebx]
add ebx,4
loop @B; на ближайшую @@: назад
newline
pop ebx
pop ecx
pop ebp
ret 2*4
VVMAS endp
; =======================

PRMAS proc
; вывод массива, передача параметров через стек, работа на
; регистрах ebx - адрес начала, ecx - количество
push ebp
mov ebp,esp
push ecx
push ebx
mov ebx,[ebp+8]
mov ecx,[ebp+12]
PP:
outint dword ptr[ebx],10
add ebx,4
loop PP
newline
pop ebx
pop ecx
pop ebp
ret 2*4
PRMAS endp
; ===================
Start:
ClrScr
ConsoleTitle " Массивы. Процедуры. Передача параметров через стек"
push N; Сначала второй параметр
push offset X; Потом первый параметр
call VVMAS
push N
push offset X
call PRMAS
outstrln 'Печать закончена'
push N/2
push offset Y
call PRMAS
exit
end Start Вспомним, что передача параметров в Паскале бывает по значению и по ссылке (параметры- значения и параметры-переменные, си без var). Пришла пора узнать, что это такое на самом низком уровне. Параметры-значения – это то, что мы кладем в регистры или в стек в виде значений. В программах с массивом – это количество элементов в массиве (кладем в стек или в регистр число, в программе с максимум оба параметра – значения. Параметром-переменной является массив, через регистр или через стек передается адрес массива. Здесь мы видим, что если бы мы вдруг захотели передать массив по значению, пришлось бы записывать в стек все числа массива, а при передаче параметра по адресу мы сообщаем процедуре адрес начала массива иона уже обращается, зная этот адрес, непосредственно к массиву в памяти. Передача параметров по ссылке также имеет место, когда этот параметр является результатом. Напишем процедуру на Паскале стремя параметрами, которая реализует действия C:=max(A,B).
procedure max(A,B: Longint; var C: Longint);
begin if A>B then C:=A
else C:=B
end.
Обратите внимание процедура непросто находит максимум двух чисел (раньше мы писали функцию, которая находила максимум и возвращала его в регистре. Процедура изменяет значение переменной, кладет найденное значение в память по указанному адресу. Передадим параметры через регистры. Первые 2 параметра передаются по значению, третий – по ссылке, то есть в регистр надо положить его адрес. При работе с этим параметром надо будет работать нес регистром, ас адресом, который в этом регистре лежит, то есть брать регистр в квадратные скобочки
include console.inc
.data
A1 dd 34
B1 dd 45
C1 dd 56
C2 dd 12
.code
MAXADR proc; C:=max(A,B)
; параметры передаются через регистры A - EAX,
; B - EBX по значению,С - ESI - по адресу)
cmp eax,ebx jg MET
mov [esi],ebx
; пересылаем значение не в сам регистра по адресу, который в этом регистре лежит
jmp KON
MET:
mov [esi],eax
KON: ret
MAXADR endp
Start:
mov eax,A1
mov ebx,B1; в регистры заносим параметры-значения
mov esi,offset C1; параметр передается по ссылке, в регистр кладем адрес
call MAXADR; C1:=max(A1,B1)
outintln C1; проверяем - значение в памяти изменилось
mov eax,C1
mov ebx,C2; в регистры заносим параметры-значения
mov esi,offset параметр передается по ссылке, в регистр кладем адрес
call MAXADR; C2:=max(C1, C2)
outintln C2; проверяем - значение в памяти изменилось
exit
end Start Перепишем эту процедуру, чтобы все параметры передавались через стек – тогда она будет записана с учетом стандартных соглашений о связях.
include console.inc
.data
A1 dd 34
B1 dd 45
C1 dd 56
C2 dd 12
.code
MAXADR proc
; параметры передаются через стек
push ebp
mov ebp,esp

push eax
push ebx
push esi
mov eax,[ebp+8]; первый параметр A
mov ebx,[ebp+12]; второй параметр B
mov esi,[ebp+16]; адрес C, куда надо класть ответ
cmp eax,ebx
jg MET
mov [esi],ebx; C:=max
jmp KON
MET:
mov [esi],eax; C:=max
KON:
pop esi
pop ebx
pop eax
pop ebp
ret 4
MAXADR endp
Start:
; procedure MAXADR(A,B:Longint; var C:Longint);
push offset C1; сначала третий параметр
push B1; теперь второй параметр
push A1; теперь первый параметр
call MAXADR
outintln C1; проверяем - значение в памяти изменилось
; А теперь C2:=max(C1,C2)
push offset C2; 3-ий
push C2; ой
push C1; 1-ый
call MAXADR; C2:=max(C1,C2)
outintln C2; проверяем - значение в памяти изменилось
exit
end Start Сохранение параметров в стеке Если процедуре в процессе работы нужно что-то хранить в памяти (локальные переменные, эти данные она может сохранять в стеке. Задача. Мы писали программу, которая подсчитывала, сколько раз буква входит в текст, и данные сохраняли в стеке. Опишем эти действия в виде процедуры. Пусть параметрами процедуры будут 2 символа начало и конец промежутка, в котором процедура она будет подсчитывать символы (программа подсчитывала латинские буквы, процедуре можно задавать, что считать, иначе какая же это процедура. Свои локальные данные процедура будет содержать в стеке, можно увидеть, что работа со стеком внутри процедуры практически ничем не отличается от работы со стеком внутри программы. Переменную и массив называть никак не будем, будем обращаться к ним по адресам.
include console.inc
comment * Параметры процедуры два символа (передаются водном двойном слове через стек. Если код первого символа больше кода второго – процедура ничего не делает. Иначе процедура вводит текст (до точки) и печатает, сколько разв текст входит каждый символ из заданного диапазона. В процедуре используется локальная переменная
для хранения длины заданного диапазона и локальный массив длина массива зависит от заданных параметров)
*
.code
; type P=packed array[1..4] of char;
; procedure Letter(x:P); {x='c1c2#0#0'}
Letter proc
push ebp
mov ebp,esp; стандартные входные действия процедуры (пролог)
sub esp,4; локальная переменная для хранения длины диапазона
push eax; сохранение регистров
push ebx
push ecx
push edx
push edi
mov edx,[ebp+8]; читаем параметр из стека
outstr 'Выбран диапазон '
outchar dl
outcharln dh,,"–"
xor ecx,ecx
mov cl,dh
sub cl,dl
jl @kon; результат <0 - делать ничего не надо
add ecx,1; вычислена длина исследуемого диапазона
outintln длина'
mov [ebp-4],ecx; сохранили ее в локальной переменной в стеке
newline
@@:
push 0; запись в стек ecx нулей - счетчики для символов
loop @B
xor ebx,ebx ; регистр для работы с массивом
outstrln 'Вводите текст сточкой на конце'
@@: начало ввода
inchar bl; ввели очередной символ
cmp bl,'.'
je @F; наметку "обработка содержимого стека"
cmp bl,dl
1   2   3   4   5   6


написать администратору сайта