Assembler / P18
.pdfкоманда
команда
Эти строки могут повторяться многократно.
Цели и условия — списки имен файлов, разделенные пробелами. Команда — это любая команда DOS. Цель обязательно начинается с первой позиции, а команда — с отступом хотя бы на одну позицию.
файл a2v0.mak
a2v0.exe: main2v0.obj sub2v0.obj c:\tasm\bin\tlink /v/m main2v0 sub2v0, a2v0 c:\tasm\bin\td a2v0
main2v0.obj: main2v0.asm macr2v0.inc c:\tasm\bin\tasm /zi/m/la main2v0
sub2v0.obj: sub2v0.asm c:\tasm\bin\tasm /zi/m/la sub2v0
Строки, которые начинаются с первой позиции, соответствуют неконцевым вершинам дерева. После двоеточия перечисляются вершины-потомки (хотя в нашем случае их хотелось бы назвать "предками"). Строки с отступом не менее единицы содержат команды, которые надо выполнить, чтобы получить целевой файл (тот, что перед двоеточием).
Перед ключами (опциями) команд надо обязательно вставлять пробел. Запуск файла на выполнение осуществляется по команде
c:\tasm\bin\make -fa2v0.mak (после ключа -f указывается имя make-
файла)
Make обрабатывает файл так. Поочередно просматриваются все зависимости. Для каждой зависимости сравниваются даты последнего изменения целей и условий. Если дата/время последнего изменения одного из файлов-условий больше, чем у любой из целей, или если файл-цель отсутствует, make вызывает все команды, указанные в зависимости.
Возможности этой утилиты намного больше, чем здесь показано. Рекомендуется make запускать так:
c:\tasm\bin\make -f2v0.mak > a2v0.txt
Тогда вы прочтете отчет о работе make и сообщения запускаемых программ в файле a2v0.txt.
В результате выполнения make-файла на диске помимо исходных файлов
(main2v0.asm, sub2v0.asm, macr2v0.asm, macro.inc) появляются файлы:
main2v0.obj, sub2v0.obj;
main2v0.lst, sub2v0.lst;
a2v0.exe;
a2v0.map.
3) Подготовка отчета.
После запуска Turbo Debugger перед вами два окна Module (1) и Watch (2). Они нам не понадобятся, но закрывать их не будем (хотя, если возникают проблемы с памятью, лишние окна рекомендуется закрывать). Перечислим действия пользователя в отладчике в предположении, что программа работает правильно.
1) Запишем адрес процедуры treat_array в окно протокола. Сейчас активно первое окно (текст главной программы). Выведем текст модуля с подпрограммами. Для этого нажмем F3 (Module) и возникшем меню выберем имя файла с подпрограммами. Подведем курсор в строку treat_array PROC и нажимаем Ctrl+I (Alt+F10/Inspect). Появляется окно, в
котором читаем адрес процедуры. Копируем содержимое окна в окно протокола. Alt+W,D (F10/Window/Dump Pane to Log). Замечание: если вы используете более позднюю версию
TD, то F10/Edit/Dump Pane to Log.
2)Создадим окно CPU. Alt+V,C,F5,Ctrl+M,Ctrl+M (эквивалент при использовании меню: F10/View/CPU, F10/Window/Zoom, Alt+F10/Mixed/Both).
3)Выполним команды загрузки DS (mov ax,@data / mov ds,ax): F7,F7.
4)В панели данных покажем массив arrb. Shift+Tab, Ctrl+G (Alt+F10/Goto). Набираем имя arrb. В панели данных отображается
ds:0000 D5 F5 D5 00 F5 00 …
5)Скопируем содержимое панели в окно протокола. Alt+W,D (F10/ Window/ Dump Pane to Log).
6)В окне данных покажем массив arrw. Alt+V,D (F10/View/Dump). Получаем 4-ое окно. Ctrl+G (Alt+F10/Goto..). Набираем имя arrw. Отобразим как слова. Ctrl+D,W
(Alt+F10/Display as…/Word) . В окне данных отображается ds:0002 00D5 00F5 …
Скопируем в окно протокола Alt+W,D.
7)Войдем в процедуру treat_elem и скопируем стек. Перейдем в окно CPU (Alt+3, т.е.
вокно 3; либо можно нажимать F6 пока не окажетесь в нужном окне). Перейдем в панель кода: Tab. Многократным нажатием на F7 попадаем в процедуру treat_elem (в ней выполним команды push ax, push bx). Переходим в панель стека: Tab,Tab,Tab. Скопируем в окно протокола: Alt+W,D. Вернемся в панель кода (необязательно): Tab,Tab.
8)Выполним программу и скопируем результат в окно протокола. Выполнить до конца: F9 (F10/Run/Run). Получим окно с сообщением: Terminated, exit code 0. Нажимаем
Enter. Переходим в панель данных: Shift+Tab. Скопируем в окно протокола: Alt+W,D. Перейдем в 4-ое окно: Alt+4 (F6,F6,F6). Скопируем в окно протокола: Alt+W,D.
9)Сохраним протокол в файле. Перейдем в окно протокола: Alt+V,L (F10/View/Log). Распахнем окно: F5 (F10/Window/Zoom). Откроем файл протокола: Ctrl+O (Alt+F10/Open log file …). Запрашивается имя файла (по умолчанию предлагается a2v0.log) —
подтверждаем. Закрываем файл: Ctrl+C (Alt+F10/Close log file).
10)Завершаем работу в TD. Alt+X (F10/File/Quit).
В текстовом редакторе правим файл a2v0.log, распечатываем его и снабжаем комментариями.
Turbo Debugger Log Inspecting treat_array @5571:006E
CPU 80286 ds:0000 D5 F5
Dump
ds:0002 00D5 00F5 CPU 80286
ss:0100
ss:00FE 0014 - адрес команды jc error (после call treat_array) ss:00FC 0079 - адрес команды mov [si],ax (после call treat_elem) ss:00FA F5D5 - содержимое AX
ss:00F8 0000 - содержимое BX
Terminated, exit code 0
CPU 80286 6356:0000 D5 75
Dump
6356:0002 80D5 00F5
Справа от исходных данных и результата можно вставить комментарий, поясняющий их (те же данные в двоичном представлении, с выделенными полями и т.д.). Что касается стека, желательно, прокручивая окно CPU, найти адреса команд, помещенные в стек.
Адрес программы обработки массива treat_array вы ищете в таблице имен листингов main2v0.lst, sub2v0.lst, а также в карте памяти a2v0.map. Адрес в отладчике мы уже
записали в окно Log. Наконец, размер кода и данных программы вы найдете в карте памяти a2v0.map. В отчет нужно включать строчки из этих файлов, вырезанные с помощью текстового редактора.
18.5. Несколько задач.
Задача. Поместить в регистр BX старшее слово регистра EAX 1-е решение
|
mov |
cx,16 ; размер слова |
m: |
rol |
eax,1 |
|
rcl |
bx,1 |
|
loop |
m |
|
rol |
eax,16 ; восстановить eax |
2-е решение
ror eax, 16 mov bx, ax
ror eax, 16 ; восстановить eax
3-е решение |
|
mov |
ebx, eax |
ror |
ebx, 16 |
(недостаток: портится старшее слово EBX)
Позднее мы увидим еще одно решение этой задачи: с использованием команды двойного сдвига.
Задача: сосчитать количество единичных битов в eax xor bx,bx ; Счетчик единичных битов mov cx,32
n:rol eax,1 adc bx,0 loop n
Задача. Расположить содержимое AX в регистре BX в обратном порядке.
|
mov |
cx, 16 |
n: |
rol |
ax,1 |
|
rcr |
bx,1 |
|
loop |
n |
18.6. Битовые операции, появившиеся в 386-м процессоре
18.6.1. Команды сканирования битов bsf и bsr.
Сканирование бита вперед |
bsf dst,src |
в dst — индекс первого справа |
|
|
единичного бита в src |
Bit Scan Forward |
|
ZF = 1, если все биты src нулевые |
|
|
ZF = 0, если в src есть единичный бит |
форматы операндов: bsf r16, r/m16, bsf r32, r/m32 |
||
|
|
|
Сканирование бита назад |
bsr dst,src |
в dst — индекс первого слева |
|
|
единичного бита в src |
Bit Scan Reverse |
|
ZF = 1, если все биты src нулевые |
|
|
ZF = 0, если в src есть единичный бит |
форматы операндов: bsr r16, r/m16, |
bsf r32, r/m32 |
Итак, "вперед" — в направлении увеличения нумерации битов, "назад" — в направлении уменьшения. Индекс — номер разряда (бита).
Пример. Выяснить индекс самого старшего установленного бита в регистре DX (пусть DX содержит число 5).
bsr |
ax, dx ; ax = 2 |
||
Пример. Сдвинуть содержимое регистра EBX вправо так, чтобы младший бит EBX был |
|||
установлен; |
если это |
невозможно, перейти на метку null (Пусть EBX = 84h = |
|
0…0 1000 |
0100b.) |
|
|
bsf |
|
ecx, ebx |
; ecx = 2 |
jz |
|
null |
|
shr |
ebx, cl |
; ebx = 21h = 0…0 0010 0001b |
18.6.2. Команды проверки битов bt, bts, btr, btc
Сначала разберем команду bt, остальные команды на нее похожи.
Проверка бита |
|
bt src, index |
сохраняет бит из src с номером index в CF |
|
Bit Test |
|
|
|
CF = 1, если бит src установлен, |
|
|
|
|
CF = 0, если бит src сброшен |
форматы операндов: bt r/m16, r16, bt r/m32, r32, bt r/m16, i8, bt r/m32, i8 |
||||
Пример. Поместить второй бит регистра CX в флаг CF. |
||||
mov |
cx, 5 |
;cx = 101b |
||
bt |
cx, 2 |
;CF = 1 |
Пример. Решим уже знакомую задачу: сосчитать количество единичных битов в регистре
AX.
|
xor |
cx, cx |
|
xor |
bx, bx |
n: |
bt |
ax, cx |
|
adc |
bx, 0 |
|
inc |
cx |
|
cmp |
cx, 16 |
|
jnae |
n |
Для оставшихся трех команд не будем приводить подробного описания — оно вполне аналогично команде bt.
btc — проверка и инвертирование (bit test and complement) — бит помещается в CF, сам бит инвертируется.
btr — проверка и сброс (bit test and reset) — бит помещается в CF, сам бит сбрасывается. bts — проверка и установка (bit test and set) — бит помещается в CF, сам бит устанавливается.
Упражнение. Придумайте примеры для каждой из приведенных команд и проверьте их работу в TD.
18.6.3. Команды двойного сдвига
Двойной сдвиг влево |
shld dst,src, count |
описание приведено ниже |
Double precision Shift Left |
SF, ZF, PF по результату; |
в CF последний выдвинутый бит; OF = 1, если произошло изменение знака (имеет смысл при count = 1)
форматы операндов: shld r/m16, r16, i8/cl shld r/m32, r32, i8/cl
dst и src объединяются и происходит сдвиг влево на count бит объединенного содержимого. src остается без изменений.
|
CF |
|
|
|
|
|
|
|
dst |
|
|
|
|
|
src |
|
|||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Аналогично работает команда двойного сдвига вправо shrd dst,src, count.
Пример. Поместить в регистр BX старшее слово регистра EAX shld ebx,eax,16
если нужно, чтобы старшее слово EBX при этом не изменялось, то shr ebx,16
shld ebx,eax,16
Пример. Пусть array — массив двойных слов. Осуществить циклический сдвиг всего массива влево на 12 бит
|
.MODEL small |
|
|
.386 |
|
|
.STACK 100h |
|
|
.DATA |
|
array |
DD 8 DUP(12345678h) |
|
LEN_ARRAY = ($-array) SHR 2 |
||
|
.CODE |
|
start: mov |
ax, @data |
|
|
mov |
ds, ax |
|
mov |
edx, array |
|
xor |
esi, esi |
|
mov |
ecx, LEN_ARRAY - 1 |
next: |
mov |
eax, array+4[esi*4] |
|
shld |
array[esi*4], eax, 12 |
|
inc |
esi |
|
loop |
next |
|
shld |
array[esi*4], edx, 12 |
|
mov |
ax, 4C00h |
|
int |
21h |
|
END |
start |
В отладчике мы видим, что теперь элементами массива являются числа 4568123h.
Команда двойного сдвига применяется для быстрого перемещения строки битов на новое место в памяти. (В англоязычной литературе используется сокращение "bit blt" (BIT Block Transfer — перемещение блока бит).
Пример. Вставить битовую подстроку длиной 16 бит в строку битов, начиная с 8-го бита (т.е. заменить биты 23:8 заданной строкой из 16 бит). Вставлямая подстрока выровнена к левому краю. [Юров, с изменениями]
MODEL small |
|
|
|
.386 |
|
|
|
.STACK 100h |
|
|
|
.DATA |
|
|
|
bit_str DD |
12345678h |
; |
строка-приемник |
p_str DD |
0ABCD0000h |
; |
вставляемая подстрока 0ABCDh |
.CODE |
|
|
|
start: mov |
ax, @data |
|
|
mov |
ds, ax |
|
|
mov |
eax, p_str |
: Поместить подстроку в EAX |
;Правый край места вставки циклически переместить к краю
;строки bit_str (т.е. слева поместить правые биты)
ror bit_str,8 ; (bit_str = 78123456)
; Сдвинуть строку вправо на длину подстроки shr bit_str,16 ; (bit_str = 00007812)
; Поместить подстроку в строку-приемник (16 – размер подстроки) shld bit_str,eax,16 : (bit_str = 7812ABCD)
; Восстановить младшие 8 бит (вернуть правые биты на место) rol bit_str,8 : (bit_str=12ABCD78)
mov ax,4C00h int 21h
END start
Задача. Переписать эту программу с использованием битовых полей (константы 8 и 16 должны вычисляться на этапе ассемблирования, а bit_str и p_str должны определяться посредством записей).
Задача. С помощью команды двойного сдвига реализовать противоположную задачу: извлечь подстроку 23:8 и поместить ее в EAX, выровняв по правому краю.
18c. Битовые операции, появившиеся в 386-м процессоре
18c.1. Команды сканирования битов bsf и bsr.
Сканирование бита вперед |
bsf dst,src |
в dst — индекс первого справа |
||
|
|
|
|
единичного бита в src |
Bit Scan Forward |
|
|
|
ZF = 1, если все биты src нулевые |
|
|
|
|
ZF = 0, если в src есть единичный бит |
форматы операндов: bsf |
r16, r/m16, |
bsf r32, r/m32 |
||
|
|
|
|
|
Сканирование бита назад |
|
bsr dst,src |
в dst — индекс первого слева |
|
|
|
|
|
единичного бита в src |
Bit Scan Reverse |
|
|
|
ZF = 1, если все биты src нулевые |
|
|
|
|
ZF = 0, если в src есть единичный бит |
форматы операндов: bsr |
r16, r/m16, |
bsf r32, r/m32 |
Итак, "вперед" — в направлении увеличения нумерации битов, "назад" — в направлении уменьшения. Индекс — номер разряда (бита).
Пример. Выяснить индекс самого старшего установленного бита в регистре DX (пусть DX содержит число 5).
bsr |
ax, dx ; ax = 2 |
||
Пример. Сдвинуть содержимое регистра EBX вправо так, чтобы младший бит EBX был |
|||
установлен; |
если это |
невозможно, перейти на метку null (Пусть EBX = 84h = |
|
0…0 1000 |
0100b.) |
|
|
bsf |
|
ecx, ebx |
; ecx = 2 |
jz |
|
null |
|
shr |
ebx, cl |
; ebx = 21h = 0…0 0010 0001b |
18c.2. Команды проверки битов bt, bts, btr, btc
Сначала разберем команду bt, остальные команды на нее похожи.
Проверка бита |
|
bt src, index |
сохраняет бит из src с номером index в CF |
|
Bit Test |
|
|
|
CF = 1, если бит src установлен, |
|
|
|
|
CF = 0, если бит src сброшен |
форматы операндов: bt r/m16, r16, bt r/m32, r32, bt r/m16, i8, bt r/m32, i8 |
||||
Пример. Поместить второй бит регистра CX в флаг CF. |
||||
mov |
cx, 5 |
;cx = 101b |
||
bt |
cx, 2 |
;CF = 1 |
Пример. Решим уже знакомую задачу: сосчитать количество единичных битов в регистре
AX.
|
xor |
cx, cx |
|
xor |
bx, bx |
n: |
bt |
ax, cx |
|
adc |
bx, 0 |
|
inc |
cx |
|
cmp |
cx, 16 |
|
jnae |
n |
Для оставшихся трех команд не будем приводить подробного описания — оно вполне аналогично команде bt.
btc — проверка и инвертирование (bit test and complement) — бит помещается в CF, сам бит инвертируется.
btr — проверка и сброс (bit test and reset) — бит помещается в CF, сам бит сбрасывается. bts — проверка и установка (bit test and set) — бит помещается в CF, сам бит устанавливается.
Упражнение. Придумайте примеры для каждой из приведенных команд и проверьте их работу в TD.
18c.3. Команды двойного сдвига
Двойной сдвиг влево |
shld dst,src, count |
описание приведено ниже |
Double precision Shift Left |
SF, ZF, PF по результату; |
|
|
|
в CF последний выдвинутый бит; |
|
|
OF = 1, если произошло |
|
|
изменение знака (имеет смысл |
|
|
при count = 1) |
форматы операндов: shld r/m16, r16, i8/cl shld r/m32, r32, i8/cl
dst и src объединяются и происходит сдвиг влево на count бит объединенного содержимого. src остается без изменений.
|
CF |
|
|
|
|
|
|
|
dst |
|
|
|
|
|
src |
|
|||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Аналогично работает команда двойного сдвига вправо shrd dst,src, count.
Пример. Поместить в регистр BX старшее слово регистра EAX shld ebx,eax,16
если нужно, чтобы старшее слово EBX при этом не изменялось, то shr ebx,16
shld ebx,eax,16
Пример. Пусть array — массив двойных слов. Осуществить циклический сдвиг всего массива влево на 12 бит
.MODEL small
.386
.STACK 100h
.DATA
array DD 8 DUP(12345678h) LEN_ARRAY = ($-array) SHR 2
.CODE
start: mov |
ax, @data |
mov |
ds, ax |
mov |
edx, array |
xor |
esi, esi |
mov |
ecx, LEN_ARRAY - 1 |
next: mov |
eax, array+4[esi*4] |
shld |
array[esi*4], eax, 12 |
inc |
esi |
loop |
next |
shld |
array[esi*4], edx, 12 |
mov |
ax, 4C00h |
int |
21h |
END |
start |
В отладчике мы видим, что теперь элементами массива являются числа 4568123h.
Команда двойного сдвига применяется для быстрого перемещения строки битов на новое место в памяти. (В англоязычной литературе используется сокращение "bit blt" (BIT Block Transfer — перемещение блока бит).
Пример. Вставить битовую подстроку длиной 16 бит в строку битов, начиная с 8-го бита (т.е. заменить биты 23:8 заданной строкой из 16 бит). Вставлямая подстрока выровнена к левому краю. [Юров, с изменениями]
MODEL small
.386
.STACK 100h
.DATA
bit_str DD 12345678h ; строка-приемник
p_str DD |
0ABCD0000h |
; вставляемая подстрока 0ABCDh |
.CODE |
|
|
start: mov |
ax, @data |
|
mov |
ds, ax |
|
mov |
eax, p_str |
: Поместить подстроку в EAX |
;Правый край места вставки циклически переместить к краю
;строки bit_str (т.е. слева поместить правые биты)
ror bit_str,8 ; (bit_str = 78123456)
; Сдвинуть строку вправо на длину подстроки shr bit_str,16 ; (bit_str = 00007812)
; Поместить подстроку в строку-приемник (16 – размер подстроки) shld bit_str,eax,16 : (bit_str = 7812ABCD)
; Восстановить младшие 8 бит (вернуть правые биты на место) rol bit_str,8 : (bit_str=12ABCD78)
mov ax,4C00h int 21h
END start
Задача. Переписать эту программу с использованием битовых полей (константы 8 и 16 должны вычисляться на этапе ассемблирования, а bit_str и p_str должны определяться посредством записей).
Задача. С помощью команды двойного сдвига реализовать противоположную задачу: извлечь подстроку 23:8 и поместить ее в EAX, выровняв по правому краю.