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

assembler_na_primeraxbazovyy_kurs_rudolf_marek

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

Глава 5. Управляющие конструкции

ние адресу, заданному оригинальным значением ЕАХ, что могло бы вызвать сбой нашей программы, а то и всей операционной системы. То же самое произойдет, если мы забудем написать команду RET: процессор продолжит выполнение с того места, где заканчивается наша подпрограмма, и рано или поздно совершит недопустимое действие.

Мы можем упростить нашу подпрограмму add_it, полностью отказавшись от инструкций POP и PUSH:

a d d _ i t :

mov

ecx,eax

;копируем значение

ЕАХ

(первый параметр) в ЕСХ

add

ecx,ebx

;добавляем

к нему

ЕВХ

(второй параметр),

 

 

;результат

будет сохранен в регистре ЕСХ

r e t

 

; возвращаемся

 

 

Команды INT и IRET: вызываем прерывание

Вернемся к теме прерываний. Прерыванием называется такое событие, когда процессор приостанавливает нормальное выполнение программы и начинает выполнять другую программу, предназначенную для обработки прерывания. Закончив обработку прерывания, он возвращается к выполнению приоста­ новленной программы.

Во второй главе было сказано, что все прерывания делятся на две группы: программные и аппаратные. Программные прерывания порождаются по ко­ манде INT. Программные прерывания можно рассматривать как «прерывания по требованию», например, когда вы вызываете подпрограмму операционной системы для вывода строки символов. В случае с программным прерыванием вы сами определяете, какое прерывание будет вызвано в тот или иной момент. Команде INT нужно передать всего один 8-битный операнд, который задает номер нужного прерывания.

INT op

Аппаратные прерывания вызываются аппаратными средствами компьютера, подключенными к общей шине (ISA или PCI). Устройство, запрашивающее прерывание, генерирует так называемый запрос на прерывание (IRQ, interrupt requests). Всего существует 16 аппаратных запросов на прерывание, поскольку только 16 проводников в шине ISA выделено для этой цели. Запрос на пре­ рывание направляется контроллеру прерываний, который, в свою очередь, за­ прашивает микропроцессор. Вместе с запросом он передает процессору номер прерывания. После запуска компьютера и загрузки операционной системы DOS, IRQ 0 (системный таймер) соответствует прерыванию 8 (часы).

Когда процессор получает номер прерывания, он помещает в стек контекст выполняемой в данный момент программы, подобно тому, как человек кладет в книгу закладку, чтобы не забыть, с какого места продолжить чтение книги. Роль закладки играют значения CS, (Е)1Р и регистр флагов.

73

Ассемблер на примерах. Базовый курс

Теперь процессор будет выполнять другую программу — обработчик пре­ рывания. Адрес этой программы называется вектором прерывания. Векторы прерывания хранятся в таблице векторов прерываний, находящейся в памяти. Таблицу прерываний можно представить себе как массив адресов подпро­ грамм, в котором индекс массива соответствует номеру прерывания.

После того, как процессор определит адрес обработчика прерывания, он за­ пишет его в пару CS и (Е)1Р. Следующая выполненная команда будет первой командой обработчика прерывания.

В десятой главе, посвященной программированию в DOS, мы опишем функции 21-го (0x21) прерывания, которое генерируется следующей инструкцией:

i n t 0x21

; вызов DOS

Возврат из обработчика прерывания осуществляется с помощью команды IRET, которая восстанавливает исходные значения (E)IP, CS и флагов из стека. Формат команды:

IRET

Давайте разберемся, как вызывается прерывание на примере 21-го преры­ вания (рис. 5.9). Мы считаем, что процессор работает в реальном режиме с 16-битной адресацией.

Рис. 5.9. Загрузка в CS и IP новых значений

Прежде всего процессор находит номер прерывания. Программные преры­ вания вызываются инструкцией INT, которая содержит номер прерывания в своем операнде. Следующий шаг — сохранение контекста программы в стеке. Продемонстрируем этот процесс с помощью команд ассемблера:

pushf

/сохраняем значения регистра признаков в стеке

 

push cs

;сохраняем регистр CS в стеке

74

 

Глава 5. Управляющие конструкции

push ip

;эта команда недопустима. Мы написали ее с

 

демонстрационной целью. Реализовать ее можно так:

 

call here

 

here:

Последний шаг — это безусловный переход на адрес, прочитанный из таблицы прерываний по номеру запрошенного прерывания: JMP far.

Здесь мы слегка забегаем вперед: пока просто примите, что таблица векто­ ров прерываний находится в самом начале адресуемой памяти, то есть по адресу 0x0000:0x0000. Каждый из векторов прерывания занимает четыре байта. Первые два байта определяют новое значение IP (то есть смещение), а оставшиеся два — новое значение сегментного регистра CS. Вектор пре­ рывания номер 0x21 хранится по адресу 0х0000:(0х21*4), поэтому его вызов можно записать так:

jmp far [0x21*4]

; эта инструкция переходит на указанный

 

;адрес, предполагаем, что DS=0, поэтому

 

;мы можем адресовать память с адреса

 

;0х0000:

0x0000)

Команду INT можно реализовать самостоятельно с помощью команд PUSHF и CALL far:

pushf

;сохраняем флаги в стеке

call far [0x21*4]

; теперь сохраняем CS и IP

 

;и выполняем "jump"

Программные прерывания часто используются как шлюз для вызова функций операционной системы. Подробнее о них мы поговорим в главах, посвященных отдельным операционным системам.

75

ева 6 Прочие команды

Изменение регистра признаков напрямую

Команда XCHG — меняем местами операнды

Команда LEA — не только вычисление адреса

Команды для работы со строками

Команды ввода/вывода (I/O)

Сдвиг и ротация

Псевдокоманды

Советы по использованию команд

Ассемблер на примерах. Базовый курс

В этой главе мы рассмотрим наиболее часто используемые команды системы команд процессора х86. С помощью этих команд вы сможете написать до­ статочно сложные программы для решения самых разнообразных задач.

6.1. Изменение регистра признаков напрямую

Несколько команд позволяют непосредственно модифицировать флаги реги­ стра признаков. Мы рассмотрим четыре команды, которые изменяют флаги IF и ID, то есть флаг прерывания и флаг направления.

Команды СИ и STI

Команды CLI (Clear Interrupt) и STI (Set Interrupt) сбрасывают и устанав­ ливают флаг прерывания IF. Иными словами, при их помощи вы можете за­ претить или разрешить аппаратные прерывания. Если этот флаг установлен (1), аппаратные прерывания разрешены. Команда CLI сбрасывает флаг (0) — запрещает аппаратные прерывания. Потом вы должны снова разрешить их, выполнив команду STI:

c l i

;отключить прерывания — использовать только в DOS!

. . .

;теперь мы никем не будем потревожены и можем

 

/выполнять какие-то действия, например, изменять

 

;таблицу прерываний

s t i

;включаем прерывания снова

Команды STD и CLD

Команды STD и CLD модифицируют значение флага DF. Этим флагом поль­ зуется группа команд для обработки строк, поэтому подробнее о нем мы по­ говорим в следующей главе. Команда CLD сбрасывает этот флаг (что означает отсчет вверх), a STD устанавливает его (отсчет в обратном порядке).

77

Ассемблер на примерах. Базовый курс

Формат команд простой:

STD

CLD

6.2. Команда XCHG — меняем местами операнды

Очень часто нам нужно поменять местами значения двух регистров. Конечно, можно это сделать, используя третий, временный, регистр, но намного проще использовать инструкцию XCHG (exchange — обмен), которая как раз для этого и предназначена.

XCHG o l , o 2

Подобно инструкции MOV, она имеет два операнда — ol и о2. Каждый из них может быть 8-, 16- и 32-разрядным, и только один из них может находиться в памяти — требования такие же, как у команды MOV.

xchg еах,еах

/меняем местами значения ЕАХ и ЕАХ.

 

 

;Так реализован NOP — пустой оператор

xchg ebx,ecx

;меняем местами значения ЕВХ и ЕСХ

xchg

al,ah

;меняем местами значения AL и АН

xchg

dl,ah

;меняем местами значения DL и АН

xchg byte [variable],cl ;меняем местами один байт памяти и CL

6.3. Команда LEA —

не только вычисление адреса

Имя этой команды — это сокращение от «Load Effective Address», то есть «загрузить эффективный адрес». Она вычисляет эффективный адрес второго операнда и сохраняет его в первом операнде (который может быть только регистром). Синтаксис этой команды требует, чтобы второй операнд был за­ ключен в квадратные скобки, но фактически она не адресует память.

LEA o l , [o2]

LEA полезна в тех случаях, когда мы не собираемся обращаться к памяти, а адрес нужен нам для другой цели:

lea edi, [ebx*4+ecx] ;загружает в регистр

EDI адрес,

;вычисленный как EDI

= ЕВХ*4+ЕСХ

Значение, вычисленное командой LEA, не обязательно рассматривать как адрес: ее можно использовать для целочисленных арифметических вычисле­ ний. Несколько примеров «экзотического» использования LEA будет при­ ведено в главе, посвященной оптимизации.

78

Глава 6. Прочие команды

6.4. Команды для работы со строками

Строкой в языке ассемблера называется последовательность символов (бай­ тов), заканчивающаяся нулевым байтом (точно так же, как в языке С).

0x43 0x6F 0x6D 0x70 0x75 0x74 0x65 0x72 0x00

C o m p u t e r / 0

Рис. 6.1. Строка заканчивается нулевым байтом

Система команд х86-совместимых процессоров содержит несколько команд, облегчающих манипуляции со строками. Каждую из них можно заменить не­ сколькими элементарными командами, но, как и в случае с командой LOOP, эти команды сделают вашу программу короче и понятнее.

В зависимости от размера операнда команды для обработки строк делятся на три группы. Первая группа предназначена для работы с 8-битными операн­ дами, то есть с отдельными символами. Имена этих команд заканчиваются на «В» (byte). Имена команд второй группы, предназначенных для работы с 16-битными операндами, заканчиваются символом «W» (word). Третья группа команд работает с 32-битными операндами, и имена их заканчиваются на «D» (double word).

Все команды, обсуждаемые в этом параграфе, работают с фиксированными операндами и не имеют аргументов. Порядок отсчета байтов в строке во время работы этих команд зависит от флага направления (DF).

Команды STOSx — запись строки в память

Под единым обозначением STOSx (STOre String) скрываются три команды:

•STOSB

•STOSW

• STOSD

Команда STOSB копирует содержимое регистра AL в ячейку памяти, адрес которой находится в паре регистров ES:(E)DI, и уменьшает или увеличивает (в зависимости от флага DF) на единицу значение регистра (E)DI, чтобы при­ готовиться к копированию AL в следующую ячейку. Если DF=0, то (E)DI будет увеличен на 1, в противном случае — уменьшен на 1. Какой регистр будет использоваться — DI или EDI — зависит от режима процессора.

Вторая инструкция, STOSW, работает аналогично, но данные берутся из регистра АХ, a (E)DI уменьшается/увеличивается на 2. STOSD копирует со­ держимое ЕАХ, a E(DI) уменьшает/увеличивает на 4.

79

Ассемблер на примерах. Базовый курс

e ld

;сбрасываем DF, направление будет вверх

stosw

;сохраняем АХ в ES:[DI] или ES:[EDI] (зависит от

 

;режима процессора) и увеличиваем (E)DI на 2

Команды LODSx — чтение строки из памяти

Под единым обозначением LODSx (LOaD String) скрываются три команды:

• LODSB •LODSW •LODSD

Действие этих команд противоположно командам предыдущей группы: они копируют порцию данных из памяти в регистр AL, АХ и ЕАХ. Адрес нужной ячейки памяти берется из пары DS:(E)SI. Если флаг DF равен нулю, то ре­ гистр SI будет увеличен на 1/2/4 (В, W, D), в противном случае — уменьшен на 1/2/4.

Команды CMPSx — сравнение строк

Под единым обозначением CMPSx (CoMPare String) скрываются три команды:

•CMPSB

•CMPSW

•CMPSD

Команда CMPSB сравнивает байт, находящийся по адресу ES:(E)DI, с байтом по адресу DS:(E)SI и изменяет значения регистров SI и DI в зависимости от флага DF. Команды CMPSB и CMPSD сравнивают не байты, а слова и двойные слова соответственно, увеличивая или уменьшая регистры SI и DI на размер порции данных (2 или 4).

Команды SCASx — поиск символа в строке

Под единым обозначением SCASx (SCAn String) скрываются три команды:

•SCASB •SCASW

•SCASD

Команды SCASB/W/D сравнивают значения регистра AL/AX/EAX со значе­ нием в памяти по адресу [ES:(E)DI]. Регистр (E)DI изменяется в зависимости от флага DF.

Команды REP и REPZ — повторение следующей команды

Команда REP (Repeat) облегчает обработку строк произвольной длины. Это так называемая префиксная команда: ее нельзя использовать отдельно,

80

Глава 6. Прочие команды

а только в паре с какой-нибудь другой командой. Она работает подобно команде LOOP: повторяет следующую за ней команду до тех пор, пока зна­ чение в регистре (Е)СХ не станет равно нулю. Регистр (Е)СХ уменьшается на единицу при каждой итерации. Чаще всего команда REP применяется в паре с MOVS или STOS:

r e p movsb

Или: r e p

s t o s b

с к о п и р о в а т ь первые (Е)СХ б а й т о в из

D S : ( E ) S I в

E S : ( E ) D I . Это ассемблерный а н а л о г

 

С-функции memcpy()

 

с к о п и р о в а т ь (Е)СХ

р а з з н а ч е н и е AL в

E S : ( E ) D I .

:Это ассемблерный

а н а л о г С-функции m e m s e t ( )

Команда REPZ (синоним REPE), подобно команде LOOPZ, позволяет уточнить условие. Следующая итерация выполняется тогда, когда не только (Е)СХ не равен нулю, но и флаг ZF не установлен. Второе условие может быть инвертировано командой REPNZ (синоним REPNE).

Эти команды часто используются вместе с SCAS или CMPS:

r e p z s c a s b Или: r e p z cmpsb

; п о в т о р я е м SCASB

; п о в т о р я е м CMPSB

Теперь вы уже знаете достаточно, чтобы реализовать на языке ассемблера функцию подсчета символов в строке, которая в языке С называется strlenQ.

Рис. 6.2. Блок-схема функции strlen()

Ассемблер на примерах. Базовый курс

Функции нужен только один аргумент — адрес начала строки, который нужно сохранить в ES:(E)DI. Результат функции (количество символов строки + нулевой символ) будет записан в ЕСХ. Указатель ES:(E)DI будет указывать на байт, следующий за последним (нулевым) символом строки. Код функции приведен в листинге 6.1.

Листинг 6.1. Код функции strlen()

strlen: push eax xor ecx,ecx

xor eax,eax dec ecx

eld

repne scasb neg ecx

pop eax ret

помещаем регистр EAX в стек

сбрасываем ЕСХ (ЕСХ=0), то же самое можно сделать так: mov ecx,О

EAX = 0

ЕСХ = ЕСХ - 1. Получаем OxFFFFFFFF - максимальная Длина строки

DF = 0, будем двигаться вперед, а не назад ищем нулевой байт отрицание ЕСХ (дополнительный код)

даст количество выполненных итераций восстанавливаем исходный ЕАХ выходим из подпрограммы

Рис. 6.3. Состояние регистров ES:(E)DI перед вызовом strlen и после ее выполнения

Вы можете легко адаптировать программу для работы только с 16-битными регистрами: просто удалите «Е» в имени каждого регистра.

Мы уже написали функцию для подсчета символов в строке, но до сих пор не знаем, как разместить строку в памяти. Пока будем считать, что в ES:(E)DI уже загружен правильный адрес начала строки. Мы вызываем нашу подпро­ грамму с помощью CALL, а после ее выполнения в регистре ЕСХ получим количество символов в строке.

call strlen

;вызываем strlen

82

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