Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

assembler_na_primeraxbazovyy_kurs_rudolf_marek

.pdf
Скачиваний:
76
Добавлен:
12.02.2015
Размер:
2 Мб
Скачать
☆

Глава 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

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]