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

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

.pdf
Скачиваний:
268
Добавлен:
14.03.2016
Размер:
6.05 Mб
Скачать

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

Команды ROR и ROL — ротация с выносом во флаг переноса

Эти команды выполняют другой вариант циклического сдвига: ROR сначала копирует младший бит первого операнда в его старший бит, а потом заносит его в CF; ROL работает в обратном направлении.

ROR о 1 , о2

ROL о 1 , о2

Операнды этих команд аналогичны операндам команд RCR и RCL.

^TTTW

ROR SF

Рис, 6.11, Как работает ROR

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

Некоторые из команд, рассмотренных нами, могут работать с операндом, рас­ положенным в памяти. Классический пример — команда MOV АХ, [number], загружающая в регистр АХ значение из области памяти, адрес которой пред­ ставлен символическим обозначением «number». Но мы до сих пор не знаем, как связать символическое обозначение и реальный адрес в памяти. Как раз для этого и служат псевдокоманды.

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

Псевдокоманды DB, DW и DD — определение констант

Чаще всего используется псевдокоманда DB (define byte), позволяюш^ая определить числовые константы и строки. Рассмотрим несколько примеров:

db

0x55

один байт в шестнадцатеричном виде

db

0x55,0x56,0x57

три последовательных байта: 0x55,

db

'а',0x55

0x56,

0x57

можно записать символ в одинарных

 

 

кавычках.

db 'Hello',13,10,'$'

Получится последовательность 0x61, 0x55

можно

записать целую строку.

Получится 0x48, 0x65, ОхбС, ОхбС, 0x6F, OxD, ОхА, 0x24

90

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

Для определения порции данных размера, кратного слову, служит директива DW (define word):

dw

0x1234

;0х34, 0x12

dw

'а.'

;0хб1, 0x00: второй байт заполняется нулями

Директива DD (define double word) задает значение порции данных размера, кратного двойному слову:

dd 0x12345678 ;0х78 0x56 0x34 0x12

dd 1.2 3 45 67е2 0 ; так определяются числа с плавающей точкой

А вот так определяется переменная, тот самый «number»:

number dd 0x1 ;переменная number инициализирована ;значением 1

Переменная «number» теперь представляет адрес в памяти, по которому за­ писано значение 0x00000001 длиной в двойное слово.

Псевдокоманды RESB, R E S W M R E S D — объявление переменных

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

Для резервирования памяти служат три директивы: RESB (резервирует байт), RESW (резервирует слово) и RESD (резервирует двойное слово). Аргументом этих псевдокоманд является количество резервируемых позиций:

resb 1

 

 

;резервирует

1 байт

resb 2

 

 

;резервирует

2

байта

resw 2

 

 

;резервирует

4

байта (2 слова)

resd 1

 

 

;резервирует

4

байта

number

resd

1

;резервирует

4

байта для переменной

buffer

resb

64

'number''

 

 

;резервирует

64 байта для

 

 

 

;переменной

buffer

Некоторые ассемблеры поддерживают дополнительные директивы выделения памяти, но мы будем использовать ассемблер NASM, который их не поддер­ живает. Поэтому мы ограничимся директивами RESx.

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

Директива TIMES — это псевдокоманда префиксного типа, то есть она ис­ пользуется только в паре с другой командой. Она повторяет последующую

91

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

псевдокоманду указанное количество раз, подобно директиве DUP из ассем­ блера Borland TASM. Применяется эта директива в тех случаях, когда нужно «забить» некоторую область памяти повторяющимся образцом.

Следующий код определяет строку, состоящую из 64 повторяющихся «Hello»: many_hello: times 64 db 'Hello'

Первый аргумент, указывающий количество повторений, может быть и вы­ ражением. Например, задачу «разместить строку в области памяти размером в 32 байта и заполнить пробелами оставшееся место» легко решить с помощью директивы TIMES:

b u f f e r

db "Hello"

;определяем

строку

times

3 2 - ( $ - b u f f e r ) db ' '

;определяем

нужное

 

 

/количество

пробелов

Выражение 32-($-buffer) возвратит значение 27, потому что $-buffer равно текущей позиции минус позиция начала строки, то есть 5.

Вместе с TIMES можно использовать не только псевдокоманды, но и команды процессора:

times 5 inc еах

;5 раз выполнить INC ЕАХ

В результате будет сгенерирован код:

inc еах inc еах inc еах inc еах inc еах

Псевдокоманда INCBIN — подключение двоичного файла

Эта директива будет полезна более опытным разработчикам. Она упаковывает графические или звуковые данные вместе с исполняемым файлом:

i n c b i n "sound.wav''

;упаковываем весь

файл

 

i n c b i n

''sound .wav" , 512

;пропускаем

первые

512

байтов

i n c b i n

"sound .wav'\ 512,80

;пропускаем

первые

512

байтов и

 

 

;последние

80

 

 

Псевдокоманда EQU — вычисление константных выражений

Эта директива определяет константу, известную во время компиляции. В ка­ честве значения константы можно указывать также константное выражение. Директиве EQU должно предшествовать символическое имя:

four EQU 4 /тривиальный пример.

;Позже я покажу и нетривиальные

92

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

Оператор SEG — смена сегмента

При создании больших программ для реального режима процессора нам нужно использовать для кода и данных несколько сегментов, чтобы обойти проблему 16-битной адресации. Пока будем считать, что сегмент — это часть адреса переменной.

С помощью оператора SEG в сегментный регистр может быть загружен адрес сегмента, где физически расположена переменная:

mov

a x , s e g counter

;поместить

в АХ адрес

сегмента,

где

 

 

;размещена

переменная

c o u n t e r

 

mov

e s , a x

/поместить

этот адрес

в сегментный

 

 

;регистр .

 

 

 

 

 

;это можно сделать только косвенно

mov

b x , c o u n t e r

;загрузить

в ВХ адрес

(смещение)

 

 

 

;переменной

c o u n t e r .

Теперь пара

ES:ВХ

 

 

;содержит полный адрес переменной c o u n t e r

mov

c x , e s : [ b x ]

/копировать

значение

переменной

 

 

 

;в регистр

СХ

 

 

В наших учебных программах на сегментные регистры можно не обращать внимания, потому что они содержат одно и то же значение. Поэтому оператор SEG нам пока не понадобится.

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

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

Сегодня, когда компьютеры очень быстры, а память стоит очень дешево, почти никто не считается ни со скоростью работы программы, ни с объемом занимаемой памяти. Но язык ассемблера часто применяется в особых ситуа­ циях — в «узких местах», где быстродействие или малый размер программы особенно важны. Удачным подбором команд можно существенно сократить размер программы или увеличить ее быстродействие — правда, при этом по­ страдает удобочитаемость. Ускорить работу программы иногда можно также путем правильного выделения памяти.

Директива ALIGN — выравнивание данных в памяти

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

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

93

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

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

К сожалению, каждый тип процессора предпочитает свой тип выравнивания. Мы можем сообщить компилятору требуемый тип директивой ALIGN. Ее аргумент — то число, по адресам, кратным которому, требуется размещать данные:

align 4 /размещает данные по адресам, кратным 4 align 16 /размещает данные по адресам, кратным 16

Загрузка значения в регистр

Много обращений к памяти можно исключить, если отказаться от непосред­ ственных операндов, что положительно скажется на быстродействии про­ граммы. Например, как мы привыкли инициализировать счетчик? Командой MOV, которая копирует в регистр значение О? Намного рациональнее для этого использовать логическую функцию XOR (если ее аргументы одинаковы, результат равен 0):

хог еах,еах

;в машинном коде 0хЗЗ,0хС0

Эта команда будет выполнена быстрее и займет меньше памяти, чем

mov еах,0

;в машинном коде 0хВ8,0,0,0,0

Но нужно помнить, что XOR изменяет регистр признаков, поэтому ее нужно использовать только в тех случаях, когда его изменение не имеет значения.

Другой часто используемой парой инструкций являются:

хог еах,еах

;ЕАХ = О

inc еах

;увеличиваем на 1

Этот фрагмент кода загружает в ЕАХ значение 1. Если использовать DEC вместо INC, в ЕАХ будет значение - 1 .

Оптимизируем арифметику

Если вам нужно добавить к значению константу, то имейте в виду, что не­ сколько команд INC будут выполнены быстрее, чем одна ADD. Таким образом, вместо команды

add

еах,4

;добавляем

4 к ЕАХ

следует использовать четыре команды:

inc

еах

; добавляем

1 к ЕАХ

inc

еах

 

 

inc

еах

 

 

inc

еах

 

 

94

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

Помните, что ни INC, ни DEC не устанавливают флаг переноса, поэтому не ис­ пользуйте их в 64-разрядной арифметике, где используется перенос. Зато при вы­ числении значения адреса вы можете смело использовать INC и DEC, поскольку арифметические операции над адресами никогда не требуют переноса.

Вы уже знаете, что для быстрого умножения и деления можно использовать поразрядные сдвиги влево и право. Умножение можно выполнять также с помощью команды LEA, которая вычисляет эффективный адрес второго операнда. Вот несколько примеров:

l e a

ebx Л ecx+Gdx'^4 + 0x500]

; загружаем в ЕВХ результат

 

 

;выражения ЕСХ

+ EDX*4 + 0x500

l e a

еЬхЛеах+еах*4~1]

; ЕВХ

= ЕАХ*5 -

1

l e a

ebx,[eax+eax*8]

; ЕВХ

= ЕАХ^-9

 

l e a

ecx,[eax+ebx]

;вычисляем ЕСХ = ЕАХ + ЕВХ

Операции сравнения

Очень часто нам нужно проверить какой-то регистр на наличие в нем 0. Со­ всем не обязательно использовать для этого СМР, можно использовать OR или TEST. Таким образом, вместо команд

стр еах,0

;ЕАХ равен О?

j z i s _ z e r o

;да? Переходим к i s _ z e r o

МОЖНО написать следующее:

or е а х , е а х

;этот

OR устанавливает тот

же

 

;флаг

(ZF), если

результат

равен О

j z i s _ z e r o

;да?

Переходим к

i s _ z e r o

 

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

Любая арифметическая команда устанавливает флаг нуля, поэтому совсем не обязательно использовать СМР для проверки на 0. Можно сразу исполь­ зовать jz:

dec

еах

j z

now_zero ;переход, если ЕАХ после декремента равен О

Иногда нужно узнать, содержит ли регистр отрицательное число. Для этого можно использовать команду TEST, которая вычисляет логическое И, но не сохраняет результат, а только устанавливает флаги регистра признаков. Флаг знака SF будет установлен в 1, если результат отрицательный, то есть если старший бит результата равен 1. Значит, мы можем выполнить TEST с двумя одинаковыми операндами: если число отрицательное, то флаг SF будет установлен (логическое И для двух отрицательных чисел даст 1 (1 AND 1 = 1) в старшем бите, то есть SF = 1):

95

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

t e s t еах,еах

; вызываем TEST для двух одинаковых

 

;операндов

js is_negative

;переходим, если SF=1

Разное

Обычно вместо нескольких простых команд проще использовать одну слож­ ную (например, LOOP или команды для манипуляций со строками). Очень часто такой подход и правильнее: сложные команды оптимизированы и могут выполнить ту же самую функцию быстрее, чем множество простых команд.

Размер исполняемого файла может быть уменьшен, если оптимизировать пере­ ходы. По умолчанию используется «средний шаг» — near, но если все цели находятся в пределах 128 байтов, используйте короткий тип перехода (short). Это позволит сэкономить один-три байта на каждом переходе.

96

Глава 7 Полезные фрагмен ты кода

простые примеры

Преобразование числа в строку

Преобразование строки в число

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

7.1. Простые примеры

Начнем с нескольких простых примеров, которые помогут уменьшить «про­ пасть» между языком высокого уровня и ассемблером.

Сложение двух переменных

Задача: сложить два 32-разрядных числа, содержащихся в переменных numberl и number2, а результат сохранить в переменной r e s u l t .

Сначала нужно выбрать регистры, куда будут загружены нужные значения. Затем нужно сложить эти регистры, а результат записать в переменную r e ­ sult:

mov

еах,[number1]

квадратные скобки означают доступ к памяти

mov

ebx,[number2]

ЕВХ = number2

 

add eax,ebx

ЕАХ - ЕАХ + ЕВХ

 

mov

[result],eax

сохраняем результат в переменной result

number1 dd 8

;определяем переменную

numberl и

number2 dd 2

/инициализируем 8

 

;переменную number2 инициализируем

result dd 0

;значением 2

 

;определяем переменную

r e s u l t

Мы можем переписать программу, используя «на подхвате» регистр ЕАХ:

mov

eax,[numberl]

; ЕАХ = ''numberl"

add

eax,[number2]

;EAX - EAX

+ number2

mov

[ r e s u l t ] , e a x

;сохраняем

результат в переменной r e s u l t

98

Глава 7. Полезные фрагменты кода

Сложение двух элементов массива

Задача: сложить два 32-битных числа. Регистр EDI содержит указатель на первое слагаемое, второе слагаемое расположено непосредственно за первым. Результат записать в EDX.

Фактически мы имеем массив из двух 32-битных чисел, адрес которого задан указателем в регистре EDI. Каждый элемент массива занимает 4 байта, сле­ довательно, второй элемент расположен по адресу, на 4 байта большему.

mov

e d x , [ e d i ]

add

edx,[edi+4"

/загружаем в EDX первый элемент массива /складываем его со вторым, результат — в EDX

Дополним программу загрузкой регистра EDI:

mov

edi,numbers

/загружаем в EDI адрес массива numbers

. . .

 

/какие - то

команды

mov

e d x , [ e d i ]

/загружаем

в

EDX первый элемент массива

add

e d x , [ e d i + 4 ]

/прибавляем

второй элемент

numbers dd 1

/массив numbers инициализируется

 

 

/значениями 1 и 2, поэтому результат

dd 2

 

/в EDX будет равен 3

 

/У второго элемента массива нет особого

 

 

/имени

 

 

В следующем пункте мы расскажем, как работать с массивами в цикле.

Суммируем элементы массива

Задача: вычислить сумму массива 8-разрядных чисел, если в ESI загружен адрес первого элемента массива. Массив заканчивается нулевым байтом^

Сумма 8-разрядных чисел может оказаться числом большей разрядности, по­ этому на всякий случай отведем для хранения результата 32-битный регистр. Элементы массива будут суммироваться до тех пор, пока не будет встречен нулевой байт.

mov

esi,array

/загружаем в ESI начало массива

mov

ebx,0

/EBX = О

mov

eax,ebx

/EAX = О

again:

/загружаем в AL элемент массива

mov al,[esi]

inc

esi

/перемещаем указатель на след. элемент

add ebx,eax

/ЕВХ = ЕВХ + ЕАХ

cmp a1,0

/AL равен нулю?

jnz again

/переходим к again, если AL не О

array db 1,2,3,4,

,6,7,8,0 /инициализируем массив.

 

 

/Сумма (ЕВХ) должна быть равна 3 6

99

Соседние файлы в папке Новая папка_2