Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Программирование на языке ассемблера

.pdf
Скачиваний:
79
Добавлен:
08.05.2015
Размер:
1.66 Mб
Скачать

mov

x, 1

mov

eax, 1

 

mov

x, eax

mov

[ebx], 1

mov

eax, 1

 

mov

[ebx], eax

Аналогично команды PUSH и POP, работающие с ячейкой памяти, можно заменить парой команд MOV + PUSH или POP + MOV.

push

x

mov

eax, x

 

push

eax

 

pop

x

pop

eax

 

mov

x, eax

8.3.4. Выравнивание

80-битные данные должны быть выравнены по 16-байтным границам (то есть четыре младших бита адреса должны быть равны нулю).

Восьмибайтные данные должны быть выравнены по восьмибайтным границам (то есть три младших бита адреса должны быть равны нулю).

Четырёхбайтные данные должны быть выравнены по границе двойного слова (то есть два младших бита адреса должны быть равны нулю).

Двухбайтные данные должны быть выравнены по границе слова.

Метки для переходов, особенно метки, отмечающие начало цикла, должны быть выравнены по 16-байтным границам.

Каждое невыравненное обращение к данным означает потерю тактов процессора.

Для выравнивания данных и кода используется директива ALIGN:

ALIGN <число>

Число должно быть степенью двойки. Данные и команда, расположенные после директивы ALIGN, будут размещены по адресу, кратному указанному числу.

9. Примеры

1.Процедура вычисления наибольшего общего делителя двух беззнаковых чисел. Для нахождения НОД используется алгоритм Евклида: пока числа не равны, надо вычитать из большего числа меньшее. Процедура получает параметры через регистры EAX и EDX и возвращает результат через регистр EAX.

NOD proc

 

 

 

 

 

 

N1: cmp

eax, edx

; Сравниваем числа

 

 

je

N3

;

Если

числа равны,

завершаем

работу процедуры

 

 

 

 

 

ja

N2

;

Если

первое число

больше,

обходим обмен

 

 

 

 

 

 

 

 

; Поскольку

команды перехода

не

меняют

флаги, оба перехода

 

 

 

 

 

;выполняются или не выполняются по

результатам одного сравнения

 

 

 

xchg

eax, edx

;

Если первое число

было

меньше, выполняем обмен

 

 

 

N2: sub

eax, edx

;

Вычитаем из большего

числа

меньшее

 

 

 

 

jmp

N1

; Переход к началу цикла

 

N3: ret

 

 

 

 

NOD endp

 

 

 

 

2.Ввод и вывод в консольном приложении. В программе используются следующие функции Win32 API.

SetConsoleTitle – меняет заголовок окна консоли. Получает один параметр – указатель на строку, которая будет выведена в заголовке. Строка должна заканчиваться нулём.

GetStrHandle – возвращает идентификатор устройства ввода, устройства вывода или устройства отчёта об ошибках.

Для консольного приложения всё три устройства являются консолью, но идентификаторы будут разными. Функция получает один параметр – указание, идентификатор какого устройства нужно вернуть. Чтобы получить идентификатор устройства ввода, надо передать функции число -10, чтобы получить идентификатор устройства вывода – число -11, а чтобы получить идентификатор

устройства отчёта об ошибках – число -12. Функция возвращает требуемый идентификатор через регистр EAX.

WriteConsole – выводит строку в консоль. Получает следующие параметры – идентификатор устройства вывода, адрес выводимой строки, количество символов для вывода, адрес переменной, куда будет записано количество выведенных символов, зарезервированный указатель.

ReadConsole – вводит строку из консоли. Получает следующие параметры – идентификатор устройства ввода, адрес памяти, куда будет записана введённая строка, максимальное количество читаемых символов, адрес переменной, куда будет записано реальное количество введённых символов, зарезервированный указатель.

Не забывайте, что параметры кладутся в стек, начиная с последнего, и что введённая строка всегда будет содержать в конце символы с кодами 13 и 10, которые появляются при нажатии на клавишу ВВОД (без чего, однако, ввод не завершится).

.686

.model flat, c option casemap: none

include \masm32\include\windows.inc include \masm32\include\kernel32.inc includelib \masm32\lib\kernel32.lib

.data

str db 256 dup(0) hStdIn dd 0

hStdOut dd 0 slength dd 0

.const

 

 

 

sConsoleTitle db 'Input and Output',0

;

Заголовок

окна

консоли. Заканчивается нулём

 

 

 

prompt db 'Input a string', 13,10

; Приглашение для

ввода. Символы с кодами 13 и 10

 

; обеспечивают перевод курсора на

следующую строку

STD_INPUT_HANDLE equ -10d

;

 

Определяем

символические имена для констант,

 

 

 

STD_OUTPUT_HANDLE equ -11d

;

указывающих

требуемое

устройство

 

 

 

.code

program:

; Вывод заголовка консоли

push

offset sConsoleTitle

;

Кладём в стек адрес

начала строки заголовка консоли

 

 

call

SetConsoleTitle

;

Вызываем функцию

; Получаем идентификатор устройства ввода

push

STD_INPUT_HANDLE

;

Кладём в стек параметр

функции GetStdHandle

 

 

call

GetStdHandle

; Вызываем функцию

mov

hStdIn, eax

;

Сохраняем полученный

идентификатор

; Получаем идентификатор устройства вывода

push STD_OUTPUT_HANDLE

call GetStdHandle

mov hStdOut, eax

; Выводим приглашение

push

0

;

Зарезервированный

параметр,

в стек кладём 0

 

 

 

 

push

0

; Указатель на переменную для

записи количества выведенных символов,

 

 

 

 

; в данном

случае не нужен, поэтому

в стек кладём 0

 

 

 

 

push

10h

 

;

Количество

выводимых

символов

 

 

 

 

 

push

offset prompt

 

; Адрес выводимой строки

push

hStdOut

;

Идентификатор

устройства

вывода

 

 

 

 

 

call

WriteConsole

; Вызываем функцию

 

; Вводим строку

 

 

 

 

 

 

push

0

;

Зарезервированный

 

параметр,

в стек кладём 0

 

 

 

 

 

 

push

offset slength

 

; Адрес переменной, куда

будет записано количество введённых символов

 

 

 

push

256

 

;

Максимальное

количество

вводимых символов

 

 

 

 

 

 

push

offset str

 

;

Адрес

для

записи

введённой строки

 

 

 

 

 

 

push

hStdIn

;

Идентификатор

 

устройства

ввода

 

 

 

 

 

 

 

call

ReadConsole

 

; Вызываем функцию

; Выводим строку

push 0

push 0

push slength

push offset str

push hStdOut

call WriteConsole

; Задержка

push 1800h call Sleep

push 0

call ExitProcess end program

3.Процедура ввода целого числа в 16-ричной системе счисления. Процедура предназначена для использования в консольном приложении и предполагает, что идентификатор устройства ввода был получен основной программой и сохранён в переменной hStdIn.

InputNumber

proc

 

 

 

 

 

 

push

ebp

 

 

;

Сохраняем

в

стеке

значение регистра EBP

 

 

 

 

 

mov

ebp,

esp

 

;

Заносим в

регистр EBP

текущее значение

вершины стека

 

 

 

 

 

sub

esp,

16

;

Резервируем

16

байт.

Вводимая строка может содержать до 8 цифр.

 

 

 

 

 

; 2 байта требуются для символов с

кодами 13 и

10. Итого 10 байт.

 

 

 

 

 

; 4 байта нужно для целочисленной переменной, куда будет записываться количество

;введённых символов. Итого 14

байт. Но выделим 16 байт, т.е. 4 двойных слова

push

ebx

;

Сохраняем

значения

важных регистров

 

 

 

push

esi

 

 

 

; Вводим строку

push

0

 

 

 

lea

eax, [ebp - 16]

; 4 байта по адресу

[EBP – 16]

предназначены для

хранения

количества

введённых

символов

 

 

 

 

push

eax

 

 

 

push

10d

 

 

 

lea

eax, [ebp - 12]

;

По адресу

[EBP – 12]

начинается память для вводимой строки

push eax

push hStdIn

call ReadConsole

; Преобразуем строку в число

 

 

 

 

 

xor

eax, eax

;

Обнуляем

 

регистр

EAX

...

 

 

 

 

 

 

xor

ebx, ebx

; ... и регистр EBX

 

mov

ecx, [ebp - 16]

;

Заносим

в

регистр

ECX

количество введённых символов

 

 

 

 

 

sub

ecx, 2

; Символы с кодами 13 и

10

обрабатывать не надо

 

 

 

 

 

lea

esi, [ebp - 12]

;

Заносим

в

регистр

ESI

адрес начала строки

 

 

 

 

 

test

ecx,

ecx

;

Используем

команду

TEST

для сравнения с нулём

 

 

 

 

 

 

jz

L2

 

; Если

ECX

= 0,

то завершаем

работу процедуры

 

 

 

 

 

 

 

L1: mov

bl, [esi]

;

Заносим

в

регистр BL

текущий символ (три старших байта EBX

 

 

 

 

 

 

 

;

содержат

0,

т.к.

ранее

была

команда XOR

EBX,

EBX)

 

 

 

 

 

 

lea

edx,

[ebx - '0']

;

Заносим

в

регистр

EDX

разность между кодом текущего символа и кодом символа '0'

 

cmp

edx, 9

;

Сравниваем

 

значение

в

регистре EDX с 9

 

 

 

 

 

 

 

ja

M1

;

Если

выше,

то

переходим

к

следующему сравнению

 

 

 

 

 

 

 

sub

bl, '0'

;

Иначе

получаем число

из кода

символа

 

 

 

 

 

 

 

 

jmp

M3

;

Переходим

к

действиям,

учитывающим

текущую цифру

 

 

 

 

 

 

 

M1: lea

edx, [ebx - 'a']

 

; Заносим в регистр EDX

разность между кодом текущего символа и кодом символа 'a'

 

 

cmp

edx, 'f' - 'a'

 

;

Сравниваем

значение

в

регистре EDX с 5

 

 

 

 

 

 

 

ja

M2

;

Если

выше,

то

переходим

к

следующему сравнению

 

 

 

 

 

 

 

sub

bl, 'a' - 10d

 

;

Иначе получаем

число

из

кода символа

 

 

 

 

 

 

 

 

jmp

M3

;

Переходим

к

действиям,

учитывающим

текущую цифру

 

 

 

 

 

 

 

M2: lea

edx, [ebx - 'A']

 

; Заносим в регистр EDX

разность между кодом текущего символа и кодом символа 'A'

 

 

cmp

edx, 'F' - 'A'

 

;

Сравниваем

значение

в

регистре EDX с 5

 

 

 

 

 

 

 

ja

L2

; Если выше, то завершаем

процедуру. Результат не определён,

 

 

 

 

 

 

 

;

т.к. был введён

некорректный

символ

 

 

 

 

 

 

 

 

sub

bl, 'A' - 10d

 

;

Иначе получаем

число

из

кода символа

 

 

 

 

 

 

 

 

M3: sal

eax, 4

; Умножаем EAX на 16

 

 

add

eax, ebx

 

;

Прибавляем

текущую

цифру

 

 

 

 

 

 

 

 

inc

esi

 

;

Переходим

к следующему

символу

 

 

 

 

 

 

 

 

dec

ecx

 

; Уменьшаем ECX на 1

 

jnz L1

; Если ECX не равно 0,

продолжаем цикл

 

L2: pop

esi

;

Восстанавливаем

значения использовавшихся регистров

 

 

pop

ebx

 

 

mov

esp, ebp

; Освобождаем стек

pop

ebp

;

Восстанавливаем

значение регистра EBP

ret

 

 

 

 

 

InputNumber endp

 

 

 

 

 

4. Процедура

вывода

числа

в

16-ричной

системе

счисления. Процедура

получает

один

параметр –

выводимое

число. Для вывода всегда формируется строка из 8-ми шестнадцатеричных цифр с лидирующими нулями. Поскольку количество символов заранее известно, они будут сразу же записываться в строку с конца, и инвертировать строку не придётся. Процедура предназначена для использования в консольном приложении и предполагает, что идентификатор устройства ввода был получен основной программой и сохранён в переменной hStdOut.

digits db '0123456789abcdef'

;

Массив

шестнадцатеричных цифр

 

 

OutputNumber proc

 

 

 

 

 

 

 

push

ebp

 

;

Сохраняем

в

стеке

значение регистра EBP

 

 

 

 

 

 

mov

ebp,

esp

;

Заносим

в

регистр

EBP

текущее значение

вершины стека

 

 

 

 

 

 

sub

esp,

12

; Выделяем в

стеке место

под

формируемую

строку

 

 

 

 

 

 

push

esi

 

 

 

 

 

 

 

; Преобразуем число в строку

mov

eax,

[ebp + 8]

 

;

Заносим

в

регистр

EAX

переданный параметр

 

 

 

 

 

 

 

 

mov

ecx,

8

;

Заносим

в

регистр

ECX

количество символов строки

 

 

 

 

 

 

 

 

mov

byte

ptr [ebp - 1], 10

 

;

Добавляем

в

конец

строки символы с

кодами 13 и 10 для перевода курсора

 

 

 

mov

byte

ptr [ebp - 2], 13

 

 

 

 

 

 

 

 

lea

esi,

[ebp - 3]

 

;

Начиная

с адреса

[EBP -

3] будут заносится цифры

 

 

 

 

 

 

 

 

L3: mov

edx,

eax

 

;

Копируем

значение

регистра EAX в регистр EDX

 

 

 

 

 

 

 

 

and

edx,

1111b

 

;

Получаем

остаток

от

деления на 16

 

 

 

 

 

 

 

 

 

shr

eax,

4

; Делим исходное число на 16

mov

dl, digits[edx]

 

;

По

полученному

остатку

от деления берём

цифру ...

 

 

 

 

 

 

 

 

mov

[esi], dl

 

;

...

и записываем

её в

строку

 

 

 

 

 

 

 

 

 

 

dec

esi

 

 

;

Уменьшаем

адрес,

т.к.

строка формируется с конца

 

 

 

 

 

 

 

 

dec

ecx

 

 

; Уменьшаем ECX на 1

 

jnz

L3

 

;

Если ECX

не

равно

0,

продолжаем цикл

; Выводим строку

 

inc

esi

; Регистр ESI указывает

на начало

строки

 

push 0

push 0

push 10

push esi

push hStdOut