Assembler / P15
.pdf11
COMMENT &
0. Текст для шифрования вводится в командной строке. В тексте — только прописные латинские буквы и пробелы. Удалить пробелы и зашифровать текст. Каждый символ преобразуется по формуле 3*код+5 (mod 26). При этом считается, что код буквы 'A' равен 0, код буквы 'B' равен 1 и т.д.
&
.MODEL small
.STACK 100h
.DATA
text DB 83 DUP(?)
msg1 DB "В командной строке нет текста", 0Dh, 0Ah, '$' msg2 DB "В тексте недопустимые символы", 0Dh, 0Ah, '$'
.CODE ;---------------------------------------------------
;Подпрограмма шифровки символа
;Вход: AL - код символа
;Выход: AL - код зашифрованного символа
;----------------------------------------------------
encode PROC
cmp al,'A' ; Если код символа не ниже кода 'A' jnae cf1
cmp al,'Z' ; и не выше кода 'Z', jnbe cf1
sub al,'A' ; Получить код символа, начиная с 0
;Вычисление нового кода по формуле 3*код+5 (mod 26) mov bl,3
mul bl add ax,5 mov bl,26 idiv bl
add ah,'A' |
; Вернуться к коду ASCII |
|
mov al,ah |
|
|
clc |
; |
нормальное завершение |
ret |
|
|
cf1: stc |
; на входе символ, отличный от прописной латинской буквы |
|
ret |
|
|
encode ENDP |
|
|
start: mov ax, @data mov ds, ax
; Копирование цифр из хвоста командной строки в строку для обработки mov cx, 0
mov cl, es:[80h] ; Длина хвоста командной строки в CX
jcxz empty |
; Если хвост пустой – на empty |
dec cx |
; Не нужно учитывать начальный пробел |
mov si, 82h |
; Смещение хвоста – в SI |
mov di, OFFSET text ; Указатель для новой строки – в DI
12
; Цикл обработки элементов строки
n: mov al, es:[si] ; Очередной символ хвоста – в AL cmp al, ' ' ; Если встретился пробел,
je cont ; то пропустить его,
call encode ; а символ - зашифровать
jc no_A_Z ; Переход, если встретился недопустимый символ
mov [di],al ; Поместить зашифрованный символ в строку-приемник inc di ; Переместить указатель в строке-приемнике
cont: inc si ; Переместить указатель в исходной строке loop n
;
;Поместить в строку-приемник символы ВК и ПС, а также терминатор mov word ptr [di], 0A0Dh
inc di inc di
mov byte ptr [di], '$'
;Вывод итоговой строки на экран
mov dx, OFFSET text mov ah, 9h
int 21h
jmp short fin
; Вывод сообщений об ошибках
empty: mov dx, OFFSET msg1 ; строка пуста mov ah, 9h
int 21h
jmp short fin
no_A_Z: mov dx, OFFSET msg2 ; в строке недопустимые символы mov ah, 9h
int 21h
; Завершение работы программы fin: mov ax, 4C00h
int 21h END start
Распечатка этого файла составляет содержание отчета. Текст программы обязательно должен включать подробные комментарии.
Отметим некоторые особенности в тексте программы.
1.Программа открывается блоком комментариев, начинающихся с директивы COMMENT. Сами комментарии должны ограничиваться парой одинаковых символов. В нашем случае выбран &, так как этот символ не встречается внутри самих комментариев.
2.Директивы Ассемблера (PROC, DB и т.д.) и операции времени трансляции (OFFSET) выделяем прописными буквами для лучшей читабельности текста программы.
3.Вместо числовых кодов букв (например, 41h) используем их символьные обозначения ('A').
13
15.12.3. Трансляция, компоновка и выполнение программы. Трансляция программы
tasm/zi/l d1v0
(ключ /zi — включение отладочной информации, /l — получение файла листинга). На выходе файлы a6v0.obj и a6v0.lst.
Компоновка tlink /v d1v0
(ключ /v — включение отладочной информации). На выходе файлы a6v0.exe и a6v0.map.
Запуск на выполнение
D:\>d1v0
Вкомандной строке нет текста
D:\>d1v0 Aa1
Втексте недопустимые символы
D:\>d1v0 NEW LINE SRTMDSR
15.12.4. Отладка
D:\>td d1v0.exe NEW LINE
(расширение .exe нужно обязательно указывать).
Закроем окна Module (Alt+F3) и Watch (Alt+F3). Создадим окно CPU (F10 → View → CPU). «Распахнем» его (F5). Последовательными нажатиями Ctrl+M добьемся, чтобы в панели кода отображались и строки исходного текста программы и сгенерированный код:
#d1v0#start: start: mov ax, @data cs:001E B82A57 mov ax,572A
Мы видим, что встроенная переменная Ассемблера @data на этапе выполнения получила значение 572Ah.
Перейдем в панель данных (Shift+Tab). Отобразим хвост командной строки
(Alt+F10 → Goto → 80).
Уменьшим размер окна CPU и отобразим окно данных (F10 → View → Dump), расположив его ниже окна CPU. В окне Dump перейдем к адресу text (Alt+F10 → Goto → text). Обратите внимание, теперь мы набираем не конкретный адрес, а его символическое обозначение.
Переходим в окно CPU (F6). Выполняем программу по шагам, чтобы проследить правильность ее выполнения. (Никогда не запускайте программу на языке Ассемблера сразу на выполнение! Наверняка в ней есть ошибки.) Для этого нажимаем F7, если нужно войти в подпрограмму, или F8, если подпрограмму нужно выполнить как одну команду.
Проследите, как изменяется содержимое стека при вызове подпрограммы и возврате в главную программу.
Посмотреть вывод на экран зашифрованной строки можно, нажав комбинацию Alt+F5. "Вернуться обратно" — также Alt+F5.
Если мы хотим испытать работу программы с другими исходными данными, нет необходимости выходить из отладчика и заново вызывать его. В главном меню: F10 → Run → Arguments. Появляется диалоговое окно Enter command line arguments. Введем, например, WEB (начальный пробел вводить не
14
нужно, он появится автоматически). Отладчик задает вопрос: Reload program so arguments take effect? (Заново загрузить программу, чтобы аргументы возымели эффект?). Разумеется, отвечаем Yes (нажимаем Enter). Чтобы в панели кода перейти к первой команде нашей программы, набираем Ctrl+G → start (опятьтаки обратите внимание: мы набираем не адрес, а его символическое имя).
Выход из отладчика — Alt+X.
Для проверки правильности самих вычислений нам поможет отладчик. Вычислим с помощью отладчика, в какой символ перейдет буква 'N'. Для этого откроем окно Watches (F10 → View → Watches) и введем в него выражение:
(('N'–'A')*3+5) mod 26d+'A',c
Здесь после запятой указан символ форматирования c, являющийся сокращением от слова character — символ. В окне появится выражение и результат его вычисления:
(('T'–'A')*3+5) mod 26d+'A',c 'S'
Обратите внимание на суффикс d у числа 26. При отладке программы на языке Ассемблера числа по умолчанию рассматриваются как 16-ричные.
Для выполнения таких же вычислений с буквой 'E' выделите уже введенное выражение и в контекстном меню выберите пункт Edit (редактирование).
15.13. Командный файл для трансляции, компоновки и отладки.
Для того чтобы не набирать каждый раз похожие команды, полезно создать командный файл. Его текст может быть, например таким.
1.bat
c:\tasm\bin\tasm /l/zi %1 IF ERRORLEVEL 1 GOTO exit c:\tasm\bin\tlink /v %1 pause
c:\tasm\bin\td %1.exe :exit
Для программы d1v0.asm вызов командного файла имеет вид
1 d1v0
Расширение файла (asm) не указывается. Если вызвать командный файл так:
1 d1v0.asm
то будет сгенерирована команда c:\tasm\bin\tlink /v d1v0.asm
вместо правильной c:\tasm\bin\tlink /v d1v0.obj
Но расширение файла при использовании tasm и tlink можно не указывать.
Авот для Turbo Debugger это расширение (exe) нужно указать обязательно.
Всистемную переменную ERRORLEVEL записывается код возврата программы tasm. Вторую строку командного файла следует читать так:
IF ERRORLEVEL >= 1 GOTO exit
но знак >= опускается. Если tasm не выдал сообщения об ошибках, то его код возврата равен нулю и происходит вызов компоновщика. Если же в процессе трансляции обнаружены ошибки, то код возврата равен 1. Происходит досрочный выход из командного файла (переход на метку exit).
Зачем в файле 1.bat размещена команда pause? Если при трансляции будут предупреждения (warnings), то прочитать их мы не успеем, так как Turbo
15
Debugger ―заслонит‖ экран с сообщениями. Сначала мы изучаем сообщения tasm и tlink и нажимаем на любую клавишу, чтобы вызвать Turbo Debugger.
15.14. Средства языка Ассемблера.
15.14.1. Переменные и константы времени ассемблирования.
В программе на языке Ассемблера можно использовать переменные времени ассемблирования. Память под них не выделяется. Они используются в вычислениях на этапе ассемблирования, и в исполняемом файле никак не фигурируют.
Вот простой пример. Нам нужно задать четыре массива слов, заполненных нулями. Размер первого массива равен N, размер второго — N + 2, размер третьего — 2N – 1, четвертого — остаток от деления N на 3. Решение при N = 5:
.DATA
DW 5 DUP(0)
DW 7 DUP(0)
DW 9 DUP(0)
DW 2 DUP(0)
Если возникнет необходимость изменить число N, то размеры массивов придется заново вычислять вручную. Хотелось бы в программе задавать число N, а размеры массивов вычислять автоматически. Решение:
|
.DATA |
|
N = 5 |
|
|
m1 |
DW |
N DUP(0) |
m2 |
DW |
N+2 DUP(0) |
m3 |
DW |
2*N-1 DUP(0) |
m4 |
DW |
N MOD 3 DUP(0) |
…
|
Если в программе будут часто использоваться эти размеры, то им можно |
||
дать свои имена: |
|||
|
.DATA |
|
|
N1 |
= 5 |
|
|
N2 |
= N1+2 |
|
|
N3 |
= 2*N1-1 |
|
|
N4 |
= N1 MOD 3 |
||
m1 |
DW |
N1 |
DUP(0) |
m2 |
DW |
N2 |
DUP(0) |
m3 |
DW |
N3 |
DUP(0) |
m4 |
DW |
N4 |
DUP(0) |
… |
|
|
|
Переменные времени ассемблирования являются 16-разрядными. Такие переменные могут участвовать в арифметических выражениях. Возможны следующие операции: + (сложение), – (вычитание), * (умножение), / (деление нацело), MOD (взятие остатка по модулю).
Константы задаются с помощью директивы EQU. Например, часто используемому сочетанию символов 0Dh,0Ah,'$' можно дать имя, которое Ассемблер будет заменять его определением.
CRLFT EQU 0Dh,0Ah,'$'
Далее в программе изменять это определение нельзя.
16
15.14.2. Счетчик адреса.
Когда начинается новая программная секция, специальная встроенная переменная Ассемблера — счетчик адреса — сбрасывается в нуль. По мере выделения новых участков памяти для переменных или команд счетчик адреса увеличивается. Посмотрим это на примере. Вот часть листинга небольшой программы.
1 |
0000 |
|
|
|
.MODEL small |
|
2 |
0000 |
|
|
|
.STACK 100h |
|
3 |
0000 |
|
|
|
.DATA |
|
4 |
0000 |
01 |
02 |
a |
DB |
1,2 |
5 |
0002 |
0003 0004 0005 |
b |
DW |
3,4,5 |
|
6 |
0008 |
48 |
65 6C 6C 6F 24 |
c |
DB |
'Hello$' |
7 |
000E |
0000000C |
d |
DD |
12 |
|
8 |
0012 |
|
|
|
.CODE |
|
9 |
0000 |
B8 |
0000s |
s: |
mov ax,@data |
|
10 |
0003 |
8E |
D8 |
|
mov ds,ax |
|
11 |
0005 |
8B |
1E 0006r |
|
mov bx,b+4 |
|
12 |
0009 |
B8 |
4C00 |
|
mov ax,4C00h |
|
13 |
000C |
CD 21 |
|
int 21h |
||
14 |
|
|
|
|
END s |
|
Во второй колонке — значение счетчика адреса. Проследите его изменение в зависимости от количества и типа данных (в секции .DATA) и в зависимости от размера команд (секция .CODE). Величина b+4 вычисляется на этапе трансляции (мы видим слева 0006r).
А вот что мы увидим в отладчике. |
|
|
-CPU Pentium----------------------- |
|
ds:0006 = 0005 |
cs:0000 B83919 |
mov |
ax,1939 |
cs:0003 8ED8 |
mov |
ds,ax |
cs:0005►8B1E0600 |
mov |
bx,[0006] |
cs:0009 B8004C |
mov |
ax,4C00 |
cs:000C CD21 int |
21 |
|
Справа вверху в панели кода отображается адрес и содержимое ячейки, на которую ссылается текущая команда (команда, адрес которой в IP).
Счетчик адреса имеет символическое имя $ и это имя можно использовать в программе.
15.14.3. Примеры программ.
Перепишем программы a3 и a4, которые ранее мы создавали с помощью отладчика.
файл a3v0.asm
.MODEL small
.STACK 100h
.DATA
d DB "68",0Dh,0Ah,'$'
.CODE start: mov ax,@data
mov ds,ax
mov dx,OFFSET d mov ah, 9h
|
|
17 |
|
int 21h |
|
|
mov al,d[0] ; Первая цифра — в AL. |
|
|
mov ah,d[1] ; Вторая цифра — в AH. |
|
|
add al,ah |
; Сумма кодов цифр — в AL. |
|
cmp al,2*'0'+ 10 ; Сравниваем сумму кодов с числом 10 |
|
|
jng m |
; Если сумма больше 10, |
|
xchg ah,d[0] |
; то меняем местами |
|
mov d[1],ah |
; цифры, |
|
jmp fin |
|
m: |
sub al,ah |
; иначе восстанавливаем код первой цифры. |
|
cmp al,'3' |
; Будет ли в AL код цифры после вычитания 3? |
|
jnge fin |
; Если ДА, |
|
sub al,3 |
; то вычитаем из кода цифры тройку |
|
mov d[0],al |
; и возвращаем цифру в память |
fin: |
mov dx,OFFSET d |
|
|
mov ah, 9h |
|
|
int 21h |
|
|
mov ax,4C00h |
|
|
int 21h |
|
|
END start |
|
Здесь |
команду mov al,d[0] можно заменить на mov al,d, но в |
|
приведенном варианте выборка первой цифры выглядит как обращение к элементу массива. Команду mov ah,d[1] можно было заменить на mov al,d+1. Обратите внимание, что текст программы приобрел наглядность за
счет использования средств |
Ассемблера: |
например, команда cmp al,33h |
заменена на cmp al,'3'. |
|
|
В отладчике мы увидим строки: |
|
|
#a3v0#18: jmp fin |
|
|
193C:0021 EB09 |
jmp |
#a3v0#fin (002C) |
193C:0023 90 |
nop |
|
Команда nop появилась как следствие "технологии обратных поправок". Ассемблер во время трансляции команды jmp fin "не знает" числового значения метки fin, поэтому резервирует для команды перехода три байта, на случай, если метка fin расположена дальше, чем 127 байтов. Далее оказывается, что достаточно двухбайтовой команды перехода и лишний байт заполняется командой nop.
Избавиться от "паразитной" команды nop можно двумя способами. Вопервых, можно в программе указать команду короткого перехода: jmp short fin. Во-вторых, можно дать транслятору возможность совершить несколько проходов по тексту программы. Для этого указывается ключ /m. Тогда все лишние команды nop будут вычищены.
Перепишем программу для задания A4. Код, который не претерпел изменений, обозначим многоточием.
файл a4v0.asm
.MODEL small
.STACK 100h
.DATA
A DB 2,1,2, 13 DUP(1)
18
DimA = $ - A
BDB DimA DUP(0)
CDW DimA DUP(-1)
.CODE
start: mov ax, @data mov ds,ax
mov si,OFFSET A mov di,OFFSET B mov bx,OFFSET C sub dx,dx
mov cx,DimA
n:mov al,[si]
. . .
loop n
mov ax,4C00h int 21h
END start
Поясним некоторые решения. В директиве A DB 2,1,2, 13 DUP(1) использована конструкция повторения DUP (DUPlicate — дублировать): тринадцать раз в память будет записана единица. В следующей строке введена переменная времени ассемблирования DimA (в исполняемом файле мы с помощью отладчика увидим только ее конкретное значение в команде mov cx,DimA). значение DimA вычисляется как разность текущего значения счетчика адреса (встроенная переменная $) и адреса массива A. Эта разность в точности равна количеству элементов массива A. В двух нижеследующих директивах переменная DimA используется как коэффициент повторения в DUP. Если мы захотим изменить в программе количество элементов в массиве A, то размер двух других массивов будет изменен автоматически. Также автоматически изменится непосредственный операнд в команде mov cx,DimA. В строке mov bx,OFFSET C на этапе ассемблирования вычисляется начальный адрес массива C, который ранее мы скрупулезно вычисляли вручную.
Запуск программы по-прежнему надо осуществлять под управлением отладчика, так как в программе не предусмотрен вывод результатов.
