Программирование на языке ассемблера
.pdfmov |
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