assembler_na_primeraxbazovyy_kurs_rudolf_marek
.pdf
Глава 6. Прочие команды
Еще одна очень нужная функция — это функция сравнения двух строк, ко торая в языке С называется strcmp().
Рис. 6.4. Блок-схема функции strcmp
Подпрограмма сравнивает две строки: адрес первой сохранен в ES:(E)DI, a адрес второй — в DS:(E)SI. Если строки одинаковы, то после завершения под программы ЕСХ будет содержать 0, а если нет, то в ЕСХ будет количество пер вых одинаковых символов. Код нашей strcmp () приведен в листинге 6.2.
Листинг 6.2. Код функции strcmp()
strcmp: |
|
push edx |
;помещаем EDX в стек |
push edi |
/помещаем EDI в стек |
call strlen |
;вычисляем длину первой строки |
mov edx,ecx |
;сохраняем длину первой строки в EDX |
mov edi,esi |
;EDI = ESI |
;push ds |
;сохраняем DS в стек |
;push ds |
;еще раз |
;pop es |
;а теперь загружаем его в ES (ES = DS) |
call strlen |
;теперь вычисляем длину второй строки |
;pop ds |
; восстанавливаем исходный DS |
cmp ecx,edx |
;какая строка длиннее? |
jae .length_ok |
;переходим, если длина первой строки (ЕСХ) |
|
;больше или равна длине второй |
83
Ассемблер на примерах. Базовый курс
mov ecx,edx |
;будем использовать более длинную строку |
.length_ok: |
|
pop edi |
;восстанавливаем исходный EDI |
eld |
;DF = О |
repe empsb |
;повторяем сравнение, пока не встретим два |
|
;разных символа или пока ЕСХ не станет |
|
/равен О |
pop edx |
; восстанавливаем исходное значение EDX |
ret |
;конец |
Наша функция stremp будет работать только тогда, когда значения сегментных регистров DS и ES равны. Корректнее было бы раскомментировать заком ментированные команды — наша функция смогла бы работать со строками, расположенными в разных сегментах. Однако в большинстве программ сег ментные регистры равны, поэтому нам не нужно учитывать их разницу.
На рис. 6.5 вы можете видеть содержимое регистров после сравнения не одинаковых строк.
ECX=0xA
Рис. 6.5. Содержимое регистров до и после вызова stremp
6.5. Команды ввода/вывода (I/O)
Периферийные устройства используют так называемые шлюзы ввода/вывода — обычно их называют портами ввода/вывода. С помощью портов ваша про грамма (или сам процессор) может «общаться» с тем или иным периферийным устройством. Для «общения» с периферийным или другим устройством на аппаратном уровне используются команды IN и OUT.
Команды IN и OUT — обмен данными с периферией
Команда IN позволяет получить от устройства, a OUT — передать устройству порцию данных в размере байта, слова или двойного слова.
IN |
al, |
dx |
OUT |
dx, |
al |
|
IN |
ax, |
dx |
OUT dx, |
ax |
|
|
IN |
eax, |
dx |
OUT dx, |
eax |
||
IN a l , |
imm8 |
OUT |
imm8, |
al |
||
IN ax, |
imm8 |
OUT |
imm8, |
ax |
||
84
Глава 6. Прочие команды
Команда IN читает данные из порта ввода/вывода, номер которого содержится в регистре DX, и помещает результат в регистр AL/AX/EAX. Другие регистры, кроме AL/AX/EAX и DX, использовать нельзя.
Команда OUT отправляет данные в порт. Типы ее операндов такие же, как у IN, но обратите внимание: операнды указываются в обратном порядке.
Диапазоны портов ввода/вывода, которые связаны с различными устройства ми, перечислены в табл. 6.1.
Диапазоны портов ввода/вывода |
Таблица 6.1 |
|
|
|
|
0000-001f :dma1 |
Первый контроллер DMA (Direct Memory Access) |
|
|
|
|
0020-003f: p i d |
Первый аппаратный контроллер прерываний |
|
|
|
|
0040-005f: timer |
Системный таймер |
|
|
|
|
0060-006f: keyboard |
Клавиатура |
|
|
|
|
0070-007f: rtc |
Часы реального времени (RTC, real time clock) |
|
|
|
|
0080-008f: dma page reg |
DMA page register |
|
|
|
|
OOaO-OObf: pic2 |
Второй аппаратный контроллер прерываний |
|
|
|
|
OOcO-OOdf: dma2 |
Второй DMA-контроллер |
|
|
|
|
OOfO-OOff: fpu |
Математический сопроцессор |
|
|
|
|
0170-0177 :ide1 |
Второй IDE-контроллер (Secondary) |
|
|
|
|
01f0-01f7:ide0 |
Первый IDE-контроллер (Primary) |
|
|
|
|
0213-0213 :isapnp read |
Интерфейс PnP (plug-and-play) шины ISA |
|
|
|
|
0220-022f: soundblaster |
Звуковая плата |
|
|
|
|
0290-0297 :w83781d |
Аппаратный мониторинг температуры |
|
|
и напряжения |
|
|
|
|
0376-0376 : idel |
Второй IDE-контроллер (продолжение) |
|
|
|
|
03c0-03df:vga+ |
Видеоадаптер |
|
|
|
|
03f2-03f5: floppy |
Дисковод для гибких дисков |
|
|
|
|
03f6-03f6: ideO |
Первый IDE-контроллер (продолжение) |
|
|
|
|
03f7-03f7: floppy DIR |
Дисковод для гибких дисков (продолжение) |
|
|
|
|
03f8-03ff: lirc_serial |
Последовательный порт |
|
|
|
|
0a79-0a79: isapnp write |
Интерфейс PnP (plug-and-play) шины ISA |
|
|
(продолжение) |
|
|
|
|
0cf8-0cff: PCI conf 1 |
Первый конфигурационный регистр шины PCI |
|
|
|
|
4000-403f: Intel Corp. 82371AB/EB/MB PIIX4 ACPI |
Чипсет ACPI |
|
|
|
|
5000-50И: Intel Corp. 82371AB/EB/MB PIIX4 ACPI |
Чипсет ACPI |
|
|
|
|
e000-e01f: Intel Corp. 82371AB/EB/MB PIIX4 USB |
Чипсет USB |
|
|
|
|
fOOO-fOOf: Intel Corp. 82371AB/EB/MB PIIX4 IDE |
Чипсет контроллера дисков |
|
|
|
|
85
Ассемблер на примерах. Базовый курс
Рассмотрение функций отдельных регистров периферийных устройств выходит далеко за рамки этой книги. Вот просто пример:
in al,0x60 /чтение значения из порта с номером 0x60 ; ( э т о скан-код последней нажатой клавиши)
Организация задержки. Команда NOP
Название этой команды звучит как «No Operation», то есть это ничего не делающая, «пустая» инструкция.
пор |
;ничего не делать |
Для чего же нужна такая команда? Когда программа работает с портами вво да/вывода, между операциями чтения и записи нужно делать паузы. Причина ясна: периферийные устройства работают медленнее процессора, и им нужно время, чтобы обработать запрос. Для организации задержки и используют NOP. Выполнение этой команды занимает один цикл процессора — маловато, поэтому чаще задержку организуют при помощи безусловного перехода на следующую команду:
jmp short delayl delayl:
Поскольку команда короткого перехода занимает 2 байта, тот же переход на следующую команду можно записать так:
jmp short $+2 ;перейти на 2 байта вперед Знак доллара означает текущий адрес.
Новые процессоры снабжены схемой предсказания переходов, то есть еще до самого перехода процессор будет знать, что ему нужно сделать. Предска занный переход будет выполнен за меньшее число циклов процессора, чем непредсказанный, поэтому на новых процессорах задержку с помощью коман ды JMP организовать нельзя. Лучшим способом «подождать» периферийное устройство будет запись произвольного значения в «безопасный» порт — это диагностический порт с номером 0x80:
out 0x80,al ; это просто задержка
6.6. Сдвиг и ротация
Операции поразрядного сдвига перемещают отдельные биты в байте, слове или двойном слове. Сдвиг может быть влево или вправо. При сдвиге вправо на одну позицию крайний правый (младший) бит теряется, а крайний левый замещается нулем. При сдвиге влево теряется крайний левый (старший) бит, а крайний правый замещается нулем. При сдвиге на несколько позиций «выталкивается» указанное количество битов, а «освободившиеся» биты замещаются нулями.
86
Глава 6. Прочие команды
Ротация (также называемая циклическим сдвигом) не выталкивает биты, а возвращает их на место «освободившихся». В результате ротации вправо крайний правый бит встанет на место крайнего левого, а остальные биты будут сдвинуты на одну позицию вправо.
Где это может использоваться, вы поймете из описания самих команд.
Команды SHR и SHL — сдвиг беззнаковых чисел
Команды SHR и SHL поразрядно сдвигают беззнаковые целые числа вправо и влево соответственно. Это самый быстрый способ умножить или разделить целое число на степень двойки.
Представим число 5 в двоичной системе — 0101b. После умножения на 2 мы получим 10, в двоичной системе это число 01010b. Сравнивая два двоичных числа, вы, наверное, заметили, как можно быстро получить из числа 5 число 10: с помощью сдвига на один бит влево, добавив один нуль справа.
Точно так же можно умножить число на любую степень двух. Например, вместо умножения на 16 (2 в степени 4) можно сдвинуть исходное число на 4 бита влево.
0 0 0 0 0 1 0 1 0
I I I I I I I I
Рис. 6.6. Умножение 5 на 2 методом поразрядного сдвига влево
Деление на степень двойки выполняется по такому же принципу, только вместо поразрядного сдвига влево нужно использовать сдвиг вправо.
Команде SHL нужно передать два операнда:
SHL o l , о2
Первый должен быть регистром или адресом памяти, который нужно сдвинуть. Второй операнд определяет число позиций, на которое нужно сдвинуть. Чаще всего это непосредственное значение. Можно использовать в качестве второ го операнда и регистр, но только CL — это касается всех операций сдвига и ротации. Сдвиг возможен не более чем на 32 позиции, поэтому принимается в расчет не весь второй операнд, а остаток от его деления на 32.
Старший «вытолкнутый» бит сохраняется в флаге переноса CF, а младший бит заменяется нулем. Кроме флага CF используется флаг знака (SF) и флаг
t |
87 |
|
Ассемблер на примерах. Базовый курс
переполнения (OF). За этими флагами нужно следить, выполняя действия над числами со знаком, чтобы избежать превращения положительного числа в от рицательное и наоборот (в этом случае флаги SF и OF устанавливаются в 1).
То же самое, что и SHL, только биты сдвигаются вправо:
SHR o l , о2
Младший бит перемещается в CF, а старший заменяется нулем. Принцип работы SHR показан на рис. 6.7.
Рис. 6.7. Как работает SHR
А теперь рассмотрим несколько примеров, которые помогут вам лучше усво ить прочитанное.
Пример: используя SHR, разделим АХ на 16, не обращая внимания на пере полнение:
shr ах,4 |
;сдвигаем АХ на 4 бита вправо |
Пример: подсчитаем количество двоичных единиц в АХ и сохраним результат
B B L .
АХ — 16-разрядный регистр, поэтому мы должны сдвинуть его влево или вправо 16 раз. После каждого сдвига мы анализируем флаг переноса CF, в который попал вытолкнутый бит, что очень легко сделать с помощью инструк ции JC. Если CF равен единице, то увеличиваем BL.
mov |
Ы , 0 |
;инициализируем счетчик |
единиц BL=0 |
||
mov ex, 16 |
; СХ = 16 |
|
|
|
|
r e p e a t : |
|
|
|
|
|
shr |
ax, 1 |
;сдвигаем на 1 бит вправо, младший бит |
|||
|
|
;попадает в |
CF |
|
|
jnc |
not_one |
;если это |
0, |
пропускаем |
следующую команду |
inc |
Ы |
;иначе — увеличиваем BL на 1 |
|||
not _ one: |
|
|
|
|
|
loop |
r e p e a t |
;повторить |
16 |
раз |
|
После выполнения этого фрагмента кода регистр BL будет содержать коли чество единиц регистра АХ, а сам регистр АХ будет содержать 0.
88
Глава 6. Прочие команды
Команды SAL и SAR — сдвиг чисел со знаком
Команды SAL и SAR используются для поразрядного сдвига целых чисел со знаком (арифметического сдвига). Команда SAL — это сдвиг влево, а команда SAR -- вправо.
Формат команд таков:
SAL o l , о2
SAR o l , о2
Команда SAR сдвигает все биты, кроме старшего, означающего знак числа — этот бит сохраняется. Младший бит, как обычно, вытесняется в CF. Операнды обеих инструкций такие же, как у SHL и SHR.
Рис. 6.8. Как работает SAR
Команды RCR и RCL — ротация через флаг переноса
Эти команды выполняют циклический поразрядный сдвиг (ротацию). RCR действует точно так же, как SHR, но вместо нуля в старший бит первого операнда заносится предыдущее содержимое CF. Исходный младший бит вытесняется в CF. Команда RCL работает подобно RCR, только в обратном направлении.
Формат команд таков:
RCR o l , о2
RCL o l , о2
Рис. 6.9. Как работает RCR
Рис. 6.10. Как работает RCL
89
Ассемблер на примерах. Базовый курс
Команды ROR и ROL — ротация с выносом во флаг переноса
Эти команды выполняют другой вариант циклического сдвига: ROR сначала копирует младший бит первого операнда в его старший бит, а потом заносит его в CF; ROL работает в обратном направлении.
ROR o l , о2
ROL o l , о2
Операнды этих команд аналогичны операндам команд RCR и RCL.
Рис. 6.11. Как работает ROR
6.7. Псевдокоманды
Некоторые из команд, рассмотренных нами, могут работать с операндом, рас положенным в памяти. Классический пример — команда MOV AX, [number], загружающая в регистр АХ значение из области памяти, адрес которой пред ставлен символическим обозначением «number». Но мы до сих пор не знаем, как связать символическое обозначение и реальный адрес в памяти. Как раз для этого и служат псевдокоманды.
Предложения языка ассемблера делятся на команды и псевдокоманды (дирек тивы). Команды ассемблера — это символические имена машинных команд, обработка их компилятором приводит к генерации машинного кода. Псев докоманды же управляют работой самого компилятора. На одной и той же аппаратной архитектуре могут работать различные ассемблеры: их команды обязательно будут одинаковыми, но псевдокоманды могут быть разными.
Псевдокоманды DB, DW и DD — определение констант
Чаще всего используется псевдокоманда DB (define byte), позволяющая определить числовые константы и строки. Рассмотрим несколько примеров:
db |
0x55 |
;один байт в шестнадцатеричном виде |
|
d b |
0 x 5 5 , 0 x 5 6 , 0 x 5 7 |
;три последовательных байта: 0x5 5, |
|
|
|
;0х5б, |
0x57 |
db 'а',0x55 |
;можно |
записать символ в одинарных |
|
;кавычках.
;Получится последовательность 0x61, 0x55 db 'Hello',13,10,'$' ;можно записать целую строку.
;Получится 0x48, 0x65, ОхбС, ОхбС, ;0x6F, OxD, OxA, 0x24
90
Глава 6. Прочие команды
Для определения порции данных размера, кратного слову, служит директива DW (define word):
dw 0x1234 |
;0х34, |
0x12 |
dw ' a ' |
;0x61, |
0x00: второй байт заполняется нулями |
Директива DD (define double word) задает значение порции данных размера, кратного двойному слову:
dd0x12345678 ;0х78 0x56 0x34 0x12
dd 1.234567e20 ;так определяются числа с плавающей точкой
Авот так определяется переменная, тот самый «number»:
number dd 0x1 ;переменная number инициализирована ;значением 1
Переменная «number» теперь представляет адрес в памяти, по которому за писано значение 0x00000001 длиной в двойное слово.
Псевдокоманды RESB, R E S W H RESD — объявление переменных
Вторая группа псевдокоманд позволяет определить неинициализированные данные. Если в первом случае мы заносим данные в память, то сейчас мы просто укажем, сколько байтов нужно зарезервировать для будущих данных. Память для этих неинициализированных переменных распределяется дина мически, а сами данные не содержатся в исполнимом файле. Грубо говоря, предыдущую группу инструкций удобно использовать для определения кон стант, а эту группу — для переменных.
Для резервирования памяти служат три директивы: RESB (резервирует байт), RESW (резервирует слово) и RESD (резервирует двойное слово). Аргументом этих псевдокоманд является количество резервируемых позиций:
resb |
1 |
|
|
;резервирует |
1 |
байт |
|
|
resb |
2 |
|
|
;резервирует |
2 |
байта |
|
|
resw |
2 |
|
|
;резервирует |
4 байта |
(2 |
слова) |
|
resd |
1 |
|
|
;резервирует |
4 |
байта |
|
|
number |
resd |
l |
;резервирует |
4 |
байта |
для |
переменной |
|
|
|
|
|
;"number" |
|
|
|
|
buffer |
resb |
64 |
,-резервирует |
64 |
байта |
для |
||
|
|
|
|
;переменной |
buffer |
|
|
|
Некоторые ассемблеры поддерживают дополнительные директивы выделения памяти, но мы будем использовать ассемблер NASM, который их не поддер живает. Поэтому мы ограничимся директивами RESx.
Псевдокоманда TIMES — повторение следующей псевдокоманды
Директива TIMES — это псевдокоманда префиксного типа, то есть она ис пользуется только в паре с другой командой. Она повторяет последующую
91
Ассемблер на примерах. Базовый курс
псевдокоманду указанное количество раз, подобно директиве DUP из ассем блера Borland TASM. Применяется эта директива в тех случаях, когда нужно «забить» некоторую область памяти повторяющимся образцом.
Следующий код определяет строку, состоящую из 64 повторяющихся «Hello»: many_hello: times 64 db 'Hello'
Первый аргумент, указывающий количество повторений, может быть и вы ражением. Например, задачу «разместить строку в области памяти размером в 32 байта и заполнить пробелами оставшееся место» легко решить с помощью директивы TIMES:
buffer |
db "Hello" |
;определяем |
строку |
times |
32-($-buffer) db ' ' |
/определяем |
нужное |
|
|
;количество |
пробелов |
Выражение 32-($-buffer) возвратит значение 27, потому что $-buffer равно текущей позиции минус позиция начала строки, то есть 5.
Вместе с TIMES можно использовать не только псевдокоманды, но и команды процессора:
times 5 inc eax |
;5 раз выполнить INC EAX |
В результате будет сгенерирован код:
inc eax inc eax inc eax inc eax inc eax
Псевдокоманда INCBIN — подключение двоичного файла
Эта директива будет полезна более опытным разработчикам. Она упаковывает графические или звуковые данные вместе с исполняемым файлом:
incbin |
"sound.wav" |
;упаковываем |
весь файл |
incbin |
"sound.wav",512 |
/пропускаем |
первые 512 байтов |
incbin |
"sound.wav",512,80 |
/пропускаем первые 512 байтов и |
|
|
|
/последние 80 |
|
Псевдокоманда EQU — вычисление константных выражений
Эта директива определяет константу, известную во время компиляции. В ка честве значения константы можно указывать также константное выражение. Директиве EQU должно предшествовать символическое имя:
four EQU 4 ; тривиальный пример.
/Позже я покажу и нетривиальные
92
