«(Учебное пособие) Иркутск 2007 УДК 681.3.6 С50 Смоленцев М.Ю. Программирование на языке Ассемблера для микропроцессоров i80x86: Учебное пособие.— Иркутск: ИрИИТ, 2007.— 600с. Ил. Табл. Библиогр.: назв. Рекомендовано ...»
Основное действие команды CMPS заключается в сравнении элемента одной строки с элементом другой строки. Это сравнение выполняется так же, как и в команде CMP, то есть путем вычитания операндов без записи куда-либо полученной разности, команда CMPS так же меняет значения регистров SI/ESI и DI/EDI и меняет их так, чтобы в них оказались адреса соседних элементов строк. В зависимости от текущего значения флага направления DF: при DF=0 значения регистров увеличиваются — происходит переход вперед, к следующим элементам, а при DF=1 значения регистров уменьшаются — происходит переход назад, к предыдущим элементам. Сам по себе флаг DF не меняется, его должен менять автор программы командами CLD (очистка флага DF) и STD (установка флага DF). По команде CLD флаг направления обнуляется (DF=0), а по команде STD флагу присваивается 1 (DF=1).
Для того чтобы эти команды можно было использовать для сравнения последовательности элементов, имеющих размерность байт, слово, двойное слово, необходимо использовать один из префиксов REPE или REPNE. Префикс REPE заставляет циклически выполняться команды сравнения до тех пор, пока содержимое регистра ECX/CX не станет равным нулю или пока не совпадут очередные сравниваемые элементы цепочек (флаг ZF=1). Префикс REPNE заставляет циклически производить сравнение до тех пор, пока не будет достигнут конец цепочки (ECX/CX=0) либо не встретятся различающиеся элементы цепочек (флаг ZF=0).
Допустим Вам необходимо сравнить пароль вводимый с клавиатуры с паролем находящимся где-то в памяти:
PASSWORD db ‘fyfcnfcbz’,0Dh ;строка символов пароля нажатии ;на клавишу «Enter»
XOR BX,BX ;позиционируемся на первый символ A1: MOV AH,0 ;получение очередного символа CMP AL,PASSWORD[BX];сравнение с очередным символом JNE EXIT ;если не совпал символ — выход из программы При использовании команды CMPS и префикса повторения REPE программа изменится следующим образом:
PASSWORD DB 'fyfcnfcbz',0Dh ;Строка для сравнения BUFFER_KLAV DB 50 DUP (?) ;буфер клавиатуры CLD ;просмотр строки в направлении возрастания MOV SI,OFFSET PASSWORD ;в DS:SI адрес пароля MOV DI,BUFFER_KLAV ;в ES:DI адрес буфера JNZ M1 ;если не конец строки, то встретились • • • ;действия, если элементы строки совпали M1: • • • ;действия, если элементы строки не совпали 16.1.2. Команды загрузки строки LODSB/LODSW/LODSD (Загрузка строки состоящей из байтов/слов/двойных слов =”LOaD String Синтаксис команд:
Семантика команды: загрузка элемента из последовательности (цепочки) операнд в регистр-аккумулятор AL/AX/EAX.
Алгоритм работы:
• загрузить элемент из ячейки памяти, адресуемой парой DS:ESI/SI, в регистр AL/AX/EAX. Размер элемента задается неявно (для команды LODS определяется по размерности операнд) или явно в соответствии с применяемой командой (для команд LODSB, LODSW, • изменить значение регистра SI на величину, равную длине элемента цепочки. Знак этой величины зависит от состояния флага DF:
DF=0 — значение положительное, то есть просмотр от начала DF=1 — значение отрицательное, то есть просмотр от конца Применение:
Команды извлекают элемент из ячейки памяти в один из регистров. Перед командой LODS можно указать префикс повторения REP, но в этом нет особого смысла, так как обычно эту команду используют в некотором цикле для просмотра некоторой цепочки с элементами фиксированного размера:
LEA SI,STR ;загружаем в регистр SI адрес строки STR 16.1.3. Команды сохранения строки STOSB/STOSW/STOSD (Сохранение строки байтов/слов/двойных слов =”STOre String Синтаксис команд:
Семантика команды: сохранение элемента из регистра-аккумулятора AL/AX/EAX в последовательности (цепочке) операнд.
Алгоритм работы:
• записать элемент из регистра AL/AX/EAX в ячейку памяти, адресуемую парой ES:DI/EDI. Размер элемента задается неявно (для команды STOS определяется из размерности операнд) или конкретной применяемой командой (для команд STOSB, STOSW, STOSD);
• изменить значение регистра DI на величину, равную длине элемента цепочки. Знак этого изменения зависит от состояния флага DF:
DF=0 — увеличить, что означает просмотр от начала цепочки к DF=1 — уменьшить, что означает просмотр от конца цепочки Применение:
Команды сохраняют элемент из регистров AL/AX/EAX в ячейку памяти.
Перед командой STOS можно указать префикс повторения REP, в этом случае появляется возможность работы с блоками памяти, заполняя их значениями в соответствии с содержимым регистра ECX/CX:
;заполнить область памяти STR пробелами STR db 'Какая-то строка' LEN_STR=$-STR ;вычисляем длину строки STR ;пример совместной работы STOSB и LODSB: копировать одну ; строку в другую до первого пробела STR1 db 'Какая-то строка' LEN_STR1=$-STR STR2 DB LEN_STR1 DUP (' ') MOV CX,LEN_STR M1: LODSB EXIT Команды пересылки строк MOVSB/MOVSW/MOVSD/MOVSQ (Пересылка строк байтов/слов/двойных/учетверенных слов = “MOVe String Byte/Word/Double/Quadruple word”) Синтаксис команд: MOVSW Семантика команды: пересылка элементов из последовательности (цепочки) операнд1 в последовательность (цепочку) операнд2.
Алгоритм работы:
• выполнить копирование байта, слова, двойного или учетверенного слова из операнда2 в операнд1, при этом адреса элементов предварительно должны быть загружены:
адрес операнда2 — в пару регистров DS:ESI/SI (DS по умолчанию, допускается замена сегмента);
адрес операнда1 — в пару регистров ES:EDI/DI (замена сегмента не допускается);
• в зависимости от состояния флага DF изменить значение регистров ESI/SI и EDI/DI:
если DF=0, то увеличить содержимое этих регистров на длину структурного элемента последовательности;
если DF=1, то уменьшить содержимое этих регистров на длину структурного элемента последовательности;
• если есть префикс повторения, то выполнить определяемые им действия.
Применение:
Команды пересылают элемент из одной ячейки памяти в другую. Размеры пересылаемых элементов зависят от применяемой команды. Команда MOVS может работать с элементами размером в байт, слово, двойное, учетверенное слово. В качестве операндов в команде указываются идентификаторы последовательностей этих элементов в памяти. Реально эти идентификаторы используются лишь для получения типов элементов последовательностей, а их адреса должны быть предварительно загружены в указанные выше пары регистров. Транслятор, обработав команду MOVS и выяснив тип операндов, генерирует одну из машинных команд MOVSB, MOVSW, MOVSD или MOVSQ. Машинного аналога для команды MOVS нет. Для адресации операнд1 обязательно должен использоваться регистр ES.
Для того чтобы эти команды можно было использовать для пересылки последовательности элементов, имеющих размерность байт, слово, двойное, учетверенное слово, необходимо использовать префикс REP.
Префикс REP заставляет циклически выполняться команды пересылки до тех пор, пока содержимое регистра ECX/CX не станет равным нулю:
STR1 DB 'STR1 копируется в STR2'
REP MOVSB
Пару команд LODS и STOS можно использовать вместо команды MOVS, если вам требуется примитивное шифрование или дешифровка данных. Наиболее часто используемый фрагмент пригодный как для шифрования, так и для дешифровки NOT EAX ;или XOR EAX,0F3D4B90Ah или ROR EAX,3;шифруем Текст получается зашифрованным от простого просмотра viewer’ом, но его криптостойкость равна нулю, если вашу программу захотят взломать.16.1.4. Команды сканирования строк SCASB/SCASW/SCASD Сканирование строки байтов/слов/двойных слов Синтаксис команд:
Семантика команды: поиск значения в последовательности (цепочке) элементов операнд в памяти.
Алгоритм работы:
• выполнить вычитание (элемент цепочки —(EAX/AX/AL)).
Элемент цепочки операнд локализуется парой ES:EDI/DI. Замена сегмента ES не допускается;
• по результату вычитания установить флаги;
изменить значение регистра EDI/DI на величину, равную длине элемента цепочки. Знак этой величины зависит от состояния флага DF:
DF=0 — величина положительная, то есть просмотр от начала DF=1 — величина отрицательная, то есть просмотр от конца Применение:
Команды сканирования сравнивают значение в регистре EAX/AX/AL с ячейкой памяти, локализуемой парой регистров ES:EDI/DI. Размер сравниваемого элемента зависит от применяемой команды. Команда SCAS может работать с элементами размером в байт, слово или двойное слово. В качестве операнда в команде указывается идентификатор последовательности элементов в памяти. Реально этот идентификатор используется лишь для получения типа элементов последовательности, а ее адрес должен быть предварительно загружен в указанную выше пару регистров. Транслятор, обработав команду SCAS и выяснив тип операндов, генерирует одну из машинных команд: SCASB, SCASW или SCASD. Машинного аналога для команды SCAS нет. Для адресации операнда обязательно должен использоваться регистр ES.
Для того чтобы эту команду можно было использовать для поиска значения в последовательности элементов, имеющих размерность байт, слово или двойное слово, необходимо использовать один из префиксов REPE или REPNE. Эти префиксы не только заставляют циклически выполняться команду поиска, пока ECX/CX0, но и отслеживают состояние флага ZF:
MOV CX,LEN_STR ;длину строки — в CX CYCL: REPE SCASB ;переход на EXIT, если цепочка просмотрена EXIT: • • • 16.2. Префиксы повторения REP/REPE/REPZ/REPNE/REPNZ (Повторить цепочечную операцию = “REPeat string operation”) Синтаксис префиксов: REPE /REPZ Семантика команды: указание условного и безусловного повторения следующей за данной командой цепочечной операции.
Алгоритм работы:
Алгоритм работы зависит от конкретного префикса. Префиксы REP, REPE и REPZ на самом деле имеют одинаковый код операции, их действия зависят от той цепочечной команды, которую они предваряют:
REP используется перед любыми цепочечными командами (MOVS, STOS, INS, OUTS). Действия REP:
1). анализ содержимого CX:
• если CX не равно 0, то выполнить цепочечную команду, следующую за данным префиксом, и перейти к шагу 2;
• если CX=0, то передать управление команде, следующей за данной цепочечной командой (выйти из цикла по REP);
2. уменьшить значение CX=CX–1 и вернуться к шагу 1;
Команды REPE и REPZ обычно используются перед цепочечными командами CMPS и SCAS. Действия REPE и REPZ:
1). анализ содержимого CX и флага ZF:
• если CX0 или ZF0, то выполнить цепочечную команду, следующую за данным префиксом, и перейти к шагу 2;
• если CX=0 или ZF=0, то передать управление команде, следующей за данной цепочечной командой (выйти из цикла по REP);
2). уменьшить значение CX=CX-1 и вернуться к шагу 1;
REPNE и REPNZ также имеют один код операции и имеют смысл при использовании перед цепочечными командами CMPS и SCAS. Действия REPNE и REPNZ:
1). анализ содержимого CX и флага ZF:
• если CX0 или ZF=0, то выполнить цепочечную команду, следующую за данным префиксом, и перейти к шагу 2;
• если CX=0 или ZF0, то передать управление команде, следующей за данной цепочечной командой (выйти из цикла по REP);
2). уменьшить значение CX=CX–1 и вернуться к шагу 1.
Применение:
Команды REP, REPE, REPZ, REPNE и REPNZ в силу специфики своей работы называются префиксами. Они имеют смысл только при использовании цепочечных операций, заставляя их циклически выполняться и тем самым без организации внешнего цикла обрабатывать последовательности элементов фиксированной длины. Большинство применяемых префиксов являются условными, то есть они прекращают работу цепочечной команды при выполнении определенных условий.
16.3. Команды загрузки адресных пар в регистры Использование строковых команд и префиксов повторения позволяет существенно ускорить обработку строк, однако перед этими командами приходится выписывать достаточно много установочных команд (установить флаг направления (CLD,STD), записать в регистр CX/ECX число повторений и т.д.). В определенной мере сократить число таких команд позволяют две команды LDS и LES, которые загружают в регистры адресные пары (указатели) и с помощью которых можно установить пары регистров DS:SI/ESI и ES:DI/EDI на обрабатываемые строки.
(Загрузка сегментного регистра DS/ES/FS/GS/SS указателем из памяти = “Load pointer into DS/ES/FS/GS/SS segment register”) Синтаксис команд: LGS, Семантика команды: получение полного указателя в виде сегментной составляющей и смещения.
Алгоритм работы:
Алгоритм работы команды зависит от действующего режима адресации (use16 или use32):
• если use16, то загрузить первые два байта из ячейки памяти операнд2 в 16-разрядный регистр, указанный операндом1. Следующие два байта в области операнд2 должны содержать сегментную составляющую некоторого адреса; они загружаются в регистр DS/ES/FS/GS/SS;
• если use32, то загрузить первые четыре байта из ячейки памяти операнд1 в 32-разрядный регистр, указанный операндом2. Следующие два байта в области операнд2 должны содержать сегментную составляющую, или селектор, некоторого адреса; они загружаются в регистр DS/ES/FS/GS/SS.
Применение:
Таким образом, с помощью данных команд в паре регистр DS/ES/FS/GS/SS и регистр-операнд1 оказывается полный адрес некоторой ячейки памяти.
Это обстоятельство можно использовать, к примеру, при работе с цепочечными командами, где существуют жесткие соглашения на размещение адресов обрабатываемых строк. Помните, что любая загрузка сегментного регистра приводит к обновлению соответствующего теневого регистра.
Контрольные вопросы и упражнения:
1. имеются следующие определения:
CONAME db ‘SPACE EXPLORERS INC.’ Используя цепочечные команды, выполните:
а) пересылку данных из CONAME в PRILINE слева направо;
б) пересылку данных из CONAME в PRILINE справа налево;
в) загрузку третьего и четвертого байтов области CONAME в регистр AX;
г) сохранение содержимого регистра AX в области по адресу PRILINE+5;
д) сравнение данных в областях CONAME и PRILINE;
е) сканирование области CONAME и поиск в ней символа “пробел”. Если символ будет найден переслать его в регистр BL.
2. определите переменную, содержащую шестнадцатеричные значения 03, 04, 05 и B4h. Продублируйте эту переменную 20 раз и выведите результат на экран.
3. Напишите программу, которая изменит порядок содержимого 4 байт от LIST до LIST+3 на обратный.
4. Напишите программу, которая пересылает информацию из области с начальным адресом LIST и конечным адресом LIST+100 в область с соответствующими адресами BLK и BLK+100.
5. Пусть в области от STRING до STRING+99 находится цепочка символов. Необходимо установить в 1 бит 5 регистра DL, если цепочка содержит код цифры; в противном случае этот бит сбрасывается в 0. постройте схему решения задачи и напишите программу.
6. Пусть таблица имен начинается с адреса TABLE и состоит из 100 элементов; каждый элемент имеет 80 байт, причем первые 8 байт представляют собой поле имени, а остальные 72 байта являются информационным полем. Напишите программу поиска в таблице заданного имени из 8 символов, хранимого в переменной NAME. если имя найдено, необходимо скопировать информацию в переменную INFO; в противном случае INFO заполняется пустыми символами (нулевыми байтами).
7. Напишите программу, которая упаковывает четыре 12-битных величины из четырех смежных слов в три смежных слова.
8. Напишите процедуру SEARCH, которая отыскивает в массиве заданный байт; в случае успеха параметр-слово содержит индекс элемента в массиве, а в случае неудачи в параметр загружается –1.
9. Найти сумму массива из десяти 16-битных величин. Первое число хранится по адресу 3000h. Сумма запоминается в стеке.
10.Сопоставить два массива байтов на идентичность. Длина первого массива указывается по адресу 3000h и далее располагаются сами байты.
Длина второго массива указывается по адресу 4000h, далее располагаются сами байты. Если оба массива идентичны, в вершину стека записывается число 0EEEEh, в противном случае число 0000h.
Глава 17. Прочие очень полезные команды Иногда вместо арифметических и логических преобразований выгоднее использовать преобразование одного кода в другой. Преобразование кода длиной не более 8 бит (всего 256 комбинаций) наиболее просто реализуется с помощью хранения выходных кодов в массиве длиной до байт и использования исходного кода в качестве индекса в этом массиве. В микропроцессоре предусмотрена специальная команда для выполнения этих действий — команда XLAT. В команде XLAT предполагается что базовый адрес массива байт находится в регистре BX, а преобразуемый байт — в регистре AL. Преобразуемое значение кода берется из массива кода и помещается в регистр AL.
Допустим необходимо цепочку цифр вывести на семисегментный индикатор Фрагмент программы преобразующей двоичный код в код семисегментного индикатора:
0,1,2,3,4,5,6,7,8 и TABLE_CIF DB 3Fh,06h,5Bh,4Fh,66h,6Dh,7Dh,07h, 7Fh,6Fh MOV BX,OFFSET TABLE_CIF ;загрузить базовый адрес
MOV CX,LENGTH STG_BCD
Синтаксис команды: HLT Семантика команды: остановка микропроцессора до прерывания или перезагрузки.Алгоритм работы: перевод микропроцессора в состояние остановки.
Применение:
В результате выполнения команды микропроцессор переходит в состояние остановки. Из этого состояния его можно вывести сигналами на входах RESET, NMI, INTR. Если для возобновления работы микропроцессора используется прерывание, то сохраненное значение пары CS:EIP/IP указывает на команду, следующую за HLT. Для иллюстрации применения данной команды рассмотрим способ переключения микропроцессора из защищенного в реальный режим и его возврата обратно в реальный режим. В микропроцессоре не предусмотрено специальных средств для подобного переключения. Сброс микропроцессора можно инициировать, если вывести байт со значением 0FEh в порт клавиатуры 64h. После этого микропроцессор переходит в реальный режим и управление получает программа BIOS, которая анализирует байт отключения в CMOS-памяти по адресу 0Fh. Если значения этого байта равно 05h — сброс микропроцессора инициирует инициализацию программируемого контроллера прерываний на значение базового вектора 08h. Далее управление передается по адресу, который находится в ячейке области данных BIOS 0040:0067h;
Если значения этого байта равно 0Ah — сброс микропроцессора инициирует непосредственно передачу управления по адресу в ячейке области данных BIOS 0040:0067h (то есть без перепрограммирования контроллера прерываний).
Если Вы не используете прерываний, то достаточно установить байт 0Fh в CMOS-памяти в 0Ah. Предварительно Вы должны инициализировать ячейку области данных BIOS 0040:0067h значением адреса, по которому необходимо передать управление после сброса. Для программирования CMOS-памяти используются номера портов 070h и 071h (Глава «Доступ к ресурсам компьютера»). Вначале в порт 070h заносится нужный номер ячейки CMOS-памяти, а затем в порт 071h — новое значение этой ячейки.
;работаем в реальном режиме, готовимся к переходу ;в защищенный режим:
PUSH ES
POP ES MOV WORD PTR ES:[67h],OFFSET RET_REAL ;RET_REAL — метка в программе, с которой должно ;начаться выполнение программы после сброса MOV ES:[69h],CS MOV AL,0Fh ;будем обращаться к ячейке 0Fh в CMOS OUT 70h,AL NOP ;чуть задержимся, чтобы аппаратура отработала ;сброс без перепрограммирования контроллера MOV AL,0Ah OUT 71h,AL ;переходим в защищенный режим установкой бита 0 CR0= ………;работаем в защищенном режиме ;готовимся перейти обратно в реальный режим MOV AL,01FCh OUT 64h,AL ;сброс микропроцессора HLT ;остановка до физического окончания процесса сброса RET_REAL: • • • ;метка, на которую будет передано ;управление после сброса Разработчики из фирмы Intel рекомендуют использовать команду HLT только тогда, кода возникает катастрофическая ошибка оборудования и дальнейшая работа бессмысленна.Синтаксис команды: NOP Семантика команды: пустая команда.
Алгоритм работы: не производит никаких действий. Более тщательный анализ машинного кода 90h показывает, что на самом деле это команда обмена содержимого регистра AX с регистром AX — XCHG AX,AX, что эквивалентно «ничего не выполнять»
Применение:
Очень распространенная хакерская команда с машинным кодом 90h. Команда NOP, занимая один байт, может использоваться для резервирования места в сегменте кода или организации программной задержки. В качестве иллюстрации можно обратиться к примеру, приведенному в описании команды HLT. В этом примере команду NOP можно используют для создания задержки для синхронизации работы микропроцессора и аппаратуры компьютера. Но чаще всего команда используется хакерами для «затирания» каких-то «ненужных» фрагментов кода в чужой программе, например вызов процедур отвечающих за идентификацию пользователя или ограничению числа запусков программы. Эквивалентное действие выполняет команда перехода на следующий адрес за командой перехода — JMP $+ (машинный код EB 00), но это уже двухбайтовая команда.
(Контроль нахождения индекса массива в границах = CHECK ARRAY Синтаксис: BOUND, Семантика команды: проверка нахождения значения индекса в границах массива.
Алгоритм работы: сравнить значение в регистре индекс с двумя значениями, расположенными последовательно в ячейке памяти, адресуемой операндом границы массива. Диапазон значений индекса определяется используемым регистром индекс:
• если это 16-разрядный регистр общего назначения, то содержащееся в нем значение проверяется на попадание в диапазон значений, которые находятся в двух последовательных словах в памяти по адресу, указываемому вторым операндом. Эти два значения являются, соответственно, значениями нижнего и верхнего индекса границы массива;
• если это 32-разрядный регистр общего назначения, то содержащееся в нем значение проверяется на попадание в диапазон значений, которые находятся в двух последовательных двойных словах в памяти по адресу, указываемому вторым операндом. Эти два значения являются, соответственно, значениями нижнего и верхнего индекса границы Если в результате проверки значение из регистра вышло за пределы указанного диапазона значений, то возбуждается прерывание INT 5h, если нет, программа продолжает выполнение.
Применение:
Команда BOUND используется для контроля выхода за нижнюю или верхнюю границы массива. Значения этих границ помещаются в два последовательных слова (двойных слова) в памяти. Адрес этих слов (двойных слов) указывается операндом границы массива. Далее по ходу работы программы значение в регистре индекс, сравнивается со значениями этих двух границ, и если нижняя граница индекс верхняя граница, то программа продолжает выполнение. В противном случае генерируется INT 5h. Далее в программе обработки прерывания INT 5h выполняется необходимая корректировка и возврат в основную программу.
;Фрагмент обработки массива с размерностью элементов в слово ;область данных
BOUNDMAS LABEL WORD
UPP_BOUND DW 200 ;верхняя граница MAS DW 100 DUP (?) XOR DI,DI ;очистка индексного регистра CYCL: MOV AX,MAS[DI] ;перебор элементов массива ADD DI,BOUND DI,BOUNDMAS
;если значение в DI не будет попадать в границы, то будет вызван ;обработчик прерывания 5, где можно скорректировать значение IP/EIP ;в стеке с тем, чтобы выйти из бесконечного цикла, например, на метку ;М2 или выполнить другие действияJMP CYCL
М2:Практически все ранние компьютеры характеризовались основанием 10 - представление на основе десятичных цифр, как каждого из нас учат в школе.
Однако двоичное представление с использованием двоичных цифр является, очевидно, более экономичным. Для представления целого числа n требуется log10(n) десятичных цифр, но всего лишь log2(n) двоичных цифр (бит). Поскольку для представления десятичной цифры требуется четыре бита, для десятичного представления требуется тем больше памяти, чем больше разрядов нам требуется, что показывает очевидное преимущество двоичной формы. Тем не менее, разработчики долгое время сохраняли десятичное представление, и оно присутствует и сегодня в форме библиотечного модуля. Это связано с тем, что разработчики продолжали верить в необходимость точности всех вычислений.
десятичные числа двоичные числа На % больше Однако ошибки возникают при округлении, например, после выполнения операции деления. Эффекты округления могут различаться в зависимости от способа представления чисел, и двоичный компьютер может выдать результаты, отличающиеся от результатов десятичного компьютера.
Поскольку финансовые транзакции – где более всего существенна точность – традиционно выполнялись вручную с использованием десятичной арифметики, разработчики полагали, что компьютеры должны производить во всех случаях те же результаты – и, следовательно, фиксировать те же ошибки.
Двоичная форма в общем случае приводит к более точным результатам, но десятичная форма остается предпочтительным вариантом в финансовых приложениях, поскольку десятичный результат в случае потребности легко проверить вручную.
Эта понятная идея, очевидно, являлась консервативной. Заметим, что до пришествия в 1964 г. IBM System/360, в которой поддерживалась как двоичная, так и десятичная арифметика, производители крупных компьютеров предлагали две линейки продуктов: двоичные компьютеры для научных потребителей и десятичные компьютеры для коммерческих потребителей – дорогостоящий подход. А сам наш любимый Pentium — не монстр? С его одним реальным и двумя защищенными режимами? С селекторами и дескрипторами, которые то ли нужны, то ли совсем не нужны.
С режимом V86 для эмуляции MS-DOS (да гнать ее в три шеи, кому она нужна! С командами, которые никто не использует (а ну-ка, навскидку, что делает команда AAA ?) Десятичные числа — специальный вид представления числовой информации, в основу которого положен принцип кодирования каждой десятичной цифры числа тетрадой — группой из четырех бит. При этом каждый байт числа содержит одну или две десятичные цифры в так называемом двоично-десятичном коде (BCD — Binary-Coded Decimal). Микропроцессор хранит BCD-числа в двух форматах:
• упакованном формате — в этом формате каждый байт содержит две десятичные цифры. Десятичная цифра представляет собой двоичное значение в диапазоне от 0 до 9 размером 4 бита. При этом код старшей цифры числа занимает старшую тетраду. Следовательно, диапазон представления десятичного упакованного числа в одном байте составляет от 00 до 99;
• неупакованном формате — в этом формате каждый байт содержит одну десятичную цифру в младшей тетраде. Старшая тетрада имеет нулевое значение. Это так называемая зона. Следовательно, диапазон представления десятичного упакованного числа в одном байте составляет от 0 до 9.
На рисунке 26 упакованное десятичное число 5674304:
На рисунке 27 двоичный эквивалент упакованного десятичного числа 5674304:
На рисунке 28 неупакованное десятичное число 99857:
На рисунке 29 двоичный эквивалент неупакованного десятичного числа 99857:
Для описания двоично-десятичных чисел в программе используют директивы описания и инициализации данных DB и DT.
Пример: Y1 DB 2,3,4,6,8,2 ;неупакованное BCD-число Y2 DT 9875645 ;упакованное BCD-число BCD-числа остались как наследство с тех пор, когда фирма Intel создавала микропроцессоры для карманных калькуляторов. Впоследствии, BCD-числа стали использовать в финансовых приложениях, там, где числа должны были быть большими и точными. BCD-числа очень легко представляются в символьном виде (в ASCII или EBCDIC-кодировке). Перевод BCD-числа в ASCII код сводится к добавлению к неупакованному BCDчислу величины 30h.
Когда я программировал на ассемблере-x86, я понял, что AAx инструкции имеют огромное значение. Да, я имею в виду BCD-инструкции, сделанные для программистов, которые не могут считать в шестнадцатеричной системе.
Многие процессоры Intel, если не все, могут "считать" десятичными числами и целыми числами между и 9 в четырехбитных единицах памяти (здесь имеется в виду представление десятичных чисел в шестнадцатеричном виде: например, 25d = 25h (1 байт - две четырех битные единицы памяти)), а также складывать, вычитать, умножать и делить их.
Во многих книгах и учебниках по ассемблеру, эти инструкции называют "неэффективными" или "полностью бесполезными".
Давайте посмотрим на них:
AAA: ASCII коррекция после сложения AAS: ASCII коррекция после вычитания AAM: ASCII коррекция после умножения AAD: ASCII коррекция после деления Хотя эти команды занимают второе место в иерархии инструкций, Intel зарезервировали четыре кода операции (опкода) (на самом деле шесть, если вы используете DAA и DAS) и один флаг (дополнительного переноса - AF) для них, что заслуживает некоторого рассмотрения.
К несчастью, ни одна из этих инструкций недокументированна Intel на должном уровне. То, что пойдет далее, Вы скорее всего знаете, но я объясню, что эти инструкции делают на самом деле.
Затем мы рассмотрим чем могут помочь BCD-инстуркции в оптимизации по размеру.
AAA - AAS Это инструкции "ASCII-коррекции AL после сложение/вычитания, используя AF, чтобы определить был ли перенос" Позвольте мне объяснить, что из себя представляет флаг AF. AF отвечает за дополнительный перенос.
Флаг AF устанавливает в 1, когда арифметическая операция вызывает перенос или заем из третьего бита результата.
Например, 5+2=7: AF = Однако, 9+8=17=11h, что больше, чем 0Fh: AF= Согласно документации Intel, AAA/AAS могут быть использованы только после инструкций ADD/SUB;
но мы можем использовать AAA, и AAS после любой арифметической команды (они все изменяют AF), включая INC/DEC и CMP.
Давайте, посмотрим что делает инструкция AAA. Ниже приведен эквивалент в псевдокоде (от Intel):
IF ((AL AND 0FH) > 9) OR (AF = 1) THEN ELSE END IF AL = AL AND 0FH Согласно документации Intel, эти команды нужно использовать так:
mov ax, add al, ;до коррекции ax=0Fh aaa ;после коррекции ax=105h => это 15 в неупакованном десятичном виде!
Но что произойдет, если Вы напишите, например, следующее:
mov ax,00F6h add al, ;до коррекции ax=00FFh aaa ;после коррекции ax=0205h Если Вы придерживаетесь документации Intel, Вы должны получить AX=0105h, а не 0205h. В этом случае псевдокод Intel не верен. Я думаю, что это работало на процессорах 8086, но новейшие процессоры использую этот псевдо-код:
IF ((AL AND 0FH) > 9) OR (AF = 1)THEN ELSE END IF;
AL = AL AND 0FH;
Для AAS существует такая же проблема, вы только должна заменить "+" на "-".
А как насчет флагов? AF и CF установлены как описано выше. OF остается без изменений, а SF очищается.
ZF = 1, если AX=0, PF устанавливается в зависимости от значения AL (логично, не правда ли?).
AAD - AAM Это наиболее интересные инструкции. Они описаны Intel, как "ASCII коррекция AX перед делением/после умножения" Раньше этот код не был документирован, но теперь его можно увидеть в документации от Intel:
;x - аргумент (1 байт) AAD x: AL = AH*x + AL AAM x: AH = AL/x Intel не говорит, что все флаги (SF, ZF, PF, OF, CF, AF) устанавливаются в зависимости от результата выполнения, точно также, как с любой арифметической командой. Для AAM, флаги устанавливаются согласно результату 'mod'-операции (AL mod x). Это означает, что SF, ZF и PF установлены согласно AL, а CF и AF очищены.
Для AAD, флаги устанавливаются согласно результату "+" операции (AH*x)+AL Итак, когда используются восьмибитные значения, AAD и AAM хорошая альтернатива MUL и DIV: короче код и есть возможность использовать флаги.
Используя эти команды с параметрами 0 или 1, можно выполнять комбинации из команд ADD, CMP, MOD:
Простые примеры.
Итак, как мы можем использовать AAx инструкции? Вот несколько примеров:
1. Проверка значения в AL Для проверки равен ли EAX 0 или 1, достаточно одной инструкции:
aaa или aas так как это эквивалент al=al and 0Fh jz @zero 2. Как проверить границы? Если Вы хотите проверить находится равно ли AL одному из чисел, задающим границу, Вы могли бы сделать что-нибудь подобное:
cmp al,MIN jz @bound_reached cmp al,MAX jz @bound_reached Используя AAM, код можно сократить:
sub al,MIN aam MAX-MIN jz @bound_reached 3. Дискретная гомотетия AL=AL+k[AL/x] Комбинация AAM и AAD является идеальной в случае, если после двух инструкций, AL увеличивается в K раз по сравнению с AL/X aam X aad X+K 4. Это очень красивый способ заменить блок CMP/Jcc/ADD инструкций. Например, если вы хотите изменить регистр ASCII символов в множестве [0-9][A-Z][a-z], вы можете написать:
aam 40h aad 60h ;в нижний регистр aam 60h aad 40h ;в верхний регистр 5. Комплексный пример.
Если Вы все еще не убедились в полезности команд AAx, вот пример, который может быть Вам действительно полезен.
Представьте: мы знаем, что AL это одно из десяти чисел (числа выбраны случайно): 01h, 25h, 39h, B3h, 78h, C4h, 6Bh, A6h, 12h, и F0h. Мы хотим проверить в каком множестве есть это число: A={25h, 6Bh, 78h, B3h, C4h} или B={01h, 12h, 39h, A6h, F0h}?
Конечно, мы можем сравнить AL с каждым значением множества A:
cmp al,25h jz @set_A cmp al,6Bh jz @set_A cmp al,78h jz @set_A cmp al,0B3h jz @set_A cmp al,0C4h jz @set_A @set_B:...
...
@set_A:...
...
Но мы можем также использовать AAD и AAM. Нам также нужно множество флагов, возвращаемое инструкцией AAD с вероятностью около 1/2: оба флага SF и PF подходят. Итак, нам нужна маленькая программа на C для того чтобы попробовать 256*256*4 возможных решений и найти решение следующей формы:
aam X aad Y jcc @set_A Jcc может быть jp, jnp, js или jns.
Эта программа ищет решения, используя PF (флаг четности) и SF (знаковый флаг).
#include #define byte unsigned int #define NR byte al,ah,x,y,k,found;
//Массив значений byte set[NR]={0x01,0x25,0x39,0xB3,0x78,0xC4,0x6B,0xA6,0x12,0xF0};
//Массив результатов : 0 для set_A, 1 для set_B yte res[NR]={1, 0, 1, 0, 0, 0, 0, 1, 1, 1};
//P-флаг массив byte p[NR] ={0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
//S-флаг массив byte s[NR] ={0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
main () {